]> granicus.if.org Git - docbook-dsssl/commitdiff
Adjust man stylesheet to better handle namespace fixup during
authorBob Stayton <bobs@sagehill.net>
Wed, 29 Jan 2014 19:19:48 +0000 (19:19 +0000)
committerBob Stayton <bobs@sagehill.net>
Wed, 29 Jan 2014 19:19:48 +0000 (19:19 +0000)
distro builds.

xsl/manpages/docbook.xsl
xsl/manpages/table.xsl
xsl/manpages/tbl.xsl [new file with mode: 0755]

index 6f39f7cfe6238866b174d15d9441e512c9375c45..575887b8dbaf449b669ec5024129a68ae7684107 100644 (file)
 
   <!-- ==================================================================== -->
 
-  <xsl:template match="/">
-    <!-- * Get a title for current doc so that we let the user -->
-    <!-- * know what document we are processing at this point. -->
-    <xsl:variable name="doc.title">
-      <xsl:call-template name="get.doc.title"/>
-    </xsl:variable>
-    <xsl:choose>
-      <!-- * when we find a namespaced document, strip the -->
-      <!-- * namespace and then continue processing it. -->
-      <xsl:when test="//self::db:*">
-        <xsl:call-template name="log.message">
-          <xsl:with-param name="level">Note</xsl:with-param>
-          <xsl:with-param name="source" select="$doc.title"/>
-          <xsl:with-param name="context-desc">
-            <xsl:text>namesp. cut</xsl:text>
-          </xsl:with-param>
-          <xsl:with-param name="message">
-            <xsl:text>stripped namespace before processing</xsl:text>
-          </xsl:with-param>
-        </xsl:call-template>
-        <xsl:variable name="stripns">
-          <xsl:apply-templates mode="stripNS"/>
-        </xsl:variable>
-        <xsl:call-template name="log.message">
-          <xsl:with-param name="level">Note</xsl:with-param>
-          <xsl:with-param name="source" select="$doc.title"/>
-          <xsl:with-param name="context-desc">
-            <xsl:text>namesp. cut</xsl:text>
-          </xsl:with-param>
-          <xsl:with-param name="message">
-            <xsl:text>processing stripped document</xsl:text>
-          </xsl:with-param>
-        </xsl:call-template>
-        <xsl:apply-templates select="exsl:node-set($stripns)"/>
-      </xsl:when>
-      <xsl:when test="//*[local-name() = 'refentry']">
-        <!-- * Check to see if we have any refentry children in this -->
-        <!-- * document; if so, process them. The reason we use -->
-        <!-- * local-name()=refentry (instead of just //refentry) to to -->
-        <!-- * check for refentry children is because this stylsheet is -->
-        <!-- * also post-processed by the stylesheet build to create the -->
-        <!-- * manpages/profile-docbook.xsl, and the refentry child check -->
-        <!-- * in the profile-docbook.xsl stylesheet won't work if we do -->
-        <!-- * a simple //refentry check. -->
-        <xsl:apply-templates select="//refentry"/>
-        <!-- * if $man.output.manifest.enabled is non-zero, -->
-        <!-- * generate a manifest file -->
-        <xsl:if test="not($man.output.manifest.enabled = 0)">
-          <xsl:call-template name="generate.manifest">
-            <xsl:with-param name="filename">
-              <xsl:choose>
-                <xsl:when test="not($man.output.manifest.filename = '')">
-                  <!-- * If a name for the manifest file is specified, -->
-                  <!-- * use that name. -->
-                  <xsl:value-of select="$man.output.manifest.filename"/>
-                </xsl:when>
-                <xsl:otherwise>
-                  <!-- * Otherwise, if user has unset -->
-                  <!-- * $man.output.manifest.filename, default to -->
-                  <!-- * using "MAN.MANIFEST" as the filename. Because -->
-                  <!-- * $man.output.manifest.enabled is non-zero and -->
-                  <!-- * so we must have a filename in order to -->
-                  <!-- * generate the manifest. -->
-                  <xsl:text>MAN.MANIFEST</xsl:text>
-                </xsl:otherwise>
-              </xsl:choose>
-            </xsl:with-param>
-          </xsl:call-template>
-        </xsl:if>
-      </xsl:when>
-      <xsl:otherwise>
-        <!-- * Otherwise, the document does not contain any -->
-        <!-- * refentry elements, so log/emit message and stop. -->
-        <xsl:call-template name="log.message">
-          <xsl:with-param name="level">Erro</xsl:with-param>
-          <xsl:with-param name="source" select="$doc.title"/>
-          <xsl:with-param name="context-desc">
-            <xsl:text> no refentry</xsl:text>
-          </xsl:with-param>
-          <xsl:with-param name="message">
-            <xsl:text>No refentry elements found</xsl:text>
-            <xsl:if test="$doc.title != ''">
-            <xsl:text> in "</xsl:text>
-              <xsl:choose>
-                <xsl:when test="string-length($doc.title) &gt; 30">
-                  <xsl:value-of select="substring($doc.title,1,30)"/>
-                  <xsl:text>...</xsl:text>
-                </xsl:when>
-                <xsl:otherwise>
-                  <xsl:value-of select="$doc.title"/>
-                </xsl:otherwise>
-              </xsl:choose>
-              <xsl:text>"</xsl:text>
-            </xsl:if>
-            <xsl:text>.</xsl:text>
+<xsl:template match="/">
+  <!-- * Get a title for current doc so that we let the user -->
+  <!-- * know what document we are processing at this point. -->
+  <xsl:variable name="doc.title">
+   <xsl:call-template name="get.doc.title"/>
+  </xsl:variable>
+  <xsl:choose>
+    <!-- fix namespace if necessary -->
+    <xsl:when test="$exsl.node.set.available != 0 and 
+                  namespace-uri(/*) = 'http://docbook.org/ns/docbook'">
+      <xsl:call-template name="log.message">
+        <xsl:with-param name="level">Note</xsl:with-param>
+        <xsl:with-param name="source" select="$doc.title"/>
+        <xsl:with-param name="context-desc">
+          <xsl:text>namesp. cut</xsl:text>
+        </xsl:with-param>
+        <xsl:with-param name="message">
+          <xsl:text>stripped namespace before processing</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+      <!-- DEBUG: uncomment to save namespace-fixed document.
+      <xsl:message>Saving namespace-fixed document.</xsl:message>
+      <xsl:call-template name="write.chunk">
+        <xsl:with-param name="filename" select="'namespace-fixed.debug.xml'"/>
+        <xsl:with-param name="method" select="'xml'"/>
+        <xsl:with-param name="content">
+          <xsl:copy-of select="$no.namespace"/>
+        </xsl:with-param>
+      </xsl:call-template>
+      -->
+      <xsl:apply-templates select="exsl:node-set($no.namespace)"/>
+    </xsl:when>
+    <!-- Can't process unless namespace fixed with exsl node-set()-->
+    <xsl:when test="namespace-uri(/*) = 'http://docbook.org/ns/docbook'">
+      <xsl:message terminate="yes">
+        <xsl:text>Unable to strip the namespace from DB5 document,</xsl:text>
+        <xsl:text> cannot proceed.</xsl:text>
+      </xsl:message>
+    </xsl:when>
+
+    <xsl:when test="//*[local-name() = 'refentry']">
+      <!-- * Check to see if we have any refentry children in this -->
+      <!-- * document; if so, process them. The reason we use -->
+      <!-- * local-name()=refentry (instead of just //refentry) to to -->
+      <!-- * check for refentry children is because this stylsheet is -->
+      <!-- * also post-processed by the stylesheet build to create the -->
+      <!-- * manpages/profile-docbook.xsl, and the refentry child check -->
+      <!-- * in the profile-docbook.xsl stylesheet won't work if we do -->
+      <!-- * a simple //refentry check. -->
+      <xsl:apply-templates select="//refentry"/>
+      <!-- * if $man.output.manifest.enabled is non-zero, -->
+      <!-- * generate a manifest file -->
+      <xsl:if test="not($man.output.manifest.enabled = 0)">
+        <xsl:call-template name="generate.manifest">
+          <xsl:with-param name="filename">
+            <xsl:choose>
+              <xsl:when test="not($man.output.manifest.filename = '')">
+                <!-- * If a name for the manifest file is specified, -->
+                <!-- * use that name. -->
+                <xsl:value-of select="$man.output.manifest.filename"/>
+              </xsl:when>
+              <xsl:otherwise>
+                <!-- * Otherwise, if user has unset -->
+                <!-- * $man.output.manifest.filename, default to -->
+                <!-- * using "MAN.MANIFEST" as the filename. Because -->
+                <!-- * $man.output.manifest.enabled is non-zero and -->
+                <!-- * so we must have a filename in order to -->
+                <!-- * generate the manifest. -->
+                <xsl:text>MAN.MANIFEST</xsl:text>
+              </xsl:otherwise>
+            </xsl:choose>
           </xsl:with-param>
         </xsl:call-template>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:template>
+      </xsl:if>
+    </xsl:when>
+    <xsl:otherwise>
+      <!-- * Otherwise, the document does not contain any -->
+      <!-- * refentry elements, so log/emit message and stop. -->
+      <xsl:call-template name="log.message">
+        <xsl:with-param name="level">Erro</xsl:with-param>
+        <xsl:with-param name="source" select="$doc.title"/>
+        <xsl:with-param name="context-desc">
+          <xsl:text> no refentry</xsl:text>
+        </xsl:with-param>
+        <xsl:with-param name="message">
+          <xsl:text>No refentry elements found</xsl:text>
+          <xsl:if test="$doc.title != ''">
+          <xsl:text> in "</xsl:text>
+            <xsl:choose>
+              <xsl:when test="string-length($doc.title) &gt; 30">
+                <xsl:value-of select="substring($doc.title,1,30)"/>
+                <xsl:text>...</xsl:text>
+              </xsl:when>
+              <xsl:otherwise>
+                <xsl:value-of select="$doc.title"/>
+              </xsl:otherwise>
+            </xsl:choose>
+            <xsl:text>"</xsl:text>
+          </xsl:if>
+          <xsl:text>.</xsl:text>
+        </xsl:with-param>
+      </xsl:call-template>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
 
   <!-- ============================================================== -->
 
index cc747cc83930f306c7d860b4120ef747f130ff8a..bb2c246c858915ab35ca1b82074d104efd56a608 100644 (file)
   <xsl:param name="tbl.font.title">B</xsl:param>
   <xsl:param name="tbl.font.headings">B</xsl:param>
   -->
+  <!-- import the templates that match on non-namespace HTML
+  elements and produce tbl markup. They
+  are separated so the namespace prefix is not added to them. -->
+  <xsl:include href="tbl.xsl"/>
+
   <xsl:param name="tbl.running.header.from.thead" select="0"/>
   <xsl:param name="tbl.column.separator.char">:</xsl:param>
 
@@ -93,7 +98,7 @@
           <xsl:for-each select="descendant-or-self::table|descendant-or-self::informaltable">
             <xsl:element name="{local-name(..)}">
               <table>
-                <xsl:copy-of select="*"/>
+                <xsl:apply-templates mode="strip.namespace" select="*"/>
               </table>
             </xsl:element>
           </xsl:for-each>
     </xsl:param>
     <xsl:param name="contents" select="exsl:node-set($html-table-output)"/>
 
-    <!-- ==================================================================== -->
-    <!-- *                       Output the table -->
-    <!-- ==================================================================== -->
-    <!-- * -->
-    <!-- * This is the "driver" part of the code; it calls a series of named
-         * templates (further below) to generate the actual tbl(1) markup, -->
-    <!-- * including the optional "options line", required "format section", -->
-    <!-- * and then the actual contents of the table. -->
-    <!-- * -->
-    <!-- ==================================================================== -->
-
-    <xsl:for-each select="$contents//table">
-      <!-- * ============================================================== -->
-      <!-- *   Output table title                                           -->
-      <!-- * ============================================================== -->
-      <xsl:if test="$title != '' or parent::td">
-        <xsl:text>.sp&#10;</xsl:text>
-        <xsl:call-template name="pinch.together"/>
-        <xsl:text>.</xsl:text>
-        <xsl:value-of select="$tbl.font.title"/>
-        <xsl:text> </xsl:text>
-        <xsl:if test="parent::td">
-          <xsl:text>*[nested&#x2580;table]</xsl:text>
-        </xsl:if>
-        <xsl:value-of select="normalize-space($title)"/>
-        <xsl:text>&#10;</xsl:text>
-      </xsl:if>
-      
-      <!-- * mark the start of the table -->
-      <!-- * "TS" = "table start" -->
-      <xsl:text>.TS</xsl:text>
-      <xsl:if test="thead and $tbl.running.header.from.thead">
-        <!-- * H = "has header" -->
-        <xsl:text> H</xsl:text>
-      </xsl:if>
-      <xsl:text>&#10;</xsl:text>
-
-      <!-- * ============================================================== -->
-      <!-- *   Output "options line"                                         -->
-      <!-- * ============================================================== -->
-      <xsl:variable name="options-line">
-        <xsl:value-of select="$allbox"/>
-        <xsl:value-of select="$center"/>
-        <xsl:value-of select="$expand"/>
-        <xsl:text>tab(</xsl:text>
-        <xsl:value-of select="$tbl.column.separator.char"/>
-        <xsl:text>)</xsl:text>
-      </xsl:variable>
-      <xsl:if test="normalize-space($options-line) != ''">
-        <xsl:value-of select="normalize-space($options-line)"/>
-        <xsl:text>;&#10;</xsl:text>
-      </xsl:if>
-
-      <!-- * ============================================================== -->
-      <!-- *   Output table header rows                                     -->
-      <!-- * ============================================================== -->
-      <xsl:if test="thead">
-        <xsl:call-template name="output.rows">
-          <xsl:with-param name="rows" select="thead/tr"/>
-        </xsl:call-template> 
-        <xsl:text>&#10;</xsl:text>
-
-        <!-- * mark the end of table-header rows -->
-        <xsl:choose>
-          <xsl:when test="$tbl.running.header.from.thead">
-            <!-- * "TH" = "table header end" -->
-            <xsl:text>.TH&#10;</xsl:text>
-          </xsl:when>
-          <xsl:otherwise>
-            <!-- * "T&" = "table continuation" and is meant just as a kind -->
-            <!-- * of convenience macro and is sorta equivalent to a "TE" -->
-            <!-- * (table end) followed immediately by a "TS" (table start); -->
-            <!-- * in this case, it marks the end of a table "subsection" -->
-            <!-- * with header rows, and the start of a subsection with body -->
-            <!-- * rows. It's necessary to output it here because the "TH" -->
-            <!-- * macro is not being output, so there's otherwise no way -->
-            <!-- * for tbl(1) to know we have the table "sectioned". -->
-            <xsl:text>.T&amp;&#10;</xsl:text>
-          </xsl:otherwise>
-        </xsl:choose>
-      </xsl:if>
-      
-      <!-- * ============================================================== -->
-      <!-- *  Output table body rows                                        -->
-      <!-- * ============================================================== -->
-      <!-- * First create node set with all non-thead rows (tbody+tfoot), -->
-      <!-- * but reordered with the tfoot rows at the end of the node set -->
-      <xsl:variable name="rows-set">
-        <xsl:copy-of select="tbody/tr|tr"/>
-        <xsl:copy-of select="tfoot/tr"/>
-      </xsl:variable>
-      <xsl:call-template name="output.rows">
-        <xsl:with-param name="source" select="$source"/>
-        <xsl:with-param name="rows" select="exsl:node-set($rows-set)"/>
-      </xsl:call-template>
-
-      <!-- * mark the end of the table -->
-      <xsl:text>&#10;</xsl:text>
-      <!-- * .TE = "Table End" -->
-      <xsl:text>.TE&#10;</xsl:text>
-      <!-- * put a blank line of space below the table -->
-      <xsl:text>.sp 1&#10;</xsl:text>
-    </xsl:for-each>
-  </xsl:template>
-
-  <!-- ==================================================================== -->
-  <!-- *                        named templates -->
-  <!-- ==================================================================== -->
-  <!-- * -->
-  <!-- * All of the following are named templates that get called directly -->
-  <!-- * or indirectly by the main "driver" part of the code (above) -->
-  <!-- * -->
-  <!-- ==================================================================== -->
-  
-  <xsl:template name="output.rows">
-    <xsl:param name="source"/>
-    <xsl:param name="rows"/>
-    <!-- * ============================================================== -->
-    <!-- *   Flatten row set into simple list of cells                    -->
-    <!-- * ============================================================== -->
-    <!-- * Now we flatten the structure further into just a set of -->
-    <!-- * cells without the row parents. This basically creates a -->
-    <!-- * copy of the entire contents of the original table, but -->
-    <!-- * restructured in such a way that we can more easily generate -->
-    <!-- * the corresponding tbl(1) markup we need to output. -->
-    <xsl:variable name="cells-list">
-      <xsl:call-template name="build.cell.list">
-        <xsl:with-param name="source" select="$source"/>
-        <xsl:with-param name="rows" select="$rows"/>
-      </xsl:call-template>
-    </xsl:variable>
-    <xsl:variable name="cells" select="exsl:node-set($cells-list)"/>
-
-    <!-- * Output the table "format section", which tells tbl(1) how to -->
-    <!-- * format each row and column -->
-    <xsl:call-template name="create.table.format">
-      <xsl:with-param name="cells" select="$cells"/>
+    <xsl:call-template name="htmltotbl">
+      <xsl:with-param name="source" select="$source"/>
+      <xsl:with-param name="title" select="$title"/>
+      <xsl:with-param name="contents" select="$contents"/>
+      <xsl:with-param name="allbox" select="$allbox"/>
+      <xsl:with-param name="expand" select="$expand"/>
+      <xsl:with-param name="center" select="$center"/>
     </xsl:call-template>
-
-    <!--* Output the formatted contents of each cell. -->
-    <xsl:for-each select="$cells/cell">
-      <xsl:call-template name="output.cell"/>
-    </xsl:for-each>
-  </xsl:template>
-
-  <!-- * ============================================================== -->
-  <!-- *    Output the tbl(1)-formatted contents of each cell.            -->
-  <!-- * ============================================================== -->
-  <xsl:template name="output.cell">
-    <xsl:choose>
-      <xsl:when test="preceding-sibling::cell[1]/@row != @row or
-                      not(preceding-sibling::cell)">
-        <!-- * If the value of the "row" attribute on this cell is -->
-        <!-- * different from the value of that on the previous cell, it -->
-        <!-- * means we have a new row. So output a line break (as long -->
-        <!-- * as this isn't the first cell in the table) -->
-        <xsl:text>&#10;</xsl:text>
-      </xsl:when>
-      <xsl:otherwise>
-        <!-- * Otherwise we are not at the start of a new row, so we -->
-        <!-- * output a tab character to delimit the contents of this -->
-        <!-- * cell from the contents of the next one. -->
-        <xsl:value-of select="$tbl.column.separator.char"/>
-      </xsl:otherwise>
-    </xsl:choose>
-    <xsl:choose>
-      <xsl:when test="@type = '^'">
-        <!-- * If this is a dummy cell resulting from the presence of -->
-        <!-- * rowpan attribute in the source, it has no contents, so -->
-        <!-- * we need to handle it differently. -->
-        <xsl:if test="@colspan and @colspan > 1">
-          <!-- * If there is a colspan attribute on this dummy row, then -->
-          <!-- * we need to output a tab character for each column that -->
-          <!-- * it spans. -->
-          <xsl:call-template name="copy-string">
-            <xsl:with-param name="string" select="$tbl.column.separator.char"/>
-            <xsl:with-param name="count">
-              <xsl:value-of select="@colspan - 1"/>
-            </xsl:with-param>
-          </xsl:call-template>
-        </xsl:if>
-      </xsl:when>
-      <xsl:otherwise>
-        <!-- * Otherwise, we have a "real" cell (not a dummy one) with -->
-        <!-- * contents that we need to output, -->
-        <!-- * -->
-        <!-- * The "T{" and "T}" stuff are delimiters to tell tbl(1) that -->
-        <!-- * the delimited contents are "text blocks" that roff -->
-        <!-- * needs to process -->
-        <xsl:text>T{&#10;</xsl:text>
-        <xsl:copy-of select="."/>
-        <xsl:text>&#10;T}</xsl:text>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:template>
-
-  <!-- * ============================================================== -->
-  <!-- *   Build a restructured "cell list" copy of the entire table    -->
-  <!-- * ============================================================== -->
-  <xsl:template name="build.cell.list">
-    <xsl:param name="source"/>
-    <xsl:param name="rows"/>
-    <xsl:param  name="cell-data-unsorted">
-      <!-- * This param collects all the "real" cells from the table, -->
-      <!-- * along with "dummy" rows that we generate for keeping -->
-      <!-- * track of Rowspan instances. -->
-      <xsl:apply-templates select="$rows" mode="cell.list">
-        <xsl:with-param name="source" select="$source"/>
-      </xsl:apply-templates>
-    </xsl:param>
-    <xsl:param  name="cell-data-sorted">
-      <!-- * Sort the cells so that the dummy cells get put where we -->
-      <!-- * need them in the structure. Note that we need to specify -->
-      <!-- * data-type="number" here because the default sorting method -->
-      <!-- * for xsl:sort is "text" (alphabetical). -->
-      <xsl:for-each select="exsl:node-set($cell-data-unsorted)/cell">
-        <xsl:sort select="@row" data-type="number"/>
-        <xsl:sort select="@slot" data-type="number"/>
-        <xsl:copy-of select="."/>
-      </xsl:for-each>
-    </xsl:param>
-    <!-- * Return the sorted cell list -->
-    <xsl:copy-of select="$cell-data-sorted"/>
-  </xsl:template>
-
-  <xsl:template match="tr" mode="cell.list">
-    <xsl:param name="source"/>
-    <xsl:variable name="row">
-      <xsl:value-of select="count(preceding-sibling::tr) + 1"/>
-    </xsl:variable>
-    <xsl:for-each select="td|th">
-      <xsl:call-template name="cell">
-        <xsl:with-param name="source" select="$source"/>
-        <xsl:with-param name="row" select="$row"/>
-        <!-- * pass on the element name so we can select the appropriate -->
-        <!-- * roff font for styling the cell contents -->
-        <xsl:with-param name="class" select="name(.)"/>
-      </xsl:call-template>
-    </xsl:for-each>
-  </xsl:template>
-
-  <xsl:template name="cell">
-    <xsl:param name="source"/>
-    <xsl:param name="row"/>
-    <xsl:param name="class"/>
-    <xsl:param name="slot">
-      <!-- * The "slot" is the horizontal position of this cell (usually -->
-      <!-- * just the same as its column, but not so when it is preceded -->
-      <!-- * by cells that have colspans or cells in preceding rows that -->
-      <!-- * that have rowspans). -->
-      <xsl:value-of select="position()"/>
-    </xsl:param>
-    <!-- * For each real TD cell, create a Cell instance; contents will -->
-    <!-- * be the roff-formatted contents of its original table cell. -->
-    <cell type=""
-          row="{$row}"
-          slot="{$slot}"
-          class="{$class}"
-          colspan="{@colspan}"
-          align="{@align}"
-          valign="{@valign}"
-          >
-      <xsl:choose>
-        <xsl:when test=".//tr">
-          <xsl:call-template name="log.message">
-            <xsl:with-param name="level">Warn</xsl:with-param>
-            <xsl:with-param name="source" select="$source"/>
-            <xsl:with-param name="context-desc">tbl convert</xsl:with-param>
-            <xsl:with-param name="message">
-              <xsl:text>Extracted a nested table</xsl:text>
-            </xsl:with-param>
-          </xsl:call-template>
-          <xsl:text>[\fInested&#x2580;table\fR]*&#10;</xsl:text>
-        </xsl:when>
-        <xsl:otherwise>
-          <!-- * Apply templates to the child contents of this cell, to -->
-          <!-- * transform them into marked-up roff. -->
-          <xsl:variable name="contents">
-            <xsl:apply-templates/>
-          </xsl:variable>
-          <!-- * We now have the contents in roff (plain-text) form, -->
-          <!-- * but we may also still have unnecessary whitespace at -->
-          <!-- * the beginning and/or end of it, so trim it off. -->
-          <xsl:call-template name="trim.text">
-            <xsl:with-param name="contents" select="$contents"/>
-          </xsl:call-template>
-        </xsl:otherwise>
-      </xsl:choose>
-    </cell>
-
-    <!-- * For each instance of a rowspan attribute found, we create N -->
-    <!-- * dummy cells, where N is equal to the value of the rowspan. -->
-    <xsl:if test="@rowspan and @rowspan > 0">
-      <!-- * If this cell is preceded in the same row by cells that -->
-      <!-- * have colspan attributes, then we need to calculate the -->
-      <!-- * "offset" caused by those colspan instances; the formula -->
-      <!-- * is to (1) check for all the preceding cells that have -->
-      <!-- * colspan attributes that are not empty and which have a -->
-      <!-- * value greater than 1, then (2) take the sum of the values -->
-      <!-- * of all those colspan attributes, and subtract from that -->
-      <!-- * the number of such colspan instances found. -->
-      <xsl:variable name="colspan-offset">
-        <xsl:value-of
-            select="sum(preceding-sibling::td[@colspan != ''
-                    and @colspan > 1]/@colspan) -
-                    count(preceding-sibling::td[@colspan != ''
-                    and @colspan > 1]/@colspan)"/>
-      </xsl:variable>
-      <xsl:call-template name="create.dummy.cells">
-        <xsl:with-param name="row" select="$row + 1"/>
-        <!-- * The slot value on each dummy cell must be offset by the -->
-        <!-- * value of $colspan-offset to adjust for preceding colpans -->
-        <xsl:with-param name="slot" select="$slot + $colspan-offset"/>
-        <xsl:with-param name="colspan" select="@colspan"/>
-        <xsl:with-param name="rowspan" select="@rowspan"/>
-      </xsl:call-template>
-    </xsl:if>
-  </xsl:template>
-
-  <xsl:template name="create.dummy.cells">
-    <xsl:param name="row"/>
-    <xsl:param name="slot"/>
-    <xsl:param name="colspan"/>
-    <xsl:param name="rowspan"/>
-    <xsl:choose>
-      <xsl:when test="$rowspan > 1">
-        <!-- * Tail recurse until we have no more rowspans, creating -->
-        <!-- * an empty dummy cell each time. The type value, '^' -->
-        <!-- * is the marker that tbl(1) uses to indicate a -->
-        <!-- * "vertically spanned heading". -->
-        <cell row="{$row}" slot="{$slot}" type="^" colspan="{@colspan}"/>
-        <xsl:call-template name="create.dummy.cells">
-          <xsl:with-param name="row" select="$row + 1"/>
-          <xsl:with-param name="slot" select="$slot"/>
-          <xsl:with-param name="colspan" select="$colspan"/>
-          <xsl:with-param name="rowspan" select="$rowspan - 1"/>
-        </xsl:call-template>
-      </xsl:when>
-    </xsl:choose>
-  </xsl:template>
-
-  <!-- * ============================================================== -->
-  <!-- *    Build the "format section" for the table                    -->
-  <!-- * ============================================================== -->
-  <!-- * Description from the tbl(1) guide: -->
-  <!-- * -->
-  <!-- * "The format section of the table specifies the layout of the -->
-  <!-- * columns.  Each line in this section corresponds to one line of -->
-  <!-- * the table... and each line contains a key-letter for each -->
-  <!-- * column of the table." -->
-  <xsl:template name="create.table.format">
-    <xsl:param name="cells"/>
-    <xsl:apply-templates mode="table.format" select="$cells"/>
-    <!-- * last line of table format section must end with a dot -->
-    <xsl:text>.</xsl:text>
-  </xsl:template>
-
-  <xsl:template match="cell" mode="table.format">
-    <xsl:choose>
-      <xsl:when test="preceding-sibling::cell[1]/@row != @row">
-        <!-- * If the value of the row attribute on this cell is -->
-        <!-- * different from the value of that on the previous cell, it -->
-        <!-- * means we have a new row. So output a line break. -->
-        <xsl:text>&#xa;</xsl:text>
-      </xsl:when>
-      <xsl:otherwise>
-        <!-- * If this isn't the first cell, output a space before it to -->
-        <!-- * separate it from the preceding key letter. -->
-        <xsl:if test="position() != 1">
-          <xsl:text> </xsl:text>
-        </xsl:if>
-      </xsl:otherwise>
-    </xsl:choose>
-    <!-- * Select an appropriate "alignment" key letter based on this -->
-    <!-- * cell's attributes. -->
-    <xsl:choose>
-      <xsl:when test="@type = '^'">
-        <xsl:text>^</xsl:text>
-      </xsl:when>
-      <xsl:when test="@align = 'center'">
-        <xsl:text>c</xsl:text>
-      </xsl:when>
-      <xsl:when test="@align = 'right'">
-        <xsl:text>r</xsl:text>
-      </xsl:when>
-      <xsl:when test="@align = 'char'">
-        <xsl:text>n</xsl:text>
-      </xsl:when>
-      <xsl:otherwise>
-        <!-- * Default to left alignment. -->
-        <xsl:text>l</xsl:text>
-      </xsl:otherwise>
-    </xsl:choose>
-    <!-- * By default, tbl(1) vertically centers cell contents within -->
-    <!-- * their cells; the "t" key latter tells it to top-align the -->
-    <!-- * contents instead. Note that tbl(1) has no options for -->
-    <!-- * bottom or baseline alignment. -->
-    <xsl:if test="@valign = 'top'">
-      <xsl:text>t</xsl:text>
-    </xsl:if>
-    <xsl:if test="@class = 'th'">
-      <!-- * If this is a heading row, generate a font indicator (B or I), -->
-      <!-- * or if the value of $tbl.font.headings is empty, nothing. -->
-      <xsl:value-of select="$tbl.font.headings"/>
-    </xsl:if>
-    <!-- * We only need to deal with colspans whose value is greater -->
-    <!-- * than one (a colspan="1" is the same as having no colspan -->
-    <!-- * attribute at all). -->
-    <xsl:if test="@colspan > 1">
-      <xsl:call-template name="process.colspan">
-        <xsl:with-param name="colspan" select="@colspan - 1"/>
-        <xsl:with-param name="type" select="@type"/>
-      </xsl:call-template>
-    </xsl:if>
-  </xsl:template>
-  
-  <xsl:template name="process.colspan">
-    <xsl:param name="colspan"/>
-    <xsl:param name="type"/>
-    <!-- * Output a space to separate this key letter from preceding one. -->
-    <xsl:text> </xsl:text>
-    <xsl:choose>
-      <xsl:when test="$type = '^'">
-        <!-- * A '^' ("vertically spanned heading" marker) indicates -->
-        <!-- * that the "parent" of this spanned cell is a dummy cell; -->
-        <!-- * in this case, we need to generate a '^' instead of the -->
-        <!-- * normal 's'. -->
-        <xsl:text>^</xsl:text>
-      </xsl:when>
-      <xsl:otherwise>
-        <!-- * s = 'spanned heading' -->
-        <xsl:text>s</xsl:text>
-      </xsl:otherwise>
-    </xsl:choose>
-    <xsl:if test="$colspan > 1">
-      <!-- * Tail recurse until we have no more colspans, outputting -->
-      <!-- * another marker each time. -->
-      <xsl:call-template name="process.colspan">
-        <xsl:with-param name="colspan" select="$colspan - 1"/>
-        <xsl:with-param name="type" select="$type"/>
-      </xsl:call-template>
-    </xsl:if>
-  </xsl:template>
-
-  <!-- * ============================================================== -->
-  <!-- *    colgroup and col                                            -->
-  <!-- * ============================================================== -->
-  <!-- * We currently don't do anything with colgroup. Not sure if it -->
-  <!-- * is widely used enough to bother adding support for it -->
-  <xsl:template match="colgroup"/>
-  <xsl:template match="col"/>
-
-  <!-- * ============================================================== -->
-  <!-- *    table footnotes                                      -->
-  <!-- * ============================================================== -->
-  <xsl:template match="footnote" mode="table.footnote.mode">
-    <xsl:variable name="footnotes" select=".//footnote"/>
-    <xsl:variable name="table.footnotes"
-                  select=".//tgroup//footnote"/>
-    <xsl:value-of select="$man.table.footnotes.divider"/>
-    <xsl:text>&#10;</xsl:text>
-    <xsl:text>.br&#10;</xsl:text>
-    <xsl:apply-templates select="*[1]" mode="footnote.body.number"/>
-    <xsl:apply-templates select="*[position() &gt; 1]"/>
-  </xsl:template>
-
-  <!-- * The following template for footnote.body.number mode was just -->
-  <!-- * lifted from the HTML stylesheets with some minor adjustments -->
-  <xsl:template match="*"  mode="footnote.body.number">
-    <xsl:variable name="name">
-      <xsl:text>ftn.</xsl:text>
-      <xsl:call-template name="object.id">
-        <xsl:with-param name="object" select="ancestor::footnote"/>
-      </xsl:call-template>
-    </xsl:variable>
-    <xsl:variable name="href">
-      <xsl:text>#</xsl:text>
-      <xsl:call-template name="object.id">
-        <xsl:with-param name="object" select="ancestor::footnote"/>
-      </xsl:call-template>
-    </xsl:variable>
-    <xsl:variable name="footnote.mark">
-      <xsl:text>[</xsl:text>
-      <xsl:apply-templates select="ancestor::footnote"
-                           mode="footnote.number"/>
-      <xsl:text>]&#10;</xsl:text>
-    </xsl:variable>
-    <xsl:variable name="html">
-      <xsl:apply-templates select="."/>
-    </xsl:variable>
-    <xsl:choose>
-      <xsl:when test="$exsl.node.set.available != 0">
-        <xsl:variable name="html-nodes" select="exsl:node-set($html)"/>
-        <xsl:choose>
-          <xsl:when test="$html-nodes//p">
-            <xsl:apply-templates select="$html-nodes" mode="insert.html.p">
-              <xsl:with-param name="mark" select="$footnote.mark"/>
-            </xsl:apply-templates>
-          </xsl:when>
-          <xsl:otherwise>
-            <xsl:apply-templates select="$html-nodes" mode="insert.html.text">
-              <xsl:with-param name="mark" select="$footnote.mark"/>
-            </xsl:apply-templates>
-          </xsl:otherwise>
-        </xsl:choose>
-      </xsl:when>
-      <xsl:otherwise>
-        <xsl:copy-of select="$html"/>
-      </xsl:otherwise>
-    </xsl:choose>
-  </xsl:template>
-
-  <!-- * The HTML stylesheets output <sup><a>...</a></sup> around -->
-  <!-- * footnote markers in tables -->
-  <xsl:template match="th/sup">
-    <xsl:apply-templates/>
-  </xsl:template>
-  <xsl:template match="a">
-    <xsl:apply-templates/>
   </xsl:template>
 
 </xsl:stylesheet>
diff --git a/xsl/manpages/tbl.xsl b/xsl/manpages/tbl.xsl
new file mode 100755 (executable)
index 0000000..1aed05b
--- /dev/null
@@ -0,0 +1,551 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet 
+  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
+  xmlns:exsl="http://exslt.org/common"
+  exclude-result-prefixes="exsl"
+  version="1.0">
+
+<!-- $Id$ -->
+<!-- This stylesheet module contains templates that match on
+non-namespaced HTML table elements and generate tbl markup.
+The HTML comes either from an HTML-markup table in the source,
+stripped of docbook namespace if necessary, or from applying
+the stock DocBook HTML templates to a CALS table. -->
+
+<xsl:import href="../common/stripns.xsl"/>
+
+<xsl:template match="*" mode="fix.namespace">
+  <xsl:apply-templates select="." mode="stripNS"/>
+</xsl:template>
+
+
+<xsl:template name="htmltotbl">
+  <xsl:param name="contents"/>
+  <xsl:param name="source"/>
+  <xsl:param name="title"/>
+  <xsl:param name="allbox"/>
+  <xsl:param name="center"/>
+  <xsl:param name="expand"/>
+
+    <!-- ==================================================================== -->
+    <!-- *                       Output the table -->
+    <!-- ==================================================================== -->
+    <!-- * -->
+    <!-- * This is the "driver" part of the code; it calls a series of named
+         * templates (further below) to generate the actual tbl(1) markup, -->
+    <!-- * including the optional "options line", required "format section", -->
+    <!-- * and then the actual contents of the table. -->
+    <!-- * -->
+    <!-- ==================================================================== -->
+
+    <xsl:for-each select="$contents//table">
+      <!-- * ============================================================== -->
+      <!-- *   Output table title                                           -->
+      <!-- * ============================================================== -->
+      <xsl:if test="$title != '' or parent::td">
+        <xsl:text>.sp&#10;</xsl:text>
+        <xsl:call-template name="pinch.together"/>
+        <xsl:text>.</xsl:text>
+        <xsl:value-of select="$tbl.font.title"/>
+        <xsl:text> </xsl:text>
+        <xsl:if test="parent::td">
+          <xsl:text>*[nested&#x2580;table]</xsl:text>
+        </xsl:if>
+        <xsl:value-of select="normalize-space($title)"/>
+        <xsl:text>&#10;</xsl:text>
+      </xsl:if>
+      
+      <!-- * mark the start of the table -->
+      <!-- * "TS" = "table start" -->
+      <xsl:text>.TS</xsl:text>
+      <xsl:if test="thead and $tbl.running.header.from.thead">
+        <!-- * H = "has header" -->
+        <xsl:text> H</xsl:text>
+      </xsl:if>
+      <xsl:text>&#10;</xsl:text>
+
+      <!-- * ============================================================== -->
+      <!-- *   Output "options line"                                         -->
+      <!-- * ============================================================== -->
+      <xsl:variable name="options-line">
+        <xsl:value-of select="$allbox"/>
+        <xsl:value-of select="$center"/>
+        <xsl:value-of select="$expand"/>
+        <xsl:text>tab(</xsl:text>
+        <xsl:value-of select="$tbl.column.separator.char"/>
+        <xsl:text>)</xsl:text>
+      </xsl:variable>
+      <xsl:if test="normalize-space($options-line) != ''">
+        <xsl:value-of select="normalize-space($options-line)"/>
+        <xsl:text>;&#10;</xsl:text>
+      </xsl:if>
+
+      <!-- * ============================================================== -->
+      <!-- *   Output table header rows                                     -->
+      <!-- * ============================================================== -->
+      <xsl:if test="thead">
+        <xsl:call-template name="output.rows">
+          <xsl:with-param name="rows" select="thead/tr"/>
+        </xsl:call-template> 
+        <xsl:text>&#10;</xsl:text>
+
+        <!-- * mark the end of table-header rows -->
+        <xsl:choose>
+          <xsl:when test="$tbl.running.header.from.thead">
+            <!-- * "TH" = "table header end" -->
+            <xsl:text>.TH&#10;</xsl:text>
+          </xsl:when>
+          <xsl:otherwise>
+            <!-- * "T&" = "table continuation" and is meant just as a kind -->
+            <!-- * of convenience macro and is sorta equivalent to a "TE" -->
+            <!-- * (table end) followed immediately by a "TS" (table start); -->
+            <!-- * in this case, it marks the end of a table "subsection" -->
+            <!-- * with header rows, and the start of a subsection with body -->
+            <!-- * rows. It's necessary to output it here because the "TH" -->
+            <!-- * macro is not being output, so there's otherwise no way -->
+            <!-- * for tbl(1) to know we have the table "sectioned". -->
+            <xsl:text>.T&amp;&#10;</xsl:text>
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:if>
+      
+      <!-- * ============================================================== -->
+      <!-- *  Output table body rows                                        -->
+      <!-- * ============================================================== -->
+      <!-- * First create node set with all non-thead rows (tbody+tfoot), -->
+      <!-- * but reordered with the tfoot rows at the end of the node set -->
+      <xsl:variable name="rows-set">
+        <xsl:copy-of select="tbody/tr|tr"/>
+        <xsl:copy-of select="tfoot/tr"/>
+      </xsl:variable>
+      <xsl:call-template name="output.rows">
+        <xsl:with-param name="source" select="$source"/>
+        <xsl:with-param name="rows" select="exsl:node-set($rows-set)"/>
+      </xsl:call-template>
+
+      <!-- * mark the end of the table -->
+      <xsl:text>&#10;</xsl:text>
+      <!-- * .TE = "Table End" -->
+      <xsl:text>.TE&#10;</xsl:text>
+      <!-- * put a blank line of space below the table -->
+      <xsl:text>.sp 1&#10;</xsl:text>
+    </xsl:for-each>
+  </xsl:template>
+
+  <!-- ==================================================================== -->
+  <!-- *                        named templates -->
+  <!-- ==================================================================== -->
+  <!-- * -->
+  <!-- * All of the following are named templates that get called directly -->
+  <!-- * or indirectly by the main "driver" part of the code (above) -->
+  <!-- * -->
+  <!-- ==================================================================== -->
+  
+  <xsl:template name="output.rows">
+    <xsl:param name="source"/>
+    <xsl:param name="rows"/>
+    <!-- * ============================================================== -->
+    <!-- *   Flatten row set into simple list of cells                    -->
+    <!-- * ============================================================== -->
+    <!-- * Now we flatten the structure further into just a set of -->
+    <!-- * cells without the row parents. This basically creates a -->
+    <!-- * copy of the entire contents of the original table, but -->
+    <!-- * restructured in such a way that we can more easily generate -->
+    <!-- * the corresponding tbl(1) markup we need to output. -->
+    <xsl:variable name="cells-list">
+      <xsl:call-template name="build.cell.list">
+        <xsl:with-param name="source" select="$source"/>
+        <xsl:with-param name="rows" select="$rows"/>
+      </xsl:call-template>
+    </xsl:variable>
+    <xsl:variable name="cells" select="exsl:node-set($cells-list)"/>
+
+    <!-- * Output the table "format section", which tells tbl(1) how to -->
+    <!-- * format each row and column -->
+    <xsl:call-template name="create.table.format">
+      <xsl:with-param name="cells" select="$cells"/>
+    </xsl:call-template>
+
+    <!--* Output the formatted contents of each cell. -->
+    <xsl:for-each select="$cells/cell">
+      <xsl:call-template name="output.cell"/>
+    </xsl:for-each>
+  </xsl:template>
+
+  <!-- * ============================================================== -->
+  <!-- *    Output the tbl(1)-formatted contents of each cell.            -->
+  <!-- * ============================================================== -->
+  <xsl:template name="output.cell">
+    <xsl:choose>
+      <xsl:when test="preceding-sibling::cell[1]/@row != @row or
+                      not(preceding-sibling::cell)">
+        <!-- * If the value of the "row" attribute on this cell is -->
+        <!-- * different from the value of that on the previous cell, it -->
+        <!-- * means we have a new row. So output a line break (as long -->
+        <!-- * as this isn't the first cell in the table) -->
+        <xsl:text>&#10;</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <!-- * Otherwise we are not at the start of a new row, so we -->
+        <!-- * output a tab character to delimit the contents of this -->
+        <!-- * cell from the contents of the next one. -->
+        <xsl:value-of select="$tbl.column.separator.char"/>
+      </xsl:otherwise>
+    </xsl:choose>
+    <xsl:choose>
+      <xsl:when test="@type = '^'">
+        <!-- * If this is a dummy cell resulting from the presence of -->
+        <!-- * rowpan attribute in the source, it has no contents, so -->
+        <!-- * we need to handle it differently. -->
+        <xsl:if test="@colspan and @colspan > 1">
+          <!-- * If there is a colspan attribute on this dummy row, then -->
+          <!-- * we need to output a tab character for each column that -->
+          <!-- * it spans. -->
+          <xsl:call-template name="copy-string">
+            <xsl:with-param name="string" select="$tbl.column.separator.char"/>
+            <xsl:with-param name="count">
+              <xsl:value-of select="@colspan - 1"/>
+            </xsl:with-param>
+          </xsl:call-template>
+        </xsl:if>
+      </xsl:when>
+      <xsl:otherwise>
+        <!-- * Otherwise, we have a "real" cell (not a dummy one) with -->
+        <!-- * contents that we need to output, -->
+        <!-- * -->
+        <!-- * The "T{" and "T}" stuff are delimiters to tell tbl(1) that -->
+        <!-- * the delimited contents are "text blocks" that roff -->
+        <!-- * needs to process -->
+        <xsl:text>T{&#10;</xsl:text>
+        <xsl:copy-of select="."/>
+        <xsl:text>&#10;T}</xsl:text>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+  <!-- * ============================================================== -->
+  <!-- *   Build a restructured "cell list" copy of the entire table    -->
+  <!-- * ============================================================== -->
+  <xsl:template name="build.cell.list">
+    <xsl:param name="source"/>
+    <xsl:param name="rows"/>
+    <xsl:param  name="cell-data-unsorted">
+      <!-- * This param collects all the "real" cells from the table, -->
+      <!-- * along with "dummy" rows that we generate for keeping -->
+      <!-- * track of Rowspan instances. -->
+      <xsl:apply-templates select="$rows" mode="cell.list">
+        <xsl:with-param name="source" select="$source"/>
+      </xsl:apply-templates>
+    </xsl:param>
+    <xsl:param  name="cell-data-sorted">
+      <!-- * Sort the cells so that the dummy cells get put where we -->
+      <!-- * need them in the structure. Note that we need to specify -->
+      <!-- * data-type="number" here because the default sorting method -->
+      <!-- * for xsl:sort is "text" (alphabetical). -->
+      <xsl:for-each select="exsl:node-set($cell-data-unsorted)/cell">
+        <xsl:sort select="@row" data-type="number"/>
+        <xsl:sort select="@slot" data-type="number"/>
+        <xsl:copy-of select="."/>
+      </xsl:for-each>
+    </xsl:param>
+    <!-- * Return the sorted cell list -->
+    <xsl:copy-of select="$cell-data-sorted"/>
+  </xsl:template>
+
+  <xsl:template match="tr" mode="cell.list">
+    <xsl:param name="source"/>
+    <xsl:variable name="row">
+      <xsl:value-of select="count(preceding-sibling::tr) + 1"/>
+    </xsl:variable>
+    <xsl:for-each select="td|th">
+      <xsl:call-template name="cell">
+        <xsl:with-param name="source" select="$source"/>
+        <xsl:with-param name="row" select="$row"/>
+        <!-- * pass on the element name so we can select the appropriate -->
+        <!-- * roff font for styling the cell contents -->
+        <xsl:with-param name="class" select="name(.)"/>
+      </xsl:call-template>
+    </xsl:for-each>
+  </xsl:template>
+
+  <xsl:template name="cell">
+    <xsl:param name="source"/>
+    <xsl:param name="row"/>
+    <xsl:param name="class"/>
+    <xsl:param name="slot">
+      <!-- * The "slot" is the horizontal position of this cell (usually -->
+      <!-- * just the same as its column, but not so when it is preceded -->
+      <!-- * by cells that have colspans or cells in preceding rows that -->
+      <!-- * that have rowspans). -->
+      <xsl:value-of select="position()"/>
+    </xsl:param>
+    <!-- * For each real TD cell, create a Cell instance; contents will -->
+    <!-- * be the roff-formatted contents of its original table cell. -->
+    <cell type=""
+          row="{$row}"
+          slot="{$slot}"
+          class="{$class}"
+          colspan="{@colspan}"
+          align="{@align}"
+          valign="{@valign}"
+          >
+      <xsl:choose>
+        <xsl:when test=".//tr">
+          <xsl:call-template name="log.message">
+            <xsl:with-param name="level">Warn</xsl:with-param>
+            <xsl:with-param name="source" select="$source"/>
+            <xsl:with-param name="context-desc">tbl convert</xsl:with-param>
+            <xsl:with-param name="message">
+              <xsl:text>Extracted a nested table</xsl:text>
+            </xsl:with-param>
+          </xsl:call-template>
+          <xsl:text>[\fInested&#x2580;table\fR]*&#10;</xsl:text>
+        </xsl:when>
+        <xsl:otherwise>
+          <!-- * Apply templates to the child contents of this cell, to -->
+          <!-- * transform them into marked-up roff. -->
+          <xsl:variable name="contents">
+            <xsl:apply-templates/>
+          </xsl:variable>
+          <!-- * We now have the contents in roff (plain-text) form, -->
+          <!-- * but we may also still have unnecessary whitespace at -->
+          <!-- * the beginning and/or end of it, so trim it off. -->
+          <xsl:call-template name="trim.text">
+            <xsl:with-param name="contents" select="$contents"/>
+          </xsl:call-template>
+        </xsl:otherwise>
+      </xsl:choose>
+    </cell>
+
+    <!-- * For each instance of a rowspan attribute found, we create N -->
+    <!-- * dummy cells, where N is equal to the value of the rowspan. -->
+    <xsl:if test="@rowspan and @rowspan > 0">
+      <!-- * If this cell is preceded in the same row by cells that -->
+      <!-- * have colspan attributes, then we need to calculate the -->
+      <!-- * "offset" caused by those colspan instances; the formula -->
+      <!-- * is to (1) check for all the preceding cells that have -->
+      <!-- * colspan attributes that are not empty and which have a -->
+      <!-- * value greater than 1, then (2) take the sum of the values -->
+      <!-- * of all those colspan attributes, and subtract from that -->
+      <!-- * the number of such colspan instances found. -->
+      <xsl:variable name="colspan-offset">
+        <xsl:value-of
+            select="sum(preceding-sibling::td[@colspan != ''
+                    and @colspan > 1]/@colspan) -
+                    count(preceding-sibling::td[@colspan != ''
+                    and @colspan > 1]/@colspan)"/>
+      </xsl:variable>
+      <xsl:call-template name="create.dummy.cells">
+        <xsl:with-param name="row" select="$row + 1"/>
+        <!-- * The slot value on each dummy cell must be offset by the -->
+        <!-- * value of $colspan-offset to adjust for preceding colpans -->
+        <xsl:with-param name="slot" select="$slot + $colspan-offset"/>
+        <xsl:with-param name="colspan" select="@colspan"/>
+        <xsl:with-param name="rowspan" select="@rowspan"/>
+      </xsl:call-template>
+    </xsl:if>
+  </xsl:template>
+
+  <xsl:template name="create.dummy.cells">
+    <xsl:param name="row"/>
+    <xsl:param name="slot"/>
+    <xsl:param name="colspan"/>
+    <xsl:param name="rowspan"/>
+    <xsl:choose>
+      <xsl:when test="$rowspan > 1">
+        <!-- * Tail recurse until we have no more rowspans, creating -->
+        <!-- * an empty dummy cell each time. The type value, '^' -->
+        <!-- * is the marker that tbl(1) uses to indicate a -->
+        <!-- * "vertically spanned heading". -->
+        <cell row="{$row}" slot="{$slot}" type="^" colspan="{@colspan}"/>
+        <xsl:call-template name="create.dummy.cells">
+          <xsl:with-param name="row" select="$row + 1"/>
+          <xsl:with-param name="slot" select="$slot"/>
+          <xsl:with-param name="colspan" select="$colspan"/>
+          <xsl:with-param name="rowspan" select="$rowspan - 1"/>
+        </xsl:call-template>
+      </xsl:when>
+    </xsl:choose>
+  </xsl:template>
+
+  <!-- * ============================================================== -->
+  <!-- *    Build the "format section" for the table                    -->
+  <!-- * ============================================================== -->
+  <!-- * Description from the tbl(1) guide: -->
+  <!-- * -->
+  <!-- * "The format section of the table specifies the layout of the -->
+  <!-- * columns.  Each line in this section corresponds to one line of -->
+  <!-- * the table... and each line contains a key-letter for each -->
+  <!-- * column of the table." -->
+  <xsl:template name="create.table.format">
+    <xsl:param name="cells"/>
+    <xsl:apply-templates mode="table.format" select="$cells"/>
+    <!-- * last line of table format section must end with a dot -->
+    <xsl:text>.</xsl:text>
+  </xsl:template>
+
+  <xsl:template match="cell" mode="table.format">
+    <xsl:choose>
+      <xsl:when test="preceding-sibling::cell[1]/@row != @row">
+        <!-- * If the value of the row attribute on this cell is -->
+        <!-- * different from the value of that on the previous cell, it -->
+        <!-- * means we have a new row. So output a line break. -->
+        <xsl:text>&#xa;</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <!-- * If this isn't the first cell, output a space before it to -->
+        <!-- * separate it from the preceding key letter. -->
+        <xsl:if test="position() != 1">
+          <xsl:text> </xsl:text>
+        </xsl:if>
+      </xsl:otherwise>
+    </xsl:choose>
+    <!-- * Select an appropriate "alignment" key letter based on this -->
+    <!-- * cell's attributes. -->
+    <xsl:choose>
+      <xsl:when test="@type = '^'">
+        <xsl:text>^</xsl:text>
+      </xsl:when>
+      <xsl:when test="@align = 'center'">
+        <xsl:text>c</xsl:text>
+      </xsl:when>
+      <xsl:when test="@align = 'right'">
+        <xsl:text>r</xsl:text>
+      </xsl:when>
+      <xsl:when test="@align = 'char'">
+        <xsl:text>n</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <!-- * Default to left alignment. -->
+        <xsl:text>l</xsl:text>
+      </xsl:otherwise>
+    </xsl:choose>
+    <!-- * By default, tbl(1) vertically centers cell contents within -->
+    <!-- * their cells; the "t" key latter tells it to top-align the -->
+    <!-- * contents instead. Note that tbl(1) has no options for -->
+    <!-- * bottom or baseline alignment. -->
+    <xsl:if test="@valign = 'top'">
+      <xsl:text>t</xsl:text>
+    </xsl:if>
+    <xsl:if test="@class = 'th'">
+      <!-- * If this is a heading row, generate a font indicator (B or I), -->
+      <!-- * or if the value of $tbl.font.headings is empty, nothing. -->
+      <xsl:value-of select="$tbl.font.headings"/>
+    </xsl:if>
+    <!-- * We only need to deal with colspans whose value is greater -->
+    <!-- * than one (a colspan="1" is the same as having no colspan -->
+    <!-- * attribute at all). -->
+    <xsl:if test="@colspan > 1">
+      <xsl:call-template name="process.colspan">
+        <xsl:with-param name="colspan" select="@colspan - 1"/>
+        <xsl:with-param name="type" select="@type"/>
+      </xsl:call-template>
+    </xsl:if>
+  </xsl:template>
+  
+  <xsl:template name="process.colspan">
+    <xsl:param name="colspan"/>
+    <xsl:param name="type"/>
+    <!-- * Output a space to separate this key letter from preceding one. -->
+    <xsl:text> </xsl:text>
+    <xsl:choose>
+      <xsl:when test="$type = '^'">
+        <!-- * A '^' ("vertically spanned heading" marker) indicates -->
+        <!-- * that the "parent" of this spanned cell is a dummy cell; -->
+        <!-- * in this case, we need to generate a '^' instead of the -->
+        <!-- * normal 's'. -->
+        <xsl:text>^</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>
+        <!-- * s = 'spanned heading' -->
+        <xsl:text>s</xsl:text>
+      </xsl:otherwise>
+    </xsl:choose>
+    <xsl:if test="$colspan > 1">
+      <!-- * Tail recurse until we have no more colspans, outputting -->
+      <!-- * another marker each time. -->
+      <xsl:call-template name="process.colspan">
+        <xsl:with-param name="colspan" select="$colspan - 1"/>
+        <xsl:with-param name="type" select="$type"/>
+      </xsl:call-template>
+    </xsl:if>
+  </xsl:template>
+
+  <!-- * ============================================================== -->
+  <!-- *    colgroup and col                                            -->
+  <!-- * ============================================================== -->
+  <!-- * We currently don't do anything with colgroup. Not sure if it -->
+  <!-- * is widely used enough to bother adding support for it -->
+  <xsl:template match="colgroup"/>
+  <xsl:template match="col"/>
+
+  <!-- * ============================================================== -->
+  <!-- *    table footnotes                                      -->
+  <!-- * ============================================================== -->
+  <xsl:template match="footnote" mode="table.footnote.mode">
+    <xsl:variable name="footnotes" select=".//footnote"/>
+    <xsl:variable name="table.footnotes"
+                  select=".//tgroup//footnote"/>
+    <xsl:value-of select="$man.table.footnotes.divider"/>
+    <xsl:text>&#10;</xsl:text>
+    <xsl:text>.br&#10;</xsl:text>
+    <xsl:apply-templates select="*[1]" mode="footnote.body.number"/>
+    <xsl:apply-templates select="*[position() &gt; 1]"/>
+  </xsl:template>
+
+  <!-- * The following template for footnote.body.number mode was just -->
+  <!-- * lifted from the HTML stylesheets with some minor adjustments -->
+  <xsl:template match="*"  mode="footnote.body.number">
+    <xsl:variable name="name">
+      <xsl:text>ftn.</xsl:text>
+      <xsl:call-template name="object.id">
+        <xsl:with-param name="object" select="ancestor::footnote"/>
+      </xsl:call-template>
+    </xsl:variable>
+    <xsl:variable name="href">
+      <xsl:text>#</xsl:text>
+      <xsl:call-template name="object.id">
+        <xsl:with-param name="object" select="ancestor::footnote"/>
+      </xsl:call-template>
+    </xsl:variable>
+    <xsl:variable name="footnote.mark">
+      <xsl:text>[</xsl:text>
+      <xsl:apply-templates select="ancestor::footnote"
+                           mode="footnote.number"/>
+      <xsl:text>]&#10;</xsl:text>
+    </xsl:variable>
+    <xsl:variable name="html">
+      <xsl:apply-templates select="."/>
+    </xsl:variable>
+    <xsl:choose>
+      <xsl:when test="$exsl.node.set.available != 0">
+        <xsl:variable name="html-nodes" select="exsl:node-set($html)"/>
+        <xsl:choose>
+          <xsl:when test="$html-nodes//p">
+            <xsl:apply-templates select="$html-nodes" mode="insert.html.p">
+              <xsl:with-param name="mark" select="$footnote.mark"/>
+            </xsl:apply-templates>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:apply-templates select="$html-nodes" mode="insert.html.text">
+              <xsl:with-param name="mark" select="$footnote.mark"/>
+            </xsl:apply-templates>
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:copy-of select="$html"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+
+  <!-- * The HTML stylesheets output <sup><a>...</a></sup> around -->
+  <!-- * footnote markers in tables -->
+  <xsl:template match="th/sup">
+    <xsl:apply-templates/>
+  </xsl:template>
+  <xsl:template match="a">
+    <xsl:apply-templates/>
+  </xsl:template>
+
+</xsl:stylesheet>