Re: [xsl] How to make auto row-spanned table

Subject: Re: [xsl] How to make auto row-spanned table
From: "G. Ken Holman" <gkholman@xxxxxxxxxxxxxxxxxxxx>
Date: Wed, 24 Apr 2013 13:56:29 -0400
At 2013-04-24 12:40 -0400, I wrote:
At 2013-04-25 01:29 +0900, Toshihiko Makita wrote:
I'm making a stylesheet that converts DITA documents to XSL-FO.

Actually, the answer is a *lot* simpler than I thought because I misread your request to convert DITA to DITA and because you made reference to a morerows attribute. The principle is the same, but in XSL-FO one can take advantage of the cell-based row-grouping approach. I even have an example of this as an exercise in my training class in the module on sorting and grouping. Most of my students have never heard of filling an XSL-FO table with only cell elements and not row elements, so the exercise is meant to help remember the technique.


Usually a table is authored with entry/@morerows attribute. But some table has no @morerows attribute.

In XSL-FO it is acceptable to say number-rows-spanned="1" when there is no spanning. I don't know if it is allowed to say morerows="0" in DITA.


It would help us help you to have the XML input data of your table example. You can post it as clear text in your message and we can cut it out and create a file from it.

I mocked up a test file that mimics your data and produces an acceptable XSL-FO result using a simplified stylesheet. Note the use of the ends-row="true/false" attribute and that there are no <table-row> elements. The output test.fo can be formatted with an XSL-FO engine to produce a result with the rows you need.



t:\ftemp>type test.xml <?xml version="1.0" encoding="UTF-8"?> <table> <tgroup> <tbody> <row><entry>A</entry><entry>B</entry><entry>C</entry><entry>D</entry><entry>E</entry><entry>F</entry></row> <row><entry>A</entry><entry>G</entry><entry>H</entry><entry>I</entry><entry>J</entry><entry>K</entry></row> <row><entry>A</entry><entry>G</entry><entry>H</entry><entry>L</entry><entry>M</entry><entry>N</entry></row> <row><entry>A</entry><entry>G</entry><entry>O</entry><entry>P</entry><entry>M</entry><entry>Q</entry></row> <row><entry>A</entry><entry>G</entry><entry>R</entry><entry>S</entry><entry>T</entry><entry>Q</entry></row> <row><entry>A</entry><entry>G</entry><entry>V</entry><entry>S</entry><entry>W</entry><entry>X</entry></row> <row><entry>A</entry><entry>Y</entry><entry>Z</entry><entry>1</entry><entry>2</entry><entry>3</entry></row> <row><entry>A</entry><entry>Y</entry><entry>4</entry><entry>1</entry><entry>5</entry><entry>6</entry></row> <row><entry>A</entry><entry>Y</entry><entry>7</entry><entry>1</entry><entry>5</entry><entry>8</entry></row> </tbody> </tgroup> </table>

t:\ftemp>call xslt2 test.xml testfo.xsl test.fo

t:\ftemp>type testfo.xsl
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="http://www.w3.org/1999/XSL/Format";
      font-family="Times" font-size="20pt"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
      xsl:version="2.0">

  <layout-master-set>
    <simple-page-master master-name="frame"
                        page-height="11in" page-width="8.5in"
                        margin-top=".5in" margin-bottom=".5in"
                        margin-left=".5in" margin-right=".5in">
      <region-body region-name="frame-body"/>
    </simple-page-master>
  </layout-master-set>

  <page-sequence master-reference="frame">
    <flow flow-name="frame-body">
      <block>This is a test of spanning rows in XSL-FO</block>

<table text-align="center">
  <table-body>
    <xsl:for-each select="table/tgroup/tbody">
    <!--create a stream of table cells, marking the end of each row-->
      <!--only the first three columns of the table are grouped-->
      <xsl:for-each-group select="row" group-by="entry[1]">
        <table-cell number-rows-spanned="{count(current-group())[.>1]}">
          <block>
            <xsl:apply-templates select="entry[1]/node()"/>
          </block>
        </table-cell>
        <xsl:for-each-group select="current-group()" group-by="entry[2]">
          <table-cell number-rows-spanned="{count(current-group())[.>1]}">
            <block>
              <xsl:apply-templates select="entry[2]/node()"/>
            </block>
          </table-cell>
          <xsl:for-each-group select="current-group()" group-by="entry[3]">
            <table-cell number-rows-spanned="{count(current-group())[.>1]}">
              <block>
                <xsl:apply-templates select="entry[3]/node()"/>
              </block>
            </table-cell>
            <!--the rest of the columns are not grouped-->
            <xsl:for-each select="current-group()">
              <xsl:for-each select="entry[position()>3]">
                <!--mark the last cell of each row as true, others as false-->
                <table-cell ends-row="{position()=last()}">
                 <block>
                   <xsl:apply-templates/>
                 </block>
                </table-cell>
              </xsl:for-each>
            </xsl:for-each>
          </xsl:for-each-group>
        </xsl:for-each-group>
      </xsl:for-each-group>
    </xsl:for-each>
  </table-body>
</table>

    </flow>
  </page-sequence>
</root>

t:\ftemp>rem Done!



