XML SelectNodes then SelectSingleNodes - only retrieves the first - c#

I have the following XML
<root>
<Operations>
<OperationId>1</OperationId>
<OtherFields>...</OtherFields>
</Operations>
<Operations>
<OperationId>2</OperationId>
<OtherFields>...</OtherFields>
</Operations>
<Operations>
<OperationId>3</OperationId>
<OtherFields>...</OtherFields>
</Operations>
</root>
Using C# and System.XML namespace I get all operations using this code:
XmlNodeList operations= doc.SelectNodes("/root/Operations");
Now I need to loop through each operation and reference the fields (OperationId, OtherFields).
I try this:
foreach (XmlNode node in xnodes)
{
Console.WriteLine("Operation ID: {0}", node.SelectNodes("//OperationId")[0].InnerText);
}
However this just repeats the first OperationId - 1.
What is wrong?
Thanks,
Andrew

Your initial query selects all Operations nodes off the root like you're expecting. However your inner query in your loop does something different.
By starting your query with a //, you're querying relative to the root of the document. So you're actually selecting all OperationId nodes in the document, not just the descendant children of the current node. You then index the first node in that result for every iteration which is why you're seeing the first id repeating.
Since node refers to the current Operations node, to select the corresponding OperationId, your query should simply be:
OperationId
I should mention that since you're only trying to select the first element of the query, it would be better to use SelectSingleNode() instead of SelectNodes. If there are any nodes selected, the first will be returned.
node.SelectSingleNode("OperationId").InnerText
However since you're only trying to select an immediate child element, I wouldn't use an XPath query there, it's unnecessary. Just access the child element directly using the indexer.
var query = doc.SelectNodes("/root/Operations");
foreach (XmlNode node in query)
{
Console.WriteLine("Operation ID: {0}", node["OperationId"].InnerText);
}

Related

Saving "skipped" nodes in xml into array

In my code, I am downloading an xml file, and because one of the nodes is variable (both name and count of them), I use code like this:
XmlNodeList arrivals = airplanes.SelectNodes("/myXml/flights/*/arrivals");
Now what I need to do, is saving names of the nodes skipped by "*" into an array, or arraylist, something like that. Later I will need to use some foreach to do something with each of the nodes, now saved as strings. I have tried
foreach(* in MyArrayList)
and that doesnt work, I get a number of errors there, assuming I cant use the " * " here.
Each XmlNode in the XmlNodeList has a ParentNode property, you should be able to use that to navigate back up from the arrivals node in the xml to the * node.
The following Linq query should get the names:
var names = arrivals.Cast<XmlNode>().Select(x => x.ParentNode.Name).ToList();
The Cast<XmlNode> is needed because XmlNodeList doesn't implement the generic IEnumerable interface.

Linq To Xml - ReplaceNodes updates number of elements found in original query

I have a linq to xml query which returns 2 nodes. I am attempting to iterate over these nodes and replace their content. However the content that is being added to the XDocument contains nodes which match my query criteria.
protected internal virtual void AddLinkDocument(XDocument content, TaskRequest request)
{
var links = content.Descendants("some.link").Where(tag => tag.Attribute("document-href") != null);
foreach (XElement link in links) //first time through there are 2 links found
{
//do some stuff
link.ReplaceNodes(inlineContent); // after content is added, "links" now in foreach now has many new links found
}
}
Why is the collection, "links" being updated dynamically each time through the foreach?
Thanks for the help!
Seems like there are a few factors at play here:
The way you've defined links, it's going to be re-queried for each iteration of foreach.
The re-query will search for all descendants matching the query (not just immediate children).
My guess is that the iteration block adds some new elements that match the query (ie, contain a "document-href" attribute).
So, I'd suggest trying either:
adding a ToArray() to the end of the definition for links;
or using Elements instead of Descendants to query only the child nodes of content.

Get specific data from XML document

