Subject: AW: Stylesheet for converting XHTML tables to CALS
From: "Huditsch, Roman \(LNG-VIE\)" <Roman.Huditsch@xxxxxxxxxxxxx>
Date: Wed, 8 Mar 2006 14:54:48 +0100
|
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(prece
ding-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.huditsch@xxxxxxxxxxxxx -->
<!-- -->
<!-- ===================================================================
-->
<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[@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[@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[&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@xxxxxxxxx]
> Gesendet: Montag, 06. Mdrz 2006 13:03
> An: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
> Betreff: Re: 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
> ______________________________________________________________
> __________
|