XPath选择多个标签

167

考虑到这个简化的数据格式:

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

你如何选择所有作为B元素的子元素的CDE标签?基本上,可以这样实现:
a/b/(c|d|e)

在我的情况下,与其只是a/b/,导致选择那些CDE节点的查询实际上相当复杂,因此我想避免这样做:

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

这是可能的吗?
4个回答

257

一个正确的答案是:

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

请注意,这一点很重要

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

既太长又不正确。这个XPath表达式将选择类似以下节点:

OhMy:c

NotWanted:d 

QuiteDifferent:e

2
“or”在for-each中不起作用,您需要使用竖线“|”代替。 - Guasqueño
9
@Guasqueño,“或”是一个逻辑运算符--它对两个布尔值进行操作。XPath的“union”运算符“|”对两组节点进行操作。它们之间有很大的区别,各自具有特定的用例。使用“|”可以解决原始问题,但会导致XPath表达式更长、更复杂、更难理解。这个答案中使用“or”运算符的简单表达式可以产生所需的节点集,并且可以在xsl:for-each XSLT操作的“select”属性中指定。请试一下。 - Dimitre Novatchev
5
@JonathanBenn,任何“不关心命名空间”的人实际上并不关心XML,也不使用XML。只有在我们想选择具有特定本地名称的所有元素而不考虑元素所在的命名空间时,使用local-name()才是正确的。这是非常罕见的情况--通常人们确实关心kitchen:tablesql:table之间的差异,或者architecture:columnsql:columnarray:columnmilitary:column之间的差异。 - Dimitre Novatchev
3
@DimitreNovatchev 你说得很对。我正在使用XPath来检查HTML,这是一个边缘情况,命名空间并不那么重要... - Jonathan Benn
2
太棒了。你是从哪里想到的? - Keith Tyler
显示剩余2条评论

53

您可以使用属性测试来避免重复:

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

与Dimitre的对立观点相反,在没有指定与命名空间交互的情况下,上述内容在真空中并非不正确。 self :: 轴是命名空间限制的,local-name() 不是。如果OP的意图是捕获 c | d | e ,而不考虑命名空间(我认为这是问题的OR性质),那么“仍然具有一些积极投票的另一个答案”是不正确的。

没有定义就不能确定,尽管如果OP澄清了问题使我错误,我很乐意删除我的回答,因为它确实是不正确的。


3
作为第三方,就我个人而言,我认为Dimitre的建议是更好的做法,除非用户有明确(且合理)的理由关心标签名与命名空间无关;如果有人这样做,而我正在混合不同命名空间内容的文档中(可能打算由不同的工具链读取),我会认为他们的行为非常不恰当。话虽如此,正如你所暗示的那样,这种争论有点不得体。 - Charles Duffy
5
正是我要找的东西。XML命名空间在实际应用中使用起来非常混乱。由于无法指定类似于 /a/b/(:c|:d|*e) 的内容,你的解决方案正是所需的。纯粹主义者可以争论他们想要的一切,但用户不关心应用程序因为生成其输入文件的任何内容而崩溃了命名空间。他们只想让它工作。 - Ghostrider
7
我只有模糊的想法,不知道这两个答案之间的区别是什么,也没有人愿意解释。"namespace restrictive" 是什么意思?如果我使用 local-name(),那是否意味着它可以匹配任何命名空间的标签?如果我使用 self::,它需要匹配哪个命名空间?如何仅匹配 OhMy:c - meustrus

16
为什么不用Saxon XML library中的a/b/(c|d|e)?我刚试过,好像可以使用。abc.xml是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>>)

这对我很有效。在Python 2中,lxml中的HTML解析似乎默认为XPath 2.0。 - Martin Burch
我在XPath 1.0(默认的java/JAXB)中尝试了a/b/(c|d|e),并且出现了错误javax.xml.transform.TransformerException: A location step was expected following the '/' or '//' token. 在XPath 1.0中,a/b/[self::c or self::d]的解决方案是有效的。 - undefined

-3

不确定这是否有帮助,但是使用XSL,我会做类似以下的事情:

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

这个 XPath 会选择 B 节点的所有子节点吗:

a/b/*

谢谢Calvin,但我没有使用XSL,并且实际上在B下面有更多的元素我不想选择。我会更新我的示例以使其更清晰。 - nickf
哦,那样的话,annakata似乎有解决方案。 - Calvin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接