Reputation: 492
I have data in an xml-file and one of its representations is in a n-column table, to be printed as labels. The way I have it now is that each printed label corresponds with one node in the xml file:
xml:
<?xml version='1.0' encoding='UTF-8'?>
<?xml-stylesheet type='text/xsl' href='table.xsl'?>
<nodes>
<node name="A" />
<node name="A" />
<node name="A" />
<node name="B" />
<node name="B" />
<node name="B" />
<node name="B" />
<node name="C" />
<node name="C" />
</nodes>
xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" media-type="text/html" />
<xsl:param name="cols">3</xsl:param> <!-- set the number of columns here -->
<xsl:template match="nodes">
<table>
<tr>
<xsl:apply-templates select="node[position() mod $cols = 1 ]" mode="row"/>
</tr>
</table>
</xsl:template>
<xsl:template match="node" mode="row">
<tr>
<xsl:apply-templates select=". | following-sibling::node[position() < $cols]" mode="cell"/>
</tr>
</xsl:template>
<xsl:template match="node" mode="cell">
<td style="border: 1px dotted #999;">
<xsl:value-of select="./@name"/>
</td>
</xsl:template>
</xsl:stylesheet>
result:
A A A
B B B
B C C
For better maintainability and data integrity, I would like to redefine the xml in such a way, that the nodes are not repeated, but have their own counts as attributes, so:
xml:
<nodes>
<node name="A" n="3"/>
<node name="B" n="4"/>
<node name="C" n="2"/>
</nodes>
My problem is, that now position() no longer corresponds with the number of printed labels. It looks like I'd need a kind of for i=1 to n
-loop. How could this be achieved?
Upvotes: 0
Views: 66
Reputation: 117073
I would suggest you do this in two steps: first, generate the required individual cells; then organize them into rows of a table:
XSLT 1.0 (+ EXSLT node-set())
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="html"/>
<xsl:param name="cols">3</xsl:param> <!-- set the number of columns here -->
<xsl:template match="/nodes">
<!-- first pass -->
<xsl:variable name="cells">
<xsl:for-each select="node">
<xsl:call-template name="generate-cells">
<xsl:with-param name="name" select="@name"/>
<xsl:with-param name="n" select="@n"/>
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<!-- output -->
<table border="1">
<xsl:for-each select="exsl:node-set($cells)/td[position() mod $cols = 1]">
<tr>
<xsl:copy-of select=". | following-sibling::td[position() < $cols]"/>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="generate-cells">
<xsl:param name="name"/>
<xsl:param name="n"/>
<xsl:if test="$n">
<td>
<xsl:value-of select="$name"/>
</td>
<xsl:call-template name="generate-cells">
<xsl:with-param name="name" select="$name"/>
<xsl:with-param name="n" select="$n - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1
Reputation: 167696
With XSLT 3, as for instance supported inside the browser using Saxon JS 2, you could use
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:param name="cols" as="xs:integer" select="3"/>
<xsl:function name="mf:repeat" as="item()*">
<xsl:param name="item" as="item()"/>
<xsl:param name="count" as="xs:integer"/>
<xsl:sequence
select="(1 to $count) ! $item"/>
</xsl:function>
<xsl:template match="/">
<html>
<head>
<title>Example</title>
</head>
<body>
<h1>Example</h1>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="nodes">
<table>
<tbody>
<xsl:for-each-group select="node ! mf:repeat(., @n)" group-adjacent="(position() - 1) idiv $cols">
<tr>
<xsl:apply-templates select="current-group()"/>
</tr>
</xsl:for-each-group>
</tbody>
</table>
</xsl:template>
<xsl:template match="node">
<td>{@name}</td>
</xsl:template>
</xsl:stylesheet>
Upvotes: 0