
本文介绍一种基于 XSLT 多阶段转换的方案,利用 Saxon(XSLT 2.0+)对中间生成的 FO 文档进行二次处理,在保留原始排版结构的前提下,自动将 ^1、^2 等标记替换为对应页脚处的脚注内容,实现真正“按出现位置分页渲染”的脚注效果。
本文介绍一种基于 xslt 多阶段转换的方案,利用 saxon(xslt 2.0+)对中间生成的 fo 文档进行二次处理,在保留原始排版结构的前提下,自动将 `^1`、`^2` 等标记替换为对应页脚处的脚注内容,实现真正“按出现位置分页渲染”的脚注效果。
在 Apache FOP 流程中,原生不支持“跨页动态脚注定位”——FOP 本身是单次渲染引擎,无法回溯已生成 PDF 的页面布局来决定脚注落点。但通过 XSLT 二次预处理 FO 树,可在 FO 层面提前完成脚注绑定,从而规避渲染时的不确定性。关键在于:将脚注逻辑从“PDF 生成后”前移到“FO 文档生成中”,并借助 XSLT 2.0+ 的强类型节点处理能力实现精准注入。
✅ 可行性说明
-
Apache FOP 本身不支持运行时脚注定位,但支持标准 XSL-FO 的
元素; - 所谓“第二遍处理”,本质是 对第一遍输出的 FO XML 文档(非 PDF)再执行一次 XSLT 转换,而非操作二进制 PDF;
- 此方案完全可行,且无需修改 FOP 配置,只需替换 XSLT 引擎为 Saxon HE(推荐 v10+),并设计两级转换逻辑。
?️ 二次处理 XSLT 示例(XSLT 2.0+)
以下是一个完整、可复用的二级转换模板结构(保存为 postprocess-footnotes.xsl):
<?xml version="1.0" encoding="UTF-8"?><stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"><!-- 第一步:导入原始 FO 生成样式表 --><import href="first-pass.xsl"></import><!-- 定义脚注映射(实际应从外部 XML 加载,见下文) --><variable name="footnotes" as="map(xs:string, xs:string)"><map><map-entry key="'f1'" select="'SRC is a sales representative council'"></map-entry><map-entry key="'f2'" select="'PWF is the Product Workflow'"></map-entry><map-entry key="'f3'" select="'Board of Road development'"></map-entry></map></variable><!-- 主入口:先执行第一遍转换,获取 FO 结果树 --><template match="/"><variable name="fo-tree" as="node()*"><apply-imports></apply-imports></variable><!-- 进入第二遍:遍历 FO 节点,注入脚注 --><apply-templates select="$fo-tree" mode="inject-footnotes"></apply-templates></template><!-- 默认浅拷贝所有 FO 元素 --><mode name="inject-footnotes" on-no-match="shallow-copy"></mode><!-- 匹配含 ^N 标记的 fo:inline / fo:block 文本内容 --><template match="fo:inline/text() | fo:block/text()" mode="inject-footnotes"><analyze-string select="." regex="\^(\d+)"><matching-substring><variable name="num" select="regex-group(1)"></variable><variable name="key" select="concat('f', $num)"></variable><!-- 插入标准 XSL-FO 脚注结构 --><footnote><inline baseline-shift="super" font-size="8pt"><value-of select="$num"></value-of></inline><footnote-body><block font-size="8pt" line-height="1.2"><value-of select="$footnotes($key)"></value-of></block></footnote-body></footnote></matching-substring><non-matching-substring><value-of select="."></value-of></non-matching-substring></analyze-string></template></stylesheet>
? 注意:上述示例中脚注内容硬编码于
中,生产环境建议通过 或 doc() 函数加载外部脚注 XML(如你的 节点),例如: <variable name="footnotes-xml" select="doc('footnotes.xml')/tabs/footnotes"></variable><variable name="f1-text" select="$footnotes-xml/f1"></variable>
? 集成与注意事项
- 必须使用 Saxon HE(非 Xalan):XSLT 1.0 不支持 xsl:mode 和 xsl:analyze-string,且无法直接操作结果树节点;Saxon 提供稳定、高性能的 XSLT 2/3 支持,与 FOP 无缝集成(仅需将其 JAR 加入 classpath)。
-
FO 文档需保持合法结构:确保第一遍输出的 FO 是格式良好、命名空间正确的 XML;二次处理时禁止破坏
、 等根结构。 -
脚注位置由 FO 渲染引擎(FOP)自动计算:
元素天然支持“随内容所在页自动落于页脚”,无需手动计算页码——这正是 XSL-FO 的核心优势。 - 性能考量:二次 XSLT 处理增加毫秒级开销,但远低于重排版或 PDF 解析方案,适合千页以内文档。
✅ 总结
该方案不依赖 PDF 操作库,不修改 FOP 行为,纯粹通过 XSLT 2.0+ 的表达能力,在 FO 层完成语义增强。它将“标记→脚注”的映射逻辑前置到 FO 生成阶段,既保证了最终 PDF 的专业排版质量,又实现了灵活、可维护、符合标准的脚注机制。对于需要精确控制脚注位置与样式的出版级文档,这是目前最稳健、最符合 XSL-FO 设计哲学的实践路径。










