xsltxslt-2.0xslt-grouping

Group consecutive numbers in XSLT


How to handle the below type of scenario using XSLT?

Example 1:

Input: <cross-refs>[5, 6, 7, 8, 9]</cross-refs>

Excepted output: <cross-refs>[5-9]</cross-refs>

Example 2:

Input: <cross-refs>[4, 6, 7, 8, 9]</cross-refs>

Excepted output: <cross-refs>[4,6-9]</cross-refs>

Example 3:

Input: <cross-refs>[4, 5, 6, 7, 9, 11, 12,13]</cross-refs>

Excepted output: <cross-refs>[4-7,9,11-13]</cross-refs>

Example 4:

Input: <cross-refs>[4, 5, 6, 7, 9, 10, 11, 12, 13]</cross-refs>

Excepted output: <cross-refs>[4-7,9-13]</cross-refs>

Thanks in advance


Solution

  • Consider the following example:

    XML

    <input>
      <cross-refs>[5, 6, 7, 8, 9]</cross-refs>
      <cross-refs>[4, 6, 7, 8, 9]</cross-refs>
      <cross-refs>[4, 5, 6, 7, 9, 11, 12, 13]</cross-refs>
      <cross-refs>[4, 5, 6, 7, 9, 10, 11, 12, 13]</cross-refs>
    </input>
    

    XSLT 2.0

    <xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
    <xsl:template match="/input">
        <output>
            <xsl:for-each select="cross-refs">
                <xsl:copy>
                    <xsl:variable name="tokens" select="tokenize(translate(., '[]', ''), ', ')" />
                    <xsl:for-each-group select="$tokens" group-adjacent="number(.) - position()">
                        <xsl:value-of select="." />
                        <xsl:if test="count(current-group()) > 1">
                            <xsl:text>-</xsl:text>
                            <xsl:value-of select="current-group()[last()]" />
                        </xsl:if>
                        <xsl:if test="position()!=last()">,</xsl:if>
                    </xsl:for-each-group>           
                </xsl:copy>
            </xsl:for-each>
        </output>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Result

    <?xml version="1.0" encoding="UTF-8"?>
    <output>
       <cross-refs>5-9</cross-refs>
       <cross-refs>4,6-9</cross-refs>
       <cross-refs>4-7,9,11-13</cross-refs>
       <cross-refs>4-7,9-13</cross-refs>
    </output>