<?xml version="1.0" encoding="utf-8"?>
<!--
Tabular structures (LaTeX {tabular}, HTML <table>). Note that this is distinct from "tables" in general, which correspond to the {table} environment in LaTeX (i.e., floating).
-->
<stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!--
Tabular structures (LaTeX {tabular}, HTML <table>).
@align: The alignment of the table as a whole.
'left' [default]
'center' | 'centre'
'right'
@valign (LaTeX only): Vertical alignment of the tabular within the paragraph.
'top'
'center' | 'centre' [default]
'bottom'
@border (HTML only): Width of cell border for HTML tables.
@scale (LaTeX only): Scaling factor for the tabular.
@rotate (LaTeX only): Rotation angle of tabular in degrees anti-clockwise.
@long-table (LaTeX only): Use the longtable environment instead of tabular so it can span multiple pages.
'no' [default]
'yes'
If @long-table is 'yes', the following changes occur:
tabular-header is repeated on every page of the longtable (\endhead)
tabular-header with @first = 'yes' appears on the first page of the longtable (\endfirsthead)
tabular-footer is repeated on every page of the longtable (\endfoot)
tabular-footer with @last = 'yes' appears on the last page of the longtable (\endlastfoot)
caption is available as a sub-element
-->
<template name="tabular" match="tabular">
<common formats="/latex/xelatex/">
<!-- spacing -->
<xsl:call-template name="newline-internal" />
<xsl:call-template name="newline-internal" />
<!-- Overall tabular alignment. -->
<xsl:if test="@align">
<xsl:text>\begin{</xsl:text>
<xsl:choose>
<xsl:when test="@align = ('left', 'right')">
<xsl:text>flush</xsl:text><xsl:value-of select="@align" />
</xsl:when>
<xsl:when test="@align = ('center', 'centre')">
<xsl:text>center</xsl:text>
</xsl:when>
</xsl:choose>
<xsl:text>}</xsl:text>
</xsl:if>
<!-- tabular rotation -->
<xsl:if test="@rotate">
<xsl:text>\rotatebox{</xsl:text>
<xsl:value-of select="@rotate" />
<xsl:text>}{</xsl:text>
</xsl:if>
<!-- tabular scaling -->
<xsl:if test="@scale">
<xsl:text>\scalebox{</xsl:text>
<xsl:value-of select="@scale" />
<xsl:text>}{</xsl:text>
</xsl:if>
<xsl:text>\begin{</xsl:text>
<xsl:value-of select="
if (@long-table = ('yes', 'y', 'true', 't', '1')) then 'longtable'
else 'tabular'" />
<xsl:text>}</xsl:text>
<!-- vertical alignment -->
<xsl:if test="@valign = ('top', 'bottom')">
<xsl:text>[</xsl:text>
<xsl:value-of select="substring(@valign, 1, 1)" />
<xsl:text>]</xsl:text>
</xsl:if>
<xsl:text>{</xsl:text>
<xsl:apply-templates select="tabular-columns" />
<xsl:text>}</xsl:text>
<xsl:apply-templates select="tabular-header" />
<!-- For long tables, we need to do the headers and footers first. -->
<xsl:choose>
<xsl:when test="@long-table = ('yes', 'y', 'true', 't', '1')">
<xsl:apply-templates select="tabular-footer" />
<xsl:apply-templates select="tabular-body" />
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="tabular-body" />
<xsl:apply-templates select="tabular-footer" />
</xsl:otherwise>
</xsl:choose>
<xsl:if test="@long-table = ('yes', 'y', 'true', 't', '1') and count(caption) ne 0">
<xsl:text>\caption{</xsl:text>
<xsl:apply-templates select="caption" />
<xsl:text>}</xsl:text>
<xsl:call-template name="newline-internal" />
<xsl:if test="@label">
<xsl:text>\label{</xsl:text>
<xsl:value-of select="@label" />
<xsl:text>}</xsl:text>
<xsl:call-template name="newline-internal" />
</xsl:if>
</xsl:if>
<xsl:text>\end{</xsl:text>
<xsl:value-of select="
if (@long-table = ('yes', 'y', 'true', 't', '1')) then 'longtable'
else 'tabular'" />
<xsl:text>}</xsl:text>
<xsl:if test="@scale"><xsl:text>}</xsl:text></xsl:if>
<xsl:if test="@rotate"><xsl:text>}</xsl:text></xsl:if>
<xsl:if test="@align">
<xsl:text>\end{</xsl:text>
<xsl:choose>
<xsl:when test="@align = ('left', 'right')">
<xsl:text>flush</xsl:text>
<xsl:value-of select="@align" />
</xsl:when>
<xsl:when test="@align = ('center', 'centre')">
<xsl:text>center</xsl:text>
</xsl:when>
</xsl:choose>
<xsl:text>}</xsl:text>
</xsl:if>
<!-- spacing -->
<xsl:call-template name="newline-internal" />
<xsl:call-template name="newline-internal" />
</common>
<common formats="/html/xhtml/">
<table>
<xsl:attribute name="border">
<xsl:value-of select="@border" />
<xsl:if test="not(@border)">0</xsl:if>
</xsl:attribute>
<xsl:attribute name="cellspacing">0</xsl:attribute>
<xsl:attribute name="style">
<xsl:text>border-collapse: collapse; </xsl:text>
<xsl:choose>
<xsl:when test="@align = ('center', 'centre')">
<xsl:text>margin-left:auto; margin-right: auto; </xsl:text>
</xsl:when>
<xsl:when test="@align eq 'right'">
<xsl:text>margin-left:auto; </xsl:text>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</xsl:attribute>
<!--
Note different ordering of tabular components: HTML requires THEAD and TFOOT to precede TBODY.
-->
<xsl:apply-templates select="tabular-header" />
<xsl:apply-templates select="tabular-footer" />
<xsl:apply-templates select="tabular-body" />
</table>
</common>
</template>
<!--
Specify column formatting, mainly for LaTeX, although HTML does get <td> ALIGN values from here.
@align: The alignment of this particular column.
'left' [default]
'center' | 'centre'
'right'
@left-border: Set to '|' to include a column separator to the left of this column.
@right-border: Set to '|' to include a column separator to the right of this column.
Careful: including a right-border on a cell and a left-border on the next cell will produce '||', not '|'. BUT, when doing multi-column or multi-row cells, always include both borders if required, because \multicolumn overrides the default border specification.
-->
<template name="aligned-tabular-column" match="tabular-columns/column[@align]">
<common formats="/latex/xelatex/">
<xsl:value-of select="@left-border" />
<xsl:value-of select="substring(@align, 1, 1)" />
<xsl:value-of select="@right-border" />
</common>
</template>
<template name="unaligned-tabular-column" match="tabular-columns/column[not(@align)]">
<common formats="/latex/xelatex/">
<xsl:value-of select="@left-border" />
<xsl:text>l</xsl:text>
<xsl:value-of select="@right-border" />
</common>
</template>
<!-- Tabular header. -->
<template name="tabular-header" match="tabular-header">
<common formats="/latex/xelatex/">
<xsl:apply-templates />
<xsl:value-of select="
if (../@long-table = ('yes', 'y', 'true', 't', '1')) then
if (@first = ('yes', 'y', 'true', 't', '1')) then '\endfirsthead'
else '\endhead'
else ''" />
</common>
<common formats="/html/xhtml/">
<thead>
<xsl:apply-templates />
</thead>
</common>
</template>
<!-- Tabular footer. -->
<template name="tabular-footer" match="tabular-footer">
<common formats="/latex/xelatex/">
<xsl:apply-templates />
<xsl:value-of select="
if (../@long-table = ('yes', 'y', 'true', 't', '1')) then
if (@last = ('yes', 'y', 'true', 't', '1')) then '\endlastfoot'
else '\endfoot'
else ''" />
</common>
<common formats="/html/xhtml/">
<tfoot>
<xsl:apply-templates />
</tfoot>
</common>
</template>
<!-- Tabular main body. -->
<template name="tabular-body" match="tabular-body">
<common formats="/latex/xelatex/"><xsl:apply-templates /></common>
<common formats="/html/xhtml/">
<tbody>
<xsl:apply-templates />
</tbody>
</common>
</template>
<!--
A single row of a tabular.
@page-break: Inhibit page breaks after this row (LaTeX only).
-->
<template name="row" match="row">
<common formats="/latex/xelatex/">
<xsl:apply-templates />
<xsl:text> \\</xsl:text>
<xsl:if test="@page-break = ('no', 'n', 'false', 'f', '0')"><xsl:text>*</xsl:text></xsl:if>
<xsl:text> </xsl:text>
</common>
<common formats="/html/xhtml/">
<tr>
<xsl:if test="@valign">
<xsl:attribute name="valign"><xsl:value-of select="@valign" /></xsl:attribute>
</xsl:if>
<xsl:apply-templates />
</tr>
</common>
</template>
<!--
This template handles the following cases:
The last row of a tabular in (Xe)LaTeX shouldn't have a \\ at the end, /unless/ you want a horizontal rule (\hline) under the last row (Lamport, p. 62). This template handles the following cases:
Case 1: The last row of a tabular-body, without a row-rule following it, and no tabular-footer.
Case 2: The last row of a tabular-footer, without a row-rule following it.
-->
<template name="last-row-body" match="tabular-body/row[( position() = last() ) and not( following-sibling::row-rule ) and not( ancestor::tabular/tabular-footer )]|tabular-footer/row[position() = last() and not( following-sibling::row-rule )]">
<common formats="/latex/xelatex/">
<xsl:apply-templates />
<xsl:text> </xsl:text>
</common>
<common formats="/html/xhtml/">
<xsl:call-template name="row" />
</common>
</template>
<!--
Horizontal rules (LaTeX only).
@columns: The column range to draw the rule across. Specify as you would for a \cline in LaTeX, e.g., '3-5'. If omitted, the rule is drawn across all columns.
@weight: The weight of the rule. Note that double weight isn't available for partial rules in LaTeX, because repeated \cline macros simply draw over the top of each other.
'single' [default]
'double' = a double rule
-->
<template name="row-rule-full" match="row-rule[not(@columns)]">
<common formats="/latex/xelatex/">
<xsl:text>\hline</xsl:text>
<xsl:if test="@weight eq 'double'"><xsl:text>\hline</xsl:text></xsl:if>
<xsl:text> </xsl:text>
</common>
<common formats="/html/xhtml/">
<tr>
<td>
<xsl:attribute name="style">
<xsl:text>border-bottom: 1px solid black; </xsl:text>
<!--
This is a bit of a hack to get a double border,
as some browsers don't support the "double" border
style yet. It looks slightly ugly, but works.
-->
<xsl:if test="@weight eq 'double'">
<xsl:text>border-top: 1px solid black; </xsl:text>
</xsl:if>
</xsl:attribute>
<xsl:attribute name="colspan">
<xsl:value-of select="count(ancestor::tabular/tabular-columns/column)" />
</xsl:attribute>
</td>
</tr>
</common>
</template>
<template name="row-rule-partial" match="row-rule[@columns]">
<common formats="/latex/xelatex/">
<!-- Tokenise the column specifications for later reference. -->
<xsl:variable name="column-specs" select="tokenize(@columns, ',')" />
<xsl:for-each select="$column-specs">
<xsl:text>\cline{</xsl:text>
<xsl:value-of select="." />
<!-- Normalise the value to a range if necessary. -->
<xsl:if test="not(contains(., '-'))">
<xsl:text>-</xsl:text>
<xsl:value-of select="." />
</xsl:if>
<xsl:text>}</xsl:text>
</xsl:for-each>
</common>
<common formats="/html/xhtml/">
<!-- Tokenise the column specifications for later reference. -->
<xsl:variable name="column-specs" select="tokenize(@columns, '[-,]')" />
<!--
We need to store the value of @weight, because the context will be changed by the for-each loop below. Default to "single" in any case.
-->
<xsl:variable name="weight">
<xsl:value-of select="@weight" />
<xsl:if test="not(@weight)">
<xsl:text>single</xsl:text>
</xsl:if>
</xsl:variable>
<tr>
<!--
Loop through all of the columns in the table, testing to see whether the column index exists in $column-specs. If so, add a border to the cell. Note that this loop changes the current context!
-->
<xsl:for-each select="1 to count(ancestor::tabular/tabular-columns/column)">
<td>
<xsl:if test="exists(index-of($column-specs, . cast as xs:string))">
<xsl:attribute name="style">
<xsl:text>border-bottom: 1px solid black; </xsl:text>
<xsl:if test="$weight eq 'double'">
<xsl:text>border-top: 1px solid black; </xsl:text>
</xsl:if>
</xsl:attribute>
</xsl:if>
</td>
</xsl:for-each>
</tr>
</common>
</template>
<!--
Multi-row cells (LaTeX only, as this is pretty trivial to achieve in HTML).
@rows: The number of rows this cell spans.
@header: Is this a header cell? [yes, NO]
-->
<!--
Hmm, the multi-row stuff is somewhat broken in LaTeX, oops. Need to insert missing columns (as was done in the calendar XSL) when a multi-row cell is encountered.
Also need to sort out \hlines in the presence of multi-row cells and also what happens to vertical cell borders :(.
This probably needs reworking.
-->
<template name="multirow-cell" match="cell" mode="latex-multi-row">
<common formats="/latex/xelatex/">
<xsl:text>\multirow{</xsl:text>
<xsl:value-of select="@rows" />
<xsl:text>}{*}{</xsl:text>
<xsl:if test="@header = ('yes', 'y', 'true', 't', '1')"><xsl:text>\textbf{</xsl:text></xsl:if>
<!-- check for embedded line breaks, if so, embed another tabular inside this one -->
<xsl:if test="count(child::br) ne 0">
<xsl:text>\begin{tabular}{@{}</xsl:text>
<xsl:choose>
<xsl:when test="@align">
<xsl:value-of select="substring(@align, 1, 1)" />
</xsl:when>
<xsl:otherwise>
<xsl:text>l</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text>@{}}</xsl:text>
</xsl:if>
<xsl:apply-templates />
<xsl:if test="count(child::br) ne 0">
<xsl:text>\end{tabular}</xsl:text>
<xsl:call-template name="newline-internal" />
</xsl:if>
<xsl:if test="@header = ('yes', 'y', 'true', 't', '1')"><xsl:text>}</xsl:text></xsl:if>
<xsl:text>}</xsl:text>
</common>
</template>
<!--
Multi-column cells (LaTeX only, as this is pretty trivial to achieve in HTML).
$num-columns: The number of columns this cell spans.
@header: Is this a header cell? [yes, NO]
-->
<template name="multicolumn-cell" match="cell" mode="latex-multi-column">
<common formats="/latex/xelatex/">
<!--
TODO: Not quite sure why the number of columns is a parameter rather than just using the @columns attribute? Something to do with defaults perhaps?
-->
<xsl:param name="num-columns">1</xsl:param>
<xsl:text>\multicolumn{</xsl:text>
<xsl:value-of select="$num-columns" />
<xsl:text>}{</xsl:text>
<xsl:value-of select="@left-border" />
<xsl:choose>
<xsl:when test="@align">
<xsl:value-of select="substring(@align, 1, 1)" />
</xsl:when>
<xsl:otherwise>
<xsl:text>l</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="@right-border" />
<xsl:text>}{</xsl:text>
<xsl:choose>
<xsl:when test="@rows">
<xsl:apply-templates select="." mode="multi-row" />
</xsl:when>
<xsl:otherwise>
<xsl:if test="@header = ('yes', 'y', 'true', 't', '1')"><xsl:text>\textbf{</xsl:text></xsl:if>
<!-- check for embedded line breaks, if so, embed another tabular inside this one -->
<xsl:if test="count(child::br) ne 0">
<xsl:text>\begin{tabular}{@{}</xsl:text>
<xsl:choose>
<xsl:when test="@align">
<xsl:value-of select="substring(@align, 1, 1)" />
</xsl:when>
<xsl:otherwise>
<xsl:text>l</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text>@{}}</xsl:text>
</xsl:if>
<xsl:apply-templates />
<xsl:if test="count(child::br) ne 0">
<xsl:text>\end{tabular}</xsl:text>
<xsl:call-template name="newline-internal" />
</xsl:if>
<xsl:if test="@header = ('yes', 'y', 'true', 't', '1')"><xsl:text>}</xsl:text></xsl:if>
</xsl:otherwise>
</xsl:choose>
<xsl:text>}</xsl:text>
</common>
</template>
<!--
Top-level template for generating cells.
@columns: The number of columns this cell spans.
@rows: The number of rows this cell spans.
@align: The alignment of this cell. [LEFT, center, right]
@header: Is this a header cell? [yes, NO]
-->
<template match="cell">
<common>
<!-- position() doesn't seem to work very well in this context. -->
<xsl:variable name="column-no"><xsl:number /></xsl:variable>
</common>
<!--
Doing this sensibly for LaTeX is actually pretty ugly, because different attribute combinations produce different code :(.
This is the sort of algorithm that can only really be clearly described by a flow chart :).
-->
<common formats="/latex/xelatex/">
<xsl:choose>
<xsl:when test="@columns">
<xsl:apply-templates select="." mode="latex-multi-column">
<xsl:with-param name="num-columns" select="@columns" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="@align">
<xsl:apply-templates select="." mode="latex-multi-column" />
</xsl:when>
<xsl:when test="@rows">
<xsl:apply-templates select="." mode="latex-multi-row" />
</xsl:when>
<xsl:otherwise>
<xsl:if test="@header = ('yes', 'y', 'true', 't', '1')"><xsl:text>\textbf{</xsl:text></xsl:if>
<!-- check for embedded line breaks, if so, embed another tabular inside this one -->
<xsl:if test="count(child::br) ne 0">
<xsl:text>\begin{tabular}{@{}</xsl:text>
<xsl:choose>
<xsl:when test="@align">
<xsl:value-of select="substring(@align, 1, 1)" />
</xsl:when>
<xsl:otherwise>
<xsl:text>l</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text>@{}}</xsl:text>
</xsl:if>
<xsl:apply-templates />
<xsl:if test="count(child::br) ne 0">
<xsl:text>\end{tabular}</xsl:text>
<xsl:call-template name="newline-internal" />
</xsl:if>
<xsl:if test="@header = ('yes', 'y', 'true', 't', '1')"><xsl:text>}</xsl:text></xsl:if>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="$column-no != last()">
<xsl:call-template name="tabular-column-separator" />
</xsl:if>
</common>
<!--
It's much easier in HTML, because a <td> is a <td> is a <td>, regardless of the attributes supplied.
-->
<common formats="/html/xhtml/">
<!--
Hmm, how to generate either a TD or TH as required, including attributes, without repeating code?
Aha, the answer is attribute value templates!
-->
<xsl:variable name="celltype">
<xsl:choose>
<xsl:when test="@header = ('yes', 'y', 'true', 't', '1')">th</xsl:when>
<xsl:otherwise>td</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$celltype}">
<xsl:attribute name="align">
<xsl:value-of select="@align" />
<xsl:if test="not(@align)">
<xsl:value-of select="ancestor::tabular/tabular-columns/column[position() = $column-no]/@align" />
<xsl:if test="not(ancestor::tabular/tabular-columns/column[position() = $column-no]/@align)">
<xsl:text>left</xsl:text>
</xsl:if>
</xsl:if>
</xsl:attribute>
<xsl:attribute name="valign">
<xsl:value-of select="@valign" />
<xsl:if test="not(@valign)"><xsl:text>middle</xsl:text></xsl:if>
</xsl:attribute>
<xsl:attribute name="colspan">
<xsl:value-of select="@columns" />
<xsl:if test="not(@columns)"><xsl:text>1</xsl:text></xsl:if>
</xsl:attribute>
<xsl:attribute name="rowspan">
<xsl:value-of select="@rows" />
<xsl:if test="not(@rows)"><xsl:text>1</xsl:text></xsl:if>
</xsl:attribute>
<xsl:apply-templates />
</xsl:element>
</common>
</template>
<!-- Long table captions are only relevant in (Xe)LaTeX. -->
<template name="long-table-caption" match="tabular[@long-table = ('yes', 'y', 'true', 't', '1')]/caption">
<common formats="/latex/xelatex/">
<xsl:apply-templates />
</common>
</template>
</stylesheet>