I’m trying to parse a valid XML document in c# .NET using xPathNavigator. The start of the XML document looks like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns1:myResponse xmlns:ns1="ExampleNS" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<myReturn href="#2" />
As you can see, there’s some namespaces defined on the root element, but then ns1 is defined later on. When I try and evaluate an xPath query /soapenv:Envelope/soapenv:Body/ns1:myResponse/myReturn I get an XPathException:
Namespace prefix 'ns1' is not defined.
What I’m doing is getting the XML document as a string, loading it into an XmlDocument object. Then I’m creating a navigator, moving to the root element and calling GetNamespacesInScope(XmlNamespaceScope.All). I loop through this collection, which contains the namespaces for xml, soapenv, xsd and xsi as defined on the root, adding them to the namespacemanager. Then I create an xPathNavigator and call Evaluate, and get the exception.
Code is this:
string response = GetXMLString();
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(response);
var xmlDocumentNavigator = xmlDocument.CreateNavigator();
xmlDocumentNavigator.MoveToFollowing(XPathNodeType.Element);
var xnm = new XmlNamespaceManager(xmlDocument.NameTable);
var namespacesInScope = xmlDocumentNavigator.GetNamespacesInScope(XmlNamespaceScope.All);
if (namespacesInScope != null)
{
foreach (var prefix in namespacesInScope.Keys)
{
xnm.AddNamespace(prefix, namespacesInScope[prefix]);
}
}
var xPathDocument = new XPathDocument(new StringReader(response));
var xPathNavigator = xPathDocument.CreateNavigator();
xPathNavigator.Evaluate("/soapenv:Envelope/soapenv:Body/ns1:myResponse/myReturn", xnm);
Why doesn’t the GetNamespacesInScope method pick up on ns1?
The problem is that
GetNamespacesInScopeonly returns the namespaces in the scope of the navigator’s current node (as I guess the method suggests :).One hack of getting all the Namespaces is to walk all the elements, get the namespaces, and then reassemble just the unique ones.
Dictionary merge code courtesy of JonSkeet
Edit
As per Paciv’s comment, if you really don’t know what the namespaces are in an xml document in advance, an alternative is to use namespace agnostic Xpath, and avoid the issues around sniffing out namespaces (and eliminating the need for namespace managers entirely).
In your example, this would be: