Add Namespaces to Root Element

2.7k views Asked by At

I'm writing an XSLT transform where I'd like all namespace prefixes to be defined on the root element. By default MS seems to create a new prefix definition on the first element in the XML hierarchy to use that schema; meaning the same schema may be referenced on multiple elements should those elements not be related to a shared ancestor of the same schema.

By coding the root element as such, all works as desired:

<!-- ... -->

<ns0:root xmlns:ns0="http://some/schema" xmlns:ns1 = "http://another/schema">
    <!-- rest of XSLT; including calls to other templates -->
</ns0:root>

<!-- ... -->

However I can't find any way to code this using xsl:element; e.g.

<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ns0="http://some/schema" 
    xmlns:ns1 = "http://another/schema"
>
    <!-- ... -->

    <xsl:element name="ns0:root">
        <xsl:attribute name="ns1" namespace="http://www.w3.org/2000/xslns/">http://another/schema</xsl:attribute>
        <!-- rest of XSLT; including calls to other templates -->
    </xsl:element> 

    <!-- ... -->

Is it possible to declare namespace prefixes against an xls:element for schemas other than that element itself?


Full Example

XML

<Demo xmlns="http://some/schema">
    <a>Hello</a>
    <b>World</b>
</Demo>

XSLT

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ns0="http://some/schema" 
    xmlns:ns1 = "http://another/schema"
    exclude-result-prefixes="xsl"
>

    <xsl:output method="xml" indent="yes" version="1.0"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/*"> 
        <xsl:element name="{name(.)}" namespace="{namespace-uri(.)}">
            <xsl:apply-templates select="@* | node()" />
        </xsl:element>
    </xsl:template> 

    <xsl:template match="/ns0:Demo/ns0:a">
        <xsl:element name="ns1:z">
            <xsl:value-of select="./text()" />
        </xsl:element>
    </xsl:template> 

    <xsl:template match="/ns0:Demo/ns0:b">
        <xsl:element name="ns1:y">
            <xsl:value-of select="./text()" />
        </xsl:element>
    </xsl:template> 

</xsl:stylesheet>

Result

<Demo xmlns="http://some/schema">
    <ns1:z xmlns:ns1="http://another/schema">Hello</ns1:z>
    <ns1:y xmlns:ns1="http://another/schema">World</ns1:y>
</Demo>

Desired Result

<Demo xmlns="http://some/schema" xmlns:ns1="http://another/schema">
    <ns1:z>Hello</ns1:z>
    <ns1:y>World</ns1:y>
</Demo>

or

<ns0:Demo xmlns:ns0="http://some/schema" xmlns:ns1="http://another/schema">
    <ns1:z>Hello</ns1:z>
    <ns1:y>World</ns1:y>
</ns0:Demo>
2

There are 2 answers

1
Martin Honnen On BEST ANSWER

Your minimal example doesn't explain why you need to use xsl:element instead of xsl:copy and/or literal result elements but as XSLT 1.0 has no xsl:namespace instruction (https://www.w3.org/TR/xslt20/#creating-namespace-nodes) your only way is copying the namespace node from the stylesheet root, as in

<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ns0="http://some/schema" 
    xmlns:ns1 = "http://another/schema"
    exclude-result-prefixes="xsl"
    >

    <xsl:output method="xml" indent="yes" version="1.0"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/*"> 
        <xsl:element name="{name(.)}" namespace="{namespace-uri(.)}">
            <xsl:copy-of select="document('')/*/namespace::*[. = 'http://another/schema']"/>
            <xsl:apply-templates select="@* | node()" />
        </xsl:element>
    </xsl:template> 

    <xsl:template match="/ns0:Demo/ns0:a">
        <xsl:element name="ns1:z">
            <xsl:value-of select="./text()" />
        </xsl:element>
    </xsl:template> 

    <xsl:template match="/ns0:Demo/ns0:b">
        <xsl:element name="ns1:y">
            <xsl:value-of select="./text()" />
        </xsl:element>
    </xsl:template> 

</xsl:stylesheet>

(or any other node having that, such as parameter or variable, but that way you additionally might to convert a result tree fragment to a node set first with exsl:node-set or ms:node-set).

As for why literal result elements and xsl:element give you different results, well, https://www.w3.org/TR/xslt#literal-result-element says:

The created element node will also have a copy of the namespace nodes that were present on the element node in the stylesheet tree ...

while https://www.w3.org/TR/xslt#section-Creating-Elements-with-xsl:element does not say that.

0
John Bollinger On

It is important to understand that although they are represented in XML documents via namespace-declaration attributes, in XPath's and XSLT's data model, the in-scope namespaces for each element are modeled via namespace nodes, not attribute nodes. Moreover, distinct elements do not share namespace nodes; each gets its own set. When using the XML output method, an XSLT processor is responsible for producing namespace declaration attributes that correctly represent the namespace nodes present in the result tree.

That fully explains why Section 7.1.3 of the XSLT 1.0 spec explicitly disallows creating a namespace declaration via an xsl:attribute element:

XSLT processors may make use of the prefix of the QName specified in the name attribute when selecting the prefix used for outputting the created attribute as XML; however, they are not required to do so and, if the prefix is xmlns, they must not do so. Thus, although it is not an error to do:

<xsl:attribute name="xmlns:xsl" namespace="whatever">http://www.w3.org/1999/XSL/Transform</xsl:attribute>

it will not result in a namespace declaration being output.

(Emphasis added.) If creating a namespace declaration that way were permitted then it would allow for the result document to express namespace nodes that were not actually present in the result tree.

An element in the result tree can obtain a namespace node in any of these ways:

  • result elements created via xsl:copy or xsl:copy-of receive copies of the original element's namespace nodes.
  • result elements created via literal result elements in the stylesheet tree get copies of all the namespace nodes of the stylesheet element, whether declared directly on that element or on an ancestor element, with some exceptions.
  • result elements created via xsl:element stylesheet elements are not explicitly specified to receive any namespace nodes, but in practice, to correctly implement the spec they need to receive a namespace node for the namespace, if any, of the element's name.
  • Because only elements have namespace nodes, it follows (but is not explicitly specified) that each element must also receive a namespace node for each namespace to which one of its attributes' names belongs, if that namespace differs from that of the element's own name.
  • a namespace node itself can be copied to the result tree, as demonstrated by the other answer.

There is no reason to expect that an XSLT processor would create additional namespace nodes in the result tree beyond those. In particular, although that might afford the possibility of a simpler XML serialization of the result tree, the tree itself would be strictly more complex.

One way, then, to ensure that the <Demo> element in your result document carries a namespace declaration for a namespace other than that element's own, any obtained by copying the result element from the input tree, or that of an attribute of the element, is to use a literal result element:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ns0="http://some/schema" 
    xmlns:ns1 = "http://another/schema">

    <xsl:output method="xml" indent="yes" version="1.0"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/ns0:Demo"> 
      <ns0:Demo>
        <xsl:apply-templates select="@* | node()" />
      <ns0:Demo>
    </xsl:template> 

    <!-- ... -->

</xsl:stylesheet>

On the other hand, if you must create the element via an xsl:element element -- which should only be necessary if its name needs to be computed -- then you'll need to copy a namespace node from the input tree.