ChatGPT解决这个技术问题 Extra ChatGPT

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

我对 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/><Comment/> 的 XPath 查询吗?

据我所知,//*[contains(text(),'ABC')] 仅返回 <Street> 元素。它不返回 <Street><Comment> 的任何祖先。
没有一个答案解决了 XPath 新版本(2.0 及更高版本,从 ~2007 年开始)中发现的不同行为,所以我添加了一个 updated answer below 来解释差异。

K
Ken Bloom

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

你的 xpath 表达式是

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

为了打破这一点,

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

您需要将其更改为

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

是一个匹配任何元素(即标签)的选择器——它返回一个节点集。外部 [] 是在该节点集中的每个单独节点上运行的条件 - 这里它在文档中的每个元素上运行。 text() 是一个选择器,它匹配作为上下文节点子节点的所有文本节点——它返回一个节点集。内部 [] 是在该节点集中的每个节点上操作的条件 - 这里是每个单独的文本节点。每个单独的文本节点都是括号中任何路径的起点,也可以明确称为 .括号内。如果它操作的任何单个节点与括号内的条件匹配,则它匹配。 contains 是一个对字符串进行操作的函数。这里传递了一个单独的文本节点 (.)。由于它是单独传递 标记中的第二个文本节点的,因此它将看到“ABC”字符串并能够匹配它。


太棒了,我有点像 xpath 菜鸟,所以让我明白这个,text() 是一个接受表达式 contains(.,'ABC') 的函数,你有机会解释一下,所以我不这样做又是愚蠢的东西;)
我已经编辑了我的答案以提供一个很长的解释。我自己对 XPath 的了解并不多——我只是做了一些试验,直到我偶然发现了这种组合。一旦我有了一个有效的组合,我就猜测发生了什么,然后查看 XPath standard 以确认我的想法并写下解释。
你如何使它成为不区分大小写的搜索?
我知道这是一个旧线程,但任何人都可以评论是否存在根本差异,最好在 Ken Bloom 和 //*[contains(., 'ABC')] 给出的答案之间使用一些简单的测试用例。我一直使用 Mike Milkin 给出的模式,认为它更合适,但只是在当前上下文中执行 contains 似乎实际上是我更经常想要的。
...//*[text()[contains(.,'ABC')]] 表示 text()[contains(.,'ABC')]true 的任何元素。 text()[contains(.,'ABC')]contains(.,'ABC')true 的上下文节点的所有文本节点子节点的节点集。由于 text()[contains(.,'ABC')] 是一个节点集,它被 boolean() 函数转换为布尔值。对于节点集,如果 boolean() 不为空,则返回 true
C
Community

XML 文档:

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

XPath 表达式:

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

//*root node 中的任何 descendant element 匹配。也就是说,除根节点之外的任何元素。

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

谓词过滤节点集 [...] 以生成新的节点集。对于要过滤的节点集中的每个节点, PredicateExpr 被评估 [...];如果 PredicateExpr 对该节点的计算结果为真,则该节点包含在新节点集中;否则,不包括在内。

如果 haystack contains needlecontains('haystack', 'needle') 返回 true

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

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

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

string() 函数返回 the first nodestring-value

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

element nodestring-value

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

text nodestring-value

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

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

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

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

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

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

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


与接受的答案一样,此答案仅与 XPath 1.0 有关。 XPath 2.0(2007 年发布)和更高版本的情况有所不同。
T
Toby Speight

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


如果我有 ' ' 或 ' ' 我们如何修剪?
contains(text(),'JB-') 不起作用! conatains两个字符串 作为参数 - contains(**string**, **string**)! text() 不是字符串,是一个函数!
R
Roger Veciana

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

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

如果有人想获取文本节点的父元素;使用 /.. 进行后修复查询,如下所示://*[text()[contains(.,'ABC')]]/text()[contains(.,"ABC")]/.. 谢谢! @罗杰
l
learningIsFun
//*[text()='ABC'] 

返回

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

当添加一个包含五个现有答案的九年前问题的答案时,指出您的答案所针对的问题的独特新方面非常重要。
我发布的答案非常简单。所以想分享一下,可能对我这样的初学者有所帮助。
k
kjhughes

涵盖 XPath 1.0 与 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 online example 表明只选择了一个 'ABC'

XPath 2.0+ 行为

使用包含多个项目的序列作为第一个参数调用 contains(string, substring) 是错误的。

这更正了上面在 XPath 1.0 中描述的反直觉行为。

XPath 2.0 online example 显示了由于 XPath 2.0+ 特有的转换错误而导致的典型错误消息。

常见解决方案

如果您希望包含后代元素(除了子元素),请将元素的字符串值作为单个字符串进行测试,而不是子文本节点的单个字符串值,this XPath, //*[contains(.,'ABC ')] 选择您的目标 Street 和 Comment 元素以及它们的 Addr 和 Home 祖先元素,因为它们也有 'ABC' 作为其字符串值的子字符串。在线示例显示祖先也被选中。如果您希望排除后代元素(子元素之外),则此 XPath, //*[text()[contains(.,'ABC')]] 仅选择您的目标 Street 和 Comment,因为只有这些元素具有其字符串的文本节点子节点值包含“ABC”子字符串。这适用于所有版本的 XPath Online 示例,仅显示选择了 Street 和 Comment。


E
Eaten by a Grue

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

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

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


p
phuongauto

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

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

一个例子:example case

Xpath 获取 bon dua madam

//h3[text()='Contact Information']/parent::div/following-sibling::div/p[text()[contains(.,'bon dua madam')]]/text()[contains(.,'bon dua madam')]

z
zagoo2000

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

//a[contains(text(),'JB-')]

contains(text(),'JB-') 不起作用! conatains两个字符串 作为参数 - contains(**string**, **string**)! text() 不是字符串,是一个函数!
@AtachiShadow 函数的结果是一个字符串

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