Extract data from XML using XmlDocument - c#

I am consuming a weather web service in c#. I am passing it Lat-Long and it returns forecasted maximum & minimum temperature of that area. Following is code that i am using
var response = client.ndfdGen(latlong);
XmlDocument doc = new XmlDocument();
doc.LoadXml(response);
And the following is the response data, that I get i.e. xml response
In this response, there is Latitude and Longitude. I have to extract this.
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:NDFDgenResponse xmlns:ns1="http://graphical.weather.gov/xml/DWMLgen/wsdl/ndfdXML.wsdl">
<dwmlOut xsi:type="xsd:string"><![CDATA[<?xml version="1.0"?>
<dwml version="1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.nws.noaa.gov/forecasts/xml/DWMLgen/schema/DWML.xsd">
<head>
<product srsName="WGS 1984" concise-name="time-series" operational-mode="official">
<title>NOAA's National Weather Service Forecast Data</title>
<field>meteorological</field>
<category>forecast</category>
<creation-date refresh-frequency="PT1H">2015-04-15T15:13:07Z</creation-date>
</product>
<source>
<more-information>http://www.nws.noaa.gov/forecasts/xml/</more-information>
<production-center>Meteorological Development Laboratory<sub-center>Product Generation Branch</sub-center></production-center>
<disclaimer>http://www.nws.noaa.gov/disclaimer.html</disclaimer>
<credit>http://www.weather.gov/</credit>
<credit-logo>http://www.weather.gov/images/xml_logo.gif</credit-logo>
<feedback>http://www.weather.gov/feedback.php</feedback>
</source>
</head>
<data>
<location>
<location-key>point1</location-key>
<point latitude="39.01" longitude="-77.02"/>
</location>
<moreWeatherInformation applicable-location="point1">http://forecast.weather.gov/MapClick.php?textField1=39.01&textField2=-77.02</moreWeatherInformation>
<time-layout time-coordinate="local" summarization="none">
<layout-key>k-p24h-n2-1</layout-key>
<start-valid-time>2015-04-17T08:00:00-04:00</start-valid-time>
<end-valid-time>2015-04-17T20:00:00-04:00</end-valid-time>
<start-valid-time>2015-04-18T08:00:00-04:00</start-valid-time>
<end-valid-time>2015-04-18T20:00:00-04:00</end-valid-time>
</time-layout>
<parameters applicable-location="point1">
<temperature type="maximum" units="Fahrenheit" time-layout="k-p24h-n2-1">
<name>Daily Maximum Temperature</name>
<value>68</value>
<value>71</value>
</temperature>
</parameters>
</data>
</dwml>]]></dwmlOut>
</ns1:NDFDgenResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I want to extract information in <time-layout time-coordinate="local" summarization="none"> like start-valid-time,end-valid-time and temperature from <temperature type="maximum" units="Fahrenheit" time-layout="k-p24h-n2-1"> tags.
How can I reach out to these nodes and iterate over it?

You're going to have to extract the CDATA first, that's really the only special challenge here - then you can use XmlDocument or XDocument or XmlReader. I'd recommend doing it this way:
var response = client.ndfdGen(latlong);
XDocument xd = null;
using (XmlReader xr = XmlReader.Create(new StringReader(response))) // load the response into an XmlReader
{
xr.ReadToDescendant("dwmlOut"); // go to the dwmlOut node
xr.Read(); // move to the CDATA in that node
xd = XDocument.Parse(xr.Value); // load **that** XML into your XDocument
}
string startTime = xd.Descendants("start-valid-time").First().Value;
and so on.
If you insist on using XmlDocument, you could use the same method here and just do XmlDocument.LoadFrom(xr.Value), but the XDocument API is a bit more flexible and will certianly perform better.

As suggested in the comments, using XDocument will get you access to a number of LINQ-to-XML methods built for just such a purpose:
// load up the xml into an XDocument
var response = client.ndfdGen(latlong);
var originalDocument = XDocument.Parse(response);
// extract cdata
var cdata = originalDocument.DescendantNodes().OfType<XCData>().First().Value;
var cdataDocument = XDocument.Parse(cdata);
// find the right element via xpath
var myElement = cdataDocument.Root.XPathSelectElement("//dwml/data/location/point");
return myElement.Attribute("latitude").Value;
Note that using the "//" operator in xPath doesn't have great performance. Try nailing down an absolute path once you get the proof of concept working. An explanation of the xPath operations available can be found on MSDN

Related

Loading Descendants of XML using XDocument.Load

