Sélection d'une classe css avec xpath

87

Je souhaite sélectionner uniquement une classe appelée .date

Pour une raison quelconque, je ne peux pas faire fonctionner cela. Si quelqu'un sait ce qui ne va pas avec mon code, ce serait très apprécié.

@$doc = new DOMDocument();
@$doc->loadHTML($html);
$xml = simplexml_import_dom($doc); // just to make xpath more simple
$images = $xml->xpath('//[@class="date"]');                             
foreach ($images as $img)
{
    echo  $img." ";
}
Teddy13
la source
2
et qu'en est-il du morceau de HTML? (Je préfère nous montrer la sortie simpleXml de asXML () car elle est plus proche de xpath)
SergeS
s'il y a plusieurs cours à fairecontains(@class, 'date')
Gordon
La réponse de @ Gordon est dangereuse, si l'attribut de classe est "datetime", il correspondra également. La réponse de user716736 est plus complète.
Niels Bom

Réponses:

242

Je veux écrire la réponse canonique à cette question car la réponse ci-dessus a un problème.

Notre problème

Le sélecteur CSS :

.foo

sélectionnera tout élément ayant la classe foo .

Comment faites-vous cela dans XPath?

Bien que XPath soit plus puissant que CSS, XPath n'a pas d'équivalent natif d'un sélecteur de classe CSS . Cependant, il existe une solution.

La bonne façon de le faire

Le sélecteur équivalent dans XPath est:

//*[contains(concat(" ", normalize-space(@class), " "), " foo ")]

La fonction normalize-space supprime les espaces de début et de fin (et remplace également les séquences de caractères d'espaces par un seul espace).

(Dans un sens plus général) c'est aussi l'équivalent du sélecteur CSS:

*[class~="foo"]

qui correspondra à tout élément dont la valeur d'attribut de classe est une liste de valeurs séparées par des espaces, dont l'une est exactement égale à foo .

Quelques façons évidentes mais incorrectes de le faire

Le sélecteur XPath:

//*[@class="foo"]

ne fonctionne pas! car il ne correspondra pas à un élément qui a plus d'une classe, par exemple

<div class="foo bar">

Il ne correspondra pas non plus s'il y a un espace supplémentaire autour du nom de la classe:

<div class="  foo ">

Le sélecteur XPath `` amélioré ''

//*[contains(@class, "foo")]

ne fonctionne pas non plus! car il correspond à tort des éléments avec la classe foobar , par exemple

<div class="foobar">

Le mérite revient à ce type, qui était la première solution publiée à ce problème que j'ai trouvée sur le Web: http://dubinko.info/blog/2007/10/01/simple-parsing-of-space-seprated-attributes- in-xpathxslt /

user716736
la source
Quel est le besoin de normaliser l'espace?
Freek
"la réponse ci-dessus" fait probablement référence à MrGlass.
LarsH
Est-ce possible <div class="foo\tbar">? Je veux dire, les noms de classe séparés par une tabulation.
Frozen Flame
1
mais <div class = "group-conditions" /> et <div class = "condition" /> est le même pour $ x ('// div [contains (concat ("", normalize-space (@class), " ")," condition ")] ')
Memke
1
@ testerjoe2 avez-vous essayé //*[contains(concat(" ", normalize-space(@class), " "), " foo ")]?
Niels Bom
11

//[@class="date"] n'est pas un xpath valide.

Essayez //*[@class="date"], ou si vous savez que c'est une image,//img[@class="date"]

MrGlass
la source
7

XPath 3.1 introduit une fonction contains-token et résout ainsi finalement cela «officiellement». Il est conçu pour prendre en charge les classes .

Exemple:

//*[contains-token(@class, "foo")]

Cette fonction s'assure que l'espace blanc (pas seulement (U + 0020)) est géré correctement, fonctionne en cas de répétition du nom de classe et couvre généralement les cas de bord.


Remarque: À partir d'aujourd'hui (13/12/2016) XPath 3.1 a le statut de recommandation candidate .

Robin Pokorny
la source
Cela ne fonctionne pas dans le dernier chrome d'aujourd'hui. Jusqu'à ce que cela fonctionne, comment contourner la limitation selon laquelle // * [contains (@class, "foo")] sélectionnera également toute classe contenant foo, telle que foobar, fooz etc.
MasterJoe
1

HTML autorise les noms d'éléments et d'attributs insensibles à la casse, puis la classe est une liste de noms de classe séparés par des espaces. Ici, nous allons pour une imgbalise et le classnommé date:

//*['IMG' = translate(name(.), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')]/@*['CLASS' = translate(name(.), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') and contains(concat(' ', normalize-space(.), ' '), concat(' ', 'date', ' '))]

Voir aussi: Conversion du sélecteur CSS en XPath

hakre
la source
1

Méfiez-vous des signes moins dans le modèle !!! Si vous recherchez "ma-propre classe" dans DOM:

<ul class="my-ownclass"><li>...</li></ul>
<ul class="someother"><li>...</li></ul>
<ul><li>...</li></ul>

$finder = new DomXPath($dom);
$nodes = $finder->query(".//ul[contains(@class, 'my-ownclass')]"); // This will NOT behave as expected! This will strangely match all the <ul> elements in DOM.
$nodes = $finder->query(".//ul[contains(@class, 'ownclass')]"); // This will match the element.
Vlado
la source