XQuery/从 XSL-FO 文件生成 PDF
您希望从 XML 文档生成具有精确页面布局的文档,例如生成 PDF。
通常,生成 PDF 文档所需的步骤为
- 检索或计算基础 XML 文档
- 将 XML 文件转换为 XSL-FO 标记,可能使用 XQuery 类型转换或 XSL
- 使用免费的 Apache FOP 或商业 FOP 渲染引擎(例如
http://www.renderx.com/ RenderX] 或 Antennahouse 将 XSL-FO 转换为 PDF
我们将使用内置的 eXist 函数将 XSL-FO 文件转换为 PDF。(如果此模块未安装和配置,请参阅 安装 XSL-FO 模块。)
该函数是 xslfo:render()。它具有以下结构
let $pdf-binary := xslfo:render($input-xml-fo-document, 'application/pdf', $parameters)
或者,如果您使用 XSL-FO 配置文件
let $pdf-binary := xslfo:render($input-xml-fo-document, 'application/pdf', $parameters, $fo-config-file)
此文件可以直接保存到 XML 文件系统。它将被存储为不可搜索的二进制文档。
然后,您可以通过提供指向文件的链接直接查看它,也可以通过使用 response:stream-binary() 函数直接将其发送到浏览器,如下所示
return response:stream-binary($pdf-binary, 'application/pdf', 'output.pdf')
以下程序将生成一个包含文本“Hello World”的 PDF 文档。
xquery version "1.0";
declare namespace fo="http://www.w3.org/1999/XSL/Format";
declare namespace xslfo="http://exist-db.org/xquery/xslfo";
let $fo :=
let $pdf := xslfo:render($fo, "application/pdf", ())
return response:stream-binary($pdf, "application/pdf", "output.pdf")
您将需要一个将 XSL-FO 转换为 PDF 的模块。以下是一些示例:
- Apache FOP 处理器(免费开源)
- Antenna House FOP 处理器(商业) http://www.antennahouse.com/
- RenderX FTP 处理器(商业) http://www.renderx.com/
确保加载模块扩展。您可以通过转到 $EXIST_HOME/conf.xml 文件并取消以下行的注释来执行此操作(大约在第 769 行)
<module class="org.exist.xquery.modules.xslfo.XSLFOModule"
uri="http://exist-db.org/xquery/xslfo">
<parameter name="processorAdapter" value="org.exist.xquery.modules.xslfo.ApacheFopProcessorAdapter"/>
</module
其中 processorAdapter 参数的可能值为
org.exist.xquery.modules.xslfo.ApacheFopProcessorAdapter for Apache's FOP
如果模块正确加载,那么您应该在函数文档中看到它。
确保您已正确编辑 $EXIST_HOME/extensions/build.properties 以将 XSLFO 设置为 true
更改
# XSL FO transformations (Uses Apache FOP) include.module.xslfo = false
为
include.module.xslfo = true
更改这两个文件后,您需要在 $EXIST_HOME 中运行“build.sh”或“build.bat”程序,以便将新的 FOP 二进制文件放入 jar 文件中。
确保构建文件可以从 Apache 网站访问正确的 fop.jar 文件。
eXist 带有一个示例 ant 任务,可以自动下载 FOP 分发 zip 文件,提取我们需要的 jar 文件树,并删除其余文件。以下是 eXist 1.4 $EXIST_HOME/modules/build.xml 中的 ant 目标
<target name="prepare-libs-xslfo" unless="libs.available.xslfo" if="include.module.xslfo.config">
<echo message="Load: ${include.module.xslfo}"/>
<echo message="------------------------------------------------------"/>
<echo message="Downloading libraries required by the xsl-fo module"/>
<echo message="------------------------------------------------------"/>
<!-- Apache FOP .95 -->
<get src="${include.module.xslfo.url}" dest="fop-0.95-bin.zip" verbose="true" usetimestamp="true" />
<unzip src="fop-0.95-bin.zip" dest="${top.dir}/${lib.user}">
<patternset>
<include name="fop-0.95/build/fop.jar"/>
<include name="fop-0.95/lib/batik-all-1.7.jar"/>
<include name="fop-0.95/lib/xmlgraphics-commons-1.3.1.jar"/>
</patternset>
<mapper type="flatten"/>
</unzip>
<delete file="fop-0.95-bin.zip"/>
</target>
请注意,现在提供 fop 1.0,因此您可以将此任务更改为以下内容
<target name="prepare-libs-xslfo" unless="libs.available.xslfo" if="include.module.xslfo.config">
<echo message="Load: ${include.module.xslfo}"/>
<echo message="------------------------------------------------------"/>
<echo message="Downloading libraries required by the xsl-fo module"/>
<echo message="------------------------------------------------------"/>
<!-- Download the Apache FOP Processor from the Apache Web Site-->
<get src="${include.module.xslfo.url}" dest="fop-1.0-bin.zip" verbose="true" usetimestamp="true" />
<unzip src="fop-1.0-bin.zip" dest="${top.dir}/${lib.user}">
<patternset>
<include name="fop-1.0/build/fop.jar"/>
<include name="fop-1.0/lib/batik-all-1.7.jar"/>
<include name="fop-1.0/lib/xmlgraphics-commons-1.3.1.jar"/>
</patternset>
<mapper type="flatten"/>
</unzip>
<delete file="fop-1.0-bin.zip"/>
</target>
以下是一个示例记录
prepare-xslfo:
[echo] Load: true
[echo] ------------------------------------------------------
[echo] Downloading libraries required by the xsl-fo module
[echo] ------------------------------------------------------
[fetch] Getting: http://apache.cs.uu.nl/dist/xmlgraphics/fop/binaries/fop-1.0-bin.zip
[fetch] To: C:\DOCUME~1\DANMCC~1\LOCALS~1\Temp\FetchTask8407348433221748527tmp
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................................................
[fetch] ....................
[fetch] Expanding: C:\DOCUME~1\DANMCC~1\LOCALS~1\Temp\FetchTask8407348433221748527tmp into C:\ws\exist-trunk\lib\us
在此过程结束时,您应该在 $EXIST_HOME/lib/extensions 文件夹中看到以下三个 jar 文件
cd $EXIST_HOME/lib/extensions $ ls -l -rwxrwxrwx+ 1 Dan McCreary None 3318083 2010-12-10 09:23 batik-all-1.7.jar -rwxrwxrwx+ 1 Dan McCreary None 3079811 2010-12-10 09:23 fop.jar -rwxrwxrwx+ 1 Dan McCreary None 569113 2010-12-10 09:23 xmlgraphics-commons-1.4.jar
如果您没有看到这些文件,则可以手动从 XSL-FO 二进制文件的下载中复制它们。
现在,转到 $EXIST_HOME 目录并键入“build”。您不应该看到任何错误消息。如果您看到错误,请转到构建文件并修复或删除这些错误。
重新启动后,您应该能够看到 XSL-FO 将文件转换为 PDF 文件。
RenderX 是一个商业 FOP 处理器,用于代替 Apache FOP 处理器。
在 eXist 1.4 上,您必须在 extensions/build.properties 中启用 include.module.xslfo = true 并运行“build.sh”或“build.bat”。如果您运行 2.0 版本,则此步骤不是必需的。
编辑 conf.xml 并注释掉对默认 Apache xslfo 模块的引用。将模块更改为使用 RenderX,如下所示
<module uri="http://exist-db.org/xquery/xslfo" class="org.exist.xquery.modules.xslfo.XSLFOModule">
<parameter name="processorAdapter" value="org.exist.xquery.modules.xslfo.RenderXXepProcessorAdapter"/>
</module>
将所有 .jar 从 XEP/lib 复制到 $EXIST_HOME/lib/user
重启 eXist 数据库。
将您的 XQuery 更改为包含 xep 配置作为 XML 元素并将其传递给渲染函数
let $pdf := xslfo:render(fo:main($id), "application/pdf", (), $config)
return
response:stream-binary($pdf, "media-type=application/pdf", $id || ".pdf")
在 $config 中,您需要确保许可证和字体的路径指向磁盘上的正确位置。
引用图像时,您必须使用绝对路径引用并确保服务器具有读取权限,或者使用相对路径引用。 相对路径引用的根目录可以在 xslfo 配置文件中设置。
xquery version "1.0";
declare namespace fo="http://www.w3.org/1999/XSL/Format";
declare namespace xslfo="http://exist-db.org/xquery/xslfo";
let $fop-config :=
<fop version="1.0">
<!-- Base URL for resolving relative URLs -->
<base>https://127.0.0.1:8080/exist/rest/db/nosql/pdf/images</base>
</fop>
let $fo := doc('/db/test/xslfo/fo-templates/sample-fo-file-with-external-references.fo')
let $pdf := xslfo:render($fo, "application/pdf", (), $fop-config)
return response:stream-binary($pdf, "application/pdf", "output.pdf")
您可能不想硬编码主机名、端口和上下文。 为了使它在任何主机、端口和上下文中都能正常工作,您可以使用以下代码来构建您的 FOP 基础。
let $get-server-name := request:get-server-name()
let $port := xs:string(request:get-server-port())
let $conditional-port :=
if ($port = '80') then () else concat(':', $port)
let $get-context-path := request:get-context-path()
let $fop-config :=
<fop version="1.0">
<!-- Base URL for resolving relative URLs -->
<base>http://{$get-server-name}{$conditional-port}{request:get-context-path()}/rest/db/nosql/resources/images</base>
</fop>
现在您可以使用以下 FOP 模板生成 PDF。
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="my-page">
<fo:region-body margin="0.5in"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="my-page">
<fo:flow flow-name="xsl-region-body">
<fo:block>Test of external SVG reference
</fo:block>
<fo:block>
SVG Chart Test
<fo:external-graphic content-width="7.5in" scaling="uniform" src="url(my-test-image.png)"/>
content-width="7.5in"
scaling="uniform"
src="url(chart.svg)"
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
创建 PDF 文档时,您可以直接在使用 SVG 格式的 PDF 文件中包含“线框图”。
从 SVG 到 PDF 的转换过程中存在一些问题,但大多数线框图都能很好地转换。
要在 eXist 中使用 SVG 渲染,您还必须加载 Sun AWT 库(如果您引用了 SVG 图像)。
http://xmlgraphics.apache.org/fop/0.95/graphics.html#batik
这意味着您必须在 JVM 启动时告诉 Java 强制加载 awt 库。
-Djava.awt.headless=true
在您的 $EXIST_HOME/startup.bat 或 $EXIST_HOME/startup.sh 中,您需要添加以下内容。
set JAVA_OPTS="-Xms128m -Xmx512m -Dfile.encoding=UTF-8 -Djava.endorsed.dirs=%JAVA_ENDORSED_DIRS% -Djava.awt.headless=true"
如果您使用“wrapper”工具启动服务器,则需要将以下行添加到 $EXIST_HOME/tools/wrapper/conf/wrapper.conf 中。
# make AWT load the fonts for SVG rendering inside of XSLFO wrapper.java.additional.6=-Djava.awt.headless=true
测试配置的简便方法之一是使用对 SVG 文件的内联引用。 您可以通过使用 fo:instream-foreign-object 元素来实现。 以下是一个示例。
<fo:block>
Test of inline SVG reference.
<fo:block>
<fo:instream-foreign-object content-width="7.5in" scaling="uniform">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="200" width="200">
<circle cx="100" cy="100" r="40" stroke="black" stroke-width="2" fill="blue"/>
</svg>
</fo:instream-foreign-object>
</fo:block>
content-width="7.5in"
scaling="uniform"
</fo:block>
请注意,这假设您已在 FOP 配置文件中配置了 <base> URL。
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="my-page">
<fo:region-body margin="0.5in"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="my-page">
<fo:flow flow-name="xsl-region-body">
<fo:block>Test of external SVG reference</fo:block>
<fo:block>
SVG Chart Test
<fo:external-graphic content-width="7.5in" scaling="uniform" src="url(chart.svg)"/>
content-width="7.5in"
scaling="uniform"
src="url(chart.svg)"
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
Latex 是一种非 XML 语言,用于排版包含数学公式的文档。 尽管语法非标,但 LaTeX 在许多数学和物理出版物中仍然很流行。 XSL-FO 包含一个扩展包,允许将 LaTeX 公式添加到 XSL-FO 文档中。 要使用该包,您必须将两个 jar 文件添加到 $EXIST_HOME/lib/extensions,重新启动 eXist,然后将适当的语法添加到您的 XSL-FO 文档中。
从该网站:http://forge.scilab.org/index.php/p/jlatexmath/downloads/
将以下文件复制到您的 $EXIST_HOME/lib/extensions 中。
- jlatexmath-1.0.3.jar
- jlatexmath-fop-1.0.3.jar
然后重启您的 eXist 服务器,以便加载 jar 文件。
然后将以下代码添加到您的 FO 中。
<fo:block>To pass, you should see a symbols for 2/4 = 1/2</fo:block>
<fo:block>
<fo:instream-foreign-object>
<latex xmlns="http://forge.scilab.org/p/jlatexmath">\frac{2}{4}=\frac{1}{2}</latex>
</fo:instream-foreign-object>
</fo:block>
请注意,XSL-FO 软件不会自动从配置文件中提取字体。 要强制将字体加载到 RAM 中,您需要将以下 auto-detect 元素添加到您的 fop 配置文件中。
<fop version="1.0">
<renderers>
<renderer mime="application/pdf">
<filterList>
<value>flate</value>
</filterList>
<fonts>
<auto-detect/>
</fonts>
</renderer>
</renderers>
</fop>
注意:此项尚未完成。
虽然 Latex 是表示公式的常用方法,但 Math Markup Language 也可以使用。
还有提示表明 http://jeuclid.sourceforge.net/ 可以使用。
这尚未经过测试。
有关如何将印刷质量的表格和图表添加到文档中,请参见 XSL-FO 表格 和 XSL-FO 图像。
当您遵循 trunk 时,有时 conf.xml 会重置为默认值,您需要在 conf.xml 中重新启用 xslfo 处理。 如果您错过此操作,则打印的错误信息类似于:“无法编译 xquery: err:xpst0017 调用未声明的函数: xslfo:render”。
Wolfgang 在 2014 年 1 月 6 日更新的步骤
- 将 license.xml 复制到 EXIST_HOME
- 将 x4u.jar、xep.jar 和 xt.jar 从 xep 复制到 EXIST_HOME/lib/user
- 编辑 conf.xml 以更改 XSL FO 驱动程序
<module uri="http://exist-db.org/xquery/xslfo" class="org.exist.xquery.modules.xslfo.XSLFOModule">
<parameter name="processorAdapter" value="org.exist.xquery.modules.xslfo.RenderXXepProcessorAdapter“/>
</module>
- 重启 eXist 以加载 jar 文件
- 将 xep.xml 从 xep 目录上传到 eXist 中的一个集合(例如 /db)
- 编辑 xep.xml 并更改 xep 字体的基目录。 它应该指向 xep 安装目录
<fonts xml:base="/Users/wolf/Source/renderx/fonts/" default-family="Arial“>
- 如果您使用的是 Mac,则可能需要更改文件中更靠后的 Arial 字体的目录
<font-group xml:base="file:/Library/Fonts/" label="Windows TrueType" embed="true" subset="true“>
- 在您的 XQuery 中调用 xep,如下所示。
let $id := request:get-parameter("id", ())
let $config := util:expand(doc("/db/xep.xml")/*)
let $pdf := xslfo:render(fo:main($id), "application/pdf", (), $config)
return
response:stream-binary($pdf, "media-type=application/pdf", $id || ".pdf“)
util:expand 技巧是必需的,因为 xslfo:render 期望 $config 的内存中 DOM 元素(这可能需要修复)。
注意:xep 将错误消息打印到标准输出,因此您通常看不到它们。 我通过启动器运行 eXist,所以我通过系统托盘菜单打开了“工具窗口”,然后点击“显示控制台消息”。
Kevin Brown (RenderX) 在 2017 年 5 月 17 日更新的步骤
与其将 RenderX 的安装拆分开,您可以编辑 RenderX 的主配置文件以解决您可能需要的其他所有文件,包括许可证文件。 因此,如果您按照以下步骤操作,安装 RenderX 会很容易。
- 将 RenderX 安装到您想要的任何目录中,或者如果 RenderX 已经安装,请记下该目录。 例如:Windows 上的安装可能位于“C:\Program Files\RenderX\XEP”中。
- 将“xep.jar”从 RenderX 的安装目录复制到 exist-db 的“/lib/user”目录中。 请注意,从上面的安装说明中可以看出,您不需要“x4u.jar”,只需要“xep.jar”。 如果您希望报告 XSL FO 的验证结果,那么您还需要“xt.jar”。 文件“xep.jar”和“xt.jar”位于 RenderX 安装目录的“/lib”目录中。 在上面的示例中,这将是“C:\Program Files\RenderX\XEP\lib”。
- 将“xep.xml”插入您的数据库。 “xep.xml”是 RenderX 配置文件,位于 RenderX 安装目录的根目录中。
- 编辑数据库中的“xep.xml”并更改“config”元素以添加一个“xml:base”属性,该属性指向磁盘上 RenderX 的安装目录。 此一步将允许找到所有其他文件(如“license.xml”、“rolemap.xml”和其他内容,如连字符和字体),因为它们都是相对于配置的“xml:base”的。 鉴于上面的示例安装,我将拥有以下根元素,它位于数据库中的“xep.xml”中。
<config xmlns="http://www.renderx.com/XEP/config" xml:base="file:/C:/Program Files/RenderX/XEP/">
- 可选地,如果您没有复制“xt.jar”或不希望进行验证,请将以下内容添加到“xep.xml”中。
<option name="VALIDATE" value="false"/>
- 如上所述编辑“conf.xml”,重启数据库并进行格式化。
这种安装实质上意味着 RenderX 的外部安装和与 exist-db 的安装本质上是相同的,共享相同的字体、连字符、许可证和其他文件。 当然,如果您更新了磁盘上的“xep.xml”,则需要在数据库中更新它。 或者,如果您更新了 RenderX,则需要将更新后的“xep.jar”复制到 exist-db 的安装目录中。
用户 Dmitriy 在创建针对没有源代码的系统的安装程序方面提供了帮助。Wolfgang 还针对 eXist 2.1 的 RenderX 说明提供了反馈。Josef Karthauser 帮助将 LaTeX 公式在 PDF 文档中正确渲染。
启用 FOP 模块的步骤应该列在 eXist 管理网站的某个地方,并从本维基教科书中删除。
RenderX 指令可以大幅简化。RenderX 配置文件 ("xep.xml") 的根元素采用 "xml:base" 作为参数。如果 RenderX 安装在某个目录中,例如在系统中的 "C:\Program Files\RenderX\XEP" 中,那么在导入 "xep.xml" 后,您只需在导入版本中编辑根元素,如下所示
<config xmlns="http://www.renderx.com/XEP/config" xml:base="file:/C:/Program Files/RenderX/XEP/">
"xep.xml" 中的所有其他引用将相对于此,因此您无需移动任何内容(例如将 license.xml 放置在 EXIST_HOME 或图像目录或字体或任何内容中)。
还要注意,"X4U.jar" 不应需要,因为它仅用于 GUI,而 "xt.jar" 除非在 "xep.xml" 中将 validate 设置为 true,否则不需要。只需要 "xep.jar"。