XPath para selecionar várias tags

132

Dado este formato de dados 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>

Como você selecionar todos os Cs, Ds e Es que são filhos de Belementos?

Basicamente, algo como:

a/b/(c|d|e)

Na minha própria situação, em vez de apenas a/b/, a consulta que antecederam a selecionar aqueles C, D, Enós é realmente muito complexo para que eu gostaria de evitar fazer isso:

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

Isso é possível?

nickf
fonte

Respostas:

207

Uma resposta correta é :

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

Observe que isso

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

é muito longo e incorreto . Essa expressão XPath selecionará nós como:

OhMy:c

NotWanted:d 

QuiteDifferent:e
Dimitre Novatchev
fonte
2
'or' não funciona em um para cada, você precisaria usar uma linha vertical em vez disso '|'
Guasqueño 26/11/2015
8
@ Guasqueño, oré um operador lógico - opera com dois valores booleanos. O operador de união XPath |opera em dois conjuntos de nós. Eles são bem diferentes e existem casos de uso específicos para cada um deles. O uso | pode resolver o problema original, mas resulta em um mais longo e mais complexo e desafiador para entender a expressão XPath. A expressão mais simples nesta resposta, que usa o oroperador produz o conjunto de nós desejado e pode ser especificada no atributo "select" de uma <xsl:for-each>operação XSLT. Apenas tente.
Dimitre Novatchev
4
@ JonathanBenn, quem "não se importa com espaços para nome" na verdade não se importa com XML e não usa XML. O uso de local-name()está correto apenas se quisermos selecionar todos os elementos com esse nome local, independentemente do espaço para nome do elemento. Esse é um caso muito raro - em geral, as pessoas se preocupam com as diferenças entre: kitchen:tablee sql:table, ou entre architecture:column, sql:column, array:column,military:column
Dimitre Novatchev
2
@DimitreNovatchev você faz um bom argumento. Estou usando XPath para inspeção HTML, que é um caso extremo, onde o namespace não é tão importante ...
Jonathan Benn
2
Isso é super. De onde você tirou isso?
Keith Tyler
46

Você pode evitar a repetição com um teste de atributo:

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

Ao contrário da opinião antagônica de Dimitre, o acima não é incorreto no vácuo em que o OP não especificou a interação com espaços para nome. O self::eixo é restrito a namespace, local-name()não é. Se a intenção do OP é capturar c|d|eindependentemente do espaço para nome (o que eu sugiro que seja um cenário provável, dada a natureza OR do problema), é "outra resposta que ainda tem votos positivos" que está incorreta.

Você não pode ser definitivo sem definição, embora eu esteja muito feliz em excluir minha resposta como genuinamente incorreta se o OP esclarecer sua pergunta de modo que eu esteja incorreto.

annakata
fonte
3
Falando como terceiro aqui - pessoalmente, acho a sugestão do Dimitre a melhor prática, exceto nos casos em que o usuário tem motivos explícitos (e bons) para se preocupar com o nome da tag irrelevante para o namespace; se alguém fizesse isso com um documento que eu misturava em conteúdo com nomes diferentes (presumivelmente destinado a ser lido por uma cadeia de ferramentas diferente), consideraria seu comportamento muito inadequado. Dito isto, o argumento é - como você sugere - um pouco impróprio.
Charles Duffy
4
Exatamente o que eu estava procurando. Os namespaces XML da maneira como são usados ​​na vida real são uma bagunça profana. Por falta de capacidade de especificar algo como / a / b / ( : c | : d | * e), sua solução é exatamente o que é necessário. Os puristas podem argumentar quanto quiserem, mas os usuários não se importam que o aplicativo seja interrompido porque o que quer que tenha gerado seu arquivo de entrada estragou os espaços de nome. Eles só querem que funcione.
Ghostrider
7
Tenho apenas a mais vaga idéia de qual seria a diferença entre essas duas respostas e ninguém se deu ao trabalho de explicar. O que significa "namespace restritivo"? Se eu usar local-name(), isso significa que corresponderia a tags com qualquer espaço para nome? Se eu usar self::, qual namespace teria que corresponder? Como eu corresponderia apenas OhMy:c?
meustrus
15

Por que não a/b/(c|d|e)? Eu apenas tentei com a biblioteca XML Saxon (envolvida muito bem com algumas qualidades de Clojure), e parece funcionar. abc.xmlé o documento descrito pelo 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
fonte
8
Sim, mas esse é o XPath 2.0
Isto funcionou bem para mim. Parece XPath 2.0 é o padrão para HTML parsing em lxml em Python 2.
Martin Burch
-1

Não tenho certeza se isso ajuda, mas com o XSL, eu faria 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>

e esse XPath não selecionará todos os filhos dos nós B:

a/b/*
Calvin
fonte
Obrigado Calvin, mas não estou usando XSL, e na verdade existem mais elementos abaixo de B que não quero selecionar. Vou atualizar meu exemplo para ficar mais claro.
nickf
Oh, nesse caso, o annakata parece ter a solução.
Calvin