Unable to extract child element from an XML using XDocument - c#

Following is the XML from which I am trying to extract a child element.
<?xml version="1.0" encoding="UTF8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header xmlns="http://SomeValue/SomeValue/2009/01/SomeValue.xsd">
<Session>
<SessionId>SomeValue</SessionId>
<SequenceNumber>1</SequenceNumber>
<SecurityToken>SomeValue</SecurityToken>
</Session>
</soap:Header>
<soap:Body>
<Security_AuthenticateReply xmlns="http://SomeValue/SomeValue">
<processStatus>
<statusCode>P</statusCode>
</processStatus>
</Security_AuthenticateReply>
</soap:Body>
</soap:Envelope>
public static string AssignSecurityToken(string response)
{
string Token = "";
XNamespace ns = "http://schemas.xmlsoap.org/soap/envelope/";
XElement xdoc = XElement.Parse(response);
XElement root = xdoc.Descendants(ns + "Header").First();
Token = root.Element("Session").Element("SecurityToken").Value;
Token = root.Descendants("Session").Descendants().Where(n => n.Name == "SecurityToken").FirstOrDefault().Value;
return Token;
}
I want to extract the element Security Token.
Following are the things that I have already worked on:
Tried extracting the element using the approach suggested in the post
How to get value of child node from XDocument
Also posting some code for reference. Both the statements that are
assigning values to the Token variable are throwing "Object not set
to an instance of an object"exception.
Thanks in advance.

You need to take into account the Header's namepace.
public static string AssignSecurityToken(string response)
{
XNamespace ns1 = "http://schemas.xmlsoap.org/soap/envelope/";
XNamespace ns2 = "http://SomeValue/SomeValue/2009/01/SomeValue.xsd";
var envelope = XElement.Parse(response);
var header = envelope.Element(ns1 + "Header");
var session = header.Element(ns2 + "Session");
var security_token = session.Element(ns2 + "SecurityToken");
return security_token.Value;
}
Actually you could go ahead and just call
return XElement.Parse(response).Descendants()
.First(x => x.Name.LocalName == "SecurityToken").Value;

For this response only, it makes sense to just parse the string and extract an element.
This response uses two namespaces, one for SOAP headers and another for the Amadeus login response. You need the second one to retrieve the token :
//SOAP-only namespace
XNamespace soap = "http://schemas.xmlsoap.org/soap/envelope/";
//Default namespace
XNamespace ns = "http://SomeValue/SomeValue/2009/01/SomeValue.xsd";
var response=XElement.Parse(xml);
var token=response.Descendants(ns+"SecurityToken").First().Value;
Other Amadeus responses are huge and XDocument won't be much better (if at all) than using WCF and deserializing to strongly typed objects. XDocument deserializes the entire XML response, the same as DataContractSerializer. Instead of getting back a strongly-typed set of objects though, you get XElements you'll have to map to something else.
If you want to reduce memory consumption by only reading the parts you'll have to use XmlReader and read the XML tokens from the response stream one by one. That's a lot more work.
Another interesting thing is that Amadeus responses use multiple namespaces. This response uses just 2. Other responses, eg searches, use many more.

You might consider working with System.Xml.XmlDocument and System.Xml.XPath.XPathNavigator which are really easy to work with.
I wrote a simple example for you (supporting UTF-8 encoding):
System.Xml.XmlDocument someXmlFile = new System.Xml.XmlDocument();
string xmlPath= #"YOUR_XML_FULL_PATH";
string innerNodeToExtract= #"/Session/SecurityToken";
try
{
// Loading xml file with utf-8 encoding:
string xmlFileStr= Systm.IO.File.ReadAllText(xmlPath, System.Text.Encoding.UTF8);
// Creating the XPathNavigator:
System.Xml.XPath.XPathNavigator xmlNavigator= someXmlFile.CreateNavigator();
// Getting the inner value as string:
string value = xmlNavigator.SelectSingleNode(innerNodeToExtract).Value;
// some code...
}
catch(Exception)
{
// Handle failures
}
Please notice that you can also:
Extract inner values using "#" key.
Move to the child using xmlNavigator.MoveToNext().
And many other things that you can read here.

Related

How to parse GENERIC XML envelope and get all nodes values(inner text) separated (not concatenated)

