【问题标题】:Efficient algorithm for comparing XML nodes用于比较 XML 节点的高效算法
【发布时间】:2008-12-05 12:28:28
【问题描述】:

我想确定一个 XML 文档中的两个不同的子节点是否相等。如果两个节点具有相同的属性和子注释集并且所有子注释也相等(即整个子树应该相等),则它们应被视为相等。

输入文档可能非常大(最多 60MB,超过 100000 个要比较的节点)并且性能是一个问题。

检查两个节点是否相等的有效方法是什么?

示例:

<w:p>
  <w:pPr>
    <w:spacing w:after="120"/>
  </w:pPr>
  <w:r>
    <w:t>Hello</w:t>
  </w:r>
</w:p>
<w:p>
  <w:pPr>
    <w:spacing w:after="240"/>
  </w:pPr>
  <w:r>
    <w:t>World</w:t>
  </w:r>
</w:p>

此 XML sn-p 描述 OpenXML 文档中的段落。该算法将用于确定文档中是否包含一个段落(w:p 节点),其属性(w:pPr 节点)与文档中前面的另一个段落具有相同的属性(w:pPr 节点)。

我的一个想法是将节点的外部 XML 存储在一个哈希集中(通常我必须首先获得一个规范的字符串表示,其中属性和子注释总是以相同的方式排序,但我可以期待我的节点已经是这种形式)。

另一个想法是为每个节点创建一个 XmlNode 对象并编写一个比较器来比较所有属性和子节点。

我的环境是 C# (.Net 2.0);非常欢迎任何反馈和进一步的想法。也许有人甚至已经有了一个好的解决方案?

编辑:微软的 XmlDiff API 实际上可以做到这一点,但我想知道是否会有更轻量级的方法。 XmlDiff 似乎总是产生一个 diffgram 并且总是首先产生一个规范节点表示,这两件事我都不需要。

EDIT2:我终于根据这里提出的建议实现了我自己的 XmlNodeEqualityComparer。非常感谢!!!!

谢谢, 迪沃

【问题讨论】:

标签: c# xml algorithm comparison performance


【解决方案1】:

我建议不要滚动您自己的哈希创建函数,而是依赖内置的XNodeEqualityComparerGetHashCode 方法。这样可以保证在创建结果时考虑属性和后代节点,也可以节省一些时间。

您的代码如下所示:

XNodeEqualityComparer comparer = new XNodeEqualityComparer();
XDocument doc = XDocument.Load("XmlFile1.xml");
Dictionary<int, XNode> nodeDictionary = new Dictionary<int, XNode>();

foreach (XNode node in doc.Elements("doc").Elements("node"))
{
    int hash = comparer.GetHashCode(node);
    if (nodeDictionary.ContainsKey(hash))
    {
        // A duplicate has been found. Execute your logic here
        // ...
    }
    else
    {
        nodeDictionary.Add(hash, node);
    }
}

我的 XmlFile1.xml 是:

<?xml version="1.0" encoding="utf-8" ?>
<doc>
  <node att="A">Blah</node>
  <node att="A">Blah</node>
  <node att="B">
    <inner>Innertext</inner>
  </node>
  <node>Blah</node>
  <node att="B">
    <inner>Different</inner>
  </node>
</doc>

nodeDictionary 最终将包含一个唯一的节点集合及其哈希值。使用DictionaryContainsKey 方法检测重复项,传入我们使用XNodeEqualityComparerGetHashCode 方法生成的节点哈希。

我认为这应该足以满足您的需求。

【讨论】:

  • XNodeEqualityComparer 是 3.5 框架的一部分,他的帖子暗示他们正在使用 2.0。我同意这可能是最好的方法,它们可能包括相关的库?
  • 仅供参考:“如果两个 XElement 节点具有相同的标记名称、具有相同值的相同属性集和(忽略 cmets 和处理指令),则它们是相等的”,这意味着换行符和新的xml 中的 cmets 不会触发不同的哈希值! msdn.microsoft.com/en-us/library/windows/apps/…
