XPath, um mehrere Tags auszuwählen

132

Angesichts dieses vereinfachten Datenformats:

<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>

Wie würden Sie alle Cs, Ds und Es auswählen, die untergeordnete BElemente von Elementen sind?

Grundsätzlich so etwas wie:

a/b/(c|d|e)

In meiner eigenen Situation, anstatt nur a/b/die Abfrage der Auswahl diejenigen führt C, D, EKnoten ist eigentlich recht komplex , so würde Ich mag , dies zu vermeiden tun:

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

Ist das möglich?

nickf
quelle

Antworten:

207

Eine richtige Antwort lautet :

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

Beachten Sie, dass dies

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

ist sowohl zu lang als auch falsch . Dieser XPath-Ausdruck wählt Knoten aus wie:

OhMy:c

NotWanted:d 

QuiteDifferent:e
Dimitre Novatchev
quelle
2
'oder' funktioniert nicht für jedes, Sie müssten stattdessen eine vertikale Linie verwenden '|'
Guasqueño
8
@ Guasqueño orist ein logischer Operator - er arbeitet mit zwei Booleschen Werten. Der XPath- Union- Operator |arbeitet mit zwei Knotensätzen. Diese sind sehr unterschiedlich und es gibt spezifische Anwendungsfälle für jeden von ihnen. Die Verwendung | kann das ursprüngliche Problem lösen, führt jedoch zu einem längeren und komplexeren und schwieriger zu verstehenden XPath-Ausdruck. Der einfachere Ausdruck in dieser Antwort, der den orOperator verwendet, erzeugt den gewünschten Knotensatz und kann im Attribut "select" einer <xsl:for-each>XSLT-Operation angegeben werden. Probier es einfach.
Dimitre Novatchev
4
@ JonathanBenn, Wer sich "nicht um Namespaces kümmert", kümmert sich eigentlich nicht um XML und verwendet kein XML. Die Verwendung local-name()ist nur dann richtig , wenn wir alle Elemente mit dem lokalen Namen, unabhängig von dem Namensraum des Element in ist auswählen möchten Dies ist ein sehr seltener Fall - in der Regel Menschen tut Pflege über die Unterschiede zwischen:. kitchen:tableUnd sql:tableoder zwischen architecture:column, sql:column, array:column,military:column
Dimitre Novatchev
2
@DimitreNovatchev Sie machen einen guten Punkt. Ich verwende XPath für die HTML-Überprüfung, was ein Randfall ist, in dem der Namespace nicht so wichtig ist ...
Jonathan Benn
2
Das ist super Wo hast du das gefunden?
Keith Tyler
46

Sie können die Wiederholung stattdessen mit einem Attributtest vermeiden:

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

Im Gegensatz zu Dimitres antagonistischer Meinung ist das oben Gesagte in einem Vakuum, in dem das OP die Interaktion mit Namespaces nicht spezifiziert hat, nicht falsch . Die self::Achse ist namespace-restriktiv, local-name()nicht. Wenn das OP beabsichtigt, c|d|eunabhängig vom Namespace zu erfassen (was angesichts der ODER-Natur des Problems sogar ein wahrscheinliches Szenario ist), ist es "eine andere Antwort, die noch einige positive Stimmen hat", die falsch ist.

Sie können ohne Definition nicht endgültig sein, obwohl ich meine Antwort gerne als wirklich falsch lösche, wenn das OP seine Frage so klärt, dass ich falsch bin.

Annakata
quelle
3
Ich spreche hier als Dritter - persönlich finde ich, dass Dimitres Vorschlag die bessere Vorgehensweise ist, außer in Fällen, in denen der Benutzer explizite (und gute) Gründe hat, sich um den für den Namespace irrelevanten Tag-Namen zu kümmern. Wenn jemand dies gegen ein Dokument tun würde, das ich in Inhalte mit unterschiedlichen Namen einmische (vermutlich dazu gedacht, von einer anderen Toolchain gelesen zu werden), würde ich sein Verhalten als sehr unangemessen betrachten. Das Argument ist jedoch - wie Sie meinen - etwas unpassend.
Charles Duffy
4
genau das, wonach ich gesucht habe. XML-Namespaces, wie sie im wirklichen Leben verwendet werden, sind ein unheiliges Durcheinander. Da Sie nicht in der Lage sind, etwas wie / a / b / ( : c | : d | * e) anzugeben, ist Ihre Lösung genau das, was benötigt wird. Puristen können argumentieren, was sie wollen, aber den Benutzern ist es egal, dass die App kaputt geht, weil alles, was ihre Eingabedatei generiert hat, die Namespaces durcheinander gebracht hat. Sie wollen nur, dass es funktioniert.
Ghostrider
7
Ich habe nur die vage Vorstellung, was der Unterschied zwischen diesen beiden Antworten sein würde, und niemand hat sich die Mühe gemacht, dies zu erklären. Was bedeutet "Namespace restriktiv"? Wenn ich benutze local-name(), bedeutet das, dass Tags mit einem beliebigen Namespace übereinstimmen würden? self::Welchen Namespace müsste ich verwenden , wenn ich ihn verwende ? Wie würde ich nur zusammenpassen OhMy:c?
Meustrus
15

Warum nicht a/b/(c|d|e)? Ich habe es gerade mit der sächsischen XML-Bibliothek versucht (gut verpackt mit etwas Clojure-Güte), und es scheint zu funktionieren. abc.xmlist das von OP beschriebene Dokument.

(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
quelle
8
Ja, aber das ist XPath 2.0
Das hat bei mir gut funktioniert. Es scheint, dass XPath 2.0 die Standardeinstellung für das HTML-Parsen in lxml unter Python 2 ist.
Martin Burch
-1

Ich bin mir nicht sicher, ob dies hilft, aber mit XSL würde ich Folgendes tun:

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

und wird dieser XPath nicht alle Kinder von B-Knoten auswählen:

a/b/*
Calvin
quelle
Danke Calvin, aber ich benutze kein XSL und es gibt tatsächlich mehr Elemente unter B, die ich nicht auswählen möchte. Ich werde mein Beispiel aktualisieren, um es klarer zu machen.
Nickf
Na ja, in diesem Fall scheint Annakata die Lösung zu haben.
Calvin