I want to parse ANY SOAP response XML, not knowing ahead the structure, usually if I have a string xml with response, strXML, and I would do this:
XmlDocument doc = new XmlDocument();
doc.LoadXml(strXML);
String str = doc.InnerText;
return str;
I would get the values of all nodes, the text, but concatenated.
I want to parse SOAP responses in a generic way.
For example if I have this response envelope coming:
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:hy="http://www.herongyang.com/Service/">
<soapenv:Header/>
<soapenv:Body>
<hy:GetExchangeRateResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<ratePart xsi:type="xsd:decimal">123.14</ratePart>
</hy:GetExchangeRateResponse>
</soapenv:Body>
</soapenv:Envelope>
The c# code above would return me for this envelope the good value, 123.14, but if there would be an extra child node to hy:GetExchangeRateResponse let say :
123.14
and
234.14
then I would get: 123.14234.14 concatenated, I want to have something like 123.14, 234.14 ...
PS: usually the services I worked with were returning one value so yeah although is a simple way was working, but not when there are multiple nodes/text.
You can fetch all text nodes from the document and then process each node separately.
For example:
var texts = doc.SelectNodes("//text()").Cast<XmlNode>().Select(x => x.Value).ToArray();
will fetch all text node values into texts (array of string).
using System;
using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
string xxx = "the xml response with multiple final nodes";
XDocument doc = XDocument.Parse(xxx);
List<string> texts = (from node in doc.DescendantNodes().OfType<XText>() select node.Value).ToList();
string asLongString = string.Join(",", texts);
Console.WriteLine("Values {0}", asLongString);
}
}
this guy gave me the answer first sorry :) https://csharpforums.net/members/johnh.4/

Pulling values from individual elements in xml

I am working with a soap response form a wcf service and wish to extract the values form the individual elements. So far I am able to get the list of values from the soap envelope using:
XDocument xDoc = XDocument.Parse(ServiceResult);
List<XElement> ResultsView = xDoc.Descendants()
.Where(x => x.Name.LocalName == "ResultsView")
.ToList();
This gives me the results list:
<a:ResultsView>
<a:Duration>4032</a:Duration>
<a:Metres>41124</a:Metres>
<a:Status>Ok</a:Status>
</a:ResultsView>
I have not been able to get the individual results by querying the ResultsView I can get all the values in a single string which is of no use. Can you suggest a method that will get the values?
The full soap envelope returned is:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><GetLocalDataResponse xmlns="http://tempuri.org/">
<GetLocalDataResult xmlns:a="http://schemas.datacontract.org/2004/07/LocalWcf"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:ResultsView>
<a:Duration>4032</a:Duration>
<a:Metres>41124</a:Metres>
<a:Status>Ok</a:Status>
</a:ResultsView>
</GetLocalDataResult></GetLocalDataResponse></s:Body></s:Envelope>
I have tried a few different methods to extract the values mainly using linq like:
var results = ResultsView.Select(x => new
{
ResultsView = (string)x.Element("Duration"),
duration = x.Element("Duration")
});
The problem is that you're asking for an element without the namespace. If you use the right namespace, you don't need to check for local names or anything like that:
XNamespace ns = "http://schemas.datacontract.org/2004/07/LoacalWcf";
XDocument doc = XDocument.Parse(ServiceResult);
XElement resultsView = doc.Descendants(ns + "ResultsView").Single();
XElement duration = resultsView.Element(ns + "Duration");
Note the use of the + operator to create an XName from an XNamespace and a string.
(It looks like you may well then want to cast duration to int rather than string to get the value in a semantically-useful form.)

Issues Querying XML with Namespace

I am attempting to consume a Rest service that returns an XML response. I have successfully made the get request, my problem is processing the response. The response includes a namespace that seems to be messing up my linq query. I have tried almost everything I can think of userNames always comes up empty. Any help would be greatly appreciated and could possibly save my sanity.
<?xml version="1.0" encoding="UTF-8"?>
<tsResponse xmlns="http://tableausoftware.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableausoftware.com/api http://tableausoftware.com/api/ts-api-2.0.xsd">
<users>
<user id="c9274ce9-0daa-4aad-9bd2-3b1d6d402119" name="_DevITTemplate" role="Unlicensed" publish="false" contentAdmin="false" lastLogin="" externalAuthUserId=""/>
string usersList =
request.DownloadString("http://bshagena-sandbox/api/2.0/sites/b4126fe9-d7ee-4083-88f9- a5eea1f40416/users/");
request.Dispose();
XDocument xd;
XElement users;
XNamespace ns = "http://tableausoftware.com/api";
xd = XDocument.Parse(usersList);
users = xd.Root.Elements().First();
var userNames = from a in users.Descendants(ns +"users")
select (string)a.Attribute("name").Value;
It is your user element which contains attribute name, not the names wrapper element. Adjust your xpath accordingly: (Your use of the XNamespace is fine)
var userNames = from a in users.Descendants(ns + "user")
select a.Attribute("name").Value;
Minor - Attribute.Value is already a string - no need to cast it :)
Here is what I did and it seemed to work. Sorry for all the comments. I am sure this is not the most efficient way to do this. I hope this helps someone else losing their mind over the same issue.
// Sends get request and stores response as a string
string usersList =
request.DownloadString("http://<serverName>/api/2.0/sites/b4126fe9-d7ee-4083-88f9-a5eea1f40416/users/");
// declares an XML document object
XDocument xd;
// Declares and XML element object
XElement users;
// Declares a stupid XML namespace object
XNamespace ns = "http://tableausoftware.com/api";
// Sets document to value of string
xd = XDocument.Parse(usersList);
// Sets the element to value of the first node of the xml document
users = xd.Root.Elements().First();
// Creates an array and queries elements based on attribute of name
var userNames = from a in users.Elements(ns + "user")
select (string)a.Attribute("name").Value;

