
Trickledown Namespaces?
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
titleelement, 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 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:

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.
- Question on namespaces
2004-08-09 16:35:50 cord_thomas - Question on namespaces
2005-01-06 12:55:47 mrylander@gmail.com - use (or not) of the namespace axis
2004-07-01 03:49:18 David Carlisle - couple of typos
2004-07-01 01:41:19 Brian Ewins - couple of typos
2004-07-02 07:50:55 Brian Ewins