Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do XML namespaces need to be declared in the root element to be matchable by an XPath query?

I can't figure out whether XPath itself is to blame or whether it's the particular XPath implementations that make this so difficult. The SO question – How to change an an XML element in a namespace with MSDeploy Parameters.xml file? – was my inspiration.

What Doesn't Work

Here's the basic example that doesn't work.

XML:

<spring>
    <objects xmlns="http://www.springframework.net">
        <object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <property name="DefaultCulture" value="en" />
        </object>
    </objects>
</spring>

XPath:

//spring/objects/object[@id='CultureResolver']/@type

The XPath query returns nothing instead of:

Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web

What I Expect to Work

I would, perhaps naively, expect the following to work.

Modified XML:

<spring>
    <spring:objects xmlns:spring="http://www.springframework.net">
        <spring:object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <spring:property name="DefaultCulture" value="en" />
        </spring:object>
    </spring:objects>
</spring>

Modified XPath query:

//spring/spring:objects/spring:object[@id='CultureResolver']/@type

This query raises an error in the online tester I used:

ERROR - Failed to evaluate XPath expression: org.apache.xpath.domapi.XPathStylesheetDOM3Exception: Prefix must resolve to a namespace: spring

What Does Work

Modified XML:

<spring xmlns="" xmlns:spring="http://www.springframework.net">
    <spring:objects>
        <spring:object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <spring:property name="DefaultCulture" value="en" />
        </spring:object>
    </spring:objects>
</spring>

Modified XPath query (same as under What I Expect to Work):

//spring/spring:objects/spring:object[@id='CultureResolver']/@type

To add to the confusion somewhat, I discovered that the following XPath query works for the original example XML (in the online tester XPath engine):

//spring/*[local-name() = 'objects' and namespace-uri() = 'http://www.springframework.net']/*[@id='CultureResolver' and local-name() = 'object' and namespace-uri() = 'http://www.springframework.net']/@type

Why?

Is this confusing because of the interplay between namespaces and prefixes? It seems like declaring a namespace without a prefix not only includes the relevant element in that namespace but also includes all of its children too, hence describing it as a "default namespace" (like in this answer to a related question). Whereas declaring a namespace with a prefix doesn't even include the relevant element in that namespace!

Is there some reason why namespaces need to be included in the root element of the XML document, independent of particular XPath implementations?

My XPath Engines

The problem I was trying to solve involved whatever XPath engine that Microsoft Web Deploy (MSDeploy) uses.

I was also using this online XPath tester.

like image 877
Kenny Evitt Avatar asked Jan 23 '15 20:01

Kenny Evitt


People also ask

What is the correct way to declaring an XML namespace?

XML Namespaces - The xmlns Attribute When using prefixes in XML, a namespace for the prefix must be defined. The namespace can be defined by an xmlns attribute in the start tag of an element. The namespace declaration has the following syntax. xmlns:prefix="URI".

What is namespace in XPath?

Namespaces provide a way of qualifying names that are used for elements, attributes, data types, and functions in XPath. Names in XPath are called QNames (qualified names) and conform to the syntax that is defined in the W3C Recommendation Namespaces in XML .

How do I add namespace prefix to XML element?

To create an attribute that declares a namespace with a prefix, you create an attribute where the namespace of the name of the attribute is Xmlns, and the name of the attribute is the namespace prefix. The value of the attribute is the URI of the namespace.

Which keyword is used to declare a namespace in XML?

In the attribute xmlns:pfx, xmlns is like a reserved word, which is used only to declare a namespace. In other words, xmlns is used for binding namespaces, and is not itself bound to any namespace. Therefore, the above example is read as binding the prefix "pfx" with the namespace "http://www.foo.com."


2 Answers

An interesting and well-asked question! As far as I can see, the difficulty lies with the way your XPath engine handles namespace declarations found in the input document.

The short answer

No, this behaviour has nothing to do with XPath in general or with the XPath specification. It is due to individual implementations.


What the specifications say

As far as the XML and XPath specifications are concerned, namespaces can be declared on any element, and there is nothing special about the outermost (or "root") element. Namespace declarations on the root element are just like any other declaration.

Of course there are still rules. For instance, a prefix must be associated with a namespace URI on the element in whose QName it is used, or on an ancestor of that element (or of that attribute). So, the following is not well-formed XML:

