如何使用 XSL-FO(和 Apache FOP)计算从应用模板返回的元素

Posted

技术标签:

【中文标题】如何使用 XSL-FO(和 Apache FOP)计算从应用模板返回的元素【英文标题】:How to count elements returned from applied template using XSL-FO (and Apache FOP) 【发布时间】:2021-10-05 11:09:50 【问题描述】:

我想做一些类似于this 但使用 XSL-FO 和 Apache FOP 的事情。

我有这样的 xml 输入(就像在链接的问题中一样):

<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>

我想创建 XSL 文件,该文件将生成 XSL-FO 以生成 PDF(使用 Apache FOP),我将在其中显示以下文本:

Total Quantity: 3
Passed: 1
Failed: 2

这些数字是:

总数量 - 唯一序列号的数量(在本例中:1111、2222 和 3333), 通过 - 通过结果的数量,但仅计算每个唯一序列号的最新结果(最高 Date)(在这种情况下,只有 3333 SerialNumberDate 99), 失败 - 失败结果的数量,但仅计算每个唯一序列号的最新结果(最高 Date)(在这种情况下,Date 88 代表 1111,Date 40 代表 2222)。

换句话说,我只需要计算每个SerialNumber 的最新Date 的结果数。结果未排序。

我尝试了solution suggested by michael.hor257k(当我只使用 xslt 在浏览器中生成 html 时有效):

<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>

但是 Apache FOP 返回 Unknown formatting object "output" encountered 错误。如何处理此错误并显示我的结果摘要?


编辑:

这是我当前的 xsl 文件:

<?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>

【问题讨论】:

XSL-FO 是一种标记语言。您不会“将 XSL 文件与 XSL-FO 一起使用”。您使用 XSL 转换生成 XSL-FO 文档,然后可以使用该文档生成 PDF 文档。为此,您需要知道 (a) 您希望 PDF 看起来像什么,以及 (b) XSL-FO 文档必须如何构建才能生成所需的 PDF。只有这样,您才能构造 XSLT 样式表以生成预期的 XSL-FO。 我更正了“将 XSL 文件与 XSL-FO 一起使用”部分问题。关于 A 部分:我在问题中描述了它(见粗体部分)。我为布局/序列/流工作了 XSL-FO,但我认为这与这里无关。对于这个问题,我会对最简单的 PDF 布局感到满意。关于B:我不确定你的意思。我认为这是我问题的本质,当然我不知道(因此提出了问题)。我有 XML 源文件和要显示的文本(见上文)。现在,如何将 XML(使用 XSLT)转换为 XSL-FO 以在我的 PDF 中生成我需要的文本? “我会对最简单的 PDF 布局感到满意。” 很好。所以发布产生这种布局的 XSL-FO 文档。然后我们将能够建议您如何修改现有的 XSLT 以生成此类文档。 “最简单的 PDF 布局”已添加到问题中。 【参考方案1】:

因此,假设您希望 XSL 转换产生以下结果:

<?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>

您可以使用以下样式表:

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>

【讨论】:

这很聪明。通常我不能多次为单个变量赋值,但在这里你在 for-each 循环之后分配 fails 变量值。因此,这是 XSL 中计数器的巧妙解决方法。我说的对吗? 不完全。每个变量只分配一次。为了计算Fail 的出现次数,我们为每个这样的出现输出一个F,然后查看结果字符串的长度。这避免了将变量转换为节点集的需要。

以上是关于如何使用 XSL-FO(和 Apache FOP)计算从应用模板返回的元素的主要内容,如果未能解决你的问题,请参考以下文章

如何在使用 FOP 的 XSL-FO 中保留带有标题的表格但允许在表格主体内分页符

使用fop在XSL-FO中设置字体

XSL:FO / Apache FOP 中的内联图像数据

FOP XSL-FO 锚定在外部目的地

pdfbox / XSL + FOP 转换 PDF文档

pdfbox / XSL + FOP 转换 PDF文档