[xsl] xhtml2cals table stylesheet

Subject: [xsl] xhtml2cals table stylesheet
From: "Roman Huditsch" <roman.huditsch@xxxxxx>
Date: Sun, 29 Mar 2009 12:01:49 +0200
Hello xsl-list, 

During the last couple of years I pretty often had to transform 

tables from XHTML to CALS and the other way around. 

Since I couldn't find something usefull for doing the XHTML to CALS way 

(the other direction is pretty well covered by the docbook stylesheets),


I tried to come up with an XSLT stylesheet myself. 

Since that was already some years ago and - as far as I can remember -
there 

was a bug dealing with rowspans and colspans appearing on the same 

table cell, I reworked it and would like to ask you for your extremely
valuable input. 

I am sure that there might be some XPaths that could be more elegant and
smarter. 

I tested the stylesheet with various more or less complex tables and it
seems to 

work as expected. 

What's still missing, though, is an XSpec description for some more test
cases. 

But that's still to come. 

Thanks a lot, 

Roman 

Here it comes: 

<?xml version="1.0" encoding="ISO-8859-1"?> 

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:rh="http://www.huditsch.bkf.at/xslt/xhtml-cals";
xmlns:xs="http://www.w3.org/2001/XMLSchema";
xmlns:xsldoc="http://www.bacman.net/XSLdoc";
xmlns:xhtml="http://www.w3.org/1999/xhtml";
exclude-result-prefixes="#all"
xmlns:doc="http://docbook.org/ns/docbook";> 

<!-- ===================================================================
--> 

<!-- --> 

<!-- This Stylesheets converts arbitrary XHTML tables into tables --> 

<!-- conforming to the OASIS CALS model --> 

<!-- --> 

<!-- Permission to copy and modify is granted, provided this notice --> 

<!-- is included in all copies and/or derived work. --> 

<!-- --> 

<!-- Author: Roman Huditsch, roman.huditsch@xxxxxx --> 

<!-- --> 

<!-- ===================================================================
--> 

<xsl:output method="xhtml" version="1.0" encoding="ISO-8859-1"
doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd";
/> 

<xsldoc:author>Roman Huditsch</xsldoc:author> 

<xsldoc:date>March 27th, 2009</xsldoc:date> 

<xsldoc:version>Version 1.0</xsldoc:version> 

<!-- ===================================================================
--> 

<!-- Per default all existing nodes should be copied into the result
document --> 

<!-- ===================================================================
--> 

<xsl:template match="node() | @*" mode="process first-pass second-pass">


<xsl:copy copy-namespaces="no"> 

<xsl:apply-templates select="@* | node()" mode="#current"/> 

</xsl:copy> 

</xsl:template> 

<!-- ===================================================================
--> 

<!-- HTML --> 

<!-- ===================================================================
--> 

<xsl:template match="xhtml:html"> 

<!-- ===================================================================
--> 

<!-- First Pass - Create a variable with "expanded" table cells based on
@colspan --> 

<!-- ===================================================================
--> 

<xsl:variable name="first-pass" as="element()+"> 

<xsl:copy> 

<xsl:copy-of select="@*"/> 

<xsl:apply-templates mode="first-pass"/> 

</xsl:copy> 

</xsl:variable> 

<!--xsl:message select="$first-pass"/--> 

<!-- ===================================================================
--> 

<!-- Second Pass - Create a variable with "expanded" table cells based
on @rowspan --> 

<!-- ===================================================================
--> 

<xsl:variable name="second-pass" as="element()+"> 

<xsl:copy> 

<xsl:copy-of select="@*"/> 

<xsl:apply-templates select="$first-pass/*" mode="second-pass"/> 

</xsl:copy> 

</xsl:variable> 

<!--xsl:message select="$second-pass"/--> 

<xsl:copy> 

<xsl:namespace name="doc" select="'http://docbook.org/ns/docbook'"/> 

<xsl:apply-templates select="$second-pass/*" mode="process"/> 

</xsl:copy> 

</xsl:template> 

<!-- ===================================================================
--> 

<!-- Table --> 

<!-- ===================================================================
--> 

<xsl:template match="xhtml:table" mode="process"> 

<!-- ===================================================================
--> 

<!-- Continue with processing --> 

<!-- ===================================================================
--> 

<doc:table> 

<xsl:apply-templates select="(@border, @width)" mode="process"/> 

<!-- <tgroup> --> 

<doc:tgroup cols="{rh:max_columns(.)}"> 

<xsl:message select="concat('Max Columns: ', rh:max_columns(.))"/> 

<xsl:call-template name="generate-colspecs"> 

<xsl:with-param name="max" select="rh:max_columns(.)" as="xs:double"/> 

<!-- Param to indicate border handling to child nodes --> 

<xsl:with-param name="border" select="not(starts-with(@border,'0'))"
tunnel="yes" as="xs:boolean"/> 

</xsl:call-template> 

<!-- <thead> --> 

