2011-05-18 71 views
5

我有兩個xml文件,都具有相同的架構,我想合併成一個單一的XML文件。是否有捷徑可尋?如何合併XML文件?

例如,

<Root> 
    <LeafA> 
     <Item1 /> 
     <Item2 /> 
    </LeafA> 
    <LeafB> 
     <Item1 /> 
     <Item2 /> 
    </LeafB> 
</Root> 

+

<Root> 
    <LeafA> 
     <Item3 /> 
     <Item4 /> 
    </LeafA> 
    <LeafB> 
     <Item3 /> 
     <Item4 /> 
    </LeafB> 
</Root> 

<Root> 
    <LeafA> 
     <Item1 /> 
     <Item2 /> 
     <Item3 /> 
     <Item4 /> 
    </LeafA> 
    <LeafB> 
     <Item1 /> 
     <Item2 /> 
     <Item3 /> 
     <Item4 /> 
    </LeafB> 
</Root> 
+2

在文本編輯器中剪切並粘貼? – BoltClock 2011-05-18 12:56:32

+1

@BoltClock我更喜歡一個腳本,因爲這些XML文件是自動生成的,並會頻繁更改。現在我的小單約2000行,包含多個需要合併的區域。 – Rachel 2011-05-18 12:58:08

+0

什麼樣的腳本?如果有一種首選語言來編寫此腳本,您可能希望將其添加到標籤中。 – BoltClock 2011-05-18 12:58:29

回答

10

「自動XML合併」聽起來像一個相對簡單的要求,但是當您進入所有細節時,它會變得非常複雜。與c#或XSLT合併對於更具體的任務將更容易,如EF模型的answer。使用工具輔助手動合併也可以作爲選項(請參閱this SO question)。

對於參考(並提供有關複雜的想法),這裏是從Java世界的開源例如:XML merging made easy

回到原來的問題。在任務規範中有幾個大的灰色區域:當考慮2個元素時等效(具有相同的名稱,匹配所選屬性或所有屬性,或者在父元素中也具有相同的位置);如何處理的情況時,原始的或合併的XML有多個相當於元素等

下面的代碼是假設

  • 我們只是在一瞬間關心的元素
  • 元素相當於如果元素名稱,屬性名稱和屬性值匹配
  • 一個元素不具有多個同名的屬性
  • 全部等效來自合併文檔的元素將與源XML文檔中的第一個等效元素結合使用。

// determine which elements we consider the same 
// 
private static bool AreEquivalent(XElement a, XElement b) 
{ 
    if(a.Name != b.Name) return false; 
    if(!a.HasAttributes && !b.HasAttributes) return true; 
    if(!a.HasAttributes || !b.HasAttributes) return false; 
    if(a.Attributes().Count() != b.Attributes().Count()) return false; 

    return a.Attributes().All(attA => b.Attributes(attA.Name) 
     .Count(attB => attB.Value == attA.Value) != 0); 
} 

// Merge "merged" document B into "source" A 
// 
private static void MergeElements(XElement parentA, XElement parentB) 
{ 
    // merge per-element content from parentB into parentA 
    // 
    foreach (XElement childB in parentB.DescendantNodes()) 
    { 
     // merge childB with first equivalent childA 
     // equivalent childB1, childB2,.. will be combined 
     // 
     bool isMatchFound = false; 
     foreach (XElement childA in parentA.Descendants()) 
     { 
      if (AreEquivalent(childA, childB)) 
      { 
       MergeElements(childA, childB); 
       isMatchFound = true; 
       break; 
      } 
     } 

     // if there is no equivalent childA, add childB into parentA 
     // 
     if (!isMatchFound) parentA.Add(childB); 
    } 
} 

它會產生期望的結果與原來的XML片斷,但如果輸入個XML比較複雜,有重複的元素,其結果將是更多...有趣:

