XSL recursive sort

自古美人都是妖i 提交于 2019-12-01 19:28:01

there is an "easy" answer that doesn't use any extension: split row values into chucks and sort on it.

<xsl:template match="root">
    <xsl:copy>
        <xsl:apply-templates select="ROW">
            <xsl:sort select="substring-before(concat(., '.'), '.')" data-type="number"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>
<xsl:template match="ROW">
    <xsl:param name="prefix" select="''"/>
    <xsl:choose>
        <!-- end of recursion, there isn't any more ROW with more chucks -->
        <xsl:when test=". = substring($prefix, 1, string-length($prefix)-1)">
            <xsl:copy-of select="."/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:variable name="chuck" select="substring-before(concat(substring-after(., $prefix), '.'), '.')"/>
            <!-- this test is for grouping ROW with same prefix, to skip duplicates -->
            <xsl:if test="not(preceding-sibling::ROW[starts-with(., concat($prefix, $chuck))])">
                <xsl:variable name="new-prefix" select="concat($prefix, $chuck, '.')"/>
                <xsl:apply-templates select="../ROW[starts-with(., $new-prefix) or . = concat($prefix, $chuck)]">
                    <xsl:sort select="substring-before(concat(substring-after(., $new-prefix), '.'), '.')" data-type="number"/>
                    <xsl:with-param name="prefix" select="$new-prefix"/>
                </xsl:apply-templates>
            </xsl:if>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

The only problem in accomplishing this is in dealing with individual numbers (between the periods) of different text lengths (i.e. sorting 1.0, 2.0 and 10.0 in that order). If there's an upper limit on the size of the individual numbers (say n digits) then create a sort key that is the concatenation of all the numbers zero-padded to n digits. For n=3, this results in

Row               Key (string)
1                 001
1.0               001000
1.0.1             001000001
1.1               001001
1.2.1             001002001
2.0.1             002000001
10.0.1            010000001

Then sort on the key. If you're stuck in XSLT 1.0 you'll have to resort to EXSLT extension functions to do the parsing and key normalization.

It's not pretty but you can use the xalan:nodeset function to "pre-process" the numbers into a nodeset with an easily sortable expression as described by Jim.

This example works for me with Xalan 2.5.1:

<?xml version="1.0"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:xalan="http://xml.apache.org/xalan">

<xsl:output method="xml" indent="yes" />

<xsl:template match="/">
    <root>
        <!-- Create a sort node with a sort expression wrapping each ROW -->
        <xsl:variable name="nodes">
            <xsl:for-each select="/root/ROW">
                <xsl:variable name="sort-string">
                    <xsl:call-template name="create-sort-string">
                        <xsl:with-param name="sort-string" select="text()" />
                    </xsl:call-template>
                </xsl:variable>
                <sort sort-by="{$sort-string}">
                    <xsl:copy-of select="." />
                </sort>
            </xsl:for-each>
        </xsl:variable>

        <!-- Now sort the sort nodes and copy out the ROW elements -->
        <xsl:for-each select="xalan:nodeset($nodes)/sort">
            <xsl:sort select="@sort-by" data-type="text" />
            <xsl:copy-of select="*" />
        </xsl:for-each>
    </root>
</xsl:template>

<xsl:template name="create-sort-string">
    <xsl:param name="sort-string" />
    <!-- Biggest number at each level -->
    <xsl:variable name="max-num" select="1000" />
    <xsl:choose>
        <xsl:when test="contains($sort-string, '.')">
            <xsl:value-of select="$max-num + number(substring-before($sort-string, '.'))" />
            <xsl:call-template name="create-sort-string">
                <xsl:with-param name="sort-string" select="substring-after($sort-string, '.')" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="concat($max-num + number($sort-string), '0')" />
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

I personally think writing an extension function would be preferable, but I know that's it's not always an option.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!