1
votes

I've been using this absolute XPath extension to get XPath strings of nodes inside XDocument's. The node can then later be queried by XPath using xdoc.XPathSelectElement(xpath).

However, this fails for XML documents that use namespaces, like the following:

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://service.svsxml.svs.com" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Header>
        <wsse:Security soapenv:mustUnderstand="1">
            <wsse:UsernameToken>
                <wsse:Username>Pizza</wsse:Username>
                <wsse:Password>Pie</wsse:Password>
            </wsse:UsernameToken>
        </wsse:Security>
    </soapenv:Header>
</soapenv:Envelope>

Calling GetAbsoluteXPath on the Username node gives:

/Envelope/Header[1]/Security[1]/UsernameToken[1]/Username[1]

However, querying the XDocument with XPathSelectElement and the above XPath yields null, because no namespace is specified.

According to answers I looked up, the solution is to add a NamespaceManager and manually edit the path. However, my paths are dynamically-generated (I don't know the namespace or structure of the XML document in advance), so hand-tweaking the strings is not an option.

My question is:

  • Is there a way to query using an XPath in a way which totally ignores namespaces?

An answer to a similarly-named question was to query by local name instead of XPath. However, this fails when multiple nodes have the name LocalName (which often occurs in the XML I'm parsing), since searching by name only throws away the specificity of the XPath entirely.

For clarification: I don't know what the XML document looks like in advance, and the XPath and namespaces are determined at run-time, not compile-time. So manually adding strings that only work for this particular example won't work in general.

2

2 Answers

0
votes

The code below is xml linq and will work with one node. With multiple nodes you have to add addional search info to get the exact node(s) you are looking for.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            XDocument doc = XDocument.Load(FILENAME);

            XElement envelope = (XElement)doc.FirstNode;
            XNamespace wsse =  envelope.GetNamespaceOfPrefix("wsse");
            string username = envelope.Descendants(wsse + "Username").FirstOrDefault().Value;

        }
    }
}
​
0
votes

I had the same requirement and found a solution like this:

XDocument doc = ...
var nsmngr = new XmlNamespaceManager(doc.CreateReader().NameTable);

foreach (var attribute in doc.Descendants().Attributes().Where(a => a.IsNamespaceDeclaration))
{
    nsmngr.AddNamespace(attribute.Name.LocalName, attribute.Value);
}

// then use the namespacemanager when querying the document
var someXPath = "/Envelope/Header[1]/Security[1]/UsernameToken[1]/Username[1]";
doc.XPathEvaluate(someXPath, nsmngr)