This is a very similar question like XSL: converting xml to sorted multi-column html table
But (unfortunately) there is an additional requirement: it must be XSLT 1.0 without extension functions, i.e. without using the node-set function.
This is my simplified XML:
<demo> <config n_columns="3" /> <messages> <msg date="2011-07-06" title="2nd message" /> <title>message list</title> <msg date="2011-07-05" title="4th message" /> <msg date="2011-07-06" title="3rd message" /> <msg date="2011-07-07" title="1st message" /> </messages> </demo>
Using this style sheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" /> <xsl:template match="/"> <xsl:apply-templates select="demo/messages"> <xsl:with-param name="n_columns" select="number(/demo/config/@n_columns)" /> </xsl:apply-templates> </xsl:template> <xsl:template match="messages"> <xsl:param name="n_columns" /> <div> <xsl:value-of select="concat(./title, ' (', $n_columns, ' columns)')" /> </div> <table> <xsl:variable name="cells" select="msg" /> <xsl:apply-templates select="$cells[(position() - 1) mod $n_columns = 0]" mode="row"> <xsl:with-param name="n_columns" select="$n_columns" /> <xsl:with-param name="cells" select="$cells" /> </xsl:apply-templates> </table> </xsl:template> <xsl:template match="msg" mode="row"> <xsl:param name="n_columns" /> <xsl:param name="cells" /> <xsl:variable name="n_row" select="position()" /> <xsl:variable name="row_cells" select="$cells[position() > ($n_row - 1) * $n_columns][position() <= $n_columns]" /> <tr> <xsl:apply-templates select="$row_cells" mode="cell" /> <xsl:call-template name="empty-cells"> <xsl:with-param name="n" select="$n_columns - count($row_cells)" /> </xsl:call-template> </tr> </xsl:template> <xsl:template match="msg" mode="cell"> <td> <xsl:value-of select="@title" /> </td> </xsl:template> <xsl:template name="empty-cells"> <xsl:param name="n" /> <xsl:if test="$n > 0"> <td> <xsl:attribute name="colspan"> <xsl:value-of select="$n" /> </xsl:attribute> <xsl:text> </xsl:text> </td> </xsl:if> </xsl:template> </xsl:stylesheet>
Produces this HTML snippet as output:
<div>message list (3 columns)</div> <table> <tr> <td>2nd message</td> <td>4th message</td> <td>3rd message</td> </tr> <tr> <td>1st message</td> <td colspan="2"> </td> </tr> </table>
Obviously the missing part of the sort ...
Revising the cell variable as follows is what I need:
<xsl:variable name="cells"> <xsl:for-each select="msg"> <xsl:sort select="@date" order="descending" /> <xsl:sort select="@title" /> <xsl:copy-of select="." /> </xsl:for-each> </xsl:variable>
But now I have to define another variable in order to convert RTF to a nodeler and pass this template to the template that I am applying.
<xsl:variable name="sCells" select="ext:node-set($cells)/*" />
Doing this will result in the following HTML snippet:
<div>message list (3 columns)</div> <table> <tr> <td>1st message</td> <td>2nd message</td> <td>3rd message</td> </tr> <tr> <td>4th message</td> <td colspan="2"> </td> </tr> </table>
Unfortunately, my XSLT engine (SAP XML Toolkit for java) does not support this (or similar) extension function. So I'm looking for another solution that does not require the node-set extension function.
I spent quite a lot of time reading all the forums, etc., but I really can't figure it out. Maybe someone has a good idea for an alternative approach? TNX!
This is a sequel based on the Dimitre solution (slightly advanced). This xml input
<demo> <config n_columns="3" /> <messages> <msg date="2011-07-06" title="2nd message" /> <title>message list</title> <msg date="2011-07-05" title="4th message" /> <msg date="2011-07-06" title="3rd message" /> <msg date="2011-07-07" title="1st message" /> <msg date="2011-07-05" title="5th message" /> <msg date="2011-07-05" title="7th message" /> <msg date="2011-07-05" title="6th message" /> </messages> </demo>
in conjunction with this XSLT style sheet
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" /> <xsl:variable name="vNumCols" select="/*/config/@n_columns" /> <xsl:variable name="vCells" select="/*/messages/msg" /> <xsl:variable name="vNumCells" select="count($vCells)" /> <xsl:variable name="vNumRows" select="ceiling($vNumCells div $vNumCols)" /> <xsl:variable name="vIndexPatternLength" select="string-length(concat('', $vNumCells))" /> <xsl:variable name="vIndexPattern"> <xsl:call-template name="padding"> <xsl:with-param name="length" select="$vIndexPatternLength" /> <xsl:with-param name="chars" select="'0'" /> </xsl:call-template> </xsl:variable> <xsl:variable name="vSortedIndex"> <xsl:for-each select="$vCells"> <xsl:sort select="@date" order="descending" /> <xsl:sort select="@title" /> <xsl:value-of select="format-number(count(preceding-sibling::msg) + 1, $vIndexPattern)" /> </xsl:for-each> </xsl:variable> <xsl:template match="/"> <xsl:apply-templates select="demo/messages" /> </xsl:template> <xsl:template match="messages"> <table> <xsl:for-each select="$vCells[not(position() > $vNumRows)]"> <xsl:variable name="vRow" select="position()" /> <tr> <xsl:for-each select="$vCells[not(position() > $vNumCols)]"> <xsl:variable name="vCol" select="position()" /> <xsl:variable name="vCell" select="($vRow - 1) * $vNumCols + $vCol" /> <xsl:variable name="vIndex" select="substring($vSortedIndex, ($vCell - 1) * $vIndexPatternLength + 1, $vIndexPatternLength)" /> <xsl:variable name="vMessage" select="$vCells[position() = $vIndex]" /> <xsl:choose> <xsl:when test="$vMessage"> <xsl:apply-templates select="$vMessage" mode="cell" /> </xsl:when> <xsl:otherwise> <xsl:call-template name="empty-cell" /> </xsl:otherwise> </xsl:choose> </xsl:for-each> </tr> </xsl:for-each> </table> </xsl:template> <xsl:template match="msg" mode="cell"> <td> <xsl:apply-templates select="." /> </td> </xsl:template> <xsl:template match="msg"> <xsl:value-of select="concat(@date, ' : ', @title)" /> </xsl:template> <xsl:template name="empty-cell"> <td> <xsl:text> </xsl:text> </td> </xsl:template> <xsl:template name="padding"> <xsl:param name="length" select="0" /> <xsl:param name="chars" select="' '" /> <xsl:choose> <xsl:when test="not($length) or not($chars)" /> <xsl:otherwise> <xsl:variable name="string" select="concat($chars, $chars, $chars, $chars, $chars, $chars, $chars, $chars, $chars, $chars)" /> <xsl:choose> <xsl:when test="string-length($string) >= $length"> <xsl:value-of select="substring($string, 1, $length)" /> </xsl:when> <xsl:otherwise> <xsl:call-template name="padding"> <xsl:with-param name="length" select="$length" /> <xsl:with-param name="chars" select="$string" /> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet>
creates this HTML output
<table> <tr> <td>2011-07-07 : 1st message</td> <td>2011-07-06 : 2nd message</td> <td>2011-07-06 : 3rd message</td> </tr> <tr> <td>2011-07-05 : 4th message</td> <td>2011-07-05 : 5th message</td> <td>2011-07-05 : 6th message</td> </tr> <tr> <td>2011-07-05 : 7th message</td> <td> </td> <td> </td> </tr> </table>
Thanks Dimitre!