trouble parsing deeply nested attributes using LINQ to XML - c#

I have been trying to parse this xml in c#
<schema uri=http://blah.com/schema >
<itemGroups>
<itemGroup description="itemGroup1 label="itemGroup1">
<items>
<item description="The best" itemId="1" label="Nutella"/>
<item description="The worst" itemId="2" label="Vegemite"/>
</items>
</itemGroup>
</itemGroups>
</schema>
\itemGroup1\Nutella-The best
\itemGroup1\Vegemite-The worst
Any help or direction would be appreciated.

XDocument xDoc = XDocument.Load(myXml); //load your XML from file or stream
var rows = xDoc.Descendants("item").Select(x => string.Format(
#"\{0}-{1}\{2}-{3}",
x.Ancestors("itemGroup").First().Attribute("description").Value,
x.Ancestors("itemGroup").First().Attribute("label").Value,
x.Attribute("label").Value,
x.Attribute("description").Value));
Let's break down what we're doing:
xDoc.Descendants("item") gets us all <item> elements in the entire document
Select(x => string.Format(format, args) projects each <item> we got from the last operation into whatever format we specify in the lambda. In this case, a formatted string.
In terms of the XML tree, we're "sitting at" the <item> level, so we need to roll back up the tree to get the data for the parent group using Ancestors. Since that method returns a sequence of elements, we know we want the first (nearest to us) so we can read its attribute.
Now you have an IEnumerable<string>, one for each <item> in your XML document and the information in the format you specified:
foreach(string row in rows)
{
Console.WriteLine(row);
}

Related

Returning all the elements with the specific attribute using XPath

So, I have an XElement within an Xml file that looks something like this:
... More parent nodes
<item cid="0x14c8" name="A" id="0xaf4b6f8">
<item cid="0x57c" luid="2001"/>
<item cid="0x578" luid="2000" value="0"/>
<item cid="0x14fa" name="B">
<item cid="0x57a" luid="2024" value="1.00"/>
<item cid="0x579" luid="2025" value="0"/>
<item link="0xb199640" cid="0x474" luid="5372"/>
...More descendants
</item>
Now, I want to return an IEnumerable<Element> object of the elements that only contain the attribute link using XPath
In my query, I'm doing:
return currentXElement.XPathSelectElements("//item[#link]").Select(item => new Element(item));
But it is returning all the elements from the complete document, not from the currentXElement which is the section that I'm trying to query.
What can I do? I have tried using /item[#link] too, and is not returning anything.
Thanks
EDIT:
At the end, as Sergey suggested,
return currentXElement.Descendants("item").Where(i => i.Attribute("link") != null).Select(item => new IpjItem(item));
//item expression selects item elements no matter where they are in the document. If you want to select items relative to current element, you should use full path. E.g.
xdoc.Root.XPathSelectElements("//item[#name='B']//item[#link]")
Here I assume that //item[#name='B'] selects your currentElement (you should use expression which select your current element), and then with //item[#link] you are searching for descendant items relative to item with name B.
NOTE: I think pure LINQ will be better here
currentXElement.Descendants("item").Where(i => i.Attribute("link") != null)

XPathSelectElement select the second when there is more than one

Hi Not sure if this can be done but I know someone here will know :)
using
XElement oNodeEquip = xmlDoc.XPathSelectElement("//ItemAry/Item/Equip");
how would I select Equip from the second in the following:
<TestInfo>
<ItemAry>
<Item>
<testData>ABC</testData>
</Item>
<Item>
<testData>XYZ</testData>
<Equip>xxx</Equip>
</Item>
</ItemAry>
</TestInfo>
there will always be at lease 2 <Item> and the node I want the value from will always be in the second <Item>
this is a WPF app using .Net 4.0
Try this XPath expression:
//ItemAry/Item[2]/Equip
It considers only the second <Item> element.
You can easily do it with Linq to Xml:
XDocument xdoc = XDocument.Load(path_to_xml);
var second = (string)xdoc.Descendants("Item").Skip(1).Element("Equip");
Use:
(/*/ItemAry/Item)[2]/Equip
The currently selected answer can produce unexpected results, depending on the specific XML document at hand:
//ItemAry/Item[2]/Equip
selects the Equip children of all Item elements that are the second Item child of their ItemAry parent.
So, if the source XML document is:
<TestInfo>
<ItemAry>
<Item>
<testData>ABC</testData>
</Item>
<Item>
<testData>XYZ</testData>
<Equip>xxx</Equip>
</Item>
</ItemAry>
<ItemAry>
<Item>
<testData>DEF</testData>
</Item>
<Item>
<testData>TUW</testData>
<Equip>yyy</Equip>
</Item>
</ItemAry>
</TestInfo>
the above potentially-wrong expression selects two elements:
<Equip>xxx</Equip>
<Equip>yyy</Equip>
The correct expression provided in this answer:
(/*/ItemAry/Item)[2]/Equip
selects just:
<Equip>xxx</Equip>
If you are ceratin about position of the node, you can use position() function in your XPath:
XElement oNodeEquip = xmlDoc.XPathSelectElement("//ItemAry/Item[position()=2]/Equip");

Ordering XMLDocument.SelectNodes results

I have the following XML which I want to order by priority. Don't fancy using XPath and thought LINQ might work but I get an argument exception thrown with the message "At least one object must implement IComparable."
Can I order the results somehow?
<Root>
<Item>
<Priority><![CDATA[4]]</Priority>
</Item>
<Item>
<Priority><![CDATA[1]]</Priority>
</Item>
<Item>
<Priority><![CDATA[3]]</Priority>
</Item>
</Root>
var result = doc.SelectNodes("//Item");
var ordered = ddddd.Cast<XmlNode>().OrderBy(x => x.SelectSingleNode("Priority")); //EXCEPTION!!!
Change x => x.SelectSingleNode("Priority") to x => x.SelectSingleNode("Priority").InnerText
Oh, and remember, that you will compare strings, not ints, that way.

How to get elements by name in XML using LINQ

I've chosen the title here as my problem is I need to get the Item nodes mentioned in the example.
I have the following XML and am having problems using LINQ to query it, I've been able to parse XML before - however I've been stuck on this for hours and hope someone can help.
Here is my XML data below (example data):
<a:entry
xmlns:a="http://www.w3.org/2005/Atom">
<a:id>98765</a:id>
<info>Data Catalogue</info>
<data>
<items>
<item>
<id>123456</id>
<value>Item One</value>
</item>
<item>
<id>654321</id>
<value>Item Two</value>
</item>
</items>
</data>
<items>
<item>
<id>123456</id>
<value>Item One</value>
</item>
<item>
<id>654321</id>
<value>Item Two</value>
</item>
</items>
<a:author>
<a:name>Catalogue</a:name>
</a:author>
</a:entry>
I want to be able to extract the ID from the Item XML tag under Items, however there is an Items Tag with Item entries under data I DO NOT want these nodes at all - I want root/items/id/id if this were expressed as path. I've tried everything I know with LINQ so if someone could help, things to note although this is sample data it is based on the system - the format cannot be changed so that is not an acceptable solution.
I can't seem to determine where I'm going wrong - every LINQ expression I try returns nothing, I think the namespace is an issue and have tried to integrate this but I'm going in circles.
Solution must work in Silverlight and C#
I have tried the following:
IEnumerable<XElement> nodes =
element.Elements().Where(e => e.Name.LocalName == "items")
However this gets me all the "items" including the ones under "data" I don't want those.
If I do the following on my XML I do see the Names of the Elements displayed:
XElement element = XElement.Parse(data);
foreach (XElement node in element.Elements())
{
MessageBox.Show(node.Name.LocalName);
}
However when I do this I cannot see the node names under items at all - I've checked the XElement and it does have the node and when I output the names above it "items" shows up along with info and id!
foreach (XElement node in element.Elements("items"))
{
MessageBox.Show(node.Name.LocalName);
}
Assuming element is your <a:entry> element:
var ids = element.Element("items")
.Elements("item")
.Select(item => item.Element("id").Value);
The Element and Elements methods return only direct children, not all descendants, so it doesn't return the <items> element which is under <data>
I had a blank Namespace Declaration in my XML I hadn't noticed once I added this into my code it worked - forgot LINQ is very NameSpace oriented!
XNamespace ns = "http://example.org/namespace";
var ids = element.Element(ns + "items")
.Elements("item")
.Select(item => item.Element("id").Value);

Read child elements using C# from xml

Greeting,
What is the best practice to read all attributes from a child elements by ID attributes using C# in xml file listed down.
Thank you,
<?xml version="1.0" encoding="utf-8"?>
<WinDLN>
<Program ID="1" Name="CIS562" StartDate="9/8/2010 5:50:00 PM" EndDate="9/8/2010 9:15:00 PM" />
<Program ID="2" Name="CIS532" StartDate="10/8/2010 5:50:00 PM" EndDate="10/8/2010 9:15:00 PM" />
<Program ID="3" Name="ECE552" StartDate="6/8/2010 5:50:00 PM" EndDate="6/8/2010 9:15:00 PM" />
</WinDLN>
The following LINQ call should do the trick:
var attrs =
doc.Descendants("Program").First(prog =>
prog.Attribute("ID").Value == "2").Attributes();
The Descendants method gives you all elements (anywhere) in the XML document that are named "Program". Using First, you can get the first one that matches some specified predicate (e.g. has "ID" equal to "2"). Note that you can use FirstOrDefault if you want to get null when there is no such element. Finally, Attributes gives you a collection of all attribtues of the element.
I think that using LINQ to XML if you can is preferrable - you'll write the same code when working with XML or other data sources, so reading and writing the code is easy (once you learn LINQ).
There are many ways to do it, e.g. LINQ over XML. But using Xpath is definitely not dead yet:
class Program
{
static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
string xml = #"... your xml ";
doc.LoadXml(xml);
// Using SelectNodes with Xpath
XmlNodeList list = doc.SelectNodes("WinDLN/Program[#ID='2']");
Console.WriteLine(list.Count); // prints 1
list = doc.SelectNodes("WinDLN/Program[#ID]");
Console.WriteLine(list.Count); // prints 3 (selected all IDs)
}
}
What method you'll choose is most often a matter of taste, select the API you're most comfortable with.

Categories