如何比较两个XML文档?

70
作为某些广泛单元测试的基类的一部分,我正在使用C# (.NET)编写一个帮助函数,该函数递归比较一个XmlDocument对象的节点与另一个对象。具体要求如下:
  • 第一个文档是源文件,例如,我想让XML文档看起来像什么。因此第二个文档是我想要查找差异的文档,并且它不应包含第一个文档中没有的额外节点。
  • 在发现太多重大差异时必须抛出异常,并且应该很容易被人类查看描述所理解。
  • 子元素顺序很重要,属性可以按任意顺序排列。
  • 某些属性是可忽略的;特别是xsi:schemaLocation和xmlns:xsi,尽管我希望能够传入哪些属性是可忽略的。
  • 命名空间的前缀必须匹配属性和元素。
  • 元素之间的空格无关紧要。
  • 元素将只有子元素或InnerText,但不会同时存在。

在我刚开始写这个东西时:是否有人编写过这样的代码,是否可能在这里共享它?

顺便问一下,你会如何称呼第一个和第二个文档?我一直称它们为“源”和“目标”,但感觉不太对,因为源是我想要目标看起来像什么,否则就会抛出异常。


节点可以相同但声明的顺序不同吗? - alexmac
不,节点必须按相同顺序排列。除了文档本身的要求外,这样做还可以使差异比较变得更简单(只需枚举子项并逐一检查)。 - Neil C. Obremski
好事,因为属性根据定义是无序的。 - Robert Rossney
可能是重复的问题:什么是比较XML文件相等的最佳方法? - Andrej Adamenko
1
称它们为“实际”和“预期”(是的,我知道我晚了13年)。 - Dawood ibn Kareem
显示剩余2条评论
13个回答

64

2
这非常酷!不幸的是,它唯一不能做到的是让我忽略某些属性。 - Neil C. Obremski
我在帖子中忘了提到,我在XSLT中做的另一件事情是过滤掉某些属性。 - runrig
工具的另一个链接在这里:http://msdn.microsoft.com/en-gb/library/aa302294.aspx。 - Miguel Madero
对我来说工作得很好,但我遇到了与注释相关的错误(即使没有指定XmlDiffOptions.IgnoreComments,它也会忽略注释)。 - Ohad Schneider
2
请注意,XML Notepad(https://github.com/microsoft/XmlNotepad)内置了“XMLDiff”,可以可视化显示XMLDiff差异编码。只需打开一个XML文件,然后转到*视图* -> 比较XML文件。您甚至可以通过选项中的XmlDiffOptions进行控制。 - Ohad Schneider

9

如果您想使用外部API调用,我建议您查看这里XML比较器。这非常方便,您可以使用API或网站UI来复制粘贴内容,从而获得所有差异。此外,您可以通过设置一个标志来忽略元素或节点的顺序。 希望这能帮助到您! - DJDeveloper
https://www.simplethread.com/checking-xml-for-semantic-equivalence-in-c/ 不太好用。例如,当您更改属性的顺序时,它无法正常工作。 - honzakuzel1989
我最终在我的Net6测试项目中选择了netbike。它看起来很适合我的场景。 - honzakuzel1989

8

这段代码可能不能完全满足您的需求,但它很简单并且我在我的单元测试中使用过。属性顺序无关紧要,但元素顺序必须一致。元素内部文本不进行比较。我还忽略了比较属性时的大小写,但您可以轻松地删除它。

public bool XMLCompare(XElement primary, XElement secondary)
{
    if (primary.HasAttributes) {
        if (primary.Attributes().Count() != secondary.Attributes().Count())
            return false;
        foreach (XAttribute attr in primary.Attributes()) {
            if (secondary.Attribute(attr.Name.LocalName) == null)
                return false;
            if (attr.Value.ToLower() != secondary.Attribute(attr.Name.LocalName).Value.ToLower())
                return false;
        }
    }
    if (primary.HasElements) {
        if (primary.Elements().Count() != secondary.Elements().Count())
            return false;
        for (var i = 0; i <= primary.Elements().Count() - 1; i++) {
            if (XMLCompare(primary.Elements().Skip(i).Take(1).Single(), secondary.Elements().Skip(i).Take(1).Single()) == false)
                return false;
        }
    }
    return true;
}

2
我无法让我的属性进行Count()操作。 - Prof. Falken
1
@Prof.Falkencontractbreached,你是否在使用正确的XElement?你应该使用System.Xml.Linq中的那些。 - Giles Roberts
1
@GilesRoberts 那可能就是了。虽然已经有一段时间了,但我记得当时被同名类型搞得很困惑。 - Prof. Falken
对于我的目的,我也对XML元素本身的值感兴趣。if (primary.Value != secondary.Value) { return false; } - Giles Roberts

7

可以尝试使用XMLUnit库。该库适用于Java和.Net。


XMLUnit看起来不错,但它只针对.Net框架。 - honzakuzel1989

6

在自动化测试中,如果需要比较两个XML输出,可以使用XNode.DeepEquals进行比较。

此方法用于比较两个节点的值,包括所有后代节点的值。

用法:

var xDoc1 = XDocument.Parse(xmlString1);
var xDoc2 = XDocument.Parse(xmlString2);

bool isSame = XNode.DeepEquals(xDoc1.Document, xDoc2.Document);
//Assert.IsTrue(isSame);

参考资料:https://learn.microsoft.com/zh-cn/dotnet/api/system.xml.linq.xnode.deepequals?view=netcore-2.2

该链接提供了关于“System.Xml.Linq.XNode.DeepEquals”方法的详细信息。此方法可用于比较两个XML节点及其子节点是否相等。使用此方法时,需要确保两个节点的结构和内容完全相同。

1
请注意,此比较在属性顺序上存在错误:https://github.com/dotnet/dotnet-api-docs/issues/830 - Giulio Caccin

5

5
比较XML文档是很复杂的。搜索xmldiff(甚至有Microsoft的解决方案)以获取一些工具。我已经用了几种方法来解决这个问题。我使用XSLT对元素和属性进行排序(因为有时它们会以不同的顺序出现,而我并不在意),并过滤掉我不想比较的属性,然后使用XML::Diff或XML::SemanticDiff perl模块,或者将每个元素和属性都漂亮地打印到单独的行中,然后使用Unix命令行diff比较结果。

3

我正在使用ExamXML来比较XML文件。你也可以试试。 这个软件的作者A7Soft还提供了用于比较XML文件的API。


3
另一种方法是:
  1. 将两个文件的内容分别获取到两个不同的字符串中。
  2. 使用XSLT转换这些字符串(它将只复制所有内容到两个新字符串中)。这将确保元素外的所有空格都被移除,最终得到两个新字符串。
  3. 现在,只需将这两个字符串进行比较即可。

这种方法不能给出差异的具体位置,但如果您只想知道是否存在差异,则可以轻松地完成此操作,而无需使用任何第三方库。


2
这并没有回答特定的问题,但是概念与问题相关。我的+1。 - yegor256

2

对于OP来说并不相关,因为它目前忽略了子项顺序,但如果您想要一个仅包含代码的解决方案,可以尝试 XmlSpecificationCompare,这是我有些误导地开发的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接