• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 328
  • Last Modified:

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 :)
0
Havin_it
Asked:
Havin_it
  • 3
  • 2
  • 2
2 Solutions
 
Geert BormansCommented:
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
 
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
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
Geert BormansCommented:
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 BormansCommented:
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

Featured Post

Free Tool: Subnet Calculator

The subnet calculator helps you design networks by taking an IP address and network mask and returning information such as network, broadcast address, and host range.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

  • 3
  • 2
  • 2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now