XPath, aby wybrać wiele tagów

132

Biorąc pod uwagę ten uproszczony format danych:

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

Jak wybrałbyś wszystkie Cs, Ds i Es, które są dziećmi Belementów?

Zasadniczo coś takiego:

a/b/(c|d|e)

W moim sytuacji, zamiast po prostu a/b/, zapytanie prowadzące do wyboru tych C, D, Ewęzły jest rzeczywiście dość skomplikowane, więc chciałbym, aby uniknąć w ten sposób:

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

czy to możliwe?

nickf
źródło

Odpowiedzi:

207

Jedną poprawną odpowiedzią jest :

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

Zauważ, że to

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

jest za długi i niepoprawny . To wyrażenie XPath wybierze węzły, takie jak:

OhMy:c

NotWanted:d 

QuiteDifferent:e
Dimitre Novatchev
źródło
2
„lub” nie działa w przypadku for-each, zamiast tego należy użyć pionowej linii „|”
Guasqueño
8
@ Guasqueño, orjest operatorem logicznym - operuje na dwóch wartościach boolowskich. Operator unii XPath |działa na dwóch zestawach węzłów. Są one zupełnie inne i dla każdego z nich istnieją specyficzne przypadki użycia. Użycie | może rozwiązać pierwotny problem, ale skutkuje dłuższym i bardziej złożonym oraz trudniejszym do zrozumienia wyrażeniem XPath. Prostsze wyrażenie w tej odpowiedzi, które używa oroperatora, tworzy żądany zestaw węzłów i może być określone w atrybucie „select” <xsl:for-each>operacji XSLT. Po prostu spróbuj.
Dimitre Novatchev
4
@JonathanBenn, Każdy, kto „nie dba o przestrzenie nazw”, w rzeczywistości nie dba o XML i nie używa XML. Użycie opcji local-name()jest poprawne tylko wtedy, gdy chcemy wybrać wszystkie elementy o tej lokalnej nazwie, niezależnie od przestrzeni nazw, w której dany element się znajduje. Jest to bardzo rzadki przypadek - na ogół ludzie przejmują się różnicami między: kitchen:tablei sql:tablelub między architecture:column, sql:column, array:column,military:column
Dimitre Novatchev
2
@DimitreNovatchev masz rację. Używam XPath do inspekcji HTML, co jest przypadkiem skrajnym, w którym przestrzeń nazw nie jest tak ważna ...
Jonathan Benn
2
To jest super. Gdzie to wymyśliłeś?
Keith Tyler
46

Zamiast tego możesz uniknąć powtórzeń za pomocą testu atrybutów:

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

W przeciwieństwie do antagonistycznej opinii Dimitre, powyższe nie jest błędne w próżni, w której OP nie określił interakcji z przestrzeniami nazw. self::Oś jest nazw restrykcyjne, local-name()nie jest. Jeśli intencją PO jest przechwytywanie c|d|eniezależnie od przestrzeni nazw (co sugerowałbym, że jest to prawdopodobny scenariusz, biorąc pod uwagę charakter OR problemu), to jest to „kolejna odpowiedź, która nadal ma kilka pozytywnych głosów”, co jest niepoprawne.

Nie możesz być definitywny bez definicji, chociaż jestem bardzo szczęśliwy, że mogę usunąć moją odpowiedź jako naprawdę niepoprawną, jeśli OP wyjaśnia jego pytanie tak, że się mylę.

annakata
źródło
3
Mówiąc tutaj jako osoba trzecia - osobiście uważam sugestię Dimitre za lepszą praktykę, z wyjątkiem przypadków, w których użytkownik ma wyraźny (i dobry) powód, aby przejmować się nazwą tagu nieistotną dla przestrzeni nazw; Gdyby ktoś zrobił to na dokumencie, który wmieszałem w treść o różnych nazwach (prawdopodobnie przeznaczony do odczytania przez inny łańcuch narzędzi), uznałbym jego zachowanie za bardzo niewłaściwe. To powiedziawszy, argument jest - jak sugerujesz - nieco niestosowny.
Charles Duffy
4
dokładnie to, czego szukałem. Przestrzenie nazw XML w sposób, w jaki są używane w prawdziwym życiu, to potworny bałagan. Z powodu braku możliwości określenia czegoś takiego jak / a / b / ( : c | : d | * e) Twoje rozwiązanie jest dokładnie tym, czego potrzebujesz. Puryści mogą argumentować, co chcą, ale użytkowników nie obchodzi, że aplikacja się zepsuje, ponieważ cokolwiek wygenerowało ich plik wejściowy, zepsuło przestrzenie nazw. Chcą tylko, żeby to działało.
Ghostrider
7
Mam tylko mgliste pojęcie, jaka byłaby różnica między tymi dwoma odpowiedziami i nikt nie zadał sobie trudu, aby wyjaśnić. Co oznacza „ograniczenie przestrzeni nazw”? Jeśli używam local-name(), czy to oznacza, że ​​dopasuje tagi do dowolnej przestrzeni nazw? Jeśli użyję self::, do jakiej przestrzeni nazw musiałaby pasować? Jak bym pasował tylko OhMy:c?
meustrus
15

Dlaczego nie a/b/(c|d|e)? Właśnie wypróbowałem z saksońską biblioteką XML (ładnie opakowaną w trochę dobroci Clojure) i wydaje się, że działa. abc.xmljest dokumentem opisanym przez 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
źródło
8
Tak, ale to XPath 2.0
To działało dobrze dla mnie. Wygląda na to, że XPath 2.0 jest domyślnym parsowaniem HTML w lxml w Pythonie 2.
Martin Burch
-1

Nie jestem pewien, czy to pomoże, ale z XSL zrobiłbym coś takiego:

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

i czy ten XPath nie wybierze wszystkich elementów podrzędnych węzłów B:

a/b/*
Calvin
źródło
Dzięki Calvin, ale nie używam XSL, a pod B jest więcej elementów, których nie chcę wybierać. Zaktualizuję mój przykład, aby był jaśniejszy.
nickf
Och, w takim razie wydaje się, że annakata ma rozwiązanie.
Calvin