Search code examples
xsltxslt-2.0xslt-grouping

Some kind of bubble sort in XSLT


I want to sort all the <text> elements by the value of the attribute top.

However an element should only be sorted if its previous sibling has a value of top that exceeds its own by 2 or more units.

For example, the following elements

<text top="100">text 1</text>
<text top="99">text 2</text>
<text top="100">text 3</text>
<text top="99">text 4</text>
<text top="35">text 5</text>
<text top="40">text 6</text>

should be transformed to:

<text top="35">text 5</text>
<text top="40">text 6</text>
<text top="100">text 1</text>
<text top="99">text 2</text>
<text top="100">text 3</text>
<text top="99">text 4</text>

So that the group:

<text top="100">text 1</text>
<text top="99">text 2</text>
<text top="100">text 3</text>
<text top="99">text 4</text>

remains as is after sorting.

I only use XSLT from time to time and only know the usual sorting approach:

<xsl:for-each select="text">
<xsl:sort select="@top" />
    <xsl:copy>
        <xsl:copy-of select="./node()|./@*" />
    </xsl:copy>
</xsl:for-each>

But the result I want to achieve would require some kind of bubble sort.

Not sure whether it's doable with pure XSLT. I have an XSLT 2.0 processor.


Solution

  • I wonder whether in XSLT 2/3 it can just be done with an adequate group-ending-with pattern:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        exclude-result-prefixes="#all"
        version="3.0">
    
        <xsl:param name="limit" as="xs:integer" select="1"/>
    
        <xsl:output indent="yes"/>
    
        <xsl:mode on-no-match="shallow-copy"/>
    
        <xsl:template match="root">
            <xsl:for-each-group select="text" group-ending-with="text[abs(xs:decimal(following-sibling::text[1]/@top) - xs:decimal(@top)) > $limit]">
                <xsl:sort select="min(current-group()/@top/xs:decimal(.))"/>
                <xsl:sequence select="current-group()"/>
            </xsl:for-each-group>
        </xsl:template>
    
    </xsl:stylesheet>
    

    Based on the much simplified XQuery code

    for tumbling window $group in root/text
    start when true()
    end $e next $ne when abs(xs:decimal($ne/@top) - xs:decimal($e/@top)) > 1
    order by min($group/@top/xs:decimal(.))
    return
      $group