嵌入 HTML 的 PDF 报告
PDF report with embedded HTML
我们有一个基于 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-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 的编辑器生成(类似于 CLEditor
、CKEditor
等)或从外部粘贴。
我的计划是按照食谱from this JavaWorld article:
- 使用
JTidy
将 HTML 格式的字符串转换为正确的 XHTML
- 进一步修改 xhtml2fo.xsl 来自 Antenna House 以删除所有文档范围和页面范围的转换
- 将修改后的 XSLT 应用于我的 XHTML 字符串 (javax.xml.transform)
- 用XPath(javax.xml.xpath)提取根目录下的所有节点
- 将结果直接输入现有的 XSL-FO 文档
我有此类代码的基本版本,但出现以下错误:
(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)
我的问题:
- 解决此问题的方法是什么?
- 能否
<fo:block>
作为一个通用容器,其中嵌套了其他对象(包括表格)?
- 这是解决任务的总体合理方法吗?
如果有人“在那里做过”,请分享您的经验。
排除故障的最佳方法是使用验证 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 属性。
如果您在 oXygen 或 XML Spy 中使用 XSLT 调试器,那么您可以逐步完成转换。使用 oXygen -- 不确定 XML Spy 或其他编辑器 -- 如果您单击调试器输出中的标记,oXygen 会突出显示来自源代码和生成该节点的样式表的标记。
获得 FO 后,focheck 框架 (https://github.com/AntennaHouse/focheck) 具有当前可用的最完整的 FO 验证。
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 在其定义的文本中有额外的限制。
您引用的文章似乎没有 '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
我们有一个基于 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-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 的编辑器生成(类似于 CLEditor
、CKEditor
等)或从外部粘贴。
我的计划是按照食谱from this JavaWorld article:
- 使用
JTidy
将 HTML 格式的字符串转换为正确的 XHTML - 进一步修改 xhtml2fo.xsl 来自 Antenna House 以删除所有文档范围和页面范围的转换
- 将修改后的 XSLT 应用于我的 XHTML 字符串 (javax.xml.transform)
- 用XPath(javax.xml.xpath)提取根目录下的所有节点
- 将结果直接输入现有的 XSL-FO 文档
我有此类代码的基本版本,但出现以下错误:
(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)
我的问题:
- 解决此问题的方法是什么?
- 能否
<fo:block>
作为一个通用容器,其中嵌套了其他对象(包括表格)? - 这是解决任务的总体合理方法吗?
如果有人“在那里做过”,请分享您的经验。
排除故障的最佳方法是使用验证 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 属性。
如果您在 oXygen 或 XML Spy 中使用 XSLT 调试器,那么您可以逐步完成转换。使用 oXygen -- 不确定 XML Spy 或其他编辑器 -- 如果您单击调试器输出中的标记,oXygen 会突出显示来自源代码和生成该节点的样式表的标记。
获得 FO 后,focheck 框架 (https://github.com/AntennaHouse/focheck) 具有当前可用的最完整的 FO 验证。
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 在其定义的文本中有额外的限制。您引用的文章似乎没有 '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