web-dev-qa-db-fra.com

Existe-t-il un meilleur moyen d'obtenir le nœud parent du résultat de la requête XPath?

Ayant un balisage comme celui-ci:

<div class="foo">
   <div><span class="a1"></span><a href="...">...</a></div>
   <div><span class="a2"></span><a href="...">...</a></div>
   <div><span class="a1"></span>some text</div>
   <div><span class="a3"></span>some text</div>
</div>

Je souhaite obtenir tous les <a> Et some text [~ # ~] seulement [~ # ~] s'ils sont adjacents span est de classe a1. Donc, à la fin de tout le code, mon résultat devrait être <a> Du premier div et some text Du troisième. Ce serait facile si <a> Et some text Étaient à l'intérieur de span ou div auraient l'attribut class, mais pas de chance.

Ce que je fais maintenant, c'est rechercher span avec la classe a1:

//div[contains(@class,'foo')]/div/span[contains(@class,'a1')]

puis j'obtiens son parent et fais une autre query() avec ce parent comme nœud de contexte. Cela semble tout simplement loin d'être efficace, la question est donc clairement de savoir s'il existe un meilleur moyen d'atteindre mon objectif.


L'ADDENDUM À LA RÉPONSE

Selon @MarcB réponse acceptée , la bonne requête à utiliser est:

//div[contains(@class,'foo')]/div/span[contains(@class,'a1')]/..

mais pour <a> il peut être préférable d'utiliser:

//div[contains(@class,'foo')]/div/span[contains(@class,'a1')]/../a

récupérez le <a> au lieu de son conteneur.

25
Marcin Orlowski

La bonne chose à propos des requêtes xpath est que vous pouvez essentiellement les traiter comme un chemin d'accès au système de fichiers, donc simplement avoir

//div[contains(@class,'foo')]/div/span[contains(@class,'a1')]/..
                                                              ^^

trouvera tous vos nœuds .a1 qui sont en dessous d'un nœud .foo, puis montera d'un niveau vers les parents des nœuds a1.

55
Marc B

ne expression qui est meilleure qu'en utilisant l'axe inverse:

//div[contains(@class,'foo')]/div[span[contains(@class,'a1')]]

Cela sélectionne tout div qui est un enfant d'un div dont l'attribut class contient la chaîne "foo" et qui (le div sélectionné) a un span enfant dont l'attribut class contient la chaîne "a1".

vérification basée sur XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:copy-of select=
  "//div[contains(@class,'foo')]
          /div[span[contains(@class,'a1')]]"/>
 </xsl:template>
</xsl:stylesheet>

Lorsque cette transformation est appliquée sur le document XML fourni:

<div class="foo">
   <div><span class="a1"></span><a href="...">...</a></div>
   <div><span class="a2"></span><a href="...">...</a></div>
   <div><span class="a1"></span>some text</div>
   <div><span class="a3"></span>some text</div>
</div>

l'expression XPath est évaluée et les éléments sélectionnés sont copiés dans la sortie:

<div>
   <span class="a1"/>
   <a href="...">...</a>
</div>
<div>
   <span class="a1"/>some text</div>

II. Remarques sur l'accès à un élément Html par l'une de ses classes:

S'il est connu que l'élément ne peut avoir qu'une seule classe, alors il n'est pas du tout nécessaire d'utiliser contains()

Ne pas utiliser:

//div[contains(@class, 'foo')]

tiliser:

//div[@class = 'foo']

ou, s'il peut y avoir des espaces de début/fin, utilisez:

//div[normalize-space(@class) = 'foo']

n problème crucial avec:

//div[contains(@class, 'foo')]

est que cela sélectionne tout div avec une classe telle que "myfoo", "foo2" ou "myfoo3".

Si l'élément peut avoir plusieurs classes et pour éviter le problème ci-dessus, l'expression XPath correcte est:

//div[contains(concat(' ', @class, ' '), ' foo ')]
16
Dimitre Novatchev