16

我有一个即时生成的 XML 文档,我需要一个函数来消除其中的任何重复节点。

我的功能如下:

declare function local:start2() {
    let $data := local:scan_books()
    return <books>{$data}</books>
};

示例输出为:

<books>
  <book>
    <title>XML in 24 hours</title>
    <author>Some Guy</author>  
  </book>
  <book>
    <title>XML in 24 hours</title>
    <author>Some Guy</author>  
  </book>
</books>

我只想要我的书籍根标签中的一个条目,还有其他标签,比如那里的小册子也需要删除重复项。有任何想法吗?


更新了以下评论。通过唯一节点,我的意思是删除具有完全相同内容和结构的节点的多次出现。

4

7 回答 7

17

一种更简单、更直接的单行 XPath 解决方案

只需使用以下 XPath 表达式

  /*/book
        [index-of(/*/book/title, 
                  title
                 )
                  [1]
        ]

例如,当应用于以下 XML 文档时

<books>
    <book>
        <title>XML in 24 hours</title>
        <author>Some Guy</author>
    </book>
    <book>
        <title>Food in Seattle</title>
        <author>Some Guy2</author>
    </book>
    <book>
        <title>XML in 24 hours</title>
        <author>Some Guy</author>
    </book>
    <book>
        <title>Food in Seattle</title>
        <author>Some Guy2</author>
    </book>
    <book>
        <title>How to solve XPAth Problems</title>
        <author>Me</author>
    </book>
</books>

上面的 XPath 表达式正确选择了以下节点

<book>
    <title>XML in 24 hours</title>
    <author>Some Guy</author>
</book>
<book>
    <title>Food in Seattle</title>
    <author>Some Guy2</author>
</book>
<book>
    <title>How to solve XPAth Problems</title>
    <author>Me</author>
</book>

解释很简单:对于every book,只选择它出现的一个——这样它在all-bookstitle中的索引与它在all-titles中的第一个索引相同。

于 2009-03-20T14:01:46.397 回答
5

您可以使用内置distinct-values()功能...

于 2010-03-11T05:44:51.533 回答
2

受函数式编程启发的解决方案。此解决方案是可扩展的,因为您可以用自定义构建的布尔函数替换“=”比较。该函数在列表长度上具有最坏情况的二次复杂度。您可以通过事先对列表进行排序并仅与直接后继者进行比较来获得复杂性。local:compare($element1, $element2)n(log n)

据我所知,fn:distinct-values(或fn:distinct-elements)函数不允许使用定制的比较函数。

declare function local:deduplicate($list) {
  if (fn:empty($list)) then ()
  else 
    let $head := $list[1],
      $tail := $list[position() > 1]
    return
      if (fn:exists($tail[ . = $head ])) then local:deduplicate($tail)
      else ($head, local:deduplicate($tail))
};

let $list := (1,2,3,4,1,2,1) return local:deduplicate($list)
于 2010-07-01T09:31:42.217 回答
1

我通过实现递归唯一性搜索功能解决了我的问题,该功能仅基于文档的文本内容进行唯一性匹配。

declare function ssd:unique-elements($list, $rules, $unique) {
    let $element := subsequence($rules, 1, 1)
    let $return :=
    if ($element) then
        if (index-of($list, $element) >= 1) then
            ssd:unique-elements(insert-before($element, 1, $list), subsequence($rules, 2), $unique)
        else <test>
            <unique>{$element}</unique>
            {ssd:unique-elements(insert-before($element, 1, $list), subsequence($rules, 2), insert-before($element, 1, $unique))/*}
            </test>
    else ()
    return $return
};

调用如下:

declare function ssd:start2() {
    let $data := ()
    let $sift-this := 
       <test>
           <data>123</data>
           <data>456</data>
           <data>123</data>
           <data>456</data>
           <more-data>456</more-data>
       </test>
    return ssd:unique-elements($data, $sift-this/*, ())/*/*
};

ssd:start2()

输出:

<?xml version="1.0" encoding="UTF-8"?>
<data>123</data>
<data>456</data>

我想如果您需要稍微不同的等价匹配,您可以相应地更改算法中的匹配。无论如何应该让你开始。

于 2009-03-13T22:50:38.077 回答
1

fn:distinct-values 呢?

于 2010-05-29T19:02:42.727 回答
1

你可以使用这个functx函数:functx:distinct-deep

无需重新发明轮子

于 2014-11-27T03:08:06.367 回答
1

要删除重复项,我通常使用辅助函数。在你的情况下,它看起来像这样:

declare function local:remove-duplicates($items as item()*) 
as item()*
{
  for $i in $items
  group by $i
    return $items[index-of($items, $i)[1]]
};

declare function local:start2() {
    let $data := local:scan_books()
    return <books>{local:remove-duplicates($data)}</books>
};
于 2017-04-13T09:56:07.177 回答