Checking in xml if node exists c# - c#

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.

Related

How to get list of values when XML contains many namespaces

I have following XML:
<?xml version="1.0" encoding="utf-16"?>
<cincinnati xmlns="http://www.sesame-street.com/abc/def/1">
<cincinnatiChild xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ElementValue xmlns:a="http://schemas.data.org/2004/07/sesame-street.abc.def.ghi">
<a:someField>false</a:someField>
<a:data xmlns:b="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<b:KeyValueThing>
<b:Key>key1</b:Key>
<b:Value i:type="a:ArrayOfPeople">
<a:Person>
<a:firstField>
</a:firstField>
<a:dictionary>
<b:KeyValueThing>
<b:Key>ID</b:Key>
<b:Value i:type="c:long" xmlns:c="http://www.w3.org/2001/XMLSchema">000101</b:Value>
</b:KeyValueThing>
<b:KeyValueThing>
<b:Key>Name</b:Key>
<b:Value i:type="c:string" xmlns:c="http://www.w3.org/2001/XMLSchema">John</b:Value>
</b:KeyValueThing>
</a:dictionary>
</a:Person>
<a:Person>
...
<b:Value i:type="c:long" xmlns:c="http://www.w3.org/2001/XMLSchema">000102</b:Value>
...
</a:Person>
</b:Value>
</b:KeyValueThing>
</a:data>
</ElementValue>
</cincinnatiChild>
</cincinnati>
I need to get a list if ID values, e.g. 000101, 000102....
I think using XPath makes sense here but the multitude of namespaces makes it confusing (so a simple XmlNamespaceManager won't do).
Basically I need something like this (this syntax is of course not correct):
XmlDocument doc = // Load the xml
doc.DocumentElement.SelectSingleNode("/cincinati/cincinnatiChild/ElementValue/a:data/b:KeyValueThing/b:Value/a:Person/a:dictionary[b:KeyValueThing/b:Key='ID']");
also when I do doc.DocumentElement.SelectSingleNode("/cincinnati") or doc.DocumentElement.SelectSingleNode("/cincinnatiChild") I get null.
Since I'm unsure how to piece together al the helpfull advice from the comments I would like to see a working c# code line, either XmlDocument or XDocument are OK.
I also tries this before the SelectSingleNode:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("a", "http://schemas.data.org/2004/07/sesame-street.abc.def.ghi");
nsmgr.AddNamespace("b", "http://schemas.microsoft.com/2003/10/Serialization/Arrays");
nsmgr.AddNamespace("c", "http://www.w3.org/2001/XMLSchema");
nsmgr.AddNamespace("i", "http://www.w3.org/2001/XMLSchema-instance");
nsmgr.AddNamespace("d", "http://www.sesame-street.com/abc/def/1");
You could bypass the namespace issues using the local-name()-function.
i.e.: in stead of b:KeyValueThing use *[local-name()='KeyValueThing']
a simple global XPath could look like this:
//*[local-name()='KeyValueThing'][*[local-name()='Key' and text()='ID' ]]/*[local-name()='Value']/text()
If you want to be more precise and speed up the XPath it would look like this:
/*[local-name()='cincinnati']/*[local-name()='cincinnatiChild']/*[local-name()='ElementValue']/*[local-name()='data']/*[local-name()='KeyValueThing']/*[local-name()='Value']/*[local-name()='Person']/*[local-name()='dictionary']/*[local-name()='KeyValueThing'][*[local-name()='Key' and text()='ID' ]]/*[local-name()='Value']/text()

Deserialize an xml file or parse using linq to xml

Simplified XML file I need to decode:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:deliverylistResponse xmlns:ns2="http://tdriverap3.wsbeans.iseries/">
<return>
<COUNT>3</COUNT>
<DELIVERIES>
<ADD1>1300 address 1</ADD1>
<CITY>NICE CITY</CITY>
<ZIP>85705</ZIP>
</DELIVERIES>
<DELIVERIES>
<ADD1>40 S PINAL PKWY AVE</ADD1>
<CITY>FLORENCE</CITY>
<ZIP>85132</ZIP>
</DELIVERIES>
<DELIVERIES>
<ADD1>1825 EAST MAIN</ADD1>
<CITY>CHANDLER</CITY>
<ZIP>85286</ZIP>
</DELIVERIES>
<ERRORCODE/>
<RUNDATE>09/26/2018</RUNDATE>
</return>
</ns2:deliverylistResponse>
</soap:Body>
</soap:Envelope>
I am using the following to try and decode each of the individual addresses in the code.
I cannot figure out how to access them.
XElement xelement = XElement.Load(#"e:\test\X2.xml");
IEnumerable<XElement> addresses = xelement.Elements();
foreach (var address in addresses)
{
Console.WriteLine(address);
Console.WriteLine(address.Element("CITY").Value);
}
The first writeline works (it outputs the entire XML tree), the second says "System.Xml.Linq.XContainer.Element(...) returned null" - I have tried using DELIVERIES, COUNT, Body etc...
Obviously I am not telling it correctly how to traverse the structure, but I do not know how to go any further with it..
UPDATE: Thanks to some help I have figured out how to do it using Linq.
I would still like to be able to deserialize it if anybody has a pointer.
I followed several tutorials, but the multiple levels of this XML seems to be throwing me off.
I have created a class to hold the data but that is as far as my success with that path has gone.
Thank you,
Joe
Thank you Crowcoder -- this is what I wound up with, which will work.
The real XML file however does have about 60 fields so this is not as good as using a deserialize routine but I can at least move forward with the project.
XElement xelement = XElement.Load(#"e:\test\x2.xml");
IEnumerable<XElement> textSegs =
from seg in xelement.Descendants("DELIVERIES")
select seg;
foreach (var address in textSegs)
{
Console.WriteLine(address.Element("ADD1").Value);
Console.WriteLine(address.Element("CITY").Value);
Console.WriteLine(address.Element("ZIP").Value);
}
Console.ReadKey();

Retrieving XmlNode SelectSingleNode Parents Node

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);

SelectSingleNode() with XPath C# Failure

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'].

Is there a bug in my XML code or in .NET?

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");

Categories

Resources