Matthew Warman
Matthew Warman

Reputation: 3442

XSLT: Unexpected inherited null xmlns attribute

I am writing a transformation where I would like to add a xmlns attribute (xmlns="myNS") to the root node.

When applying the transformation the xmlns attribute is included in some of the child elements.

I can't work out how to change my transformation to only apply it to the root element.

XML

<db:result xmlns:db="http://www.sonicsw.com/esb/service/dbservice">
    <db:resultSet version="1.1">
        <db:row>
            <id>a</id>
            <value1>b</value1>
            <value2>c</value2>
        </db:row>
        <db:row>
            <id>a</id>
            <value1>d</value1>
            <value2>e</value2>
        </db:row>
        <db:row>
            <id>a</id>
            <value1>f</value1>
            <value2>g</value2>
        </db:row>
        <db:row>
            <id>a</id>
            <value1>h</value1>
            <value2>i</value2>
        </db:row>
    </db:resultSet>
</db:result>

XSLT

<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:db="http://www.sonicsw.com/esb/service/dbservice" 
    exclude-result-prefixes="db">
    <xsl:template match="/">
            <xsl:for-each-group select="//db:row" group-by="id">
                <xsl:sort select="id"/>
                    <xsl:apply-templates select="." mode="document"/>
            </xsl:for-each-group>
    </xsl:template>

    <xsl:template match="db:row" mode="document">
        <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="myNS">
            <id><xsl:value-of select="id"/></id>
            <lines>
                <xsl:apply-templates select="//db:row[id=current()/id]" mode="line"/>
            </lines>
        </root>        
    </xsl:template>

    <xsl:template match="db:row" mode="line">
        <line>
            <valueof1><xsl:value-of select="value1"/></valueof1>
            <valueof2><xsl:value-of select="value2"/></valueof2>
        </line>
    </xsl:template>
</xsl:stylesheet>

XML Output

<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="myNS">
    <id>a</id>
    <lines>
        <line xmlns="">
            <valueof1>b</valueof1>
            <valueof2>c</valueof2>
        </line>
        <line xmlns="">
            <valueof1>d</valueof1>
            <valueof2>e</valueof2>
        </line>
        <line xmlns="">
            <valueof1>f</valueof1>
            <valueof2>g</valueof2>
        </line>
        <line xmlns="">
            <valueof1>h</valueof1>
            <valueof2>i</valueof2>
        </line>
    </lines>
</root>

XML Expected Output

<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="myNS">
    <id>a</id>
    <lines>
        <line>
            <valueof1>b</valueof1>
            <valueof2>c</valueof2>
        </line>
        <line>
            <valueof1>d</valueof1>
            <valueof2>e</valueof2>
        </line>
        <line>
            <valueof1>f</valueof1>
            <valueof2>g</valueof2>
        </line>
        <line>
            <valueof1>h</valueof1>
            <valueof2>i</valueof2>
        </line>
    </lines>
</root>

Note: I found this existing post but can't workout how to apply the suggested solution:

Upvotes: 1

Views: 466

Answers (1)

Ian Roberts
Ian Roberts

Reputation: 122394

Literal result elements in a stylesheet take their namespace from the xmlns declarations in scope at that point in the stylesheet, i.e. within

    <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="myNS">
        <id><xsl:value-of select="id"/></id>
        <lines>
            <xsl:apply-templates select="//db:row[id=current()/id]" mode="line"/>
        </lines>
    </root>

the root element and all its unprefixed children are in the myNS namespace. However in

<xsl:template match="db:row" mode="line">
    <line>
        <valueof1><xsl:value-of select="value1"/></valueof1>
        <valueof2><xsl:value-of select="value2"/></valueof2>
    </line>
</xsl:template>

the line and valueofN elements are in no namespace, since there's no default xmlns in scope at this point in the stylesheet.

So the simple answer is to move the xmlns="myNS" from the root element in the db:row template up into the top-level xsl:stylesheet instead:

<xsl:stylesheet version="2.0" 
    xmlns="myNS"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:db="http://www.sonicsw.com/esb/service/dbservice" 
    exclude-result-prefixes="db">
    <xsl:template match="/">
            <xsl:for-each-group select="//db:row" group-by="id">
                <xsl:sort select="id"/>
                    <xsl:apply-templates select="." mode="document"/>
            </xsl:for-each-group>
    </xsl:template>

    <xsl:template match="db:row" mode="document">
        <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <id><xsl:value-of select="id"/></id>
            <lines>
                <xsl:apply-templates select="//db:row[id=current()/id]" mode="line"/>
            </lines>
        </root>        
    </xsl:template>

    <xsl:template match="db:row" mode="line">
        <line>
            <valueof1><xsl:value-of select="value1"/></valueof1>
            <valueof2><xsl:value-of select="value2"/></valueof2>
        </line>
    </xsl:template>
</xsl:stylesheet>

Though note that this stylesheet will not produce well-formed output if the row elements in the original XML don't all have the same id, as you'll get multiple root elements with no single parent. You may wish to add a wrapping element in the match="/" template to ensure the output is well formed.

P.S. since you're in a for-each-group it may be more efficient to lose the mode="document" template and move its content directly inside the f-e-g, then use current-group instead of having to filter for [id=current()/id]:

    <xsl:template match="/">
        <xsl:for-each-group select="//db:row" group-by="id">
            <xsl:sort select="current-grouping-key()"/>
            <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <id><xsl:value-of select="current-grouping-key()"/></id>
                <lines>
                   <xsl:apply-templates select="current-group()" mode="line"/>
                </lines>
            </root>        
        </xsl:for-each-group>
    </xsl:template>

Upvotes: 2

Related Questions