【问题标题】:How can I merge XML files?如何合并 XML 文件?
【发布时间】:2011-08-28 01:12:39
【问题描述】:

我有两个具有相同架构的 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>

【问题讨论】:

  • 在文本编辑器中剪切和粘贴?
  • @BoltClock 我更喜欢脚本,因为这些 xml 文件是自动生成的,并且会经常更改。现在我的小号大约有 2000 行长,包含多个需要合并的区域。
  • 什么样的脚本?如果有编写此脚本的首选语言,您可能希望将其添加到标签中。
  • 我们使用什么语言? - C#? XSLT?
  • @Jon 不管是最简单的...我没有过多地使用 XML,所以我希望有一些简单的工具可以组合 xml 文件。通常我使用 C#

标签: c# xml


【解决方案1】:

转发https://www.perlmonks.org/?node_id=127848的答案

将以下内容粘贴到 perl 脚本中

use strict;
require 5.000;

use Data::Dumper;
use XML::Simple;
use Hash::Merge;

my $xmlFile1 = shift || die "XmlFile1\n";
my $xmlFile2 = shift || die "XmlFile2\n";

my %config1 = %{XMLin ($xmlFile1)};
my %config2 = %{XMLin ($xmlFile2)};
my $merger = Hash::Merge->new ('RIGHT_PRECEDENT');
my %newhash = %{ $merger->merge (\%config1, \%config2) };
# XMLout (\%newhash, outputfile => "newfile", xmldecl => 1, rootname => 'config');
print XMLout (\%newhash);

【讨论】:

    【解决方案2】:

    您可以这样做,使用 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)
    

    【讨论】:

      【解决方案3】:

      “自动 XML 合并”听起来是一个相对简单的要求,但是当您深入了解所有细节时,它会很快变得复杂。对于更具体的任务,与 c# 或 XSLT 合并会更容易,例如在 answer 中用于 EF 模型。也可以选择使用工具来协助手动合并(请参阅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 sn-ps 会产生想要的结果,但是如果输入的 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>
      

      【讨论】:

      • 这正是我所需要的。非常感谢。 :)
      【解决方案4】:

      我最终使用了 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 节点。

      【讨论】:

      • @"手动指定节点" - 这在某种程度上是预期的,无论它是 c# 还是 XSLT。否则,在原始示例中,无法判断合并是应该产生 2 个叶子,每个叶子有 4 个节点,还是产生 4 个叶子,每个叶子有 2 个节点。
      • 如果节点定义相同,则节点应该合并。在上面的例子中,它应该产生 2 个叶子,每个叶子有 4 个节点,因为两个叶子节点是相同的。
      • 啊,我明白了,谢谢。 (并且可能 identical 意味着 equivalent?)在一般形式中,原始问题是一个非常有趣的练习,但看起来您已经为更具体的情况找到了解决方案,其中元素由@Name 标识。
      • @DK 我很想知道你是否还能找到一种简单的通用方法。我只选择了这个解决方案,因为我不耐烦并且想要尽快
      • 我已经发布了我在周末玩过的代码。它使用的想法与您的想法几乎相同。我的观点是通用任务可能会变得非常复杂,例如对于 EF 模型,使用已有的模型会更容易。
      【解决方案5】:

      这是一个类似这样的简单 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>
      

      【讨论】:

      • 我不明白如何使用 xlst...你能给我指出一个好的起点吗?
      • 我实际上一直在尝试找出这里找到的脚本的 xlst:www2.informatik.hu-berlin.de/~obecker/XSLT/#merge 但我最终放弃了,只是制作了自己的 C# 脚本。不过还是谢谢。
      【解决方案6】:

      vimdiff file_a file_b 只是一个例子

      BeyondCompare 是我在 Windows 上时的最爱http://www.scootersoftware.com/

      【讨论】:

      • 这只是向我展示了差异......我想实际合并节点,而不是让它们解决差异。
      【解决方案7】:

      如果格式总是这样,那么这个方法没有问题:

      从第一个文件中删除最后两行并附加第二个文件,同时删除前两行。

      看看Linux命令headtail可以删除第一行和最后两行。

      【讨论】:

      • xml 文件中有多个区域要合并,所以这不起作用。我将扩展我的示例以表明这一点
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多