xslt tables, generating tables

Tables

1. Arrange data in multiple columns
2. Generating a table, Example
3. Making balanced two-column tables from one-column data
4. Balanced tables from a single list, using attribute values
5. Alternate colors on TABLE rows
6. Turning columns into Rows
7. Broken tables
8. Generating balanced tables
9. XSL Table Styling
10. Output two diff. elements side by side in HTML
11. Multiple Rows in a Table
12. Retriving next 10 elements
13. Colouring table cells
14. Splitting up tables
15. Alternate bg colors for table and address summaries
16. Balancing the number of cells in a table
17. How to find the Maximum number of cells in a row
18. Table from ADO
19. Cals table stylesheet
20. Replace repetition in cell values by span values.
21. Normalize / Simplify HTML-Tables with row-span / col-span
22. How to split a large cals table

1.

Arrange data in multiple columns

David Carlisle



> When an item is the 5th, 10th, 15th etc. 
> (using "position() mod 5 =0")
> it should close this row and start the next.

Wrong viewpoint! In XSL you should think in terms of
constructing a tree not of writing out a linear XML
syntax. Clearly in a `tree view' there is no sense to
</tr><tr>.

What you want to do is select every 5th node, and put that
node and its four next siblings into a tr node.  so
(untested)

<xsl:for-each select="item[position() mod 5 = 1]">
<tr>
  <xsl:apply-templates 
     select=".|following-sibling::item[position() &lt; 5]"/>
</tr>
<xsl:for-each>

2.

Generating a table, Example

Steve Muench


| I need some help figuring out how to do a somewhat complex transform (XML
| into HTML) that I need help with. Its essentially a cross-tab type
| situation.
	

Here's a stylesheet that produces the cross-product you're looking for. It uses an <xsl:key> like functional indexes to speed up the iteration of unique regions.

Assuming the input is:

<demo>
  <salesman id="0001" name="Rick Peterson">
    <account id="act001" region="Midwest">Johnosn's Laundry</account>
    <account id="act002" region="Canada">Franks's Laundry</account>
    <account id="act003" region="Alaska">Mary's Laundry</account>
    <account id="act004" region="New Jersey">Bill's Laundry</account>
    <account id="act005" region="Midwest">Hammond's Laundry</account>
  </salesman> 
  <salesman id="0003" name="Ty Coon">
    <account id="act006" region="Canada">Franks's Diner</account>
    <account id="act007" region="Midwest">Johnosn's Diner</account>
    <account id="act008" region="Canada">Hammond's Diner</account>
    <account id="act009" region="Alaska">Mary's Diner</account>
    <account id="act010" region="Alaska">Bill's Diner</account>
  </salesman> 
</demo>

The following stylesheet produces an HTML table crosstab with salespeople down the left (ordered by name) and regions across the top (ordered by name), with a grid cell for each crosstab, and &nsbp; for empty cells.

<!-- Example of a "CrossTab" stylesheet 
       using <xsl:key>'s for grouping -->
<xsl:stylesheet version="1.0" 
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="b" match="@region" use="."/>
  <xsl:template match="/">
    <html>  
      <body>
        <table border="1" cellspacing="0">
          <!-- Generate the row of table header cells -->
          <tr>
            <th>Salesperson</th>
            <!-- 
             | Generate a header cell for each unique region name
             |
             | See Chapter 9 of "Building Oracle XML Applications" 
                                 from O'Reilly
             | for a detailed explanation of how this 
                  <xsl:key> based technique works
             +-->
            <xsl:for-each 
        select="//@region[generate-id(.)=generate-id(key('b',.)[1])]">
              <!-- Sort by the region name 
                   (the value of the current @region attribute -->
              <xsl:sort select="."/>
              <th>
                <xsl:value-of select="."/>
              </th>
            </xsl:for-each>
          </tr>
        <!-- Generate a row for each salesman -->
        <xsl:for-each select="demo/salesman">
          <!-- Sort by salesman name -->
          <xsl:sort select="@name"/>
          <!-- Keep the current salesman in a variable for later -->
          <xsl:variable name="cur" select="."/>
          <tr>
            <!-- First cell has the salesman's name -->
            <td bgcolor="yellow">
                 <xsl:value-of select="@name"/></td>
            <!-- Generate a cell for each unique region -->
            <xsl:for-each 
             select="//@region[generate-id(.)=
                    generate-id(key('b',.)[1])]">
              <td>
                <!-- If no accts for current salesman in current region, 
                   do &nbsp; -->
                <xsl:if 
                  test="not($cur/account[@region=current()])">
                        &#160;</xsl:if>
               
<!-- List matching accounts for current salesman in current region -->
                <xsl:for-each select="$cur/account[@region=current()]">
                  <xsl:value-of select="."/>
                  <xsl:if test="position() != last()"><br/></xsl:if>
                </xsl:for-each>
              </td>
            </xsl:for-each>
          </tr>
        </xsl:for-each>
      </table>
    </body>
  </html>
  </xsl:template>
</xsl:stylesheet>

3.

Making balanced two-column tables from one-column data

Mike Brown





4.17.1 

Input 
<?xml version='1.0'?>
<TASKS>
  <TASK>
    <COMPONENTS>
      <COMPONENT>A</COMPONENT>
      <COMPONENT>B</COMPONENT>
      <COMPONENT>C</COMPONENT>
      <COMPONENT>D</COMPONENT>
      <COMPONENT>E</COMPONENT>
      <COMPONENT>F</COMPONENT>
      <COMPONENT>G</COMPONENT>
      <COMPONENT>H</COMPONENT>
      <COMPONENT>I</COMPONENT>
      <COMPONENT>J</COMPONENT>
      <COMPONENT>K</COMPONENT>
    </COMPONENTS>
  </TASK>
</TASKS>

xsl

<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes"/>

<xsl:template match="/">

  <xsl:apply-templates/>
</xsl:template>
<xsl:template match="TASKS/TASK/COMPONENTS">
  <xsl:variable name="t-size" select="count(COMPONENT)"/>
  <xsl:variable name="half" select="ceiling($t-size div 2)"/>
   
<TABLE>
  <xsl:for-each select="COMPONENT[position() <= $half]">
   <xsl:variable name="here" select="position()"/>
    <TR>
      <TD><xsl:value-of select="."/></TD>
      <TD>
        <xsl:choose>
	  <xsl:when test="../COMPONENT[$here+$half]">
	    <xsl:value-of select="../COMPONENT[$here+$half]"/>
          </xsl:when>
          <xsl:otherwise></xsl:otherwise>
        </xsl:choose>
      </TD>
    </TR>
  </xsl:for-each>
</TABLE>

</xsl:template>
</xsl:stylesheet>

Result


<TABLE>
<TR>
<TD>A</TD><TD>G</TD>
</TR>
<TR>
<TD>B</TD><TD>H</TD>
</TR>
<TR>
<TD>C</TD><TD>I</TD>
</TR>
<TR>
<TD>D</TD><TD>J</TD>
</TR>
<TR>
<TD>E</TD><TD>K</TD>
</TR>
<TR>
<TD>F</TD><TD></TD>
</TR>
</TABLE>
  


    </TR>
  </xsl:for-each>
</TABLE>


4.

Balanced tables from a single list, using attribute values

Mark Eisenhut

Example of creating two column balanced table from one column data using XML element attributes. Example input

<?xml version='1.0'?>
<TASKS>
  <TASK>
    <COMPONENTS>
      <COMPONENT type="A"></COMPONENT>
      <COMPONENT type="B"></COMPONENT>
      <COMPONENT type="C"></COMPONENT>
      <COMPONENT type="D"></COMPONENT>
      <COMPONENT type="E"></COMPONENT>
      <COMPONENT type="F"></COMPONENT>
      <COMPONENT type="G"></COMPONENT>
      <COMPONENT type="H"></COMPONENT>
      <COMPONENT type="I"></COMPONENT>
      <COMPONENT type="J"></COMPONENT>
      <COMPONENT type="K"></COMPONENT>
    </COMPONENTS>
  </TASK>
</TASKS>

And the XSLT

<xsl:template match="TASKS/TASK/COMPONENTS">
  <xsl:variable name="t-size" select="count(COMPONENT)"/>
  <xsl:variable name="half" select="ceiling($t-size div 2)"/>

<TABLE border="1">
   <xsl:for-each select="COMPONENT[position() &lt;= $half]">
   <xsl:variable name="here" select="position()"/>
     <TR>
      <TD><xsl:value-of select="@type"/></TD>
      <TD>
        <xsl:choose>
              <xsl:when test="../COMPONENT[$here+$half]">
          <xsl:value-of select="../COMPONENT[$here+$half]/@type"/>
          </xsl:when>
          <xsl:otherwise></xsl:otherwise>
        </xsl:choose>
      </TD>
    </TR>
  </xsl:for-each>
</TABLE>

5.

Alternate colors on TABLE rows

Steve Muench

| Does anyone have any tips for adding a background color to every *alternate*
| TABLE row in the result tree?

There are lots of ways to do this, but the one I've settled on in the XSLT-driven database apps I build is the following:

(1) I create a CSS Stylesheet containing two CSS classes like "row0" and "row1" as follows (I shorten to "r0" and "r1"):

    # My CSS File named "Something.css"
    .r0 {background-color: #f9f9f9}
    .r1 {background-color: #f7f7e7}

(2) I create an XSLT stylesheet that creates an HTML page that links to this Something.css stylesheet:

<!-- Root template of my stylesheet -->
    <xsl:template match="/">
      <html>
        <head>
           <title>Cool XSLT App</title>
           <link rel="stylesheet" type="text/css" href="Something.css"/>
        </head>
        <body><xsl:apply-templates/></body>
      </html>
    </xsl:template>

(3) In my template that is creating HTML table rows, I use an attribute value template to alternate the *name* of the CSS class in use for that row to "toggle" between the names "r0" for even rows and "r1" for odd rows...

    <!-- Match a row of database query query results in XML -->
    <xsl:template match="ROW">
      <tr class="r{position() mod 2}">
        <xsl:apply-templates/>
      </tr>
    </xsl:template>

The expression {position() mod 2} will alternate between the values 1 and 0 so the effective value of the "class" attribute on the <tr> element I'm creating is "r1" and "r0".

This way, I can control the fonts/colors of my entire site by touching a single CSS file, while XSLT takes care of all the fancy stuff.

6.

Turning columns into Rows

Francis Norton

The column-first representation of the data is somewhat counter-intuitive, and normally unhelpful. However it's not a big deal. Here's a solution

 t.xml
 <?xml version="1.0"?>
 <root>
   <field name="field1">
     <string>field1.row1</string>
     <string>field1.row2</string>
     <string>field1.row3</string>
     <string>field1.row4</string>
   </field>
   <field name="field2">
     <string>field2.row1</string>
     <string>field2.row2</string>
     <string>field2.row3</string>
     <string>field2.row4</string>
   </field>
   <field name="field3">
     <string>field3.row1</string>
     <string>field3.row2</string>
     <string>field3.row3</string>
     <string>field3.row4</string>
   </field>
 </root>
t.xsl
 <?xml version="1.0" encoding="iso-8859-1"?>
 <xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 <!-- main body and loop -->
 <xsl:template match="/">
   <html>
     <head>
       <title>Results</title>
     </head>
     <body>
       <table>
 <!-- get horizontal - <xsl:for-each> is inside the <tr> -->
         <tr>
           <xsl:for-each select="//field">
             <th>
               <xsl:apply-templates select="@name"/>
             </th>
           </xsl:for-each>
         </tr>
 <!-- get vertical - <tr>s are inside the <xsl:for-each>  -->
         <xsl:for-each select="//field[1]/string">
           <tr>
             <xsl:call-template name="row">
               <xsl:with-param name="row-no" select="position()"/>
             </xsl:call-template>
           </tr>
         </xsl:for-each>
       </table>
     </body>
   </html>
 </xsl:template>

 <!-- do a row here - use parameter to get position() into pattern -->
 <xsl:template name="row">
   <xsl:param name="row-no"/>

   <xsl:for-each select="//field/string[position() = $row-no]">
     <td>
       <xsl:apply-templates/>
     </td>
   </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

7.

Broken tables

David Carlisle

Given

<TABROW><TT>ati</TT><COLSEP>ATI</TABROW>
<TABROW><TT>r128</TT><COLSEP>ATI Rage 128</TABROW>

Each table row, instead of tagging up the cell data, tags up the seperators!

<TABLE ALIGN="CENTER" BORDER="1">
<TABULAR CA="|l|l|">
<TABROW>Driver Name<COLSEP/></TABROW>
<TABROW><TT>apm</TT><COLSEP/>Alliance Pro Motion</TABROW>
<TABROW><TT>ati</TT><COLSEP/>ATI</TABROW>
<TABROW><TT>chips</TT><COLSEP/>Chips & Technologies</TABROW>
<TABROW><TT>cirrus</TT><COLSEP/>Cirrus Logic</TABROW>
<TABROW><TT>cyrix</TT> (*)<COLSEP/>Cyrix MediaGX</TABROW>
<TABROW><TT>fbdev</TT><COLSEP/>Linux fbdev</TABROW>
<TABROW><TT>glide</TT><COLSEP/>Glide2x (3Dfx)</TABROW>
<TABROW><TT>glint</TT><COLSEP/>3Dlabs, TI</TABROW>
<TABROW><TT>i740</TT><COLSEP/>Intel i740</TABROW>
<TABROW><TT>i810</TT><COLSEP/>Intel i810</TABROW>
<TABROW><TT>mga</TT><COLSEP/>Matrox</TABROW>
<TABROW><TT>neomagic</TT><COLSEP/>NeoMagic</TABROW>
<TABROW><TT>nv</TT><COLSEP/>NVIDIA</TABROW>
<TABROW><TT>r128</TT><COLSEP/>ATI Rage 128</TABROW>
<TABROW><TT>rendition</TT><COLSEP/>Rendition</TABROW>
<TABROW><TT>s3virge</TT><COLSEP/>S3 ViRGE</TABROW>
<TABROW><TT>sis</TT><COLSEP/>SiS</TABROW>
<TABROW><TT>tdfx</TT><COLSEP/>3Dfx</TABROW>
<TABROW><TT>tga</TT><COLSEP/>DEC TGA</TABROW>
<TABROW><TT>trident</TT><COLSEP/>Trident</TABROW>
<TABROW><TT>tseng</TT><COLSEP/>Tseng Labs</TABROW>
<TABROW><TT>vga</TT><COLSEP/>Generic VGA</TABROW>
</TABULAR>
</TABLE>
<xsl:stylesheet 
	    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0"
                >

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

<xsl:template match="TABULAR">
<tbody>
<xsl:apply-templates/>
</tbody>
</xsl:template>

<xsl:template match="TABROW">
<row>
<xsl:call-template name="entry"/>
</row>
</xsl:template>

<xsl:template match="TT">
<tt>
<xsl:apply-templates/>
</tt>
</xsl:template>

<xsl:template name="entry">
<xsl:param name="x" select="0"/>
<entry>
<xsl:apply-templates 
	    select="node()[count(preceding-sibling::COLSEP)=$x]"/>
</entry>
<xsl:if test="$x < count(COLSEP)">
<xsl:call-template name="entry">
  <xsl:with-param select="$x+1" name="x"/>
</xsl:call-template>
</xsl:if>
</xsl:template>

</xsl:stylesheet>

8.

Generating balanced tables

Warren Hedley

My xml tables don't have the same number of cells in each row. How can I convert such that the output contains empty cells for missing ones on the input.

The following stylesheet should do what you want (and does with Saxon 5.3.2). Obviously there are some optimisations possible - all of "match"ed templates could be moved into the root template, for instance - but I separated it out for clarity. Sorry, no documentation, but I think it's pretty clear what's going on - the key thing to note is that you have to use recursion to implement counting functionality in XSLT.

<?xml version="1.0" encoding="UTF-8"?>

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

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

<xsl:template match="Table">
  <xsl:variable name="num_columns">
    <xsl:call-template name="count_columns" />
  </xsl:variable>

  <table>
    <xsl:apply-templates select="HeadRow">
      <xsl:with-param name="num_columns" select="$num_columns" />
    </xsl:apply-templates>
    <xsl:apply-templates select="Row">
      <xsl:with-param name="num_columns" select="$num_columns" />
    </xsl:apply-templates>
    <xsl:apply-templates select="Caption">
      <xsl:with-param name="num_columns" select="$num_columns" />
    </xsl:apply-templates>
  </table>
</xsl:template>


<xsl:template name="count_columns">
  <xsl:param name="num_columns" select="'0'" />
  <xsl:param name="num_rows"    select="count(HeadRow) + count(Row)" />
  <xsl:param name="current_row" select="1" />

  <xsl:choose>
    <xsl:when test="$current_row < $num_rows">
      <xsl:variable name="row" select="(HeadRow | Row)[$current_row]" />
      <xsl:choose>
        <xsl:when test="count($row/Cell) > $num_columns">
          <xsl:call-template name="count_columns">
            <xsl:with-param name="num_columns" select="count($row/Cell)" />
            <xsl:with-param name="num_rows"    select="$num_rows" />
            <xsl:with-param name="current_row" select="$current_row + 1" />
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:call-template name="count_columns">
            <xsl:with-param name="num_columns" select="$num_columns" />
            <xsl:with-param name="num_rows"    select="$num_rows" />
            <xsl:with-param name="current_row" select="$current_row + 1" />
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$num_columns" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>


<xsl:template match="HeadRow">
  <xsl:param name="num_columns" />

  <thead>
    <tr>
      <xsl:for-each select="Cell">
        <td><xsl:copy-of select="*|text()" /></td>
      </xsl:for-each>
      <xsl:call-template name="insert_blank_cells">
        <xsl:with-param name="num_cells" select="$num_columns - count(Cell)" />
      </xsl:call-template>
    </tr>
  </thead>
</xsl:template>


<xsl:template match="Row">
  <xsl:param name="num_columns" />

  <tr>
    <xsl:for-each select="Cell">
      <td><xsl:copy-of select="*|text()" /></td>
    </xsl:for-each>
    <xsl:call-template name="insert_blank_cells">
      <xsl:with-param name="num_cells" select="$num_columns - count(Cell)" />
    </xsl:call-template>
  </tr>
</xsl:template>


<xsl:template match="Caption">
  <xsl:param name="num_columns" />

  <tr>
    <td colspan="{$num_columns}"><xsl:copy-of select="*|text()" /></td>
  </tr>
</xsl:template>


<xsl:template name="insert_blank_cells">
  <xsl:param name="num_cells" />

  <xsl:if test="$num_cells > 0">
    <td><xsl:value-of select="' '" /></td>
    <xsl:call-template name="insert_blank_cells">
      <xsl:with-param name="num_cells" select="$num_cells - 1" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>


</xsl:stylesheet>

9.

XSL Table Styling

Mike Brown

> What I'm trying to do is lay out two rows of data with 4 items per row
    

Generally when people ask questions about HTML tables, they've tried a tag-based approach, which inevitably fails because they are thinking about how to arbitrarily declare the beginning and end of each row. In the well-formed world of XML and XSLT, one needs a more object-oriented approach. If you break the problem down into the answers to these questions, you might see your solution more quickly:

 - What always determines when a new row is added?
 - What always goes into each row?
 - What always goes into each cell?

In your case, for each "row" of data, you're wanting to create 3 table rows. Your stylesheet is trying to create a new set of table rows for each *unit* of data (each 'hit' element that is matched), so of course you end up with too many table rows and not enough columns in your table.

I'm guessing your XML looks something like

<result>
  <hit>
    <name>Foo</name>
    <small-image>foo.jpg</small-image>
    <short-desc>This is foo.</short-desc>
  </hit>
  ...
</result>

Here is one way to approach the problem, and it is flexible for any number of desired columns with an unknown number of 'hit' elements. It will also fill in cells in empty columns, in case the number of 'hit' elements isn't evenly divisible by the number of columns desired. (untested code; watch for typos)

<xsl:template match="result">
  <xsl:variable name="cols" select="4"/>
  <xsl:variable name="all_hits" select="hit"/>
  <table>
    <!-- start a new data row for every 1st, 5th, 9th, etc. 'hit' element -->
    <xsl:for-each select="$all_hits[position() mod $cols = 1]">
      <xsl:variable name="this_hit_pos" select="position()"/>
      <xsl:variable name="current_row_hits" 
	    select="$all_hits[position() >= $this_hit_pos and position() 
	    &lt; $this_hit_pos + $cols]"/>
      <!-- go generate the 3 table rows for this one data row -->
      <xsl:call-template name="make_table_rows">
        <xsl:with-param name="cols" select="$cols"/>
        <xsl:with-param name="current_row_hits" select="$current_row_hits"/>
      </xsl:call-template>
    </xsl:for-each>
  </table>
</xsl:template>

<xsl:template name="make_table_rows">
  <xsl:param name="cols" select="1"/>
  <xsl:param name="current_row_hits" select="/.."/>
  <!-- selects above are defaults in case nothing was passed in -->
  <xsl:if test="$current_row_hits">
    <xsl:variable name="num_empty_cols" select="$cols - $current_row_hits"/>
    <tr>
      <xsl:for-each select="$current_row_hits">
        <td width="175">
          <img width="175" height="175" src="{small-image}"/>
        </td>
      </xsl:for-each>
      <xsl:if test="$num_empty_cols"> <!-- true if not zero -->
        <xsl:call-template name="make_empty_cells">
          <xsl:with-param name="num" select="$num_empty_cols"/>
        </xsl:call-template>
      </xsl:if>
    </tr>
    <tr>
      <xsl:for-each select="$current_row_hits">
        <td width="175">
          <font face="Verdana" size="2">
            <b>
              <xsl:value-of select="name"/>
            </b>
          </font>
        </td>
      </xsl:for-each>
      <xsl:if test="$num_empty_cols"> <!-- true if not zero -->
        <xsl:call-template name="make_empty_cells">
          <xsl:with-param name="num" select="$num_empty_cols"/>
        </xsl:call-template>
      </xsl:if>
    </tr>
    <tr>
      <xsl:for-each select="$current_row_hits">
        <td width="175">
          <font face="Verdana" size="1">
            <xsl:value-of select="short-desc"/>
          </font>
        </td>
      </xsl:for-each>
      <xsl:if test="$num_empty_cols"> <!-- true if not zero -->
        <xsl:call-template name="make_empty_cells">
          <xsl:with-param name="num" select="$num_empty_cols"/>
        </xsl:call-template>
      </xsl:if>
    </tr>
  </xsl:if>
</xsl:template>

<xsl:template name="make_empty_cells">
  <xsl:param name="num" select="0"/>
  <xsl:if test="$num">
    <td>&#160;</td>
    <xsl:call-template name="make_empty_cells">
      <xsl:with-param name="num" select="$num - 1"/>
    </xsl:call-template>
  </xsl:if>
</xsl:template>

10.

Output two diff. elements side by side in HTML

Jeni Tennison

>i.e. The text should flow around the image which can come, once on the
>left side, the second time, on the right side. The whole thing is inside
>a table and the images and paragraphs are cells.
    

I'm not exactly sure what the table you're after looks like, so I'm going to assume it's something like:

<table>
  <tr>
    <td>image-1</td>
    <td colspan="2">paragraph-1</td>
  </tr>
  <tr>
    <td colspan="2">paragraph-2</td>
    <td>image-2</td>
  </tr>
  <tr>
    <td colspan="3">paragraph-3</td>
  </tr>
  ...
</table>

To make it easy to find the images, I'm going to define a global variable to hold them:

<xsl:variable name="images" select="/story/image" />

You need a template that matches something high up, either the root node (/) or the document element (story) to manage the processing (limiting it to the paragraphs) and to put in the HTML that wraps around your table:

<xsl:template match="story">
  <html>
    <head><title>A story</title></head>
    <body>
      <table>
        <xsl:apply-templates select="paragraph" />
      </table>
    </body>
  </html>
</xsl:template>

The paragraph-matching template needs to insert a row that includes the paragraph into the table. If there is an image with the equivalent position, it also needs to insert the image into the table. It can find such an image, if there is one by finding the image within the $images variable at the position of the current paragraph:

  <xsl:variable name="position" select="position()" />
  <xsl:variable name="image" select="$images[$position]" />

If it is inserting an image, and it's an odd paragraph, the image needs to go in the first cell; if it's an even paragraph, the image needs to go in the second cell. You can find out whether a paragraph is even or odd by looking at the result of the position of the paragraph mod 2. This kind of conditional processing is best managed using xsl:choose, xsl:when and xsl:otherwise. I've nested it here for clarity:

<xsl:template match="paragraph">
  <xsl:variable name="position" select="position()" />
  <xsl:variable name="image" select="$images[$position]" />
  <xsl:choose>
    <xsl:when test="$image">
      <xsl:choose>
        <xsl:when test="$position mod 2 = 1">
          <tr>
            <td><img src="{$image}" /></td>
            <td colspan="2"><xsl:value-of select="." /></td>
          </tr>
        </xsl:when>
        <xsl:otherwise>
          <tr>
            <td colspan="2"><xsl:value-of select="." /></td>
            <td><img src="{$image}" /></td>
          </tr>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise>
      <tr><td colspan="3"><xsl:value-of select="." /></td></tr>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Using SAXON, this gives the output I was aiming for with the source that you provided. I hope that it's close enough to what you want to give you an idea about how to proceed.

11.

Multiple Rows in a Table

Mike Brown


> I am trying to create a Calendar table with a new row after every 7th day.
> Here is the XML I'm working with:
> 
> <Month>            
>             <day date="1" />
>             <day date="2"/>
>             <day date="3" />
> [...]
>             <day date="31"/>
> </Month>  

First I would constrain myself to a good template-based design pattern. Once you've determined what days you're processing, any given day in the set is going to be treated the same way, i.e. it gets a cell with the date in it:

<xsl:template match="day">
  <td><xsl:value-of select="@date"/></td>
</xsl:template>

The trick is to choose the right days to process. You were on the right track by looking at every 7th day. When processing each of those days, you want to go find the next 6 to complete the row.

Be careful about the use of mod and position(); the first node is at position 1, and you want positions 1, 8, 15, etc., not 0, 7, 14 -- so it would be position() mod 7 = 1.

<xsl:template match="month">
  <xsl:for-each select="day[position() mod 7 = 1]">
    <tr>
      <xsl:apply-templates select=". | following-sibling::day[position() &lt; 7]"/>
    </tr>
  </xsl;for-each>
</xsl:template>

Your relatively flat schema lends itself well to the use of following-sibling. If it were not so flat, another option is to get the following 6 day elements by their position in the set of all day elements:

<xsl:template match="month">
  <xsl:variable name="currentmonth" select="."/>
  <xsl:for-each select="day[position() mod 7 = 1]">
    <xsl:variable name="pos" select="position()"/>
    <tr>
      <xsl:apply-templates 
	    select="$currentmonth/day[position() 
	    &gt;= $pos and position() &lt; $pos + 7]"/>
    </tr>
  </xsl;for-each>
</xsl:template>
However this would be wasteful in your case.

One thing to note is that HTML tables need to have all cell spaces accounted for. If there are fewer than 7 days in the last set to process, you'll be left with some unfilled cells. So before that </tr> I would add something like this to make one big last cell:

<xsl:if test="last() &lt; position() + 6">
  <td colspan="{position() + 6 - last()}">&#160;</td>
</xsl:if>

or this plus the named template below:

<xsl:if test="last() &lt; position() + 6">
  <xsl:call-template name="make_empty_cells">
    <xsl:with-param name="count" select="position() + 6 - last()"/>
  </xsl:call-template>
</xsl:if>

<xsl:template name="make_empty_cells">
  <xsl:param name="count"/>
  <xsl:if test="$count">
    <td>&#160;</td>
    <xsl:call-template name="make_empty_cells">
      <xsl:with-param name="count" select="$count - 1"/>
    </xsl:call-template>
  </xsl:if>
</xsl:template>

Mike Kay offers

The basic principle is to structure template rules according to the output structure, not the input structure. You want a rule that generates a <week> element containing seven <day> elements? Write it like this:

<xsl:template match="day[position() mod 7 = 0]"/>
<week>
  <xsl:for-each select=". | following-sibling::day[position() &lt; 7]">
    <day><xsl:value-of select="@date"/></day>
  </xsl:for-each>
</week>
</xsl:template>

<xsl:template match="day"/> <!-- ignore the other days -->

12.

Retriving next 10 elements

Jeni Tennison


>   we are using Xml with Xsl for presentaion.In this our requirment is ,If
>we are getting 100 results,in the xml format we want to display 10 at a time
>without going to the database again.I think the xsl should be able to take
>all the 100 results and display 10 each time.please suggest with code if
>possible.

Since you don't give any example XML to work with, I'm going to give a general answer with a silly example that is:

<list>
  <item>1</item>
  <item>2</item>
  ...
</list>

Note that in this example all the items that you're interested in appear on the same level: they're siblings. There is a more general solution where this isn't the case, but this *is* the case in the vast majority of situations where people want to group by position, as far as I can tell.

The design pattern for doing this has two stages: 1. select the first of the group 2. during the processing of the first of the group, select the rest of the group

Selecting the first of the group involves selecting those nodes that have a position in the set that, when 'mod'ed with the number of elements per group, evaluates to 1, so generally:

  $nodes[position() mod $group-size = 1]

or with my example:

  item[position() mod 10 = 1]

When you've identified the first of a group, finding the rest of the group is a matter of looking for the next n siblings, where n is the size of the group minus 1 or, in other words, those that have a position in the set of following siblings that is less than the size of the group. The group consists of the node you've got (the first in the group) and the next n nodes:

  . | following-sibling::*[position() &lt; $group-size]

or with my example:

  . | following-sibling::item[position() &lt; 10]

You can do the actual processing with either xsl:apply-templates or with xsl:for-each. With xsl:apply-templates it looks like:

<xsl:template match="list">
  <xsl:apply-templates select="item[position() mod 10 = 1]"
                       mode="group" />
</xsl:template>

<xsl:template match="item" mode="group">
  <xsl:apply-templates
      select=". | following-sibling::item[position() &lt; 10]" />
</xsl:template>

With xsl:for-each it looks like:

<xsl:template match="list">
  <xsl:for-each select="item[position() mod 10 = 1]">
    <xsl:apply-templates
        select=". | following-sibling::item[position() &lt; 10]" />
  </xsl:for-each>
</xsl:template>

Generally, I would keep the size of the groups that you wanted to generate in a separate variable or, better, a global parameter, that would allow you to vary it later on, either by changing the stylesheet at one place or by passing a different value for the parameter into the stylesheet:

<xsl:param name="group-size" select="10" />

13.

Colouring table cells

David Carlisle

If there really is only a fixed range, you'll probably have an easier time with a lookup table than hex arithmetic, something like

<xsl:template match="parent">
<xxx bgcolor="{document('')/xsl:stylesheet/x:colors[position() = current()/@level]}>
...

where your stylesheet has

<xsl:stylesheet ...
  xmlns:x="data:anything">

<x:colors>#FFFFFF</x:colors>
<x:colors>#FFFFBB</x:colors>
<x:colors>#FFFF77</x:colors>
...

with whatever 10 values you want.

On the other hand, if you specify the function that maps your level range to your required colour gradient, it would be possible....

14.

Splitting up tables

Oliver Becker

> I have the following structure
> <TABLE>
>   <HEADERROW>
> 	<HEADER>a</HEADER> 
>        <HEADER>b</HEADER> 
>        <HEADER>c</HEADER>
>   </HEADERROW>
>   <DATAROWS>	
>      <DATAROW>
> 	<DATA>1</DATA> <DATA>1</DATA> <DATA>1</DATA>
>      </DATAROW>
>      <DATAROW>
> 	<DATA>2</DATA> <DATA>2</DATA> <DATA>2</DATA>
>      </DATAROW>
... [snip]
>   </DATAROWS>
> </TABLE>

> 
> What I need is to generate three tables from the same XML each with its own
> table header.
> 
> First table would (arbitrarily) have 5 consective rows (rows 1-5).
> Second table would have 2 consecutive rows (6-7).
> Third table would have 2 consective rows (8-9).

The trick is to select the rows you want to process with an XPath expression using the context position of each row. E.g. the first table should contain all the rows that have a position greater or equal 1 and lower or equal 5:

<xsl:apply-templates select="DATAROW[position() &gt;= 1 and
                                           position() &lt;= 5]" />

Now you have to take care to create the table headers at the appropriate position in your stylesheet: first skip them, but afterwards process them explicitly.

I hope the following complete stylesheet shows what I shortly tried to explain:

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

<xsl:template match="TABLE">
   <!-- skip HEADERROW -->
   <xsl:apply-templates select="DATAROWS" />
</xsl:template>

<xsl:template match="DATAROWS">
   Table 1:
   <table>
      <xsl:apply-templates select="preceding-sibling::HEADERROW" />
      <xsl:apply-templates select="DATAROW[position() &gt;= 1 and
                                           position() &lt;= 5]" />
   </table>
   Table 2:
   <table>
      <xsl:apply-templates select="preceding-sibling::HEADERROW" />
      <xsl:apply-templates select="DATAROW[position() &gt;= 6 and
                                           position() &lt;= 7]" />
   </table>
   Table 3:
   <table>
      <xsl:apply-templates select="preceding-sibling::HEADERROW" />
      <xsl:apply-templates select="DATAROW[position() &gt;= 8 and
                                           position() &lt;= 9]" />
   </table>
</xsl:template>

<xsl:template match="HEADERROW | DATAROW">
   <tr><xsl:apply-templates /></tr>
</xsl:template>

<xsl:template match="HEADER">
   <th><xsl:value-of select="."/></th>
</xsl:template>

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

</xsl:stylesheet>

15.

Alternate bg colors for table and address summaries

Jeni Tennison


>I want to have one summary return with a grey background and the other 
>summary with a white background.
	

You want to have the two summaries in different colours. The place where you set the colour of the summary is within @STYLE attribute of the DIV element:

>      
<DIV STYLE="background-color:#FFFFFF; color:white; padding:4px">

Now, for the first summary, you want a gray background:

  STYLE="background-color:gray; color:white; padding:4px"

And for the second summary, you want a white background:

  STYLE="background-color:white; color:white; padding:4px"

To change the value of an attribute dependent on a condition, the best thing to do is to use xsl:attribute to create the attribute, and then xsl:choose to determine what the content should be inside it:

<DIV>
  <xsl:attribute name="STYLE">
    <xsl:choose>
      <xsl:when test="...">background-color:gray; </xsl:when>
      <xsl:otherwise>background-color:white; </xsl:otherwise>
    </xsl:choose>
    <xsl:text>color:white; padding:4px</xsl:text>
  </xsl:attribute>
  ...
</DIV>

The difficulty that it sounds like you were facing is what the 'test' should be - how to determine whether the background colour should be gray or white. At the point at which this test is made, you're within an xsl:for-each; the current node is a Summary, and the current node list contains all the Summary elements that are children of Address-Summaries within the document. This means you can tell whether the current node (a Summary) is the first Summary element in the list or not by checking its position():

  test="position() = 1"

If you have lots of Summary elements and you want to alternate colours odd and even, then you can check whether a Summary element is in an odd position using mod:

   test="position() mod 2 = 1"

So, try:

<DIV>
  <xsl:attribute name="STYLE">
    <xsl:choose>
      <xsl:when test="position() mod 2 = 1">background-color:gray; </xsl:when>
      <xsl:otherwise>background-color:white; </xsl:otherwise>
    </xsl:choose>
    <xsl:text>color:white; padding:4px</xsl:text>
  </xsl:attribute>
  ...
</DIV>

16.

Balancing the number of cells in a table

Steve Muench

Here's a solution that does what I think you're trying to achieve (generate an appropriate colspan="xxx" for rows that have fewer than the max(cells) number of cells)

Given your input:

<table>
   <row>
    <cell>...</cell>
   </row>
   <row>
    <cell>...</cell>
    <cell>...</cell>
    <cell>...</cell>
   </row>
   <row>
    <cell>...</cell>
    <cell>...</cell>
   </row>
</table>

the stylesheet below produces the output:

<html>
   <body>
      <table>
         <tr>
            <td colspan="3">...</td>
         </tr>
         <tr>
            <td>...</td>
            <td>...</td>
            <td>...</td>
         </tr>
         <tr>
            <td>...</td>
            <td colspan="2">...</td>
         </tr>
      </table>
   </body>
</html>

Here's the stylesheet:

<x:stylesheet 
	  xmlns:x="http://www.w3.org/1999/XSL/Transform"  version="1.0">
  <!-- Compute the max(count(cell)) for all <row> elements -->
  <x:variable name="maxcells">
    <x:for-each select="/table/row">
      <x:sort data-type="number" 
	  order="descending" select="count(cell)"/>
      <x:if test="position()=1"><x:value-of 
	  select="count(cell)"/></x:if>
    </x:for-each>
  </x:variable>
  <x:template match="cell">
    <td>
      <!-- If we're processing the last cell and $maxcells is greater -->
      <x:if test="position()=last() and $maxcells > position()">
        <x:attribute name="colspan">
          <x:value-of select="$maxcells - position() + 1"/>
        </x:attribute>
      </x:if>
      <x:apply-templates/>
    </td>
  </x:template>
  <x:template match="/">
    <html><body>
      <x:apply-templates/>
    </body></html>
  </x:template>
  <x:template match="table">
    <table>
      <x:apply-templates select="row"/>
    </table>
  </x:template>
  <x:template match="row">
    <tr>
      <x:apply-templates select="cell"/>
    </tr>
  </x:template>
</x:stylesheet>

17.

How to find the Maximum number of cells in a row

Mike Kay



> I need to compare the number of cells in each row element and 
> get the count
> of cell elements in the row that has the most 

	

there's no built in max and min functions; alternatives are:

sort, and find the first/last
recursive template
extension functions saxon:max(), saxon:min()

Doing (a), with <table> as current node:

<xsl:variable name="maxcells">
  <xsl:for-each select="row"><xsl:sort 
                                 select="count(cell)"
                                 order="descending"/>
    <xsl:if test="position()=1"><xsl:value-of
          select="count(cell)"/></xsl:if>
  </xsl:for-each>
</xsl:variable>

18.

Table from ADO

James Newman

I have just been given the task at my company to look in XSLT and SQL server. I had run into a brick wall when it came to producing a general HTML table from the output of an ADO object (recordset.save XX, adXMLPersist) because all the attributes are specified under a separate node <s:Schema> but the actual data is in the form <row fieldName1="XX" fieldName2="YY"/>.

The problem being that not all fields are passed back as attributes to the row node but are omitted under certain circumstances.

I had an example working but the time to transform was a product of rows, fields requested and fields actual present. I was able to find a much neater (and quicker) solution. Anyway I'm sure there are better solutions but I thought I'd send you my stylesheet just to say thank you because it now handles any standard "select * from TABLE" SQL statement.

THE STYLESHEET

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema" 
xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882">

<xsl:template match="/">
 <HTML>
  <BODY>
    <h1>Collected <xsl:value-of 
	  select="count(//rs:data/z:row)"/> records</h1>
    <table rules="all">
     <tr>
	<td>Rec Num</td>
	<xsl:apply-templates 
		select="xml/s:Schema/s:ElementType/s:AttributeType"/>
     </tr>
     <xsl:call-template name="rows">
	<xsl:with-param name="cnum" select="1"/>
     </xsl:call-template>
    </table>
   </BODY>
  </HTML>
</xsl:template>

<xsl:template name="rows">
 <xsl:param name="cnum"/>

	<!-- row exists then carry on -->
   <xsl:if test="xml/rs:data/z:row[$cnum]">
	<tr>
	 <td><xsl:value-of select="$cnum"/></td>
	<!-- go through all the attributes (i.e. fields) defined -->
	  <xsl:for-each 
               select="xml/s:Schema/s:ElementType/s:AttributeType">
	     <td>
<!-- set variable to be value of attribute we will search for -->
		<xsl:variable name="myTest" select="@name"/>
<!-- insert value of X attribute, if doesn't exist get blank -->
		 <b><xsl:value-of 
	select="/xml/rs:data/z:row[$cnum]/@*[name() = $myTest]"/></b>

<!-- add blank to stop <td/> tag, bad in netscape apparently -->
	<xsl:text> </xsl:text>
		</td>
	 </xsl:for-each>
	</tr>
<!-- do again with next record -->
	<xsl:call-template name="rows">
	 <xsl:with-param name="cnum" select="$cnum + 1"/>
	</xsl:call-template>
  </xsl:if>
</xsl:template>


<!-- Insert headers -->
<xsl:template match="s:AttributeType">
	<th><xsl:value-of select="@name"/></th>
</xsl:template>

</xsl:stylesheet>

THE DATA FROM ADO

<?xml-stylesheet type="text/xsl" href="sample.xsl"?>
<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" 
xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" 
xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
<s:Schema id="RowsetSchema">
 <s:ElementType name="row" content="eltOnly">
   <s:AttributeType name="field1" 
		rs:number="1" rs:nullable="true" 
		rs:writeunknown="true">
     <s:datatype dt:type="string" rs:dbtype="str" dt:maxLength="20"/>
   </s:AttributeType>
   <s:AttributeType name="field2" rs:number="2" 
   		    rs:nullable="true" 
                    rs:writeunknown="true">
	<s:datatype dt:type="string" rs:dbtype="str" dt:maxLength="40"/>
    </s:AttributeType>
    <s:AttributeType name="field3" rs:number="3" 
    	rs:nullable="true" 
	rs:writeunknown="true">
       <s:datatype dt:type="string" rs:dbtype="str" dt:maxLength="20"/>
     </s:AttributeType>
     <s:extends type="rs:rowbase"/>
  </s:ElementType>
</s:Schema>
<rs:data>
 <z:row field1="XX" field2="YY"/>
</rs:data>

19.

Cals table stylesheet

Andrew Welch

For anyone working with CALS tables, here is a stylesheet I wrote a while back to display them. I think it covers all of the variations that CALS allows (which is a lot).

It will produce a table 300px high, with a 'fixed header' so that the body scrolls while the head is fixed.

It just requires that 't' 'b' 'l' and 'r' are defined in your CSS as 1px solid black borders.

Im confident that it can take whatever gets thrown at it, but if anyone can improve it (and repost :) or find a fault, please let me know.

(Ive just removed some things specific to my company, so if some of the html structure looks slightly off, its probably because Ive just deleted some it)

<xsl:stylesheet
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
	extension-element-prefixes="exsl"
	version="1.0">

<xsl:param name="print" select="'no'"/>
<xsl:param name="printWidth" select="0"/>

<!-- tests for aircrew dm, we dont use scroll bars on aircrew dm's-->
<xsl:variable name="acrw" select="boolean(/dmodule/content/acrw)"/>

<xsl:variable name="colspecs-firstPass-rtf">
  <xsl:for-each select="//table">
    <table id="{generate-id()}">
      <xsl:for-each select="tgroup">
        <tgroup id="{generate-id()}">
          <xsl:for-each select=".//colspec">
            <col id="{generate-id()}" colname="{@colname}"
number="{count(preceding-sibling::colspec|.)}"
width="{translate(@colwidth,'inm*','')}" align="{@align}">
              <xsl:attribute name="inHead">
                <xsl:choose>
                  <xsl:when test="parent::thead">yes</xsl:when>
                  <xsl:otherwise>no</xsl:otherwise>
                </xsl:choose>
              </xsl:attribute>
            </col>
          </xsl:for-each>
          <xsl:for-each select="spanspec">
            <spanspec id="{generate-id()}" spanname="{@spanname}"
namest="{@namest}" nameend="{@nameend}" align="{@align}"/>
          </xsl:for-each>
        </tgroup>
      </xsl:for-each>
    </table>
  </xsl:for-each>
</xsl:variable>

<xsl:variable name="colspecs-firstPass"
select="exsl:node-set($colspecs-firstPass-rtf)"/>

<xsl:variable name="colspecs-rtf">
  <xsl:for-each select="$colspecs-firstPass">
    <xsl:for-each select="table">
      <table id="{@id}">
        <xsl:for-each select="tgroup">
          <tgroup id="{@id}">
            <xsl:variable name="totalGroupWidth" select="sum(col[@inHead =
'no']/@width)"/>
            <xsl:variable name="totalHeadWidth" select="sum(col[@inHead =
'yes']/@width)"/>
            <xsl:for-each select="col">
              <col id="{@id}" colname="{@colname}" number="{@number}"
align="{@align}" inHead="{@inHead}">
                <xsl:attribute name="width">
                  <xsl:choose>
                    <xsl:when test="@inHead = 'no'">
                      <xsl:value-of select="@width div $totalGroupWidth *
100"/>
                    </xsl:when>
                    <xsl:otherwise>
                      <xsl:value-of select="@width div $totalHeadWidth *
100"/>
                    </xsl:otherwise>
                  </xsl:choose>
                </xsl:attribute>
              </col>
            </xsl:for-each>
            <xsl:for-each select="spanspec">
              <xsl:copy-of select="."/>
            </xsl:for-each>
          </tgroup>
        </xsl:for-each>
      </table>
    </xsl:for-each>
  </xsl:for-each>
</xsl:variable>

<xsl:variable name="colspecs" select="exsl:node-set($colspecs-rtf)"/>

<xsl:variable name="spanspecs-rtf">
  <xsl:for-each select="$colspecs">
    <xsl:for-each select="table/tgroup">
      <xsl:variable name="tgroupId" select="@id"/>
      <xsl:for-each select="spanspec">
        <col id="{@id}" spanname="{@spanname}">
          <xsl:attribute name="colspan">
            <xsl:value-of select="1 + preceding-sibling::col[@colname =
current()/@nameend]/@number - preceding-sibling::col[@colname =
current()/@namest]/@number"/>
          </xsl:attribute>
          <xsl:attribute name="width">
            <xsl:call-template name="calculateWidth">
              <xsl:with-param name="tgroupId" select="$tgroupId"/>
              <xsl:with-param name="startCol"
select="preceding-sibling::col[@colname = current()/@namest]/@number"/>
              <xsl:with-param name="endCol"
select="preceding-sibling::col[@colname = current()/@nameend]/@number"/>
            </xsl:call-template>
          </xsl:attribute>
          <xsl:attribute name="align">
            <xsl:choose>
              <xsl:when test="string-length(@align)"><xsl:value-of
select="@align"/></xsl:when>
              <xsl:otherwise><xsl:value-of
select="preceding-sibling::col[@colname =
current()/@nameend]/@align"/></xsl:otherwise>
            </xsl:choose>
          </xsl:attribute>
        </col>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:for-each>
</xsl:variable>

<xsl:variable name="spanspecs" select="exsl:node-set($spanspecs-rtf)"/>

<xsl:template name="calculateWidth">
  <xsl:param name="tgroupId" select="0"/>
  <xsl:param name="startCol" select="0"/>
  <xsl:param name="endCol" select="0"/>
  <xsl:param name="totalWidth" select="0"/>
  <xsl:choose>
    <xsl:when test="$startCol &lt;= $endCol">
      <xsl:call-template name="calculateWidth">
        <xsl:with-param name="tgroupId" select="$tgroupId"/>
        <xsl:with-param name="startCol" select="$startCol + 1"/>
        <xsl:with-param name="endCol" select="$endCol"/>
        <xsl:with-param name="totalWidth">
          <xsl:for-each select="$colspecs">
            <xsl:for-each select="table/tgroup[@id = $tgroupId]">
              <xsl:value-of select="col[@number = $startCol]/@width +
$totalWidth"/>
            </xsl:for-each>
          </xsl:for-each>
        </xsl:with-param>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$totalWidth"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="getBorders">
  <xsl:param name="frame" select="''"/>
  <xsl:choose>
    <xsl:when test="$frame = 'ALL'">t b l r</xsl:when>
    <xsl:when test="$frame = 'TOP'">t</xsl:when>
    <xsl:when test="$frame = 'TOPBOT'">t b</xsl:when>
    <xsl:when test="$frame = 'BOTTOM'">b</xsl:when>
    <xsl:when test="$frame = 'SIDES'">l r</xsl:when>
    <xsl:when test="$frame = 'NONE'">t b l r</xsl:when>
    <xsl:otherwise>t b l r</xsl:otherwise>
  </xsl:choose>
</xsl:template>


<xsl:template match="table">
  <xsl:variable name="tableFrame">
     <xsl:call-template name="getBorders">
       <xsl:with-param name="frame" select="@frame"/>
     </xsl:call-template>
  </xsl:variable>

  <div>
    <xsl:apply-templates>
      <xsl:with-param name="tableFrame" select="$tableFrame"/>
    </xsl:apply-templates>
  </div>
</xsl:template>

<xsl:template match="tgroup">
  <xsl:param name="tableFrame"/>
  <xsl:variable name="tgroupId" select="generate-id()"/>
  <!--When the table is dead on 300 high IE can get caught in a dependency
loop if we set the 'else'
      value to 'scrollHeight', so instead we use the dummy string 'nause' to
do nothing, because it
      was a right nause finding and fixing the bug... -->
  <div class="{$tableFrame}">
    <xsl:attribute name="style">
      <xsl:text>width:100%;</xsl:text>
      <xsl:if 
 test="$print = 'no'">overflow:auto;height:expression(scrollHeight >= 300 ? 300 :
'nause');</xsl:if>
    </xsl:attribute>
    <xsl:if test="$print = 'no' and .//thead">
      <xsl:attribute
name="onscroll">nextSibling.scrollLeft=scrollLeft</xsl:attribute>
    </xsl:if>
   <table cellpadding="3" cellspacing="0" style="width:100%">
     <colgroup>
       <xsl:for-each select="$colspecs">
         <xsl:for-each select="table/tgroup[@id = $tgroupId]/col[@inHead =
'no']">
           <col width="{@width}%"/>
         </xsl:for-each>
       </xsl:for-each>
     </colgroup>
     <xsl:if test="thead">
     <thead>
     <xsl:for-each select="thead/row">
       <tr>
         <xsl:apply-templates select="entry">
           <xsl:with-param name="inHead" select="'true'"/>
           <xsl:with-param name="fakey" select="'true'"/>
           <xsl:with-param name="tgroupId" select="$tgroupId"/>
         </xsl:apply-templates>
       </tr>
     </xsl:for-each>
     </thead>
     </xsl:if>

     <tbody>
     <xsl:apply-templates select="tbody">
       <xsl:with-param name="tgroupId" select="$tgroupId"/>
     </xsl:apply-templates>
     </tbody>
   </table>
  </div>
  <xsl:if test="$print = 'no' and .//thead">
  <div class="t b {$tableFrame}">
    <xsl:attribute name="style">
      <xsl:text>position:relative;
 
background-color:expression(document.body.currentStyle.backgroundColor);
                overflow:hidden;
 
top:expression(-previousSibling.clientHeight-((previousSibling.scrollWidth>p
reviousSibling.clientWidth)?18:2));</xsl:text>
      <xsl:choose>
        <!--take into account border widths-->
        <xsl:when test="contains($tableFrame,'l
r')">width:expression(previousSibling.clientWidth + 2)</xsl:when>
 
<xsl:otherwise>width:expression(previousSibling.clientWidth)</xsl:otherwise>
      </xsl:choose>
    </xsl:attribute>
   <div style="width:expression(parentNode.previousSibling.scrollWidth)">
    <table cellSpacing="0" cellpadding="3" style="width:100%">
    <xsl:variable name="genIdh" select="generate-id()"/>
     <colgroup>
       <xsl:for-each select="$colspecs">
         <xsl:choose>
           <xsl:when test="table/tgroup[@id = 
          $tgroupId]/col[@inHead = 'yes']">
             <xsl:for-each 
      select="table/tgroup[@id = $tgroupId]/col[@inHead = 'yes']">
               <col width="{@width}%"/>
             </xsl:for-each>
           </xsl:when>
           <xsl:otherwise>
             <xsl:for-each 
          select="table/tgroup[@id = $tgroupId]/col[@inHead= 'no']">
               <col width="{@width}%"/>
             </xsl:for-each>
           </xsl:otherwise>
         </xsl:choose>
       </xsl:for-each>
     </colgroup>
     <thead>
    <xsl:for-each select="thead/row">
     <tr>
       <xsl:apply-templates select="entry">
         <xsl:with-param name="inHead" select="'true'"/>
         <xsl:with-param name="tgroupId" select="$tgroupId"/>
       </xsl:apply-templates>
     </tr>
    </xsl:for-each>
    </thead>
   </table>
  </div>
  </div>
  </xsl:if>
</xsl:template>

<xsl:template match="tbody">
  <xsl:param name="tgroupId" select="''"/>
  <xsl:apply-templates>
    <xsl:with-param name="tgroupId" select="$tgroupId"/>
  </xsl:apply-templates>
</xsl:template>

<xsl:template match="row|caprow">
  <xsl:param name="tgroupId" select="''"/>
  <xsl:param name="inHead" select="'false'"/>
  <xsl:if test="@id"><a><xsl:attribute name="name"><xsl:value-of
        select="@id"/></xsl:attribute></a></xsl:if>
  <tr>
    <xsl:apply-templates>
       <xsl:with-param name="tgroupId" select="$tgroupId"/>
       <xsl:with-param name="inHead" select="$inHead"/>
    </xsl:apply-templates>
  </tr>
</xsl:template>

<xsl:template match="entry|capentry">
  <xsl:param name="inHead" select="'false'"/>
  <xsl:param name="fakey" select="'false'"/>
  <xsl:param name="tgroupId" select="'no tgroup id generated'"/>
  <xsl:if test="@id"><a><xsl:attribute 
  name="name"><xsl:value-of 
    select="@id"/></xsl:attribute></a></xsl:if>
  <td>
    <xsl:if test="@morerows">
      <xsl:attribute name="rowspan">
        <xsl:value-of select="@morerows + 1"/>
      </xsl:attribute>
    </xsl:if>

    <xsl:choose>
      <xsl:when test="@spanname">
      <xsl:variable name="spanname" select="@spanname"/>
      <xsl:for-each select="$spanspecs">
        <xsl:attribute name="colspan">
          <xsl:value-of select="col[@spanname = $spanname]/@colspan"/>
        </xsl:attribute>
        <xsl:if test="$inHead = 'false'">
          <xsl:attribute name="width">
            <xsl:value-of 
              select="col[@spanname = $spanname]/@width"/>%<xsl:text/>
          </xsl:attribute>
        </xsl:if>
        <xsl:attribute name="align">
          <xsl:value-of select="col[@spanname = $spanname]/@align"/>
        </xsl:attribute>
      </xsl:for-each>
      </xsl:when>
      <xsl:otherwise>
       <xsl:if test="@namest">
        <xsl:variable name="nameStart" select="@namest"/>
        <xsl:variable name="nameEnd" select="@nameend"/>
        <xsl:variable name="nameStartId"
         select="generate-id(ancestor::tgroup/colspec[@colname = $nameStart])"/>
        <xsl:variable name="nameEndId"
         select="generate-id(ancestor::tgroup/colspec[@colname = $nameEnd])"/>
        <xsl:attribute name="colspan">
          <xsl:for-each select="$colspecs">
            <xsl:value-of select="1 + table/tgroup[@id = $tgroupId]/col
           [@id  = $nameEndId]/@number - table/tgroup
           [@id =  $tgroupId]/col[@id = $nameStartId]/@number"/>
          </xsl:for-each>
        </xsl:attribute>
        <xsl:if test="$inHead = 'false'">
        <xsl:attribute name="width">
          <xsl:call-template name="calculateWidth">
            <xsl:with-param name="tgroupId" select="$tgroupId"/>
            <xsl:with-param name="startCol">
              <xsl:for-each select="$colspecs">
                 <xsl:value-of select="table[@id = 
              $tgroupId]/col[@id = $nameStartId]/@number"/>
              </xsl:for-each>
            </xsl:with-param>
            <xsl:with-param name="endCol">
              <xsl:for-each select="$colspecs">
                 <xsl:value-of select="table[@id = 
                    $tgroupId]/col[@id = $nameEndId]/@number"/>
              </xsl:for-each>
            </xsl:with-param>
          </xsl:call-template>
          <xsl:text>%</xsl:text>
        </xsl:attribute>
        </xsl:if>
      </xsl:if>
      </xsl:otherwise>
    </xsl:choose>

    <xsl:variable name="colname" select="@colname"/>
    <xsl:for-each select="$colspecs">
      <xsl:if test="table/tgroup[@id = $tgroupId]/col[@colname =
                 $colname]/@align">
          <xsl:attribute name="align">
            <xsl:value-of 
      select="table/tgroup[@id = $tgroupId]/col[@colname = $colname]/@align"/>
          </xsl:attribute>
      </xsl:if>
    </xsl:for-each>

    <xsl:if test="@align">
      <xsl:attribute name="align">
        <xsl:value-of select="@align"/>
      </xsl:attribute>
    </xsl:if>

    <xsl:attribute name="valign">
      <xsl:choose>
        <xsl:when test="@valign"><xsl:value-of select="@valign"/></xsl:when>
        <xsl:otherwise>top</xsl:otherwise>
      </xsl:choose>
    </xsl:attribute>
    <xsl:attribute name="class">
      <xsl:if test="@rowsep='1' or (parent::*/@rowsep='1' and 
            $inHead = 'false')">b </xsl:if>
      <xsl:if test="@colsep='1' and following-sibling::entry">r </xsl:if>
      <xsl:if test="$inHead = 'true'">bold </xsl:if>
      <xsl:if test="$inHead = 'true' and $print != 'no'">b </xsl:if>
    </xsl:attribute>
    <xsl:if test="$fakey = 'true'">
      <xsl:attribute name="searchChar">`</xsl:attribute>
    </xsl:if>
    <xsl:apply-templates/>
    <span>
      <xsl:if test="$fakey = 'true'">
        <xsl:attribute name="searchChar">£</xsl:attribute>
      </xsl:if>
      <xsl:if test=". = ''"><xsl:text>##160;</xsl:text></xsl:if>
    </span>
  </td>
 </xsl:template>

