最近因为项目需要将对象打印特定模式的PDF,经大佬建议,选择了使用FOP,较之iText,灵活性更强,对代码依赖更少。
下面简要说明一下如何使用及demo。
1. 需求描述
根据现有一个pdf模板,将值填入pdf中导出。其中,值从Java一个List<Map<String, Object>>获取,每个Map展示在一页pdf中,整个List生成为一个pdf文件。
2. 需求分析
1)生成pdf模板
2)将每个Map循环填入pdf中,使得每个Map占用单独一页
3. 技术探测
1)进入fop官网 https://xmlgraphics.apache.org/fop/ 学习,最快捷的方法就是将官网提供的Examples下载到本地运行,这样可以有更直观的感受,可以帮助快速get关键点。
2)fop是一个基于xsl(制作pdf模板)来生成pdf文件的。可以插入图片,将xml,Java对象,SVG等转换为pdf格式的文件。
3)由于xsl确定样式,所以Java代码里只需要提供要展示的值即可,将样式与代码解耦。只要值确定,无论后续pdf样式如何更改,Java代码都无需再动,便于维护。而正因为样式由xsl决定,所以FOP的可定制性非常强,推荐使用。
4. 实践
经过分析,发现fop在将Java对象转换为pdf时,先将对象转换为了xml格式,所以本需求的完成主要聚集在以下两点:
1) xsl学习----官网提供的examples代码里有相对简单的fop.xml文件,可以作为入门。更多的知识点需要学习fo:xsl以及xsl语法,可参考 http://www.w3school.com.cn/xslfo/index.asp、http://www.w3school.com.cn/xsl/xsl_languages.asp;模板制作好后,可参考https://xmlgraphics.apache.org/fop/quickstartguide.html下方,使用命令行 fop -xml XXX.xml -xsl XXX.xsl -pdf xxx.pdf 来测试模板是否为目标模板,此时不需要Java代码参与,更省时省力:)
2) Java对象转xml (采用XStream)
由于需要转换的为List<Map<String, Object>>对象,而fop模板文件中会引用map中的key,而简单的使用XStream无法满足需求(只会将key和value的值同时打印出来,而xml标签里是key/value的详细类型),需要使用XStream提供的Convertor类(从XStream的jar包里取出了MapConvertor类到本地修改),进行简要的转换,使得生成的xml标签为Map的key,而值是Map的value。
关键代码下所示:
public boolean canConvert(Class type)
{
if (this.type != null) {
return type.equals(this.type);
}
return (type.equals(HashMap.class)) ||
(type.equals(Hashtable.class)) ||
(type.getName().equals("java.util.LinkedHashMap")) ||
(type.getName().equals("java.util.concurrent.ConcurrentHashMap")) ||
(type.getName().equals("sun.font.AttributeMap") ||
(type.equals(HashedMap.class)) );
}
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context)
{
Map map = (Map)source;
for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();)
{
Map.Entry entry = (Map.Entry)iterator.next();
ExtendedHierarchicalStreamWriterHelper.startNode(writer, entry.getKey().toString(), entry.getClass());
writeItem(entry.getValue(), context, writer);
writer.endNode();
}
}
3)关键点
3.1)模板---每个Map成为一页,这里使用xsl:for-each
<fo:page-sequence master-reference="A4-portrait">
<fo:flow flow-name="xsl-region-body">
<fo:block>
<xsl:for-each select="List/map">
...
<!-----加上if判断,使得最后一页不生成空白页-->
<xsl:if test="last()>position()">
<fo:block break-before="page"/>
</xsl:if>
</xsl:for-each>
</fo:block>
</fo:flow>
</fo:page-sequence>
3.2)模板---由于要生成不定行的表格(表头一定,但表行数视具体数据而定),使用
<fo:table-body>
<xsl:apply-templates select="XXX"/>
...
</fo:table-body>
<xsl:template match="XXX">
...
</xsl:template>
注意:这里的"XXX"指的是xml中的xpath(不熟悉的需要学习一下),根据这个path,才能找到惟一的标签,这样才能取出标签中的值
已有 0 人发表留言,猛击->> 这里<<-参与讨论
ITeye推荐