Thursday, January 13, 2011

Changing the Namespace of an element without altering the namespace of child elements

When tranforming xml there is occasionally a need to alter the namespace of a parent element without changing the namepace of the child element e.g.

<p781:parent xmlns:p781="http://v1.parent.codemumbler.co.uk/" xmlns:p463="http://v1.child.codemumbler.co.uk/">
    <p463:childNumber>12345</p463:childNumber>
    <p463:childDate>2010-12-24</p463:childDate>
</p781:parent>

note the parent elemnt has two namespace declaration attributes, one for p781 that it uses itself and one for p463 that is used by its child elements.

Now, there is no mechanism in XSLT to copy a node and change its namespace on the fly. As far as xml is concerned the namespace of an element is one of its fundemantal identifying parts and should not be messed with (which may make you wonder why I am doing it, but I’m a well behaved coder and do what I’m told. By which I mean it was a case of JFDI). One way around this is to create a new element with the same name and set the namespace to be the one I want. e.g.

<!--The parent element has to be moved from the parent namespace to the newParent namespace-->
<xsl:template match="//*[local-name()='parent' and namespace-uri()='http://v1.parent.codemumbler.co.uk']">
    <!--Specify the QName for the new parent element-->
    <xsl:element name="newParent:parent">
        <!--now carry on down the rest of the structure-->
        <xsl:apply-templates select="@*|*|node()"/>

The newParent alias will already of been set in the xslt - I'll show this in the final example containing the whole transform.

There is a problem now though, the namespace declaration for p463 is missing, you can leave it out altogether, the transform will cope with it and add the declaration to the child node but it will be very very messy. Another option is to iterate through the namespace declations in the original element, find the one for the namespace we want to include and take it over WITH its original alias, this will result in the output xml looking almost as nice when it comes out of the transform as when it went in. e.g.

<!--The existing structure has a namespace declaration that is not copied over with the attributes, so it has to be added manually-->
<xsl:for-each select="namespace::node()">
    <xsl:choose>
        <!--specific namespace-->
        <xsl:when test=".= 'http://v1.child.codemumbler.co.uk'">
            <!--This will add a namespace declation to the new element but preserve the prefix of the declaration from the source element-->
            <xsl:namespace name="{name(.)}">
                <xsl:value-of select="."/>
            </xsl:namespace>
        </xsl:when>
    </xsl:choose>
</xsl:for-each>


And done :-/

The output of the transform is:


<newParent:parent xmlns:p463="http://v1.newparent.codemumbler.co.uk/">
    <p463:childNumber xmlns:p781="http://v1.parent.codemumbler.co.uk">12345</p463:childNumber>
    <p463:childDate xmlns:p781="http://v1.parent.codemumbler.co.uk">2010-12-24</p463:childDate>


I know that the old namespace is still being carried down into the child elemnts, but the whole thing is now compliant with the output schema I am using and as I stated at the start - transforms are not designed to play about with namespaces. If you wanted the child elements to be totally tidy you could add the original namespace back in at the parent level or rebuild the entire structure.

Entire transform:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:newParent=http://v1.newParent.codemumbler.co.uk>
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <!--copy every element unless a template match is specified lower down-->
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    <!--The parent element has to be moved from the parent namespace to the newParent namespace-->
    <xsl:template match="//*[local-name()='parent' and namespace-uri()='http://v1.parent.codemumbler.co.uk']">
        <!--Specify the QName for the new parent element-->
        <xsl:element name="newParent:parent">
            <!--The existing structure has a namespace declaration  that is not copied over with the attributes, so it has to be added manually-->
            <xsl:for-each select="namespace::node()">
                <xsl:choose>
                    <!--specific namespace-->
                    <xsl:when test=".= 'http://v1.child.codemumbler.co.uk'">
                        <!--This will add a namespace declation to the new element but preserve the prefix of the declaration from the source element-->
                        <xsl:namespace name="{name(.)}">
                            <xsl:value-of select="."/>
                        </xsl:namespace>
                    </xsl:when>
                </xsl:choose>
            </xsl:for-each>
            <!--now carry on down the rest of the structure-->
            <xsl:apply-templates select="@*|*|node()"/>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>