<xsl:template match="capgrp">
  <xsl:variable name="widths-rtf">
    <xsl:for-each select="colspec">
      <col>
        <xsl:attribute name="width"><xsl:value-of
            select="translate(@colwidth,'inm*','')"/></xsl:attribute>
      </col>
    </xsl:for-each>
  </xsl:variable>
  <xsl:variable name="widths" select="exsl:node-set($widths-rtf)"/>
  <p>
    <xsl:attribute name="align"><xsl:value-of
             select="@align"/></xsl:attribute>
    <table style="width:100%">
    <colgroup>
     <xsl:for-each select="$widths">
       <xsl:variable name="total" select="sum(col/@width)"/>
       <xsl:for-each select="col">
         <col>
           <xsl:attribute name="width"><xsl:value-of 
          select="(@width div $total) * 100"/>%</xsl:attribute>
         </col>
       </xsl:for-each>
     </xsl:for-each>
    </colgroup>
    <xsl:apply-templates/>
   </table>
  </p>
</xsl:template>



<xsl:template name="colspanWidth">
  <xsl:param name="startCol" select="0"/>
  <xsl:param name="endCol" select="0"/>
  <xsl:param name="totalWidth" select="0"/>
  <xsl:param name="tgroupId" select="0"/>
  <xsl:choose>
    <xsl:when test="$startCol != $endCol">
      <xsl:call-template name="colspanWidth">
        <xsl:with-param name="startCol">
          <xsl:for-each select="$colspecs">
            <xsl:value-of select="table/tgroup[@id =
        $tgroupId]/col[preceding-sibling::col[1][@colname = $startCol]]/@colname"/>
          </xsl:for-each>
        </xsl:with-param>
        <xsl:with-param name="endCol" select="$endCol"/>
        <xsl:with-param name="tgroupId" select="$tgroupId"/>
        <xsl:with-param name="totalWidth">
          <xsl:for-each select="$colspecs">
            <xsl:value-of select="table/tgroup[@id = 
              $tgroupId]/col[@colname= $startCol]/@width + $totalWidth"/>
          </xsl:for-each>
        </xsl:with-param>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$totalWidth"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>

20.

Replace repetition in cell values by span values.

David Carlisle


> Given

 <column_header>
<header rowid="0">
<data columnid="01" name="2003"/>
<data columnid="11" name="2003"/>
<data columnid="21" name="2003"/>
<data columnid="31" name="2003"/>
<data columnid="41" name="2003"/>
<data columnid="51" name="2003"/>
<data columnid="61" name="2003"/>
<data columnid="71" name="2003"/>
</header>
<header rowid="1">
<data columnid="01" name="Feb"/>
<data columnid="11" name="Feb"/>
<data columnid="21" name="Feb"/>
<data columnid="31" name="Feb"/>
<data columnid="41" name="Y-T-D(Feb)"/>
<data columnid="51" name="Y-T-D(Feb)"/>
<data columnid="61" name="Y-T-D(Feb)"/>
<data columnid="71" name="Y-T-D(Feb)"/>
</header>
<header rowid="2">
<data columnid="01" name="Actual"/>
<data columnid="11" name="Budget"/>
<data columnid="21" name="Fav/(Unfav) $"/>
<data columnid="31" name="Fav/(Unfav) %"/>
<data columnid="41" name="Actual"/>
<data columnid="51" name="Budget"/>
<data columnid="61" name="Fav/(Unfav) $"/>
<data columnid="71" name="Fav/(Unfav) %"/>
</header>
</column_header>

> I'm trying to get the results to look like this:

<table border="1" cellpadding="0" cellspacing="0"
           bordercolor="#111111">
<tr>
<td colspan="8">2003</td>
</tr>
<tr>
<td colspan="4">Feb</td>
<td colspan="4">Y-T-D(Feb)</td>
</tr>




> Basically I'm doing grouping, which I have seen numerous postings for, 
> but couldn't find anything that dealt with actual colspans and the 
> such.  My first idea was to use the "following-sibling::data[1]/@name 
> = @name" test to keep track of how many columns to span.  If the above 
> test was false, I would draw the table cell using the current colspan 
> counter.  Otherwise, if the above test was true, then I would skip 
> drawing the table cell and increment the colspan counter.

Your mind has been corrupted by exposure to limited programming languages designed around the capabilities of a machine of the previous century,

If you were describing what colspan means to a person, you wouldn't (I hope) say you start off with an imaginary variable, initialized to zero, and then move from one column to the next, and if the value is the same, increment this variable by one, until you get to a column that is not followed by a column with an identical value, whereupon you use the value as the colspan attribute.

You're more likely to say,

if you have a group of columns that use the same text, group them in one cell and use colspan to give the size of the group of columns.

Program xslt as you would talk to a human. You just want to use grouping (as you said) and then use count() to return the size of the current group.

