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

Re: XSLT conditional sorting problem

Subject: Re: XSLT conditional sorting problem
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Tue, 11 Nov 2003 09:06:44 +0000
xsl conditional sort
Hi Ned,

> When I encounter an album by various artists, I'd like to handle it
> as below, but was unable to figure out how to correctly do this:
>
> "Various Artists" - Album_1
>         Artist - Song_1
>         Artist - Song-2
>         ...

You're using Muenchian Grouping to group the <song> elements based on
their album, and applying templates to the first <song> in a
particular album in 'albums' mode.

Within that template, you can (and do) get hold of all the songs on
the album using:

  key('albums', @album)

Hold that in a variable called songs:

  <xsl:variable name="songs" select="key('albums', @album)" />

Now, if it's true that an @artist attribute of one of the <song>
elements in $songs is *not*equal*to* another @artist attribute of one
of the <song> elements in $songs, then you have an album by various
artists. You can test this with:

  $songs/@artist != $songs/@artist

Note that the != comparison uses "existential semantics" -- it returns
true if any of the comparisons between the artist attributes returns
true, so if *any* artist is not the same as *any* other artist.

So you can use this test to determine whether to put "Various Artists"
or use the name of the only artist:

  <xsl:choose>
    <xsl:when test="$songs/@artist != $songs/@artist">
      <xsl:text>Various Artists</xsl:text>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="@artist" />
    </xsl:otherwise>
  </xsl:choose>

You can also use the test to determine a mode in which to apply
templates to the songs, or to pass a parameter into a template,
whichever suits you better. For example:

  <ul title="Songs">
    <xsl:apply-templates select="$songs" mode="songs">
      <xsl:sort select="@tracknr" data-type="number" />
      <xsl:with-param name="show-artist"
        select="$songs/@artist != $songs/@artist" />
    </xsl:apply-templates>
  </ul>

with:

<xsl:template match="song" mode="songs">
  <xsl:param name="show-artist" select="false()" />
  <li title="(Track {@tracknr})">
    <xsl:if test="$show-artist">
      <xsl:value-of select="@artist"/>
      <xsl:text> - </xsl:text>
    </xsl:if>
    <xsl:value-of select="@title"/>
  </li>
</xsl:template>

> One additional thing, if anyone's feeling particularly intelligent
> and helpful - I would be interested to see how I'd sort my XML data
> into nested lists on both artist and album, as such:
>
> Artist_1
>         Album_1
>                 Song_1
>                 Song_2
>                 ...
>         Album_2
>         ...
> Artist_2
> ...

To do that, you need two levels of Muenchian Grouping. Define the two
keys, one that indexes the <song> elements by artist:

<xsl:key name="songs-by-artist" match="song" use="@artist" />

and another that indexes the <song> elements by artist and album by
concatenating the two values:

<xsl:key name="songs-by-artist-and-album" match="song"
  select="concat(@artist, ' - ', @album)" />

At the first level, you want to apply templates to the first <song> by
a particular artist, in 'artists' mode:

<xsl:template match="music">
  <ul title="Songs">
    <xsl:apply-templates mode="artists"
      select="song[generate-id(.) =
                   generate-id(key('songs-by-artist',@artist)[1])]" />
  </ul>
</xsl:template>

The template for 'artists' mode needs to give the artist, and then
apply templates to the first <song> element with that artist on each
particular album, in 'albums' mode, using the second key:

<xsl:template match="song" mode="artists">
  <li>
    <xsl:value-of select="@artist" />
    <xsl:variable name="songs"
                  select="key('songs-by-artist', @artist)" />
    <ul title="Songs">
      <xsl:apply-templates mode="albums"
        select="$songs[generate-id(.) =
                       generate-id(key('songs-by-artist-and-album',
                                       concat(@artist, ' - ', @album))[1])]" />
    </ul>
  </li>
</xsl:template>

The template for 'albums' mode is similar to what you have already,
except that it uses the 'songs-by-artist-and-album' key. A simplified
version would be:

<xsl:template match="song" mode="albums">
  <li>
    <xsl:value-of select="@album" />
    <xsl:variable name="songs"
      select="key('songs-by-artist-and-album',
                  concat(@artist, ' - ', @album))" />
    <ul title="Songs">
      <xsl:apply-templates select="$songs" mode="songs">
        <xsl:sort select="@tracknr" data-type="number" />
      </xsl:apply-templates>
    </ul>
  </li>
</xsl:template>

I don't know how you want to combine this two-level grouping with the
special handling of albums by "Various Artists" -- whether you want
the album to be listed under the artist or you want a special "Various
Artists" category -- but hopefully you should be able to work out how
to do it. If you need more help, let us know.

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.