XSL recursive sort

后端 未结 3 1810
清歌不尽
清歌不尽 2021-01-19 08:45

I am facing a problem where I need to sort elements, depending on their value, which contains a numbers, separated by periods. I need to sort elements depending on the value

相关标签:
3条回答
  • 2021-01-19 08:52

    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>
    
    0 讨论(0)
  • 2021-01-19 09:05

    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.

    0 讨论(0)
  • 2021-01-19 09:08

    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.

    0 讨论(0)
提交回复
热议问题