Creating XML from the parent node information - c#

I have an xml file that has information of the roots as follows:
usequery attribute has a SQL query from which the XML element data is populated from.
<ARAXmlFormat>
<root name="level1" index = "1" parentid ="0" haschildren="yes"/>
<root name="level2" index = "2" parentid ="1" haschildren="yes" usequery="query2"/>
<root name="level21" index = "3" parentid ="2" haschildren="no" usequery="query1"/>
<root name="level22" index = "4" parentid ="2" haschildren="no" usequery="query3"/>
<root name="level3" index = "5" parentid ="1" haschildren="yes"/>
<root name="level31" index = "6" parentid ="5" haschildren="no" usequery="query4"/>
</ARAXmlFormat>
From this I need to generate an XML tree as follows. As of now I already have individual XElements for leve2, level21, level22, level31. But how do I create XML but adding these elements in the XML format as below from the parentid information above?
<level1>
<level2>
<level21 attrib1 ="val1" attrib2="val2"/>
<level22 attrib1 ="val1" attrib2="val2"/>
</level2>
<level3>
<level31 attrib1 ="val1" attrib2="val2"/>
</level3>
</level1>

You can do it like this:
var source = XDocument.Parse(xml); // or whatever
var sourceElems = source.Root.Elements("root");
var result = new XDocument(new XElement("result"));
var resultElems = new Dictionary<int, XElement>();
resultElems.Add(0, result.Root);
foreach (var sourceElem in sourceElems)
{
var resultElem = new XElement((string)sourceElem.Attribute("name"));
int parentId = (int)sourceElem.Attribute("parentid");
resultElems[parentId].Add(resultElem);
resultElems.Add((int)sourceElem.Attribute("index"), resultElem);
}
Basically, walk through the elements, and for each find the parent in a dictionary, add it as a child to that parent and finally add it to the dictionary, so that it can be parent itself. This assumes that parent is always declared before all its children.
With your source data, it creates the following result:
<result>
<level1>
<level2>
<level21 />
<level22 />
</level2>
<level3>
<level31 />
</level3>
</level1>
</result>

I don't know where the value of attrib1 and attrib2 attributes come from. But as far as the hierarchical structure of the output XML tree is concerned, you could construct it recursively, using something like this:
var doc = XDocument.Load("roots.xml"); // read input xml file
Func<int, IEnumerable<XElement>> selectChildNodes = null; // declare delegate
selectChildNodes = parentId => doc.Elements("ARAXmlFormat").Elements().Where(
p => p.Attribute("parentid").Value == parentId.ToString()
).Select(
x => new XElement(
x.Attribute("name").Value,
selectChildNodes(Convert.ToInt32(x.Attribute("index").Value))
)
);
IEnumerable<XElement> levels = selectChildNodes(0);
This solution works independent of order of <root> elements in the input file. selectChildNodes is a delegate that accepts the index of parent node as the input parameter and returns all child nodes of that element recursively. The output would be like this, without attributes:
<level1>
<level2>
<level21 />
<level22 />
</level2>
<level3>
<level31 />
</level3>
</level1>
The value of node attributes could be included when constructing each node with new XElement.

Related

How to get elements without child elements?

