I just ran into an issue where my code was parsing xml fine but once I added in a second node it started to load incorrect data. The real code spans a number of classes and projects but for the sample I've put together the basics of what's causing the issue
When the code runs I'd expect the output to be the contents of the second Task node, but instead the contents of the first node is output. It keeps pulling from the first occurrence of the EmailAddresses node despite how when you check the settings object its inner xml is that of the second Task node. The call to SelectSingleNode("//EmailAddresses") is where the issue happens.
I have two ways around this issue
Remove the leading slashes from the EmailAddresses XPath expression
Call Clone() after getting the Task or Settings node
Solution 1 works in this case but I believe this will cause other code in my project to stop working.
Solution 2 looks more like a hack to me than a real solution.
MY question is am I in fact doing this correctly and there's a bug in .NET (all versions) or am I just pulling the XML wrong?
The c# code
var doc = new XmlDocument();
doc.Load(#"D:\temp\Sample.xml");
var tasks = doc.SelectSingleNode("Server/Tasks");
foreach (XmlNode threadNode in tasks.ChildNodes)
{
if (threadNode.Name.ToLower() != "thread")
{
continue;
}
foreach (XmlNode taskNode in threadNode.ChildNodes)
{
if (taskNode.Name.ToLower() != "task" || taskNode.Attributes["name"].Value != "task 1")
{
continue;
}
var settings = taskNode.SelectSingleNode("Settings");
var emails = settings.SelectSingleNode("//EmailAddresses");
Console.WriteLine(emails.InnerText);
}
}
The XML
<?xml version="1.0"?>
<Server>
<Tasks>
<Thread>
<Task name="task 2">
<Settings>
<EmailAddresses>task 2 data</EmailAddresses>
</Settings>
</Task>
</Thread>
<Thread>
<Task name="task 1">
<Settings>
<EmailAddresses>task 1 data</EmailAddresses>
</Settings>
</Task>
</Thread>
</Tasks>
</Server>
From http://www.w3.org/TR/xpath/#path-abbrev
// is short for
/descendant-or-self::node()/. For
example, //para is short for
/descendant-or-self::node()/child::para
and so will select any para element in
the document (even a para element that
is a document element will be selected
by //para since the document element
node is a child of the root node);
And also:
A location step of . is short for
self::node(). This is particularly
useful in conjunction with //. For
example, the location path .//para
is short for
self::node()/descendant-or-self::node()/child::para
and so will select all para descendant
elements of the context node.
Instead of:
var settings = taskNode.SelectSingleNode("Settings");
var emails = settings.SelectSingleNode("//EmailAddresses");
Use:
var emails = taskNode.SelectSingleNode("Settings/EmailAddresses");
The // XPath expression does not do what you think it does. It selects nodes in the document from the current node that match the selection no matter where they are.
In other words, it's not limited by the current scope, it actually crawls back up the document tree and starts matching from the root element.
To select the first <EmailAddresses> element in your current scope, you only need:
var emails = settings.SelectSingleNode("EmailAddresses");
Related
I send some request and i get xml response sometimes i get
<?xml version="1.0" encoding="UTF-8"?>
<debt-response>
<status>0</status>
<name>ნ.ს.</name>
<schedules>
<schedule>07.07.2017 1171.8000 GEL 1</schedule>
<schedule>07.08.2017 1171.8000 GEL 1</schedule>
<schedule>07.09.2017 1171.8000 GEL 1</schedule>
</schedules>
</debt-response>
and sometimes i get
<?xml version="1.0" encoding="UTF-8"?>
<debt-response>
<status>0</status>
<name>ნ.ბ.</name>
<schedules>
<schedule>06.07.2018 1.5 GEL 1</schedule>
<debt>15.06.2018 0.97</debt>
</schedules>
</debt-response>
I am using var acc_numArray = xmlDoc.SelectNodes("/debt-response/schedules/debt"); but if no such element exists it goes in exception.
I want to get that debt if such node exists any solution ?
You could try
var debt = XDocument.Load("path to your xml").Descendants("debt").FirstOrDefault()?.Value;
which gets the first debt element and returns its value.
If you have more than one debt, the following code saves the element values in a list:
var debt = XDocument.Load("path to your xml").Descendants("debt").Select(d => d.Value).ToList();
If you are reading the xml from a stream, use XDocument.Parse("your string") instead of Load().
Note: you will need the System.Xml.Linq namespace.
Without seeing your code, I can only guess what you're doing - but one thing is certain, this line will not throw an exception if no nodes can be located by the specified XPath:
var acc_numArray = xmlDoc.SelectNodes("/debt-response/schedules/debt");
Instead, it will return an XmlNodeList with a count of zero.
I am assuming your exception is occurring when you try and access the nodelist as if it had items in it - but simply check the count and only access it if it is non-zero:
var acc_numArray = xmlDocument.SelectNodes("/debt-response/schedules/debt");
if (acc_numArray.Count > 0)
{
// Do stuff here
}
If you put an example of your actual code into the question it will be a lot easier to help you.
I have a rather large XML file from a computer diagnostics session, and my goal is to grab the test results data and pump it into a PDF for the customer. I've very little experience with XML and this is turning out to be a huge problem.
Here is a sample of the Document:
<pcd:DiagLog xmlns="http://www.pc-doctor.com/2004/8/diagLogger"
xmlns:pcd="http://www.pc-doctor.com/2004/8/diagLogger"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.pc-doctor.com/2004/8/diagLogger
http://www.pc-doctor.com/2004/8/diagLogger/diagLogger.xsd">
<Application>
<version>6.0.6818.10</version>
<Start>
<Time hour="04" minute="14" second="01" millisecond="34" month="10" day="15" year="2016" utcOffset="-480">2016-10-15T04:14:01.034-08:00</Time>
</Start>
<OS>Windows 10 Service Pack 0 PE x86 32-bit</OS>
</Application>
.......
<DiagInfo>
....
<TestResult EnglishResult="PASS">
....
</TestResult>
</DiagInfo>
There are thousands of lines between </Application> and <DiagInfo>, but I'm only concerned with the information found in <DiagInfo> and <TestResult>.
I thought I could grab the Nodes by simply:
XmlDocument doc = new XmlDocument();
doc.Load(xmlFilePath);
XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable);
manager.AddNamespace("pcd", "http://www.pc-doctor.com/2004/8/diagLogger");
XmlNodeList xnList = doc.SelectNodes("/pcd:DiagLog/DiagInfo", manager);
But this is returning an empty list. When I refer to Namespace Manager or XsltContext needed, it appears I'm doing it right, but I don't think I'm understanding adding a namespace correctly. When I change the Root Element to just: <Diagnostics></Diagnostics> instead of the <pcd:DiagLog>, and try: doc.SelectNodes("/Diagnostics/DiagInfo", manager); my nodes list is populated.
Can anyone see where I'm screwing up the Namespace?
You need to use the namespace prefix for all nodes in that namespace.
This is incorrect: /pcd:DiagLog/DiagInfo.
This is correct: /pcd:DiagLog/pcd:DiagInfo.
stack overflow has helped me a ton and decided to join and ask a question myself.
My process that I am trying to do is basically select a node out of an XML document and delete the entire node that the user had selected.
Now for some code!
int index = index = list_serverlist.SelectedIndex;
string selectedItem = list_serverlist.Items[index].ToString();
XmlNode selectedNode = doc.SelectSingleNode("/ServerList/Server/ServerName[text()='" + selectedItem + "']");
selectedNode.ParentNode.RemoveAll();
doc.Save(filePath);
Also the XML file that I am using
<?xml version="1.0"?>
<ServerList>
<Server>
<ServerName>FAB13-HST01</ServerName>
<ServerIP>wasd</ServerIP>
<ServerUsername>..\Administrator</ServerUsername>
<ServerPassword>wasd</ServerPassword>
</Server>
<Server>
<ServerName>FAB13-HST02</ServerName>
<ServerIP>wasd</ServerIP>
<ServerUsername>..\Administrator</ServerUsername>
<ServerPassword>wasd</ServerPassword>
</Server>
<Server>
<ServerName>FAB13-HST03</ServerName>
<ServerIP>wasd</ServerIP>
<ServerUsername>..\Administrator</ServerUsername>
<ServerPassword>wasd</ServerPassword>
</Server>
</ServerList>
Now how I see that code happening is...
basically I get what the user selected out of the ListBox make it a string and than select the single node that has that in the ServerName field. Which when debugging seems to work fine.
However when I use the command
selectedNode.ParentNode.RemoveAll();
It deletes all childs of the node, and not including the parent null. When I debug it and try to get the Parent it seems to be returning null for some odd reason and can't figure out why.
New to XML so not sure what I am doing wrong...
If you try to get the parent after calling RemoveAll(), the selected node no longer exists.
To remove the whole server element, you could use something like.
XmlNode nodeParent = selectedNode.ParentNode;
nodeParent.ParentNode.RemoveChild(nodeParent);
I'm trying to do very simple operations on a .cxml file. As you know it's basically an .xml file. This is a sample file I created to test the application:
<?xml version="1.0" encoding="utf-8"?>
<Collection xmlns:p="http://schemas.microsoft.com/livelabs/pivot/collection/2009" SchemaVersion="1.0" Name="Actresses" xmlns="http://schemas.microsoft.com/collection/metadata/2009">
<FacetCategories>
<FacetCategory Name="Nationality" Type="LongString" p:IsFilterVisible="true" p:IsWordWheelVisible="true" p:IsMetaDataVisible="true" />
</FacetCategories>
<!-- Other entries-->
<Items ImgBase="Actresses_files\go144bwo.0ao.xml" HrefBase="http://www.imdb.com/name/">
<Item Id="2" Img="#2" Name="Anna Karina" Href="nm0439344/">
<Description> She is a nice girl</Description>
<Facets>
<Facet Name="Nationality">
<LongString Value="Danish" />
</Facet>
</Facets>
</Item>
</Items>
<!-- Other entries-->
</Collection>
I can't get any functioning simple code like:
XDocument document = XDocument.Parse(e.Result);
foreach (XElement x in document.Descendants("Item"))
{
...
}
The test on a generic xml is working. The cxml file is correctly loaded in document.
While watching the expression:
document.Descendants("Item"), results
the answer is:
Empty "Enumeration yielded no results" string
Any hint on what can be the error? I've also add a quick look to get Descendants of Facet, Facets, etc., but there are no results in the enumeration. This obviously doesn't happen with a generic xml file I used for testing. It's a problem I have with .cxml.
Basically your XML defines a default namespace with the xmlns="http://schemas.microsoft.com/collection/metadata/2009" attribute:
That means you need to fully qualify your Descendants query e.g.:
XDocument document = XDocument.Parse(e.Result);
foreach (XElement x in document.Descendants("{http://schemas.microsoft.com/collection/metadata/2009}Item"))
{
...
}
If you remove the default namespace from the XML your code actually works as-is, but that is not the aim of the exercise.
See Metadata.CXML project under http://github.com/Zoomicon/Metadata.CXML sourcecode for LINQ-based parsing of CXML files.
Also see ClipFlair.Metadata project at http://github.com/Zoomicon/ClipFlair.Metadata for parsing one's CXML custom facets too
BTW, at http://ClipFlair.codeplex.com can checkout the ClipFlair.Gallery project for how to author ASP.net web-based forms to edit metadata fragments (parts of CXML files) and merge them together in a single one (that you then convert periodically to DeepZoom CXML with PAuthor tool from http://pauthor.codeplex.com).
If anyone is interested in doing nesting (hierarchy) of CXML collections see
http://github.com/Zoomicon/Trafilm.Metadata
and
http://github.com/Zoomicon/Trafilm.Gallery
i got a XML File as export from Wireshark and want to select the Number of the actual frame
The structure of this file is like this
<packet>
<proto>
...
</proto>
....
<proto>
<field name="frame.number" show="1">
</proto>
</packet>
<packet>
<proto>
...
</proto>
....
<proto>
<field name="frame.number" show="2">
</proto>
</packet>
...and so on...
I use this code to select the packets/fields
XmlNodeList packages = xmlDoc.SelectNodes("//packet");
foreach (XmlNode packet in packages) {
string frameNumber = packet.SelectSingleNode("//field[#name='frame.number']").
Attributes["show"].Value;
Console.WriteLine(frameNumber);
}
If I Debug through the code, it always selects the right Nodes with the correct attributes. But at each iteration there is a "1" printed out.
Does anyone suspect what failure this is? I didn't found anything on the internet for this failure
Thank you very much!!
Its because your XPath in SelectSingleNode starts with // - which means "start from the root of the document". Therefore you're always getting the first one.
Just change the XPath in that method to proto/field[#name='frame.number'].