XPath per selezionare più tag


132

Dato questo formato di dati semplificato:

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

Come sceglieresti tutte le Cs, Ds e Eche sono figli di Belementi?

Fondamentalmente, qualcosa del tipo:

a/b/(c|d|e)

Nella mia situazione, invece di a/b/, la query che portano alla selezione di quelli C, D, Enodi è in realtà abbastanza complesso quindi vorrei evitare di fare questo:

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

È possibile?

Risposte:


207

Una risposta corretta è :

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

Si noti che questo

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

è sia troppo lungo che errato . Questa espressione XPath selezionerà nodi come:

OhMy:c

NotWanted:d 

QuiteDifferent:e

2
'or' non funziona su un for-each, invece di utilizzare una linea verticale '|'
Guasqueño,

8
@ Guasqueño, orè un operatore logico - opera su due valori booleani. L' operatore del sindacato XPath |opera su due set di nodi. Questi sono abbastanza diversi e ci sono casi d'uso specifici per ciascuno di essi. L'uso | può risolvere il problema originale, ma si traduce in un'espressione XPath più lunga, più complessa e stimolante. L'espressione più semplice in questa risposta, che utilizza l' oroperatore, produce il set di nodi desiderato e può essere specificata nell'attributo "select" di un'operazione <xsl:for-each>XSLT. Provalo e basta.
Dimitre Novatchev,

4
@JonathanBenn, Chiunque "non si preoccupi degli spazi dei nomi" in realtà non si preoccupa di XML e non usa XML. L'uso di local-name()è corretto solo se vogliamo selezionare tutti gli elementi con quel nome locale, indipendentemente dallo spazio dei nomi in cui si trova l'elemento. Questo è un caso molto raro - in generale le persone si preoccupano delle differenze tra: kitchen:tablee sql:table, o tra architecture:column, sql:column, array:column,military:column
Dimitre Novatchev

2
@DimitreNovatchev hai fatto un buon punto. Sto usando XPath per l'ispezione HTML, che è un caso limite in cui lo spazio dei nomi non è così importante ...
Jonathan Benn

2
Questo è super. Dove l'hai inventato?
Keith Tyler,

46

Puoi invece evitare la ripetizione con un test di attributo:

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

Contrariamente all'opinione antagonista di Dimitre, quanto sopra non è errato in un vuoto in cui l'OP non ha specificato l'interazione con gli spazi dei nomi. L' self::asse è restrittivo dello spazio dei nomi, local-name()non lo è. Se l'intenzione del PO è catturare a c|d|eprescindere dallo spazio dei nomi (che suggerirei sia anche uno scenario probabile data la natura OR del problema), allora è "un'altra risposta che ha ancora dei voti positivi" che non è corretta.

Non puoi essere definitivo senza definizione, anche se sono abbastanza felice di cancellare la mia risposta come veramente errata se il PO chiarisce la sua domanda in modo tale che io sia errato.


3
Parlando come una terza parte qui - personalmente, trovo che il suggerimento di Dimitre sia la migliore pratica tranne nei casi in cui l'utente ha ragioni esplicite (e buone) di preoccuparsi del nome del tag irrilevante dello spazio dei nomi; se qualcuno lo facesse a fronte di un documento che stavo mescolando in contenuti con spaziatura diversa (presumibilmente destinato a essere letto da una toolchain diversa), considererei il loro comportamento molto inappropriato. Detto questo, l'argomento è, come suggerisci, un po 'sconcertante.
Charles Duffy,

4
esattamente quello che stavo cercando. Gli spazi dei nomi XML nel modo in cui vengono utilizzati nella vita reale sono un disordine diabolico. Per mancanza di poter specificare qualcosa come / a / b / ( : c | : d | * e) la tua soluzione è esattamente ciò di cui hai bisogno. I puristi possono sostenere tutto ciò che vogliono, ma agli utenti non importa che l'app si rompa perché qualunque cosa abbia generato il loro file di input ha rovinato gli spazi dei nomi. Vogliono solo che funzioni.
Ghostrider,

7
Ho solo la vaga idea di quale sarebbe la differenza tra queste due risposte e nessuno si è preso la briga di spiegare. Che cosa significa "restrittivo dello spazio dei nomi"? Se lo uso local-name(), significa che corrisponderebbe ai tag con qualsiasi spazio dei nomi? Se uso self::, quale spazio dei nomi dovrebbe corrispondere? Come avrei abbinato solo OhMy:c?
Meustrus

15

Perché no a/b/(c|d|e)? Ho appena provato con la libreria XML Saxon (chiusa bene con un po 'di bontà Clojure) e sembra funzionare. abc.xmlè il documento descritto dall'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>>)

8
Sì, ma è XPath 2.0

Questo ha funzionato bene per me. Sembra che XPath 2.0 sia l'impostazione predefinita per l'analisi HTML in lxml su Python 2.
Martin Burch

-1

Non sono sicuro che questo aiuti, ma con XSL farei qualcosa del tipo:

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

e questo XPath non selezionerà tutti i figli dei nodi B:

a/b/*

Grazie Calvin, ma non sto usando XSL, e in realtà ci sono più elementi sotto B che non voglio selezionare. Aggiornerò il mio esempio per essere più chiaro.
Nickf

Oh, beh in quel caso annakata sembra avere la soluzione.
Calvin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.