xml file comparison - c#

Is there any way to compare two XML files in C#? I only want to compare the nodes of the first file with that of of second file. I don't want to append the missing nodes.
Is there any way to do this?
Here's what I have tried:
var docA = XDocument.Parse(#"<mind_layout></mind_layout>");
var docB = XDocument.Parse(#"<mind_layout></mind_layout>");
var rootNameA = docA.Root.Name;
var rootNameB = docB.Root.Name;
var equalRootNames = rootNameB.Equals(rootNameA);
var descendantsA = docA.Root.Descendants();
var descendantsB = docB.Root.Descendants();
for (int i = 0; i < descendantsA.Count(); i++)
{
var descendantA = descendantsA.ElementAt(i);
var descendantB = descendantsB.ElementAt(i);
var equalChildNames = descendantA.Name.Equals(descendantB.Name);
var valueA = descendantA.Value;
var valueB = descendantA.Value;
var equalValues = valueA.Equals(valueB);
}
where <mind_layout> is the root node in both the files.

If you just want to compare the file contents (including, for example, indentation), you coud do:
if (File.ReadAllText(#"C:\path\to\file1.xml") == File.ReadAllText(#"C:\path\to\file2.xml"))
{
// Same TEXT content
}
(Warning: this is not the most optimized check you could do!)
If you want to compare the XML content (regardless of the formatting), you can do:
var doc1 = XDocument.Load(File.OpenRead(#"C:\path\to\file1.xml"));
var doc2 = XDocument.Load(File.OpenRead(#"C:\path\to\file2.xml"));
if (XDocument.DeepEquals(doc1, doc2))
{
// Same XML content
}

Related

Need help retrieving XML data using Linq

I am trying to retrieve data from an XML file and return the parsed data in a list. Depending on what I use to access the data (Element or Attributes) I either get null (in case of Element) or something I cannot decipher (in case of Attributes).
XML Looks like this:
<DATA_RESPONSE>
<HEADER>
<MSGID>IS20101P:091317125610:98::34:0</MSGID>
</HEADER>
<DATA>
<ROW ID='IS20101P' PE_NAME='APP-029' PE_ID='4' CODE='4829' DATA='5,1,500,1' />
<ROW ID='IS20101P' PE_NAME='APPS-029' PE_ID='4' CODE='4829' DATA='4,1,500,1' />
...
</DATA>
<SUMMARY>
</SUMMARY>
<ERRORS>
</ERRORS>
</DATA_RESPONSE>
I am using the following to get the data. I read the file and store XML in a string and call a method with this string as argument:
public static Hashtable GetIDSData(string sXMLString)
{
Hashtable result = new Hashtable();
result.Add("Success", false);
result.Add("ErrorMessage", "");
result.Add("ID", "");
result.Add("PE_NAME", "");
result.Add("PE_ID", "");
result.Add("CODE", "");
result.Add("DATA", "");
xmlDoc.InnerXml = sXMLString;
XmlElement root = xmlDoc.DocumentElement;
XDocument doc = XDocument.Parse(sXMLString);
XmlNode node = xmlDoc.SelectSingleNode("DATA_RESPONSE/DATA");
if (node != null)
{
var AddressInfoList = doc.Root.Descendants("ROW").Select(Address => new
{
ID = Address.Attributes("ID")?.ToString(),
PEName = Address.Attributes("PE_NAME")?.ToString(),
PEID = Address.Attributes("PE_ID")?.ToString(),
Code = Address.Attributes("CODE")?.ToString(),
Data = Address.Attributes("DATA")?.ToString(),
}).ToList();
foreach (var AddressInfo in AddressInfoList)
{
if (string.IsNullOrEmpty(AddressInfo.Code))
{
result["Success"] = false;
result["ErrorMessage"] = "Invalid Code; code is empty.";
}
else
{
result["Success"] = true;
result["ErrorMessage"] = "";
result["ID"] = AddressInfo.ID;
result["PE_NAME"] = AddressInfo.PEName;
result["PE_ID"] = AddressInfo.PEID;
result["CODE"] = AddressInfo.Code;
result["DATA"] = AddressInfo.Data;
}
}
return result;
}
In Linq section, if I use Address.Element("ID").Value, I get null returned.
There is no namespace used in XML.
First off, the GetIDSData() method does not compile as is, because at the line xmlDoc.InnerXml = sXMLString, xmlDoc has not been defined.
I'm assuming you want xmlDoc to be an XmlDocument loaded with the contents of the sXMLString parameter, so I'm changing that line to:
XmlDocument xmlDoc = new XmlDocument {InnerXml = sXMLString};
Also, your root variable is never used, so I removed it for clarity.
Now as for the main part of your question, given your current syntax, you are calling .ToString() on a collection of attributes, which is obviously not what you want. To fix this, when you're iterating the AddressInfoList, You want to fetch the attribute values like:
ID = Address.Attributes("ID")?.Single().Value
or
ID = address.Attribute("ID")?.Value
...rather than Address.Attributes("ID")?.ToString() as you have above.
You are not selecting values of attributes. In your code you are selecting attributes. Not sure what are you trying to achieve, but here is my modified version of your code that loads all elements into DataTable
public static DataTable GetIDSData(string sXMLString)
{
DataTable result = new DataTable();
result.Columns.Add("Success");
result.Columns.Add("ErrorMessage");
result.Columns.Add("ID");
result.Columns.Add("PE_NAME");
result.Columns.Add("PE_ID");
result.Columns.Add("CODE");
result.Columns.Add("DATA");
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.InnerXml = sXMLString;
XmlElement root = xmlDoc.DocumentElement;
XDocument doc = XDocument.Parse(sXMLString);
XmlNode node = xmlDoc.SelectSingleNode("DATA_RESPONSE/DATA");
if (node != null)
{
var AddressInfoList = doc.Root.Descendants("ROW").Select(Address => new
{
ID = Address.Attributes("ID").Select(i=>i.Value) ,
PEName = Address.Attributes("PE_NAME").Select(i=>i.Value),
PEID = Address.Attributes("PE_ID").Select(i=>i.Value),
Code = Address.Attributes("CODE").Select(i=>i.Value),
Data = Address.Attributes("DATA").Select(i=>i.Value),
}).ToList();
AddressInfoList.ForEach(e =>
{
e.Code.ToList().ForEach(c =>
{
DataRow row = result.NewRow();
if (!string.IsNullOrEmpty(c))
{
row["Success"] = true;
row["ErrorMessage"] = "";
row["ID"] = e.ID.First();
row["PE_NAME"] = e.PEName.First();
row["PE_ID"] = e.PEID.First();
row["CODE"] = e.Code.First();
row["DATA"] = e.Data.First();
}
else
{
row["Success"] = false;
row["ErrorMessage"] = "Invalid Code; code is empty.";
}
result.Rows.Add(row);
});});
result.Dump();
return result;
}
return result;
}
And this is the result that you will get in your datatable.
ID = Address.Attributes("ID")?.ToString(),
You want to use Attribute(name) (without s) instead:
ID = Address.Attributes("ID")?.Value,

Nested XML from list with needed data on multiple lines

I need to format an XML to a given hierarchy from a list (List<>). The list contains, for the banking information, data spread across multiple rows as shown in the image.
The XML output needs to be formatted like this:
<ROOT>
<DocumentElement>
<Supplier>
<Company>STV</Company>
<Code>000199</Code>
<Name>TrafTrans</Name>
<BankAccounts>
<SupplierBankAccount>
<Bban>220-012510-63</Bban>
<Name>B1</Name>
</SupplierBankAccount>
<SupplierBankAccount>
<Bban>RUIL</Bban>
<Name>RUIL</Name>
</SupplierBankAccount>
</BankAccounts>
<SupplierAddresses>
<SupplierAddress>
<Type>PostalAddress</Type>
<Name>Loc TrafTrans</Name>
<IsDefault>true</IsDefault>
<AddressParts>
<SupplierAddressPart>
<AddressPartKey>STREET_NAME</AddressPartKey>
<AddressPartText>Somewhere</AddressPartText>
</SupplierAddressPart>
<SupplierAddressPart>
<AddressPartKey>COUNTRY</AddressPartKey>
<AddressPartText>SPAIN</AddressPartText>
</SupplierAddressPart>
</AddressParts>
</SupplierAddress>
</SupplierAddresses>
</Supplier>
</DocumentElement>
</ROOT>
I already have a method that converts a list to an XML and returns a string. But the problem is that this only formats one item from the list and there could be additional info in the following items.
public static string SuppliersToXML(List<SupplierItem> supplier)
{
CultureInfo ci = new CultureInfo("en-US");
XmlDocument doc = new XmlDocument();
var root = doc.CreateElement("ROOT");
var rootNode = doc.AppendChild(root);
var docElem = doc.CreateElement("DocumentElement");
var docElemNode = rootNode.AppendChild(docElem);
foreach (var item in supplier)
{
var supplierElem = doc.CreateElement("Supplier");
var companyElem = (XmlNode)doc.CreateElement("Company");
companyElem.InnerText = item.Company.ToString();
//more code...
supplierElem.AppendChild(companyElem);
//more code...
}
return doc.OuterXml;
}
I have found the answer myself, it may not be the prettiest code ever written. But it does the job.
public static string SuppliersToXML(List<SupplierItem> supplier)
{
//A distinct select is needed because bank info can be on multiple lines. So first a list is generated with correct info except for bank information
List<SupplierItem> distinctsupplier = supplier
.GroupBy(p => p.Code)
.Select(g => g.First())
.ToList();
CultureInfo ci = new CultureInfo("en-US");
XmlDocument doc = new XmlDocument();
var root = doc.CreateElement("ROOT");
var rootNode = doc.AppendChild(root);
var docElem = doc.CreateElement("DocumentElement");
var docElemNode = rootNode.AppendChild(docElem);
foreach (var item in distinctsupplier)
{
var supplierElem = doc.CreateElement("Supplier");
var companyElem = (XmlNode)doc.CreateElement("Company");
companyElem.InnerText = item.Company.ToString();
var codeElem = (XmlNode)doc.CreateElement("Code");
codeElem.InnerText = item.Code.ToString();
var codeName = (XmlNode)doc.CreateElement("Name");
codeName.InnerText = item.Name.ToString();
//supplieridentifier part
var supplierIdsElem = doc.CreateElement("SupplierIdentifiers");
var supplierIdElem = doc.CreateElement("SupplierIdentifier");
var supplierIdValueElem = (XmlNode)doc.CreateElement("SupplierIDValue");
supplierIdValueElem.InnerText = item.SupplierIDValue;
//supplieraddress part
var supplierAddressesElem = doc.CreateElement("SupplierAddresses");
var supplierAddressElem = doc.CreateElement("SupplierAddress");
var supplierTypeValueElem = (XmlNode)doc.CreateElement("Type");
supplierTypeValueElem.InnerText = item.Type;
var supplierNameValueElem = (XmlNode)doc.CreateElement("Name");
supplierNameValueElem.InnerText = item.AddressName;
var supplierDefaultValueElem = (XmlNode)doc.CreateElement("IsDefault");
supplierDefaultValueElem.InnerText = item.AddressIsDefault;
//address part
var AddressPartElem = doc.CreateElement("AddressParts");
var supplierAddressPartsElem = doc.CreateElement("SupplierAddressPart");
//Street
var AddressPartElemStreetKeyElem = (XmlNode)doc.CreateElement("AddressPartKey");
AddressPartElemStreetKeyElem.InnerText = item.AddressStreetKey;
var AddressPartElemStreetValueElem = (XmlNode)doc.CreateElement("AddressPartText");
AddressPartElemStreetValueElem.InnerText = item.AddressStreetText;
//Country
var AddressPartElemCountryKeyElem = (XmlNode)doc.CreateElement("AddressPartKey");
AddressPartElemCountryKeyElem.InnerText = item.AddressCountryKey;
var AddressPartElemCountryValueElem = (XmlNode)doc.CreateElement("AddressPartText");
AddressPartElemCountryValueElem.InnerText = item.AddressCountryText;
//add elements to supplierelem
supplierElem.AppendChild(companyElem);
supplierElem.AppendChild(codeElem);
supplierElem.AppendChild(codeName);
//bankaccounts part
var bankAccountElem = doc.CreateElement("BankAccounts");
//select all rows that contain multiple lines, so the bank info can be extracted, for a certain supplier
var bankInformation = supplier.Where(s => s.Code == item.Code).ToList();
foreach (var bankitem in bankInformation)
{
if (item.Code == bankitem.Code)
{
var bankAccountSupplElem = doc.CreateElement("SupplierBankAccount");
var bankAccountBbanElem = doc.CreateElement("Bban");
var bankAccountBbanValueElem = (XmlNode)doc.CreateElement("Bban");
bankAccountBbanValueElem.InnerText = bankitem.Bban;
var bankAccountNameElem = doc.CreateElement("Name");
var bankAccountNameValueElem = (XmlNode)doc.CreateElement("Name");
bankAccountNameValueElem.InnerText = bankitem.BankName;
var bankAccountElemNode = supplierElem.AppendChild(bankAccountElem);
var bankAccountSupplElemNode = bankAccountElemNode.AppendChild(bankAccountSupplElem);
bankAccountSupplElemNode.AppendChild(bankAccountBbanValueElem);
bankAccountSupplElemNode.AppendChild(bankAccountNameValueElem);
}
}
//add address elements to supplierelem
var supplierAddressesElemNode = supplierElem.AppendChild(supplierAddressesElem);
var supplierAddressElemNode = supplierAddressesElemNode.AppendChild(supplierAddressElem);
supplierAddressElemNode.AppendChild(supplierTypeValueElem);
supplierAddressElemNode.AppendChild(supplierNameValueElem);
supplierAddressElemNode.AppendChild(supplierDefaultValueElem);
//add addressparts to supplieraddressesnode
//Street
var AddressPartElemNode = supplierAddressElemNode.AppendChild(AddressPartElem);
var supplierAddressPartsElemNode = AddressPartElemNode.AppendChild(supplierAddressPartsElem);
supplierAddressPartsElemNode.AppendChild(AddressPartElemStreetKeyElem);
supplierAddressPartsElemNode.AppendChild(AddressPartElemStreetValueElem);
//Country (first reinitialize supplieraddresspart)
supplierAddressPartsElem = doc.CreateElement("SupplierAddressPart");
var supplierAddressPartsCountryElemNode = AddressPartElemNode.AppendChild(supplierAddressPartsElem);
supplierAddressPartsCountryElemNode.AppendChild(AddressPartElemCountryKeyElem);
supplierAddressPartsCountryElemNode.AppendChild(AddressPartElemCountryValueElem);
//add all supplierelements to docelemnode
docElemNode.AppendChild(supplierElem);
}
return doc.OuterXml;
}

XML with elements of the same name

How do I edit the data in each Address Line, since they all have the same name?
<StructuredAddress>
<AddressLine></AddressLine>
<AddressLine></AddressLine>
<AddressLine></AddressLine>
</StructuredAddress>
My code so far, which doesn't work, is this. How do I isolate each AddressLine indivisually and insert the appropriate data?
XElement StructuredAddress = PatientAddress.Descendants("StructuredAddress").First();
StructuredAddress.Element("AddresLine").Value = cc.address1;
StructuredAddress.Element("AddresLine").Value = cc.address2;
StructuredAddress.Element("AddresLine").Value = cc.address3;
You may want to access each <AddressLine> using it's index :
XElement StructuredAddress = PatientAddress.Descendants("StructuredAddress").First();
var address = StructuredAddress.Elements("AddresLine").ToList();
address[0].Value = cc.address1;
address[1].Value = cc.address2;
address[2].Value = cc.address3;
You could iterate them which will present them in ordinal order:
foreach (var addressLine in StructuredAddress.Elements("AddressLine"))
{
addressLine.Value = ...
}
Or by index;
var lines = StructuredAddress.Elements("AddressLine").ToList();
lines[0].Value = "...";

Next node in xml with same element name

I've been working on this problem for a few hours now and I have searched all around with no luck for a solution :(
What I am trying to do is print out names of the nodes, what i have is the amount of nodes that exist so I know how many times to loop but am having the hardest of times retrieving the values
What I have tried:
int num = Convert.ToInt32(queuecount);
var jobs = QueueXML.SelectSingleNode(xpathjobsfilename).InnerText;
PreviousQueue = jobs.ToString();
//foreach(loop < num)
//{
// if (CurrentQueue == PreviousQueue)
// {
// }
// else
// {
// resultsListView.Items.Clear();
// resultsListView.Items.Add(jobs[num]);
// }
// loop++;
//}
foreach (char JobName in jobs.ToString())
{
if (CurrentQueue == PreviousQueue)
{
}
else
{
resultsListView.Items.Clear();
resultsListView.Items.Add(jobs[num]);
}
}
Edit: Example XML
<jobs>
<job>
<timeleft>0:00:00</timeleft>
<mb>1419.60536003</mb>
<msgid></msgid>
<filename>Extended_Final</filename>
<mbleft>1274.33209419</mbleft>
<id>nzo_i7qxxq</id>
</job>
<job>
<timeleft>0:00:00</timeleft>
<mb>9.22459220886</mb>
<msgid></msgid>
<filename>Video2</filename>
<mbleft>9.22459220886</mbleft>
<id>2m3dv5</id>
</job>
</jobs>
I want to retrieve the job details for each individual jobs
Use this code to loop through your job-nodes.
XmlDocument doc = new Windows.Data.Xml.Dom.XmlDocument();
doc.Load(#"/path/to/xml/file");
foreach (XmlNode job in doc.SelectNodes("/jobs/job"))
{
string filename = job.SelectSingleNode("filename").InnerText;
double mbleft = double.Parse(job.SelectSingleNode("mbleft").InnerText);
}
I am not quite sure what you want to do with it. If you want to use that information throughout your program, I'd create a job datatype and parse the XML document to a List<Job>. In any case the above code will enable you to access the information you are after.
Use LINQ2XML
XElement doc=XElement.Load("yourXMLfile.xml");
string timeleft,mb,msgid,filename,mbleft,id;
foreach(XElement elm in doc.Descendants().Elements("job"))
{
timeleft = elm.Element("timeleft").Value; //time left value
mb = elm.Element("mb").Value; //mb value
msgid = elm.Element("msgid").Value; //msgid value
filename = elm.Element("filename").Value; //filename value
mbleft = elm.Element("mbleft").Value; //mbleft value
id = elm.Element("id").Value; //id value
}
Below is the code to add individual job details to a List of dictionary.
It eliminates selecting single node in the loop when there are lot of child nodes. And, more generic.
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.LoadXml(yourXmlString);
if (doc.HasChildNodes)
{
System.Xml.XmlNodeList jobLst = doc.DocumentElement.ChildNodes;
System.Collections.Generic.Dictionary<string, string> jobDescription;
var lstjobDescription = new System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, string>>();
string name;
for (int i = 0; i < jobLst.Count; i++)
{
var responseDoc = new System.Xml.XmlDocument();
responseDoc.LoadXml(jobLst[i].OuterXml);
jobDescription = new System.Collections.Generic.Dictionary<string, string>();
foreach (System.Xml.XmlNode node in responseDoc.SelectNodes("//job/*")) //select all nodes of Job
{
jobDescription.Add(node.Name, node.InnerText);
}
lstjobDescription.Add(jobDescription);
}
}

When doing linq to xml and displaying data in datagridview my code only parses one file in directory folder

How come during the linq to xml parse my code is only reading one fo the xml files in the folder rather than all of them?
private void button1_Click(object sender, EventArgs e)
{
string[] fileEntries = Directory.GetFiles(#"", "*.cfg*");
foreach (string fileName in fileEntries)
{
XDocument doc = XDocument.Load(fileName);
var query = from x in doc.Descendants("")
select new
{
MaxChild = x.Element("Max").Value,
MinChild = x.Element("Min").Value
};
var query2 = from y in doc.Descendants("")
select new
{
MaxChild = y.Element("Max").Value,
MinChild = y.Element("Min").Value
};
var query3 = from z in doc.Descendants("")
select new
{
MaxChild = z.Element("Max").Value,
MinChild = z.Element("Min").Value
};
var bs3 = new BindingSource { DataSource = query.Union(query2.Union(query3)) };
dataGridView1.AutoGenerateColumns = true;
dataGridView1.AutoSize = true;
dataGridView1.DataSource = bs3;
dataGridView1.Columns[0].HeaderText = "Max";
dataGridView1.Columns[1].HeaderText = "Min";
}
}
You're iterating over the files in the directory - but simply reassigning the datasource each time. In other words, it's exactly the same problem as in your earlier question, except that instead of it being to do with three queries, it's to do with reassigning it for each file.
Try this instead, using a single query:
var query = from file in fileEntries
let doc = XDocument.Load(file)
from x in doc.Descendants("XAxisCalib")
.Concat(doc.Descendants("YAxisCalib"))
.Concat(doc.Descendants("ZAxisCalib"))
select new
{
// Include this to see the file for each value
File = file,
MaxChild = x.Element("Max").Value,
MinChild = x.Element("Min").Value
};
var bs3 = new BindingSource { DataSource = query };
dataGridView1.AutoGenerateColumns = true;
dataGridView1.AutoSize = true;
dataGridView1.DataSource = bs3;
EDIT: Okay, if you know there's going to be exactly one of each element, it's fairly easy:
var query = from file in fileEntries
let doc = XDocument.Load(file)
let x = doc.Descendants("XAxisCalib").Single()
let y = doc.Descendants("YAxisCalib").Single()
let z = doc.Descendants("ZAxisCalib").Single()
select new
{
// Include this to see the file for each value
File = file,
MaxChildX = x.Element("Max").Value,
MinChildX = x.Element("Min").Value,
MaxChildY = y.Element("Max").Value,
MinChildY = y.Element("Min").Value,
MaxChildZ = z.Element("Max").Value,
MinChildZ = z.Element("Min").Value
};

Categories