"Automatic XML merge" sounds like a relatively simple requirement, but when you go into all the details, it gets complex pretty fast. Merge with c# or XSLT will be much easier for more specific task, like in the answer for EF model. Using tools to assist with a manual merge can also be an option (see this SO question).
For the reference (and to give an idea about complexity) here's an open-source example from Java world: XML merging made easy
Back to the original question. There are few big gray-ish areas in task specification: when 2 elements should be considered equivalent (have same name, matching selected or all attributes, or also have same position in the parent element); how to handle situation when original or merged XML have multiple equivalent elements etc.
The code below is assuming that
- we only care about elements at the moment
- elements are equivalent if element names, attribute names, and attribute values match
- an element doesn't have multiple attributes with the same name
- all equivalent elements from merged document will be combined with the first equivalent element in the source XML document.
.
// 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);
}
}
It will produce desired result with the original XML snippets, but if input XMLs are more complex and have duplicate elements, the result will be more... interesting:
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);
}
Here's merged document showing how equivalent elements from document B were combined together:
<Root>
<LeafA>
<Item1 />
<Item2 />
<SubLeaf>
<X />
<Y />
</SubLeaf>
<Item3 />
</LeafA>
<LeafB>
<Item1 />
<Item2 />
<Item5 />
<Item6 />
</LeafB>
<LeafA Name="X">
<Item3 />
</LeafA>
</Root>