XPath contains(text(),'some string')在与具有多个text子节点的节点一起使用时不起作用

我有一个小问题,XPath包含与dom4j…

假设我的XML是

<Home>
<Addr>
<Street>ABC</Street>
<Number>5</Number>
<Comment>BLAH BLAH BLAH <br/><br/>ABC</Comment>
</Addr>
</Home>

假设我想找到文本中所有有ABC的节点,给定根元素…

所以我需要写的XPath是

//*[contains(text(),'ABC')]

然而,这不是dom4j返回的内容....这是dom4j的问题还是我对XPath工作原理的理解,因为该查询只返回Street元素而不返回Comment元素?

DOM使Comment元素成为具有四个标记2的复合元素

[Text = 'XYZ'][BR][BR][Text = 'ABC']

我假设查询仍然应该返回元素,因为它应该找到元素并在其上运行contains,但它没有……

下面的查询返回元素,但它返回的不仅仅是元素——它还返回父元素,这对问题来说是不可取的。

//*[contains(text(),'ABC')]

有人知道只返回元素<Street/><Comment/>的XPath查询吗?

892402 次浏览

<Comment>标记包含两个文本节点和两个作为子节点的<br>节点。

你的xpath表达式是

//*[contains(text(),'ABC')]

为了分析这个问题,

  1. *是一个匹配任何元素(即标签)的选择器——它返回一个节点集。
  2. []是一个条件,操作在该节点集中的每个节点上。如果它操作的任何单个节点与括号内的条件匹配,它就匹配。
  3. text()是一个选择器,它匹配上下文节点的所有子文本节点——它返回一个节点集。
  4. contains是一个作用于字符串的函数。如果传递给它一个节点集,则该节点集为通过返回节点集中文档顺序第一的节点的字符串值转换为字符串。因此,它只能匹配<Comment>元素中的第一个文本节点——即BLAH BLAH BLAH。由于不匹配,所以在结果中不会得到<Comment>

你需要把这个改成

//*[text()[contains(.,'ABC')]]
  1. *是一个匹配任何元素(即标签)的选择器——它返回一个节点集。
  2. 外层[]是一个条件,操作在该节点集中的每个节点上——这里它操作在文档中的每个元素上。
  3. text()是一个选择器,它匹配上下文节点的所有子文本节点——它返回一个节点集。
  4. 内部的[]是一个条件,操作在该节点集中的每个节点上——这里是每个单独的文本节点。每个单独的文本节点都是括号中任何路径的起点,也可以在括号中显式地引用为.。如果它操作的任何单个节点与括号内的条件匹配,它就匹配。
  5. contains是一个作用于字符串的函数。这里传递给它一个单独的文本节点(.)。由于它被单独传递给<Comment>标记中的第二个文本节点,因此它将看到'ABC'字符串并能够匹配它。

[contains(text(),'')]只返回true或false。它不会返回任何元素结果。

XML文档:

<Home>
<Addr>
<Street>ABC</Street>
<Number>5</Number>
<Comment>BLAH BLAH BLAH <br/><br/>ABC</Comment>
</Addr>
</Home>

XPath表达式:

//*[contains(text(), 'ABC')]

//*匹配根节点中的任意后代元素。也就是说,除了根节点之外的任何元素。

[...]是一个谓词,它过滤节点集。它返回...true的节点:

谓词筛选节点集[…]生成一个新的节点集。对于要筛选的节点集中的每个节点,将计算PredicateExpr[…];如果该节点的PredicateExpr值为true,则该节点包含在新的节点集中;否则,不包括它。

如果haystack 包含 needle,则contains('haystack', 'needle')返回true:

函数:boolean contains(string, string)

contains函数如果第一个参数字符串包含第二个参数字符串,则返回true,否则返回false。

但是contains()的第一个参数是字符串。它传递节点。为了处理每个作为第一个参数传递的节点或节点集都是转换,由string()函数传递给一个字符串:

参数被转换为string类型,就像调用string函数一样。

string()函数返回第一个节点string-value:

