traverse every element in xml tree using linq to xml - c#

I would like to traverse every element and attribute in an xml and grab the name an value without knowing the names of the elements in advance. I even have a book on linq to xml with C# and it only tells me how to query to get the value of elements when I already know the name of the element.
The code below only gives me the most high level element information. I need to also reach all of the descending elements.
XElement reportElements = null;
reportElements = XElement.Load(filePathName.ToString());
foreach (XElement xe in reportElements.Elements())
{
MessageBox.Show(xe.ToString());
}

Elements only walks one level; Descendants walks the entire DOM for elements, and you can then (per-element) check the attributes:
foreach (var el in doc.Descendants()) {
Console.WriteLine(el.Name);
foreach (var attrib in el.Attributes()) {
Console.WriteLine("> " + attrib.Name + " = " + attrib.Value);
}
}

You should try:
reportElements.Descendants()

Related

C# Linq2XML get parent attribute from child element value

I close my last question as it was commented that not enough research had been done. The more I research them more confused I am getting. What I think should work in my understanding and from post here and elsewhere is not working.
XML sample
<?xml version="1.0" encoding="UTF-8" ?>
<multistatus xmlns="DAV:">
<schmeata>
<classschema name="Space">
<base_class>Space</base_class>
</classschema>
<classschema name="Tapestry">
<base_class>File</base_class>
</classschema>
<classschema name="Document">
<base_class>File</base_class>
</classschema>
</schmeata>
</multistatus>
I am trying to get the name attribute of the classschema nodes that have base_class children with the 'File' value. so the result should be 'Tapestry' and 'Document'
I can easily return all classschemata nodes with
foreach (XmlNode node in xmlDoc.SelectNodes("//DAV:schemata/DAV:classschema", nsmgr))
{
strNode = node.Attributes["name"].Value;
responseString += strNode +" ";
}
return responseString;
And I can get the base_class value = to 'File' by looping through all the base_class nodes like this.
foreach (XmlNode node in xmlDoc.SelectNodes("//DAV:schemata/DAV:classschema/DAV:base_class", nsmgr))
{
if (node.InnerText == "File")
{
strNode = node.InnerText;
responseString += strNode +" ";
}
}
return responseString;
but if I try and filter or use axis to reference the parent node from the child I am failing.
An example of my filtering efforts are based at the SelectNodes method.
foreach (XmlNode node in xmlDoc.SelectNodes("//DAV:schemata/DAV:classschema[/DAV:base_class(contains,'File')]", nsmgr)) or
foreach (XmlNode node in xmlDoc.SelectNodes("//DAV:schemata/DAV:classschema[/DAV:base_class=='File']", nsmgr))
along with many, many other variations as examples I have seen is hard to tell if it is LINQ2XML or XDocument and mix in for PHP or other languages where aren't always specified I am now jumpled.
My next attempt will be SelectNodes("//DAV:schemata/DAV:classschemata[/DAV:baseclass(contains,'File')]"#name,nsmgr);
and variations on that.
I have thought from other examples that they had exactly what I wanted but when implemented did not work, for reasons I cannot explain.
This should give you the result you are after. Basically it looks for all classschema elements that have a first element with Value == "File" and then selects their name attributes Value. Please note I also used the string.Join method (pretty handy for stuff like this) to turn the result into a space delimited string which is what you need.
var xmlDoc = XDocument.Load("YourFile.xml");
var result = xmlDoc.Descendants("{DAV:}classschema")
.Where(x => x.Elements().First().Value == "File")
.Attributes("name")
.Select(x => x.Value);
string spaceDelimited = string.Join(" ", result);
This is what I am using that now works. I would swear on a stack of Bibles I had done this in earlier testing and it filed. But it does work. In posting the code I see that the inner loop I was using is now commented out and was the problem. I was looping something that did not need to be looped so was returning empty.
foreach (XmlNode node in xmlDoc.SelectNodes("//DAV:schemata/DAV:classschema[DAV:base_class='File']", nsmgr))
{
//strNode = node.Attributes["name"].Value;
//if (node.InnerText == "File")
// {
strNode = node.Attributes["name"].Value;
//strNode = node.InnerText;
responseString += strNode +" ";
// }
}
return responseString;
Thanks you for the help.
My next step will be to use each of the returned nodes and get all base_class elements and filter for only some of them to be returned. Not sure what I am looking for yet. Need to evaluate the XML to look for uniquenesss to capture what I want further.
Meaning now that I have the only classschema nodes with children elements containing 'File' get some of the child element siblings but not all. Challenge for another day.

Xelement adds element value to itself twice

In my code i iterate through an xelement and have it return the value of each node within that element e.g.
foreach(XElement n in XDocument.Descedants("element_name)
{
Console.WriteLine("Searching: " n.Value);
}
My problem is the both <Directory> elements are returned in the string
Searching: C:\Users\215358\OneDrive\MusicC:\Users\215358\Dropbox\Music
My XML file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Directories>
<Directory>C:\Users\215358\OneDrive\Music</Directory>
<Directory>C:\Users\215358\Dropbox\Music</Directory>
</Directories>
I expect it to output the second line element in <Directory> like this:
C:\Users\215358\Dropbox\Music
Why is this happening?
XElement.Value gets the concatenated text contents of an element. This includes the text of child elements which is not always very helpful. If you just want the text from the current element, you can find the text node in its child nodes.
foreach(XElement n in XDocument.Descedants("Directory"))
{
var text = n.Nodes().Where (x => x is XText).Cast<XText>().FirstOrDefault ();
if(text!=null){
Console.WriteLine("Searching: " + text.Value);
}else{
Console.WriteLine("No text node found");
}
}
Since you want to iterate through each entry and search element value, you could do something like this.
foreach (var element in doc.Descendants("Directory"))
{
if((string)element.Value == "searchstring")
{
// your logic
}
}
In case if you are looking for second element in the xml, you could apply Skip extension to skip specified count of elements.
var secondelement = doc.Descendants("Directory").Skip(1); // Skip first element;
or if you are looking for last element, you could take Last or LastOrDefault extension.
var lastelement = doc.Descendants("Directory").LastOrDefault();
Check this example.

Append attribute to each element in XML File

I have an xml and I want to append an attribute to each element in xml file.
IEnumerable<XElement> childList = from el in xml.Elements()
select el;
textBox1.Text = childList.ToString();
foreach (XElement el in childList)
{
el.Add(new XAttribute("Liczba_Potomkow", "dziesiec"));
textBox1.Text = el.ToString();
xml.Save("Employees.xml");
}
unfortunately, when I open the file only the first line seems to be affected. (only first elements gets new attribute). Why is so ?
I assume xml is an XDocument? If so, you're calling Elements() directly on the parent of the root element - so the only element it finds will be the root element itself.
If you want to do something for all elements in the document, you should use the Descendants() method.
Additionally, your query expression is pointless - you might as well just use xml.Elements() - and I really don't think you should be saving in a loop.
I think you just want:
foreach (var element in xml.Descendants())
{
element.Add(new XAttribute("Liczba_Potomkow", "dziesiec"));
}
xml.Save("Employees.xml");

Finding element in XDocument?

I have a simple XML
<AllBands>
<Band>
<Beatles ID="1234" started="1962">greatest Band<![CDATA[lalala]]></Beatles>
<Last>1</Last>
<Salary>2</Salary>
</Band>
<Band>
<Doors ID="222" started="1968">regular Band<![CDATA[lalala]]></Doors>
<Last>1</Last>
<Salary>2</Salary>
</Band>
</AllBands>
However ,
when I want to reach the "Doors band" and to change its ID :
using (var stream = new StringReader(result))
{
XDocument xmlFile = XDocument.Load(stream);
var query = from c in xmlFile.Elements("Band")
select c;
...
query has no results
But
If I write xmlFile.Elements().Elements("Band") so it Does find it.
What is the problem ?
Is the full path from the Root needed ?
And if so , Why did it work without specify AllBands ?
Does the XDocument Navigation require me to know the full level structure down to the required element ?
Elements() will only check direct children - which in the first case is the root element, in the second case children of the root element, hence you get a match in the second case. If you just want any matching descendant use Descendants() instead:
var query = from c in xmlFile.Descendants("Band") select c;
Also I would suggest you re-structure your Xml: The band name should be an attribute or element value, not the element name itself - this makes querying (and schema validation for that matter) much harder, i.e. something like this:
<Band>
<BandProperties Name ="Doors" ID="222" started="1968" />
<Description>regular Band<![CDATA[lalala]]></Description>
<Last>1</Last>
<Salary>2</Salary>
</Band>
You can do it this way:
xml.Descendants().SingleOrDefault(p => p.Name.LocalName == "Name of the node to find")
where xml is a XDocument.
Be aware that the property Name returns an object that has a LocalName and a Namespace. That's why you have to use Name.LocalName if you want to compare by name.
You should use Root to refer to the root element:
xmlFile.Root.Elements("Band")
If you want to find elements anywhere in the document use Descendants instead:
xmlFile.Descendants("Band")
The problem is that Elements only takes the direct child elements of whatever you call it on. If you want all descendants, use the Descendants method:
var query = from c in xmlFile.Descendants("Band")
My experience when working with large & complicated XML files is that sometimes neither Elements nor Descendants seem to work in retrieving a specific Element (and I still do not know why).
In such cases, I found that a much safer option is to manually search for the Element, as described by the following MSDN post:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/3d457c3b-292c-49e1-9fd4-9b6a950f9010/how-to-get-tag-name-of-xml-by-using-xdocument?forum=csharpgeneral
In short, you can create a GetElement function:
private XElement GetElement(XDocument doc,string elementName)
{
foreach (XNode node in doc.DescendantNodes())
{
if (node is XElement)
{
XElement element = (XElement)node;
if (element.Name.LocalName.Equals(elementName))
return element;
}
}
return null;
}
Which you can then call like this:
XElement element = GetElement(doc,"Band");
Note that this will return null if no matching element is found.
The Elements() method returns an IEnumerable<XElement> containing all child elements of the current node. For an XDocument, that collection only contains the Root element. Therefore the following is required:
var query = from c in xmlFile.Root.Elements("Band")
select c;
Sebastian's answer was the only answer that worked for me while examining a xaml document. If, like me, you'd like a list of all the elements then the method would look a lot like Sebastian's answer above but just returning a list...
private static List<XElement> GetElements(XDocument doc, string elementName)
{
List<XElement> elements = new List<XElement>();
foreach (XNode node in doc.DescendantNodes())
{
if (node is XElement)
{
XElement element = (XElement)node;
if (element.Name.LocalName.Equals(elementName))
elements.Add(element);
}
}
return elements;
}
Call it thus:
var elements = GetElements(xamlFile, "Band");
or in the case of my xaml doc where I wanted all the TextBlocks, call it thus:
var elements = GetElements(xamlFile, "TextBlock");

Linq to Xml question: struggling with a simple example

I am attempting to use XML for some simple formatting and embedded links. I'm trying to parse the XML using Linq to Xml, but I'm struggling with parsing a text "Value" with embedded elements in it. For example, this might be a piece of XML I want to parse:
<description>A plain <link ID="1">table</link> with a green hat on it.</description>
Essentially, I want to enumerate through the "Runs" in the Value of the description node. In the above example, there would be a text node with a value of "A plain ", followed by a "link" element, whose value is "table", followed by another text node whose value is " with the green hat on it.".
How do I do this? I tried enumerating the root XElement's Elements() enumeration, but that only returned the link element, as did Descendants(). DescendantNodes() did return all the nodes, but it also returned the subnodes of the link elements. In this case, a text node containing "table", in addition to the element that contained it.
You'll need to access the Nodes() method, check the XmlNodeType, and cast as appropriate to access each object's properties and methods.
For example:
var xml = XElement.Parse(#"<description>A plain <link ID=""1"">table</link> with a green hat on it.</description>");
foreach (var node in xml.Nodes())
{
Console.WriteLine("Type: " + node.NodeType);
Console.WriteLine("Object: " + node);
if (node.NodeType == XmlNodeType.Element)
{
var e = (XElement)node;
Console.WriteLine("Name: " + e.Name);
Console.WriteLine("Value: " + e.Value);
}
else if (node.NodeType == XmlNodeType.Text)
{
var t = (XText)node;
Console.WriteLine(t.Value);
}
Console.WriteLine();
}
XElement.Nodes() will enumerate only the top level child nodes.
Just use the Nodes() method on your description element.
var xmlStr = #"<description>A plain <link ID=""1"">table</link> with a green hat on it.</description>";
var descriptionElement = XElement.Parse(xmlStr);
var nodes = descriptionElement.Nodes();
foreach (var node in nodes)
Console.WriteLine("{0}\t\"{1}\"", node.NodeType, node);
Yields:
Text "A plain "
Element "<link ID="1">table</link>"
Text " with a green hat on it."

Categories