2011-05-18 110 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)