XSLT: date duration sorting and number-to-word transformation

Hi all,

I'm working on a CV for film-industry workers in XML with an XSL stylesheet to output XHTML.  It's divided into "role" elements, each of which describes a sub-discipline, e.g. "screenwriter", "producer", "actor"; a role in turn contains "job" elements with date, title and details.  Here's an example job:

<job>
      <start month="10" year="1997"/>
      <end month="10" year="1998"/>
      <title>Halloween Ghost Stories</title>
      <description>Broadcast on Scot FM</description>
</job>

I have the following constraints for the date/duration representation:
1) A duration less than a month has only an "end" element
2) A job which is currently ongoing has only a "start" element
3) Ongoing jobs go at the start of the ordering
4) Jobs that have a start and an end are sorted by end date (descending)
5) All "month" attributes are transformed to their abbreviated word-form.

My current XSLT fragment for fulfilling all the above goes as follows:

<xsl:for-each select="job[not(end)]">
      <xsl:sort select="start/@year" data-type="number" order="descending"/>
      <xsl:sort select="start/@month" data-type="number" order="descending"/>
      <tr>
            <td class="jobdate">
                  <xsl:choose>
                        <xsl:when test="start/@month=01">Jan </xsl:when>
                        <xsl:when test="start/@month=02">Feb </xsl:when>
                        <xsl:when test="start/@month=03">Mar </xsl:when>
                        <xsl:when test="start/@month=04">Apr </xsl:when>
                        <xsl:when test="start/@month=05">May </xsl:when>
                        <xsl:when test="start/@month=06">Jun </xsl:when>
                        <xsl:when test="start/@month=07">Jul </xsl:when>
                        <xsl:when test="start/@month=08">Aug </xsl:when>
                        <xsl:when test="start/@month=09">Sep </xsl:when>
                        <xsl:when test="start/@month=10">Oct </xsl:when>
                        <xsl:when test="start/@month=11">Nov </xsl:when>
                        <xsl:when test="start/@month=12">Dec </xsl:when>
                  </xsl:choose>
            <xsl:value-of select="start/@year"/> - Present</td>
            <td class="jobtitle"><xsl:value-of select="title"/></td>
            <td class="jobdesc"><xsl:value-of select="description"/></td>
      </tr>
</xsl:for-each>
<xsl:for-each select="job[end]">
      <xsl:sort select="end/@year" data-type="number" order="descending"/>
      <xsl:sort select="end/@month" data-type="number" order="descending"/>
      <xsl:sort select="start/@year" data-type="number" order="descending"/>
      <xsl:sort select="start/@month" data-type="number" order="descending"/>
      <tr>
            <td class="jobdate">
            <xsl:if test="start">
                  <xsl:if test="start/@month">
                        <xsl:choose>
                              <xsl:when test="start/@month=01">Jan </xsl:when>
                              <xsl:when test="start/@month=02">Feb </xsl:when>
                              <xsl:when test="start/@month=03">Mar </xsl:when>
                              <xsl:when test="start/@month=04">Apr </xsl:when>
                              <xsl:when test="start/@month=05">May </xsl:when>
                              <xsl:when test="start/@month=06">Jun </xsl:when>
                              <xsl:when test="start/@month=07">Jul </xsl:when>
                              <xsl:when test="start/@month=08">Aug </xsl:when>
                              <xsl:when test="start/@month=09">Sep </xsl:when>
                              <xsl:when test="start/@month=10">Oct </xsl:when>
                              <xsl:when test="start/@month=11">Nov </xsl:when>
                              <xsl:when test="start/@month=12">Dec </xsl:when>
                        </xsl:choose>
                  </xsl:if>
                  <xsl:value-of select="start/@year"/><xsl:text> - </xsl:text>
            </xsl:if>
            <xsl:if test="end/@month">
                  <xsl:choose>
                        <xsl:when test="end/@month=01">Jan </xsl:when>
                        <xsl:when test="end/@month=02">Feb </xsl:when>
                        <xsl:when test="end/@month=03">Mar </xsl:when>
                        <xsl:when test="end/@month=04">Apr </xsl:when>
                        <xsl:when test="end/@month=05">May </xsl:when>
                        <xsl:when test="end/@month=06">Jun </xsl:when>
                        <xsl:when test="end/@month=07">Jul </xsl:when>
                        <xsl:when test="end/@month=08">Aug </xsl:when>
                        <xsl:when test="end/@month=09">Sep </xsl:when>
                        <xsl:when test="end/@month=10">Oct </xsl:when>
                        <xsl:when test="end/@month=11">Nov </xsl:when>
                        <xsl:when test="end/@month=12">Dec </xsl:when>
                  </xsl:choose>
            </xsl:if>
            <xsl:value-of select="end/@year"/></td>
            <td class="jobtitle"><xsl:value-of select="title"/></td>
            <td class="jobdesc"><xsl:value-of select="description"/></td>
      </tr>
</xsl:for-each>