Here is my xml:
<Root>
<FirstChild id="1" att="a">
<SecondChild id="11" att="aa">
<ThirdChild>123</ThirdChild>
<ThirdChild>456</ThirdChild>
<ThirdChild>789</ThirdChild>
</SecondChild>
<SecondChild id="12" att="ab">12</SecondChild>
<SecondChild id="13" att="ac">13</SecondChild>
</FirstChild>
<FirstChild id="2" att="b">2</FirstChild>
<FirstChild id="3" att="c">3</FirstChild>
</Root>
This xml doc is very big and may be 1 GB size or more. For better performance in querying, i want to read xml doc step by step. So, in first step i want to read only "First Child"s and their attributes like below:
<FirstChild id="1" att="a"></FirstChild>
<FirstChild id="2" att="b">2</FirstChild>
<FirstChild id="3" att="c">3</FirstChild>
And after that, I maybe want to get "SecondChild"s by id of their parent and so ...
<SecondChild id="11" att="aa"></SecondChild>
<SecondChild id="12" att="ab">12</SecondChild>
<SecondChild id="13" att="ac">13</SecondChild>
How can I do it?
Note: XDoc.Descendants() or XDoc.Elements() load all specific elements with all child elements!
Provided that you have memory available to hold the file, I suggest treating each search step as an item in the outer collection of a PLINQ pipeline.
I would start with an XName collection for the node collections that you want to retrieve. By nesting queries within XElement constructors, you can return new instances of your target nodes, with only name and attribute information.
With a .Where(...) statement or two, you could also filter the attributes being kept, allow for some child nodes to be retained, etc.
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace LinqToXmlExample
{
public class Program
{
public static void Main(string[] args)
{
XElement root = XElement.Load("[your file path here]");
XName[] names = new XName[] { "firstChild", "secondChild", "thirdChild" };
IEnumerable<XElement> elements =
names.AsParallel()
.Select(
name =>
new XElement(
$"result_{name}",
root.Descendants(name)
.AsParallel()
.Select(
x => new XElement(name, x.Attributes()))))
.ToArray();
}
}
}
I suggest creating a new element and copy the attributes.
var sourceElement = ...get "<FirstChild id="1" att="a">...</FirstChild>" through looping, xpath or any method.
var element = new XElement(sourceElement.Name);
foreach( var attribute in sourceElement.Attributes()){
element.Add(new XAttribute(attribute.Name, attribute.Value));
}
In VB this you could do this to get a list of FirstChild
'Dim yourpath As String = "your path here"
Dim xe As XElement
'to load from a file
'xe = XElement.Load(yourpath)
'for testing
xe = <Root>
<FirstChild id="1" att="a">
<SecondChild id="11" att="aa">
<ThirdChild>123</ThirdChild>
<ThirdChild>456</ThirdChild>
<ThirdChild>789</ThirdChild>
</SecondChild>
<SecondChild id="12" att="ab">12</SecondChild>
<SecondChild id="13" att="ac">13</SecondChild>
</FirstChild>
<FirstChild id="2" att="b">2</FirstChild>
<FirstChild id="3" att="c">3</FirstChild>
</Root>
Dim ie As IEnumerable(Of XElement)
ie = xe...<FirstChild>.Select(Function(el)
'create a copy
Dim foo As New XElement(el)
foo.RemoveNodes()
Return foo
End Function)

Query XML stored in C# string

I have to read some values from XML,below is my sample XML
<?xml version="1.0" encoding="utf-16"?>
<ParentNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ChildNode>
<GrandChild Name="title" Value="Mr" />
<GrandChild Name="Name" Value="Test" />
<GrandChild Name="Age" Value="25" />
<GrandChild Name="Gender" Value="Male" />
</ChildNode>
</ParentNode>
I have to read values of Name and Age nodes, this is how I am doing
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(myXMLstring);
var nodes = xmlDoc.SelectNodes("/ParentNode/ChildNode");
foreach (XmlNode childrenNode in nodes)
{
}
but this code is running for only once, I tried this inside loop,buts its not running
var gchild= childrenNode.SelectNodes("/GrandChild");
foreach (XmlNode namevalue in gchild)
{
}
How can I get the values of Name and Age node?
Your XML contains only a single ChildNode so the XPATH expression /ParentNode/ChildNode will return only a single result. If you wanted to iterate over the grandchildren you should use /ParentNode/ChildNode/GrandChild or //GrandChild, eg:
var nodes = xmlDoc.SelectNodes("/ParentNode/ChildNode/GrandChild");
The result will be the same.
A single slash at the start of an XPath expression means that the path starts from the root, so /GrandChild returns nothing because there is no GrandChild note at the root level. A double slash // means wherever in the hierarchy, so //GrandChild will return all GrandChild nodes in the file
SelectNodes uses XPath expressions. In XPath, if the expression stars with / it'll start selecting relative to root.
Just use a relative xpath expression. In your case:
var gchild = childrenNode.SelectNodes("./GrandChild");
Or the equivalent:
var gchild = childrenNode.SelectNodes("GrandChild");
Or, if you only aim to iterate over those GrandChild elements, there's no reason to select the ChildNode first, you could iterate directly:
var nodes = xmlDoc.SelectNodes("/ParentNode/ChildNode/GrandChild");

How to the replace node names with new name and keep the attributes using C# and Linq to XML?

