322

我对 dom4j 包含的 XPath 有一个小问题...

假设我的 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元素成为具有四个标签的复合元素

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

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

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

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

有谁知道只返回元素<Street/>和的 XPath 查询<Comment/>

4

9 回答 9

861

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

你的 xpath 表达式是

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

为了打破这一点,

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

您需要将其更改为

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

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 对该节点的计算结果为真,则该节点包含在新节点集中;否则,不包括在内。

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

功能:布尔包含(字符串,字符串)

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

但是contains()将字符串作为其第一个参数。它通过了节点。为了处理作为第一个参数传递的每个节点或节点集被函数转换为字符串string()

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

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

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

string-value元素节点的:

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

string-value文本节点的:

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

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

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

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

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

以下表达式//*[text()[contains(., 'ABC')]]匹配具有至少一个子文本节点且包含ABC. .表示上下文节点。在这种情况下,它是除根节点之外的任何元素的子文本节点。所以对于上面的文档,路径匹配Street, 和Comment元素。

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

于 2019-07-10T13:23:02.597 回答
7

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

于 2016-12-24T23:08:10.023 回答
5
//*[text()='ABC'] 

返回

<street>ABC</street>
<comment>BLAH BLAH BLAH <br><br>ABC</comment>
于 2020-06-16T14:12:29.703 回答
4

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

//*[text()[contains(.,'ABC')]]/text()[contains(.,"ABC")]
于 2020-03-06T09:43:31.487 回答
2

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

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

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

于 2021-07-30T15:21:29.190 回答
1

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

这个 XPath,

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

XPath 1.0 和更高版本的 XPath (2.0+) 的行为有所不同。

常见行为

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

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. 如果您希望包含祖先元素,请将元素的字符串值作为单个字符串进行测试,而不是子文本节点的单个字符串值,此 XPath,

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

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

    在线示例显示祖先也被选中。

  2. 如果您希望排除祖先元素,这个 XPath,

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

    仅选择您的目标StreetComment因为只有那些元素具有其字符串值包含子字符串的文本节点子节点'ABC'。这适用于所有版本的 XPath

    在线示例仅显示Street并被Comment选中。

于 2022-02-24T16:57:55.620 回答
0

这是主题问题的最佳答案:

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

一个例子: 示例案例

Xpath 获取bon dua madam

//h3[text()='Contact Information']/parent::div/following-sibling::div/p[text()[contains(.,'bon dua madam')]]/text()[contains(.,'bon dua madam')]
于 2021-08-18T03:09:34.590 回答
-1

我花了一点时间,但最终想通了。包含以下一些文本的自定义 xpath 非常适合我。

//a[contains(text(),'JB-')]
于 2018-10-19T10:19:26.760 回答