Let's say I have this XML node:
<items>
<item>...<item>
<item>...<item>
<item>...<item>
<item>...<item>
<item>...<item>
...
</items>
where there are N item
nodes.
Now I would like to transform it into an HTML table with 4 columns. (e.g. if N=12, there are 3 complete rows, and if N=27, there are 7 rows, the last having 3 cells)
How could I go about doing this?
My gut call is to do it this way, where {{something}}
is what I don't know how to implement:
<xsl:template match="items">
<table>
<xsl:call-template name="partition-items">
<xsl:with-param name="skip" select="0" />
</xsl:call-template>
</table>
</xsl:template>
<xsl:template name="partition-items">
<xsl:param name="skip" />
{{ if # of items in current node > $skip,
output a row,
and call partition-items($skip+4)
}}
<xsl:template />
The pieces I don't know how to implement, are
item
elements in the current nodeitem
element in the current nodeUpdate from comments
How to pad the last row with empty
<td />
elements so that each row contains exactly the wanted cells?
I. XSLT 1.0 solution:
Here is probably one of the shortest possible solutions that notably doesn't require explicit recursion:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pNumCols" select="4"/>
<xsl:template match="/*">
<table>
<xsl:apply-templates select="*[position() mod $pNumCols =1]"/>
</table>
</xsl:template>
<xsl:template match="item">
<tr>
<xsl:apply-templates mode="copy" select=
". | following-sibling::*[not(position() >= $pNumCols)]"/>
</tr>
</xsl:template>
<xsl:template match="item" mode="copy">
<td><xsl:value-of select="."/></td>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<items>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
<item>13</item>
<item>14</item>
<item>15</item>
<item>16</item>
<item>17</item>
<item>18</item>
<item>19</item>
<item>20</item>
<item>21</item>
<item>22</item>
<item>23</item>
<item>24</item>
<item>25</item>
<item>26</item>
<item>27</item>
</items>
the wanted, correct result is produced:
<table>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
</tr>
<tr>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
</tr>
<tr>
<td>9</td>
<td>10</td>
<td>11</td>
<td>12</td>
</tr>
<tr>
<td>13</td>
<td>14</td>
<td>15</td>
<td>16</td>
</tr>
<tr>
<td>17</td>
<td>18</td>
<td>19</td>
<td>20</td>
</tr>
<tr>
<td>21</td>
<td>22</td>
<td>23</td>
<td>24</td>
</tr>
<tr>
<td>25</td>
<td>26</td>
<td>27</td>
</tr>
</table>
Explanation:
The wanted number of cells per row is specified in the external/global parameter $pNumCols
.
Templates are applied only on such children of the top element, whose position is the start of a new row -- they are generated by the expression $k * $pNumCols +1
, where $k can be any integer.
The template that processing each row-starting item creates a row (tr
element) and within it applies templates in a special mode "copy"
for the $pNumCols
starting with itself.
The template matching an item
in mode "copy"
simply creates a cell (td
element) and outputs inside it the string value of the item
element being matched.
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pNumCols" select="4"/>
<xsl:template match="items">
<table>
<xsl:for-each-group select="item"
group-by="(position()-1) idiv $pNumCols">
<tr>
<xsl:for-each select="current-group()">
<td>
<xsl:apply-templates/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
</xsl:stylesheet>
applied on the same XML document as before, this transformation produces the same, correct result.
Explanation:
The <xsl:for-each-group>
instruction is used to select the different groups of item
elements where each group contains the elements that must be represented in one row.
The standard XPath 2.0 operator idiv
is used for this purpose.
The XSLT 2.0 function current-group()
contains all items that must be presented in the current row.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With