[Home] [By Thread] [By Date] [Recent Entries]

Subject: Re: request: flattening or normalising tables with spanned cells
From: "Trevor Nicholls trevor@xxxxxxxxxxxxxxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Wed, 30 Sep 2026 14:29:01 -0000
Sorry I don't know how that happened, what's below should be easier to read

On 1/10/2026 03:23, Trevor Nicholls trevor@xxxxxxxxxxxxxxxxxx wrote:
So I took Andrew's stylesheet and tested it in my environment, but unfortunately I have a couple of problems with it. One I think I can solve, the other one I do not understand.

Here's the stylesheet, unedited:

<!-- Table Normalization - HT Andrew Welch -->

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
\xA0 \xA0 \xA0 \xA0 <xsl:output method="xml" encoding="UTF-8"/>


\xA0 \xA0 \xA0 \xA0 <xsl:variable name="table_with_no_colspans">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:apply-templates mode="colspan"/>
\xA0 \xA0 \xA0 \xA0 </xsl:variable>

\xA0 \xA0 \xA0 \xA0 <xsl:variable name="table_with_no_rowspans">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:for-each select="$table_with_no_colspans">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:apply-templates mode="rowspan"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:for-each>
\xA0 \xA0 \xA0 \xA0 </xsl:variable>

\xA0 \xA0 \xA0 \xA0 <xsl:template match="/">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:apply-templates select="$table_with_no_rowspans" mode="final"/>
\xA0 \xA0 \xA0 \xA0 </xsl:template>


\xA0 \xA0 \xA0 \xA0 <xsl:template match="@*|*" mode="#all">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy>
<!-- Note 2 -->
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:apply-templates select="@*|*" mode="#current"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:copy>
\xA0 \xA0 \xA0 \xA0 </xsl:template>


\xA0 \xA0 \xA0 \xA0 <xsl:template match="td" mode="colspan">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:choose>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:when test="@colspan">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:variable name="this" select="." as="element()"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:for-each select="1 to @colspan">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <td>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy-of select="$this/@*[not(name() = 'colspan')][not(name() = 'width')]"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy-of select="$this/node()"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </td>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:for-each>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:when>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:otherwise>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy-of select="."/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:otherwise>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:choose>
\xA0 \xA0 \xA0 \xA0 </xsl:template>


\xA0 \xA0 \xA0 \xA0 <xsl:template match="tbody" mode="rowspan">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy-of select="tr[1]"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:apply-templates select="tr[2]" mode="rowspan">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:with-param name="previousRow" select="tr[1]"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:apply-templates>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:copy>
\xA0 \xA0 \xA0 \xA0 </xsl:template>


\xA0 \xA0 \xA0 \xA0 <xsl:template match="tr" mode="rowspan">
<!-- Note 1 -->
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:param name="previousRow" as="node()"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:variable name="currentRow" select="."/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:variable name="normalizedTDs">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:for-each select="$previousRow/td">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:choose>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:when test="@rowspan > 1">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy>
<xsl:attribute name="rowspan"><xsl:value-of select="@rowspan - 1"/></xsl:attribute>
<xsl:copy-of select="@*[not(name() = 'rowspan')]"/>
<xsl:copy-of select="node()"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:copy>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:when>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:otherwise>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy-of select="$currentRow/td[1 + count(current()/preceding-sibling::td[not(@rowspan) or (@rowspan = 1)])]"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:otherwise>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:choose>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:for-each>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:variable>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:variable name="newRow" as="element(tr)">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy-of select="$currentRow/@*"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy-of select="$normalizedTDs"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:copy>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:variable>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy-of select="$newRow"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:apply-templates select="following-sibling::tr[1]" mode="rowspan">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:with-param name="previousRow" select="$newRow"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:apply-templates>
\xA0 \xA0 \xA0 \xA0 </xsl:template>


\xA0 \xA0 \xA0 \xA0 <xsl:template match="td" mode="final">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:choose>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:when test="@rowspan">
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy-of select="@*[not(name() = 'rowspan')]"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy-of select="node()"/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:copy>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:when>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:otherwise>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 <xsl:copy-of select="."/>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:otherwise>
\xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 \xA0 </xsl:choose>
\xA0 \xA0 \xA0 \xA0 </xsl:template>


</xsl:stylesheet>

<!-- end of Andrew's stylesheet -->

When I try to run this transform across one of my documents (using Saxon) I get this error:

Error at xsl:param on line 56 of file: (path-to-xsl):
\xA0 \xA0 XTDE0610: A value must be supplied for the parameter because the default value is not a valid instance of the required type


If I remove the as="node()" from the previousRow param at Note 1 above I get a different error:

Error on line 61 of file:
\xA0 \xA0 XPTY0019: Required item type of first operand of '/' is node(); supplied value has item type xs:string


The default value is kind of academic because so far as I can see this template is always called with-param, so I tried replacing the line with

\xA0 \xA0<xsl:param name="previousRow" as="node()?"/>

and the transform then runs. Unfortunately it only fixes the runtime error, it doesn't actually generate sensible output.

My second problem is that the output of this transformation contains the XML structure but it has lost all the content.

To fix that I had to add "text()" to the apply-templates at Note 2. Now most of the document is preserved, but it's clear that every single <tr> in the output document is empty. This tells me that the fix at Note 1 may simply be masking a more serious problem.

Is there enough in the foregoing for somebody to see what might be wrong in my/Andrew's stylesheet?

Thanks
T


On 29/09/2026 02:52, Martin Honnen martin.honnen@xxxxxx wrote:

On 28/09/2026 15:47, Trevor Nicholls trevor@xxxxxxxxxxxxxxxxxx wrote:
(note: I'm looking for an XSL2 solution at this point.)


https://ajwelch.blogspot.com/2006/09/table-normalization-in-xslt-20.html

Current Thread
Site Map | Privacy Policy | Terms of Use | Trademarks
Free Stylus Studio XML Training:
W3C Member