xmlxsltxsl-fomuenchian-grouping

How to count elements returned from applied template using XSL-FO (and Apache FOP)


I would like to do something similar to this but using XSL-FO and Apache FOP.

I have xml input like this (exactly like in the linked question):

<Results>
    <Result ID="0">
        <SerialNumber>3333</SerialNumber>
        <Status>Fail</Status>
        <Date>21</Date>
    </Result>
    <Result ID="1">
        <SerialNumber>1111</SerialNumber>
        <Status>Fail</Status>
        <Date>34</Date>
    </Result>
    <Result ID="2">
        <SerialNumber>1111</SerialNumber>
        <Status>Pass</Status>
        <Date>67</Date>
    </Result>
    <Result ID="3">
        <SerialNumber>2222</SerialNumber>
        <Status>Fail</Status>
        <Date>40</Date>
    </Result>
    <Result ID="4">
        <SerialNumber>1111</SerialNumber>
        <Status>Fail</Status>
        <Date>55</Date>
    </Result>
    <Result ID="5">
        <SerialNumber>1111</SerialNumber>
        <Status>Fail</Status>
        <Date>88</Date>
    </Result>
    <Result ID="6">
        <SerialNumber>2222</SerialNumber>
        <Status>Fail</Status>
        <Date>22</Date>
    </Result>
    <Result ID="7">
        <SerialNumber>1111</SerialNumber>
        <Status>Fail</Status>
        <Date>86</Date>
    </Result>
    <Result ID="8">
        <SerialNumber>3333</SerialNumber>
        <Status>Pass</Status>
        <Date>99</Date>
    </Result>
</Results>

I would like to create XSL file which will generate XSL-FO to generate PDF (using Apache FOP) in which I will display the following text:

Total Quantity: 3
Passed: 1
Failed: 2

Those numbers are:

In another words I need to count number of results only for the latest Date per SerialNumber. Results are not sorted.

I tried solution suggested by michael.hor257k (which works when I use just xslt to generate html in my browser):

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:key name="result-by-sn" match="Result" use="SerialNumber" />

<xsl:template match="/Results">
    <xsl:variable name="temp">
        <xsl:for-each select="Result[count(. | key('result-by-sn', SerialNumber)[1]) = 1]">
            <xsl:for-each select="key('result-by-sn', SerialNumber)">
                <xsl:sort select="Date" order="descending"/>
                <xsl:if test="position()=1 and Status='Fail'">x</xsl:if>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:variable>
    <output>
        <xsl:value-of select="string-length($temp)"/>
    </output>
</xsl:template>

</xsl:stylesheet>

But Apache FOP returns Unknown formatting object "{}output" encountered error. How to deal with this error and display my results summary?


EDIT:

Here is my current xsl file:

<?xml version="1.0" encoding="UTF-8"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">

<!-- KEY FOR FINDING UUT RESULTS -->
<xsl:key name="result-by-sn" match="Results/Result" use="SerialNumber"/>

    <xsl:template match="/">
        <fo:root>
            <fo:layout-master-set>
                <fo:simple-page-master master-name="my_page" margin="0.5in">
                    <fo:region-body/>
                </fo:simple-page-master>
            </fo:layout-master-set>
            <fo:page-sequence master-reference="my_page">
                <fo:flow flow-name="xsl-region-body">
                    <fo:block>Total Quantity: <xsl:value-of select="count(Results/Result[generate-id() = generate-id(key('result-by-sn', SerialNumber)[1])])"/></fo:block>
                    <fo:block>Passed: <!--<xsl:apply-templates select="Results" mode="count"><xsl:with-param name="status" select="'Pass'"/></xsl:apply-templates>--></fo:block>
                    <fo:block>Failed: <!--<xsl:apply-templates select="Results" mode="count"><xsl:with-param name="status" select="'Fail'"/></xsl:apply-templates>--></fo:block>
                </fo:flow>
            </fo:page-sequence>
        </fo:root>
    </xsl:template>

<!-- TEMPLATE TO COUNT RESULTS -->
<!--<xsl:template match="Results" mode="count">
    <xsl:param name="status" select="'Pass'"/>
    <xsl:variable name="temp">
        <xsl:for-each select="Result[generate-id()=generate-id(key('result-by-sn', SerialNumber)[1])]">
            <xsl:for-each select="key('result-by-sn', SerialNumber)">
                <xsl:sort select="Date" order="descending"/>
                <xsl:if test="position() = 1 and  Status = $status">x</xsl:if>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:variable>
    <output>
        <xsl:value-of select="string-length($temp)"/>
    </output>
</xsl:template>-->

</xsl:stylesheet>

Solution

  • So, assuming you want your XSL transformation to produce a result of:

    <?xml version="1.0" encoding="UTF-8"?>
    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
      <fo:layout-master-set>
        <fo:simple-page-master master-name="my_page" margin="0.5in">
          <fo:region-body/>
        </fo:simple-page-master>
      </fo:layout-master-set>
      <fo:page-sequence master-reference="my_page">
        <fo:flow flow-name="xsl-region-body">
          <fo:block>Total Quantity: 3</fo:block>
          <fo:block>Passed: 1</fo:block>
          <fo:block>Failed: 2</fo:block>
        </fo:flow>
      </fo:page-sequence>
    </fo:root>
    

    you can use the following stylesheet:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fo="http://www.w3.org/1999/XSL/Format">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    
    <xsl:key name="result-by-sn" match="Result" use="SerialNumber" />
    
    <xsl:template match="/Results">
        <!-- determine counts -->
        <xsl:variable name="distinct-results" select="Result[count(. | key('result-by-sn', SerialNumber)[1]) = 1]" />
        <xsl:variable name="count-distinct" select="count($distinct-results)" />
        <xsl:variable name="fails">
            <xsl:for-each select="$distinct-results">
                <xsl:for-each select="key('result-by-sn', SerialNumber)">
                    <xsl:sort select="Date" order="descending"/>
                    <xsl:if test="position()=1 and Status='Fail'">F</xsl:if>
                </xsl:for-each>
            </xsl:for-each>
        </xsl:variable>
        <xsl:variable name="count-fails" select="string-length($fails)" />
        <!-- output -->
        <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
            <fo:layout-master-set>
                <fo:simple-page-master master-name="my_page" margin="0.5in">
                    <fo:region-body/>
                </fo:simple-page-master>
            </fo:layout-master-set>
            <fo:page-sequence master-reference="my_page">
                <fo:flow flow-name="xsl-region-body">
                    <fo:block>
                        <xsl:text>Total Quantity: </xsl:text>
                        <xsl:value-of select="$count-distinct"/>
                    </fo:block>
                    <fo:block>
                        <xsl:text>Passed: </xsl:text>
                        <xsl:value-of select="$count-distinct - $count-fails"/>
                    </fo:block>
                    <fo:block>
                        <xsl:text>Failed: </xsl:text>
                        <xsl:value-of select="$count-fails"/>
                    </fo:block>
                </fo:flow>
            </fo:page-sequence>
        </fo:root>
    </xsl:template>
    
    </xsl:stylesheet>