Since I had already written a DITA -> DITA stylesheet adding morerows= before discovering I misread your question, I've added below a stylesheet that does that with the same input data:


t:\ftemp>type test.xml <?xml version="1.0" encoding="UTF-8"?> <table> <tgroup> <tbody> <row><entry>A</entry><entry>B</entry><entry>C</entry><entry>D</entry><entry>E</entry><entry>F</entry></row> <row><entry>A</entry><entry>G</entry><entry>H</entry><entry>I</entry><entry>J</entry><entry>K</entry></row> <row><entry>A</entry><entry>G</entry><entry>H</entry><entry>L</entry><entry>M</entry><entry>N</entry></row> <row><entry>A</entry><entry>G</entry><entry>O</entry><entry>P</entry><entry>M</entry><entry>Q</entry></row> <row><entry>A</entry><entry>G</entry><entry>R</entry><entry>S</entry><entry>T</entry><entry>Q</entry></row> <row><entry>A</entry><entry>G</entry><entry>V</entry><entry>S</entry><entry>W</entry><entry>X</entry></row> <row><entry>A</entry><entry>Y</entry><entry>Z</entry><entry>1</entry><entry>2</entry><entry>3</entry></row> <row><entry>A</entry><entry>Y</entry><entry>4</entry><entry>1</entry><entry>5</entry><entry>6</entry></row> <row><entry>A</entry><entry>Y</entry><entry>7</entry><entry>1</entry><entry>5</entry><entry>8</entry></row> </tbody> </tgroup> </table>

t:\ftemp>call xslt2 test.xml testdita.xsl test.out.xml

t:\ftemp>type test.out.xml
<?xml version="1.0" encoding="UTF-8"?><table>
<tgroup>
<tbody>
<row><entry morerows="8">A</entry><entry>B</entry><entry>C</entry><entry>D</entry><entry>E</entry><entry>F</entry></row>
<row><entry morerows="4">G</entry><entry morerows="1">H</entry><entry>I</entry><entry>J</entry><entry>K</entry></row>
<row><entry>L</entry><entry>M</entry><entry>N</entry></row>
<row><entry>O</entry><entry>P</entry><entry>M</entry><entry>Q</entry></row>
<row><entry>R</entry><entry>S</entry><entry>T</entry><entry>Q</entry></row>
<row><entry>V</entry><entry>S</entry><entry>W</entry><entry>X</entry></row>
<row><entry morerows="2">Y</entry><entry>Z</entry><entry>1</entry><entry>2</entry><entry>3</entry></row>
<row><entry>4</entry><entry>1</entry><entry>5</entry><entry>6</entry></row>
<row><entry>7</entry><entry>1</entry><entry>5</entry><entry>8</entry></row></tbody>
</tgroup>
</table>
t:\ftemp>type testdita.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="2.0">


<xsl:template match="tbody">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<!--create a stream of entries, marking the end of each row-->
<xsl:variable name="stream-entries" as="element()*">
<!--only the first three columns of the table are grouped-->
<xsl:for-each-group select="row" group-by="entry[1]">
<entry>
<xsl:copy-of select="entry[1]/@*"/>
<xsl:for-each select="count(current-group())[.>1]">
<xsl:attribute name="morerows" select=".-1"/>
</xsl:for-each>
<xsl:copy-of select="entry[1]/node()"/>
</entry>
<xsl:for-each-group select="current-group()" group-by="entry[2]">
<entry>
<xsl:copy-of select="entry[2]/@*"/>
<xsl:for-each select="count(current-group())[.>1]">
<xsl:attribute name="morerows" select=".-1"/>
</xsl:for-each>
<xsl:copy-of select="entry[2]/node()"/>
</entry>
<xsl:for-each-group select="current-group()" group-by="entry[3]">
<entry>
<xsl:copy-of select="entry[3]/@*"/>
<xsl:for-each select="count(current-group())[.>1]">
<xsl:attribute name="morerows" select=".-1"/>
</xsl:for-each>
<xsl:copy-of select="entry[3]/node()"/>
</entry>
<!--the rest of the columns are not grouped-->
<xsl:for-each select="current-group()">
<xsl:copy-of select="entry[position()>3]"/>
<!--mark the end of each row for compartmentalization-->
<row-ends-here/>
</xsl:for-each>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:variable>
<!--reconstitute rows from the stream of entries by finding each row end-->
<xsl:for-each-group select="$stream-entries"
group-ending-with="row-ends-here">
<xsl:text>&#xa;</xsl:text>
<row>
<xsl:copy-of select="current-group()[position()&lt;last()]"/>
</row>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>


<xsl:template match="@*|node()"><!--identity for all other nodes-->
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>
t:\ftemp>rem Done!


I hope these examples help.


. . . . . . . . . Ken


-- Contact us for world-wide XML consulting and instructor-led training | Free 5-hour lecture: http://www.CraneSoftwrights.com/links/udemy.htm | Crane Softwrights Ltd. http://www.CraneSoftwrights.com/s/ | G. Ken Holman mailto:gkholman@xxxxxxxxxxxxxxxxxxxx | Google+ profile: https://plus.google.com/116832879756988317389/about | Legal business disclaimers: http://www.CraneSoftwrights.com/legal |

Current Thread