I am attempting to use XDocument.Load to access some latitude and longitude figures. Here is the example XML document;
<?xml version="1.0" encoding="utf-8"?>
<Response xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/search/local/ws/rest/v1">
<Copyright>Copyright © 2016 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.</Copyright>
<BrandLogoUri>http://dev.virtualearth.net/Branding/logo_powered_by.png</BrandLogoUri>
<StatusCode>200</StatusCode>
<StatusDescription>OK</StatusDescription>
<AuthenticationResultCode>ValidCredentials</AuthenticationResultCode>
<TraceId>31a206847f9341d28689e0e7185e163d|DB40051719|7.7.0.0|DB4SCH010061262</TraceId>
<ResourceSets>
<ResourceSet>
<EstimatedTotal>1</EstimatedTotal>
<Resources>
<Location>
<Name>SW1A 1AA, London, London, United Kingdom</Name>
<Point>
<Latitude>51.501018524169922</Latitude>
<Longitude>-0.14159967005252838</Longitude>
</Point>
<BoundingBox>
<SouthLatitude>51.497155806599245</SouthLatitude>
<WestLongitude>-0.14987251765942367</WestLongitude>
<NorthLatitude>51.5048812417406</NorthLatitude>
<EastLongitude>-0.1333268224456331</EastLongitude>
</BoundingBox>
<EntityType>Postcode1</EntityType>
<Address>
<AdminDistrict>England</AdminDistrict>
<AdminDistrict2>London</AdminDistrict2>
<CountryRegion>United Kingdom</CountryRegion>
<FormattedAddress>SW1A 1AA, London, London, United Kingdom</FormattedAddress>
<Locality>London</Locality>
<PostalCode>SW1A 1AA</PostalCode>
</Address>
<Confidence>High</Confidence>
<MatchCode>Good</MatchCode>
<GeocodePoint>
<Latitude>51.501018524169922</Latitude>
<Longitude>-0.14159967005252838</Longitude>
<CalculationMethod>Rooftop</CalculationMethod>
<UsageType>Display</UsageType>
</GeocodePoint>
</Location>
</Resources>
</ResourceSet>
</ResourceSets>
</Response>
And here is the code I am using attempting to access latitude and longitude;
string latitude = XDocument.Load(#"test.xml").Root
.Descendants("ResourceSets")
.Descendants("ResourceSet")
.Descendants("Resources")
.Descendants("Location")
.Descendants("GeocodePoint")
.Select(element => element.Attribute("Latitude").Value).FirstOrDefault();
But this returns an empty string. How can I navigate the document correctly?
First thing you don't need to call in multiples level Descendants method if you want to get all GeocodePoint nodes. You can only do this:
XNamespace ns = "http://schemas.microsoft.com/search/local/ws/rest/v1";
string latitude = XDocument.Load(#"test.xml")
.Descendants(ns+"GeocodePoint")
.Select(e=> (string)e.Element(ns+"Latitude"))
.FirstOrDefault();
With that call Linq to XML will retrieve all the GeocodePoints in your xml
If you want to get lat and long values, then you can project either to an anonymous type or a custom class (DTO) like this:
XNamespace ns = "http://schemas.microsoft.com/search/local/ws/rest/v1";
var coord= XDocument.Load(#"xml.xml")
.Descendants(ns+"GeocodePoint").Select(e => new { Lat = (string)e.Element(ns+"Latitude"), Lng = (string)e.Element(ns+"Longitude") })
.FirstOrDefault();
About your issue
Your problem was you were calling Attribute method to get the Latitude value, but as you can see in your xml structure GeocodePoint node doesn't have that as an attribute, it is a nested element. That's way you need to use Element method instead. The second issue was you need to take the namespace into account as I show above.
You are not using namespace. Your Xml provided with namespace
<Response xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.microsoft.com/search/local/ws/rest/v1">
So you need to use it when searching for elements.
XDocument doc = XDocument.Load(#"C:\tmp\data.xml");
XNamespace ns = doc.Root.Name.Namespace;
string value = doc.Root.Descendants(ns + "Latitude").FirstOrDefault().Value;
Or search without namespace, by LocalName of element
string value = doc.Root
.Descendants
.Where(element => element.Name.LocalName.Equals("Latitude"))
.FirstOrDefault()
.Value;
If you using Descendats method, then you can search straight for element you need.

Getting an XElement with a namespace via XPathSelectElements

I have an XML e.g.
<?xml version="1.0" encoding="utf-8"?>
<A1>
<B2>
<C3 id="1">
<D7>
<E5 id="abc" />
</D7>
<D4 id="1">
<E5 id="abc" />
</D4>
<D4 id="2">
<E5 id="abc" />
</D4>
</C3>
</B2>
</A1>
This is may sample code:
var xDoc = XDocument.Load("Test.xml");
string xPath = "//B2/C3/D4";
//or string xPath = "//B2/C3/D4[#id='1']";
var eleList = xDoc.XPathSelectElements(xPath).ToList();
foreach (var xElement in eleList)
{
Console.WriteLine(xElement);
}
It works perfectly, but if I add a namespace to the root node A1, this code doesn't work.
Upon searching for solutions, I found this one, but it uses the Descendants() method to query the XML. From my understanding, this solution would fail if I was searching for <E5> because the same tag exists for <D7>, <D4 id="1"> and <D4 id="2">
My requirement is to search if a node exists at a particular XPath. If there is a way of doing this using Descendants, I'd be delighted to use it. If not, please guide me on how to search using the name space.
My apologies in case this is a duplicate.
To keep using XPath, you can use something link this:
var xDoc = XDocument.Parse(#"<?xml version='1.0' encoding='utf-8'?>
<A1 xmlns='urn:sample'>
<B2>
<C3 id='1'>
<D7><E5 id='abc' /></D7>
<D4 id='1'><E5 id='abc' /></D4>
<D4 id='2'><E5 id='abc' /></D4>
</C3>
</B2>
</A1>");
// Notice this
XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
nsmgr.AddNamespace("sample", "urn:sample");
string xPath = "//sample:B2/sample:C3/sample:D4";
var eleList = xDoc.XPathSelectElements(xPath, nsmgr).ToList();
foreach (var xElement in eleList)
{
Console.WriteLine(xElement);
}
but it uses the Descendants() method to query the XML. From my understanding, this solution would fail if I was searching for because the same tag exists for , and
I'm pretty sure you're not quite understanding how that works. From the MSDN documentation:
Returns a filtered collection of the descendant elements for this document or element, in document order. Only elements that have a matching XName are included in the collection.
So in your case, just do this:
xDoc.RootNode
.Descendants("E5")
.Where(n => n.Parent.Name.LocalName == "B4");
Try this
var xDoc = XDocument.Parse("<A1><B2><C3 id=\"1\"><D7><E5 id=\"abc\" /></D7><D4 id=\"1\"><E5 id=\"abc\" /></D4><D4 id=\"2\"><E5 id=\"abc\" /></D4></C3></B2></A1>");
foreach (XElement item in xDoc.Element("A1").Elements("B2").Elements("C3").Elements("D4"))
{
Console.WriteLine(item.Element("E5").Value);//to get the value of E5
Console.WriteLine(item.Element("E5").Attribute("id").Value);//to get the value of attribute
}

How to get the Element value from returned Soap based XML Message

I am to get the element value of Soap based retuned XML as below.
XML File:
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SendToDSSResponse xmlns="http://tempuri.org/">
<SendToDSSResult>
<RC>0</RC>
<RCD></RCD>
<PCKT>
<IDNO>1212</IDNO>
<IDTYPE>051</IDTYPE>
<NOBOX>121216</NOBOX>
<NAME>James</NAME>
</PCKT>
</SendToDSSResult>
</SendToDSSResponse>
</soap:Body>
</soap:Envelope>
Now I want to get the values of IDNO, NoBox and Name. I am trying to use the following code below to get the values but it throws an Exception. What's the correct way to get the element values?
C# Code:
var xDoc = XDocument.Parse(cleanXml); //OR XDocument.Load(filename)
string Name = xDoc.Descendants("Name").First().Value;
I think you should add XNamespace and then you can read out the specific value from the nodes or tags under the node, try this demo in your ConsoleApplication:
XDocument doc = XDocument.Load("XMLFile1.xml");
var result = doc.Descendants(XNamespace.Get("http://tempuri.org/")+"NAME").First();
Console.WriteLine(result.Value);
Use Root property.
string name = xDoc.Root.Descendants("NAME").First().Value;

Using XmlTextReader to Loop though XML attributes that have the same name

I am doing some practice code with the XmlTextReader. I have written some very basic XML as shown here:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<task name="mixed_sprite_task_test">
<sprite>
<type>animatedSprite</type>
<id>0</id>
<name>animatedSprite</name>
<fileName>iyezoz</fileName>
<startingPositionX>200</startingPositionX>
<startingPositionY>200</startingPositionY>
<sheetSizeX>12</sheetSizeX>
<sheetSizeY>35</sheetSizeY>
<startingFrameX>0</startingFrameX>
<startingFrameY>0</startingFrameY>
<startingState>standing</startingState>
<movementSpeed>15</movementSpeed>
<frameDelay>0.055</frameDelay>
</sprite>
<sprite>
<type>staticSprite</type>
<id>0</id>
<name>staticSprite</name>
<fileName>Super_Mario_63</fileName>
<startingPositionX>0</startingPositionX>
<startingPositionY>0</startingPositionY>
</sprite>
<sprite>
<type>simpleSprite</type>
<id>0</id>
<name>simpleSprite</name>
<fileName>imgres</fileName>
<startingPositionX>100</startingPositionX>
<startingPositionY>100</startingPositionY>
<movementSpeed>15</movementSpeed>
</sprite>
</task>
This file shows that I have a task. In the task I have 3 sprites.
In my code I want to loop through each sprite and collect the information.
I can get the data from the first sprite with no issue. Is there a certain way to loop through an xml with attributes of the same name?
Thank-you!
I prefer Linq2Xml.
var xDoc = XDocument.Parse(xmlstring); //or XDocument.Load(filename);
var sprites = xDoc.Descendants("sprite")
.Select(s=>s.Elements()
.ToDictionary(e=>e.Name.LocalName,e=>(string)e))
.ToList();
You can use it as
var type = sprites[0]["type"];
or can take a safe action
string delay;
if (sprites[1].TryGetValue("frameDelay", out delay))
{
Console.WriteLine(delay);
}
You can select all the nodes named "sprite"
var myXml = new XmlDocument();
myXml.Load(myDocument);
XmlNode rootElement = myXml.DocumentElement;
foreach (XmlNode item in rootElement.SelectNodes(#"/task/sprite"))
{
// do stuff with node
}

Why doesn't this XPath query returns any nodes?

I'm querying Sharepoint server-side and getting back results as Xml. I want to slim down the Xml into something more lightweight before sending it to jQuery through a WebMethod.
However my XPath query isn't working. I thought the following code would return all Document nodes, but it returns nothing. I've used XPath a little before, I thought //Document do the trick.
C# XPath query
XmlDocument xmlResults = new XmlDocument();
xmlResults.LoadXml(xml); // XML is a string containing the XML source shown below
XmlNodeList results = xmlResults.SelectNodes("//Document");
XML being queried
<ResponsePacket xmlns="urn:Microsoft.Search.Response">
<Response domain="QDomain">
<Range>
<StartAt>1</StartAt>
<Count>2</Count>
<TotalAvailable>2</TotalAvailable>
<Results>
<Document relevance="126" xmlns="urn:Microsoft.Search.Response.Document">
<Title>Example 1.doc</Title>
<Action>
<LinkUrl size="32256" fileExt="doc">http://hqiis99/Mercury/Mercury documents/Example 1.doc</LinkUrl>
</Action>
<Description />
<Date>2010-08-19T14:44:56+01:00</Date>
</Document>
<Document relevance="31" xmlns="urn:Microsoft.Search.Response.Document">
<Title>Mercury documents</Title>
<Action>
<LinkUrl size="0" fileExt="aspx">http://hqiis99/mercury/Mercury documents/Forms/AllItems.aspx</LinkUrl>
</Action>
<Description />
<Date>2010-08-19T14:49:39+01:00</Date>
</Document>
</Results>
</Range>
<Status>SUCCESS</Status>
</Response>
</ResponsePacket>
You're trying to select Document elements which don't have a namespace... whereas the default namespace is actually "urn:Microsoft.Search.Response" here.
I think you want something like this:
XmlDocument xmlResults = new XmlDocument();
xmlResults.LoadXml(xml);
XmlNamespaceManager manager = new XmlNamespaceManager(xmlResults.NameTable);
manager.AddNamespace("ns", "urn:Microsoft.Search.Response.Document");
XmlNodeList results = xmlResults.SelectNodes("//ns:Document", manager);
This finds two elements.
If you can use LINQ to XML instead, it makes it all somewhat easier:
XDocument results = XDocument.Parse(xml);
XNamespace ns = "urn:Microsoft.Search.Response.Document";
var documents = results.Descendants(ns + "Document");
I love LINQ to XML's namespace handling :)
Alternatively, you could try the following and ignore the namespaces:
XmlDocument xmlResults = new XmlDocument();
xmlResults.LoadXml(xmlString);
XmlNodeList results = xmlResults.SelectNodes("//*[local-name()='Document']");

Categories