xmlxslttransformationsequential

How to perform multiple, sequential transformations in one XSLT stylesheet


I have to perform two transformations over an XML document, first I need to modify it and then I need to sort the result of the previous modification.

The XML document is as follows:

<root>
    <s>
        <a>
            <b>
                <c>
                    <f1>1</f1>
                    <f2>0004</f2>
                </c>
                <c>
                    <f1>1</f1>
                    <f2>003x</f2>
                </c>
                <c>
                    <f1>0</f1>
                    <f2>0002</f2>
                </c>
                <c>
                    <f1>1</f1>
                    <f2>0001</f2>
                </c>
            </b>
        </a>
    </s>
    <s>
        <a>
            <b>
                <c>
                    <f1>0</f1>
                    <f2>0006</f2>
                </c>
                <c>
                    <f1>0</f1>
                    <f2>005x</f2>
                </c>
            </b>
        </a>
    </s>
</root>

I have two XSLT stylesheets, one for each transformation, they work well if executed separately, I mean in two steps, apply the first one and then apply the second one over the result of the first one.

This is the first stylesheet (value modification transformation):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="//f2">
        <xsl:copy>
            <xsl:choose>
                <xsl:when test="ends-with(.,'x')">
                    <xsl:value-of select="concat('0', .)"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="."/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

This other is the second one (substructure sorting):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="root/s/a/b">
        <xsl:copy>
            <xsl:apply-templates>
                <xsl:sort select="f1"/>
                <xsl:sort select="f2"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

The result of these two stylesheets applied to the XML document is:

<root>
    <s>
        <a>
            <b>
                <c>
                    <f1>0</f1>
                    <f2>0002</f2>
                </c>
                <c>
                    <f1>1</f1>
                    <f2>0001</f2>
                </c>
                <c>
                    <f1>1</f1>
                    <f2>0003x</f2>
                </c>
                <c>
                    <f1>1</f1>
                    <f2>0004</f2>
                </c>
            </b>
        </a>
    </s>
    <s>
        <a>
            <b>
                <c>
                    <f1>0</f1>
                    <f2>0005x</f2>
                </c>
                <c>
                    <f1>0</f1>
                    <f2>0006</f2>
                </c>
            </b>
        </a>
    </s>
</root>

This is the expected result and it is correct.

Now I need to merge them into only one XSLT stylesheet, to be executed in only one step, but my attempts so far can't get the same result, the second transformation is applied to the original XML document instead of the result of the first transformation.

This is XSLT stylesheet I'm trying:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
    <!-- Begin first transformation -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="//f2">
        <xsl:copy>
            <xsl:choose>
                <xsl:when test="ends-with(.,'x')">
                    <xsl:value-of select="concat('0', .)"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="."/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:copy>
    </xsl:template>
    <!-- End first transformation -->
    <!-- Begin second transformation -->
    <xsl:template match="root/s/a/b">
        <xsl:copy>
            <xsl:apply-templates>
                <xsl:sort select="f1"/>
                <xsl:sort select="f2"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    <!-- End second transformation -->
</xsl:stylesheet>

The result of this attempt on merged stylesheet is:

<root>
    <s>
        <a>
            <b>
                <c>
                    <f1>0</f1>
                    <f2>0002</f2>
                </c>
                <c>
                    <f1>1</f1>
                    <f2>0001</f2>
                </c>
                <c>
                    <f1>1</f1>
                    <f2>0004</f2>
                </c>
                <c>
                    <f1>1</f1>
                    <f2>0003x</f2>
                </c>
            </b>
        </a>
    </s>
    <s>
        <a>
            <b>
                <c>
                    <f1>0</f1>
                    <f2>0006</f2>
                </c>
                <c>
                    <f1>0</f1>
                    <f2>0005x</f2>
                </c>
            </b>
        </a>
    </s>
</root>

The problem with this result is that the 'c' substructures which values of field f2 ending with 'x' are in the wrong positions.

Even if I try another copy template like the first one just before the sorting template the result is still not the desired. I don't know what I'm doing wrong, please help me.

Sorry if this question is too long, I tried to make shorter but it lead to misunderstandings.

Thanks.


Solution

  • You could combine the 2 transforms simply as:

    (edited)

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>
    
    <xsl:template match="/root">
        <xsl:variable name="first-transformation">
            <xsl:apply-templates mode="first"/>
        </xsl:variable>
        <xsl:copy>
            <xsl:apply-templates select="$first-transformation"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="@*|node()" mode="#all">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" mode="#current"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="f2[ends-with(., 'x')]" mode="first">
        <xsl:copy>
            <xsl:value-of select="concat('0', .)"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="b">
        <xsl:copy>
            <xsl:apply-templates select="c">
                <xsl:sort select="f1"/>
                <xsl:sort select="f2"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>
    

    This is in XSLT 2.0.