tags:

views:

599

answers:

3

I am trying to build a list that parses my entire xml document. I need to list the numeric names then the alpha names. The list should look something like this.

6
6600 Training
6500 Training

A
Accelerated Training

T
Training

This is a snippet of the xml.

<courses>
    <course>    
     <name>Accelerated Training</name>
    </course>
    <course>     
     <name>6600 Training</name>
    </course> 
         <course>      
     <name>Training</name>
    </course>
    <course>     
     <name>6500 Training</name>
    </course> 

</courses>

This is the code I am currently using. I found this in another question on the site and have customized it somewhat. Currently it doesn't take into account my need for parsing by number and it also returns out of alphabetical order.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt; 

  <xsl:output omit-xml-declaration="yes" indent="yes"/> 
  <xsl:variable name="vLower" select= "'abcdefghijklmnopqrstuvwxyz'"/> 
  <xsl:variable name="vUpper" select= "'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/> 

  <xsl:key name="kTitleBy1stLetter" match="courses/course"  use="substring(name,1,1)"/>    

  <xsl:template match="/*">      

    <xsl:for-each select="course [generate-id() = generate-id(key('kTitleBy1stLetter', substring(name,1,1)) [1] ) ]">        
      <xsl:variable name="v1st" select="substring(name,1,1)"/>        
      <h2><xsl:value-of select="$v1st"/></h2>        
      <div class="{translate($v1st, $vUpper, $vLower)}-content">
        <ul>
          <xsl:for-each select="key('kTitleBy1stLetter',$v1st)">               
            <li><xsl:value-of select="name"/></li>
          </xsl:for-each>          
        </ul>      
      </div>      
    </xsl:for-each>        
  </xsl:template>
</xsl:stylesheet>
A: 

Well the numbers part is tricky if you want anything complex, but based on your ideal output all you're missing is a simple sort on your for-each:

<xsl:sort select="key('kTitleBy1stLetter', substring(name,1,1))" />

caveat: I make no claims about this being the best or only or otherwise method, merely that this works full stop, and uses what you already have.

annakata
/me smirks at the defensive comment... :-D
Tomalak
Oh - I think it would be <xsl:sort select="substring(name, 1, 1)" /> or <xsl:sort select="name" />.
Tomalak
+6  A: 

Basically you need to group by first letter and sort by <name>. You are on a good way with your Muenchian grouping approach already.

I would suggest an alternative that's a bit easier on the eye:

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

<xsl:template match="courses">
  <xsl:apply-templates select="course" mode="initial">
    <xsl:sort select="name" />
  </xsl:apply-templates>
</xsl:template>

<xsl:template match="course" mode="initial">
  <xsl:variable name="initial" select="substring(name, 1, 1)" />
  <xsl:variable name="courses" select="key('kInitial', $initial)" />
  <xsl:if test="generate-id() = generate-id($courses[1])">
    <h2><xsl:value-of select="$initial"/></h2>
    <ul>
      <xsl:apply-templates select="$courses">
        <xsl:sort select="name" />
      </xsl:apply-templates>
    </ul>
  </xsl:if>
</xsl:template>

<xsl:template match="course">
  <li>
    <xsl:value-of select="name"/>
  </li>
</xsl:template>

outputs:

<h2>6</h2>
<ul>
  <li>6500 Training</li>
  <li>6600 Training</li>
</ul>
<h2>A</h2>
<ul>
  <li>Accelerated Training</li>
</ul>
<h2>T</h2>
<ul>
  <li>Training</li>
</ul>

EDIT: For the sake of legibility I left out the upper-casing of the first letter. The correct key would be this (you can't use a variable in a key, hence the literal alphabet strings):

<xsl:key name="kInitial" match="course" use="
  translate(
    substring(name, 1, 1), 
    'abcdefghijklmnopqrstuvwxyz', 
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  )
" />

The same goes of course for the $initial variable in the second template, but here you can in fact use variables again.

EDIT #2: Since sorting is case-sensitive as well, you can use the same expression:

<xsl:sort select="translate(substring(name, 1, 1), $vLower, $vUpper)" />
Tomalak
Thank you very much. As far as parsing is concerned this is working perfectly. This all in a table and I have been using the code below to create a banding effect. How can I implement this? <xsl:if test="position() mod 2 =1)"> <xsl:attribute name="bgcolor">#e7e7e7</xsl:attribute></xsl:if>
BillZ
Where should the coloring go?
Tomalak
+1 for an insightful, responsible and non-sloppy answer :)
Dimitre Novatchev
It is a banded table and the coloring goes in every other row.
BillZ
But *currently* it's not a table, but <h2>, <ul> and <li>. So... where should the coloring go?
Tomalak
Sorry what I posted here is a simplified version of my code. I did figure it out though. I needed to move this chenck of code just above my display cell.<xsl:if test="(position() mod 2 = 1)"><xsl:attribute name="bgcolor">#e7e7e7</xsl:attribute></xsl:if>
BillZ
Well done. :) So everything is fine now?
Tomalak
Awesome script, just what I was looking for!
Matt W
+1  A: 

An XSLT 2.0 solution:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="text"/>

    <xsl:template match="/*">
      <xsl:for-each-group select="course"
           group-by="upper-case(substring(name,1,1))">
        <xsl:sort select="current-grouping-key()"/>

        <xsl:sequence select=
           "concat('&#xA;', current-grouping-key())"/>

        <xsl:for-each select="current-group()">
          <xsl:sort select="upper-case(name)"/>
          <xsl:sequence select="concat('&#xA;&#x9;', name)"/>
        </xsl:for-each>
      </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>

When the above transformation is applied on the originally-provided XML document:

<courses>
    <course>
     <name>Accelerated Training</name>
    </course>
    <course>
     <name>6600 Training</name>
    </course>
    <course>
     <name>Training</name>
    </course>
    <course>
     <name>6500 Training</name>
    </course>
</courses>

the wanted result is produced (in text format for simplicity -- producing the Html is left as an exercise for the reader :)

6 
    6500 Training 
    6600 Training 
A 
    Accelerated Training 
T 
    Training

Do note:

  1. The use of the <xsl:for-each-group> XSLT 2.0 instruction

  2. The use of the current-grouping-key() and current-group() XSLT 2.0 functions.

  3. The use of the upper-case() XPath 2.0 function

Dimitre Novatchev
Nice and clean. +1 Some of the 2.0 functions should have been in 1.0 from the start. Especially string processing and grouping is a pain in 1.0.
Tomalak