Using Linq to XML to parse XML file with separate metadata element - c#

I have an xml file with the following format that I'm trying to parse with C# Linq to XML. The problem is that it has this separate metadata element which is the only thing identifying the values below. Is there a good way to do this? I don't have any power to change the format of this file.
<?xml version="1.0" encoding="utf-8?>
<dataset xmlns="http://developer.cognos.com/schemas/xmldata/1/" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance">
<metadata>
<item name="Address Line 1" type="xs:string" length="512"/>
<item name="Address Line 2" type="xs:string" length="512"/>
<item name="Date Of Birth" type="xs:dateTime"/>
</metadata>
<data>
<row>
<value>123 Main St</value>
<value xs:nil="true" />
<value>1970-01-01T00:00:00</value>
</row>
<row>
<value>125 Main St</value>
<value>Apt 1</value>
<value>1980-01-01T00:00:00</value>
</row>
</data>
</dataset>
The actual file has about 30 item and corresponding value elements in each row and several hundred row elements following this format. I'm basically looking for the best way to match up the metadata to the values. If Linq to XML is not the best way to achieve this, I'm open to other suggestions that work with C# and .NET 4.5.
I tried just collecting the metadata items in a list and using indices to match them to the values, but it seems to build the list in an arbitrary order, so I'm not sure I can rely on that ordering to identify the values.
XDocument xdoc = XDocument.Load(#"Export.xml");
XNamespace xns = "http://developer.cognos.com/schemas/xmldata/1/";
var metadataQuery = from t in xdoc.Descendants(xns + "item") select t;
List<XElement> metadata = metadataQuery.ToList(); // This list appears to be ordered randomly

The order of elements is always the same, in the order in which they appear in the xml file.
var xdoc = XDocument.Load(#"Export.xml");
XNamespace xns = "http://developer.cognos.com/schemas/xmldata/1/";
var metadata = xdoc.Descendants(xns + "item").ToList();
var data = xdoc.Descendants(xns + "row").ToList();
foreach (var datum in data)
{
var values = datum.Elements(xns + "value").ToList();
for (int i = 0; i < values.Count; i++)
{
Console.WriteLine(metadata[i].Attribute("name").Value + ": " + values[i].Value);
}
Console.WriteLine();
}

Here is a simple method to parse file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
XElement dataset = (XElement)doc.FirstNode;
XNamespace ns = dataset.Name.Namespace;
var results = doc.Descendants(ns + "row").Select(x => new {
firstAddr = (string)x.Elements(ns + "value").FirstOrDefault(),
secondAddr = (string)x.Elements(ns + "value").Skip(1).FirstOrDefault(),
dob = (DateTime)x.Elements(ns + "value").Skip(2).FirstOrDefault()
}).ToList();
}
}
}

Related

How to merge one specific tag into one XML using c#?

