带有嵌入 HTML 的 PDF 报告
Posted
技术标签:
【中文标题】带有嵌入 HTML 的 PDF 报告【英文标题】:PDF report with embedded HTML 【发布时间】:2015-12-23 17:17:47 【问题描述】:我们有一个基于 Java 的系统,它从数据库中读取数据,将单个数据字段与预设的 XSL-FO
标记合并,并将结果转换为 PDF
和 Apache FOP
。
XSL-FO
格式如下所示:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html [
<!ENTITY nbsp " ">
<!-- 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 的编辑器(类似于CLEditor
、CKEditor
等)生成或从外部粘贴。
我的计划是按照食谱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”! (没有可用的上下文信息)
我的问题:
-
解决此问题的方法是什么?
<fo:block>
能否用作一个通用容器,其中嵌套了其他对象(包括表格)?
这是解决任务的总体合理方法吗?
如果有人已经“去过那里”,请分享您的经验。
【问题讨论】:
【参考方案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 属性。
【讨论】:
我们需要支持<span style="font-family: Arial, Verdana; font-size: 10pt; font-style: normal; font-variant: normal; line-height: normal; font-weight: normal;">
据我所知,您引用的样式表忽略了任何 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 完成这项工作似乎是一种合理的方法。
您可以将 <!-- Field A -->
替换为非 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 文档中(已经有它的<fo:root>
元素。我认为在 fo:block
中的另一个 root
不受欢迎。我错了吗?
不,你没有错:fo:block/fo:root
不是规范的一部分。我现在看到了您操纵 XPath 的原因。以上是关于带有嵌入 HTML 的 PDF 报告的主要内容,如果未能解决你的问题,请参考以下文章
如何将带有嵌入图像的 XHTML+CSS 转换为 XSL-FO?