I have xml document like this:
<level1>
<level2>
<level3>
<attribute1>...</attribute1>
<attribute2>false</attribute2>
<attribute3>...</attribute3>
</level3>
<level3>
<attribute1>...</attribute1>
<attribute2>true</attribute2>
<attribute3>...</attribute3>
</level3>
</level2>
<level2>
<level3>
<attribute1>...</attribute1>
<attribute2>false</attribute2>
...
...
...
I'm using c#, and I want to go thru all "level3", and for every "level3", i want to read attribute2, and if it says "true", i want to print the corresponding attribute3 (can be "level3" without these attributes).
I keep the xml in XmlDocument.
Then I keep all the "level3" nodes like this:
XmlNodeList xnList = document.SelectNodes(String.Format("/level1/level2/level3"));
(document is the XmlDocument).
But from now on, I don't know exactly how to continue. I tried going thru xnList with for..each, but nothing works fine for me..
How can I do it?
Thanks a lot
Well I'd use LINQ to XML:
var results = from level3 in doc.Descendants("level3")
where (bool) level3.Element("attribute2")
select level3.Element("attribute3").Value;
foreach (string result in results)
{
Console.WriteLine(result);
}
LINQ to XML makes all kinds of things much simpler than the XmlDocument API. Of course, the downside is that it requires .NET 3.5...
(By the way, naming elements attributeN is a bit confusing... one would expect attribute to refer to an actual XML attribute...)
You can use LINQ to XML and reading this is a good start.
You can use an XPath query. This will give you a XmlNodeList that contains all <attribute3> elements that match your requirement:
var list = document.SelectNodes("//level3[attribute2 = 'true']/attribute3");
foreach(XmlNode node in list)
{
Console.WriteLine(node.InnerText);
}
You can split the above xpath query in three parts:
"//level3" queries for all descendant elements named <level3>.
"[attribute2 = 'true']" filters the result from (1) and only keeps the elements where the child element <attribute2> contains the text true.
"/attribute3" takes the <attribute3> childnode of each element in the result of (2).

Relative XPath selection using XmlNode (c#)

Say i have the following xml file:
<a>
<b>
<c></c>
</b>
<b>
<c></c>
</b>
</a>
var nodes = doc.SelectNodes("/a/b");
will select the two b nodes.
I then loop these two nodes suchas:
foreach (XmlNode node in nodes) { }
However, when i call node.SelectNodes("/a/b/c"); It still returns both values and not just the descendants.
Is it possible to select nodes only descening from the current node?
In the foreach loop, you already know that node is a /a/b in the original document - so to get just its c children simply use a relative xpath:
node.SelectNodes("c")
You can use node.SelectSingleNode("C");
/a/b[1]/c
For intance gets the nodelist of all the children of the first b that have the tagname c.
To get the first c as a singleton nodelist use /a/b[1]/c[1]. /a/b/c[1] again returns a nodelist of multiple nodes.
SelectSingleNode is probably misleading, as far as I know, XPath always returns a nodelist, which can contain optionally one node (or even be empty).
//c[1] just selects the first c in the document.

Reading an XML File and Selecting Nodes in .NET

I have a heavier XML file with lots and lots of tree nodes. I need to pick-up some particular node (for example say Diet), under which there are multiple sections.
ie. Diet node occurs randomly in the XML, so i need to find the node as Diet and get its child elements and save it to DB.
Assume that Diet is not only one line, it has 10-12 entries underneath it (may be i can get its contents using InnerXML, but really can't get line by line nodes)
Make sure you have added a reference to "System.xml.Linq'.
Suck out all the Diet elements:
XElement wholeFile = XElement.Load(#"C:\DietSampleXML.xml");
IEnumerable<XElement> dietElements = wholeFile.Descendants("Diet");
If you set a breakpoint and hover the mouse over "dietElements" and click "Results View", you will see all the Diet elements and their inner xml.
Now iterate through dietElements to add each element and/or children to your database: "foreach (XElement x in dietElements) { ... }"
I tested this with the following xml:
<?xml version="1.0" encoding="utf-8" ?>
<TestElement>
<Diet>
<Name>Atkins</Name>
<Colories>1000</Colories>
</Diet>
<TestElement2>
<Diet>
<Name>Donuts Only</Name>
<Calories>1500</Calories>
</Diet>
</TestElement2>
<TestElement3>
<TestElement4>
<Diet>
<Name>Vegetarian</Name>
<Calories>500</Calories>
</Diet>
</TestElement4>
</TestElement3>
</TestElement>
Depending on the structure of your XML file, you might try loading it into a DataSet (DataSet.ReadXML()) and see what DataTable it puts your Diet nodes into ... if it parses it ok then it is pretty simple to loop through the DataTable and get all your Diet node values.
I wrote a little toy app that opens XML like that, listing all the DataTables in a tree view then showing the table content in a grid. The VS project file for it is here or just an MSI to install it is here, if you want to see how a DataSet parses your XML file.
In XPath, it's just //Diet
To say more, I'd need to know more about your environment.
var doc = XDocument.Load("yourfile.xml");
var nodes = from d in doc.Desendants("Diet")
select d;
foreach(var node in nodes)
{ // do stuff with node
}
The pseudo code below, contains the XPath statement that would get you all elements who have a 'Diet' as parent. Since it produces a XmlNodeList you can walk every node and save it to the DB. For performance i would consider consolidating what you want to save, and then save it, not per line (round trip for every entry is sub-optimal)
XmlNodeList list = xDoc.DocumentElement.SelectNodes("//*[parent::Diet]");
foreach (XmlNode entry in list)
{
DAL.SaveToDatabase(entry);
}
Hope this helps,

Categories

Resources