tags:

views:

30

answers:

3

I have a problem where a table has 100s of rows. It causes an issue and needs to be split into several smaller tables with fewer rows each.

My html is valid xml as well.

How can I split the table every x rows into a new table?

And, how can I copy the table style, and first row (header) into each subsequent table.

So something like this

<table class="..." style="...">
   <tr>
       <td>head 1</td>
       <td>head 2</td>
   </tr>

   <tr>
       <td>col1</td>
       <td>col2</td>
   </tr>

   <tr>
       <td>col1</td>
       <td>col2</td>
   </tr>

   <tr>
       <td>col1</td>
       <td>col2</td>
   </tr>

   <tr>
       <td>col1</td>
       <td>col2</td>
   </tr>

   <tr>
       <td>col1</td>
       <td>col2</td>
   </tr>

   <tr>
       <td>col1</td>
       <td>col2</td>
   </tr>
<table>

Becomes

<table class="..." style="...">
   <tr>
       <td>head 1</td>
       <td>head 2</td>
   </tr>

   <tr>
       <td>col1</td>
       <td>col2</td>
   </tr>

   <tr>
       <td>col1</td>
       <td>col2</td>
   </tr>
</table>

<table class="..." style="...">
   <tr>
       <td>head 1</td>
       <td>head 2</td>
   </tr>

   <tr>
       <td>col1</td>
       <td>col2</td>
   </tr>

   <tr>
       <td>col1</td>
       <td>col2</td>
   </tr>
</table>

<table class="..." style="...">
   <tr>
       <td>head 1</td>
       <td>head 2</td>
   </tr>

   <tr>
       <td>col1</td>
       <td>col2</td>
   </tr>

</table>

<table class="..." style="...">
   <tr>
       <td>head 1</td>
       <td>head 2</td>
   </tr>
   <tr>
       <td>col1</td>
       <td>col2</td>
   </tr>
<table>
+1  A: 

Here's an XSLT2 solution using for-each-group. To change the number of items per table change the divisor in the group-adjacent attribute. Tested in Oxygen/XML with Saxon 9.2.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
  exclude-result-prefixes="xs xd"
  version="2.0">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="table">
    <xsl:variable name="tblNode" select="."/>
    <xsl:variable name="header"  select="tr[1]"/>
    <xsl:for-each-group select="tr[position() > 1]" group-adjacent="(position()-1) idiv 3">
      <xsl:element name="table">
        <xsl:copy-of select="$tblNode/@*"/>
        <xsl:copy-of select="$header"/>
        <xsl:apply-templates select="current-group()"/>
      </xsl:element>
    </xsl:for-each-group>
  </xsl:template>
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Explanation:

  1. In the "table" template create a variable for the table node itself, so we can copy its attributes later, and a variable containing the first row to be reused as the header.
  2. Use for-each-group to select adjacent sets where the result of "position()-1 idiv 3" gives the same result. For the first three rows this returns 0; for the next three, the result is 1, and so on. The divisor controls how many rows get grouped into each set.
  3. For each set, generate a table element, then copy all of the original table's attributes, followed by the header row, then use the identity template (at the end of the stylesheet) to copy all the rows.

Note that if you had nested tables inside the rows you'd have to modify this slightly to avoid having the "table" template match the inner tables.

Jim Garrison
@Jim Garrison: +1 Good XSLT 2.0 solution.
Alejandro
A: 

This XSLT 1.0 stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:param name="pMaxRow" select="2"/>
    <xsl:template match="/">
        <html>
            <xsl:apply-templates
                 select="table/tr[(position()-1) mod $pMaxRow = 1]"
                 mode="table"/>
        </html>
    </xsl:template>
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="tr" mode="table">
        <table>
            <xsl:apply-templates select="../@*|../tr[1]|.|following-sibling::tr
                                             [$pMaxRow > position()]"/>
        </table>
    </xsl:template>
</xsl:stylesheet>

Output:

<html>
    <table class="..." style="...">
        <tr>
            <td>head 1</td>
            <td>head 2</td>
        </tr>
        <tr>
            <td>col1</td>
            <td>col2</td>
        </tr>
        <tr>
            <td>col1</td>
            <td>col2</td>
        </tr>
    </table>
    <table class="..." style="...">
        <tr>
            <td>head 1</td>
            <td>head 2</td>
        </tr>
        <tr>
            <td>col1</td>
            <td>col2</td>
        </tr>
        <tr>
            <td>col1</td>
            <td>col2</td>
        </tr>
    </table>
    <table class="..." style="...">
        <tr>
            <td>head 1</td>
            <td>head 2</td>
        </tr>
        <tr>
            <td>col1</td>
            <td>col2</td>
        </tr>
        <tr>
            <td>col1</td>
            <td>col2</td>
        </tr>
    </table>
</html>

Edit: Compact code.

Alejandro
+1  A: 

This is the classical XSLT 1.0 solution for such kind of problems:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:param name="prowLimit" select="12"/>

    <xsl:variable name="vTable" select="/*"/>

 <xsl:template match="node()|@*" name="identity">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="tr">
  <xsl:if test="position() mod $prowLimit = 1">
    <table>
      <xsl:copy-of select="$vTable/@*"/>
      <xsl:copy-of select=
      ". | following-sibling::tr[not(position() > $prowLimit -1)]"/>
    </table>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>
Dimitre Novatchev