This is the mail archive of the xsl-list@mulberrytech.com mailing list .


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Sample of grouping and sorting a relation captured in XML


Hello all,

In order to give something back to the XSL-group, I would like to give you
the following sample of transforming a relation captured in XML into a table
with formatted (grouped and sorted) data.

The example is from a travel brochure, where you can often find a table with
departure dates and fee for a certain journey.

Below is the contents of 4 files:
1. journey.dtd
2. journey.xml
3. journey.xsl
4. journey.html

The crux of the application is in the XSL rule '<xsl:template
match='journey/fees/departures' mode='table'>' in combination with a hash

<xsl:key name='DepartureByMonthAndFee'
      match='/journey/fees/departures/departure/duration/fee'
      use='concat(substring(normalize-space(../../node()), 1, 6), "::",
normalize-space(node()))'/>

The key of the has is a combination of part of the departure date (year and
month) and a fee, e.g. 200103::1598.00. Per key there are 1 or more fee
nodes.

Inside the rule there is a double for-each loop that takes care of sorting
and grouping data. The outer loop iterates over the actual data in a sorted
fashion. The inner loop iterates over the nodes in the hash beloning to this
'key' in a sorted fashion. You only evaluate the node in the hash if it is
the actual node you are looking at. For this node you detemine whether is is
the first or the last element on the hash-nodes for this key. For the
bordercases you do special processing like starting a new table row.


Here is the bare rule:
<xsl:template match='journey/fees/departures' mode='table'>
<xsl:for-each select='departure/duration/fee'>
<xsl:sort select='normalize-space(../../node())' data-type='number'
order='ascending'/>
<xsl:sort select='normalize-space(node())' data-type='number'
order='ascending'/>
<xsl:variable name='nodeID' select='generate-id(.)'/>
<xsl:for-each select='key("DepartureByMonthAndFee",
concat(substring(normalize-space(../../node()), 1, 6), "::",
normalize-space(node())))'>
  <xsl:sort select='normalize-space(../../node())' data-type='number'
order='ascending'/>
  <xsl:sort select='normalize-space(node())' data-type='number'
order='ascending'/>
  <xsl:if test='$nodeID = generate-id(.)'>
    <xsl:if test='position() = 1'>
      <xsl:apply-templates select='../..' mode='month'/>
    </xsl:if>
    <xsl:apply-templates select='../..' mode='day'/>
    <xsl:choose>
      <xsl:when test='position() != last()'>
        <xsl:text>-</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select='self::node()'/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:if>
  </xsl:for-each>
</xsl:for-each>
</xsl:template>


(I'm afraid the actual XSL-rule got a bit unreadable because of the
HTML-tags. I had some problems with that, but I did not want to go into
that. However if you read past the HTML-tags, you'll see the way I did this.
The HTML isn't great but it did display correct in Internet Explorer on
Mac.)

Below is the contents of the files:

File: journey.dtd
<!-- Document Type Defintion: journey.dtd
     Author: Peter Paulus
     Company: Neroc Publishing Solutions
     CreationDate: 20001114
-->

<!ELEMENT journey (fees)>
<!ELEMENT fees (departures)>
<!ELEMENT departures (departure*)>
<!ELEMENT departure (#PCDATA|duration)*>
<!ELEMENT duration (#PCDATA|fee)*>
<!ELEMENT fee (#PCDATA)>

File: journey.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE journey SYSTEM "journey.dtd">

<journey>
<fees>
<departures>
<departure>20010312
    <duration>14
      <fee>1598.00</fee>
    </duration>
</departure>
<departure>20001004
    <duration>14
      <fee>2000.00</fee>
    </duration>
</departure>
<departure>20001018
    <duration>14
      <fee>1000.00</fee>
    </duration>
</departure>
<departure>20010305
    <duration>14
      <fee>1598.00</fee>
    </duration>
</departure>
<departure>20000920
    <duration>14
      <fee>7500.00</fee>
    </duration>
</departure>
<departure>20000920
    <duration>8
      <fee>5500.00</fee>
    </duration>
</departure>
<departure>20000906
    <duration>14
      <fee>4000.00</fee>
    </duration>
    <duration>8
      <fee>3000.00</fee>
    </duration>
</departure>
<departure>20010326
    <duration>14
      <fee>1698.00</fee>
    </duration>
</departure>
<departure>20010319
    <duration>14
      <fee>1698.00</fee>
    </duration>
</departure>
<departure>20000906
    <duration>14
      <fee>1500.00</fee>
    </duration>
</departure>
</departures>
</fees>

</journey>

File: journey.xsl
<?xml version='1.0' encoding='ISO-8859-1'?>

<xsl:transform version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>

<xsl:output method='text'/>

<xsl:key name='DepartureByMonthAndFee'
      match='/journey/fees/departures/departure/duration/fee'
      use='concat(substring(normalize-space(../../node()), 1, 6), "::",
normalize-space(node()))'/>

<xsl:template match='/'>
<xsl:apply-templates/>
</xsl:template>

<xsl:template match='journey'>
<xsl:text>&lt;html&gt;</xsl:text>
<xsl:text>&lt;head&gt;</xsl:text>
<xsl:text>&lt;title&gt;</xsl:text>
<xsl:text>Sample Table</xsl:text>
<xsl:text>&lt;/title&gt;</xsl:text>
<xsl:text>This example is from the travelling business. It is the result of
an XSL transformation that processes a property of a journey, i.e. a
relation: 'Fee by departure'.</xsl:text>
<xsl:text>&lt;br/&gt;</xsl:text>
<xsl:text>&lt;br/&gt;</xsl:text>
<xsl:text>The first data cell in the table show the raw data. The second
data cell show formatted data.</xsl:text>
<xsl:text>&lt;br/&gt;</xsl:text>
<xsl:text>&lt;br/&gt;</xsl:text>
<xsl:text>The raw data is an (unsorted) relation with signature Departure x
Duration -> Fee.&lt;br&gt;In XML it looks like:&lt;br&gt;</xsl:text>
<xsl:text>&lt;PRE&gt;<![CDATA[
&lt;journey&gt;
&lt;fees&gt;
&lt;departures&gt;
&lt;departure&gt;20010312
    &lt;duration&gt;14
      &lt;fee&gt;1598.00&lt;/fee&gt;
    &lt;/duration&gt;
&lt;/departure&gt;
&lt;departure&gt;20001004
    &lt;duration&gt;14
      &lt;fee&gt;2000.00&lt;/fee&gt;
    &lt;/duration&gt;
&lt;/departure&gt;
&lt;departure&gt;20001018
    &lt;duration&gt;14
      &lt;fee&gt;1000.00&lt;/fee&gt;
    &lt;/duration&gt;
&lt;/departure&gt;
...
&lt;/fees&gt;
&lt;/journey&gt;
]]>&lt;/PRE&gt;</xsl:text>
<xsl:text>&lt;br/&gt;</xsl:text>
<xsl:text>&lt;br/&gt;</xsl:text>
<xsl:text>The formatted data groups departures in the same month with the
same fee. The resulting groups are sorted by month (and year).</xsl:text>
<xsl:text>&lt;br/&gt;</xsl:text>
<xsl:text>&lt;br/&gt;</xsl:text>
<xsl:text>N.b.: The duration of a journey does not play a role in this
example, but it is in the relation for other purposes.</xsl:text>
<xsl:text>&lt;br/&gt;</xsl:text>
<xsl:text>&lt;br/&gt;</xsl:text>
<xsl:text>&lt;/head&gt;</xsl:text>
<xsl:text>&lt;body&gt;</xsl:text>
<xsl:text>&lt;table border='2'&gt;</xsl:text>
<xsl:text>&lt;tr&gt;</xsl:text>
<xsl:text>&lt;td colspan='2' align='center'&gt;</xsl:text>
<xsl:text>Journey data: Fee by departure</xsl:text>
<xsl:text>&lt;/td&gt;</xsl:text>
<xsl:text>&lt;/tr&gt;</xsl:text>
<xsl:text>&lt;td valign='top'&gt;</xsl:text>
<xsl:apply-templates select='fees/departures' mode='relation'/>
<xsl:text>&lt;/td&gt;</xsl:text>
<xsl:text>&lt;td valign='top'&gt;</xsl:text>
<xsl:apply-templates select='fees/departures' mode='table'/>
<xsl:text>&lt;/td&gt;</xsl:text>
<xsl:text>&lt;/table&gt;</xsl:text>
<xsl:text>&lt;/body&gt;</xsl:text>
<xsl:text>&lt;/html&gt;</xsl:text>
</xsl:template>

<xsl:template match='journey/fees/departures' mode='relation'>
<xsl:text>&lt;table border='2' cellpadding='6'&gt;</xsl:text>
<xsl:text>&lt;tr&gt;</xsl:text>
<xsl:text>&lt;td colspan='3' align='center'&gt;Raw
data&lt;/td&gt;</xsl:text>
<xsl:text>&lt;/tr&gt;</xsl:text>
<xsl:text>&lt;tr&gt;</xsl:text>
<xsl:text>&lt;td align='center'&gt;Departure&lt;/td&gt;</xsl:text>
<xsl:text>&lt;td align='center'&gt;Duration&lt;/td&gt;</xsl:text>
<xsl:text>&lt;td align='center'&gt;Fee&lt;/td&gt;</xsl:text>
<xsl:text>&lt;/tr&gt;</xsl:text>
<xsl:for-each select='departure/duration/fee'>
  <xsl:sort select='../../node()' data-type='number' order='ascending'/>
  <xsl:text>&lt;tr&gt;</xsl:text>
  <xsl:text>&lt;td&gt;</xsl:text>
  <xsl:value-of select='../../node()'/>
  <xsl:text>&lt;/td&gt;</xsl:text>
  <xsl:text>&lt;td&gt;</xsl:text>
  <xsl:value-of select='../node()'/>
  <xsl:text>&lt;/td&gt;</xsl:text>
  <xsl:text>&lt;td&gt;</xsl:text>
  <xsl:value-of select='node()'/>
  <xsl:text>&lt;/td&gt;</xsl:text>
  <xsl:text>&lt;/tr&gt;</xsl:text>
</xsl:for-each>
<xsl:text>&lt;/table&gt;</xsl:text>
</xsl:template>

<xsl:template match='journey/fees/departures' mode='table'>
<xsl:text>&lt;table border='2' cellpadding='6'&gt;</xsl:text>
<xsl:text>&lt;tr&gt;</xsl:text>
<xsl:text>&lt;td colspan='3' align='center'&gt;Formatted
data&lt;/td&gt;</xsl:text>
<xsl:text>&lt;/tr&gt;</xsl:text>
<xsl:text>&lt;tr&gt;</xsl:text>
<xsl:text>&lt;td align='center'&gt;Month&lt;/td&gt;</xsl:text>
<xsl:text>&lt;td align='center'&gt;Day&lt;/td&gt;</xsl:text>
<xsl:text>&lt;td align='center'&gt;Fee&lt;/td&gt;</xsl:text>
<xsl:text>&lt;/tr&gt;</xsl:text>
<xsl:for-each select='departure/duration/fee'>
<xsl:sort select='normalize-space(../../node())' data-type='number'
order='ascending'/>
<xsl:sort select='normalize-space(node())' data-type='number'
order='ascending'/>
<xsl:variable name='nodeID' select='generate-id(.)'/>
<xsl:for-each select='key("DepartureByMonthAndFee",
concat(substring(normalize-space(../../node()), 1, 6), "::",
normalize-space(node())))'>
  <xsl:sort select='normalize-space(../../node())' data-type='number'
order='ascending'/>
  <xsl:sort select='normalize-space(node())' data-type='number'
order='ascending'/>
  <xsl:if test='$nodeID = generate-id(.)'>
    <xsl:if test='position() = 1'>
      <xsl:text>&lt;tr&gt;</xsl:text>
      <xsl:text>&lt;td&gt;</xsl:text>
      <xsl:apply-templates select='../..' mode='month'/>
      <xsl:text>&lt;/td&gt;</xsl:text>
      <xsl:text>&lt;td&gt;</xsl:text>
    </xsl:if>
    <xsl:apply-templates select='../..' mode='day'/>
    <xsl:choose>
      <xsl:when test='position() != last()'>
        <xsl:text>-</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>&lt;/td&gt;</xsl:text>
        <xsl:text>&lt;td&gt;</xsl:text>
        <xsl:apply-templates select='self::node()'/>
        <xsl:text>&lt;/td&gt;</xsl:text>
        <xsl:text>&lt;/tr&gt;</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:if>
  </xsl:for-each>
</xsl:for-each>
<xsl:text>&lt;/table&gt;</xsl:text>
</xsl:template>

<xsl:template match='journey/fees/departures/departure' mode='year'>
<xsl:value-of select='substring(normalize-space(self::node()), 1, 4)'/>
</xsl:template>

<xsl:template match='journey/fees/departures/departure' mode='month'>
<xsl:variable name='month' select='substring(normalize-space(self::node()),
5, 2)'/>
<xsl:choose>
<xsl:when test='$month = "01"'>Januari</xsl:when>
<xsl:when test='$month = "02"'>Februari</xsl:when>
<xsl:when test='$month = "03"'>March</xsl:when>
<xsl:when test='$month = "04"'>April</xsl:when>
<xsl:when test='$month = "05"'>May</xsl:when>
<xsl:when test='$month = "06"'>June</xsl:when>
<xsl:when test='$month = "07"'>July</xsl:when>
<xsl:when test='$month = "08"'>August</xsl:when>
<xsl:when test='$month = "09"'>September</xsl:when>
<xsl:when test='$month = "10"'>October</xsl:when>
<xsl:when test='$month = "11"'>November</xsl:when>
<xsl:when test='$month = "12"'>December</xsl:when>
</xsl:choose>
</xsl:template>

<xsl:template match='journey/fees/departures/departure' mode='day'>
<xsl:number value='number(substring(normalize-space(self::node()), 7, 2))'/>
</xsl:template>

<xsl:template match='fees/departures/departure/duration/fee'>
<xsl:value-of select='round(number(normalize-space(self::node())))'/>
<xsl:text>,--</xsl:text>
</xsl:template>

</xsl:transform>

File: journey.html
<html>
<head>
<title>Sample Table</title>
This example is from the travelling business. It is the result of an XSL
transformation that processes a property of a journey, i.e. a relation: 'Fee
by departure'.
<br/>
<br/>
The first data cell in the table show the raw data. The second data cell
show formatted data.
<br/>
<br/>
The raw data is an (unsorted) relation with signature Departure x Duration
-> Fee.
<br>
In XML it looks like:
<br>
<PRE>
&lt;journey&gt;
&lt;fees&gt;
&lt;departures&gt;
&lt;departure&gt;20010
312
    &lt;duration&gt;14
      &lt;fee&gt;1598.00&lt;/fee&gt;

&lt;/duration&gt;
&lt;/departure&gt;
&lt;departure&gt;20001004

&lt;duration&gt;14
      &lt;fee&gt;2000.00&lt;/fee&gt;

&lt;/duration&gt;
&lt;/departure&gt;
&lt;departure&gt;20001018

&lt;duration&gt;14
      &lt;fee&gt;1000.00&lt;/fee&gt;

&lt;/duration&gt;
&lt;/departure&gt;
...
&lt;/fees&gt;
&lt;/journey&gt;
</PR
E>
<br/>
<br/>The formatted data groups departures in the same month with the same
fee. The resulting groups are sorted by month (and year).<br/>
<br/>N.b.: The duration of a journey does not play a role in this example,
but it is in the relation for other purposes.<br/>
<br/>
</head>
<body>
<table border='2'>
<tr>
<td colspan='2' align='center'>Journey data: Fee by departure</td>
</tr>
<td valign='top'>
<table border='2' cellpadding='6'>
<tr>
<td colspan='3' align='center'>Raw data</td>
</tr>
<tr>
<td align='center'>Departure</td>
<td align='center'>Duration</td>
<td align='center'>Fee</td>
</tr>
<tr>
<td>20000906
    </td>
<td>14
      </td>
<td>4000.00</td>
</tr>
<tr>
<td>20000906
    </td>
<td>8
      </td>
<td>3000.00</td>
</tr>
<tr>
<td>20000906
    </td>
<td>14
      </td>
<td>1500.00</td>
</tr>
<tr>
<td>20000920
    </td>
<td>14
      </td>
<td>7500.00</td>
</tr>
<tr>
<td>20000920
    </td>
<td>8
      </td>
<td>5500.00</td>
</tr>
<tr>
<td>20001004
    </td>
<td>14
      </td>
<td>2000.00</td>
</tr>
<tr>
<td>20001018
    </td>
<td>14
      </td>
<td>1000.00</td>
</tr>
<tr>
<td>20010305
    </td>
<td>14
      </td>
<td>1598.00</td>
</tr>
<tr>
<td>20010312
    </td>
<td>14
      </td>
<td>1598.00</td>
</tr>
<tr>
<td>20010319
    </td>
<td>14
      </td>
<td>1698.00</td>
</tr>
<tr>
<td>20010326
    </td>
<td>14
      </td>
<td>1698.00</td>
</tr>
</table>
</td>
<td valign='top'>
<table border='2' cellpadding='6'>
<tr>
<td colspan='3' align='center'>Formatted data</td>
</tr>
<tr>
<td align='center'>Month</td>
<td align='center'>Day</td>
<td align='center'>Fee</td>
</tr>
<tr>
<td>September</td>
<td>6</td>
<td>1500,--</td>
</tr>
<tr>
<td>September</td>
<td>6</td>
<td>3000,--</td>
</tr>
<tr>
<td>September</td>
<td>6</td>
<td>4000,--</td>
</tr>
<tr>
<td>September</td>
<td>20</td>
<td>5500,--</td>
</tr>
<tr>
<td>September</td>
<td>20</td>
<td>7500,--</td>
</tr>
<tr>
<td>October</td>
<td>4</td>
<td>2000,--</td>
</tr>
<tr>
<td>October</td>
<td>18</td>
<td>1000,--</td>
</tr>
<tr>
<td>March</td>
<td>5-12</td>
<td>1598,--</td>
</tr>
<tr>
<td>March</td>
<td>19-26</td>
<td>1698,--</td>
</tr>
</table>
</td>
</table>
</body>
</html>


I hope this helps you in developing XSL.

With kind regards,
Peter Paulus


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]