xmlxslt

XSL Sequential Sort Execution


I'm trying to sort a list based on the sorting of its inner list using XSL.

I have the following XML

<Shirt>
    <Material Name="Fleece">
        <Color Name="Green">
            <Details>
                <!--Omitted-->
            </Details>
        </Color>
        <Color Name="Blue">
            <Details>
                <!--Omitted-->
            </Details>
        </Color>
    </Material>
    <Material Name="Cotton">
        <Color Name="Red">
            <Details>
                <!--Omitted-->
            </Details>
        </Color>
        <Color Name="Acolor">
            <Details>
                <!--Omitted-->
            </Details>
        </Color>
    </Material>
</Shirt>

I have the following XSL

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>

    <!-Sort this first-->
    <xsl:template match="Shirt/Material">
        <xsl:copy>
            <xsl:apply-templates select="Color|@*">
                <xsl:sort select="@Name" order="ascending" />
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

    <!-Sort this second-->
    <xsl:template match="Shirt">
        <xsl:copy>
            <xsl:apply-templates select="Material|@*">
                <xsl:sort select="Color/@Name" order="ascending" />
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
</xsl:transform>

I gain the following output

<Shirt>
    <Material Name="Fleece">
        <Color Name="Blue">
            <Details>
                <!--Omitted-->
            </Details>
        </Color>
        <Color Name="Green">
            <Details>
                <!--Omitted-->
            </Details>
        </Color>
    </Material>
    <Material Name="Cotton">
        <Color Name="Acolor">
            <Details>
                <!--Omitted-->
            </Details>
        </Color>
        <Color Name="Red">
            <Details>
                <!--Omitted-->
            </Details>
        </Color>
    </Material>
</Shirt>

My goal is for the Material "Cotton" to be the first in the list since it has the Color "AColor" when sorted alphabetically. I understand that the transformations are projected to an output tree in an ambiguous order. However, is it possible to "sequentially" execute the sort transformations in the XSL? Sorting the Color list first then the base the Material list sort on that? Also, unfortunately I'm stuck with XSLT 1.0 only


Solution

  • Sorting the Color list first then the base the Material list sort on that?

    Try it this way:

    XSLT 1.0 + EXSLT node-set() function

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    extension-element-prefixes="exsl">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <!-- identity transform -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="Shirt">
        <xsl:copy>
            <!-- pre-sort -->
            <xsl:variable name="pre-sort">
                <xsl:apply-templates select="Material" mode="first-pass"/>
            </xsl:variable>
            <!-- output -->
            <xsl:apply-templates select="exsl:node-set($pre-sort)/Material">
                <xsl:sort select="Color[1]/@Name"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="Material" mode="first-pass">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates select="Color">
                <xsl:sort select="@Name"/>
            </xsl:apply-templates>
        </xsl:copy> 
    </xsl:template>
    
    </xsl:stylesheet>