Re: multiple output and grouping

Subject: Re: multiple output and grouping
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Thu, 16 Nov 2000 10:27:52 +0000
Luca,

> That is, I have a list of authors and a list of records associated
> with each author. What I want to do is sorting the list by author
> and grouping the authors (with their records) alphabetically in
> different HTML files, like this:

OK.  First you need to be able to, given a letter, find out which
authors to output.  You can find the authors whose names begin with a
certain letter using starts-with().  For example:

  /authors/author[starts-with(name, $letter)]

In other words, select the author elements whose name child element
starts with $letter.
  
Or you can set up a key that lets you index into the list of authors
according to the first letter of their name:

<xsl:key name="authors"
         match="author"
         use="substring(name, 1, 1)" />

In other words, set up a key space called 'authors' that indexes into
any author elements according to the first character of their name.
You can then access all authors whose names start with a certain
letter using:

  key('authors', $letter)

This is probably more efficient, especially as you'll be retrieving
them 26 times, and especially if you have a long list of authors (as I
guess is likely?)

So, given a letter, you can collect the nodes that represent the
authors together and output whatever you want from them. When you
iterate over them using xsl:for-each or apply templates to then using
xsl:apply-templates you can sort them according to their name using
xsl:sort:

  <xsl:sort select="name" />

You need to have the letter passed into this general template as a
parameter. Something like:

<xsl:template name="output-authors-by-letter">
  <xsl:param name="letter" select="'A'" />
  <saxon:output file="{$letter}.html">
    <html>
      <head>
        <title>Authors starting with <xsl:value-of select="$letter"
        /></title>
      </head>
      <body>
        <h1><xsl:value-of select="$letter" /></h1>
        <xsl:for-each select="key('authors', $letter)">
          <xsl:sort select="name" />
          <h2><xsl:value-of select="name" /></h2>
          <xsl:apply-templates select="records" />
        </xsl:for-each>
      </body>
    </html>
  </saxon:output>
</xsl:template>

So, how to get the letter to be passed into the template.  First, you
need to know your alphabet, so set a variable up to hold it:

<xsl:variable name="alphabet" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />

Now you need something to work its way through that string.  In XSLT
you can do this by recursion: have a template that takes a string
(starting with the full alphabet), takes the first letter and calls
the above template with it, and then calls itself on the rest of that
string:

<xsl:template name="output-authors">
  <xsl:param name="alphabet" select="$alphabet" />
  <xsl:if test="$alphabet">
    <xsl:call-template name="output-authors-by-letter">
      <xsl:with-param name="letter"
                      select="substring($alphabet, 1, 1)" />
    </xsl:call-template>
    <xsl:call-template name="output-authors">
      <xsl:with-param name="alphabet"
                      select="substring($alphabet, 2)" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>

You can also do this using xsl:for-each and the Piez Technique (see,
Wendell ;)  Take a node set of 26 nodes (you probably have that many
elements in your document easily) and iterate over them, using the
position() of the node to tell which letter to take from the alphabet:

<xsl:template name="output-authors">
  <xsl:for-each select="//*[position() &lt;= 26]">
    <xsl:call-template name="output-authors-by-letter">
      <xsl:with-param name="letter"
                      select="substring($alphabet, position(), 1)" />
    </xsl:call-template>
  </xsl:for-each>
</xsl:template>

I hope that helps,

Jeni

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



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


Current Thread