“自动 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>