public static void Test() 
{ 
    var a = XDocument.Parse(@" 
    <Root> 
     <LeafA> 
      <Item1 /> 
      <Item2 /> 
      <SubLeaf><X/></SubLeaf> 
     </LeafA> 
     <LeafB> 
      <Item1 /> 
      <Item2 /> 
     </LeafB> 
    </Root>"); 
    var b = XDocument.Parse(@" 
    <Root> 
     <LeafB> 
      <Item5 /> 
      <Item1 /> 
      <Item6 /> 
     </LeafB> 
     <LeafA Name=""X""> 
      <Item3 /> 
     </LeafA> 
     <LeafA> 
      <Item3 /> 
     </LeafA> 
     <LeafA> 
      <SubLeaf><Y/></SubLeaf> 
     </LeafA> 
    </Root>"); 

    MergeElements(a.Root, b.Root); 
    Console.WriteLine("Merged document:\n{0}", a.Root); 
} 

這裏的合併文檔展示瞭如何從文檔B相當於元素合併起來:

<Root> 
    <LeafA> 
    <Item1 /> 
    <Item2 /> 
    <SubLeaf> 
     <X /> 
     <Y /> 
    </SubLeaf> 
    <Item3 /> 
    </LeafA> 
    <LeafB> 
    <Item1 /> 
    <Item2 /> 
    <Item5 /> 
    <Item6 /> 
    </LeafB> 
    <LeafA Name="X"> 
    <Item3 /> 
    </LeafA> 
</Root> 
+1

我喜歡它,謝謝:) – Rachel 2011-05-30 17:55:28

+1

這正是我需要的。非常感謝。 :) – Yogesh 2011-09-02 14:15:47

+0

完美!這正是我一直在尋找的,謝謝! – Cranialsurge 2013-09-26 03:52:13

1

如果格式總是完全一樣=新文件沒有什麼錯用這種方法:

從第一個文件中刪除最後兩行,並刪除前兩行時附加第二個文件。

查看Linux命令headtail,它們可以刪除第一行和最後兩行。

+0

xml文件中有多個區域要合併,因此這不起作用。我將展開我的示例以顯示 – Rachel 2011-05-18 12:59:55

0

vimdiff file_a file_b僅作爲一個例子

BeyondCompare是一個最喜歡的,當我在窗口http://www.scootersoftware.com/

+0

這只是向我展示了差異......我想實際合併節點,而不是解決它們之間的差異。 – Rachel 2011-05-18 14:14:36

1

這是一個簡單的XSLT轉換,這樣的事情(你適用於文檔A.XML):

<xsl:variable name="docB" select="document('b.xml')"/> 
<xsl:template match="Root"> 
    <Root><xsl:apply-templates/></Root> 
</xsl:template> 
<xsl:template match="Root/LeafA"> 
    <xsl:copy-of select="*"/> 
    <xsl:copy-of select="$docB/Root/LeafA/*"/> 
</xsl:template> 
<xsl:template match="Root/LeafB"> 
    <xsl:copy-of select="*"/> 
    <xsl:copy-of select="$docB/Root/LeafB/*"/> 
</xsl:template> 
+0

我不明白如何使用xlst ...你能指點我一個好的起點嗎? – Rachel 2011-05-18 15:47:32

+0

我實際上一直在試圖找出xlst這裏找到的腳本:http://www2.informatik.hu-berlin.de/~obecker/XSLT/#merge,但我最終放棄了,只是做了我自己的C#腳本。不過謝謝。 – Rachel 2011-05-18 16:38:11

0

我最終使用C#並創建了自己的腳本。當我問這個問題時,我知道我可以做到這一點,但是我想知道是否有更快的方法來做到這一點,因爲我從來沒有真正使用過XML。

腳本沿着這個線路去:

var a = new XmlDocument(); 
a.Load(PathToFile1); 

var b = new XmlDocument(); 
b.Load(PathToFile2); 

MergeNodes(
    a.SelectSingleNode(nodePath), 
    b.SelectSingleNode(nodePath).ChildNodes, 
    a); 

a.Save(PathToFile1); 

而且MergeNodes()看起來是這樣的:

private void MergeNodes(XmlNode parentNodeA, XmlNodeList childNodesB, XmlDocument parentA) 
{ 
    foreach (XmlNode oNode in childNodesB) 
    { 
     // Exclude container node 
     if (oNode.Name == "#comment") continue; 

     bool isFound = false; 
     string name = oNode.Attributes["Name"].Value; 

     foreach (XmlNode child in parentNodeA.ChildNodes) 
     { 
      if (child.Name == "#comment") continue; 

      // If node already exists and is unchanged, exit loop 
      if (child.OuterXml== oNode.OuterXml&& child.InnerXml == oNode.InnerXml) 
      { 
       isFound = true; 
       Console.WriteLine("Found::NoChanges::" + oNode.Name + "::" + name); 
       break; 
      } 

      // If node already exists but has been changed, replace it 
      if (child.Attributes["Name"].Value == name) 
      { 
       isFound = true; 
       Console.WriteLine("Found::Replaced::" + oNode.Name + "::" + name); 
       parentNodeA.ReplaceChild(parentA.ImportNode(oNode, true), child); 
      } 
     } 

     // If node does not exist, add it 
     if (!isFound) 
     { 
      Console.WriteLine("NotFound::Adding::" + oNode.Name + "::" + name); 
      parentNodeA.AppendChild(parentA.ImportNode(oNode, true)); 
     } 
    } 
} 

它不是完美的 - 我必須手動指定我想合併的節點,但這對我來說是快速和容易的,因爲我幾乎不瞭解XML,所以我很高興:)

它實際上效果更好,它只合並指定節點,因爲我正在使用它來合併實體框架的edmx文件,而我只想真正合並SSDL,CDSL和MSL節點。

+0

@「手動指定節點」 - 無論它是c#還是XSLT,都會在某種程度上預期。否則,在原始示例中,不可能判斷合併是否應產生2個葉片,每個葉片4個節點,或4個葉片每個葉片2個節點。 – 2011-05-20 13:52:43

+0

如果節點定義相同,節點應該合併。在上面的例子中,由於兩個葉節點是相同的,它應該產生2個葉子,每個葉子有4個節點。 – Rachel 2011-05-20 14:17:01

+0

啊,我明白了,謝謝。 (可能*相同*意味着*等價*?)在通用形式中,原始問題是一個非常有趣的練習,但看起來您已經爲更具體的情況獲得瞭解決方案,其中元素由@Name標識。 – 2011-05-20 20:11:12

0

你可以做到這一點,是加載使用xml創建數據集併合並數據集。

Dim dsFirst As New DataSet() 
    Dim dsMerge As New DataSet() 

    ' Create new FileStream with which to read the schema. 
    Dim fsReadXmlFirst As New System.IO.FileStream(myXMLfileFirst, System.IO.FileMode.Open) 
    Dim fsReadXmlMerge As New System.IO.FileStream(myXMLfileMerge, System.IO.FileMode.Open) 

    Try 
     dsFirst.ReadXml(fsReadXmlFirst) 

     dsMerge.ReadXml(fsReadXmlMerge) 

     Dim str As String = "Merge Table(0) Row Count = " & dsMerge.Tables(0).Rows.Count 
     str = str & Chr(13) & "Merge Table(1) Row Count = " & dsMerge.Tables(1).Rows.Count 
     str = str & Chr(13) & "Merge Table(2) Row Count = " & dsMerge.Tables(2).Rows.Count 

     MsgBox(str) 

     dsMerge.Merge(dsFirst, True) 

     DataGridParent.DataSource = dsMerge 
     DataGridParent.DataMember = "rulefile" 

     DataGridChild.DataSource = dsMerge 
     DataGridChild.DataMember = "rule" 

     str = "" 
     str = "Merge Table(0) Row Count = " & dsMerge.Tables(0).Rows.Count 
     str = str & Chr(13) & "Merge Table(1) Row Count = " & dsMerge.Tables(1).Rows.Count 
     str = str & Chr(13) & "Merge Table(2) Row Count = " & dsMerge.Tables(2).Rows.Count 

     MsgBox(str)