LINQ xml finding nodes returns null - c#

I try to parse an xml file with XDocument class, with criteria that if the child node matches a given string, its parent node is selected.
<SalesQuotes xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://api.some.com/version/1">
<Pagination>
<NumberOfItems>2380</NumberOfItems>
<PageSize>200</PageSize>
<PageNumber>1</PageNumber>
<NumberOfPages>12</NumberOfPages>
</Pagination>
<SalesQuote>
<Guid>825634b9-28f5-4aa7-98e7-5e4a4ed6bc6a</Guid>
<LastModifiedOn>2018-01-09T12:23:56.6133445</LastModifiedOn>
<Comments>Please note:
installation is not included in this quote
</Comments>
</SalesQuote>
</SalesQuotes>
I tried using
var contents = File.ReadAllText(path: "test1.xml");
var doc = XDocument.Parse(contents);
var root = doc.Root;
var sq = root.Elements("SalesQuote");//return null
var theQuote = root.Elements("SalesQuote").Where(el => el.Element("Guid").Value == "825634b9-28f5-4aa7-98e7-5e4a4ed6bc6a");//return null
var theAlternativeQuote =
from el in doc.Descendants("SalesQuote").Elements("Guid")
where el.Value == "825634b9-28f5-4aa7-98e7-5e4a4ed6bc6a"
select el;//return null
I can't seem to find what's wrong.
Any help is much appreciated! Thanks.

You ignored the namespace bro.
Do remove the xmlns attribute in your XML or try this:
var contents = File.ReadAllText("XMLFile1.xml");
var doc = XDocument.Parse(contents);
var root = doc.Root;
XNamespace ns = "http://api.some.com/version/1";
var sq = root.Descendants(ns + "SalesQuotes"); //return null
var theQuote = root.Elements(ns + "SalesQuote")
.Where(el => el.Element(ns + "Guid").Value == "825634b9-28f5-4aa7-98e7-5e4a4ed6bc6a"); //return null
var theAlternativeQuote =
from el in doc.Descendants(ns + "SalesQuote").Elements(ns + "Guid")
where el.Value == "825634b9-28f5-4aa7-98e7-5e4a4ed6bc6a"
select el; //return null

If you are not too concerned about keeping your current implementation, you could consider using a Typed DataSet and load your XML into fully typed, structured objects.
Querying those objects with Linq will be more straight forward than what I see in your current implementation.
You might also find this useful:
SO Question: Deserialize XML Document to Objects

yap, you're missing the namespace that you can grab with document.Root.GetDefaultNamespace()
// Load
var document = XDocument.Parse(xml);
var xmlns = document.Root.GetDefaultNamespace();
// Find
var query = from element in document
.Descendants(xmlns + "SalesQuote")
.Elements(xmlns + "Guid")
where element.Value == "825634b9-28f5-4aa7-98e7-5e4a4ed6bc6a"
select element;

Related

C# split xml innertext or parse innerxml

