Re: [xsl] Sort problem

Subject: Re: [xsl] Sort problem
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Mon, 5 Feb 2001 12:17:50 +0000
Hi Mick,

> How can I get the result of the 'substring-before' part and the
> 'following::word' part to be handled as one entity?

There are two ways of going about this.  The first is to use the
Becker Method to create a string to sort on conditionally.  This is a
really horrendous thing to use, but if you want to stick with XSLT 1.0
without any extension functions, then it's really your only choice.

The Becker Method is based on the fact that if you do:

  1 div boolean-condition

then you'll get 1 if the boolean condition is true, and infinity if
the boolean condition is false.  If you use that within a substring:

  substring(string-value, 1 div boolean-condition)

then you will only get the string value if the boolean condition is
true: otherwise you get the empty string.  So, if you do:

  concat(substring(value-if-true, 1 div boolean-condition),
         substring(value-if-false, 1 div not(boolean-condition)))

then you can build conditional strings based on boolean conditions.
In your example the condition is whether the last character in the
word (the context node) is a '-':

  substring(., string-length()) = '-'

If this is true, then you want to concatenate this value (minus the
'-') with the value of the next word with a type attribute equal to
'end':

  concat(substring(., 1, string-length() - 1),
         following::word[@type = 'end']

If the condition is false, then you just want the value of the word
itself.  So the complete conditional string is:

  concat(substring(concat(substring(., 1, string-length() - 1),
                          following::word[@type = 'end']),
                   1 div (substring(., string-length()) = '-')),
         substring(., 1 div not(substring(., string-length()) = '-')))

Horrible, isn't it!  Anyway, you can use this in your sort:

  <xsl:for-each select="root/line/word">
     <xsl:sort
        select="concat(
                  substring(
                     concat(substring(., 1, string-length() - 1),
                            following::word[@type = 'end']),
                     1 div (substring(., string-length()) = '-')),
                  substring(.,
                     1 div not(substring(., string-length()) = '-')))" />
     ...
  </xsl:for-each>

By the way, personally I'd separate out the three conditions for the
words into separate templates and apply templates rather than using an
xsl:choose, but it's probably just a matter of taste.

<xsl:template match="word">
   <xsl:value-of select="." />
   <br />
</xsl:template>

<xsl:template match="word[@type = 'end']" />

<xsl:template match="word[substring(., string-length()) = '-']">
   <xsl:value-of select="substring(., 1, string-length() - 1)" />
   <xsl:value-of select="following::word[@type = 'end']" />
   <br />
</xsl:template>

The second method is quite a bit cleaner, but it involves using an
extension function at the moment (though it won't when XSLT 1.1 is
finalised).  Basically, you can put together the words that you want
within a result tree fragment, then convert that into a node set, and
sort that node set.

First, create the variable by iterating over the words you're
interested in and creating a separate word element for each of them:

   <xsl:variable name="words">
      <xsl:for-each select="root/line/word[not(@type = 'end')]">
         <word><xsl:apply-templates select="." /></word>
      </xsl:for-each>
   </xsl:variable>

Here I'm using the same templates as above to create the relevant word
dependent on its structure.

Now that you have the words you want to sort on in a result tree
fragment, you can turn that into a node set and iterate over it in
whatever order you want:

   <xsl:for-each select="msxsl:node-set($words)/word">
      <xsl:sort select="." />
      <xsl:value-of select="." />
      <br />
   </xsl:for-each>

Note that to use the msxsl:node-set extension function, you need to
declare the MSXML namespace as urn:schemas-microsoft-com:xslt, and you
probably want to mark msxsl as an extension element prefix using the
extension-element-prefixes attribute on xsl:stylesheet to prevent
spurious namespace declarations being added to your result tree.

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