I need to change
<Test Language="English" Id="0" />
to
<Exam Language="English" Id="0" />
How to the replace node names with new name and keep the attributes ?
You can use Name property
var xdoc = XDocument.Load("input.xml");
var nodes=xdoc.Descendants("Test").ToList();//Get all "Test" node
nodes.ForEach(d => d.Name = "Exam "); // Set name to 'Exam'
xdoc.Save("output.xml");

c# linq to xml XElement group by ancestor attribute to create list of entities collection with key

I have below xml.
<?xml version="1.0" encoding="utf-8" ?>
<MESSAGE ID="PND">
<STORE ID="6697">
<HEADER ID ="1"
LOADCLOSEDDATETIME="20130312121212"
DELIVERYDATE="20130312"
TRAILERID=""
TRIPROUTEID=""
DEPOTCODE=""/>
<RECORD TPNB="123456666"
NOOFCASES=""
OUTERCASELENGTH="1"
OUTERCASEWIDTH="2"
OUTERCASEHEIGHT="3"
UNITSPERCASE=""
USEBYDATE="20130312121212"
MU="">
</RECORD >
<RECORD TPNB="123456666"
NOOFCASES=""
OUTERCASELENGTH ="1"
OUTERCASEWIDTH="2"
OUTERCASEHEIGHT="3"
UNITSPERCASE=""
USEBYDATE="20130312121212"
MU="">
</RECORD>
</STORE>
<STORE ID="6647">
<HEADER ID ="1"
LOADCLOSEDDATETIME="20130312121212"
DELIVERYDATE="20130312"
TRAILERID=""
TRIPROUTEID=""
DEPOTCODE=""/>
<RECORD TPNB="123456666"
NOOFCASES=""
OUTERCASELENGTH ="1"
OUTERCASEWIDTH="2"
OUTERCASEHEIGHT="3"
UNITSPERCASE=""
USEBYDATE="20130312121212"
MU="">
</RECORD>
</STORE>
<TRAILER ID="9" RECORDCOUNT=" 3" />
</MESSAGE>
I want to populate an entity with this xml where all my records belonging to a particular storeid should be grouped together into a list.
var _preNotifiedProduct =
from nlist in xDocument.Descendants("RECORD")
group nlist by nlist.Anestors("STORE").Attributes().First().Value
into cust
select new {key=cust.Key,value = ??};
Here key is my store id, and the value should be list of type (XElement) RECORD which belongs to that store id.
If I assign value=cust this will show me all nonpublic member elements which are in the Record xelement list.
How do I get it into a xelement list?
cust implements IEnumerable<XElement>, so you can just call ToList() on it:
var _preNotifiedProduct =
from nlist in xDocument.Descendants("RECORD")
group nlist by nlist.Ancestors("STORE").Attributes().First().Value
into cust
select new
{
key = cust.Key,
value = cust.ToList()
};
However, I would rewrite your query to become:
var _preNotifiedProduct = from store in xDocument.Root.Elements("STORE")
select new
{
key = (int)store.Attribute("ID"),
value = store.Elements("RECORD").ToList()
};
Should return the same, but should also be more readable and efficient.

Inserting and saving xml using Linq to XML

If i have an XML file settings.xml like below
<Root>
<First>
</First>
</Root>
I Load the XML first using XDocument settings = XDocument.Load("settings.xml")
How should I insert a XML node inside the node First and save it using LINQ-to-XML?
First you need to find the First element. Then you can add other elements and attributes to it.
There are more than one way to find an element in the xml: Elements, Descendants, XPathSelectElement, etc.
var firstElement = settings.Descendants("First").Single();
firstElement.Add(new XElement("NewElement"));
settings.Save(fileName);
// or
var newXml = settings.ToString();
Output:
<Root>
<First>
<NewElement />
</First>
</Root>
Or element with attribute:
firstElement.Add(
new XElement("NewElement", new XAttribute("NewAttribute", "TestValue")));
Output:
<Root>
<First>
<NewElement NewAttribute="TestValue" />
</First>
</Root>
[Edit] The answer to the bonus question. What to do if the first element does not exist and I want to create it:
var root = settings.Element("Root");
var firstElement = root.Element("First");
if (firstElement == null)
{
firstElement = new XElement("First");
root.Add(firstElement);
}
firstElement.Add(new XElement("NewElement"));

Categories