LINQ to XML Selecting Child Elements - c#

I am trying to extract information from an XML file into an object using LINQ to XML. Although I can return the document and section Id attributes I cannot get access to the Items for each section element, it returns an IEnumerable of all the items in the document. I know this is correct as I’m calling Descendants but am struggling to get it to return only the child items of each section element. Can anybody help?
XML Document
<root>
<document id="1">
<section id="1.1">
<item id="1.1.1"></item>
<item id="1.1.2"></item>
<item id="1.1.3"></item>
</section>
<section id="1.2">
<item id="1.2.1"></item>
<item id="1.2.2"></item>
</section>
</document>
</root>
LINQ Query
XElement documentRoot = XElement.Load("document.xml");
var documents = (from docs in documentRoot.Descendants("document")
select new
{
Id = (string) docs.Attribute("id"),
Sections = docs.Elements("section"),
Items = docs.Elements("section").Elements("item")
}).ToList();
foreach(var doc in documents)
{
foreach(var section in doc.Sections)
{
Console.WriteLine("SectionId: " + section.Attribute("Id"));
foreach(var item in doc.Items)
{
Console.WriteLine("ItemId: " + section.Attribute("Id"));
}
}
}

You got some small typos, on attribute Id and at item loop. But if you're trying to get all section items into that items collection, you're wrong at design level, as Items should be a Section property, not Document (so you'll need to query your XML twice).
Or, you can to do something like:
var documents =
(from docs in documentRoot.Descendants("document")
select new
{
Id = (string) docs.Attribute("id"),
Sections = docs.Elements("section")
}).ToList();
foreach (var doc in documents)
{
foreach (var section in doc.Sections)
{
Console.WriteLine("SectionId: " + section.Attribute("id"));
foreach (var item in section.Elements("item"))
{
Console.WriteLine("ItemId: " + item.Attribute("id"));
}
}
}
Output:
SectionId: id="1.1"
ItemId: id="1.1.1"
ItemId: id="1.1.2"
ItemId: id="1.1.3"
SectionId: id="1.2"
ItemId: id="1.2.1"
ItemId: id="1.2.2"

Do you want a flat structure ?!?! (from LinqPad)
XElement documentRoot = XElement.Parse (
#"<root>
<document id='1'>
<section id='1.1'>
<item id='1.1.1'></item>
<item id='1.1.2'></item>
<item id='1.1.3'></item>
</section>
<section id='1.2'>
<item id='1.2.1'></item>
<item id='1.2.2'></item>
</section>
</document>
</root>
");
var documents = (from docs in documentRoot.Descendants("section")
select new
{
SectionId = (string) docs.Attribute("id"),
Items = docs.Elements("item")
});
documents.Dump();
This gives 2 SectionId items that containes XElements with related items.

Related

How to retrieve attributes value with multiple level using Xml Linq?

I have a xml like this :
<?xml version="1.0" encoding="iso-8859-1" ?>
<Menu>
<Group Flow="Horizontal">
<Item Text="Basket" >
<Item Text="Favorites">
<Item Text="Add" Roles="SuperAdmin">
<Item Text="Evaluation" Roles="Admin">
</Item>
<Item Text="Titularisation" Roles="users,Admin">
</Item>
</Item>
</Group>
</Menu>
and I want to retrieve all attributes "Roles".
I tried this :
XElement rootElement = XElement.Load(#"C:\Users\TTM\Desktop\GeneralMenu.xml");
XElement menuElt = rootElement.Element("Group");
var itemsAdd = from el in menuElt.Descendants("Item")
where (string)el.Attribute("Text") == "Add"
select el;
foreach (var item in itemsAdd)
{
Console.WriteLine(item.Attribute("Roles").Value);
}
Console.Read();
but I get only the "Roles" of this balise:
<Item Text="Add" Roles="SuperAdmin">
Try this:-
XDocument xdoc = XDocument.Load(XMLFile);
var itemAdd = xdoc.Descendants("Item")
.Where(x => (string)x.Attribute("Text") == "Add")
.Select(x => new
{
Roles = (string)x.Attribute("Roles"),
ChildRoles = x.Descendants("Item")
.Select(z => (string)z.Attribute("Roles")).ToList()
});
Here, Roles will contain the Roles from your parent node, and ChildRoles will hold all the Roles present in the descendants of parent where Text is Add.
You can then access them using foreach loop:-
foreach (var item in itemAdd)
{
Console.WriteLine(item.Roles);
foreach (var childRoles in item.ChildRoles)
{
Console.WriteLine(childRoles);
}
}
If you want all the roles to be fetched in a single property then you can fetch it like this:-
Roles = x.Descendants("Item").Select(z => (string)z.Attribute("Roles"))
.Concat(new List<string> { (string)x.Attribute("Roles") }).ToList()

How find element in xdocument and read next elements after them

So i have next xml document:
<Items>
<Item>
<ID>123</ID>
<Name>Super Item</Name>
<Count>1</Count>
<Price>45</Price>
</Item>
<Item>
<ID>456</ID>
<Name>not super Item</Name>
<Count>10</Count>
<Price>5</Price>
</Item>
<Item>
<ID>789</ID>
<Name>Simple Item</Name>
<Count>6</Count>
<Price>10</Price>
</Item>
</Items>
So how can i find needed item by ID and read next values? Thanks in advance.
code:
XDocument doc = XDocument.Load (filePath);
foreach (var item in doc.Descendants("ID"))
{
if ((string)item.Element("ID") == "789")
{
How to read Name "Simple Item"?
How to read Count "6"?
How to read Price "10"?
}
}
By what you are asking you could formatt your xml like this:
<Items>
<Item id="123">
<Name>Super Item</Name>
<Count>1</Count>
<Price>45</Price>
</Item>
<Item id="456">
<Name>not super Item</Name>
<Count>10</Count>
<Price>5</Price>
</Item>
<Item id="789">
<Name>Simple Item</Name>
<Count>6</Count>
<Price>10</Price>
</Item>
</Items>
Then in code:
int yourId = 456;
XDocument doc = XDocument.Load("test.xml");
var result = from el in doc.Root.Elements("Item")
where el.Attribute("id").Value == yourId.ToString()
select el;
The id here is an attribute.And for reading its values 2 ways:
//1º
foreach (var item in result.Elements())
{
Console.WriteLine(item.Name + " = " + item.Value);
}
//2º - will print the element
Console.WriteLine(result);
It depends on what you want to do when you find those values. Here is a general method using a foreach loop to find the item with the specified ID and returning it's name:
private string GetItemName(string _id)
{
XmlDocument xDoc = new XmlDocument();
xDoc.Load("myXmlFile.xml");
foreach (XmlNode item in xDoc.SelectNodes("/Items/Item"))
{
if (item.SelectSingleNode("ID").InnerText == _id)
{
// we found the item! Now what do we do?
return item.SelectSingleNode("Name").InnerText;
}
}
return null;
}

Flatten results from hierachical XML file using Linq to XML

I currently have the following C# with Linq to XML code working but I'm hoping there is a better way to form the Linq query to then be able to extract the results into a flat structure.
Here are the current results formatted as I would like them to appear:
7990697: result_status = Complete
7990756: result_status = Incomplete
I want to be able to access the results using the syntax result.resultStatusId instead of result.meta.ToList()[0].resultStatusId
var xDoc = XDocument.Parse(#"<fs_response status='success' timestamp='2014-07-10 14:38:39'>
<headings>
<heading for='result_status'>Result Status</heading>
<heading for='1'>First Name</heading>
<heading for='2'>Last Name</heading>
</headings>
<results>
<result id='7990697'>
<metas>
<meta id='result_status'>Complete</meta>
</metas>
<items>
<item id='1' index='3' type='text'>
<value>Bugs</value>
</item>
<item id='2' index='4' type='text'>
<value>Bunny</value>
</item>
</items>
</result>
<result id='7990756'>
<metas>
<meta id='result_status'>Incomplete</meta>
</metas>
<items>
<item id='1' index='3' type='text'>
<value>Yogi</value>
</item>
<item id='2' index='4' type='text'>
<value>Bear</value>
</item>
</items>
</result>
</results>
<total_results>2</total_results>
</fs_response>");
var results =
from x in xDoc.Elements("fs_response").Elements("results").Elements("result")
select new
{
id = x.Attribute("id").Value,
meta = from m in x.Elements("metas").Elements("meta")
where m.Attribute("id").Value == "result_status"
select new
{
resultStatusId = m.Attribute("id").Value,
resultStatus = m.Value
}
};
results.Dump();
foreach (var result in results.ToList())
{
Console.WriteLine("{0}: {1} = {2}",
result.id,
result.meta.ToList()[0].resultStatusId,
result.meta.ToList()[0].resultStatus);
//Console.WriteLine("{0}: {1} = {2}",
// result.id,
// result.resultStatusId,
// result.resultStatus);
}
I would change the query to something like this:
var results = xDoc.Element("fs_response")
.Element("results")
.Elements("result")
.Select (e => new
{
id = e.Attribute("id").Value,
meta = e.Element("metas")
.Elements("meta")
.Where (m =>
m.Attribute("id").Value == "result_status")
.Select (m => new
{
resultStatusId = m.Attribute("id").Value,
resultStatus = m.Value
})
.Single()
});
Some notes:
Notice the change in a few locations from Elements to Element. This indicates that you know there will only be one result with that name, or if you only want the first with that name. Use Elements when you expect multiple results. In some cases in the example above, I'm making assumptions about your xml schema (such as the fact that there will only be one metas Element per result). This may perform faster as it will not have to continue searching for Elements of a specific name once it has found one.
Also see the use of Single(). This indicates that you are expecting one and only one element with the id attribute equal to result_status. If you expect 0 or 1, you can use SingleOrDefault(), or if you want the first in a potential list of matching elements you can use First() or FirstOrDefault(). Once you add this, you will no longer have to call ToList()[0] in the results:
Console.WriteLine("{0}: {1} = {2}",
result.id,
result.meta.resultStatusId,
result.meta.resultStatus);
var results =
from x in xDoc.Elements("fs_response").Elements("results").Elements("result")
from m in x.Elements("metas").Elements("meta")
where m.Attribute("id").Value == "result_status"
select new
{
id = x.Attribute("id").Value,
resultStatusId = m.Attribute("id").Value,
resultStatus = m.Value,
};
foreach (var result in results.ToList())
{
Console.WriteLine("{0}: {1} = {2}", result.id, result.resultStatusId, result.resultStatus);
}

XElement attribute sorting

I have a XML file like that:
<Users>
<User>
<Adress Name="bbbb"/>
<Adress Name="aaaa" />
</User>
</Users>
I want to sort User element's nodes in ascending order. How can I order Adress elements?
Thank you for your help.
If node is your user node:
node.Elements("Adress").OrderBy(e=>e.Attribute("Name").Value)
Are you merely wanting to work with the XML objects in memory or are you looking to store the sorted results back in a file?
This code shows reordering the elements within an XDocument so that you can save it.
string xml = #"<Users>
<User>
<Address Name=""bbbb""/>
<Address Name=""aaaa"" />
</User>
<User>
<Address Name=""dddd""/>
<Address Name=""cccc"" />
</User>
</Users> ";
XDocument document = XDocument.Parse(xml);
var users = document.Root.Elements("User");
foreach (var user in users)
{
var elements = user.Elements("Address").OrderBy(a => a.Attribute("Name").Value).ToArray();
user.Elements().Remove();
user.Add(elements);
}
If you want an ordered in-memory model, then you can do it like this
var query = from user in document.Root.Elements("User")
select new
{
Addresses = from address in user.Elements("Address")
orderby address.Attribute("Name").Value
select new
{
Name = address.Attribute("Name").Value
}
};
foreach (var user in query)
{
foreach (var address in user.Addresses)
{
// do something with address.Name
}
}

Select unique XElements (by attribute) with a filter using LinqToXml

I have an XML document looking similar to this:
<items>
<item cat="1" owner="14">bla</item>
<item cat="1" owner="9">bla</item>
<item cat="1" owner="14">bla</item>
<item cat="2" owner="12">bla</item>
<item cat="2" owner="12">bla</item>
</items>
Now I'd like to get all unique owners (I actually only need the attribute value of the owner) belonging to a specified category using a linq query. In my example, the query for cat 1 would return a list containing 9 and 14. How can I do that? Linq syntax would be preferred over Lambdas. Thanks in advance ;)
Presuming the fragment is in itemsElement:
var distinctOwners = (from item in itemsElement.Element("item")
where itemElements.Attribute("cat") == 1
select item.Attribute("owner")).Distinct();
Apologies for formatting and indentation!
Try this function:-
static IEnumerable<int> GetOwners(XDocument doc, string cat)
{
return from item in doc.Descendants("item")
where item.Attribute("cat").Value == cat
select (int)item.Attribute("owner")).Distinct();
}
XElement ele = XElement.Parse(#"<items><item cat=""1"" owner=""14"">bla</item><item cat=""1"" owner=""9"">bla</item>" +
#"<item cat=""1"" owner=""14"">bla</item><item cat=""2"" owner=""12"">bla</item>" +
#"<item cat=""2"" owner=""12"">bla</item></items>");
int cat = 1;
List<int> owners = ele.Elements("item")
.Where(x=>x.Attribute("cat").Value==cat.ToString()).Select(x=>Convert.ToInt32(x.Attribute("owner").Value)).Distinct().ToList();

Categories

Resources