通过返回节点集中文档顺序第一个节点的字符串值,将节点集转换为字符串。如果节点集为空,则返回空字符串。

元素节点string-value:

元素节点的字符串值是该元素节点的所有文本节点后代的字符串值按文档顺序的串联。

文本节点string-value:

文本节点的字符串值是字符数据。

因此,基本上string-value是包含在节点中的所有文本(所有后代文本节点的拼接)。

text()是一个匹配任何文本节点的节点测试:

对于任何文本节点,节点test text()都是true。例如,child::text()将选择上下文节点的文本节点子节点。

话虽如此,//*[contains(text(), 'ABC')]匹配任何元素(除了根节点),其第一个文本节点包含ABC。因为text()返回一个节点集,其中包含上下文节点的所有子文本节点(表达式相对于其求值)。但是contains()只取第一个。因此,对于上面的文档,路径匹配Street元素。

下面的表达式//*[text()[contains(., 'ABC')]]匹配任何元素(除了根节点),至少有一个子文本节点,包含ABC.表示上下文节点。在本例中,它是除根节点外的任何元素的子文本节点。因此,对于上面的文档,路径匹配StreetComment元素。

现在,//*[contains(., 'ABC')]匹配包含ABC的任何元素(除了根节点)(在后代文本节点的拼接中)。对于上面的文档,它匹配HomeAddrStreetComment元素。因此,//*[contains(., 'BLAH ABC')]匹配HomeAddrComment元素。

接受的答案也将返回所有的父节点。即使字符串在
之后,也只使用ABC获取实际节点:

//*[text()[contains(.,'ABC')]]/text()[contains(.,"ABC")]
//*[text()='ABC']

返回

<street>ABC</street>
<comment>BLAH BLAH BLAH <br><br>ABC</comment>

下面是匹配包含给定文本字符串的节点的另一种方法。首先查询文本节点本身,然后获取父节点:

//text()[contains(., "ABC")]/..

对我来说,这很容易阅读和理解。

涵盖XPath 1.0 vs XPath 2.0+行为的现代答案

这个XPath,

//*[contains(text(),'ABC')]

在XPath 1.0和XPath(2.0+)的后续版本中表现不同。

常见的行为

  • //*选择文档中的所有元素。
  • []根据其中表达的谓词过滤这些元素。
  • 谓词中的contains(string, substring)将把这些元素过滤为字符串子字符串是子字符串的元素。

XPath 1.0行为

  • contains(string, substring)将通过获取节点集中的第一个节点的字符串值将节点集转换为字符串
  • 对于//*[contains(text(),'ABC')],该节点集将是文档中每个元素的所有子文本节点。
  • 由于只使用第一个文本节点子节点,违反了所有子文本节点都测试'ABC'子字符串包含的期望。
  • 对于不熟悉上述转换规则的人来说,这将导致反直觉的结果。

XPath 1.0在线示例表示只选择了一个'ABC'

XPath 2.0+行为

  • 调用contains(string, substring)时,将多个项的序列作为第一个参数是错误的。
  • 这纠正了上面在XPath 1.0中描述的违反直觉的行为。

XPath 2.0在线示例显示了一个典型的错误消息,这是XPath 2.0+特有的转换错误。

常见的解决方案

  1. 如果你希望包含后代元素(不包括子元素),将元素的< em >字符串值< / em >作为单个字符串进行测试,而不是作为子文本节点的单个字符串值进行测试,这个XPath,

    //*[contains(.,'ABC')]
    

    选择你的目标StreetComment元素,以及它们的AddrHome祖先元素,因为它们的字符串值也有'ABC'作为子字符串。

    网上的例子显示祖先也被选中。

  2. 如果您希望排除后代元素(除了子元素),这个XPath,

    //*[text()[contains(.,'ABC')]]
    

    只选择你的目标StreetComment,因为只有这些元素有文本节点的子节点,其字符串值包含'ABC'子字符串。对于所有版本的XPath都是如此

    网上的例子只显示StreetComment被选中。