I'd use muenchian grouping for this (See jeni's site)

basically index each cell by its rowid and value:

<xsl:key name="x" match="data" use="concat(../@rowid,':',name())"/>

then

<xsl:for-each
select="data[genarate-id(.)=generate-id(key('x',concat(../@rowid,':',name())
[1])]">
<!-- this is first of each group -->
<data>
<xsl:if test="key('x',concat(../@rowid,':',name()))[2]">
<xsl:attribute name="colspan"><xsl:value-of
select="count(key('x',concat(../@rowid,':',name())))"/>
</xsl:attribute>

21.

Normalize / Simplify HTML-Tables with row-span / col-span

Andrew Welch + Michael Müller-Hillebrand

Out of a slightly different requirement Andrew Welch sent a very good XSL stylesheet which handles "normalization" of a table using colspan and rowspan attributes into a table where each row has the same number of cells.

I just put in some finishing touches, comments, and removed code which was related to the original requirement.

Imagine the following table (using table, tbody, tr, and td elements width colspan and rowpsan attributes):


    
    +-----------+-----------+
    | a         | b         |
    |           +-----+-----+
    |           | c   | d   |
    +-----------+-----+     |
    | e               |     |
    +-----+-----+-----+     |
    | f   | g   | h   |     |
    +-----+-----+-----+-----+

The script below changes that table to this format:


    +-----+-----+-----+-----+
    | a   | a   | b   | b   |
    +-----+-----+-----+-----+
    | a   | a   | c   | d   |
    +-----+-----+-----+-----+
    | e   | e   | e   | d   |
    +-----+-----+-----+-----+
    | f   | g   | h   | d   |
    +-----+-----+-----+-----+
    

Thanks to Andrew for that algorithm!

    
    <!--
    
    TABLE NORMALIZATION
    
    Heres a potential solution...
    
    It works by first creating a variable with the colspans 
    normalized, which is easy enough.  It then normalizes the 
    rowspans by iterating over the variable a row at time, 
    using two nodesets.  One is the current row, one is the 
    previous row ($rowBlock). Where the previous row has a <td> 
    with a rowspan > 1, it copies it to a result variable with 
    the rowspan decremented.  Where the previous row just has a 
    normal <td>, it copies the correct <td> from the current 
    row to the result variable.  It maintains a pair of 
    pointers to ensure the correct <td>'s are being compared.  
    The 'result variable' is then used as the previous row in 
    the next iteration of the <tr> processing.  This is based 
    on the fact that the first row of the table is always 
    complete - it contains the correct number of columns.
    
    -->
    
    <!--
      2004-03-23: Some finishing touches by Michael Müller-Hillebrand
    -->
    
    <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="xml" indent="yes" 
      omit-xml-declaration="yes" 
      encoding="UTF-8" />
    
    <!-- FIRST: REMOVE COLSPANS FROM INPUT TABLE --> 
    <xsl:variable name="table_with_no_colspans-rtf">
      <xsl:apply-templates mode="colspan"/>
    </xsl:variable>
    
    <xsl:variable name="table_with_no_colspans"
      select="exsl:node-set($table_with_no_colspans-rtf)"/>
    
    <!-- SECOND: REMOVE ROWSPANS FROM INPUT TABLE --> 
    <xsl:variable name="table_with_no_rowspan-rtf">
      <xsl:for-each select="$table_with_no_colspans">
        <xsl:apply-templates mode="rowspan"/>
      </xsl:for-each>
    </xsl:variable>
    
    <xsl:variable name="table_with_no_rowspan"
      select="exsl:node-set($table_with_no_rowspan-rtf)"/>
    
    <!--  ROOT TEMPLATE -->
    
    <xsl:template match="/">
      <NOCOLSPAN><xsl:copy-of 
    select="$table_with_no_colspans"/></NOCOLSPAN>
      <NOROWSPAN><xsl:copy-of 
    select="$table_with_no_rowspan"/></NOROWSPAN>
    
    <!-- THIRD: REMOVE REMAINING ROWSPAN ATTRIBUTES FROM INPUT TABLE -->
      <FINAL>
        <xsl:for-each select="$table_with_no_rowspan">
          <xsl:apply-templates mode="final"/>
        </xsl:for-each>
      </FINAL>
    </xsl:template>
    
    <!-- TEMPLATES USED TO REMOVE COLSPAN -->
    
    <xsl:template match="@*|*" mode="colspan">
      <xsl:copy>
        <xsl:apply-templates select="@*|*" mode="colspan"/>
      </xsl:copy>
    </xsl:template>
    
    <xsl:template match="td" mode="colspan">
      <xsl:choose>
        <xsl:when test="@colspan">
          <td>
            <xsl:copy-of 
    select="@*[not(contains('colspanwidth', name()))]"/>
            <xsl:copy-of select="node()"/>
          </td>
          <xsl:call-template name="give_me_tds">
            <xsl:with-param name="number-of-tds" select="@colspan - 1"/>
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:copy-of select="."/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>
    
    <xsl:template name="give_me_tds">
      <xsl:param name="number-of-tds" select="'1'"/>
      <xsl:if test="$number-of-tds > 0">
        <td>
          <xsl:copy-of select="@*[not(contains('colspanwidth', 
    name()))]"/>
          <xsl:copy-of select="node()"/>
        </td>
        <xsl:call-template name="give_me_tds">
          <xsl:with-param name="number-of-tds" 
    select="$number-of-tds - 1"/>
        </xsl:call-template>
      </xsl:if>
    </xsl:template>
    
    <!-- TEMPLATES USED TO REMOVE ROWSPAN -->
    
    <xsl:template match="@*|*" mode="rowspan">
      <xsl:copy>
        <xsl:apply-templates select="@*|*" mode="rowspan" />
      </xsl:copy>
    </xsl:template>
    
    <xsl:template match="tbody" mode="rowspan">
      <tbody>
        <!-- first row is always okay -->
        <xsl:copy-of select="tr[1]" />
        <xsl:apply-templates select="tr[2]" mode="rowspan">
          <xsl:with-param name="rowBlock" select="tr[1]" />
        </xsl:apply-templates>
      </tbody>
    </xsl:template>
    
    <xsl:template match="tr" mode="rowspan">
      <xsl:param name="rowBlock" select="/.." /><!-- "/.." = 
    empty node-set -->
    
      <xsl:variable name="current" select="." />
      <xsl:variable name="rowBlockVar">
        <xsl:call-template name="makeRow">
          <xsl:with-param name="rowBlock" select="$rowBlock" />
          <xsl:with-param name="current" select="$current" />
        </xsl:call-template>
      </xsl:variable>
    
      <tr>
        <xsl:copy-of select="$current/@*" />
        <xsl:copy-of select="$rowBlockVar" />
      </tr>
    
      <xsl:apply-templates select="following-sibling::tr[1]" 
    mode="rowspan">
        <xsl:with-param name="rowBlock" 
    select="exsl:node-set($rowBlockVar)" />
      </xsl:apply-templates>
    </xsl:template>
    
    <xsl:template name="makeRow">
      <xsl:param name="rowBlock" />
      <xsl:param name="current" />
      <xsl:param name="rowBlockPointer" select="1" />
      <xsl:param name="currentPointer" select="1" />
    
      <xsl:if test="$rowBlock/td[$rowBlockPointer]"><!-- any 
    cell in prev row? -->
        <xsl:variable name="rbRowspan">
          <xsl:for-each select="$rowBlock">
            <xsl:choose>
              <xsl:when test="td[$rowBlockPointer]/@rowspan">
                <xsl:value-of select="td[$rowBlockPointer]/@rowspan"/>
              </xsl:when>
              <xsl:otherwise>1</xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>
        </xsl:variable>
    
        <xsl:choose>
          <xsl:when test="$rbRowspan > 1 or 
    not($current/td[$currentPointer])">
            <td>
              <xsl:copy-of 
    select="$rowBlock/td[$rowBlockPointer]/@*[not(contains('rows
    pan', name()))]"/>
              <xsl:if test="$rbRowspan > 2">
                <xsl:attribute name="rowspan"><xsl:value-of 
    select="$rbRowspan - 1" /></xsl:attribute>
              </xsl:if>
              <xsl:copy-of 
    select="$rowBlock/td[$rowBlockPointer]/node()"/>
            </td>
            <xsl:call-template name="makeRow">
              <xsl:with-param name="rowBlock" select="$rowBlock"/>
              <xsl:with-param name="current" select="$current"/>
              <xsl:with-param name="rowBlockPointer" 
    select="$rowBlockPointer + 1"/>
              <xsl:with-param name="currentPointer" 
    select="$currentPointer"/>
            </xsl:call-template>
          </xsl:when>
          <xsl:otherwise>
            <xsl:for-each select="$current">
              <xsl:copy-of select="td[$currentPointer]"/>
              <xsl:call-template name="makeRow">
                <xsl:with-param name="rowBlock" select="$rowBlock"/>
                <xsl:with-param name="current" select="$current"/>
                <xsl:with-param name="rowBlockPointer" 
    select="$rowBlockPointer + 1"/>
                <xsl:with-param name="currentPointer" 
    select="$currentPointer + 1"/>
              </xsl:call-template>
            </xsl:for-each>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:if>
    </xsl:template>
    
    <!-- TEMPLATES FOR FINAL OUTPUT -->
    
    <xsl:template match="@*|*" mode="final">
      <xsl:copy>
        <xsl:apply-templates select="@*|*" mode="final" />
      </xsl:copy>
    </xsl:template>
    
    <xsl:template match="td" mode="final">
      <xsl:choose>
        <xsl:when test="@rowspan">
          <td>
            <xsl:copy-of select="@*[not(contains('rowspan', name()))]"/>
            <xsl:copy-of select="node()"/>
          </td>
        </xsl:when>
        <xsl:otherwise>
          <xsl:copy-of select="."/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>
    
    </xsl:stylesheet>
    

