Creating namespace nodes in XSLT 1.0
Introduction and motivating example
This entry discusses the better way to add namespace nodes to an element in XSLT 1.0. The goal is to try to have the right namespace bindings in context for content using them, as attribute values storing XPath expressions. There is no way to guarantee it, but the code developed below is reasonable enough to work in most cases.
In addition to showing how to generate a namespace node
in the result tree, this entry discusses specific problems
and tricks when employed in a meta-stylesheet,
and how to use the XSLT 2.0 instruction
xsl:namespace
when available.
The motivating case is the base meta-stylesheet (the skeleton) currently developped for ISO Schematron. Schematron Love In, the mailing list for Schematron, was very active these last days, after Rick posted a first draft of the skeleton for ISO Schematron. One of the most discussed points was How to have the correct namespace bindings in the generated stylesheet? But here is an example first, with a little explanation, before going further:
<schema xmlns="http://purl.oclc.org/dsdl/schematron"> <ns prefix="fg" uri="http://www.fgeorges.org/"/> <title>Sample schema</title> <pattern name="Test schema"> <rule context="fg:elem"> <assert test="@id">'fg:elem' must have an @id.</assert> </rule> </pattern> </schema>
Namespaces used in the attribute contents are declared by
the element ns
. For each node in the tested
document matching the rule/@context
match
pattern, the XPath expression in assert/@test
is evaluated and must be true. If not, an error is
reported. The more natural way to implement this language
is generating an XSLT stylesheet, and the more natural way
to generate it is using an XSLT meta-stylesheet.
Generating a namespace binding in XSLT 1.0
There is no way to guarantee, in an XSLT 1.0 stylesheet, that the output tree will contain a namespace binding for a specified prefix. The serializer is permitted to change any prefix in the output tree, providing that the prefix is still bound to the right URI. This is convenient to not worry about to bindings to the same prefix. The serializer change one prefix, and all is fine.
All? Really? All is fine regarding names of elements
and attributes in the output tree (this is what nemspaces
are about), but not regarding the attribute values using
them, for example. If one attribute value is
"fg:elem"
as above, and the serializer change
the prefix to ns1
(changing also all element
and attribute names in this namespace), the attribute value
cannot be interpreted as an XPath expression anymore. And
if a nemspace binding is not used (if no element or
attribute is n this namespace), the serializer can drop the
namespace binding.
The usual trick to add a namespace node to an element is
to use one of the xxx:node-set()
functions like
the following:
<!-- In the stylesheet. --> <elem> <xsl:variable name="dummy"> <ns:elem xmlns:ns="http://uri"/> </xsl:variable> <xsl:copy-of select="exsl:node-set($dummy)/*/namespace::*"/> </elem> <!-- In the result tree (in most cases). --> <elem xmlns:ns="http://uri"/>
If a xxx:node-set()
extension function is
not available, the only way to add a namespace binding in
the result tree is to add an element or an attribute in the
namespace to the result tree. In a meta-stylesheet, we can
add an attribute to an XSLT instruction, if this attribute
is not in the null namespace or in the XSLT namespace (say
we don't use extensions in the generated stylesheet):
<!-- In the stylesheet. --> <elem> <xsl:attribute name="ns:dummy-for-xmlns" xmlns:ns="http://uri"/> </elem> <!-- In the result tree (in most cases). --> <elem xmlns:ns="http://uri" ns:dummy-for-xmlns=""/>
So we can write a named template to handle all of this:
<xsl:template name="make-namespace-node"> <xsl:param name="prefix"/> <xsl:param name="uri"/> <xsl:choose> <!-- All supported node-set() functions. --> <xsl:when test="function-available('exsl:node-set')"> <xsl:variable name="dummy"> <xsl:element name="{ $prefix }:e" namespace="{ $uri }"/> </xsl:variable> <xsl:copy-of select="exsl:node-set($dummy)/*/namespace::*"/> </xsl:when> <xsl:when test="function-available('msxsl:node-set')"> <xsl:variable name="dummy"> <xsl:element name="{ $prefix }:e" namespace="{ $uri }"/> </xsl:variable> <xsl:copy-of select="msxsl:node-set($dummy)/*/namespace::*"/> </xsl:when> <!-- If no node-set() available. --> <xsl:otherwise> <xsl:attribute name="{ concat($prefix, ':dummy-for-xmlns') }" namespace="{ $uri }"/> </xsl:otherwise> </xsl:choose> </xsl:template>
Back to Schematron
The skeleton for ISO Schematron is a meta-stylesheet.
That means the result tree of the transformation is itself
an XSLT stylesheet. The ns
element in
Schematron appear as childs of the root element. The goal
is to generate namespace declarations on the generated
element xsl:stylesheet
. Are there some
specificities we have to take care to?
There is one for sure. In the case a
xxx:node-set()
function is not available, we
are creating a dummy attribute on the
xsl:stylesheet
. But this cannot be an
attribute in the XSLT namespace. If it was, the XSLT
processor would say it doesn't know this attribute when
running the generated stylesheet, and report an error.
An error that can occurs is trying to add a namespace
node with the same name of a namespace node already
existsing on the element, but bound to another URI. But we
cannot know all prefixes used in the generated stylesheet.
The only thing we can do, I think, is asking schema authors
to not bind usual prefixes to other URIs they are usually
bound to. For example, if you really want to use the
xsl
prefix for the XSLT namespace, no problem.
If you want to use it for your own namespace URI, accept all
consequences.
But I'm affraid no check can be done formally. So here is what the template now looks for:
<xsl:template name="make-namespace-node"> <xsl:param name="prefix"/> <xsl:param name="uri"/> <xsl:choose> <!-- EXSLT's node-set(). --> <xsl:when test="function-available('exsl:node-set')"> <xsl:variable name="dummy"> <xsl:element name="{ $prefix }:e" namespace="{ $uri }"/> </xsl:variable> <xsl:copy-of select="exsl:node-set($dummy)/*/namespace::*"/> </xsl:when> <!-- MSXSL's node-set(). --> <xsl:when test="function-available('msxsl:node-set')"> <xsl:variable name="dummy"> <xsl:element name="{ $prefix }:e" namespace="{ $uri }"/> </xsl:variable> <xsl:copy-of select="msxsl:node-set($dummy)/*/namespace::*"/> </xsl:when> <!-- If no node-set(), cannot handle XSLT namespace. --> <xsl:when test="@uri = 'http://www.w3.org/1999/XSL/Transform'"> <xsl:message terminate="yes"> <xsl:text>Using the XSLT namespace in Schematron </xsl:text> <xsl:text>rules is not supported in this processor: </xsl:text> <xsl:value-of select="system-property('xsl:vendor')"/> </xsl:message> </xsl:when> <!-- If no node-set(), dummy attribute trick. --> <xsl:otherwise> <xsl:attribute name="{ concat($prefix, ':dummy-for-xmlns') }" namespace="{ $uri }"/> </xsl:otherwise> </xsl:choose> </xsl:template>
Is it possible to use xsl:namespace
if
available?
This entry discusses only generating namespace nodes in
an XSLT 1.0 stylesheet, because in XSLT 2.0 tere is the
xsl:namespace
that does this job. But an XSLT
1.0 stylesheet can be run by an XSLT 2.0 processor. And in
this case it can uses XSLT 2.0 instructions. How to do that
in a reliabe way?
If we add xsl:namespace
in the XSLT 1.0
stylesheet, XSLT 1.0 processors will generate a compilation
error, telling you that you probably made a mistake because
you use an unknown XSLT instruction. Don't try to lie to
your processor, and tell it the truth: that piese of code is
actually XSLT 2.0 code. How? Simply by creating an
external stylesheet module with @version="2.0"
.
Obviously, XSLT 2.0 processors will not have problem
here. And XSLT 1.0 processors? They will run (for this
module) in forwards-compatible mode. So they will
not generate compilation error for XSLT instructions they
don't know, but you still have to prevent them to actually
instantiate these instructions. It is easy with
element-available()
:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:template name="make-namespace-node"> <xsl:param name="prefix"/> <xsl:param name="uri"/> <xsl:choose> <!-- In XSLT 2.0. --> <xsl:when test="element-available('xsl:namespace')"> <xsl:namespace name="{ $prefix }" select="$uri"/> </xsl:when> <!-- EXSLT's node-set(). --> <xsl:when test="function-available('exsl:node-set')" use-when="false()"> <xsl:variable name="dummy"> <xsl:element name="{ $prefix }:e" namespace="{ $uri }"/> </xsl:variable> <xsl:copy-of select="exsl:node-set($dummy)/*/namespace::*"/> </xsl:when> <!-- MSXSL's node-set(). --> <xsl:when test="function-available('msxsl:node-set')" use-when="false()"> <xsl:variable name="dummy"> <xsl:element name="{ $prefix }:e" namespace="{ $uri }"/> </xsl:variable> <xsl:copy-of select="msxsl:node-set($dummy)/*/namespace::*"/> </xsl:when> <!-- If no node-set(), cannot handle XSLT namespace. --> <xsl:when test="@uri = 'http://www.w3.org/1999/XSL/Transform'" use-when="false()"> <xsl:message terminate="yes"> <xsl:text>Using the XSLT namespace in Schematron </xsl:text> <xsl:text>rules is not supported in this processor: </xsl:text> <xsl:value-of select="system-property('xsl:vendor')"/> </xsl:message> </xsl:when> <!-- If no node-set(), dummy attribute trick. --> <xsl:otherwise use-when="false()"> <xsl:attribute name="{ concat($prefix, ':dummy-for-xmlns') }" namespace="{ $uri }"/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet>
Note the @version="2.0"
and the use of
@use-when
. With an XSLT 2.0 processor, that
will cause the other branches to be even not compiled. With
an XSLT 1.0 processor, that will have no effect, because it
doesn't know this attribute.
Labels: xslt
1 Comments:
Inspiring, thank you
Post a Comment
<< Home