I have xml structure as below and i try to fetch nodes using linq to xml. I am kind of stuck how to read child's child node also I will have to get all child2 node values as coma separated values. And also have to read any dynamic nodes present under child node.
Here are sample xml's.
File 1:
<parent>
<doc>
<order>testorder</order>
<preorder>yes</preorder>
<child>
<childs1>
<child2>abc</child2>
<child2>efg</child2>
</childs1>
<preview>current</preview>
<provision>enable</provision>
</child>
</doc>
</parent>
File 2 :
<parent>
<doc>
<order>testorder</order>
<preorder>yes</preorder>
<child>
<preview>current</preview>
<provision>enable</provision>
<other>abc</other>
</child>
</doc>
</parent>
My sudo code :
XDocument xdoc = XDocument.Load(file);
var customers =
from cust in xdoc.Descendants("doc")
select new
{
Title = cust.Element("order").Value,
preorder = cust.Element("preorder").Value,
innernode= from inner in cust.Elements("child")
select new {
site = (inner.Element("preview") != null) ? inner.Element("preview").Value : null,
node=(inner.Element("childs1") != null) ? string.Join(",", from seg in inner.Elements("child2") select seg.Value) :null,
Should store if any extra dynamic nodes are there ,
},
};
foreach(var item in customers)
{
// read all nodes
}
Your code to fetch child2 is trying to look in doc's descendants, but you want to look at childs1's descendants. As for dynamic fields you can do it by creating a dictionary to get any elements that don't match the hardcoded ones, as i did below.
var customers =
from cust in xdoc.Descendants("doc")
select new
{
Title = cust.Element("order").Value,
preorder = cust.Element("preorder").Value,
innernode = from inner in cust.Elements("child")
select new
{
site = (inner.Element("preview") != null) ? inner.Element("preview").Value : null,
node = (inner.Element("childs1") != null) ? string.Join(",", from seg in inner.Elements("childs1").Elements("child2") select seg.Value) : null,
dynamicElements = inner.Elements()?.Where(e => e.Name != "preview" && e.Name != "childs1")?.ToDictionary(e => e.Name, e => e.Value)
},
};
Related
I am parsing an XML fragment using LINQ to XML and I am finding one of the nodes I am selecting is missing the child nodes I am expecting.
Example XML
<CustomerList>
<Customer>
<LastName>Smith</LastName>
<FirstName>Todd</FirstName>
</Customer>
<Customer>
<LastName>Jones</LastName>
<FirstName>Fred</FirstName>
</Customer>
<Customer>Tom Jones</Customer> <!-- Missing child nodes -->
</CustomerList>
When I try and pull out the LastName and FirstName values I get the error Object reference not set to an instance of an object.
Parsing XML with LINQ
XDocument xml = XDocument.Parse(xmlResponse);
List<CustomerModel> nodeList = xml.Descendants("CustomerList")
.Descendants("Customer")
.Select(x => new CustomerModel
{
LastName = x.Element("LastName").Value,
FirstName = x.Element("FirstName").Value,
}).ToList<CustomerModel>();
In the cases where the <Customer> nodes do not have the <LastName> and <FirstName> nodes how do I skip over them or better yet not even select them in the first place?
What I was suggesting is:
XDocument xml = XDocument.Parse(xmlResponse);
List<CustomerModel> nodeList = xml.Descendants("CustomerList")
.Descendants("Customer")
.Where(x => x.Element("LastName") != null && x.Element("FirstName") != null)
.Select(x => new CustomerModel
{
LastName = x.Element("LastName").Value,
FirstName = x.Element("FirstName").Value,
}).ToList<CustomerModel>();
I have the following XML and query through the ID,how do get the Parent Hierarchy
<Child>
<Child1 Id="1">
<Child2 Id="2">
<Child3 Id="3">
<Child4 Id="4">
<Child5 Id="5"/>
<Child6 Id="6"/>
</Child4>
</Child3>
</Child2>
</Child1>
</Child>
In this if i query(Id = 4) and find out the Parent elements using Linq in the particular element how to get the following output with Hierarchy.
<Child>
<Child1 Id="1">
<Child2 Id="2">
<Child3 Id="3">
<Child4 Id="4"/>
</Child3>
</Child2>
</Child1>
</Child>
Thanks In Advance.
Assume you want just one node parent tree:
string xml = #"<Child>
<Child1 Id="1">
<Child2 Id="2">
<Child3 Id="3">
<Child4 Id="4">
<Child5 Id="5"/>
<Child6 Id="6"/>
</Child4>
</Child3>
</Child2>
</Child1>
</Child>";
TextReader tr = new StringReader(xml);
XDocument doc = XDocument.Load(tr);
IEnumerable<XElement> myList =
from el in doc.Descendants()
where (string)el.Attribute("Id") == "4" // here whatever you want
select el;
// select your hero element in some way
XElement hero = myList.FirstOrDefault();
foreach (XElement ancestor in hero.Ancestors())
{
Console.WriteLine(ancestor.Name); // rebuild your tree in a separate document, I print ;)
}
To search for every element of your tree iterate retrieve the node with the select query without the where clause and call the foreach for every element.
Based on the sample XML provided, you could walk up the tree to find the parent node once you've found the node in question:
string xml =
#"<Child>
<Child1 Id='1'>
<Child2 Id='2'>
<Child3 Id='3'>
<Child4 Id='4'>
<Child5 Id='5'/>
<Child6 Id='6'/>
</Child4>
</Child3>
</Child2>
</Child1>
</Child>";
var doc = XDocument.Parse( xml );
// assumes there will always be an Id attribute for each node
// and there will be an Id with a value of 4
// otherwise an exception will be thrown.
XElement el = doc.Root.Descendants().First( x => x.Attribute( "Id" ).Value == "4" );
// discared all child nodes
el.RemoveNodes();
// walk up the tree to find the parent; when the
// parent is null, then the current node is the
// top most parent.
while( true )
{
if( el.Parent == null )
{
break;
}
el = el.Parent;
}
In Linq to XML there is a method called AncestorsAndSelf on XElement that
Returns a collection of elements that contain this element, and the
ancestors of this element.
But it will not transform your XML tree the way you want it.
What you want is:
For a given element, find the parent
Remove all elements from parent but the given element
Remove all elements from the given element
Something like this in Linq (no error handling):
XDocument doc = XDocument.Parse("<xml content>");
//finding element having 4 as ID for example
XElement el = doc.Descendants().First(el => el.Attribute("Id").Value == "4");
el.RemoveNodes();
XElement parent = el.Parent;
parent.RemoveNodes();
parent.Add(el);
[Edit]
doc.ToString() must give you what you want as a string.
[Edit]
Using RemoveNodes instead of RemoveAll, the last one also removes attributes.
Removing nodes from the chosen element too.
I found the following way
XElement elementNode = element.Descendants()
.FirstOrDefault(id => id.Attribute("id").Value == "4");
elementNode.RemoveNodes();
while (elementNode.Parent != null)
{
XElement lastNode = new XElement(elementNode);
elementNode = elementNode.Parent;
elementNode.RemoveNodes();
elementNode.DescendantsAndSelf().Last().AddFirst(lastNode);
}
return or Print elementNode.
I have the following xml:
<?xml version="1.0" encoding="utf-8" ?>
<layout>
<menu name="Employees" url="Employees.aspx" admin="0">
</menu>
<menu name="Projects" url="Projects.aspx" admin="1">
</menu>
<menu name="Cases" url="Cases.aspx" admin="1">
</menu>
<menu name="CaseView" url="CaseView.aspx" admin="1" hidden="1" parent="Projects">
</menu>
<menu name="Management" url="" admin="1">
<item name="Groups" url="Groups.aspx" admin="1" parent="Management"/>
<item name="Statuses" url="Statuses.aspx" admin="1"/>
</menu>
</layout>
Here I have CaseView and Groups that both have a 'parent' attribute.
Currently I iterate like this:
IEnumerable<XElement> menus =
doc.Element("layout").Elements();
foreach (var menu in menus)
{
string name = menu.Attribute("name").Value;
string active = "";
string url = menu.Attribute("url").Value;
if(activePage == url)
{
active = "class=\"active\"";
}
...
What I want is:
if(activePage == url || ActiveIsChildOf(name, activePage))
{
active = "class=\"active\"";
}
Essentially this method needs to find if an element with activePage as its url attribute exists. If it does, see if it has a parent attribute; if it does, check if the parent == name.
Is there some way to find an element by attribute or something?
ex:
XElement e = doc.GetByAttribute("url",activePage)
Thanks
Since you are using Linq to XML, you can use Descendants method - it returns all child elements, not just the direct children. After that, you can use LINQ to filter the results.
XDocument doc;
string activePage;
var activeMenu = doc.Descendants("menu")
.FirstOrDefault(o => o.Attribute("url").Value == activePage);
You might need to check if o.Attribute("url") does not return null (it does when the attribute does not exist) if you cannot guarantee that the source XML does not have such attribute for all menu elements.
You can also skip the argument to Descendants() to check all elements - in your sample data that would allow you to check both menu and item elements. For example:
var activeMenu = doc.Descendants()
.Where(o => o.Name == "menu" || o.Name == "item")
.FirstOrDefault(o => o.Attribute("url").Value == activePage);
If xpath is too cryptic, you can use LINQ:
IEnumerable<XElement> hits =
(from el in XMLDoc.root.Elements("item")
where (string)el.Attribute("url") == activePage
select el);
or like this:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements("item").FirstOrDefault(e => ((string)e.Attribute("url")) == activePage);
if(null != xele )
{
// do something with it
}
And you probably want it case-insensitive:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements("item").FirstOrDefault(e => StringComparer.OrdinalIgnoreCase.Equals((string)e.Attribute("url") , activePage));
if(null != xele )
{
// do something with it
}
If you want both menu and item, use this:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements().FirstOrDefault(e => StringComparer.OrdinalIgnoreCase.Equals((string)e.Attribute("url") , activePage));
if(null != xele )
{
// do something with it
}
You can simply use xPath. It's a query language for XML.
You can formulate something like this :
var xDoc = new XmlDocument();
xDoc.Load("XmlFile.xml");
//Fetch your node here
XmlNode = xDoc.SelectSingleNode(/layout/menu[#url='activepage'][1]);
It returns a set of node and the index 1 is to get the first node of the given set.
You can always use xDoc.SelectNodes if you want all the matching nodes.
Since you are using LINQ you can simply include System.Xml.XPath and select nodes with XPathSelectElement or XPathSelectElements.
You can do that with XPath:
doc.SelectNodes("//*[#url='" + activePage + "']")
It will return all document items that have activePage as url attribute.
A case insensitive search example, converting xml to a dictionary:
Dim expandos = XDocument.Parse(Request("Xml")).Root.Elements.Select(
Function(e)
Dim expando As Object = New ExpandoObject,
dic = e.Attributes.ToDictionary(Function(a) a.Name.LocalName, Function(a) a.Value,
StringComparer.InvariantCultureIgnoreCase)
expando.PedidoId = dic("PedidoId")
expando.FichaTecnicaModeloId = dic("FichaTecnicaModeloId")
expando.Comodo = dic("Comodo")
expando.Cliente = dic("Cliente")
Return expando
End Function)
Assuming I have an XmlDocument like this:
<xmlFile>
<details>
<code1>ADJ</code1>
<code2>ADC </code2>
<Shipment>
<foo></foo>
<bar></bar>
</Shipment>
<Shipment>
<foo></foo>
<bar></bar>
</Shipment>
</details>
<details>
<code1>ADJ</code1>
<code2>SCC </code2>
<Shipment>
<foo></foo>
<bar></bar>
</Shipment>
</details>
</xmlFile>
I need to process each in an xml file but only shipments that fall under the tags with a child node with a value of "ADC". So far I have:
// Assume there is an XmlDocument named xml
XmlNodeList details= xml.GetElementsByTagName("details");
foreach (XmlNode node in details)
{
if (node["code2"].InnerText == "ADC ")
{
// Get each shipment and process it accordingly.
}
}
I can't figure out what to do next. Thanks.
Assuming Data\Sample.xml contains xml as mentioned in the question,
Following is the XLINQ query
XElement root = XElement.Parse(File.ReadAllText(#"Data\Sample.xml"));
var adcShipment = root.Descendants().Where(e=>String.Equals(e.Value, "ADC "));
//next query for nodes/elements inside/next to ADC shipments
XPath can simplify your search for matches:
foreach (XmlNode node in xml.SelectNodes("/xmlFile/details[normalize-space(code2)='ADC']"))
{
string foo = node.SelectSingleNode("foo").InnerText;
string bar = node.SelectSingleNode("bar").InnerText;
}
I'm in the process of adding XPath parsing to this library: https://github.com/ChuckSavage/XmlLib/
I modified it so you can do this:
XElement root = XElement.Load(file);
var shipments = root.XPath("details[starts-with(*,'ADC')]/Shipment");
Long-hand that looks like:
var shipments = root.Elements("details")
.Where(x => x.Elements().Any(xx => ((string)xx).StartsWith("ADC")))
.Elements("Shipment");
This is the sort of thing you're after
XmlNodeList details = xml.GetElementsByTagName("details");
foreach (XmlNode node in details)
{
if (node["code2"].InnerText.Trim() == "ADC")
{
// Get each shipment and process it accordingly.
foreach(XmlNode shipment in node.SelectNodes("Shipment"))
{
var foo = shipment.SelectSingleNode("foo");
var bar = shipment.SelectSingleNode("bar");
}
}
}
I have a xml like this.
<?xml version="1.0" encoding="utf-8" ?>
<Category ID="1" AICategoryName="Schedule K: Income/Loss" Taxonomy="K">
<Level1 ID="11965" Name="Guaranteed payments" Taxonomy="4">
<Level2 ID="27058" Name="Gtd Pmts(trade/bus) to Sch. M-1" Taxonomy="1">
</Level2>
<Level2 ID="27059" Name="Gtd Pmts not to Sch. M-1" Taxonomy="2">
</Level2>
</Level1>
<Level1 ID="119652" Name="2Guaranteed payments" Taxonomy="4">
<Level2 ID="227058" Name="2Gtd Pmts(trade/bus) to Sch. M-1" Taxonomy="1">
</Level2>
<Level2 ID="227059" Name="2Gtd Pmts not to Sch. M-1" Taxonomy="2">
</Level2>
</Level1>
</Category>
I want to get the child nodes under a parent node by providing the parent node attribite ID.
for example if I provide Level1 and 11965, I should get all the level 2 nodes and their Name and IDs.
I have tried this code.
XDocument xd = XDocument.Load(xmlPath);
var xl = from xml2 in xd.Descendants("Level1")
where xml2.Parent.Attribute("ID").Value == parentNode.ID
select xml2;
But the code yeilds no results. also once I get the xl, how can I iterate through it to get child node names and IDs?
XDocument xd = XDocument.Load(xmlPath);
var nodes = (from n in xd.Root.Desendants(tagName/*Level1*/) where n.Attribute("Id").Value == "idValue" select n.Elements()).single().select(n=>{return new{
Id = n.attribute("Id").value,
Name = n.attribute("Name").value,
Taxonomy = n.attribute("Taxonomy").value
}});
you can also change the code above if the tag name requested is always "Level1" and the xml struvvture is fixed, to this.
XDocument xd = XDocument.Load(xmlPath);
var nodes = (from n in xd.Root.Elements("Level1") where n.Attribute("Id").Value == "idValue" select n.Elements()).single().select(n=>{return new{
Id = n.attribute("Id").value,
Name = n.attribute("Name").value,
Taxonomy = n.attribute("Taxonomy").value
}});
LINQ (suppose you always get single Level1 node for provided id):
XDocument xd = XDocument.Load(xmlPath);
int parentId = 119652;
var nodes = (from level1 in xd.Descendants("Level1")
where ((int)level1.Attribute("ID")) == parentId
select level1.Descendants("Level2"))
.Single()
.Select(level2 => new { ID = (int)level2.Attribute("ID"),
Name = level2.Attribute("Name").Value });
Iterate
foreach (var level2 in nodes)
// level2.Name and level2.ID
If there possible that Level1 node not exist for provided id or you have several Level1 nodes with same ID:
int parentId = 119652;
XDocument xd = XDocument.Load(xmlPath);
var query = xd.Descendants("Level1")
.Where(level1 => ((int)level1.Attribute("ID")) == parentId)
.SelectMany(level1 => level1.Descendants("Level2"))
.Select(level2 => new { ID = (int)level2.Attribute("ID"),
Name = level2.Attribute("Name").Value });
foreach (var level2 in query)
// level2.Name and level2.ID