tags:

views:

88

answers:

3

@Oded: Sorry to have been poor in my exposition... My input document has a fragment like this:

<recordset name="resId" >
<record n="0">example 1</record>
<record n="1">example 2</record>
<record n="2">example 1</record>
....
<record n="N">example 1</record>
</recordset>

containing an arbitrarily long node sequence. The attribute "n" reports the order of the node in the sequence. I need to arrange as output that sequence in a M (rows) x N (columns) table and I have some trouble doing that. I cannot call a template

<xsl:template match="recordset">
   <table>
      <xsl:apply-templates select="record"/>
   </table>
</xsl:template>

with something like:

<xsl:template match="record">
<xsl:if test="@n mod 3 = 0">
    <tr>
</xsl:if>
........
<td><xsl:value-of select"something"></td>

because code is invalid (and I should repeat it at the end of the template in some way) and I must put some (maybe too much) trust in the presence of the numbered attribute. Someone has a hint? Thanks!

+2  A: 

You must ensure that nesting is never broken. Things you want nested in the output must be nested in the XSLT.

<xsl:variable name="perRow" select="3" />

<xsl:template match="recordset">
  <table>
    <xsl:apply-templates 
      mode   = "tr"
      select = "record[position() mod $perRow = 1]"
    />
  </table>
</xsl:template>

<xsl:template match="record" mode="tr">
  <tr>
    <xsl:variable name="td" select="
      . | following-sibling::record[position() &lt; $perRow]
    " />
    <xsl:apply-templates mode="td" select="$td" />
    <!-- fill up the last row -->
    <xsl:if test="count($td) &lt; $perRow">
      <xsl:call-template name="filler">
        <xsl:with-param name="rest" select="$perRow - count($td)" />
      </xsl:call-template>
    </xsl:if>
  </tr>
</xsl:template>

<xsl:template match="record" mode="td">
  <td>
    <xsl:value-of select="." />
  </td>
</xsl:template>

<xsl:template name="filler">
  <xsl:param name="rest" select="0" />
  <xsl:if test="$rest">
    <td />
    <xsl:call-template name="filler">
      <xsl:with-param name="rest" select="$rest - 1" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>
Tomalak
@Tomalak - great solution. I was wondering if it'll still work for a variable with different accumulated elements, for example: <xsl:variable name="packageElements" select="Transfer | Coach | Flight | Train" /> instead of <recordset> element in this example. I'm trying to do it, but stuck. Will really appreciate any help. Thanks!
DashaLuna
@Tomalak I'm using msxsl processor. I'm stuck with trying to get the list of nodes in the $td variable. It selects the current node (specified as "."), but I can't figure out how to specify all the following nodes after current one from the $packageElements variable correctly.
DashaLuna
@DashaLuna: This is nothing that can (or should) be answer in these comments. I think it's best to make a separate question, set a link here if you like, and show your XML and the XSLT you have so far.
Tomalak
@Tomalak: Sorry about that, you're right. I have created a new question - http://stackoverflow.com/questions/2997284/xslt1-0-rendering-sequence-of-different-elements-stored-in-a-variable-as-m-x-n-taThanks for suggestion.
DashaLuna
A: 

In XSLT 1.0, using a general n-per-row template.

With the row element name as a parameter, the n-per-row template is not tied to you input or output format.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

  <xsl:output method="xml" indent="yes" />

  <xsl:template match="recordset">
    <table>
      <xsl:call-template name="n-per-row">
        <xsl:with-param name="select" select="record" />
        <xsl:with-param name="row-size" select="2"/>
        <xsl:with-param name="row-element" select="'tr'"/>
      </xsl:call-template>
    </table>
  </xsl:template>

  <xsl:template match="record">
    <xsl:copy-of select="."/>
  </xsl:template>

  <xsl:template name="n-per-row">
    <xsl:param name="select" />
    <xsl:param name="row-size" />
    <xsl:param name="row-element" />
    <xsl:param name="start">
      <xsl:text>1</xsl:text>
    </xsl:param>

    <xsl:variable name="count" select="count($select)" />
    <xsl:variable name="last-tmp" select="number($start) + number($row-size)" />
    <xsl:variable name="last">
      <xsl:choose>
        <xsl:when test="$last-tmp &gt; $count">
          <xsl:value-of select="$count"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$last-tmp"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>

    <xsl:element name="{$row-element}">
      <xsl:apply-templates select="$select[position() &lt;= $last]"/>
    </xsl:element>

    <xsl:if test="count($select) &gt; $last">
      <xsl:call-template name="n-per-row">
        <xsl:with-param name="select" select="$select[position() &gt; $last]"/>
        <xsl:with-param name="row-size" select="$row-size"/>
        <xsl:with-param name="row-element" select="$row-element"/>
        <xsl:with-param name="start" select="$start"/>
      </xsl:call-template>
    </xsl:if>

  </xsl:template>

</xsl:stylesheet>
Lachlan Roche
@Lachlan: This solution does much more recursion than really necessary.
Tomalak
A: 

Using xslt 2.0

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output indent="yes"/>
    <xsl:param name="rows">3</xsl:param>
    <xsl:template match="recordset">
        <table>
            <xsl:for-each-group select="record" group-by="count(preceding-sibling::*) mod $rows ">
                <xsl:value-of select="current-grouping-key()"/>
                <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>
Krab