How to merge 2 XML files with C#
I have two XML files and I want to merge this two files to one. But how? I have tried a lot but nothing helps. As you can see the merged XML has left the text from the second attribute if it came from the first XML. Second the Element have to be ordered by there Id/Name/whatevername the first attribute have. Third if a node doesn't exist in XML 2 then it has to be created at the same plac开发者_C百科e as in XML 1.
The XML files showed here are just a fragment of the whole XML, there 're a lot more attributes names.
How can I do this with C#?
XML 1
<APPLICATION>
<AC>
<CLASS Name="Hello1" Capt="do1"/>
<CLASS Name="Hello2" Capt="do2"/>
<CLASS Name="Hello5" Capt="do5"/>
<CLASS Name="Hello8" Capt="do8"/>
</AC>
<BO>
<ITEM Id="1" DefaultValue="name1"/>
<ITEM Id="3" DefaultValue="name3"/>
<ITEM Id="11" DefaultValue="name11"/>
<ITEM Id="12" DefaultValue="name12">
<VAL>
<REASON Id="Job1" SecondOne="Hallo"/>
</VAL>
</ITEM>
</BO>
<POP Id="Green" Value="Monster"/>
<POP Id="Blue" Value="Doggie"/>
XML 2
<APPLICATION>
<AC>
<CLASS Name="Hello1" Capt="dodo1"/>
<CLASS Name="Hello2" Capt="dodo2"/>
<CLASS Name="Hello3" Capt="dodo3"/>
<CLASS Name="Hello9" Capt="dodo9"/>
</AC>
<CARS Wheel="Fore" Default="45x255xZ"/>
<CARS Wheel="BACK" Default="45x255xZ"/>
<CARS Wheel="SPARE" Default="45x255xZ"/>
<BO>
<ITEM Id="1" DefaultValue="namename1"/>
<ITEM Id="3" DefaultValue=""/>
<ITEM Id="9" DefaultValue="name11"/>
<ITEM Id="10" DefaultValue="name12">
<VAL>
<REASON Id="Job1" SecondOne="Hallo"/>
</VAL>
</ITEM>
</BO>
XML should look like this after merge:
<APPLICATION>
<AC>
<CLASS Name="Hello1" Capt="dodo1"/>
<CLASS Name="Hello2" Capt="dodo2"/>
<CLASS Name="Hello3" Capt="dodo3"/>
<CLASS Name="Hello5" Capt=""/>
<CLASS Name="Hello8" Capt=""/>
<CLASS Name="Hello9" Capt="dodo9"/>
</AC>
<CARS Wheel="Fore" Default="45x255xZ"/>
<CARS Wheel="BACK" Default="45x255xZ"/>
<CARS Wheel="SPARE" Default="45x255xZ"/>
<BO>
<ITEM Id="1" DefaultValue="namename1"/>
<ITEM Id="3" DefaultValue=""/>
<ITEM Id="9" DefaultValue="name11"/>
<ITEM Id="10" DefaultValue="name12">
<VAL>
<REASON Id="Job1" SecondOne="Hallo"/>
</VAL>
</ITEM>
<ITEM Id="11" DefaultValue=""/>
<ITEM Id="12" DefaultValue="">
<VAL>
<REASON Id="Job1" SecondOne=""/>
</VAL>
</ITEM>
</BO>
<POP Id="Green" Value=""/>
<POP Id="Blue" Value=""/>
Thanx for all the answers, but still I have the problem that I don't know how the tagnames are, so I cann't hardcode the tags.
I just have to give you an example what it can look like. But the next time I get my XML files, the tags from above can be totally different. That is where the problem is. So I cann't say new XElement("BO", boChildren), because the next time this tag doesn't exist anymore.
Or I cann't hardcode this ==> var cars = xDocuments.SelectMany(x => x.Root.Elements("CARS")).Merge(); because the next time I get my XML files "CARS" doesn't exist anymore.
I think you can do this with Linq to XML. Create separate queries for each segment (AC, BO, CARS, POP) where you join them together and then put those together into a new document.
Here's a little snippet to get you started:
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace XML_Merge {
class Program {
static void Main(string[] args) {
// load two xdocs
var x1 = XDocument.Load("x1.xml");
var x2 = XDocument.Load("x2.xml");
// select the CLASS nodes from each
var c1 = x1.Descendants("AC").First().Descendants("CLASS");
var c2 = x2.Descendants("AC").First().Descendants("CLASS");
// this one gives the distinct union of the two, you can put that into the result xdoc.
var cComb =
c1
.Union(c2)
.Distinct(new ClassComparer()) // this uses the IEqualityComparer from below
.OrderBy(c => c.Attribute("Name").Value);
}
}
// This is required for Union to work. (Also Intersect etc)
class ClassComparer : IEqualityComparer<XElement> {
public bool Equals(XElement x, XElement y) { return x.Attribute("Name").Value == y.Attribute("Name").Value; }
public int GetHashCode(XElement obj) { return obj.Attribute("Name").Value.GetHashCode(); }
}
}
Just repeat for the other nodes in the source docs and then put it all together.
Good luck,
Gert-Jan
i suggest you dont do that with C#. Try to use XSLT where you can, which is mainly used for transforming xmls.
if you do want to use C#, then use new C# 3.5/4 XDocument. It has nice LINQ based syntax which makes it easier to work with.
You can use XmlDocument class. Check this link: http://support.microsoft.com/kb/311530
You can have something like this:
class APPLICATION
{
public APPLICATION()
{
this.Classes = new List<CLASS>();
this.Cars = new List<CARS>();
this.Items = new List<ITEM>();
this.Pops = new List<POP>();
}
public List<CLASS> Classes { get; set; }
public List<CARS> Cars { get; set; }
public List<ITEM> Items { get; set; }
public List<POP> Pops { get; set; }
public override string ToString()
{
string toString = string.Empty;
using (MemoryStream stream = new MemoryStream())
{
using (XmlTextWriter writer = new XmlTextWriter(stream, Encoding.UTF8))
{
writer.Formatting = Formatting.Indented;
writer.Indentation = 5;
writer.WriteStartDocument();
writer.WriteStartElement("APPLICATION");
writer.WriteStartElement("AC");
if (this.Classes != null && Classes.Count > 0)
{
foreach (CLASS c in Classes)
{
writer.WriteStartElement("CLASS");
writer.WriteAttributeString("Name", c.Name);
writer.WriteAttributeString("Capt", c.Capt);
writer.WriteEndElement(); //CLASS
}
}
writer.WriteEndElement(); //AC
if (this.Cars != null && Cars.Count > 0)
{
foreach (CARS c in Cars)
{
writer.WriteStartElement("CARS");
writer.WriteAttributeString("Wheel", c.Wheel);
writer.WriteAttributeString("Default", c.Default);
writer.WriteEndElement(); //CARS
}
}
writer.WriteStartElement("BO");
if (this.Items != null && Items.Count > 0)
{
foreach (ITEM c in Items)
{
writer.WriteStartElement("ITEM");
writer.WriteAttributeString("Id", c.Id);
writer.WriteAttributeString("DefaultValue", c.DefaultValue);
if (c.Reason != null)
{
writer.WriteStartElement("VAL");
writer.WriteStartElement("REASON");
writer.WriteAttributeString("Id", c.Reason.Id);
writer.WriteAttributeString("SecondOne", c.Reason.SecondOne);
writer.WriteEndElement(); //ITEM
writer.WriteEndElement(); //VAL
}
writer.WriteEndElement(); //ITEM
}
}
writer.WriteEndElement(); //BO
writer.WriteEndElement(); //APPLICATION
writer.WriteEndDocument();
writer.Flush();
stream.Position = 0;
XmlTextReader reader = new XmlTextReader(stream);
reader.MoveToContent();
toString = reader.ReadOuterXml();
writer.Close();
stream.Close();
}
}
return toString;
}
}
public class REASON
{
public REASON()
{
Id = string.Empty;
SecondOne = string.Empty;
}
public string Id { get; set; }
public string SecondOne { get; set; }
}
public class ITEM
{
public ITEM()
{
Id = string.Empty;
DefaultValue = string.Empty;
}
public string Id { get; set; }
public string DefaultValue { get; set; }
public REASON Reason { get; set; }
}
public class CARS
{
public CARS()
{
Wheel = string.Empty;
Default = string.Empty;
}
public string Wheel { get; set; }
public string Default { get; set; }
}
public class CLASS
{
public CLASS()
{
Name = string.Empty;
Capt = string.Empty;
}
public string Name { get; set; }
public string Capt { get; set; }
}
public class POP
{
public POP()
{
Id = string.Empty;
Value = string.Empty;
}
public string Id { get; set; }
public string Value { get; set; }
}
And use it like this:
APPLICATION application = new APPLICATION();
application.Classes = ... //Populate this with classes read from xml 1 and 2.
application.Cars = ... //Populate this with cars read from xml 1 and 2.
application.Items = ... //Populate this with items read from xml 1 and 2.
application.Pops = ... //Populate this with pops read from xml 1 and 2.
string yourXmlString = application.ToString();
Here is some code to get you started. However, you have some very specific requirements how the merged elements are generated fromt the original elements. You will have to implement that in an extension method:
var xDocuments = new[] { XDocument.Parse(xml1), XDocument.Parse(xml2) };
var acChildren = xDocuments.SelectMany(x => x.Root.Elements("AC"))
.SelectMany(x => x.Elements()).Merge();
var cars = xDocuments.SelectMany(x => x.Root.Elements("CARS")).Merge();
var boChildren = xDocuments.SelectMany(x => x.Root.Elements("BO"))
.SelectMany(x => x.Elements()).Merge();
var pops = xDocuments.SelectMany(x => x.Root.Elements("POP")).Merge();
var mergedXDocument = new XDocument(
new XElement("APPLICATION",
new XElement("AC", acChildren),
cars,
new XElement("BO", boChildren),
pops
)
);
Here is the template for the extension method:
public static class Extensions {
public static IEnumerable<XElement> Merge(this IEnumerable<XElement> xElements) {
// Implement the requirement:
// "the merged XML has left the text from the second attribute if it came from
// the first XML"
}
}
精彩评论