2012-02-16 39 views
1

我試圖審計一些定製的軟件中使用的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中使用它,我不認爲?)

回答

0

這裏的重要部分是後代查詢。它將第一個文檔中的每個元素都轉換爲其祖先列表中的每個元素,其中每個元素都包含元素的名稱,並且它是同名的兄弟中的索引。我認爲這可以以某種方式用於加入,但我不知道如何使用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

0

我有一個通用的代碼塊庫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