Menu

Trickledown Namespaces?

June 30, 2004

John E. Simpson

Q: Why aren't my namespaces being inherited?

Some time ago I read something about namespace declarations which said, "The namespace declaration given for the current element is also valid for all its children and descendants."

Please see my XML and XSLT below. According to the above, can I treat (in XSLT) a title element as fa:title and fb:title? When I try to do so, viewing the XML in Internet Explorer (IE), I can't see the expected result.

My XML (test.xml):

<?xml-stylesheet type="text/xsl"

href="main.xsl"?>

<root>

  <head xmlns:fa="http://www.test.com/element-a">

    <title>I am in file 'fa'</title>

  </head>

  <head xmlns:fb="http://www.test.com/element-b">

    <title>I am in file 'fb'</title>

  </head>

</root>

My XSLT stylesheet (main.xsl):

<xsl:stylesheet version="1.0"

  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

  xmlns="http://www.test.com/xml"

  xmlns:fa="http://www.test.com/element-a"

  xmlns:fb="http://www.test.com/element-b">

  <xsl:output method="html"/>

  <xsl:template match="/">

    <html>

      <body>

        <p style="color:red">FA: 

          <xsl:value-of

select="string(//*/*/fa:title)"/>

        </p>

        <p style="color:blue">FB: 

          <xsl:value-of

select="string(//*/*/fb:title)"/>

        </p>

      </body>

    </html>

  </xsl:template>

</xsl:stylesheet>

Please advise, how do I achieve this? Is my understanding wrong?

A: Yes, your understanding is wrong... but understandably so. First, let's deal with that quote you remember. According to the Namespaces in XML 1.1 Recommendation (emphasis added):

The scope of a namespace declaration declaring a prefix extends from the beginning of the start-tag in which it appears to the end of the corresponding end-tag... Such a namespace declaration applies to all element and attribute names within its scope whose prefix matches that specified in the declaration.

That is, the declaration doesn't automatically "trickle down" to apply to all subordinate element and attribute names -- just to the ones which share the prefix named in the declaration.

In your case, your stylesheet does these two things related to namespaces:

  • includes appropriate namespace declarations for both the fa: and fb: prefixes -- and also for the default namespace (that is, covering those cases in which element/attribute names have no prefix); and
  • seems to distinguish between two forms of the title element, one prefixed fa: and the other prefixed fb:.

About this second bulleted item: You've set up two xsl:value-of elements, whose select attributes point several levels down in the document tree to an fa:title element in one case, and an fb:title element in the other. I'll come back to discuss those location paths in a moment. But, for now, consider (in light of the above quote from the Namespaces Recommendation) what you're asking the XSLT processor to search for: elements in the source tree (test.xml) literally named fa:title and fb:title -- including the prefixes. But test.xml has no such elements; the namespace declarations which it contains apply only to element names prefixed fa: and fb:, respectively. Even though your two title elements are subordinate to head elements declaring those namespaces, the best you can say is that the namespace declarations are potentially in effect but actually unused by each title element.

Thus, in IE, your document looks something like the partial screen capture in Figure 1:

Figure 1: Prior to namespace fixup

The two literal text strings show up correctly, but the xsl:value-of elements come up empty-handed. As you say, this isn't what you want.

There are a couple of ways to fix this. Which you use depends on what you're trying to achieve in a larger sense, how much control you have over the source document and the corresponding stylesheet, and, perhaps, on your tastes.

Probably the most straightforward way to fix it is to add the appropriate prefixes to the title elements in test.xml; this approach has the advantage of requiring no changes at all to your stylesheet. Those portions of test.xml would now look like the following:

  <head

xmlns:fa="http://www.test.com/element-a">

    <fa:title>I am in file

'fa'</fa:title>

  </head>

  <head xmlns:fb="http://www.test.com/element-b">

    <fb:title>I am in file

'fb'</fb:title>

  </head>

Alternatively, you might think of tinkering with the stylesheet. Before dealing with the namespace-related question, though, I'd like to suggest some ways to clean up your stylesheet a little.

For starters, you don't need to use the string() function to return the string-value of your location path. Because the last step in each location path is the name of an element, the xsl:value-of elements' select attributes will automatically return the string-value of the selection. Thus, factoring in that you don't need the (useless) namespace prefixes, the central portion of your stylesheet will now look like this:

<p style="color:red">FA:

  <xsl:value-of

select="//*/*/title"/>

</p>

