4

I have a situation where I need to check for attribute values that may be successively numbered and input a dash between the start and end values.

<root>
<ref id="value00008 value00009 value00010 value00011 value00020"/>
</root>

The ideal output would be...

8-11, 20

I can tokenize the attribute into separate values, but I'm unsure how to check if the number at the end of "valueXXXXX" is successive to the previous value.

I'm using XSLT 2.0

Mads Hansen
  • 63,927
  • 12
  • 112
  • 147
Jeff
  • 877
  • 2
  • 11
  • 17

1 Answers1

4

You can use xsl:for-each-group with @group-adjacent testing for the number() value subtracting the position().

This trick was apparently invented by David Carlisle, according to Michael Kay.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     version="2.0">
  <xsl:output indent="yes"/>
  <xsl:template match="/">
        <xsl:variable name="vals" 
               select="tokenize(root/ref/@id, '\s?value0*')[normalize-space()]"/>

        <xsl:variable name="condensed-values" as="item()*">

          <xsl:for-each-group select="$vals" 
                              group-adjacent="number(.) - position()">
              <xsl:choose>
                  <xsl:when test="count(current-group()) > 1">
                    <!--a sequence of successive numbers, 
                        grab the first and last one and join with '-' -->
                    <xsl:sequence select="
                               string-join(current-group()[position()=1 
                                              or position()=last()]
                                           ,'-')"/>
                  </xsl:when>
                  <xsl:otherwise>
                      <!--single value group-->
                      <xsl:sequence select="current-group()"/>
                  </xsl:otherwise>
              </xsl:choose>
          </xsl:for-each-group>
        </xsl:variable>

      <xsl:value-of select="string-join($condensed-values, ',')"/>

  </xsl:template>
</xsl:stylesheet>
Community
  • 1
  • 1
Mads Hansen
  • 63,927
  • 12
  • 112
  • 147
  • This is a great answer. One thing is missing for me. How can I test if the group is the last item and not output the ", "? – Jeff Oct 21 '13 at 21:06
  • It should not have a trailing comma. Have you executed it? string-join() will concatenate the values from the sequence in the first parameter using the value of the second string parameter and will not append to the last item. – Mads Hansen Oct 21 '13 at 22:52
  • I see. I had to modify for what I needed to output. I needed each item wrapped in a separate tag so I removed the string-join from my execution. – Jeff Oct 21 '13 at 23:23