Cannot parse xml from Yahoo! Fantasy Sports with c#

I did some searching around the web and could not find the cause of my problem so I apologize if that has already been asked in another form I just did not understand.
My problem is that I am trying to parse the XML retrieved from Yahoo! Fantasy Sports but nothing seems to be working.
I have converted the XML I received (using a GET request with my credentials) into a string. Here it is for evaluation.
<?xml version="1.0" encoding="UTF-8" ?>
- <fantasy_content xml:lang="en-US" yahoo:uri="http://fantasysports.yahooapis.com/fantasy/v2/game/223/players" xmlns:yahoo="http://www.yahooapis.com/v1/base.rng" time="5489.1560077667ms" copyright="Data provided by Yahoo! and STATS, LLC" refresh_rate="60" xmlns="http://fantasysports.yahooapis.com/fantasy/v2/base.rng">
- <game>
<game_key>223</game_key>
<game_id>223</game_id>
<name>Football PLUS</name>
<code>pnfl</code>
<type>full</type>
<url>http://football.fantasysports.yahoo.com/f2</url>
<season>2009</season>
- <players count="25">
- <player>
<player_key>223.p.8261</player_key>
<player_id>8261</player_id>
- <name>
<full>Adrian Peterson</full>
<first>Adrian</first>
<last>Peterson</last>
<ascii_first>Adrian</ascii_first>
<ascii_last>Peterson</ascii_last>
</name>
<editorial_player_key>nfl.p.8261</editorial_player_key>
<editorial_team_key>nfl.t.16</editorial_team_key>
<editorial_team_full_name>Minnesota Vikings</editorial_team_full_name>
<editorial_team_abbr>Min</editorial_team_abbr>
- <bye_weeks>
<week>9</week>
</bye_weeks>
<uniform_number>28</uniform_number>
<display_position>RB</display_position>
- <headshot>
<url>http://l.yimg.com/iu/api/res/1.2/7gLeB7TR77HalMeJv.iDVA--/YXBwaWQ9eXZpZGVvO2NoPTg2MDtjcj0xO2N3PTY1OTtkeD0xO2R5PTE7Zmk9dWxjcm9wO2g9NjA7cT0xMDA7dz00Ng--/http://l.yimg.com/j/assets/i/us/sp/v/nfl/players_l/20120913/8261.jpg</url>
<size>small</size>
</headshot>
<image_url>http://l.yimg.com/iu/api/res/1.2/7gLeB7TR77HalMeJv.iDVA--/YXBwaWQ9eXZpZGVvO2NoPTg2MDtjcj0xO2N3PTY1OTtkeD0xO2R5PTE7Zmk9dWxjcm9wO2g9NjA7cT0xMDA7dz00Ng--/http://l.yimg.com/j/assets/i/us/sp/v/nfl/players_l/20120913/8261.jpg</image_url>
<is_undroppable>1</is_undroppable>
<position_type>O</position_type>
- <eligible_positions>
<position>RB</position>
</eligible_positions>
<has_player_notes>1</has_player_notes>
</player>
- <player>
</players>
</game>
</fantasy_content>
The two methods I have tried are these (PLEASE NOTE: "xmlContent" is the string that contains the XML listed above):
1.)
XDocument chicken = XDocument.Parse(xmlContent);
var menus = from menu in chicken.Descendants("name")
select new
{
ID = menu.Element("name").Value,
};
and
2.)
byte[] encodedString = Encoding.UTF8.GetBytes(xmlContent);
MemoryStream ms = new MemoryStream(encodedString);
XmlDocument doc = new XmlDocument();
doc.Load(ms);
foreach (XmlNode row in doc).SelectNodes("//fantasy_content"))
{}
Basically, I get no results enumerated. I have a feeling I am missing some key steps here though. Any help is greatly appreciated. Thank you all.
UPDATE:
As per the awesome suggestions I received, I tried three more things. Since it is not working still, I did not listen very well. :) Actually, that is semi-accurate, please bear with my newbie attempt at working with XML here as I really am thankful for the responses. Here is how I am screwing up the great suggestions, can you offer another tip on what I missed? Thank you all again.
As per Jon Skeet's suggestion (this yields no results for me):
1.) XNamespace ns = "http://fantasysports.yahooapis.com/fantasy/v2/base.rng";
XDocument chicken = XDocument.Parse(xmlContent);
var menus = from menu in chicken.Descendants(ns + "fantasy_content")
select new
{
ID = menu.Element("name").Value,
};
As per the second suggestion (this throws me an error):
2.) var result = XElement.Load(xmlContent).Descendants().Where(x => x.Name.LocalName == "name");
As per the combinations of suggesting I need to identify the namespace and Yahoo! guide at: http://developer.yahoo.com/dotnet/howto-xml_cs.html
3.) xmlContent = oauth.AcquirePublicData(rtUrl, "GET");
byte[] encodedString = Encoding.UTF8.GetBytes(xmlContent);
MemoryStream ms = new MemoryStream(encodedString);
XmlDocument doc = new XmlDocument();
doc.Load(ms);
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("fantasy_content", "http://fantasysports.yahooapis.com/fantasy/v2/base.rng");
XmlNodeList nodes = doc.SelectNodes("/name", ns);
foreach (XmlNode node in nodes)
{
}
This is what's tripping you up:
xmlns="http://fantasysports.yahooapis.com/fantasy/v2/base.rng"
You're asking for elements in the unnamed namespace - but the elements default to the namespace shown above.
It's easy to fix that in LINQ to XML (and feasible but less simple in XmlDocument)
XNamespace ns = "http://fantasysports.yahooapis.com/fantasy/v2/base.rng";
var menus = chicken.Descendants(ns + "name")
...
Note that in your original method you're actually looking for name elements within the name elements - that's not going to work, but the namespace part is probably enough to get you going.
EDIT: It's not clear why you're using an anonymous type at all, but if you really want all the name element values from the document as ID properties in anonymous type instances, just use:
XNamespace ns = "http://fantasysports.yahooapis.com/fantasy/v2/base.rng";
var menus = chicken.Descendants(ns + "name")
.Select(x => new { ID = x.Value });
Note that there's no need to use Descendants(ns + "fantasy_content") as that's just selecting the root element.
Element name consists of two parts: xmlns(namespace) and localname. If xmlns is absent, name is equal to local name. So, you have to create name with namespace or ignore it
You can ignore namespace in your LINQ, just use LocalName
var result = XElement.Load(#"C:\fantasy_content.xml")
.Descendants()
.Where(x => x.Name.LocalName == "name")
.ToList();

Extract XML from SOAP envelope in c#

I have a response from web service in SOAP envelope as follows:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<ProcessTranResponse xmlns="http://www.polaris.co.uk/XRTEService/2009/03/">
<ProcessTranResult xmlns:a="http://schemas.datacontract.org/2004/07/XRTEService" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:PrintFormFileNameContents i:nil="true"/>
<a:ResponseXML>response_message</a:ResponseXML>
</ProcessTranResult>
</ProcessTranResponse>
</s:Body>
And I want to get response_message to a string variable. I tried doing
XDocument doc = XDocument.Parse(Response);
XNamespace xmlnsa = "http://schemas.datacontract.org/2004/07/XRTEService";
var ResponseXML = doc.Descendants(xmlnsa + "ResponseXML");
And when I use watch I see in ResponseXML -> Results View[0] -> Value my response_message, but I can't figure out what is the next step to get to Value from C#.
XContainer.Descendants returns a collection of elements. You should then try something like this:
foreach (XElement el in ResponseXML)
{
Console.WriteLine(el.Value);
}
Or you can do something like this if you know that there is always only one response:
XDocument doc = XDocument.Parse(Response);
XNamespace xmlnsa = "http://schemas.datacontract.org/2004/07/XRTEService";
XElement ResponseXML = (from xml in XMLDoc.Descendants(xmlnsa + "ResponseXML")
select xml).FirstOrDefault();
string ResponseAsString = ResponseXML.Value;
You can employ several solutions to meet your purpose while you may wanna introduce the structure of xml content or not.
Static attitude
You can simply use this:
XmlDocument _doc = new XmlDocument();
doc.LoadXml(_stream.ReadToEnd());
Then find the desired data with something like this:
doc.LastChild.FirstChild.FirstChild.LastChild.InnerText;
Read xml structure
You can write some additional lines of code to introduce namespaces and other xml contents to find/map the available data, by taking a look at extracting-data-from-a-complex-xml-with-linq

Categories