繁体   English   中英

如何合并 XML 文件?

[英]How can I merge XML files?

我有两个 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>

“自动 XML 合并”听起来是一个相对简单的要求,但是当您深入了解所有细节时,它会很快变得复杂。 对于更具体的任务,与 c# 或 XSLT 合并会容易得多,例如在 EF 模型的答案中。 使用工具来协助手动合并也可以是一种选择(请参阅此 SO 问题)。

对于参考(并给出有关复杂性的想法),这里有一个来自 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>

如果格式总是这样,则此方法没有任何问题:

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

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

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

您可以这样做的方法是使用 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)

vimdiff file_a file_b只是一个例子

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

我最终使用 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,我很高兴 :)

实际上,它只合并指定的节点效果更好,因为我使用它来合并 Entity Framework 的 edmx 文件,而且我真的只想合并 SSDL、CDSL 和 MSL 节点。

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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM