views:

630

answers:

3

I need this XML,

<list columns="3">
  <item>martin</item>
  <item>donald</item>
  <item>whistler</item>
  <item>mother</item>
  <item>carl</item>
  <item>liz</item>
  <item>cosmo</item>
</list>

to look like this:

<table>
  <tr>
    <td>martin</td>
    <td>donald</td>
    <td>whistler</td>
  </tr>
  <tr>
    <td>mother</td>
    <td>carl</td>
    <td>liz</td>
  </tr>
  <tr>
    <td>cosmo</td>
    <td></td>
    <td></td>
  </tr>
</table>

When columns="4", it should look like this:

<table>
  <tr>
    <td>martin</td>
    <td>donald</td>
    <td>whistler</td>
    <td>mother</td>
  </tr>
  <tr>
    <td>carl</td>
    <td>liz</td>
    <td>cosmo</td>
    <td></td>
  </tr>
</table>

Any hints on what the XSLT file should look like? Near as I can tell, it requires some kind of loop (recursion?), but I'm not sure if there's a more elegant way.

A: 

Something that works, but looks a bit ugly: abuse position().

<xsl:param name="columns">4</xsl:param>
<xsl:template match="list">
  <xsl:variable name="theList" select="."/>
  <xsl:for-each select="//*[position()<(count(item) / $columns)]>
    <xsl:variable name="idx" select="position()"/>
    <tr>
    <xsl:for-each select="//*[position()<$columns]">
       <td><xsl:value-of select="$theList/item[position() + $idx * $columns]"/></td>
    </xsl:for-each>
    </tr>
  </xsl:for-each>
</xsl:template>

There are other ways: for example select first all those nodes that divide with rest 0 by columns in the list, and then walk over those.

See http://www.ibm.com/developerworks/library/x-tipnodst.html for a longer description of the technique above.

ankon
+4  A: 

The approach I would take is to match items in the 1st, 4th, 7th positions by using the 'mod' function of the position() on each item.

After matching each such item, just loop through the following siblings based on the column count.

For the last row, where there may be insufficient items to complete the row, there is a recursive template to add in empty cells based on how many items were in the last row.

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

   <!-- Global variable to get column count -->
   <xsl:variable name="columns" select="number(/list/@columns)"/>

   <!-- Match the root node -->
   <xsl:template match="list">
      <table>
         <!-- Match items in the 1st, 4th, 7th positions, etc (or whatever the column variable holds) -->
         <xsl:apply-templates select="item[position() mod $columns = 1]"/>
      </table>
   </xsl:template>

   <xsl:template match="item">
      <tr>
         <!-- Output the current item -->
         <td>
            <xsl:value-of select="."/>
         </td>
         <!-- Output the following items based on the number of required columns -->
         <xsl:for-each select="following-sibling::item[position() &lt; $columns]">
            <td>
               <xsl:value-of select="."/>
            </td>
         </xsl:for-each>
         <!-- Add in any empty cells if numberof following items is not sufficient -->
         <xsl:call-template name="emptycell">
            <xsl:with-param name="cellcounter" select="count(following-sibling::item[position() &lt; $columns]) + 1" />
         </xsl:call-template>
      </tr>
   </xsl:template>

   <!-- Recursive template to add in empty cells when there are not enough items to complete a row -->
   <xsl:template name="emptycell">
      <xsl:param name="cellcounter" />
      <xsl:if test="$cellcounter &lt; $columns">
         <td></td>
         <xsl:call-template name="emptycell">
            <xsl:with-param name="cellcounter" select="$cellcounter + 1" />
         </xsl:call-template>
      </xsl:if>   
   </xsl:template>

</xsl:stylesheet>
Tim C
Thanks Tim, works great.
hyperslug
A: 

I suggest using CSS3 columns. The specification will become a Candidate Recommendation (call for implementations stage) rather soon, and is already implemented in Gecko and WebKit (Firefox, Safari, Chrome), with vendor prefixes.

Code:

ul { -moz-column-count: 3; -webkit-column-count: 3; }
Ms2ger