[xsl] Nested xsl:iterate with innermost xsl:on-completion?

Subject: [xsl] Nested xsl:iterate with innermost xsl:on-completion?
From: "Tony Graham" <tgraham@xxxxxxxxxx>
Date: Thu, 13 Feb 2014 18:55:43 -0000 (GMT)
To try out xsl:iterate, I'm trying to process a HTML-like table to HTML
but with empty <td> added where there's a 'gap' in the original table.

E.g., in the following three-column table, there's a 'gap' at the end of
the head row and at the end of the second body row:

----
<table  border="1">
  <colgroup>
    <col align="left" span="1"/>
    <col align="left" span="1"/>
    <col align="left" span="1"/>
  </colgroup>
  <thead>
    <tr>
      <td rowspan="1" colspan="1">Head</td>
      <td rowspan="1" colspan="1"></td>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td rowspan="2" colspan="1">1.1, 2.1</td>
      <td rowspan="1" colspan="1">1.2</td>
      <td rowspan="1" colspan="1">1.3</td>
    </tr>
    <tr>
      <td rowspan="1" colspan="1">2.2</td>
    </tr>
  </tbody>
</table>
----

In the spirit of 'XSLT 3.0 even if it kills me', I'm using a map to keep
track of cells spanning between rows (and handling column spans is an
exercise for another day).

For <thead> and <tbody>, the stylesheet below uses xsl:iterate to iterate
over the <tr> in the <thead> or <tbody>, and that xsl:iterate contains
another xsl:iterate for iterating over the table cells.  The map for
keeping track of cells spanning table rows is updated okay, but the only
way I've found to use the updated map in a subsequent row is to return the
map as the result of an xsl:on-completion in the inner xsl:iterate.

That wouldn't be so bad, but currently it means putting the entire result
of processing the row into a $content variable just so everything except
the last item can be copied to the result and the last item -- the result
of the xsl:on-completion -- can be used in the xsl:next-iteration of the
outer xsl:iterate.

Is there a better way to do this rather than buffering the result of an
xsl:iterate just to also get a variable's value?

Regards,

Tony Graham                                         tgraham@xxxxxxxxxx
Consultant                                       http://www.mentea.net
Chair, Print and Page Layout Community Group @ W3C    XML Guild member
  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --  --
Mentea       XML, XSL-FO and XSLT consulting, training and programming


----
<?xml version="1.0"?>
<xsl:stylesheet version="3.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
  xmlns:xlink="http://www.w3.org/1999/xlink";
  xmlns:map="http://www.w3.org/2005/xpath-functions/map";
  xmlns:mml="http://www.w3.org/1998/Math/MathML";
  xmlns:x3tb="https://github.com/MenteaXML/xslt3testbed";
  xmlns:xs="http://www.w3.org/2001/XMLSchema";
  exclude-result-prefixes="xlink map mml x3tb xs">

  <xsl:output method="html"/>

  <xsl:template match="/">
    <html>
      <head></head>
      <body>
        <xsl:apply-templates />
      </body>
    </html>
  </xsl:template>

  <xsl:template match="table">
    <xsl:variable name="cols" as="map(*)*">
      <xsl:for-each select="col | colgroup/col">
        <xsl:sequence select="map:entry(position(), .)" />
      </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="col-map" as="map(xs:integer, element(col))"
                  select="map:new($cols)" />
    <xsl:copy>
      <xsl:apply-templates select="@*" mode="table-copy"/>
      <xsl:apply-templates>
        <xsl:with-param name="col-map" select="$col-map"
as="map(xs:integer, element(col))" />
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="thead | tbody | tfoot">
    <xsl:param name="col-map" as="map(xs:integer, element(col))" />
    <xsl:variable name="cell-map"
                  select="map:new(for $i in 1 to count(map:keys($col-map))
                                    return map:entry($i, 0))"
                  as="map(xs:integer, xs:integer)" />
    <xsl:copy>
      <xsl:apply-templates select="@*" mode="table-copy"/>
      <xsl:iterate select="tr">
        <xsl:param name="cell-map" select="$cell-map"
                   as="map(xs:integer, xs:integer)" />
        <xsl:message select="('row', count(map:keys($cell-map)))"/>
        <xsl:variable name="content" as="item()+">
          <xsl:iterate select="1 to count(map:keys($cell-map))">
            <xsl:param name="colnum"
                       select="1"
                       as="xs:integer" />
            <xsl:param name="cell-map"
                       select="$cell-map"
                       as="map(xs:integer, xs:integer)" />
            <xsl:param name="cells" select="*" as="element()*" />

            <xsl:message select="map:get($cell-map, .)"/>
            <xsl:choose>
              <xsl:when test="map:get($cell-map,.) > 0">
                <xsl:message select="'rowspanned'"/>
                <xsl:next-iteration>
                  <xsl:with-param
                      name="colnum" select="$colnum + 1" as="xs:integer" />
                  <xsl:with-param
                      name="cell-map"
                      select="map:new(($cell-map,
                                       map:entry(., map:get($cell-map,.) -
1)))"
                      as="map(xs:integer, xs:integer)" />
                </xsl:next-iteration>
              </xsl:when>
              <xsl:when test="empty($cells)">
                <xsl:message select="'no cell'"/>
                <td/>
                <xsl:next-iteration>
                  <xsl:with-param name="colnum" select="$colnum + 1"/>
                </xsl:next-iteration>
              </xsl:when>
              <xsl:otherwise>
                <xsl:message select="concat('cell: ''',
                                            $cells[1]),
                                            '''; rowspan: ',
                                            string($cells[1]/@rowspan)"/>
                <xsl:apply-templates select="$cells[1]" />
                <xsl:next-iteration>
                  <xsl:with-param
                      name="colnum" select="$colnum + 1" as="xs:integer" />
                  <xsl:with-param name="cell-map"
                                  select="map:new(($cell-map,
                                                   map:entry(.,
xs:integer(($cells[1]/@rowspan,
1)[1]) - 1)))"/>
                  <xsl:with-param name="cells" select="$cells[position() >
1]"/>
                </xsl:next-iteration>
              </xsl:otherwise>
            </xsl:choose>
            <xsl:on-completion>
              <xsl:sequence select="$cell-map" />
            </xsl:on-completion>
          </xsl:iterate>
          </xsl:variable>
        <xsl:copy>
          <xsl:apply-templates select="@*" mode="table-copy"/>
          <!--<xsl:call-template name="named-anchor"/>-->
          <xsl:sequence select="$content[position() != last()]" />
        </xsl:copy>
        <xsl:next-iteration>
          <xsl:with-param name="cell-map"
                          select="$content[last()]" />
        </xsl:next-iteration>
      </xsl:iterate>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="col | colgroup | th | td">
    <xsl:copy>
      <xsl:apply-templates select="@*" mode="table-copy"/>
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="@*" mode="table-copy">
    <xsl:copy-of select="."/>
  </xsl:template>

</xsl:stylesheet>
----

Current Thread