嵌入 HTML 的 PDF 报告

PDF report with embedded HTML

我们有一个基于 Java 的系统,它从数据库中读取数据,将各个数据字段与预设的 XSL-FO 标签合并,并将结果转换为 PDFApache FOP.

XSL-FO 格式如下:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE Html [
<!ENTITY nbsp  "&#160;"> 
    <!-- all other entities -->
]>
<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" indent="yes" />
    <xsl:template match="/">

        <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:svg="http://www.w3.org/2000/svg" font-family="..." font-size="...">
            <fo:layout-master-set>          
                <fo:simple-page-master master-name="Letter Page" page-width="8.500in" page-height="11.000in">

                    <!-- appropriate settings -->

                </fo:simple-page-master>
            </fo:layout-master-set>
            <fo:page-sequence master-reference="Letter Page">

                <!-- some static content -->

            <fo:flow flow-name="xsl-region-body">
                    <fo:block>
                        <fo:table ...>
                            <fo:table-column ... />
                            <fo:table-body>
                                <fo:table-row>
                                    <fo:table-cell ...>
                                        <fo:block text-align="...">
                                            <fo:inline font-size="..." font-weight="...">
                                                <!-- Header / Title -->
                                            </fo:inline>
                                        </fo:block>
                                    </fo:table-cell>
                                </fo:table-row>
                            </fo:table-body>
                        </fo:table>
                    </fo:block>

                    <fo:block>

                        <fo:table ...>
                            <fo:table-column ... />
                            <fo:table-body> 
                                <fo:table-row>
                                    <fo:table-cell>
                                        <fo:block ...>
                                            <!-- Field A -->                                
                                        </fo:block>
                                    </fo:table-cell>
                                </fo:table-row>
                            </fo:table-body>
                        </fo:table>

                        <!-- Other fields in a very similar fashion as the above "Field A" -->

                    </fo:block>

                </fo:flow>      

            </fo:page-sequence>

        </fo:root>              

    </xsl:template>

</xsl:stylesheet>

现在我正在寻找一种方法来允许某些字段包含静态 HTML-formatted 内容。此内容将由我们支持 HTML 的编辑器生成(类似于 CLEditorCKEditor 等)或从外部粘贴。

我的计划是按照食谱from this JavaWorld article:

我有此类代码的基本版本,但出现以下错误:

(Location of error unknown)org.apache.fop1.fo.ValidationException: "{http://www.w3.org/1999/XSL/Format}table-body" is not a valid child of "fo:block"! (No context info available)

我的问题:

  1. 解决此问题的方法是什么?
  2. 能否<fo:block>作为一个通用容器,其中嵌套了其他对象(包括表格)?
  3. 这是解决任务的总体合理方法吗?

如果有人“在那里做过”,请分享您的经验。

排除故障的最佳方法是使用验证 viewer/editor 检查 XSL FO。许多(例如 oXygen)会在您打开它们时向您显示 XSL FO 结构中的错误,并且它们会描述问题(就像报告的错误一样)。

在你的例子中,你显然有一个 fo:table-body 作为 fo:block 的 child。它不可能是。一个 fo:table-body 只有一个有效的 parent, fo:table。您要么缺少 fo:table 标记,要么错误地在此位置插入了 fo:block。

在我看来,我做的事情可能会略有不同。我会将 XHTML 内容内嵌到 XSL FO 中您想要的位置。然后我将创建一个恒等式转换,它复制 fo-based 的所有内容,但使用 XSL 转换 XHTML 部分。这样,您实际上可以在像 oXygen 这样的 XSL 编辑器中执行该转换,并查看错误发生的位置和确切原因。像任何其他调试器一样。

注意:您可能还希望查看其他 XSL,尤其是当您的 HTML 可能具有任何 style="" CSS 属性时。如果是这种情况,它不是简单的 HTML,那么您将需要一种更好的方法来处理 HTML 和 CSS 到 FO。

http://www.cloudformatter.com/css2pdf is based on this complete transform. That general stylesheet is available here: http://xep.cloudformatter.com/doc/XSL/xeponline-fo-translate-2.xsl

我是该样式表的作者。它做的比你问的要多得多,但有一个相当复杂的解析递归,用于将 CSS 样式转换为 XSL FO 属性。

  1. 如果您在 oXygen 或 XML Spy 中使用 XSLT 调试器,那么您可以逐步完成转换。使用 oXygen -- 不确定 XML Spy 或其他编辑器 -- 如果您单击调试器输出中的标记,oXygen 会突出显示来自源代码和生成该节点的样式表的标记。

    获得 FO 后,focheck 框架 (https://github.com/AntennaHouse/focheck) 具有当前可用的最完整的 FO 验证。

  2. fo:block 可以包含表格等。在 XSL 1.1 规范中,每个 FO 的定义都包含一个 'Contents' 小节,其中列出了其允许的内容。参见,例如,http://www.w3.org/TR/xsl11/#fo_block. The definitions of the 'parameter entities' in the content models are at http://www.w3.org/TR/xsl11/#d0e6532,但一些 FO 在其定义的文本中有额外的限制。

  3. 您引用的文章似乎没有 'extract all the nodes under the root with XPath' 步骤,我不确定您为什么需要它。除此之外,它看起来是使用 Java.

    完成这项工作的合理方法

不是将从 JTidy-ed HTML 转换的 FO 插入静态 FO,而是可以将 <!-- Field A --> 替换为非 FO 标记,该标记提供足够的信息来引用要插入的字段。然后,您可以制作一个 XSLT 样式表,通过对 FO 部分进行恒等变换,将模板+参考文档转换为直接 FO——正如@kevin-brown 的回答——并使用参考标记中的信息构建URI 与 document() 函数 (http://www.w3.org/TR/xslt#document) 一起使用以查找要插入的标记。

如果字段内容的 FO 位于磁盘上,那么使用 document() 很简单。如果不是,那么您必须执行一些操作,例如覆盖 XSLT 处理器使用的 URIResolver,这样它就可以正确地检索内容,而不是在磁盘上查找。您甚至可以让 JTidying 作为检索 HTML 的 URIResolver 的一部分发生。您也可以将 URIResolver 转换为 FO 'inside',或者也像@kevin-brown 建议的那样,将其作为单独的模式进行。如果转换在 URIResolver 检索 FO 之前或期间完成,那么模板+对 FO 的引用的 'main' 转换只需要提取 FO 子文档的正确部分,例如document('constructed-URI')/fo:root/fo:page-sequence/*。但是,如果您要从 Antenna House 修改样式表,那么您应该能够将其修改为不产生外部 fo:root,等等。

几年前我做了类似的事情,为基于 XSLT 的服务器覆盖了 libxslt XSLT 处理器的 URI 解析器:内部 XSLT 处理器连续运行的上下文被保存为特殊 URI 的文档,不一定完全写入文件系统。

相反,您可以编写一个扩展函数来查找对字段的引用。例如,Print and Page Layout Community Group @ W3C 已经为多个 XSLT 处理器生成了扩展函数,这些处理器在 XSLT 转换的中间运行一个 FO 处理器,以获取格式化结果的区域树的 XML .参见 http://www.w3.org/community/ppl/wiki/XSLTExtensions