Search code examples
c#.netxsltxslcompiledtransform

Add Namespaces to Root Element


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>

Solution

  • 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.