Pretty long-winded, no?  What I'd like to know is: (a) Is there a way of formatting all the months in a single code-block and still be able to sort and order them as above; and (b) is there a way I can assimilate the "job[not(end)]" and "job[end]" blocks together, again while maintaining the precedence of the "job[not(end)]" instances?

I'm hoping for a solution that won't require restructuring of the XML itself, though I'm open to that if absolutely necessary.

Thanks in advance :)
LVL 10
Havin_itAsked:
Who is Participating?
 
Geert BormansInformation ArchitectCommented:
Havin_it,

and here is a rewrite of your stylesheet with the lookup table included

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"  xmlns:map="urn:internal" exclude-result-prefixes="map">
    <xsl:template match="/">
        <xsl:for-each select="job[not(end)]">
            <xsl:sort select="start/@year" data-type="number" order="descending"/>
            <xsl:sort select="start/@month" data-type="number" order="descending"/>
            <tr>
                <td class="jobdate">
                    <xsl:variable name="startMonth" select="start/@month"/>
                    <xsl:value-of select="document('')//map:date[@month = $startMonth]"/>
                    <xsl:value-of select="start/@year"/> - Present</td>
                <td class="jobtitle"><xsl:value-of select="title"/></td>
                <td class="jobdesc"><xsl:value-of select="description"/></td>
            </tr>
        </xsl:for-each>
        <xsl:for-each select="job[end]">
            <xsl:sort select="end/@year" data-type="number" order="descending"/>
            <xsl:sort select="end/@month" data-type="number" order="descending"/>
            <xsl:sort select="start/@year" data-type="number" order="descending"/>
            <xsl:sort select="start/@month" data-type="number" order="descending"/>
            <tr>
                <td class="jobdate">
                    <xsl:if test="start">
                        <xsl:if test="start/@month">
                            <xsl:variable name="startMonth" select="start/@month"/>
                            <xsl:value-of select="document('')//map:date[@month = $startMonth]"/>
                        </xsl:if>
                        <xsl:value-of select="start/@year"/><xsl:text> - </xsl:text>
                    </xsl:if>
                    <xsl:if test="end/@month">
                        <xsl:variable name="endMonth" select="end/@month"/>
                        <xsl:value-of select="document('')//map:date[@month = $endMonth]"/>
                    </xsl:if>
                    <xsl:value-of select="end/@year"/></td>
                <td class="jobtitle"><xsl:value-of select="title"/></td>
                <td class="jobdesc"><xsl:value-of select="description"/></td>
            </tr>
        </xsl:for-each>
       
    </xsl:template>
    <map:dateTable>
        <map:date month="01">Jan </map:date>
        <map:date month="02">Feb </map:date>
        <map:date month="03">Mar </map:date>
        <map:date month="04">Apr </map:date>
        <map:date month="05">May </map:date>
        <map:date month="06">Jun </map:date>
        <map:date month="07">Jul </map:date>
        <map:date month="08">Aug </map:date>
        <map:date month="09">Sep </map:date>
        <map:date month="10">Oct </map:date>
        <map:date month="11">Nov </map:date>
        <map:date month="12">Dec </map:date>
    </map:dateTable>
   
   
</xsl:stylesheet>
0
 
Geert BormansInformation ArchitectCommented:
Hi Havin_it,
>                <xsl:choose>
>                     <xsl:when test="end/@month=01">Jan </xsl:when>
>                     <xsl:when test="end/@month=02">Feb </xsl:when>
>                     <xsl:when test="end/@month=03">Mar </xsl:when>
>                     <xsl:when test="end/@month=04">Apr </xsl:when>
>                     <xsl:when test="end/@month=05">May </xsl:when>
>                     <xsl:when test="end/@month=06">Jun </xsl:when>
>                     <xsl:when test="end/@month=07">Jul </xsl:when>
>                     <xsl:when test="end/@month=08">Aug </xsl:when>
>                     <xsl:when test="end/@month=09">Sep </xsl:when>
>                     <xsl:when test="end/@month=10">Oct </xsl:when>
>                     <xsl:when test="end/@month=11">Nov </xsl:when>
>                     <xsl:when test="end/@month=12">Dec </xsl:when>
>                </xsl:choose>

I would put this in a lookup table
at the end of the stylesheet
and then use the document('') function to retrieve the value

eg
               <map:dateTable>
                    <map:date month="01">Jan </map:date>
                    <map:date month="02">Feb </map:date>
                    <map:date month="03">Mar </map:date>
                    <map:date month="04">Apr </map:date>
                    <map:date month="05">May </map:date>
                    <map:date month="06">Jun </map:date>
                    <map:date month="07">Jul </map:date>
                    <map:date month="08">Aug </map:date>
                    <map:date month="09">Sep </map:date>
                    <map:date month="10">Oct </map:date>
                    <map:date month="11">Nov </map:date>
                    <map:date month="12">Dec </map:date>
               </map:dateTable>


Cheers!
0
 
jkmyoungCommented:
(a) -Add xmlns:my to stylesheet node
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:my="internal">

