Hi again,
I tried to come up with a solution for the colspan and rowspan problem
when doing a HTML to CALS table transformation.
All I could think of was a three pass transformation.
1. create additional table cells for each colspan attribute
(number according to the colspan value)
2. create additional table cells for each rowspan attribute in a preceding row
3. do the transformation an delete previously created cells
Here comes the stylesheet:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY preceding_rowspan_td
"preceding::xhtml:td[count(preceding-sibling::xhtml:td)=current()/count(preceding-sibling::xhtml:td)+1
and @rowspan]">
]>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ln="http://www.lexisnexis.at/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="xsldoc xs
ln xhtml" xmlns:doc="http://docbook.org/ns/docbook">
<!--
===================================================================
-->
<!--
-->
<!-- This Stylesheets converts arbitrary XHTML tables into tables
conforming to the -->
<!-- OASIS CALS model
-->
<!--
-->
<!--
-->
<!-- Author: Roman Huditsch,
roman(_dot_)huditsch(_at_)lexisnexis(_dot_)at
-->
<!--
-->
<!--
===================================================================
-->
<xsl:output method="xhtml" version="1.0" encoding="ISO-8859-1"/>
<xsldoc:author>Roman Huditsch</xsldoc:author>
<xsldoc:date>March 2, 2006</xsldoc:date>
<xsldoc:version>Version 0.9</xsldoc:version>
<xsl:namespace-alias stylesheet-prefix="" result-prefix="xhtml"/>
<!--
=================================================================== -->
<!-- Per default all existing nodes should be copied into the result
document -->
<!--
=================================================================== -->
<xsl:template match="node() | @*" mode="process first-pass second-pass">
<xsl:copy>
<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:copy>
<xsl:namespace name="doc"
select="'http://docbook.org/ns/docbook'"/>
<xsl:namespace name="xhtml"
select="'http://www.w3.org/1999/xhtml'"/>
<xsl:apply-templates select="$second-pass"
mode="process"/>
</xsl:copy>
</xsl:template>
<!--
=================================================================== -->
<!-- Table
-->
<!--
=================================================================== -->
<xsl:template match="xhtml:table" mode="process">
<!-- Create param to indicate border handling to child nodes
-->
<xsl:param name="border" select="not(starts-with(@border,'0'))"
tunnel="yes"/>
<!--
=================================================================== -->
<!-- Continue with processing
-->
<!--
=================================================================== -->
<doc:table>
<xsl:apply-templates select="(@border, @width)"
mode="process"/>
<!-- <tgroup> -->
<doc:tgroup>
<xsl:message select="concat('Max Columns: ',
ln:max_columns(.))"/>
<xsl:call-template name="generate-colspecs">
<xsl:with-param name="max"
select="ln:max_columns(.)" as="xs:double"/>
</xsl:call-template>
</doc:tgroup>
<!-- <thead> -->
<xsl:apply-templates select="xhtml:thead"
mode="process"/>
<!-- <tbody> -->
<doc:tbody>
<xsl:apply-templates select="xhtml:tr |
xhtml:tbody/xhtml:tr" mode="process"/>
</doc:tbody>
<!-- <tfoot> -->
<xsl:apply-templates select="xhtml:tfoot"
mode="process"/>
</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 > $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(ln: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>
<!--
=================================================================== -->
<!-- thead, tfoot
-->
<!--
=================================================================== -->
<xsl:template match="xhtml:thead | xhtml:tfoot" mode="process">
<xsl:element name="{local-name()}"
namespace="http://docbook.org/ns/docbook">
<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
-->
<!--
=================================================================== -->
<xsl:template match="xhtml:td" mode="process">
<xsl:variable name="position"
select="count(preceding-sibling::*) + 1"/>
<doc:entry colname="col{$position}">
<xsl:if test="@colspan > 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 > 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="ln:max_columns" as="xs:double">
<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[(_at_)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))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<!--
==============================================================================
-->
<!-- Function for searching the maximun width for a column - Input:
Context Element, Output: Maximum Double -->
<!--
==============================================================================
-->
<xsl:function name="ln: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:td[(_at_)id=('rowspan',
'colspan')]" mode="process"/>
<!--
=================================================================== -->
<!-- First Pass - create empty cells for colspans
-->
<!--
=================================================================== -->
<xsl:template match="xhtml:*[(_at_)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[&preceding_rowspan_td;]"
mode="second-pass">
<xsl:variable name="rowDiff"
select="parent::xhtml:tr/count(preceding-sibling::*)+1 -
(&preceding_rowspan_td;[1]/parent::xhtml:tr/count(preceding-sibling::*)+1)"
as="xs:integer"/>
<xsl:variable name="rowspan"
select="&preceding_rowspan_td;[1]/@rowspan" as="xs:integer"/>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates mode="second-pass"/>
</xsl:copy>
<xsl:if test="$rowDiff < $rowspan">
<xsl:for-each select="1 to ($rowspan - 1)">
<td id="rowspan"/>
</xsl:for-each>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Every feedback is welcome!
wbr,
Roman
-----Ursprüngliche Nachricht-----
Von: David Carlisle [mailto:davidc(_at_)nag(_dot_)co(_dot_)uk]
Gesendet: Montag, 06. März 2006 13:03
An: xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com
Betreff: Re: [xsl] Stylesheet for converting XHTML tables to CALS
I just finished my first attempt to transform XHTML tables
into tables
conforming to the CALS table model.
Attached is my XSLT 2.0 stylesheet. Every feedback is
heartily welcome
:)
You appear to be generating elements in the xhtml namespace
they should probably be in no-namespace or the new docbook 5
namespace or something, seeing as <entry> etc are not xhtml.
<xsl:param name="border" select="if(starts-with(@border,
'0')) then(xs:boolean(0)) else(xs:boolean(1))" as="xs:boolean"
tunnel="yes"/>
you are starting with a boolean value
starts-with(@border,'0')
then if it is true, taking an integer literal and coercing it
to a boolean. If you need boolean values you can just use
true() and false() but here you just need <xsl:param
name="border" select="not(starts-with(@border,'0'))"/>
<xsl:apply-templates
select="xhtml:tr[not(parent::*[local-name()=('thead',
'tbody', 'tfoot')])] | xhtml:tbody/xhtml:tr"/>
It's best not to use local-name() in such tests but just to
use name tests (which are namespace aware) however in this
case the current element is <table> so the parent of every
element selected by xhtml:tr is table and so the filter
testing on local-name is not doing anything.
so it could be
select="xhtml:tr| xhtml:tbody/xhtml:tr"/>
In
<xsl:template match="xhtml:th | xhtml:td">
<xsl:variable name="position"
select="count(preceding-sibling::*) + 1"/>
<entry>
<xsl:if test="@colspan > 1">
<xsl:attribute name="namest">
<xsl:value-of
select="concat('col',$position)"/>
don't you need to take account of any colspan attributes in
earlier columns, and rowspan attributes in earlier rows in
order to know which coulmn an entry in a table corresponds
to? (This is the hardest part of switching between html and
cals tables). In the above you are assuming that the second
td entry in a row is corresponding to the second column, but
this is not the case if the first entry spans columns, or if
an entry in an earlier row spans into the first cell of this row.
David
______________________________________________________________
__________
This e-mail has been scanned for all viruses by Star. The
service is powered by MessageLabs. For more information on a
proactive anti-virus service working around the clock, around
the globe, visit:
http://www.star.net.uk
______________________________________________________________
__________
--~------------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail:
<mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--
--~------------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--