【解决方案2】:

这种方法怎么样:

对于文档中的所有&lt;w:pPr&gt; 节点(我想每个&lt;w:p&gt; 不超过一个),将所有相关数据(元素名称、属性、值)连接成一个字符串:

// string format is really irrelevant, so this is just a bogus example
'!w:keep-with-next@value="true"!w:spacing@w:before="10"@w:after="120"'

按字母顺序执行,以适应不同的文档顺序。

使用这些字符串作为键和对各自&lt;w:p&gt; 节点的引用作为值来构建集合。

在执行此操作的过程中,当您遇到给定键已存在于集合中的点时,您会发现一个具有相同属性的段落。如果您想继续收集,请使用节点列表作为收集值。

我不能说它的性能有多好,但我想实施和发现并不难。

【讨论】:

  • 我做了一个一次性的 XSLT 转换,它完成了所描述的字符串构建:pastebin.me/49393ec501fe9。也许它比通过 DOM 遍历“手动”执行它更快。您将获得此类元素的列表:'!w:spacing@w:after"240"'。
【解决方案3】:

即使正确定义问题也非常具有挑战性

“当两个xml文档相等时?”

这有很多原因:

  1. XML 文档是一棵树,它可能具有不同的文本表示形式。
  2. 在比较中可能会或可能不会考虑仅包含空格的节点
  3. 在比较中可能会考虑也可能不会考虑评论节点
  4. 在比较中可能会或可能不会考虑 PI 节点
  5. 词法差异:或
  6. 不同的前缀可能与两个文档中的同一个命名空间相关联
  7. 命名空间节点可能显示为定义在 doc1 的节点上,也可能显示为未定义但继承自 doc2 中相应节点的父节点
  8. 可以在 doc1 中的属性周围使用引号,但在 doc2 中可以使用撇号
  9. 实体可以在 doc1 中使用,但它们可以在 doc2 中预先展开
  10. 这两个文档可能有不同但语义相同的 DTD

因此,尝试为两个 XML 文档的相等性比较生成函数的正确实现似乎是幼稚和不切实际的。

我的建议是deep-equal() 函数与兼容的 XPath 2.0 引擎一起使用。

【讨论】:

  • 有一个 W3C 建议来处理这些问题:Canonical XML Version 1.0 (w3.org/TR/xml-c14n.html)。您描述的问题在许多情况下必须考虑,例如在验证 XML 文档的数字签名时。
  • 它不能处理所有这些问题。例如,没有 namspace 重写:w3.org/TR/xml-c14n.html#NoNSPrefixRewriting。因此,两个仅具有与特定命名空间相关联的不同前缀的 XML 文档将具有不同的规范化。
【解决方案4】:

这是我敲出的一个哈希函数,它试图解决您的部分问题。请注意,我几乎没有编写散列函数的经验,主要是为了从人们那里获得关于它在解决这个特定问题的有效性方面的反馈。我不建议在生产中使用它。

static int HashXElement(XElement elem)
{
    int hash = 23;

    foreach (XAttribute attrib in elem.Attributes())
    {
        int attribHash = 23;
        attribHash = attribHash * 37 + attrib.Name.GetHashCode();
        attribHash = attribHash * 37 + attrib.Value.GetHashCode();
        hash = hash ^ attribHash;
    }

    foreach(XElement subElem in elem.Descendants())
    {
        hash = hash * 37 + XmlHash(subElem);
    }

    hash = hash * 37 + elem.Value.GetHashCode();

    return hash;
}

想法是使子节点的顺序重要,但属性的顺序不重要。

【讨论】:

    【解决方案5】:

    不是您问题的直接答案,而是与您要达到的目标密切相关:查看XmlDiff(.net XML 电动工具)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-20
      • 2014-10-09
      相关资源
      最近更新 更多