xsl-list
[Top] [All Lists]

Re: [xsl] Natural sort of strings

2011-03-01 14:37:19
Thank you for the reference.
In fact, for I'm satisfied with Michael Kay's response, as I'm using 
Saxon.
On the other hand I've implemented a natural-sort function in xslt 2 
(quoted in original post with a small bug)
--
Vladimir Nesterovsky
http://www.nesterovsky-bros.com/ 

-------- Original Message --------
From: "Hermann Stamm-Wilbrandt" <STAMMW(_at_)de(_dot_)ibm(_dot_)com>
Sent: Tuesday, March 01, 2011 3:15 PM
To: xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com
Subject: Re: [xsl] Natural sort of strings

Vladimir,

as Michael said you can go with saxon-specific alphanumeric collation.

In old post "sorting on decimal" there was an (XSLT 1.0) alternative to
using collations (restricted):

http://www.biglist.com/lists/lists.mulberrytech.com/xsl-list/archives/200905
/msg00208.html

It depends on your number ranges whether that may be helpful.


Mit besten Gruessen / Best wishes,

Hermann Stamm-Wilbrandt
Developer, XML Compiler, L3
Fixpack team lead
WebSphere DataPower SOA Appliances
https://www.ibm.com/developerworks/mydeveloperworks/blogs/HermannSW/
----------------------------------------------------------------------
IBM Deutschland Research & Development GmbH
Vorsitzender des Aufsichtsrats: Martin Jetter
Geschaeftsfuehrung: Dirk Wittkopp
Sitz der Gesellschaft: Boeblingen
Registergericht: Amtsgericht Stuttgart, HRB 243294



From:       "Vladimir Nesterovsky" <vladimir(_at_)nesterovsky-bros(_dot_)com>
To:         <xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com>
Date:       02/24/2011 11:57 AM
Subject:    [xsl] Natural sort of strings



Hello!

I needed to sort strings in "natural" order. That means that I needed
output like this:

"item1/1"
"item2/1"
"item2/2"
"item2/12"
"item3"
"item4"
"item5"
"item6"
"item7"
"item8"
"item9"
"item10/1"
"item11"
"item12"
"item13"
"item14"
"item15"
"item16"
"item17"
"item18"
"item19"
"item20"

The task looks simple but my implementation is surprisingly untrivial.
Is there simple one?

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
  xmlns:xs="http://www.w3.org/2001/XMLSchema";
  xmlns:t="http://www.nesterovsky-bros.com/xslt/public";
  exclude-result-prefixes="t xs">

  <xsl:template match="/" name="main">
    <xsl:variable name="items" as="xs:string*">
      <xsl:for-each select="1 to 20">
        <xsl:sequence select="concat('item', .)"/>
      </xsl:for-each>

      <xsl:sequence select="'item1/1'"/>
      <xsl:sequence select="'item10/1'"/>
      <xsl:sequence select="'item2/1'"/>
      <xsl:sequence select="'item2/2'"/>
      <xsl:sequence select="'item2/12'"/>
    </xsl:variable>

    <xsl:variable name="regular-sort" as="xs:string*">
      <xsl:perform-sort select="$items">
        <xsl:sort select="." order="ascending"/>
      </xsl:perform-sort>
    </xsl:variable>

    <xsl:variable name="natural-sort" as="xs:string*"
      select="t:natural-sort($regular-sort, true())"/>

    <xsl:message>
      <xsl:text>Regular sort:
</xsl:text>

      <xsl:for-each select="$regular-sort">
        <xsl:value-of select="."/>
        <xsl:text>
</xsl:text>
      </xsl:for-each>

      <xsl:text>
Natural sort:
</xsl:text>

      <xsl:for-each select="$natural-sort">
        <xsl:value-of select="."/>
        <xsl:text>
</xsl:text>
      </xsl:for-each>
    </xsl:message>
  </xsl:template>

  <!--
    Sorts strings in "natural" order.
      $values - values to sort.
$ascending - true for ascending, and false for descending order.
Returns an ordered sequence of values.
  -->
  <xsl:function name="t:natural-sort" as="xs:string*">
    <xsl:param name="values" as="xs:string*"/>
    <xsl:param name="ascending" as="xs:boolean"/>

    <xsl:variable name="indices" as="xs:integer*"
      select="t:natural-sort-indices($values, $ascending, false())"/>

    <xsl:sequence select="
      for $i in $indices return
        $values[$i]"/>
  </xsl:function>

  <!--
    Natural sort implementation.
      $values - values to sort.
$ascending - true for ascending, and false for descending order.
$number - true for a number, and false for a non number start 
prefix.
Returns an ordered sequence of value indices.
  -->
  <xsl:function name="t:natural-sort-indices" as="xs:integer*">
    <xsl:param name="values" as="xs:string*"/>
    <xsl:param name="ascending" as="xs:boolean"/>
    <xsl:param name="number" as="xs:boolean"/>

    <xsl:for-each-group select="1 to count($values)"
      group-by="
        for
          $i in .,
          $value in $values[$i]
        return
        (
          if ($number) then
            t:number-prefix($value)
          else
            t:non-number-prefix($value)
        )">

      <xsl:sort
        select="
          if ($number) then
            xs:integer(current-grouping-key())
          else
            current-grouping-key()"
        order="{
          if ($ascending) then
            'ascending'
          else
            'descending'
          }"/>

      <xsl:variable name="start" as="xs:integer"
        select="string-length(current-grouping-key()) + 1"/>
      <xsl:variable name="group" as="xs:integer+"
select="current-group()"/>

      <xsl:choose>
        <xsl:when test="count($group) = 1">
          <xsl:sequence select="$group"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:variable name="next" as="xs:integer+" select="
            t:natural-sort-indices
            (
              (
                for $i in $group return
                  substring($values[$i], $start)
              ),
              $ascending,
              not($number)
            )"/>

          <xsl:sequence select="
            for $i in $next return
              $group[$i]"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each-group>
  </xsl:function>

  <!--
    Gets a number prefix for a value.
      $value - a value to get
prefix for.
      Returns a value prefix.
  -->
  <xsl:function name="t:number-prefix" as="xs:string?">
    <xsl:param name="value" as="xs:string"/>

    <xsl:variable name="parts" as="xs:string*">
      <xsl:analyze-string select="$value" regex="^[0-9]+">
        <xsl:matching-substring>
          <xsl:sequence select="."/>
        </xsl:matching-substring>
      </xsl:analyze-string>
    </xsl:variable>

    <xsl:sequence select="$parts[1]"/>
  </xsl:function>

  <!--
    Gets a non number prefix for a value.
      $value - a value to
get prefix for.
      Returns a value prefix.
  -->
  <xsl:function name="t:non-number-prefix" as="xs:string?">
    <xsl:param name="value" as="xs:string"/>

    <xsl:variable name="parts" as="xs:string*">
      <xsl:analyze-string select="$value" regex="^[^0-9]+">
        <xsl:matching-substring>
          <xsl:sequence select="."/>
        </xsl:matching-substring>
      </xsl:analyze-string>
    </xsl:variable>

    <xsl:sequence select="$parts[1]"/>
  </xsl:function>

</xsl:stylesheet>

Thanks
--
Vladimir Nesterovsky
http://www.nesterovsky-bros.com/ 






--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--

<Prev in Thread] Current Thread [Next in Thread>