xmlxsltxslt-1.0

XSL sorting nodes at different levels of one hierarchy


I have an XML in which I wish to sort different nodes based on different criteria. I wish to have the output as an XML as well. The issue I run into is when a node-set matching one of the sort is an ancestor of a node that is captured by a different sort criterion.

Below is the XSL:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.1">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:template match="//*[string]">
        <xsl:copy>
            <xsl:for-each select="string">
                <xsl:sort order="ascending" data-type="text" case-order="upper-first"/>
                <xsl:copy-of select="."/>
                <xsl:value-of select="'&#10;'"/>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/root/foo">
        <xsl:copy>
            <xsl:for-each select="bar">
                <xsl:sort order="ascending" data-type="text" case-order="upper-first" select="name"/>
                <xsl:copy-of select="."/>
                <xsl:value-of select="'&#10;'"/>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

This is a sample XML that when fed as input for the above XSL, shows the problem:

<root>
    <foo>
        <bar>
            <name>W</name>
            <blah>
                <string>P</string>
                <string>R</string>
                <string>Q</string>
                <string>S</string>
            </blah>
        </bar>
        <bar>
            <name>U</name>
            <blah>
                <string>N</string>
                <string>L</string>
                <string>M</string>
                <string>O</string>
            </blah>
        </bar>
        <bar>
            <name>V</name>
            <blah>
                <string>Z</string>
                <string>X</string>
                <string>Y</string>
            </blah>
        </bar>
    </foo>
    <some>
        <other>
            <hierarchy>
                <string>D</string>
                <string>A</string>
                <string>C</string>
                <string>B</string>
            </hierarchy>
        </other>
    </some>
    <some>
        <other>
            <hierarchy>
                <with />
                <no />
                <impact />
            </hierarchy>
        </other>
    </some>
</root>

This is the output that I get:

<root>
    <foo><bar>
            <name>U</name>
            <blah>
                <string>N</string>
                <string>L</string>
                <string>M</string>
                <string>O</string>
            </blah>
        </bar>
<bar>
            <name>V</name>
            <blah>
                <string>Z</string>
                <string>X</string>
                <string>Y</string>
            </blah>
        </bar>
<bar>
            <name>W</name>
            <blah>
                <string>P</string>
                <string>R</string>
                <string>Q</string>
                <string>S</string>
            </blah>
        </bar>
</foo>
    <some>
        <other>
            <hierarchy><string>A</string>
<string>B</string>
<string>C</string>
<string>D</string>
</hierarchy>
        </other>
    </some>
    <some>
        <other>
            <hierarchy>
                <with/>
                <no/>
                <impact/>
            </hierarchy>
        </other>
    </some>
</root>

As one can see, the <name> nodes as well as the <string> nodes that appear in the some-other-hierarchy are sorted. However, the <string> nodes that are in the foo-bar-blah hierarchy are unsorted.

I am required to use these versions of the libraries:

While they are old, I do not think/expect that will be an issue for the solution (i.e., the problem stems from my lack of knowledge regarding xsl:sort and not the technology)

I would appreciate any/all help. Thank you for listening,


Solution

  • Instead of

    <xsl:template match="//*[string]">
        <xsl:copy>
            <xsl:for-each select="string">
                <xsl:sort order="ascending" data-type="text" case-order="upper-first"/>
                <xsl:copy-of select="."/>
                <xsl:value-of select="'&#10;'"/>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/root/foo">
        <xsl:copy>
            <xsl:for-each select="bar">
                <xsl:sort order="ascending" data-type="text" case-order="upper-first" select="name"/>
                <xsl:copy-of select="."/>
                <xsl:value-of select="'&#10;'"/>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    

    try

    <xsl:template match="*[string]">
        <xsl:copy>
            <xsl:apply-templates select="string">
                <xsl:sort order="ascending" data-type="text" case-order="upper-first"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/root/foo">
        <xsl:copy>
            <xsl:apply-templates select="bar">
                <xsl:sort order="ascending" data-type="text" case-order="upper-first" select="name"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>