I have two XMLs'
XML1:
'<?xml version="1.0" encoding="utf-8"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Filename>1234</Filename>
<Sequence Type="FRONT">
<Object>
<Value>3421</Value>
<Value>John</Value>
</Object>
</Sequence>
</Data>'
XML2:
'<?xml version="1.0" encoding="utf-8"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Filename>1234</Filename>
<Sequence Type="FRONT">
<Object>
<Value>1234</Value>
<Value>SAM</Value>
</Object>
</Sequence>
</Data>'
I want the output like below
'<?xml version="1.0" encoding="utf-8"?>
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Filename>1234</Filename>
<Sequence Type="FRONT">
<Object>
<Value>3421</Value>
<Value>John</Value>
</Object>
<Object>
<Value>1234</Value>
<Value>SAM</Value>
</Object>
</Sequence>
</Data>'
I.e I want to merge Object tag from XML2 to XML1 using C# code.
Could someone please help me?
You can use XPath to select the nodes you need and then simply use .NET xml using System.Xml
For more information look at https://www.w3schools.com/xml/xpath_intro.asp
Load Xml Documents
I saved the two sample xml-files you provided to separate files and imported them like this
XmlDocument doc1 = new XmlDocument();
XmlDocument doc2 = new XmlDocument();
using (var sw = new StreamReader("xml1.xml"))
{
var text = sw.ReadToEnd();
doc1.LoadXml(text);
}
using (var sw = new StreamReader("xml2.xml"))
{
var text = sw.ReadToEnd();
doc2.LoadXml(text);
}
Select nodes with XPATH
We will take all elements that have the name 'object' and add them to child of the other xml's 'sequence'-element. Therefore we select the 'sequence'-element of one document and the 'object'elements of the other document.
var sequenceNodes = doc1.SelectSingleNode("/Data/Sequence");
var objectNodes = doc2.SelectNodes("/Data/Sequence/Object");
Concat the nodes into one document
Then we take each 'object'-element, import it into the other document-context and append it under the 'sequence'-node
foreach (XmlNode node in objectNodes)
{
XmlNode importedNode = doc1.ImportNode(node, true);
sequenceNodes.AppendChild(importedNode);
}
Output the file
using (var stringWriter = new StringWriter())
using (var xmlTextWriter = XmlWriter.Create(stringWriter))
{
doc1.WriteTo(xmlTextWriter);
xmlTextWriter.Flush();
File.AppendAllText("out.xml", stringWriter.GetStringBuilder().ToString());
}
The outputfile looks like this:
Please, take a look at this solution:
what-is-the-fastest-way-to-combine-two-xml-files-into-one
Using Union method seems to be what you are seeking.
Run an XSLT transformation:
<xsl:template name="merge">
<Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Filename>1234</Filename>
<Sequence Type="FRONT">
<xsl:copy-of select="document('a.xml')//Object"/>
<xsl:copy-of select="document('b.xml')//Object"/>
</Sequence>
</Data>
</xsl:template>
Try following linq which joins all filenames with same value.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication166
{
class Program
{
const string FILENAME1 = #"c:\temp\test.xml";
const string FILENAME2 = #"c:\temp\test1.xml";
static void Main(string[] args)
{
XDocument doc1 = XDocument.Load(FILENAME1);
XNamespace ns1 = doc1.Root.GetDefaultNamespace();
XDocument doc2 = XDocument.Load(FILENAME2);
XNamespace ns2 = doc2.Root.GetDefaultNamespace();
var joins= from d1 in doc1.Descendants(ns1 + "Data")
join d2 in doc2.Descendants(ns2 + "Data")
on (string)d1.Element(ns1 + "Filename") equals (string)d2.Element(ns2 + "Filename")
select new { d1 = d1, d2 = d2};
foreach (var join in joins)
{
XElement d2Object = join.d2.Descendants("Object").FirstOrDefault();
join.d1.Descendants("Sequence").FirstOrDefault().Add(XElement.Parse(d2Object.ToString()));
}
}
}
}

C# Evaluate multiple Xpaths against an XmlDocument