-Add this to your xslt somewhere:
<my:mapping>
  <month>Jan</month>
  <month>Feb</month>
  <month>Mar</month>
  <month>Apr</month>
  <month>May</month>
  <month>Jun</month>
  <month>Jul</month>
  <month>Aug</month>
  <month>Sep</month>
  <month>Oct</month>
  <month>Nov</month>
  <month>Dec</month>
</my:mapping>

-To get the month use:
<xsl:value-of select="document('')//month[position() = current()/start/@month]"/>
or
<xsl:value-of select="document('')//month[position() = current()/end/@month]"/>
depending on the situation
0
Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

 
jkmyoungCommented:
Ah! Gertone beat me to it :)
b)
<xsl:for-each select="job">
<xsl:sort select="count(end)" order="ascending"/>    
<xsl:sort select="end/@year" data-type="number" order="descending"/>
<xsl:sort select="end/@month" data-type="number" order="descending"/>
<xsl:sort select="start/@year" data-type="number" order="descending"/>
<xsl:sort select="start/@month" data-type="number" order="descending"/>
0
 
Geert BormansInformation ArchitectCommented:
Havin_it,

note this line
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"  xmlns:map="urn:internal" exclude-result-prefixes="map">

the foreign namespace needs a namespace declaration of course
and to avoid the namespace node gets copied to the output tree you can exlude the result prefix

cheers

0
 
Havin_itAuthor Commented:
Blimey!  Thanks for the verbose responses, both of you.  I'll try and get this working, and get back to you.

Cheers :)
0
 
Havin_itAuthor Commented:
Right, I was able to combine both your solutions into a much shorter block of code.  I actually had a similar section elsewhere in the document transforming a numeric date to the full name of the month, so I was able to extend the lookup table to accomodate that as well.

Rewritten fragment:

<xsl:for-each select="job">
      <xsl:sort select="count(end)" order="ascending"/>
      <xsl:sort select="end/@year" data-type="number" order="descending"/>
      <xsl:sort select="end/@month" data-type="number" order="descending"/>
      <xsl:sort select="start/@year" data-type="number" order="descending"/>
      <xsl:sort select="start/@month" data-type="number" order="descending"/>
      <tr>
            <td class="jobdate">
            <xsl:if test="start">
                  <xsl:if test="start/@month">
                        <xsl:variable name="startMonth" select="start/@month"/>
                        <xsl:value-of select="document('')//map:date[@month = $startMonth]/map:abbrev"/>
                        <xsl:text> </xsl:text>
                  </xsl:if>
                  <xsl:value-of select="start/@year"/><xsl:text> - </xsl:text>
            </xsl:if>
            <xsl:choose>
                  <xsl:when test="end">
                        <xsl:if test="end/@month">
                              <xsl:variable name="endMonth" select="end/@month"/>
                              <xsl:value-of select="document('')//map:date[@month = $endMonth]/map:abbrev"/>
                              <xsl:text> </xsl:text>
                        </xsl:if>
                        <xsl:value-of select="end/@year"/>
                  </xsl:when>
                  <xsl:otherwise>Present</xsl:otherwise>
            </xsl:choose></td>
            <td class="jobtitle"><xsl:value-of select="title"/></td>
            <td class="jobdesc"><xsl:value-of select="description"/></td>
      </tr>
</xsl:for-each>

Lookup table:

<map:dateTable>
      <map:date month="01">
            <map:abbrev>Jan</map:abbrev>
            <map:full>January</map:full>
      </map:date>
      <map:date month="02">
            <map:abbrev>Feb</map:abbrev>
            <map:full>February</map:full>
      </map:date>
      <map:date month="03">
            <map:abbrev>Mar</map:abbrev>
            <map:full>March</map:full>
      </map:date>
      <map:date month="04">
            <map:abbrev>Apr</map:abbrev>
            <map:full>April</map:full>
      </map:date>
      <map:date month="05">
            <map:abbrev>May</map:abbrev>
            <map:full>May</map:full>
      </map:date>
      <map:date month="06">
            <map:abbrev>Jun</map:abbrev>
            <map:full>June</map:full>
      </map:date>
      <map:date month="07">
            <map:abbrev>Jul</map:abbrev>
            <map:full>July</map:full>
      </map:date>
      <map:date month="08">
            <map:abbrev>Aug</map:abbrev>
            <map:full>August</map:full>
      </map:date>
      <map:date month="09">
            <map:abbrev>Sep</map:abbrev>
            <map:full>September</map:full>
      </map:date>
      <map:date month="10">
            <map:abbrev>Oct</map:abbrev>
            <map:full>October</map:full>
      </map:date>
      <map:date month="11">
            <map:abbrev>Nov</map:abbrev>
            <map:full>November</map:full>
      </map:date>
      <map:date month="12">
            <map:abbrev>Dec</map:abbrev>
            <map:full>December</map:full>
      </map:date>
</map:dateTable>


Props to both of you for providing pieces of the puzzle, award to Geert for being first to the punch ;)
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.