RE: [xsl] sorting a subset of elements

Subject: RE: [xsl] sorting a subset of elements
From: "Dion Houston" <dionh@xxxxxxxxxxxxx>
Date: Thu, 22 Aug 2002 00:25:53 -0700
With XSLT 1.0 and nearly every XSLT processor I'm aware of you can sort,
filter, and sort again, however the way to do this is processor
dependent.  With MSXML and System.Xml for example:

(note this is pseudo-XSL)

<xsl:stylesheet ... xmlns:msxsl="urn:schemas-microsoft-com:xslt">
    <xsl:param name="NumberToReturn" select="90"/>
    <xsl:template match="/">
        <xsl:variable name="SortedByPrice.tf">
            <xsl:apply-templates select="/Path/To/My/Interesting/Node">
                <xsl:sort select="@price" data-type="number"/>
            </xsl:apply-templates>
        </xsl:variable>
        <xsl:variable name="SortedByPrice" 
            select="msxsl:node-set($SortedByPrice.tf)"/>
        <xsl:for-each select="$SortedByPrice/*[position() &lt
            $NumberToReturn + 1]">
            <xsl:sort select="@name"/>
            <!-- output it -->
		<xsl:copy-of select="."/>
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="Node">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

HTH!

Dion

-----Original Message-----
From: Jarno.Elovirta@xxxxxxxxx [mailto:Jarno.Elovirta@xxxxxxxxx] 
Sent: Wednesday, August 21, 2002 11:30 PM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
Subject: RE: [xsl] sorting a subset of elements

Hi,

> I have created an xml document with over 300 items sorted by 
> price.  For the
> internet I want to display all items, however for print I only want to
> display the 90 most expensive and i want them sorted 
> alpabetically.  I am
> not sure how to accomplish this.

You can't sort, filter and sort again with XSLT 1.0; using XSLT 1.1 or
2.0 you could sort the items into a variable and sort it again. So,
either do two transformations, where you sort by price and filter in the
first one, and sort by name in the second one; or write a recursive
template that will select the first 50 most expensive items, but that
will probably be quite inefficient, e.g. (should work)

<?xml version='1.0' encoding='UTF-8' ?>
<xsl:stylesheet version='1.0'
                xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>

<xsl:template match="itemlist">
  <xsl:copy>
    <xsl:call-template name="max-n">
      <xsl:with-param name="ns" select="item" />
    </xsl:call-template>
  </xsl:copy>
</xsl:template>

<xsl:template match="item">
  <xsl:copy-of select="." />
</xsl:template>

<xsl:template name="max-n">
  <xsl:param name="ns" select="/.." />
  <xsl:param name="selected" select="/.." />
  <xsl:param name="n" select="50"/>

  <xsl:choose>
    <xsl:when test="not(count($ns) = 0) and count($selected) &lt; $n">
      <xsl:variable name="max">
        <xsl:call-template name="max">
          <xsl:with-param name="ns" select="$ns" />
        </xsl:call-template>
      </xsl:variable>
      <xsl:variable name="tbs" select="$ns[@price = $max]" />
      <xsl:call-template name="max-n">
        <xsl:with-param name="ns" select="$ns[not(@price = $max)]" />
        <xsl:with-param name="selected" select="$selected | $ns[@price =
$max][position() &lt; ($n + 1 - count($selected))]" />
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:apply-templates select="$selected">
        <xsl:sort select="@description" />
      </xsl:apply-templates>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="max">
  <xsl:param name="ns" select="/.." />

  <xsl:choose>
    <xsl:when test="count($ns) > 2">
      <xsl:variable name="c" select="ceiling(count($ns) div 2)" />
      <xsl:variable name="head">
        <xsl:call-template name="max">
          <xsl:with-param name="ns" select="$ns[position() &lt;= $c]" />
        </xsl:call-template>
      </xsl:variable>
      <xsl:variable name="tail">
        <xsl:call-template name="max">
          <xsl:with-param name="ns" select="$ns[position() > $c]" />
        </xsl:call-template>
      </xsl:variable>
      <xsl:choose>
        <xsl:when test="$head > $tail">
          <xsl:value-of select="$head" />
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$tail" />
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:when test="count($ns) > 1">
      <xsl:choose>
        <xsl:when test="$ns[1]/@price > $ns[2]/@price">
          <xsl:value-of select="$ns[1]/@price" />
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$ns[2]/@price" />
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$ns/@price" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>

But you do not want that with 300 items, especially if just about every
item has a unique price; so go with the two (chained) transformations.

Cheers,

Jarno

 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread