Subject: [xsl] Sample of grouping and sorting a relation captured in XML From: Peter Paulus <paulus@xxxxxxxx> Date: Fri, 05 Jan 2001 14:50:56 +0100 |
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><html></xsl:text> <xsl:text><head></xsl:text> <xsl:text><title></xsl:text> <xsl:text>Sample Table</xsl:text> <xsl:text></title></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><br/></xsl:text> <xsl:text><br/></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><br/></xsl:text> <xsl:text><br/></xsl:text> <xsl:text>The raw data is an (unsorted) relation with signature Departure x Duration -> Fee.<br>In XML it looks like:<br></xsl:text> <xsl:text><PRE><![CDATA[ <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> ... </fees> </journey> ]]></PRE></xsl:text> <xsl:text><br/></xsl:text> <xsl:text><br/></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><br/></xsl:text> <xsl:text><br/></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><br/></xsl:text> <xsl:text><br/></xsl:text> <xsl:text></head></xsl:text> <xsl:text><body></xsl:text> <xsl:text><table border='2'></xsl:text> <xsl:text><tr></xsl:text> <xsl:text><td colspan='2' align='center'></xsl:text> <xsl:text>Journey data: Fee by departure</xsl:text> <xsl:text></td></xsl:text> <xsl:text></tr></xsl:text> <xsl:text><td valign='top'></xsl:text> <xsl:apply-templates select='fees/departures' mode='relation'/> <xsl:text></td></xsl:text> <xsl:text><td valign='top'></xsl:text> <xsl:apply-templates select='fees/departures' mode='table'/> <xsl:text></td></xsl:text> <xsl:text></table></xsl:text> <xsl:text></body></xsl:text> <xsl:text></html></xsl:text> </xsl:template> <xsl:template match='journey/fees/departures' mode='relation'> <xsl:text><table border='2' cellpadding='6'></xsl:text> <xsl:text><tr></xsl:text> <xsl:text><td colspan='3' align='center'>Raw data</td></xsl:text> <xsl:text></tr></xsl:text> <xsl:text><tr></xsl:text> <xsl:text><td align='center'>Departure</td></xsl:text> <xsl:text><td align='center'>Duration</td></xsl:text> <xsl:text><td align='center'>Fee</td></xsl:text> <xsl:text></tr></xsl:text> <xsl:for-each select='departure/duration/fee'> <xsl:sort select='../../node()' data-type='number' order='ascending'/> <xsl:text><tr></xsl:text> <xsl:text><td></xsl:text> <xsl:value-of select='../../node()'/> <xsl:text></td></xsl:text> <xsl:text><td></xsl:text> <xsl:value-of select='../node()'/> <xsl:text></td></xsl:text> <xsl:text><td></xsl:text> <xsl:value-of select='node()'/> <xsl:text></td></xsl:text> <xsl:text></tr></xsl:text> </xsl:for-each> <xsl:text></table></xsl:text> </xsl:template> <xsl:template match='journey/fees/departures' mode='table'> <xsl:text><table border='2' cellpadding='6'></xsl:text> <xsl:text><tr></xsl:text> <xsl:text><td colspan='3' align='center'>Formatted data</td></xsl:text> <xsl:text></tr></xsl:text> <xsl:text><tr></xsl:text> <xsl:text><td align='center'>Month</td></xsl:text> <xsl:text><td align='center'>Day</td></xsl:text> <xsl:text><td align='center'>Fee</td></xsl:text> <xsl:text></tr></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><tr></xsl:text> <xsl:text><td></xsl:text> <xsl:apply-templates select='../..' mode='month'/> <xsl:text></td></xsl:text> <xsl:text><td></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></td></xsl:text> <xsl:text><td></xsl:text> <xsl:apply-templates select='self::node()'/> <xsl:text></td></xsl:text> <xsl:text></tr></xsl:text> </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:for-each> </xsl:for-each> <xsl:text></table></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> <journey> <fees> <departures> <departure>20010 312 <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> ... </fees> </journey> </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
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
Re: [xsl] Programming without Assig, David Carlisle | Thread | Re: [xsl] Sample of grouping and so, Jeni Tennison |
[xsl] Programming without Assignmen, Dan . Diebolt | Date | [xsl] XSL:FO support for full-page , Tony Coates |
Month |