XPath pour sélectionner plusieurs balises

132

Compte tenu de ce format de données simplifié:

<a>
    <b>
        <c>C1</c>
        <d>D1</d>
        <e>E1</e>
        <f>don't select this one</f>
    </b>
    <b>
        <c>C2</c>
        <d>D2</d>
        <e>E1</e>
        <g>don't select me</g>
    </b>
    <c>not this one</c>
    <d>nor this one</d>
    <e>definitely not this one</e>
</a>

Comment sélectionneriez-vous tous les Cs, Ds et Es qui sont des enfants d' Béléments?

En gros, quelque chose comme:

a/b/(c|d|e)

Dans ma propre situation, au lieu de simplement a/b/, la requête menant à la sélection ceux C, D, des Enoeuds est je serais en fait assez complexe pour éviter de faire comme ceci:

a/b/c|a/b/d|a/b/e

Est-ce possible?

nickf
la source

Réponses:

207

Une bonne réponse est :

/a/b/*[self::c or self::d or self::e]

Notez que ceci

a/b/*[local-name()='c' or local-name()='d' or local-name()='e']

est à la fois trop long et incorrect . Cette expression XPath sélectionnera des nœuds comme:

OhMy:c

NotWanted:d 

QuiteDifferent:e
Dimitre Novatchev
la source
2
'ou' ne fonctionne pas sur un for-each, vous devrez utiliser une ligne verticale à la place '|'
Guasqueño
8
@ Guasqueño, orest un opérateur logique - il opère sur deux valeurs booléennes. L' opérateur d' union XPath |fonctionne sur deux ensembles de nœuds. Ceux-ci sont assez différents et il existe des cas d'utilisation spécifiques pour chacun d'eux. L'utilisation | peut résoudre le problème d'origine, mais il en résulte une expression XPath plus longue et plus complexe et difficile à comprendre. L'expression plus simple de cette réponse, qui utilise l' oropérateur, produit l'ensemble de nœuds voulu et peut être spécifiée dans l'attribut "select" d'une <xsl:for-each>opération XSLT. Essayez-le.
Dimitre Novatchev
4
@JonathanBenn, Quiconque "ne se soucie pas des espaces de noms" ne se soucie pas du XML et n'utilise pas XML. L'utilisation de local-name()n'est correcte que si nous voulons sélectionner tous les éléments avec ce nom local, quel que soit l'espace de nom dans lequel se trouve l'élément. C'est un cas très rare - en général, les gens se soucient des différences entre: kitchen:tableet sql:table, ou entre architecture:column, sql:column, array:column,military:column
Dimitre Novatchev
3
@DimitreNovatchev vous faites un bon point. J'utilise XPath pour l'inspection HTML, qui est un cas limite où l'espace de noms n'est pas si important ...
Jonathan Benn
2
C'est super. Où avez-vous trouvé ça?
Keith Tyler
46

Vous pouvez éviter la répétition avec un test d'attribut à la place:

a/b/*[local-name()='c' or local-name()='d' or local-name()='e']

Contrairement à l'opinion antagoniste de Dimitre, ce qui précède n'est pas incorrect dans le vide où l'OP n'a pas spécifié l'interaction avec les espaces de noms. L' self::axe est restrictif pour l'espace de noms, local-name()n'est pas. Si l'intention du PO est de capturer c|d|eindépendamment de l'espace de noms (ce que je suggérerais est même un scénario probable étant donné la nature OU du problème), alors c'est "une autre réponse qui a encore des votes positifs" qui est incorrecte.

Vous ne pouvez pas être définitif sans définition, même si je suis tout à fait heureux de supprimer ma réponse comme étant véritablement incorrecte si le PO clarifie sa question de telle sorte que je suis incorrecte.

Annakata
la source
3
Parlant en tant que tiers ici - personnellement, je trouve que la suggestion de Dimitre est la meilleure pratique, sauf dans les cas où l'utilisateur a des raisons explicites (et bonnes) de se soucier du nom de la balise sans rapport avec l'espace de noms; si quelqu'un faisait cela contre un document que je mélangeais dans un contenu à espacement de noms différent (probablement destiné à être lu par une chaîne d'outils différente), je considérerais leur comportement très inapproprié. Cela dit, l'argument est - comme vous le suggérez - un peu inconvenant.
Charles Duffy
4
exactement ce que je cherchais. Les espaces de noms XML tels qu'ils sont utilisés dans la vie réelle sont un gâchis impie. Faute de pouvoir spécifier quelque chose comme / a / b / ( : c | : d | * e), votre solution est exactement ce dont vous avez besoin. Les puristes peuvent argumenter tout ce qu'ils veulent, mais les utilisateurs ne se soucient pas que l'application se casse parce que tout ce qui a généré leur fichier d'entrée a gâché les espaces de noms. Ils veulent juste que ça marche.
Ghostrider
7
Je n'ai qu'une idée très vague de la différence entre ces deux réponses et personne n'a pris la peine de l'expliquer. Que signifie «restrictif d'espace de noms»? Si j'utilise local-name(), cela signifie-t-il qu'il correspondrait aux balises avec n'importe quel espace de noms? Si j'utilise self::, quel espace de noms devrait-il correspondre? Comment pourrais-je correspondre uniquement OhMy:c?
meustrus
15

Pourquoi pas a/b/(c|d|e)? Je viens d'essayer avec la bibliothèque XML Saxon (bien enveloppée avec un peu de bonté Clojure), et cela semble fonctionner. abc.xmlest le doc décrit par OP.

(require '[saxon :as xml])
(def abc-doc (xml/compile-xml (slurp "abc.xml")))
(xml/query "a/b/(c|d|e)" abc-doc)
=> (#<XdmNode <c>C1</c>>
    #<XdmNode <d>D1</d>>
    #<XdmNode <e>E1</e>>
    #<XdmNode <c>C2</c>>
    #<XdmNode <d>D2</d>>
    #<XdmNode <e>E1</e>>)
Pavel Repin
la source
8
Oui, mais c'est XPath 2.0
Cela a bien fonctionné pour moi. Il semble que XPath 2.0 soit la valeur par défaut pour l'analyse HTML dans lxml sur Python 2.
Martin Burch
-1

Je ne sais pas si cela aide, mais avec XSL, je ferais quelque chose comme:

<xsl:for-each select="a/b">
    <xsl:value-of select="c"/>
    <xsl:value-of select="d"/>
    <xsl:value-of select="e"/>
</xsl:for-each>

et ce XPath ne sélectionnera-t-il pas tous les enfants des nœuds B:

a/b/*
Calvin
la source
Merci Calvin, mais je n'utilise pas XSL, et il y a en fait plus d'éléments sous B que je ne veux pas sélectionner. Je vais mettre à jour mon exemple pour être plus clair.
nickf
Oh, eh bien dans ce cas, annakata semble avoir la solution.
Calvin