22.

How to split a large cals table

David Carlisle




Some of the tables I need to process are fairly large (200+ columns).
The publishing software I use will not render tables with this many
columns (unsurprisingly). I need to break these large tables into much
smaller ones, say 10 columns. Each table would repeat column 1 (row
titles).

Input

<table id="1">
  <tgroup cols="239">
    <colspec colname="col1" colnum="1"
     colwidth="*"/>
    ...
    <colspec colname="col239" colnum="3"
     colwidth="*"/>
    <tbody>
      <row id="t1-1">
<entry colname="col1"
       id="1">Statement</entry>
...
<entry colname="col239"
       id="239"/>
      </row>
      ...
    </tbody>
  </tgroup>
</table>

Desired output

OUTPUT: The colspecs are sequential and I have added new attributes
(oldcolspec & olcolname) to show the original column number)
---------

<table id="1">
  <tgroup cols="11">
    <colspec colname="col1" colnum="1"
     oldcolspec="1" colwidth="*"/>
    ...
    <colspec colname="col10" colnum="10"
     oldcolspec="10" colwidth="*"/>
    <tbody>
      <row id="t1-1">
<entry colname="col1"
       oldcolname="1" id="1">Statement</entry>
...
<entry colname="col10"
       oldcolname="10" id="10"/>
      </row>
    </tbody>
  </tgroup>
