<?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. --> <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') or (@align = 'right')"> <xsl:text>flush</xsl:text><xsl:value-of select="@align" /> </xsl:when> <xsl:when test="(@align = 'center') or (@align = '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{tabular}</xsl:text> <!-- vertical alignment --> <xsl:if test="@valign = 'top' or @valign = '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" /> <xsl:apply-templates select="tabular-body" /> <xsl:apply-templates select="tabular-footer" /> <xsl:text>\end{tabular}</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') or (@align = 'right')"> <xsl:text>flush</xsl:text> <xsl:value-of select="@align" /> </xsl:when> <xsl:when test="(@align = 'center') or (@align = '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'"> <xsl:text>margin-left:auto; margin-right: auto; </xsl:text> </xsl:when> <xsl:when test="@align='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 /></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 /></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. @no-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="@no-page-break = 'yes'"><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 = '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 = '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 = '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'"><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) != 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) != 0"> <xsl:text>\end{tabular}</xsl:text> <xsl:call-template name="newline-internal" /> </xsl:if> <xsl:if test="@header = 'yes'"><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'"><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) != 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) != 0"> <xsl:text>\end{tabular}</xsl:text> <xsl:call-template name="newline-internal" /> </xsl:if> <xsl:if test="@header = 'yes'"><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'"><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) != 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) != 0"> <xsl:text>\end{tabular}</xsl:text> <xsl:call-template name="newline-internal" /> </xsl:if> <xsl:if test="@header = 'yes'"><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'">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> </stylesheet>