What is a good way to find a specific value in an XML document using C#?
Asked Answered
T

6

6

I'm calling a WebService exposed by Oracle that accepts an input of an ItemID and returns to me the corresponding Item Number. I want to grab the Item Number that has been returned out of the XML contained in the response.

The XML looks like this:

<env:Envelope
  xmlns: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:ns0="http://dev1/MyWebService1.wsdl">
 <env:Header>
  <wsse:Security
    xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
    xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
    xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
    env:mustUnderstand="1"/>
 </env:Header>
 <env:Body>
  <ns0:getItemNbrByItemIdResponseElement>
   <ns0:result>1010603</ns0:result>
  </ns0:getItemNbrByItemIdResponseElement>
 </env:Body>
</env:Envelope>

I'm interested in grabbing only the <ns0:result>1010603</ns0:result> particularly only the 1010603.

I haven't done a lot of work parsing XML using C# and I'm playing around with a few different methods so far. What is the recommended way to do this?

I'm on VS2008 (so XPath is available etc.)

Terr answered 17/12, 2008 at 14:42 Comment(0)
P
15

I'd personally use LINQ to XML, because I find that easier to deal with than XPath, particularly when namespaces are involved. You'd do something like:

XNamespace ns0 = "http://dev1/MyWebService1.wsdl";

String result = doc.Descendants(ns0 + "result").First().Value;

Note that doc here is expected to be an XDocument, not an XmlDocument. (My guess is that this is why it wasn't showing up for you.)

Preceptor answered 17/12, 2008 at 14:46 Comment(5)
Agreed; without LINQ you'd need to use namespace-managers and all sorts - this is much simpler.Conics
+1, namespaces "need" to be easier to work with which XLINQ does nicelyAlbany
When trying to use this VS is complaining about the .Descendants method. Despite having using System.Linq and System.Xml.Linq present. Any thoughts/what I'm missing? I certainly like how concise this solution is.Terr
@Jon - Yep that fixed up the issue there. Now just figuring out why it's stating that the sequence contains no elements. :) Thanks again for the fix!Terr
I needed to use an XMLNodeReader in order to properly populate the XDocument, that's all. Now it works like a champ. Thanks again Jon.Terr
C
5

fwiw you can cheat the namespace issue with an xpath like this: //*[local-name()='result']

Conchoidal answered 17/12, 2008 at 14:54 Comment(1)
this is much more elegant than the chosen Answer.Typeface
V
3

If you don't want to go for Linq you could use XPathDocument to retrieve the value:

XPathDocument xmldoc = new XPathDocument(@"C:\tmp\sample.xml");
XPathNavigator nav = xmldoc.CreateNavigator();

XmlNamespaceManager nsMgr = new XmlNamespaceManager(nav.NameTable);
nsMgr.AddNamespace("ns0", "http://dev1/MyWebService1.wsdl");

XPathNavigator result = nav.SelectSingleNode("//ns0:result", nsMgr);
System.Diagnostics.Debug.WriteLine(result.Value);

XPathDocument has a lower memory footprint and is most likely faster in your scenario than XmlDocument. XmlDocument builds up a complete object model of your XML document in memory whereas XPathDocument does not do that.

Voorhees answered 17/12, 2008 at 14:52 Comment(0)
P
2

Off the top of my head, the following should work:

XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;

XmlNamespaceManager mgr = GetNamespace(doc);
doc.LoadXml(xmltext);

XmlNode nd = doc.DocumentElement.SelectSingleNode("//ns0:result", mgr);

The namespace code looks like this:

private XmlNamespaceManager GetNamespace(XmlDocument document)
{
    XmlNamespaceManager mgr = new XmlNamespaceManager(document.NameTable);
    mgr.AddNamespace("ns0", "http://dev1/MyWebService1.wsdl");
    return mgr;
}

You need to use the namespace manager because the XML document has namespaces associated with it, and XPath uses this in query resolution.

Perambulate answered 17/12, 2008 at 14:51 Comment(3)
That's close... Just tried it out and it's giving me an error: Namespace Manager or XsltContext needed. This query has a prefix, variable, or user-defined function.Terr
My bad - the SelectSingleNode should have a reference to the namespace manager with it. I'll update it.Perambulate
Thanks for the fix! :) I could just step-up my game and learn a few things and then you wouldn't have to solve my problems for me! ;)Terr
T
1

To solve this, I used Jon Skeet's answer. Here's the code that I had to implement to make this work (for anyone else's future benefit).

XmlDocument xmlDoc = new XmlDocument();

XNamespace ns0 = "http://dev1/MyWebService1.wsdl";

xmlDoc.Load(request.GetResponse().GetResponseStream());

XDocument xDoc = XDocument.Load(new XmlNodeReader(xmlDoc));                          

String result = xDoc.Descendants(ns0 + "result").First().Value;

This of course assumes I'm getting my response back from an HttpWebRequest named request.

Terr answered 17/12, 2008 at 16:42 Comment(0)
H
0

There are very good and complete answers to this question.

I'd add just out of curiosity, that an extremely simple XPath expression does the job in this particular case:

    normalize-space(/)

This is easily done in C# using something like the two lines below:

        XPathNavigator navigator = document.CreateNavigator();

        string res = (string)navigator.Evaluate("normalize-space(/)");

With the good optimization of the .NET XPath engine, its evaluation may even be efficient.

Hercegovina answered 17/12, 2008 at 15:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.