resurrected user
resurrected user

Reputation: 492

repeatedly printing xml-node in a table

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() &lt; $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

Answers (2)

michael.hor257k
michael.hor257k

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() &lt; $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

Martin Honnen
Martin Honnen

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

Related Questions