XPath节点表达式求值及text()函数相关技术疑问
关于XPath节点文本匹配的几个疑问解答
让我一步步拆解你的问题,结合你提供的XML示例来解释:
问题1:表达式nodeName='text'是如何求值的?
当你在XPath中写nodeName='xxx'时,本质是把nodeName所指代节点的字符串值与右侧字符串做比较。元素节点的字符串值遵循XPath规范:是该元素所有后代文本节点(按文档顺序)的拼接结果,不管这些文本是直接子节点还是嵌套在子元素里的内容。
拿你提供的第一个<C>节点举例:
<C>hello<E>EEE</E>world</C>
它的字符串值是helloEEEworld——把hello(C的直接子文本)、EEE(E的子文本,属于C的后代)、world(C的另一个直接子文本)按顺序拼接后的结果。所以C='helloEEEworld'会返回true,因为两者完全匹配。
问题2:C='hello'是不是C[text()='hello']的语法简写?
绝对不是,二者的匹配逻辑完全不同:
C='hello':比较的是C节点的整个字符串值是否等于hello,只有当C的所有后代文本拼接起来正好是hello时才返回true。C[text()='hello']:检查的是C节点是否存在直接子文本节点等于hello,只要C的直接子节点里有一个文本内容是hello,不管其他后代文本是什么,都会返回true。
比如你的第一个C节点,C[text()='hello']返回true(因为有直接子文本hello),但C='hello'返回false(因为整个字符串值是helloEEEworld)。
问题3:为什么/root/A/B[C[text()="hello"]]有效,其他类似表达式无效?
这涉及text()轴的返回值和XPath的布尔判断逻辑:
text()轴返回的是上下文节点的所有直接子文本节点(按文档顺序)。你的第一个C节点的直接子文本节点有两个:hello和world(中间的<E>是元素节点,它的文本EEE不属于C的直接子文本)。- 当你写
text()="xxx"时,XPath会隐式检查:节点集中是否存在至少一个节点的内容等于xxx。
所以:
/root/A/B[C[text()="hello"]]有效:因为C的直接子文本里确实有hello这个节点。/root/A/B[C[text()="helloworld"]]无效:C的直接子文本里没有任何一个节点的内容是helloworld(只有两个分开的hello和world)。/root/A/B[C[text()="helloEEEworld"]]无效:EEE是<E>的子文本,不属于C的直接子文本,所以text()返回的节点里根本没有它。
你提到的W3C规范是对的:text()确实返回上下文节点的所有直接子文本节点,但在布尔表达式中,它是做存在性匹配,而非拼接所有文本后再比较。
问题4:如何用C[text()=语法选中包含第一个C节点的B节点?
如果你想基于C的直接子文本节点来匹配,最简单的写法就是你已经验证有效的:
/root/A/B[C[text()="hello"]]
或者更简洁的:
/root/A/B[C/text()="hello"]
如果你的需求是匹配C的整个字符串值(即helloEEEworld)但一定要用text()相关的语法,分两种情况:
- XPath 2.0及以上:可以用
string-join把所有后代文本拼接起来比较:/root/A/B[string-join(C/descendant::text(), "")="helloEEEworld"] - XPath 1.0:只能手动拼接已知的文本节点(因为XPath 1.0没有字符串拼接函数):
/root/A/B[concat(C/text()[1], C/E/text(), C/text()[2])="helloEEEworld"]
不过说实话,这种场景下直接用C[.="helloEEEworld"](.代表当前节点的字符串值)会更简洁直观,完全没必要硬套text()语法。
内容的提问来源于stack exchange,提问作者7stud