I am writing a C# program where I would like to store a series of XPath statements as strings and evaluate them against an XMLDocument (or some other C# XML structure if there's a better one for this purpose) and store the resulting values in a dictionary / object.
My challenge is that my XPaths are not being able to be evaluated.
As a very simplified example, suppose this is my XML:
<root>
<a>
<child1 Id="Id1" Name="Name1" />
<child2 Id="Id2" Name="Name2" />
</a>
</root>
and, for example, one of my XPaths is:
//a/*[#Id='Id1']/name()
(Get the name of a's child element with the Id attribute = "Id1")
The simplified version of the code I'm trying to write to do this would be:
var xpath = #"//a/*[#Id='Id1']/name()";
var xml = #"<root><a><child1 Id='Id1' Name='Name1' /><child2 Id='Id2' Name='Name2' /></a></root>";
var doc = new XmlDocument();
doc.LoadXml(xml);
var navigator = doc.CreateNavigator();
string ChildName = (string)navigator.Evaluate(xpath);
but I am getting the error that my XPath has an invalid token - Which I'm assuming the be the name() portion.
Is there any way to accomplish this using direct XPath statements rather than traversing the tree?
Thanks!!
Pretty sure you just need to rearrange your XPath if I'm understanding you correctly. Try this:
name(//a/*[#Id='Id1'])
Using xml liinq :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication68
{
class Program
{
static void Main(string[] args)
{
string xml = "<root>" +
"<a>" +
"<child1 Id=\"Id1\" Name=\"Name1\" />" +
"<child2 Id=\"Id2\" Name=\"Name2\" />" +
"</a>" +
"</root>";
XDocument doc = XDocument.Parse(xml);
Dictionary<string, string> dict = doc.Descendants("a").FirstOrDefault().Elements().Where(x => x.Name.LocalName.StartsWith("child"))
.GroupBy(x => (string)x.Attribute("Id"), y => (string)y.Attribute("Name"))
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
}

LINQ to XML how compare children with repeater items

I've got a print button that gives XML data to filling an adobe lifecycle template. I'm trying to compare ROWID in XML with a repeater rowid to make it print the information about the row the button was clicked on.
Int32 rownum = Convert.ToInt32(e.CommandArgument.ToString());
string xmlROWID = Xmld.Descendants("ROWID").First().Value;
Here are 2 of the children in the XML:
<VKRSADL>
<CUSTOMER_SADLS>
<TABLEVALUE>
<ROW>
<ROWID>0</ROWID>
<ID>Съкредитополучател</ID>
<TYPE>48</TYPE>
<TYPEID>1</TYPEID>
<TYPECODE>1</TYPECODE>
<CRSCODE>777</CRSCODE>
<EGN />
<NAME />
<XML>
<SADL0>
<OwnerCrsCode />
<TABLEVALUE />
</SADL0>
</XML>
<XMLCHECK />
</ROW>
<ROW>
<ROWID>1</ROWID>
<ID>Съкредитополучател</ID>
<TYPE>48</TYPE>
<TYPEID>2</TYPEID>
<TYPECODE>1</TYPECODE>
<CRSCODE>123123</CRSCODE>
<EGN />
<NAME />
<XML>
<SADL1>
<OwnerCrsCode />
<TABLEVALUE />
</SADL1>
</XML>
<XMLCHECK />
</ROW>
</TABLEVALUE>
</CUSTOMER_SADLS>
</VKRSADL>
Comparing it to the First() gives me only the first ROWID, and there can be multiple. How can I compare the repeater rowid it to each of the ROWID's I've got saved?
I've tried this:
foreach (var child in Xmld.Root.Element("REQUEST").Element("VKRSADL").Element("CUSTOMER_SADLS").Element("TABLEVALUE").Elements("ROW").Elements()) {
//DO SOMETHING
}
but I get an error:
An exception of type 'System.NullReferenceException' occurred in App_Web_f2rvuyke.dll but was not handled in user code
Additional information: Object reference not set to an instance of an object.
As I understand from your question, you want to iterate through ROW element to get all elements which their ROWID is equal to a certain value.
You can use below code to get all ROW elements, then you can do what ever you want with each ROW, in my example below I print ROWID to console window:
string xml = "<VKRSADL><CUSTOMER_SADLS><TABLEVALUE><ROW><ROWID>0</ROWID><ID>Съкредитополучател</ID><TYPE>48</TYPE><TYPEID>1</TYPEID><TYPECODE>1</TYPECODE><CRSCODE>777</CRSCODE><EGN /><NAME /><XML><SADL0><OwnerCrsCode /><TABLEVALUE /></SADL0></XML><XMLCHECK /></ROW><ROW><ROWID>1</ROWID><ID>Съкредитополучател</ID><TYPE>48</TYPE><TYPEID>2</TYPEID><TYPECODE>1</TYPECODE><CRSCODE>123123</CRSCODE><EGN /><NAME /><XML><SADL1><OwnerCrsCode /><TABLEVALUE /></SADL1></XML><XMLCHECK /></ROW></TABLEVALUE></CUSTOMER_SADLS></VKRSADL>";
XDocument xDoc = XDocument.Parse(xml);
foreach (var child in xDoc.Element("VKRSADL").Element("CUSTOMER_SADLS").Element("TABLEVALUE").Elements().Where(e => e.Name == "ROW"))
{
Console.WriteLine(child.Element("ROWID").Value);
}
Note: Above example assume that the XML schema will not be changed, and if it changed then an exception could be occurred.
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
var results = doc.Descendants("ROW").Select(x => new {
rowID = (int)x.Element("ROWID"),
id = (string)x.Element("ID"),
type = (int)x.Element("TYPE"),
typeID = (int)x.Element("TYPEID"),
typeCode = (int)x.Element("TYPECODE"),
crsCode = (int)x.Element("CRSCODE"),
}).ToList();
}
}
}

How can i add "sub" child to a node with attribute using system.xml

i wish you a happy new year!
I got following XML structure:
<?xml version="1.0" encoding="UTF-8"?>
<SW.CodeBlock ID="0">
<SW.CompileUnit ID="1">
<AttributeList>
<NetworkSource>
<FlgNet xmlns="http://www.TEST.com">
<Parts> </Parts>
</FlgNet>
</NetworkSource>
</AttributeList>
</SW.CompileUnit>
<SW.CompileUnit ID="2">
<AttributeList>
<NetworkSource>
<FlgNet xmlns="http://www.TEST.COM">
<Parts> </Parts>
</FlgNet>
</NetworkSource>
</AttributeList>
</SW.CompileUnit>
</SW.CodeBlock>
How can i add a Child in "Parts" from SW.CompileUnit ID = 1 and SW.CompileUnit ID = 2 etc.?
I would like to create a loop (for-loop), which creates a child in "Parts" for every "SW.CompileUnit"-Node
Could you please help me?
PS: I use VS2015, C#, not using Linq or XPath etc.
Until now I add a child like this:
XmlNode xPiece = xdoc.SelectSingleNode("//NS2:Parts",nsmgr);
xPiece.AppendChild(myXMLElement);
but it only adds a child in the first SW.CompileUnit Node (with the ID=1)
...
Thanks in advance
SelectSingleNode() returns only the first matched elements. To get all matched elements, you're supposed to use SelectNodes() instead :
var nodes = xdoc.SelectNodes("//NS2:Parts",nsmgr);
foreach(XmlNode node in nodes)
{
//create new myXMLElement
....
//and then append it to current <Parts>
node.AppendChild(myXMLElement);
}
Btw, parameter of SelectNodes() and SelectSingleNode() are XPath expressions (just saying, because you wrote "I use VS2015, C#, not using Linq or XPath etc").
Use XML Linq. Not sure if I got your request exactly correct.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<SW.CodeBlock ID=\"0\">" +
"<SW.CompileUnit ID=\"1\">" +
"<AttributeList>" +
"<NetworkSource>" +
"<FlgNet xmlns=\"http://www.TEST.com\">" +
"<Parts> </Parts>" +
"</FlgNet>" +
"</NetworkSource>" +
"</AttributeList>" +
"</SW.CompileUnit>" +
"<SW.CompileUnit ID=\"2\">" +
"<AttributeList>" +
"<NetworkSource>" +
"<FlgNet xmlns=\"http://www.TEST.COM\">" +
"<Parts> </Parts>" +
"</FlgNet>" +
"</NetworkSource>" +
"</AttributeList>" +
"</SW.CompileUnit>" +
"</SW.CodeBlock>";
XDocument doc = XDocument.Parse(xml);
var compileUnits = doc.Descendants("SW.CompileUnit").Select(x => new {
ID = (string)x.Attribute("ID"),
parts = x.Descendants().Where(y => y.Name.LocalName == "Parts").FirstOrDefault()
}).ToList();
foreach (var compileUnit in compileUnits)
{
compileUnit.parts.Add(new XElement(compileUnit.parts.Name.Namespace + "ID", compileUnit.ID));
}
}
}
}