</table>
<table id="2">
  <tgroup cols="11">
    <colspec colname="col1" colnum="1"
     oldcolspec="11" colwidth="*"/>
    ...
    <colspec colname="col10" colnum="10"
     oldcolspec="20" colwidth="*"/>
    <tbody>
      <row id="t1-1">
<entry colname="col1"
       oldcolname="11" id="1">Statement</entry>
...
<entry colname="col10"
       oldcolname="20" id="10"/>
      </row>
    </tbody>
  </tgroup>
</table>

...

<table id="23">
  <tgroup cols="10">
    <colspec colname="col1" colnum="1"
     oldcolspec="231" colwidth="*"/>
    ...
    <colspec colname="col10" colnum="10"
     oldcolspec="239" colwidth="*"/>
    <tbody>
      <row id="t1-1">
<entry colname="col1"
       oldcolname="231" id="1">Statement</entry>
...
<entry colname="col10"
       oldcolname="239" id="10"/>
      </row>
    </tbody>
  </tgroup>
</table>

Can you promise not to have any colspan elements? (especially overlapping ones) ? If there are no colspans at all it's not too hard, if there are some spanning entries but none spanning the vertical border on which you are splitting it's doable but more tedious, if there are entries spanning your splits then you have to start splitting up individual entries which can be arbitrarily complicated, depending on what's in there. An entry that spans the entire table and contains a reference to a single image makes splitting the table interesting....

This agreed, DC produced:

<xsl:template match="table">
<xsl:for-each select="tgroup/colspec[position()!=1][position() mod 9 = 1]">
<xsl:variable name="start" select="(position()-1)*9+2"/>
<table id="t{position()}">
<tgroup>
<colspec colname="col1" colwidth="*"/>
<xsl:for-each select="(.|following-sibling::colspec)[position() &lt;10]">
<colspec colname="col{position()+1}" colwidth="*"/>
<tbody>
<xsl:for-each select="../row">
<row>
<xsl:copy-of select="@*"/>
<xsl:for-each select="entry[position()=1 or (position()&gt;=$start)][position()&lt;=10]">
<entry colname="col{position()}">
<xsl:apply-templates/>
</entry>
</xsl:for-each>
</row>
</xsl:for-each>
</tbody>
</xsl:for-each>
</tgroup>
</table>
</xsl:for-each>
</xsl:template>