Apache FOP 将Java对象转换为pdf文件

最近因为项目需要将对象打印特定模式的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,才能找到惟一的标签,这样才能取出标签中的值
   

猜你喜欢

转载自april2017.iteye.com/blog/2407767