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.
Related
I have an XML document I am trying to query with LINQ to XML. The XML is ..
<?xml version="1.0" encoding="UTF-8"?>
<response>
<operation>
<authentication>
<username>redacted</username>
<isauthenticated>true</<isauthenticated>>
</authentication>
<result>
<status>success</status>
<other-elements></<other-elements>
<other-elements></<other-elements>
<other-elements>
<other-sub-elements></other-sub-elements>
<other-sub-elements></other-sub-elements>
</<other-elements>
</result>
</operation>
</response>
I am trying to read the value of the node <status> to determine if the API call was successful. I am having trouble putting together the LINQ syntax necessary to grab the value of the <status node. I thought I could use XPath syntax as such to grab the value.
XDocument xml = XDocument.Parse(xmlResponse);
string returnValue = = xml.Descendants("result/status").Select(x => x.Name).ToString();
However, I am getting the following error ..
The '/' character, hexadecimal value 0x2F, cannot be included in a name.
Try this code:
XDocument xdoc = XDocument.Parse(#"
<response>
<operation>
<authentication>
<username>redacted</username>
<isauthenticated>true</isauthenticated>
</authentication>
<result>
<status>success</status>
</result>
</operation>
</response>
");
Boolean isSuccess;
String s = xdoc.Element("response").Element("operation").Element("result").Element("status").Value;
isSuccess = s == "success";
It gets the value of the status element and checks whether it is equal to a specific value; in this case, isSuccess will be true.
The LINQ-to-XML methods Elements() and Descendants() only deal with single names, not xpath-like paths. If you want to give an xpath expression, use the xpath extensions.
// using System.Xml.Linq;
var status = (string)xml.XPathSelectElement("//result/status");
Otherwise you need to build up an equivalent query using the methods correctly.
var status = (string)xml.Descendants("result").Elements("status").FirstOrDefault();
From the following XML, I want to find a value based on the Employer.
<?xml version="1.0" encoding="UTF-8"?>
<Document>
<Details>
<Employer>Taxes</Employer>
<Adr>
<Strt>Street</Strt>
<Twn>Town</Twn>
</Adr>
</Details>
<DetailsAcct>
<Recd>
<Payroll>
<Id>9</Id>
</Payroll>
</Recd>
<br>
<xy>A</xy>
</br>
</DetailsAcct>
</Document>
the C# code I applied is
detail = root.SelectSingleNode($"//w:Document//w:Employer[contains(text(), 'Taxes']/ancestor::Employer",nsmgr);
But it gives me an invalid token error.
What am I missing?
The error was due to [contains(...], notice closing parentheses is missing. And since you want to return Employer element, no need for ancestor::Employer here :
//w:Document//w:Employer[contains(., 'Taxes')]
If the XML posted resembles structure of the actual XML (except the namespaces), better to use more specific XPath i.e avoid using costly // :
/w:Document/w:Details/w:Employer[contains(., 'Taxes')]
An alternative is to use LINQ to XML.
If the XML is in a string:
string xml = "<xml goes here>";
XDocument document = XDocument.Parse(xml);
XElement element = document.Descendants("Employer").First();
string value = element.Value;
If the XML is in a .xml file:
XDocument document = XDocument.Load("xmlfile.xml");
XElement element = document.Descendants("Employer").First();
string value = element.Value;
You can also find an employer element with a specific value, if that's what you need:
XElement element = document.Descendants("Employer").First(e => e.Value == "Taxes");
Note: this will throw an exception if no element is found with the specified value. If that is not acceptable, then you can replace .First(...) with .FirstOrDefault(...) which will simply return null if no element is found.
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
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
}
I want to return the latitude node (for example) from the following XML string (from Yahoo geocoding API.)
<ResultSet version="1.0">
<Error>0</Error>
<ErrorMessage>No error</ErrorMessage>
<Locale>us_US</Locale>
<Quality>60</Quality>
<Found>1</Found>
<Result>
<quality>87</quality>
<latitude>37.68746446</latitude>
<longitude>-79.6469878</longitude>
<offsetlat>30.895931</offsetlat>
<offsetlon>-80.281192</offsetlon>
<radius>500</radius>
<name></name>
<line1>123 Main Street</line1>
<line2>Greenville, SC 29687</line2>
<line3></line3>
<line4>United States</line4>
<house>123</house>
<street>Main Street</street>
<xstreet></xstreet>
<unittype></unittype>
<unit></unit>
<postal>29687</postal>
<neighborhood></neighborhood>
<city>Greenville</city>
<county>Greenville County</county>
<state>South Carolina</state>
<country>United States</country>
<countrycode>US</countrycode>
<statecode>SC</statecode>
<countycode></countycode>
<uzip>29687</uzip>
<hash>asdfsdfas</hash>
<woeid>127757446454</woeid>
<woetype>11</woetype>
</Result>
</ResultSet>
I already have this XML successfully loaded into an XElement instance but I cannot seem to be able to find the way to load the latitude node (for example) into a string variable. If there is no node or the node is empty then I would like to get a Null or Nullstring. If there is more than one (there won't be but just in case) then return the first instance.
I thought this would be easy but I can't get it to work. All of the Linq queries I have tried are returning null.
While I am at it if you could explain it with enough detail so that I can also get the Error node. I only mention it because it is at a different level.
Thanks.
Seth
To get latitude's value:
var latitudeElement = resultXML.Descendants("latitude").FirstOrDefault();
string latitude = latitudeElement == null ? String.Empty : latitudeElement.Value;
And you could get the Error element with the following:
var errorElement = resultXML.Descendants("Error").First();
I'm using resultXML as the reference to the parsed XML.
Make sure you're using the System.Xml.XPath namespace, and try:
var doc = XDocument.Parse(<your xml here>);
var el = doc.XPathSelectElement("ResultSet/Result/latitude");
el should contain an XElement class or null if the node wasn't found.
See the MSDN docs for XPath 1.0 for more info on how to use it.