<prefix:root>
    <child xmlns:prefix="www.example.com"/>
</prefix:root>

And the second important rule: A default namespace can only apply to the element on which it is declared and all descendant elements. In the following document, the root element is in no namespace at all:

<root>
   <child xmlns="www.example.com">
      <grandchild/>
   </child>
</root>

The specifications I am talking about are the XML, XML Namespaces and Xpath specifications.

What happens in your implementation of XPath

Now, if an XPath expression is evaluated against an XML document, all namespace declarations present in this input document must also explicitly be made available (declared, or "registered") to the XPath engine.

Some implementations of XPath simplify this by simply redeclaring all namespace declarations that are in scope for an element or attribute of an XML document that serves as input for an Xpath engine (also see this).

In your case, it seems only declarations made on the outermost element are considered. That's why your last XML document:

<spring xmlns="" xmlns:spring="http://www.springframework.net">
    <spring:objects>
        <spring:object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <spring:property name="DefaultCulture" value="en" />
        </spring:object>
    </spring:objects>
</spring>

works - because the namespace declaration is made on the root element and you execute the XPath expression from the root element. You could omit the undeclaration of a default namespace though, because it doesn't have any effect.


Finally, to answer your last question:

Is there some reason why namespaces need to be included in the root element of the XML document, independent of particular XPath implementations?

No, there is no reason namespace declarations should be on the root element, except

  • that they are somewhat easier to find when declared on the root element, in my opinion (highly subjective)
  • if you'd like to declare a default namespace for the whole document. Declaring it on the root element is the only way to have it also apply to the root element
  • if the root element itself has a qualified name, i.e. is prefixed. Then, you must declare this prefix and namespace URI on the root element.

If your implementation of XPath automatically redeclares namespace declarations that are in scope, you can of course take advantage of it, but it will also be confusing sometimes, as you have noticed.

like image 130
Mathias Müller Avatar answered Oct 24 '22 15:10

Mathias Müller


No, the namespace definition for the document and XPath are separate. Some implementations automatically register the space definitions of the current context by default. I consider this a bug, because it makes the XPath ambiguous.

Let's start with a simple example:

<foo:element xmlns:foo="urn:foo"/>

That defines an alias/prefix foo for the namespace urn:foo. The XML parser resolves that and recognizes that the node element belongs to the namespace urn:foo. For debugging reason the node name could be written as {urn:foo}element.

If you change the prefix, or even remove it this is always resolved the same way. Consider the following examples:

<foo:element xmlns:foo="urn:foo"/>
<bar:element xmlns:bar="urn:foo"/>
<element xmlns="urn:foo"/>

The prefix/alias is only valid for the node and its descendants. Any descendant can have its own definition that possibly can overwrite the one of its ancestor.

For XPath you define you own aliases. You write a namespace resolver or register them on the XPath engine. That really depends on the implementation.

Here is a small PHP example:

$dom = new DOMDocument();
$dom->loadXml('<foo:element xmlns:foo="urn:foo"/>');

$xpath = new DOMXPath($dom);
$xpath->registerNamespace('alias', 'urn:foo');

var_dump($xpath->evaluate('name(/alias:element)'));

Output:

string(11) "foo:element"

You can see that the namespace definitions for the XPath are separate and independent from the prefixes defined in the XML document.

In Javascript XPath is used with the Document.evaluate(). The third argument is the namespace resolver.

var resolver = {
  namespaces : {
   'alias' : 'urn:foo'
  },
  lookupNamespaceURI : function(prefix) {
    if (prefix == '') {
      return null;
    }
    return this.namespaces[prefix] || null;
  }
};

console.log(
    document.evaluate(
       'name(/alias:element)'
    ),
    document,
    resolver,
    XPathResult.ANY_TYPE,
    null
  ).stringValue
);

Back to you question. You have to find out how you register/define the aliases/prefixes for you namespaces. After that you can use them in you XPath expressions. If you define the alias spring for the namespace http://www.springframework.net" the following XPath expression should work:

//spring/spring:objects/spring:object[@id='CultureResolver']/@type

like image 40
ThW Avatar answered Oct 24 '22 16:10

ThW