XSL-FO 教程

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最新)

  1. W3C官方规范(最权威)
    https://www.w3.org/TR/xsl11/
  2. Apache FOP 官方文档
    https://xmlgraphics.apache.org/fop/
  3. 《XSL-FO》 Dave Pawson(经典,虽然老但核心不变)
  4. Antenna House 的 XSL-FO 样例库(非常全)
    https://www.antenna.co.jp/AHF/help/en/ahf-sample.html
  5. GitHub 上搜索 “xsl-fo invoice” “xsl-fo report” 有大量真实项目模板

如果你有具体的场景(发票、报表、图书排版、条码、复杂表格等),可以告诉我,我可以直接给你对应模板。祝你玩转XSL-FO!

文章已创建 2838

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部