<xsl:apply-templates select="xhtml:thead" mode="process"> 

<!-- Param to indicate border handling to child nodes --> 

<xsl:with-param name="border" select="not(starts-with(@border,'0'))"
tunnel="yes" as="xs:boolean"/> 

</xsl:apply-templates> 

<!-- <tbody> --> 

<doc:tbody> 

<xsl:apply-templates select="xhtml:tr | xhtml:tbody/xhtml:tr"
mode="process"> 

<!-- Param to indicate border handling to child nodes --> 

<xsl:with-param name="border" select="not(starts-with(@border,'0'))"
tunnel="yes" as="xs:boolean"/> 

</xsl:apply-templates> 

</doc:tbody> 

<!-- <tfoot> --> 

<xsl:apply-templates select="xhtml:tfoot" mode="process"> 

<!-- Param to indicate border handling to child nodes --> 

<xsl:with-param name="border" select="not(starts-with(@border,'0'))"
tunnel="yes" as="xs:boolean"/> 

</xsl:apply-templates> 

</doc:tgroup> 

</doc:table> 

</xsl:template> 

<!-- ===================================================================
--> 

<!-- Table attributes --> 

<!-- ===================================================================
--> 

<xsl:template match="@border" mode="process"> 

<xsl:attribute name="frame"> 

<xsl:value-of select="if(starts-with(., '0')) then('none')
else('all')"/> 

</xsl:attribute> 

</xsl:template> 

<xsl:template match="xhtml:table/@width" mode="process"> 

<xsl:attribute name="pgwide"> 

<xsl:value-of select="if(.='100%') then('0') else('1')"/> 

</xsl:attribute> 

</xsl:template> 

<!-- ===================================================================
--> 

<!-- colspec --> 

<!-- ===================================================================
--> 

<xsl:template name="generate-colspecs"> 

<xsl:param name="border" tunnel="yes"/> 

<xsl:param name="max" as="xs:double"/> 

<xsl:param name="count" select="1" as="xs:double"/> 

<xsl:choose> 

<xsl:when test="$count &gt; $max"/> 

<xsl:otherwise> 

<doc:colspec colnum="{$count}" colname="{concat('col', $count)}"
colsep="{if($border) then('1') else('0')}"> 

<xsl:choose> 

<xsl:when test="xhtml:colgroup/xhtml:col[$count]/@width"> 

<xsl:apply-templates select="xhtml:colgroup/xhtml:col[$count]/@width"
mode="process"/> 

</xsl:when> 

<xsl:when test="( ./(*/* | *)/xhtml:td[$count] | ./(*/* |
*)/xhtml:th[$count])/@width"> 

<xsl:attribute name="colwidth"> 

<xsl:value-of select="concat(rh:max_width(., $count), '*')"/> 

</xsl:attribute> 

</xsl:when> 

</xsl:choose> 

</doc:colspec> 

<xsl:call-template name="generate-colspecs"> 

<xsl:with-param name="max" select="$max"/> 

<xsl:with-param name="count" select="$count + 1"/> 

</xsl:call-template> 

</xsl:otherwise> 

</xsl:choose> 

</xsl:template> 

<xsl:template match="xhtml:col/@width" mode="process"> 

<xsl:attribute name="colwidth"> 

<xsl:value-of select="."/> 

</xsl:attribute> 

</xsl:template> 

<!-- ===================================================================
--> 

<!-- thead, tfoot --> 

<!-- ===================================================================
--> 

<xsl:template match="xhtml:thead | xhtml:tfoot" mode="process"> 

<xsl:element name="doc:{local-name()}"> 

<xsl:copy-of select="@valign"/> 

<xsl:apply-templates mode="process"/> 

</xsl:element> 

</xsl:template> 

<!-- ===================================================================
--> 

<!-- TR --> 

<!-- ===================================================================
--> 

<xsl:template match="xhtml:tr" mode="process"> 

<xsl:param name="border" tunnel="yes"/> 

<doc:row rowsep="{if($border) then('1') else('0')}"> 

<xsl:copy-of select="@valign"/> 

<xsl:apply-templates mode="process"/> 

</doc:row> 

</xsl:template> 

<!-- ===================================================================
--> 

<!-- TD | TH --> 

<!-- ===================================================================
--> 

<xsl:template match="xhtml:td | xhtml:th" mode="process"> 

<xsl:variable name="position" select="count(preceding-sibling::*) + 1"/>


<doc:entry colname="col{$position}"> 

<xsl:if test="@colspan &gt; 1"> 

<xsl:attribute name="namest"> 

<xsl:value-of select="concat('col',$position)"/> 

</xsl:attribute> 

<xsl:attribute name="nameend"> 

<xsl:value-of select="concat('col',$position + number(@colspan) - 1)"/> 

</xsl:attribute> 

</xsl:if> 

<xsl:if test="@rowspan &gt; 1"> 

<xsl:attribute name="morerows"> 

