[XSL-LIST Mailing List Archive Home] [By Thread] [By Date] [Recent Entries] [Reply To This Message]

Re: Muenchian method, and keys 'n stuff

Subject: Re: Muenchian method, and keys 'n stuff
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Wed, 23 Jan 2002 11:23:02 +0000
xsl for each length
I wrote:
> ...OK. So actually you have two levels of grouping:
>
>   - group by letter (to enable you to work out which letters don't
>     have authors)
>   - group by author
>
> And both of the groupings occur within a particular section. And the
> 'adventure' element is actually giving the name of the section,
> right? I thought that all the section elements were called
> 'adventure' (for some reason). So you could use the name of the
> element around the r elements to give you the 'id', rather than
> generating one.
>
> [I'm going to send an XSLT 2.0 solution to the above problem in a
> separate mail...]

So here it is.

First, an iteration over the alphabet, which I'll do using the method
Dimitre suggested (since this is the only one that's actually
supported in the current WDs). We can now iterate over a particular
series of integers, which we'll use here to index into the letters in
the alphabet, so we don't need to use recursion.

The alphabet is stored in a variable:

  <xsl:variable name="l" select="'abcdefghijklmnopqrstuvwxyz'" />

And we use an xsl:for-each to iterate from 1 to the number of letters
in that alphabet (could hardcode the 26, but taking the
string-length() is more extensible):

  <xsl:for-each select="1 to string-length($l)">
    ...
  </xsl:for-each>

Within the xsl:for-each, you can get the letter in the alphabet using
the substring() function, either using the position() or the current
item itself:

  <xsl:for-each select="1 to string-length($l)">
    <xsl:variable name="letter" select="substring($l, ., 1)" />
    ...
  </xsl:for-each>

A key might be more efficient, but to make it simple, I'll just
collect the r elements by their Ra/lett equalling the $letter:

  <xsl:for-each select="1 to string-length($l)">
    <xsl:variable name="letter" select="substring($l, ., 1)" />
    <xsl:variable name="books" select="r[Ra/lett = $letter]" />
    ...
  </xsl:for-each>

So we can use that to test whether there are any books for that
particular letter and send a message if not:

  <xsl:for-each select="1 to string-length($l)">
    <xsl:variable name="letter" select="substring($l, ., 1)" />
    <xsl:variable name="books" select="r[Ra/lett = $letter]" />
    <xsl:choose>
      <xsl:when test="$books">
        ...
      </xsl:when>
      <xsl:otherwise>
        No books whose authors start with
        <xsl:value-of select="$letter" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each>

Within the xsl:when, we want to group the books by their Ra/auth. This
is very easy in XSLT 2.0 with the xsl:for-each-group instruction,
using the group-by attribute:

  <xsl:for-each select="1 to string-length($l)">
    <xsl:variable name="letter" select="substring($l, ., 1)" />
    <xsl:variable name="books" select="r[Ra/lett = $letter]" />
    <xsl:choose>
      <xsl:when test="$books">
        <xsl:for-each-group select="$books"
                            group-by="Ra/auth">
          <h2><xsl:value-of select="Ra/auth" /></h2>
          ...
        </xsl:for-each-group>
      </xsl:when>
      <xsl:otherwise>
        No books whose authors start with
        <xsl:value-of select="$letter" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each>

And finally to apply templates to each of the r elements to get their
details, we retrieve the r elements in the particular author group
using the current-group() function:

  <xsl:for-each select="1 to string-length($l)">
    <xsl:variable name="letter" select="substring($l, ., 1)" />
    <xsl:variable name="books" select="r[Ra/lett = $letter]" />
    <xsl:choose>
      <xsl:when test="$books">
        <xsl:for-each-group select="$books"
                            group-by="Ra/auth">
          <h2><xsl:value-of select="Ra/auth" /></h2>
          <xsl:apply-templates select="current-group()" />
        </xsl:for-each-group>
      </xsl:when>
      <xsl:otherwise>
        No books whose authors start with
        <xsl:value-of select="$letter" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each>

---

The second-level grouping is handled nicely by the xsl:for-each-group
instruction, although personally I'd prefer it if we could split the
above code up a bit more into templates. In the XSLT 2.0 WD, you can
only iterate over a sequence of simple typed values using
xsl:for-each, and you can only group anything if you iterate over
those groups with xsl:for-each-group. Given how often we try to
discourage people from using xsl:for-each, it seems strange to be
so heavily reliant on xsl:for-each for these tasks.

The first-level grouping isn't handled by xsl:for-each-group at all.
The xsl:for-each-group construction is only useful if you don't know
in advance what the groups are going to be. Admittedly the cases when
you don't know what the groups should be are the most common, and the
most difficult to handle in XSLT 1.0, but I wonder whether
xsl:for-each-group should be used for this kind of grouping problem
too, for consistency and because it's still a lot easier to use, and
more efficient I would imagine, than something like the above.

This would be a variation on the group-by version of
xsl:for-each-group, where you actually supply the values for the
groups that you want to create, and the current-group() could be empty
if there were no selected nodes that fell into that group. It doesn't
really fit into the current processing model for xsl:for-each-group
(since the current item within the xsl:for-each-group is the first
item in the group, but in this case it should be the value for the
group that you're trying to build), but perhaps something like:

  <xsl:for-each-group select="r" group-by="Ra/lett"
                      groups="for $i in 1 to string-length($l)
                              return substring($l, $i, 1)">
    <xsl:variable name="letter" select="." />
    <xsl:variable name="books" select="current-group()" />
    ...
  </xsl:for-each-group>

Cheers,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/


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


Current Thread

PURCHASE STYLUS STUDIO ONLINE TODAY!

Purchasing Stylus Studio from our online shop is Easy, Secure and Value Priced!

Buy Stylus Studio Now

Download The World's Best XML IDE!

Accelerate XML development with our award-winning XML IDE - Download a free trial today!

Don't miss another message! Subscribe to this list today.
Email
First Name
Last Name
Company
Subscribe in XML format
RSS 2.0
Atom 0.3
Site Map | Privacy Policy | Terms of Use | Trademarks
Free Stylus Studio XML Training:
W3C Member
Stylus Studio® and DataDirect XQuery ™are products from DataDirect Technologies, is a registered trademark of Progress Software Corporation, in the U.S. and other countries. © 2004-2013 All Rights Reserved.