XPath para seleccionar múltiples etiquetas

132

Dado este formato de datos simplificado:

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

¿Cómo seleccionarías todos los Cs, Dsy Es que son hijos de Belementos?

Básicamente, algo como:

a/b/(c|d|e)

En mi propia situación, en lugar de a/b/, la consulta que condujo a la selección de los C, D, Enodos es en realidad bastante compleja así que me gustaría evitar hacer esto:

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

es posible?

nickf
fuente

Respuestas:

207

Una respuesta correcta es :

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

Tenga en cuenta que esto

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

es a la vez demasiado largo e incorrecto . Esta expresión XPath seleccionará nodos como:

OhMy:c

NotWanted:d 

QuiteDifferent:e
Dimitre Novatchev
fuente
2
'o' no funciona en un para cada uno, necesitaría usar una línea vertical en su lugar '|'
Guasqueño
8
@ Guasqueño, ores un operador lógico: opera con dos valores booleanos. El operador de unión XPath |opera en dos conjuntos de nodos. Estos son bastante diferentes y hay casos de uso específicos para cada uno de ellos. El uso | puede resolver el problema original, pero da como resultado una expresión XPath más larga y más compleja y desafiante. La expresión más simple en esta respuesta, que usa el oroperador, produce el conjunto de nodos deseado y puede especificarse en el atributo "select" de una <xsl:for-each>operación XSLT. Solo inténtalo.
Dimitre Novatchev
44
@JonathanBenn, A cualquiera que "no le importen los espacios de nombres" en realidad no le importa XML, y no usa XML. El uso de local-name()solo es correcto si queremos seleccionar todos los elementos con ese nombre local, independientemente del espacio de nombres en el que se encuentre el elemento. Este es un caso muy raro: en general, a las personas les importan las diferencias entre: kitchen:tabley sql:table, o entre architecture:column, sql:column, array:column,military:column
Dimitre Novatchev
2
@DimitreNovatchev haces un buen punto. Estoy usando XPath para la inspección de HTML, que es un caso extremo donde el espacio de nombres no es tan importante ...
Jonathan Benn
2
Eso es super ¿De dónde se te ocurrió eso?
Keith Tyler
46

En su lugar, puede evitar la repetición con una prueba de atributo:

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

Contrariamente a la opinión antagónica de Dimitre, lo anterior no es incorrecto en un vacío donde el OP no ha especificado la interacción con espacios de nombres. El self::eje es restrictivo de espacio de nombres, local-name()no lo es. Si la intención del OP es capturar c|d|eindependientemente del espacio de nombres (lo que sugeriría es incluso un escenario probable dada la naturaleza OR del problema), entonces es "otra respuesta que todavía tiene algunos votos positivos" que es incorrecta.

No puede ser definitivo sin una definición, aunque me complace eliminar mi respuesta como realmente incorrecta si el OP aclara su pregunta de tal manera que soy incorrecta.

annakata
fuente
3
Hablando como un tercero aquí, personalmente, considero que la sugerencia de Dimitre es la mejor práctica, excepto en los casos en que el usuario tiene una razón explícita (y buena) para preocuparse por el nombre de la etiqueta irrelevante del espacio de nombres; Si alguien hiciera esto contra un documento que estaba mezclando en contenido de espacios de nombres diferentes (presumiblemente destinado a ser leído por una cadena de herramientas diferente), consideraría su comportamiento muy inapropiado. Dicho esto, el argumento es, como sugiere, un poco impropio.
Charles Duffy
44
Exactamente lo que estaba buscando. Los espacios de nombres XML en la forma en que se usan en la vida real son un desastre profano. Por falta de poder especificar algo como / a / b / ( : c | : d | * e) su solución es exactamente lo que se necesita. Los puristas pueden discutir todo lo que quieran, pero a los usuarios no les importa que la aplicación se rompa porque lo que sea que generó su archivo de entrada arruinó los espacios de nombres. Solo quieren que funcione.
Ghostrider
77
Solo tengo una vaga idea de cuál sería la diferencia entre estas dos respuestas y nadie se ha molestado en explicarlo. ¿Qué significa "restrictivo de espacio de nombres"? Si lo uso local-name(), ¿eso significa que coincidiría con las etiquetas con cualquier espacio de nombres? Si lo uso self::, ¿qué espacio de nombre tendría que coincidir? ¿Cómo coincidiría solo OhMy:c?
meustrus
15

¿Por qué no a/b/(c|d|e)? Acabo de probar con la biblioteca XML de Saxon (bien envuelta con algo de bondad de Clojure), y parece funcionar. abc.xmles el documento descrito por 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
fuente
8
Sí, pero eso es XPath 2.0
Esto funcionó bien para mí. Parece que XPath 2.0 es el predeterminado para el análisis HTML en lxml en Python 2.
Martin Burch
-1

No estoy seguro si esto ayuda, pero con XSL, haría algo como:

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

y este XPath no seleccionará todos los hijos de nodos B:

a/b/*
Calvin
fuente
Gracias Calvin, pero no estoy usando XSL, y en realidad hay más elementos debajo de B que no quiero seleccionar. Actualizaré mi ejemplo para que sea más claro.
nickf
Oh, bueno, en ese caso, annakata parece tener la solución.
Calvin