We've got a web service that returns a very simple XML.
<?xml version="1.0"?>
<t:RequestResult xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://our.website.com/ns/" xmlns:t="http://our.website.com/ns/">
<t:Result>No candy for you today.</t:Result>
<t:Success>false</t:Success>
</t:RequestResult>
The caller gets this XML no problem, using XMLHTTP. But XPath queries don't work agains this XML because of "Reference to undeclared namespace prefix: 't'"
Why so? I'd say the 't' prefix is somewhat declared. Is this document invalid in any way?
In case you wonder why we had to use XmlNamespaceDeclarations to add namespace prefixes in the first place, that is because otherwise the resulting document cannod be queried upon becase it has a target namespace but hasn't got a prefix for it, so XPath ignores node names because they don't belong to requested (empty) namespace, and we don't wan't to use constructions like "//*[namespace-uri()='http://our.website.com/ns' and local-name()='RequestResult']"
.
Surprisingly enough (for me), this is the default behaviour for XPath. By default, namespace prefixes are not allowed in an XPath query.
To resolve this, one must register desired prefixes with
SelectionNamespaces
property of the DOMObject.After that one can use expressions qualified with
t:
in XPath queries. This also resolvers the original problem that forced us to use XmlNamespaceDeclarations in the first place.You've already answered the question, but it's worth understanding why this is.
The namespace that an element is in cannot be determined solely by the namespace prefix. To find what namespace an element named
t:foo
is in, you have to search up the ancestor-or-self axis until you find the nearest node that defines the namespace fort:
. For instance:In that document, every element whose name is
one
is in thens-one
namespace, and every element whose name istwo
is in thens-two
namespace. You can tell that the deepest element in that document is inns-two
not becauset:
intrinsically meansns-two
, but because if you search up the ancestor-or-self axis, the first element that you hit with anxmlns:t
attribute on it - its parent - tells you the namespace.Given that, which nodes should the XPath expression
//t:*
match? It's impossible to say, because what namespacet:
is mapped to changes throughout the document.Also, namespace prefixes are temporary, but namespaces are permanent. If you know that
one
is inns-one
, you really, truly don't care whether its prefix ist:
orx:
or if it has no prefix at all and just anxmlns
attribute.When you're querying an XML document with XPath, you need a way of specifying what namespace a given element is in. And that's what
SelectionNamespaces
in a DOMDocument, or a namespace manager in C#, or whatever are for: they tell you what namespaces the prefixes in your XPath queries represent. So if I've set the prefixa:
tons-one
, the XPath//a:one
will find me all of the elements namedone
in thens-one
namespace, irrespective of what the actual prefix they're using in the document I'm searching is.This is a little counterintuitive when you're first learning it, but really, it's the only way that makes any sense at all.