I have an XML file with a structure similar to this
<entry name="something">
<members>
<member>aaa</member>
<member>bbb</member>
</members>
</entry>
<entry name="something_else">
<members>
<member>ccc</member>
<member>ddd</member>
</members>
</entry>
I need to be able to get the values out of each of the member nodes to store in a datatable. if i use the innertext property, it concatenates the values (aaabbb). there is nothing discernible to split the string on. I can also use the inner XML but then i just get a string with the XML structure (aaa bbb<\member>)
What is the best way to get each value out of the XML elements and store it in a string array?
here is what I have been trying.
foreach (XmlNode grpNode in GrpList)
{
subNode = grpNode.Attributes["name"];
if (subNode != null)
{
Obj = grpNode.Attributes["name"].Value;
}
subNode = grpNode["members"];
if (subNode != null)
{
string innerXml = string.Empty;
innerXml = grpNode["members"].InnerXml.ToString();
string[] tempArrary = innerXml.Split(new char[] {'>', '<'});
}
}
You can use Xpath to iterate through Entry nodes and get the members within it like this
string xml = "<root><entry name='something'>" +
"<members>" +
"<member>aaa</member>" +
"<member>bbb</member>" +
"</members>" +
"</entry>" +
"<entry name='something_else'>" +
"<members>" +
"<member>ccc</member>" +
"<member>ddd</member>" +
"</members>" +
"</entry></root>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
var memsList = doc.SelectNodes("//entry");
foreach (XmlNode a in memsList)
{
Console.WriteLine(a.Attributes["name"].Value);
var memList = a.SelectNodes("members/member");
foreach(XmlNode x in memList)
Console.WriteLine(x.InnerText);
}
You need to iterate the child elements within members, so something like:
foreach (var node in grpNode["members"].ChildNodes)
{
var value = node.InnerText;
}
That said, you would be better off using LINQ to XML unless you have some specific reason to use XmlDocument. This gives you much more expressive code, for example:
var doc = XDocument.Parse(xml);
var something = doc.Descendants("entry")
.Where(e => (string)e.Attribute("name") == "something")
.Single();
var somethingMembers = something.Descendants("member")
.Select(e => e.Value)
.ToArray();
This should do the trick:
XDocument xdoc = XDocument.Load(#"Path/to/file");
var result = xdoc.Descendants("member").Select (x => x.Value).ToArray();
Result:
Demo Code
the xml you've provided isn't valid. But assuming you just want the inner text of all member nodes into a string array, I'd just use Linq-To-Xml (XDocument):
var results = XDocument.Parse(xmlString)
.Descendants("member")
.Select(m => m.Value)
.ToArray();
Even though you're using the old XmlDocument API, by throwing in an .OfType<XmlNode>() you can convert an XmlNodeList to a generic enumerable and thereby mix in some linq and lambda syntax, for instance:
var tempArrary = subNode.SelectNodes("member").OfType<XmlNode>().Select(n => n.InnerText).ToArray();

Get child node attribute value based on parent node attribute value

I have a xml given below
<session xmlns="http://test.net/schema/session/1.0" name="test" start="2015-07-01T07:20:31.425Z">
<download>
<filename value="/UAT/Incoming/ Status/Archive/8-22-2011 3-20-14 PM306.xml" />
<result success="false">
<message>Timeout waiting .</message>
</result>
</download>
</session>
I want to select the message node value only if Result node value is false.
I donot want to check on hard coded parent node like node download because it may change
Can anyone help me please..
XDocument doc = XDocument.Load("your xml file path");
var result = doc.Elements().
First(e => e.Name == "download")
.Elements().First(e => e.Name == "result");
if (result.Attributes().First(a => a.Name == "success").Value == "false")
return result.Elements().First(e => e.Name == "message").Value;
This worked for me:
XNamespace ns = XNamespace.Get("http://test.net/schema/session/1.0");
IEnumerable<XElement> failures =
doc
.Descendants(ns + "download")
.Concat(doc.Descendants(ns + "upload"))
.Elements(ns + "result")
.Elements(ns + "message")
.Where(e => e.Parent.Attributes("success").Any(a => !(bool)a));
From your input I got this:
First of all you need to set the namespace for your XML file like this:-
XNamespace ns = "http://test.net/schema/session/1.0";
I will start looking into descendants of download because I need to find the result element which contains the success attribute (whose value we want to check). Thus, a simple where condition will filter those nodes for us and finally we can select the message node.
Update:
You can use Concat if you want to search both download & upload like this:-
IEnumerable<XElement> result = xdoc.Descendants(ns + "download")
.Concat(xdoc.Descendants(ns + "upload"))
.Where(x => x.Element(ns + "result") != null &&
(string)x.Element(ns + "result").Attribute("success") == "false")
.Select(x => x.Element(ns +"result").Element(ns +"message"));
I am also checking in the where clause if result node exist or not by checking for null, otherwise it may result in Null reference exception.
Using some XPath, the following piece of code should work
string xml = #"<session xmlns=""http://test.net/schema/session/1.0"" name=""test"" start=""2015-07-01T07:20:31.425Z"">
<download>
<filename value=""/UAT/Incoming/ Status/Archive/8-22-2011 3-20-14 PM306.xml"" />
<result success=""false"">
<message>Timeout waiting .</message>
</result>
</download>
</session>";
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(xml);
// Here, you load the namespace used in your xml. You'll need it later in your XPath queries
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xdoc.NameTable);
nsmgr.AddNamespace("test", "http://test.net/schema/session/1.0");
// XPath to select the "result" node whose attribute "success" equals false
XmlElement xelt = xdoc.DocumentElement.SelectSingleNode("descendant::test:download/test:result[#success=\"false\"]", nsmgr) as XmlElement;
// return the "message" node
return xelt.FirstChild as XmlElement;
As already mentioned, you'll find more details about XPath expression on this link: https://msdn.microsoft.com/en-us/library/d271ytdx%28v=vs.110%29.aspx

