This is the mail archive of the
xsl-list@mulberrytech.com
mailing list .
Re: Table formatting challenge
- To: xsl-list at lists dot mulberrytech dot com
- Subject: Re: [xsl] Table formatting challenge
- From: Oliver Becker <obecker at informatik dot hu-berlin dot de>
- Date: Thu, 12 Jul 2001 17:39:12 +0200 (MET DST)
- Reply-To: xsl-list at lists dot mulberrytech dot com
> Consider the following table:
>
> +--------+--------+--------+--------+
> | | B | | D |
> + A +--------+ C +--------+
> | | | | E |
> +--------+--------+--------+--------+
>
> Represented by this markup:
>
> <informaltable>
> <tgroup cols="4">
> <colspec colname="c4" colnum="4"/>
> <tbody>
> <row>
> <entry morerows="1">A</entry>
> <entry>B</entry>
> <entry morerows="1">C</entry>
> <entry>D</entry>
> </row>
> <row>
> <entry namest="c4">E</entry>
> </row>
> </tbody>
> </tgroup>
> </informaltable>
>
> The correct HTML for this table is:
>
> <table border="1">
> <tr>
> <td rowspan="2">A</td>
> <td>B</td>
> <td rowspan="2">C</td>
> <td>D</td>
> </tr>
> <tr>
> <td class="auto-generated"></td>
> <td>E</td>
> </tr>
> </table>
>
> Can anyone see a practical way to achieve this result without
> resorting to extension functions? The tricky bit is not simply
> noticing that the introduction of namest may require some
> auto-generated cells to be inserted, but calculating correctly, in the
> presence of overhang (from an arbitrarily large number of preceeding
> rows), how many cells to insert.
>
> I'm hoping I've overlooked something clever and elegant.
Evaluate it yourself ;-)
Tested with Saxon and Xalan, doesn't need extensions.
I hope my comments are reasonable useful.
Cheers,
Oliver
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
<xsl:template match="informaltable">
<table border="1">
<xsl:apply-templates />
</table>
</xsl:template>
<xsl:template match="row">
<tr>
<xsl:call-template name="process-row">
<xsl:with-param name="col" select="1" />
</xsl:call-template>
</tr>
</xsl:template>
<!--
| key for the colspec element;
| not optimal, because colnames don't have to be globally unique, I assume
+-->
<xsl:key name="colspec" match="colspec" use="@colname" />
<!--
| Process each column of a row
+-->
<xsl:template name="process-row">
<xsl:param name="col" />
<!-- is the current column number <= total number of cols? -->
<xsl:if test="$col <= ../../@cols">
<!-- compute the overhang for this column -->
<xsl:variable name="overhang">
<xsl:call-template name="compute-overhang">
<xsl:with-param name="col" select="$col" />
<xsl:with-param name="rows" select="preceding-sibling::row" />
</xsl:call-template>
</xsl:variable>
<!-- only if $overhang is 0 we have to create a <td> -->
<xsl:if test="$overhang = 0">
<!-- find out, if there's an explicit entry in the input -->
<!-- perhaps one with a namest attribute? -->
<xsl:variable name="namest"
select="entry[key('colspec',@namest)/@colnum = $col]" />
<xsl:choose>
<xsl:when test="$namest">
<!-- you may optimize this if you like and discard
rowspan="1" attributes ;-) -->
<td rowspan="{sum($namest/@morerows)+1}">
<xsl:value-of select="$namest" />
</td>
</xsl:when>
<xsl:otherwise>
<!-- look for an entry at the requested position -->
<xsl:variable name="entry-id">
<xsl:call-template name="find-entry">
<xsl:with-param name="col" select="$col" />
<xsl:with-param name="entries" select="entry" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="entry"
select="entry[generate-id()=$entry-id]" />
<xsl:choose>
<xsl:when test="$entry">
<td rowspan="{sum($entry/@morerows)+1}">
<xsl:value-of select="$entry" />
</td>
</xsl:when>
<xsl:otherwise>
<td class="auto-generated" />
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
<!-- next column -->
<xsl:call-template name="process-row">
<xsl:with-param name="col" select="$col + 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<!--
| Compute the overhang for a given column
+-->
<xsl:template name="compute-overhang">
<xsl:param name="col" /> <!-- column number -->
<xsl:param name="rows" /> <!-- preceding rows -->
<xsl:param name="overhang" select="0" /> <!-- current overhang -->
<xsl:choose>
<xsl:when test="not($rows)">
<!-- no rows left -->
<xsl:value-of select="$overhang" />
</xsl:when>
<xsl:when test="$overhang > 0">
<!-- we have already an overhang, go to the next row -->
<xsl:call-template name="compute-overhang">
<xsl:with-param name="col" select="$col" />
<xsl:with-param name="rows" select="$rows[position() != 1]" />
<xsl:with-param name="overhang" select="$overhang - 1" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- ok, current overhang is 0, we have still rows;
let's look for an entry in our column -->
<xsl:variable name="namest" select=
"$rows[1]/entry[key('colspec', @namest)[@colnum = $col]]" />
<xsl:choose>
<xsl:when test="$namest">
<!-- there is an <entry namest=...> element -->
<xsl:call-template name="compute-overhang">
<xsl:with-param name="col" select="$col" />
<xsl:with-param name="rows" select="$rows[position() != 1]" />
<xsl:with-param name="overhang"
select="sum($namest/@morerows)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="entry-id">
<xsl:call-template name="find-entry">
<xsl:with-param name="col" select="$col" />
<xsl:with-param name="entries" select="$rows[1]/entry" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="entry"
select="$rows[1]/entry[generate-id()=$entry-id]" />
<xsl:choose>
<xsl:when test="$entry">
<xsl:call-template name="compute-overhang">
<xsl:with-param name="col" select="$col" />
<xsl:with-param name="rows"
select="$rows[position() != 1]" />
<xsl:with-param name="overhang"
select="sum($entry/@morerows)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- an auto-generated cell must be created at the end;
it has no overhang -->
<xsl:call-template name="compute-overhang">
<xsl:with-param name="col" select="$col" />
<xsl:with-param name="rows"
select="$rows[position() != 1]" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
| Finds an entry for a requested column;
| returns an id (generate-id) of this entry, since we cannot return a node
+-->
<xsl:template name="find-entry">
<xsl:param name="col" /> <!-- requested col, relative to $entries -->
<xsl:param name="entries" /> <!-- left entry elements -->
<xsl:choose>
<!-- the first entry doesn't have a namest attribute, touche! -->
<xsl:when test="$col=1 and $entries[1][not(@namest)]">
<xsl:value-of select="generate-id($entries[1])" />
</xsl:when>
<!-- dito, except that we don't look for the first entry,
process the rest recursively -->
<xsl:when test="$col>1 and $entries[1][not(@namest)]">
<xsl:call-template name="find-entry">
<xsl:with-param name="col" select="$col - 1" />
<xsl:with-param name="entries" select="$entries[position() != 1]" />
</xsl:call-template>
</xsl:when>
<!-- the first entry has a namest attribute, `move' $col the
appropriate number of steps and process the rest recursively -->
<xsl:when test="$entries[1][@namest]">
<xsl:call-template name="find-entry">
<xsl:with-param name="col"
select="$col - key('colspec', $entries[1]/@namest)/@colnum" />
<xsl:with-param name="entries" select="$entries[position() != 1]" />
</xsl:call-template>
</xsl:when>
<!-- otherwise, i.e. $col < 1 or $entries is empty,
return nothing -->
</xsl:choose>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
/-------------------------------------------------------------------\
| ob|do Dipl.Inf. Oliver Becker |
| --+-- E-Mail: obecker@informatik.hu-berlin.de |
| op|qo WWW: http://www.informatik.hu-berlin.de/~obecker |
\-------------------------------------------------------------------/
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list