How can I get text and attributes values in my XML

XML example :
<?xml version="1.0" encoding="utf-8" ?>
<brand name="brand1" num_brand="118" enabled="True">
<price>
<nodePattern>pattern</nodePattern>
<attribute type="text" ></attribute>
<treatment enabled="1" type="Regex">reg</treatment>
</price>
<title>
<nodePattern>pattern</nodePattern>
<attribute type="text" ></attribute>
<treatment enabled="1" type="Regex">reg</treatment>
</title>
</brand>
Please, how can I get the different attributes values and text for all my different nodes (for example name, num_brand and enabled for brand, enabled, type and "reg" for treatment) using System.Xml.Linq ?
Thank you !
The System.Xml.Linq namespace is much nicer than the System.Xml namespace. Your XDocument has one XElement, which in turn has children elements. Each element has attributes and a value.
Here's an example for you:
var text = #"<?xml version=""1.0"" encoding=""utf-8"" ?>
<brand name=""brand1"" num_brand=""118"" enabled=""True"">
<price>
<nodePattern>pattern</nodePattern>
<attribute type=""text"" ></attribute>
<treatment enabled=""1"" type=""Regex"">reg</treatment>
</price>
<title>
<nodePattern>pattern</nodePattern>
<attribute type=""text"" ></attribute>
<treatment enabled=""1"" type=""Regex"">reg</treatment>
</title>
</brand>";
XDocument document = XDocument.Parse(text);
// one root element - "brand"
System.Diagnostics.Debug.Assert(document.Elements().Count() == 1);
XElement brand = document.Element("brand");
// brand has two children - price and title
foreach (var element in brand.Elements())
Console.WriteLine("element name: " + element.Name);
// brand has three attributes
foreach (var attr in brand.Attributes())
Console.WriteLine("attribute name: " + attr.Name + ", value: " + attr.Value);
You have many ways to do that. One of them is the XmlDocument.
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(myXML);
foreach(XmlNode node in xmlDoc.DocumentElement.ChildNodes){
string text = node.InnerText; //you can loop through children
}
Take a look on this post :
How do I read and parse an XML file in C#?
Personnaly I like the Linq To Xml approach, more infos here :
https://msdn.microsoft.com/en-us/library/bb387061.aspx
try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENMAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENMAME);
var brand = doc.Descendants("brand").Select(x => new
{
name = x.Attribute("name").Value,
num_brand = x.Attribute("num_brand").Value,
enabled = x.Attribute("enabled").Value,
nodePattern = x.Element("price").Element("nodePattern").Value,
attribute = x.Element("price").Element("attribute").Attribute("type").Value,
priceTreatmentEnable = x.Element("price").Element("treatment").Attribute("enabled").Value,
priceTreatmentType = x.Element("price").Element("treatment").Attribute("type").Value,
priceTreatment = x.Element("price").Element("treatment").Value,
titleTreatmentEnable = x.Element("title").Element("treatment").Attribute("enabled").Value,
titleTreatmentType = x.Element("title").Element("treatment").Attribute("type").Value,
titleTreatment = x.Element("title").Element("treatment").Value
}).FirstOrDefault();
}
}
}
​

Categories