XPATH:查找所有具有相同值的元素,直到值发生变化

XPATH: find all elements with same value until the value changes

这是一个示例 XML:

<?xml version="1.0" ?>
<someparent>
    <somechild>
        <description>I want this</description>
        <id>98</id>
    </somechild>
    <somechild>
        <description>I don't want this</description>
        <id>98</id>
    </somechild>
    <somechild>
        <description>I want this too</description>
        <id>2</id>
    </somechild>
    <somechild>
        <description>Nope, not that one</description>
        <id>2</id>
    </somechild>
    <somechild>
        <description>Not that one either</description>
        <id>2</id>
    </somechild>
    <somechild>
        <description>Yep, I want this</description>
        <id>41</id>
    </somechild>
</someparent>

<id> 元素始终分组:具有相同 <id> 值的所有元素在文档中彼此跟随。我可能在一个文件中有数千个不同的 <id>s。我想要的是找到每个 <somechild> 元素,它是其对应 <id> 组的第一个 occurrence。所以我的预期结果是:

    <somechild>
        <description>I want this</description>
        <id>98</id>
    </somechild>
    <somechild>
        <description>I want this too</description>
        <id>2</id>
    </somechild>
    <somechild>
        <description>Yep, I want this</description>
        <id>41</id>
    </somechild>

我需要一个 XPATH 命令来 select 所有这些“组中的第一项”。我尝试了 following-siblingpreceding-sibling 轴的各种组合,但我不能完全正确。通过以下声明,我已经非常接近我想要实现的目标:

//someparent/somechild/id[text()=parent::somechild/preceding-sibling::somechild/id[text()]]/parent::somechild

这实际上 returns 我 想要的所有节点,因为它 select 是 不想要的所有项目 他们小组中的第一个(所以它基本上是我想要的完美否定!)。但是我这辈子都想不出如何反转结果。

如有任何帮助,我们将不胜感激。

这个O(n2) XPath 1.0表达式,

//someparent/somechild[not(id = preceding-sibling::somechild/id)]

将 select 所有 somechild 没有前面兄弟姐妹的元素具有相同的 id 子元素,

   <somechild>
        <description>I want this</description>
        <id>98</id>
    </somechild>
    <somechild>
        <description>I want this too</description>
        <id>2</id>
    </somechild>
    <somechild>
        <description>Yep, I want this</description>
        <id>41</id>
    </somechild>

根据要求。


更新

Michael Kay 很有帮助的是,上述 XPath 的算法复杂度为 O(n2),因为对于每个子兄弟姐妹,都会比较所有前面的兄弟姐妹。这对少数兄弟姐妹来说无关紧要,但 OP 提到了数千个,因此大小问题成为一个问题。

看他的,这是一个更好的O(n)。

他进一步观察到 O(n) XPath 1.0 表达式是可能的只要只检查紧接在前的兄弟姐妹 :

//someparent/somechild[not(id = preceding-sibling::somechild[1]/id)]
                                                            ^^^

这种较低复杂度的 XPath 将为 OP 的示例案例产生相同的结果。

一个区分案例将涉及具有 id 值的较晚的兄弟姐妹重复较早的 id 值的集群。例如,添加另一组 id 个具有 98 值的兄弟姐妹:

<someparent>
  <somechild>
    <description>I want this</description>
    <id>98</id>
  </somechild>
  <somechild>
    <description>I don't want this</description>
    <id>98</id>
  </somechild>
  <somechild>
    <description>I want this too</description>
    <id>2</id>
  </somechild>
  <somechild>
    <description>Nope, not that one</description>
    <id>2</id>
  </somechild>
  <somechild>
    <description>Not that one either</description>
    <id>2</id>
  </somechild>
  <somechild>
    <description>Yep, I want this</description>
    <id>41</id>
  </somechild>
  <somechild>
    <description>REPEAT CASE 1</description>
    <id>98</id>
  </somechild>  
  <somechild>
    <description>REPEAT CASE 2</description>
    <id>98</id>
  </somechild>
</someparent>

不同之处在于 O(n) XPath 将 包含 REPEAT CASE 1 somechild 元素,但是 O(n2) XPath will 包括远距离重复的 REPEAT CASE 1

<somechild>
    <description>I want this</description>
    <id>98</id>
</somechild>
<somechild>
    <description>I want this too</description>
    <id>2</id>
</somechild>
<somechild>
    <description>Yep, I want this</description>
    <id>41</id>
</somechild>
<somechild>
  <description>REPEAT CASE 1</description>
  <id>98</id>
</somechild>

只要需求不需要非立即比较,使用效率更高的O(n) XPath。

在 XPath 3.1 中:

fold-left(//somechild, (), function($z, $i) {
    if ($i/id = $z[last()]/id) then $z else ($z, $i)
})

与公认的解决方案不同,这应该具有 O(n) 复杂度(假设 X[last()] 在恒定时间内执行)。

另一种类似于@kjhughes 提出的解决方案的语法:

//id[not(text()=preceding::id/text())]/..

另一个解决方案:

//id[text()!=preceding::id[1]/text() or count(preceding::id)=0]/..

Select id 当前面的第一个 id 值不等于当前 id 的值时。然后selectparent。 Count用于select第一个somechild元素的第一个id

当然可以用//代替绝对路径来提高效率