AW: [xsl] Stylesheet for converting XHTML tables to CALS

Subject: AW: [xsl] 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 &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(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 &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="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 &lt; $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: [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 &gt; 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
> ______________________________________________________________
> __________

Current Thread