<p style="color:blue">FB:

  <xsl:value-of

select="//*/*/title"/>

</p>

Secondly, the location paths themselves are more complicated than they need to be. All those intervening slashes and asterisks can be replaced by a single reference to the descendant:: axis, in this manner:

<p style="color:red">FA:

  <xsl:value-of

select="descendant::title"/>

</p>

<p style="color:blue">FB:

  <xsl:value-of

select="descendant::title"/>

</p>

(The context node at the point of the xsl:value-of elements, established by the xsl:template element's match element, is the document root. Therefore, in order to get all the title (or any other) elements in the document, you don't need to reset the context to the document root -- which is what your opening slash does -- and then laboriously walk the tree down to a specific level.)

A quasi-shortcut to using the descendant:: axis like this would be to replace it with a single double slash:

<p style="color:red">FA:

  <xsl:value-of select="//title"/>

</p>

<p style="color:blue">FB:

  <xsl:value-of select="//title"/>

</p>

(Note: The double slash actually is a shortcut for the descendant-or-self:: axis, which can be a performance drag when processing large XML source documents but can be useful for small "play" documents like yours. As you'll see in a moment, though, using the // can produce very different results even for small documents.)

Whichever approach you use to cleaning up the XPath location path, alas, you end up with a still not-right result, as depicted in Figure 2:

Figure 2: Following XPath simplification - still not there!

At this point, the problem is that both XPath location paths in both xsl:value-of elements are identical. They both locate the node-set consisting of all title elements in the source tree -- and the string-value of any such node-set is always the string-value of the first node in the set (the first title element, in this case).

Now what?

Also in XML Q&A

From English to Dutch?

From XML to SMIL

From One String to Many

Getting in Touch with XML Contacts

Little Back Corners

Unfortunately, assuming you really do want to use namespaces to locate the two different title elements -- well, it just isn't possible. The reason: again, while you've declared the namespaces, they aren't actually in use in the source tree (by any element, not just the two titles). So there's nothing namespace-related with which to distinguish the two title elements.

You can still distinguish them, of course, using a somewhat clunky solution such as selecting them based on their positions. In order to do so, you'll have to drop the // shortcut and revert to using the descendant:: axis, like this:

<p style="color:red">FA:

  <xsl:value-of

select="descendant::title[1]"/>

</p>

<p style="color:blue">FB:

  <xsl:value-of

select="descendant::title[2]"/>

</p>

(If you used the // shortcut here, you'd be telling the XSLT processor to locate, respectively, (a) any title elements which are the first child of their parents and (b) any ones which are the second child of their parents. There is no match for condition (b), though. As a result, all you'd get would be the "FA: I am in file 'a'" string for the FA paragraph in the result tree, and plain old "FB:" for the FB paragraph.)

As desired, your output viewed in IE now looks like Figure 3:

Figure 3: Looks right... but no namespaces used!

This may feel like cheating, though, because you still haven't solved the namespace question.

The bottom line: in order to process namespaces in an XSLT stylesheet, you must not only declare the URIs associated with their prefixes (like your xmlns:fb="http://www.test.com/element-b" declaration). You must also actually declare and use the prefixes in the source document.

As I suggested previously, you can tack the fa: and fb: prefixes onto the names of the title elements in test.xml, leaving your stylesheet in its original form. Alternatively, you could apply those prefixes to the title elements' parents -- the two head elements:

<fa:head

xmlns:fa="http://www.test.com/element-a">

  <title>I am in file 'fa'</title>

</fa:head>

<fb:head

xmlns:fb="http://www.test.com/element-b">

  <title>I am in file 'fb'</title>

</fb:head>

Then your stylesheet could be constructed like this, referencing each [prefix]:head element and the corresponding (unprefixed) title:

<p style="color:red">FA:

  <xsl:value-of

select="descendant::fa:head/title"/>

</p>

<p style="color:blue">FB:

  <xsl:value-of

select="descendant::fb:head/title"/>

</p>

Results in this case are identical to those shown in Figure 3, above.

By the way, you may wonder about the possibility of somehow using the default namespace, associated in your stylesheet with the URI http://www.test.com/xml. This won't work under XSLT 1.0. By a strange fluke in the spec, you can't match on the default namespace without using a prefix at all; but of course as soon as you use a prefix, an element or attribute name is no longer in the default namespace. This anomaly is expected to be addressed in the XSLT 2.0 standard which is, as of this writing, still in "last call" status.