带有嵌入 HTML 的 PDF 报告

Posted

技术标签:

【中文标题】带有嵌入 HTML 的 PDF 报告【英文标题】:PDF report with embedded HTML 【发布时间】:2015-12-23 17:17:47 【问题描述】:

我们有一个基于 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- page->

                    <!-- 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 格式 内容。此内容将由我们支持 HTML 的编辑器(类似于CLEditorCKEditor 等)生成或从外部粘贴。

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

使用JTidy 将HTML 格式的字符串转换为正确的XHTML 进一步修改 Antenna House 中的 xhtml2fo.xsl 以删除所有文档范围和页面范围的转换 将此修改后的 XSLT 应用到我的 XHTML 字符串 (javax.xml.transform) 用XPath(javax.xml.xpath)提取根目录下的所有节点 将结果直接输入到现有的 XSL-FO 文档中

我有此类代码的准系统版本并收到以下错误:

(错误位置未知)org.apache.fop1.fo.ValidationException: "http://www.w3.org/1999/XSL/Formattable-body" 不是有效的孩子 的“fo:block”! (没有可用的上下文信息)

我的问题:

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

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

【问题讨论】:

【参考方案1】:

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

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

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

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

http://www.cloudformatter.com/css2pdf 就是基于这个完整的转换。该通用样式表可在此处获得:http://xep.cloudformatter.com/doc/XSL/xeponline-fo-translate-2.xsl

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

【讨论】:

我们需要支持&lt;span style="font-family: Arial, Verdana; font-size: 10pt; font-style: normal; font-variant: normal; line-height: normal; font-weight: normal;"&gt; 据我所知,您引用的样式表忽略了任何 style="" 属性。这就是为什么我将您指向另一个将处理整个 style="" CSS 字符串并将它们转换为 XSL FO 属性的原因。整个站点都基于此样式表,因为 css-to-pdf javascript 会抓取浏览器 DOM(所有 style="" 属性)并将 (X)HTML 输入转换为 XSL FO 并对其进行格式化。 我在这里有点困惑。您是说您的 XSL 可以单独使用,而不是作为整个云转换器的一部分? 它是 AGPL,拥有它,使用它(你会想要削减它以删除所有页面格式的东西,就像你删除另一个一样)。如果您对其进行了增强,请在 GITHUB 上让我们知道 css-to-pdf 项目。 github.com/Xportability/css-to-pdf 通用样式表的链接:xep.cloudformatter.com/doc/XSL/xeponline-fo-translate-2.xsl 似乎有点损坏。嗯,已经2岁多了。有人有最新的链接吗?【参考方案2】:

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

    拥有 FO 后,focheck 框架 (https://github.com/AntennaHouse/focheck) 将拥有目前可用的最完整的 FO 验证。

    fo:block 可以包含表格等。在 XSL 1.1 规范中,每个 FO 的定义都包括一个列出其允许内容的“内容”小节。参见,例如,http://www.w3.org/TR/xsl11/#fo_block。内容模型中“参数实体”的定义位于 http://www.w3.org/TR/xsl11/#d0e6532,但某些 FO 在其定义的文本中有额外的限制。

    您引用的文章似乎没有“使用 XPath 提取根下的所有节点”步骤,我不确定您为什么需要它。除此之外,使用 Java 完成这项工作似乎是一种合理的方法。


您可以将 &lt;!-- Field A --&gt; 替换为非 FO 标记,以提供足够的信息来引用要插入的字段,而不是将您的 JTidy-ed HTML 转换的 FO 插入到静态 FO 中。然后,您可以制作一个 XSLT 样式表,通过对 FO 部分进行身份转换(如@kevin-brown 的回答)并使用参考标记中的信息来构造与 document() 函数 (http://www.w3.org/TR/xslt#document) 一起使用以查找要插入的标记的 URI。

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

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

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

【讨论】:

本文将 HTML 文件转换为全新的 PDF 文件。我需要将结果嵌入到已经存在的 XSL-FO 文档中(已经有它的 &lt;fo:root&gt; 元素。我认为在 fo:block 中的另一个 root 不受欢迎。我错了吗? 不,你没有错:fo:block/fo:root 不是规范的一部分。我现在看到了您操纵 XPath 的原因。

以上是关于带有嵌入 HTML 的 PDF 报告的主要内容,如果未能解决你的问题,请参考以下文章

在 Delphi 中生成带有嵌入图像的 HTML 电子邮件

如何将带有嵌入图像的 XHTML+CSS 转换为 XSL-FO?

使用本地 SSAS 作为数据集的带有服务主体的嵌入式 powerbi 报告

在 HTML 中嵌入 PDF

使嵌入的 PDF 在 iPad 中可滚动

未嵌入字体的 Ghostscript 和 PDF