Trickledown Namespaces?
June 30, 2004
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:
andfb:
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 prefixedfa:
and the other prefixedfb:
.
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:
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:
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 |
|
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 title
s). 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:
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.