使用Linq和XElement检测XML中的结构差异

本文关键字:结构 XML Linq XElement 检测 使用 | 更新日期: 2023-09-27 18:24:54

我正在尝试审计定制软件中使用的一些XML。我可以使用"XNode.DeepEquals"来检测相同结构中的变化,然后为发生变化的元素添加一个额外的属性,以便突出显示它们。

我的问题是,当结构发生变化时,这种方法就会失败。(我在执行DeepEquals的同时枚举两个XElement,如果它们不相等——递归地调用相同的方法来过滤出发生确切变化的地方)

很明显,当我枚举时,这就不一样了,而且被比较的节点不一样。参见以下示例:

之前

<?xml version="1.0" encoding="utf-16"?>
<Prices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Price default="true">
    <Expression operator="Addition">
        <LeftOperand>
            <AttributeValue field="ccx_bandwidth" />
        </LeftOperand>
        <RightOperand>
            <Constant value="10" type="Integer" />
        </RightOperand>
    </Expression>
</Price>
<Price default="false">
    <Expression operator="Addition">
        <LeftOperand>
            <AttributeValue field="ccx_bandwidth" />
        </LeftOperand>
        <RightOperand>
            <Constant value="99" type="Integer" />
        </RightOperand>
    </Expression>
</Price>
<RollupChildren />

之后

<?xml version="1.0" encoding="utf-16"?>
<Prices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Price default="true">
    <Expression operator="Addition">
        <LeftOperand>
            <AttributeValue field="ccx_bandwidth" />
        </LeftOperand>
        <RightOperand>
            <Constant value="10" type="Integer" />
        </RightOperand>
    </Expression>
</Price>
<RollupChildren />

因此,您可以看到后一个价格节点已被删除,我需要显示此更改。

目前,我可以访问这两段xml,并在加载审计应用程序时使用"auditchanged"属性对其进行修改,在我的silverlight应用程序中,我也用转换器绑定了后台。

我一直在尝试使用Linq到Xml,并考虑在查询中连接这两个XElement,但不确定如何进行。

理想情况下,我想做的是将两个XElement合并在一起,但添加一个单独的属性,这取决于它是添加还是删除,然后我可以使用转换器将其绑定到转换器上,适当地用红色或绿色突出显示。

有人对此有什么好主意吗?(我一直在看XmlDiff,但我不认为我不能在Silverlight中使用它?)

使用Linq和XElement检测XML中的结构差异

我在代码块库中有一个通用的difference类http://codeblocks.codeplex.com

加载XML文档并将每个文档视为IEnumerable(扁平化的XML树)应该允许您使用difference,如下所示:http://codeblocks.codeplex.com/wikipage?title=Differ%20Sample&referringTitle=主页

以下是differ.cs的源代码:http://codeblocks.codeplex.com/SourceControl/changeset/view/96119#1887406

Diff原型是:

static IEnumerable<DiffEntry> Diff(IEnumerable<T> oldData, IEnumerable<T> newData, Comparison<T> identity, Comparison<T> different)

这里的重要部分是后代查询。它将第一个文档中的每个元素都转到其祖先列表中,其中每个项都包含元素的名称及其在同名同级中的索引。我认为这可以以某种方式用于连接,尽管我不知道如何使用linq进行完全外部连接。因此,我只是使用这些列表来查找第二个文档中的元素,然后根据结果,可能会将其标记为已删除或已更改。

var doc = XDocument.Load(in_A);
var doc2 = XDocument.Load(in_B);
var descendants = doc.Descendants().Select(d => 
    d.AncestorsAndSelf().Reverse().Select(el => 
        new {idx = el.ElementsBeforeSelf(el.Name).Count(), el, name = el.Name}).ToList());
foreach (var list in descendants) {
    XContainer el2 = doc2;
    var el = list.Last().el;
    foreach (var item in list) {
        if (el2 == null) break;
        el2 = el2.Elements(item.name).Skip(item.idx).FirstOrDefault();
    }
    string changed = "";
    if (el2 == null) changed += " deleted";
    else {
        var el2e = el2 as XElement;
        if (el2e.Attributes().Select(a => new { a.Name, a.Value })
            .Except(el.Attributes().Select(a => new { a.Name, a.Value })).Count() > 0) {
                changed += " attributes";
        }
        if (!el2e.HasElements && el2e.Value != el.Value) {
            changed += " value";
        }
        el2e.SetAttributeValue("found", "found");
    }
    if (changed != "") el.SetAttributeValue("changed", changed.Trim());
}
doc.Save(out_A);
doc2.Save(out_B);