Get Atttribute value from XDocument

I am trying to get the ResponseCode attribute value out of this XML.
The XML is an XDocument
<IDMResponse xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" MajorVersion="1" xmlns="http://www.fake.org/namespace/">
<ARTSHeader>
<Response ResponseCode="Rejected">
<RequestID>1</RequestID>
<BusinessError Severity="Error">
<Code>IdmInvalidUserNamePasswordLoginProvided</Code>
<Description>Invalid username or password, if problem persists, please contact Administrator</Description>
</BusinessError>
</Response>
</ARTSHeader>
</IDMResponse>
With XPath: (No error checks done)
XmlNamespaceManager nsm = new XmlNamespaceManager(new NameTable());
nsm.AddNamespace("def", "http://www.fake.org/namespace/");
XDocument doc = XDocument.Parse(xml);
string code =
doc
.XPathSelectElement(#"/def:IDMResponse/def:ARTSHeader/def:Response", nsm)
.Attribute("ResponseCode")
.Value;
foreach (XElement el in doc.Root.Elements())
{
if(el.Name.ToString() == "ARTSHeader")
foreach(XElement ell in el.Elements())
{
if(ell.Name.ToString() == "Response")
string responseCode = ele.Attribute("ResponseCode").Value;
}
}
Does this work for you? I dont know the whole structure of your xml so you might need to go deeper into the nested xml to get to Response first
One possible way :
.....
XNamespace ns = "http://www.fake.org/namespace/";
string responseCode = (string)doc.Descendants(ns+"Response")
.First()
.Attribute("ResponseCode");
Console.WriteLine(responseCode);
You could try this one out, I haven`t tested so you might need to rearrange some structure
XDocument doc1 = XDocument.Parse(soapResult);
XNamespace ns1 = "http://www.fake.org/namespace/";
var items = doc1.Descendants(ns1 + "ARTSHeader").Descendants(ns1 + "Response").First().Attribute("ResponseCode").Descendants(ns1 + "BusinessError").First().Attribute("Severity")
.Select((x) => new
{
Code = x.Element(ns1 + "Code").Value,
Description = x.Element(ns1 + "Description").Value,
});

XDocument does not load Xml string properly

I'm trying to do the following: load an Xml string into a XDocument object, but when I try to access elements through Descendants method it return nothing when I tried to see the value of inner elements in Visual Studio it does not recognize it as Xml so what is the problem here?
string xml = #"<ArrayOfKeyValueOfstringint xmlns=""http://schemas.microsoft.com/2003/10/Serialization/Arrays"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
<KeyValueOfstringint>
<Key>crscmprsn_ttlprt1</Key>
<Value>1</Value>
</KeyValueOfstringint>
<KeyValueOfstringint>
<Key>ptntmntrfrm_ttlprt1</Key>
<Value>1</Value>
</KeyValueOfstringint>
</ArrayOfKeyValueOfstringint>";
var xdoc = XDocument.Parse(xml);
IEnumerable<XElement> elements = xdoc.Descendants("KeyValueOfstringint");
var lst = new List<KeyValuePair<string,int>>();
foreach (var item in elements)
{
var k = item.Element("Key").Value;
int v = int.Parse(item.Element("Value").Value);
var kvp = new KeyValuePair<string,int>(k,v);
lst.Add(kvp);
}
You need to specify namespace to get your elements:
var ns = "http://schemas.microsoft.com/2003/10/Serialization/Arrays";
var elements = xdoc.Descendants(ns + "KeyValueOfstringint");
For more information about xml namespaces take a look at: Working with XML Namespaces

Linq to XML dynamic XML Decendants

I'm parsing a lot of XML files using Linq to XML synatx, everything works when I try to access top level elements
var indexroot = (from element in prodcutDocument.Root.Descendants("indexroot")
select new
{
model = (string)element.Element("MODEL"),
}).FirstOrDefault()
The problem occurs when I need to access lower level childs of that document I tried:
var indexroot = (from element in prodcutDocument.Root.Descendants("indexroot")
select new
{
ddName = (string)element.Descendants("DD_NAME").Elements("name").First();
}).FirstOrDefault()
and
var indexroot = (from element in prodcutDocument.Root.Descendants("indexroot").Descendants("DD_NAME")
select new
{
ddName = (string)element.Element("name")
}).FirstOrDefault();
Sadly none of that works and i get same error "Sequence contains no elements". And one more thing sometimes the XML document contains those tags and sometimes not is something like this enough for handling this case?
var indexroot = (from element in prodcutDocument.Root.Descendants("indexroot").Descendants("DD_NAME")
select new
{
ddName = (string)element.Element("name") ?? "-"
}).FirstOrDefault();
Edit:
I don't think is possible to paste short version of XML that would be simple, so here's full version: http://pastebin.com/uDkP3rnR and for the code example:
XDocument prodcutDocument = XDocument.Load(this.ServerPATHData + file);
var indexroot = (from element in prodcutDocument.Root.Descendants("indexroot")
select new
{
modelis = (string)element.Element("MODELIS"),
T2918_0 = (string)element.Descendants("dd_DARBINIS_GRAFIKAS_SPEC").First()
}).FirstOrDefault();
writeTxt.WriteLine("modelis: " + indexroot.modelis);
writeTxt.WriteLine("T2979_0" + indexroot.T2918_0);
In examining the sample XML that you posted on PasteBin, it appears to me that the elements that you mention appear only once. To access them, you can simply specify a path to each as follows:
XElement indexroot = document.Root.Element("indexroot");
XElement modelis = indexroot.Element("MODELIS");
XElement dd_dgs = indexroot.Element("dd_DARBINIS_GRAFIKAS_SPEC");
XElement voltageuv = dd_dgs.Element("VoltageUV");
string t2979_0 = (string)voltageuv.Element("T2979_0");
string t2861_60 = (string)voltageuv.Element("T2861_60");
string t2757_121 = (string)voltageuv.Element("T2757_121");
(Note that you may need to check for null if there is a chance that any of the elements you are trying to access may not be present. Without doing so, you'll encounter a NullReferenceException.)
Here is a snippet of the XML that you posted to give context to the above code:
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<PDB>
<indexroot>
<ed_BENDRA_MAKS_SUV_GALIA>1.45</ed_BENDRA_MAKS_SUV_GALIA>
<ed_BENDRA_MAKS_SROVE>6.48</ed_BENDRA_MAKS_SROVE>
<TIPAS>1</TIPAS>
<MODELIS>RIS 2500 HW EC 3.0</MODELIS>
<dd_DARBINIS_GRAFIKAS_SPEC>
<VoltageUV>
<T2979_0>229,42</T2979_0>
<T2861_60>227,98</T2861_60>
<T2757_121>228,97</T2757_121>
</VoltageUV>
<CurrentIA>
<T2979_0>2,56</T2979_0>
<T2861_60>2,63</T2861_60>
<T2757_121>2,72</T2757_121>
</CurrentIA>
</dd_DARBINIS_GRAFIKAS_SPEC>
</indexroot>
</PDB>
You can just change:
element.Descendants("dd_DARBINIS_GRAFIKAS_SPEC").First()
to this:
element.Descendants("dd_DARBINIS_GRAFIKAS_SPEC").FirstOrDefault() ?? "-"

Categories