0

Source XML:

<Root>
    <Data>
        <Value>10</Value>
    </Data>
    <Data>
        <Value>10</Value>
    </Data>
    <Data>
        <Value>15</Value>
    </Data>
    <Data>
        <Value>2</Value>
    </Data>
    <Data>
        <Value>32</Value>
    </Data>
    <Data>
        <Value>5</Value>
    </Data>
    <Data>
        <Value>40</Value>
    </Data>
    <Data>
        <Value>18</Value>
    </Data>
    <Data>
        <Value>50</Value>
    </Data>
    <Data>
        <Value>1</Value>
    </Data>
    <Data>
        <Value>43</Value>
    </Data>
    <Data>
        <Value>5</Value>
    </Data>
    <Data>
        <Value>21</Value>
    </Data>
    <Data>
        <Value>87</Value>
    </Data>
    <Data>
        <Value>8</Value>
    </Data>
....
</Root>

XSL-FO Code: My code contains one table-row, and one table-footer

<fo:table>
    <fo:table-column column-width="50mm"/>
    <fo:table-column column-width="50mm"/>
    <fo:table-footer>
        <fo:table-row>
            <fo:table-cell>
                <fo:block>Subtotal: </fo:block>
            </fo:table-cell>
            <fo:table-cell>
                <fo:block>
                    <fo:retrieve-table-marker retrieve-class-name="Subtotal" retrieve-position-within-table="last-starting"/>
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
    </fo:table-footer>
    <fo:table-body>
    <xsl:for-each select="/Root/Data">      
     <fo:table-row>
            <fo:table-cell>
                <fo:block>Value:</fo:block>
            </fo:table-cell>
            <fo:table-cell>
                <fo:block> 
                    <xsl:value-of select="Value"/>
                    <fo:marker marker-class-name="Subtotal">
                        <xsl:variable name="position" select="position()"/>
                        <xsl:value-of xpath="sum(../Data[position() &lt;= $position]/Value)"/>
                    </fo:marker>
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
      </xsl:for-each>
    </fo:table-body>
</fo:table>

I am generating PDF outputs from the above table. In PDF, the table can have up to 15 table rows per page. On each page, the subtotal needs to be calculated, by suming up only the Values displayed on the current page.

My table above (the only solution I found was using a table-marker) calculates the correct subtotal for Page1, but fails for Page2, etc. It appends the subtotals from all previous pages to the current page.

For example: if the subtotal of Value elements displayed on Page 1 is 10, and the subtotal of Values displayed on Page 2 is 20, my table will display on Page1, the subtotal as 10, but on Page2, the subtotal will 30, instead of 20. Can anyone help in solving this?

Thanks in Advance

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
iwasaki
  • 77
  • 3
  • Is that number of rows/Data elements per page known (e.g. 15) so that you could process e.g. `for-each select="Root/Data[position() mod 15 = 1]" and then form e.g. `` and sum e.g. `sum($data-of-page/Value)`? – Martin Honnen Dec 06 '21 at 20:23
  • Yes, the number of rows per page is known (no more than 15 pages). Thank you so much, this works in calculating the sum, but now I am struggling with showing all 15 Values in the table. By repeating after Root/Data[position() mod 15 = 1] is displaying only first element and the sum. – iwasaki Dec 07 '21 at 12:25
  • You can also use fo:marker to rewrite a marker containing the subtotal and then retrieve that marker into the footer area. The footer area could contain a "fake" table row that contains this subtotal. see https://stackoverflow.com/questions/17195230/is-it-possible-to-get-a-running-total-to-display-in-xslt-xsl-fo?rq=1 – Kevin Brown Dec 07 '21 at 18:02

2 Answers2

1

Use "positional grouping" in XSLT 1 as in

<xsl:for-each select="Root/Data[position() mod 15 = 1]">
  <xsl:variable name="data-of-page" select=". | following-sibling::Data[position() &lt; 15]"/>

  <xsl:for-each select="$data-of-page">
     <fo:table-row>
            <fo:table-cell>
                <fo:block>Value:</fo:block>
            </fo:table-cell>
            <fo:table-cell>
                <fo:block> 
                    <xsl:value-of select="Value"/>
                    <xsl:if test="position() = last()">
                      <fo:marker marker-class-name="Subtotal">
                        <xsl:value-of xpath="sum($data-of-page/Value)"/>
                      </fo:marker>
                    </xsl:if>
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
  </xsl:for-each>
</xsl:for-each>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
0

The sole advantage of this version is that it will give correct subtotals if the first page has fewer than 15 rows; e.g., if there's a title or text before the start of the table.

<xsl:stylesheet
    version="1.0"
    xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions"
    xmlns:fo="http://www.w3.org/1999/XSL/Format"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:strip-space elements="*" />

<xsl:output indent="yes" />

<xsl:variable name="rows-per-page" select="15" />

<xsl:template match="/">
  <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"
           xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions"
           xml:lang="en">
    <fo:layout-master-set>
      <fo:simple-page-master master-name="spm" size="A7">
        <fo:region-body margin="20mm" margin-top="15mm" />
      </fo:simple-page-master>
    </fo:layout-master-set>
    <fo:page-sequence master-reference="spm">
      <fo:flow flow-name="xsl-region-body">
        <xsl:apply-templates />
      </fo:flow>
    </fo:page-sequence>
  </fo:root>
</xsl:template>

<xsl:template match="Root">
  <fo:block />
  <fo:table space-before="3lh">
    <fo:table-column column-width="50%"/>
    <fo:table-column column-width="50%"/>
    <fo:table-footer>
      <fo:table-row>
        <fo:table-cell>
          <fo:block>Subtotal: </fo:block>
        </fo:table-cell>
        <fo:table-cell>
      <fo:block-container>
            <fo:retrieve-table-marker retrieve-class-name="Remainder" retrieve-position-within-table="first-starting"/>
            <fo:retrieve-table-marker retrieve-class-name="Subtotal" retrieve-position-within-table="last-starting"/>
      </fo:block-container>
        </fo:table-cell>
      </fo:table-row>
    </fo:table-footer>
    <fo:table-body>
      <xsl:apply-templates />
    </fo:table-body>
  </fo:table>
</xsl:template>

<xsl:template match="Data">
  <fo:table-row>
    <fo:table-cell>
      <fo:block>Value:</fo:block>
    </fo:table-cell>
    <fo:table-cell>
      <fo:block>
        <fo:marker marker-class-name="Subtotal">
          <xsl:if test="position() != last()">
          <fo:block-container width="4em" background-color="white">
            <fo:block>
              <xsl:value-of
                  select="sum(preceding-sibling::Data[position() &lt; $rows-per-page]) + Value"/>
            </fo:block>
          </fo:block-container>
          </xsl:if>
        </fo:marker>
        <fo:marker marker-class-name="Remainder">
          <fo:block-container width="4em" z-index="-1" absolute-position="absolute">
            <fo:block>
              <xsl:value-of
                  select="sum(following-sibling::Data) + Value"/>
            </fo:block>
          </fo:block-container>
        </fo:marker>
        <xsl:value-of select="Value"/>
      </fo:block>
    </fo:table-cell>
  </fo:table-row>
</xsl:template>

</xsl:stylesheet>
Tony Graham
  • 7,306
  • 13
  • 20