XSL-FO是W3C推出的用于定义文档排版格式的XML词汇表,属于XSL规范的一部分,主要作用是将XML结构数据转换为具有固定布局的输出文档,最常见的应用场景就是生成PDF文件。它通过对页面尺寸、边距、字体、段落间距等排版属性的精确配置,实现复杂的文档样式控制,适合生成发票、报表、合同等对格式要求严格的文档。

XSL-FO的核心组成
XSL-FO文档本身是一个符合XML规范的文档,核心由以下几个部分构成:
- 根元素<fo:root>:所有XSL-FO内容的最外层容器,需要声明对应的命名空间。
- 布局主控<fo:layout-master-set>:定义文档的页面模板,比如单页模板、奇偶页不同的模板等。
- 页面序列<fo:page-sequence>:引用布局主控中定义的模板,是实际内容输出的容器。
- 流<fo:flow>:放在页面序列内部,承载所有要输出的实际内容,比如文本、表格、图片等。
环境准备
要使用XSL-FO生成PDF,需要准备两个核心工具:
- 支持XSLT转换的工具,比如Java生态的Saxon,用于将XML和XSL-FO模板转换为格式化对象树。
- 支持XSL-FO渲染的PDF生成器,比如Apache FOP,它可以将XSL-FO文档渲染为PDF文件。
如果是Java项目,可以直接在Maven中引入Apache FOP的依赖:
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>fop</artifactId>
<version>2.9</version>
</dependency>
完整生成示例
步骤1:准备XML数据文件
首先创建一个简单的员工信息XML文件,作为生成PDF的数据源:
<?xml version="1.0" encoding="UTF-8"?>
<employees>
<employee>
<id>1001</id>
<name>张三</name>
<department>技术部</department>
<salary>15000</salary>
</employee>
<employee>
<id>1002</id>
<name>李四</name>
<department>产品部</department>
<salary>13000</salary>
</employee>
</employees>
步骤2:编写XSL-FO模板
创建一个XSL文件,同时包含XSLT转换规则和XSL-FO排版定义,将XML数据映射到PDF的排版结构中:
<?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">
<!-- 输出格式为XML -->
<xsl:output method="xml" indent="yes"/>
<!-- 匹配根节点,生成XSL-FO根元素 -->
<xsl:template match="/">
<fo:root>
<!-- 定义页面布局 -->
<fo:layout-master-set>
<fo:simple-page-master master-name="A4" page-width="210mm" page-height="297mm">
<fo:region-body margin="20mm"/>
</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" text-align="center" margin-bottom="15mm">
员工信息表
</fo:block>
<!-- 表格 -->
<fo:table border="1pt solid black" width="100%">
<fo:table-column column-width="25%"/>
<fo:table-column column-width="25%"/>
<fo:table-column column-width="25%"/>
<fo:table-column column-width="25%"/>
<!-- 表头 -->
<fo:table-header>
<fo:table-row background-color="#cccccc">
<fo:table-cell border="1pt solid black" padding="3mm">
<fo:block text-align="center">员工ID</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid black" padding="3mm">
<fo:block text-align="center">姓名</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid black" padding="3mm">
<fo:block text-align="center">部门</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid black" padding="3mm">
<fo:block text-align="center">薪资</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<!-- 表格内容,遍历employee节点 -->
<fo:table-body>
<xsl:for-each select="employees/employee">
<fo:table-row>
<fo:table-cell border="1pt solid black" padding="3mm">
<fo:block text-align="center">
<xsl:value-of select="id"/>
</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid black" padding="3mm">
<fo:block text-align="center">
<xsl:value-of select="name"/>
</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid black" padding="3mm">
<fo:block text-align="center">
<xsl:value-of select="department"/>
</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid black" padding="3mm">
<fo:block text-align="center">
<xsl:value-of select="salary"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
步骤3:Java代码实现PDF生成
使用Apache FOP的API,将XML和XSL模板转换为PDF文件:
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class XslFoPdfGenerator {
public static void main(String[] args) {
try {
// 初始化FOP工厂
FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
// 输出PDF的文件流
OutputStream out = new FileOutputStream("员工信息表.pdf");
// 创建FOP实例,指定输出格式为PDF
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, out);
// 加载XML数据源
Source xmlSource = new StreamSource(new File("employees.xml"));
// 加载XSL-FO模板
Source xslSource = new StreamSource(new File("employee_template.xsl"));
// 获取转换器
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer(xslSource);
// 设置转换结果到FOP的SAX结果
Result result = new SAXResult(fop.getDefaultHandler());
// 执行转换
transformer.transform(xmlSource, result);
// 关闭输出流
out.close();
System.out.println("PDF生成成功,文件路径:员工信息表.pdf");
} catch (Exception e) {
e.printStackTrace();
}
}
}
常见问题说明
在实际使用中可能会遇到以下问题:
- 中文显示乱码:需要在XSL-FO中配置支持中文的字体,比如宋体、黑体,同时需要将字体文件配置到FOP的字体目录中。
- 页面内容溢出:可以通过调整<fo:region-body>的边距,或者设置内容的字体大小、行间距来适配页面。
- 复杂样式实现:XSL-FO支持嵌套块、列表、图片等复杂元素,可以参考W3C的XSL-FO规范文档实现更多排版需求。
XSL-FO虽然语法相对繁琐,但是在需要精准控制文档排版的场景下,比直接操作PDF生成API更加灵活,适合批量生成格式统一的正式文档。