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

问题描述:

我试图审计一些定制的软件中使用的XML。我能够使用“XNode.DeepEquals”检测相同结构中的更改,然后向已更改的元素添加额外的属性,以便突出显示它们。使用Linq和XElement检测XML中的结构差异

我的问题是,当结构确实改变这种方法失败。 (我在同一时间枚举在两个XElements执行DeepEquals,如果他们不相等 - 递归调用同样的方法来过滤掉其中的确切变化occurr)

这显然现在分崩离析当我列举和被比较的节点是不一样的。请参见下面的示例:

以前

<?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片段,并在审计应用程序的负载上使用'silverchanged'属性修改它们,在我的Silverlight应用程序中,我使用转换器绑定背景。

我一直在玩Linq到Xml,看着加入查询中的两个XElements,但不知道如何继续。

理想情况下,我想要做的是合并两个XElements在一起,但添加一个单独的属性取决于它是否被添加或删除,然后我可以绑定到一个转换器说适当的红色或绿色突出显示。

有没有人对此有过任何明智的想法? (我一直在看XmlDiff,但是我不能在Silverlight中使用它,我不认为?)

这里的重要部分是后代查询。它将第一个文档中的每个元素都转换为其祖先列表中的每个元素,其中每个元素都包含元素的名称,并且它是同名的兄弟中的索引。我认为这可以以某种方式用于加入,但我不知道如何使用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); 
+0

我已经试过以上一个简单的例子,它的要求并不完全工作,因为它似乎认为所有的事情被删除,如果有些事情不太地方预计。好主意,所以我可以玩这个更多 – Chris 2012-02-16 19:49:32

+0

嗯,我试了两个问题的例子,它的工作是正确的,即标记第二个价格标签和它的后代被删除。 – user1096188 2012-02-17 06:33:56

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

加载XML文档,并把每个文件作为一个IEnumerable(扁平化XML树)应允许您使用不同的不同类,如下所示:http://codeblocks.codeplex.com/wikipage?title=Differ%20Sample&referringTitle=Home

以下是不同的源代码。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) 
+0

我明白不同之处在做什么,但我对如何将它设置为运动有点困惑。由于除了单个节点名称之外,我没有必要加入任何内容,因此可以在名称上设置身份比较器,但不确定是否遵循了如何在不同节点类型上轻松识别差异。如果你可以扩展上述,我会给它另一个裂缝 – Chris 2012-02-16 19:51:01