XSL-FO 教程(从零到能上手实战)
XSL-FO(XSL Formatting Objects)是W3C标准,用于将XML数据精确排版成PDF(最常见的是生成PDF)。它通常和XSLT一起使用:XSLT把原始XML转换成FO文件,然后FO渲染引擎(如Apache FOP、RenderX、Antenna House)把FO转成PDF。
当前(2025年)虽然有更现代的方案(CSS Paged Media + HTML),但银行、保险、出版、政府报表等领域仍然大量使用XSL-FO,因为它对排版控制极度精确、完全可编程、可重复。
1. 基本概念和流程
原始XML → XSLT → 中间FO文件 (.fo) → FO处理器 → PDF(或其它格式)
常见FO处理器:
- Apache FOP(免费,开源,最流行)
- RenderX XEP(商业,速度快,支持好)
- Antenna House Formatter(商业,功能最强,支持CSS+FO混合)
- PrinceXML(商业,虽然不是FO,但支持类似功能)
2. 第一个最简单的XSL-FO例子
创建一个能直接生成PDF的完整FO文件(hello.fo):
<?xml version="1.0" encoding="UTF-8"?>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<!-- 版面布局大师 -->
<fo:layout-master-set>
<fo:simple-page-master master-name="A4" page-width="210mm" page-height="297mm"
margin="20mm">
<fo:region-body margin-top="20mm" margin-bottom="20mm"/>
<fo:region-before extent="15mm"/> <!-- 页眉 -->
<fo:region-after extent="15mm"/> <!-- 页脚 -->
</fo:simple-page-master>
</fo:layout-master-set>
<!-- 页面序列 -->
<fo:page-sequence master-reference="A4">
<!-- 页眉 -->
<fo:static-content flow-name="xsl-region-before">
<fo:block text-align="center" font-size="10pt">
我的第一个XSL-FO PDF
</fo:block>
</fo:static-content>
<!-- 页脚
<fo:static-content flow-name="xsl-region-after">
<fo:block text-align="center" font-size="10pt">
第 <fo:page-number/> 页
</fo:block>
</fo:static-content>
<!-- 正文内容 -->
<fo:flow flow-name="xsl-region-body">
<fo:block font-size="18pt" font-weight="bold" space-after="12pt" text-align="center">
Hello, XSL-FO 世界!
</fo:block>
<fo:block font-size="12pt" line-height="1.5" space-after="8pt">
这是一段普通段落文字。XSL-FO 可以精确控制每一毫米的间距、字体、颜色、分页等。
</fo:block>
<!-- 列表 -->
<fo:list-block provisional-distance-between-starts="8mm" space-after="10pt">
<fo:list-item>
<fo:list-item-label end-indent="label-end()"><fo:block>•</fo:block></fo:list-item-label>
<fo:list-item-body start-indent="body-start()"><fo:block>第一项内容</fo:block></fo:list-item-body>
</fo:list-item>
<fo:list-item>
<fo:list-item-label end-indent="label-end()"><fo:block>•</fo:block></fo:list-item-label>
<fo:list-item-body start-indent="body-start()"><fo:block>第二项内容</fo:block></fo:list-item-body>
</fo:list-item>
</fo:flow>
</fo:page-sequence>
</fo:root>
用Apache FOP生成PDF(命令行):
fop -fo hello.fo -pdf hello.pdf
就生成了你的第一份PDF!
3. 核心概念速览
| 概念 | 作用 | 常用元素 |
|---|---|---|
| fo:root | 文档根元素 | |
| fo:layout-master-set | 定义页面模板 | fo:simple-page-master |
| fo:page-sequence | 一组使用同一模板的页面 | master-reference |
| fo:flow | 流动内容(正文) | flow-name=”xsl-region-body” |
| fo:static-content | 固定内容(页眉页脚) | flow-name=”xsl-region-before/after” |
| fo:block | 块级元素(相当于div) | |
| fo:inline | 行内元素 | |
| fo:table | 表格 | fo:table, fo:table-body |
| fo:list-block | 列表 | |
| fo:external-graphic | 图片 | src=”url(‘xxx.jpg’)” |
4. 常用XSLT模板(XML → FO)
实际项目中很少手写FO,而是用XSLT把业务XML转成FO。
data.xml:
<?xml version="1.0" encoding="UTF-8"?>
<invoice>
<number>2025001</number>
<date>2025-11-29</date>
<customer>张三科技有限公司</customer>
<items>
<item name="笔记本电脑" qty="2" price="8999"/>
<item name="显示器" qty="1" price="1999"/>
</items>
<total>19997</total>
</invoice>
transform.xsl(核心片段):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/">
<fo:root>
<fo:layout-master-set>
<fo:simple-page-master master-name="A4" page-height="297mm" page-width="210mm" margin="20mm">
<fo:region-body/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="A4">
<fo:flow flow-name="xsl-region-body">
<fo:block font-size="20pt" font-weight="bold" space-after="20pt">
发票 <xsl:value-of select="/invoice/number"/>
</fo:block>
<fo:block font-size="12pt">
日期: <xsl:value-of select="/invoice/date"/>
</fo:block>
<fo:block font-size="12pt" space-after="15pt">
客户: <xsl:value-of select="/invoice/customer"/>
</fo:block>
<!-- 表格 -->
<fo:table table-layout="fixed" width="100%" border="1pt solid black">
<fo:table-column column-width="40%"/>
<fo:table-column column-width="20%"/>
<fo:table-column column-width="20%"/>
<fo:table-column column-width="20%"/>
<fo:table-header>
<fo:table-row background-color="#eeeeee" font-weight="bold">
<fo:table-cell padding="4pt"><fo:block>商品名称</fo:block></fo:table-cell>
<fo:table-cell padding="4pt"><fo:block>数量</fo:block></fo:table-cell>
<fo:table-cell padding="4pt"><fo:block>单价</fo:block></fo:table-cell>
<fo:table-cell padding="4pt"><fo:block>小计</fo:block></fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<xsl:for-each select="/invoice/items/item">
<fo:table-row>
<fo:table-cell padding="4pt"><fo:block><xsl:value-of select="@name"/></fo:block></fo:table-cell>
<fo:table-cell padding="4pt" text-align="right"><fo:block><xsl:value-of select="@qty"/></fo:block></fo:table-cell>
<fo:table-cell padding="4pt" text-align="right"><fo:block><xsl:value-of select="@price"/></fo:block></fo:table-cell>
<fo:table-cell padding="4pt" text-align="right">
<fo:block><xsl:value-of select="@qty * @price"/></fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
<fo:block text-align="right" font-size="14pt" margin-top="20pt">
合计:¥<xsl:value-of select="/invoice/total"/>
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
执行转换:
# 先用XSLT转成FO
xsltproc transform.xsl data.xml > invoice.fo
# 再用FOP生成PDF
fop -fo invoice.fo -pdf invoice.pdf
5. 实战常见需求技巧
| 需求 | 关键属性/元素 |
|---|---|
| 强制分页 | |
| 防止某块内部分页 | keep-together.within-page=”always” |
| 表格跨页时重复表头 | fo:table-header |
| 设置页眉页脚 | fo:static-content + xsl-region-before/after |
| 中文支持(嵌入字体) | 或 FOP配置 |
| 条形码/二维码 | fo:instream-foreign-object 或 Barcode4J 扩展 |
| 水印 | fo:block-container absolute-position=”absolute” |
6. 推荐学习资源(2025最新)
- W3C官方规范(最权威)
https://www.w3.org/TR/xsl11/ - Apache FOP 官方文档
https://xmlgraphics.apache.org/fop/ - 《XSL-FO》 Dave Pawson(经典,虽然老但核心不变)
- Antenna House 的 XSL-FO 样例库(非常全)
https://www.antenna.co.jp/AHF/help/en/ahf-sample.html - GitHub 上搜索 “xsl-fo invoice” “xsl-fo report” 有大量真实项目模板
如果你有具体的场景(发票、报表、图书排版、条码、复杂表格等),可以告诉我,我可以直接给你对应模板。祝你玩转XSL-FO!