<xsl:value-of select="number(@rowspan) - 1"/> 

</xsl:attribute> 

</xsl:if> 

<xsl:copy-of select="@align"/> 

<xsl:apply-templates mode="process"/> 

</doc:entry> 

</xsl:template> 

<!--
========================================================================
================ --> 

<!-- Function for counting the number of columns - Input: Context
Element, Output: Maximum Double --> 

<!--
========================================================================
================ --> 

<xsl:function name="rh:max_columns" as="xs:integer"> 

<xsl:param name="context" as="element()"/> 

<xsl:choose> 

<xsl:when test="$context/xhtml:colgroup[not(@span)]"> 

<xsl:sequence select="count($context/xhtml:colgroup/xhtml:col)"/> 

</xsl:when> 

<xsl:when test="$context/xhtml:colgroup[@span]"> 

<xsl:sequence select="$context/xhtml:colgroup/@span"/> 

</xsl:when> 

<xsl:otherwise> 

<xsl:sequence select="max(for $x in ($context | $context/* )/xhtml:tr
return count($x/xhtml:td | $x/xhtml:th))"/> 

</xsl:otherwise> 

</xsl:choose> 

</xsl:function> 

<!--
========================================================================
================ --> 

<!-- Function for searching the maximun width for a column - Input:
Context Element, Output: Maximum Double --> 

<!--
========================================================================
================ --> 

<xsl:function name="rh:max_width" as="xs:double"> 

<xsl:param name="context" as="element()"/> 

<xsl:param name="count" as="xs:double"/> 

<xsl:sequence select="max(for $x in ($context/* | $context/*/*
)/(xhtml:td[$count] | xhtml:th[$count])/@width return (if($x castable as
xs:double) then($x) else(replace($x, '[a-z%]', ''))))"/> 

</xsl:function> 

<!-- ===================================================================
--> 

<!-- Suppressed elements --> 

<!-- ===================================================================
--> 

<xsl:template match="xhtml:colgroup | xhtml:*[@id=('rowspan',
'colspan')]" mode="process"/> 

<!-- ===================================================================
--> 

<!-- First Pass - create empty cells for colspans --> 

<!-- ===================================================================
--> 

<xsl:template match="xhtml:*[@colspan]" mode="first-pass"> 

<xhtml:td> 

<xsl:copy-of select="@*"/> 

<xsl:apply-templates mode="first-pass"/> 

</xhtml:td> 

<xsl:for-each select="1 to (xs:integer(@colspan)-1)"> 

<xhtml:td id="colspan"/> 

</xsl:for-each> 

</xsl:template> 

<!-- ===================================================================
--> 

<!-- Second Pass - create empty cells for rowspans --> 

<!-- ===================================================================
--> 

<xsl:template match="xhtml:td[not(@rowspan)] | xhtml:th[not(@rowspan)]"
mode="second-pass"> 

<!-- what is my current position in the row? --> 

<xsl:variable name="currentPos" select="count(preceding-sibling::*)"
as="xs:integer"/> 

<!-- what is the position of the next previous cell with a rowspan
attribute in its row? That can also be 0, if there is no rowspan before
--> 

<xsl:variable name="rowspanBeforePos"
select="preceding::*[self::xhtml:td or
self::xhtml:th][@rowspan][1]/count(preceding-sibling::*)"
as="xs:integer?"/> 

<!-- calculate the difference between the row with the rowspan and my
current row --> 

<xsl:variable name="rowDiff"
select="parent::xhtml:tr/count(preceding-sibling::*) -
preceding::*[@rowspan][1]/parent::xhtml:tr/count(preceding-sibling::*)"
as="xs:integer?"/> 

<!-- number of rows to span --> 

<xsl:variable name="rowSpan"
select="preceding::xhtml:*[@rowspan][1]/@rowspan" as="xs:integer?"/> 

<!-- It's possible that there is a colspan on the row spanning cell,
too, that must be taken into account --> 

<xsl:variable name="colSpan"
select="preceding::xhtml:*[@rowspan][1]/@colspan" as="xs:integer?"/> 

<!-- create empty cell for the rowspan --> 

<xsl:if test="($rowDiff &lt; $rowSpan) and
$currentPos=$rowspanBeforePos"> 

<xhtml:td id="rowspan"/> 

<xsl:for-each select="2 to $colSpan"> 

<xhtml:td id="rowspan"/> 

</xsl:for-each> 

</xsl:if> 

<xsl:copy> 

<xsl:copy-of select="@*"/> 

<xsl:apply-templates mode="second-pass"/> 

</xsl:copy> 

</xsl:template> 

</xsl:stylesheet> 


  _____  

Ich verwende die kostenlose Version von SPAMfighter
<http://www.spamfighter.com/lde> , die bisher
8491 Spammails entfernt und mir so eine Menge Zeit gespart hat.
Rund 6 Millionen Leute nutzen SPAMfighter schon 

Current Thread