YAZONG 我的开源

java通过freemarker生成word文档

  ,
0 评论0 浏览

1、使用freemarker的原因是:由于POI和ITEXT在处理复杂word文档格式时欠缺,尤其在生成WORD文档时,(其他处理word的工具需要动态加载windows动态链接库,有些还要收费),并且需求要在WORD指定位置插入图片,所以在参考了POI和ITEXT官网处理WORD和网络上其他处理WORD的工具后,发现freemarker可以通过设定WORD模板,经过处理生成ftl文件后,可直接通过freemarker的工具jar包可以生成指定位置的WORD文件。(不过用模板生成有个别缺点,一是:插入WORD中的图片大小会按照模板中的图片大小自动进行放大或缩小,所以建议最好按照事先规定的格式来上传固定图片尺寸;二是:最终生成的WORD文档在打开时有时会出现提示文档错误,解决方案:模板用office2003编辑(比如WORD2010高版本保存为WORD2003即可,千万不要用WPS处理!),之后另存为选择2003WORD-XML,而不是直接保存为WORD-XML)。

#注意事项:JDK为64位的jdk1.7.0_45,windows系统为64位win10

2、生成最终WORD文档的基本流程为

执行WORD模板--XML--FTL--java工具类生成需要的WORD文档

#处理工具类见4、5

3、WORD模板,例如:(以下是一个WORD文档的模板test.doc,就一页,太大,分三个图截屏的,可自定义截图)

#注意事项:因为通过freemarker生成WORD的每一页数据是通过WORD模板填充进去的,所以WORD模板的最上方和最下方千万不要有多余的空行!

Screenshot20200105java通过freemarker生成word文档你可以选择不平凡51CTO博客1.png

4、把上述模板另存为2003-WORDXML文档,比如2003test.xml

#由于这里要生成多页,所以在生成的2003test.xml中需要修改以下地方:

4、1打开2003test.xml文档,会发现所有的图片生成了BASE64编码,需要先把BASE64编码全部删掉。

注意事项:

不要删除

<v:shape id="图片 3" o:spid="_x0000_i1027" type="#_x0000_t75" alt="3" style="width:184.15pt;height:44.3pt" o:gfxdata="12UDrfwYItvs0vXB5M1Dx9n60DjNqrixU8BcEBeZAAOdMdL7wSgZ03ZiRH1mlh2t8kTOPdQbTzdJ">

中o:gfxdata的内容,

只删除

<w:binData w:name="wordml://02000003.jpg" xml:space="preserve">

</w:binData>

的内容。

#用WORD2003和WORD2007模板生成XML有个别区别,WORD2007中没有<v:shape>标签,并且图片标签不是<w:binData>而是pkg:binaryDatapkg:binaryData需要单独循环处理,并不在body标签中,虽然WORD2007的这种方式最终生成word会打开报错,数据和图片都写进去了,但是在打开生成的WORD会报错(暂时没找到解决方案,希望解决的同仁看见能告知一下,十分感谢!推测是WORD2003标签<v:shape>中o:gfxdata的编码导致的),会导致用户体验不好,所以我取消了用WORD2007生成模板的方式。

#删除完之后可以使用XML格式化工具,先把XML文档格式化,以下操作过程按照格式化后的XML格式文件讲解。

格式化地址:http://tool.oschina.net/codeformat/xml。

4、2由于要生成多页,所以要加入list循环标签及对应值标签(以MAP形式获取)。

#以下只标注需要改动的地方

4、2、1加入list标签,直接搜索<w:tbl>和</w:tbl>即可,这一个XML按照我的例子只有一对。

#listdataMap是个list,这个list中放入了多个map,是dataMap。

Screenshot20200105java通过freemarker生成word文档你可以选择不平凡51CTO博客2.png

4、2、2加入list标签中的值标签,搜索模板中的Value1替换成mediaName,Test1替换成${dataMap.mediaName},Test2和其他的同理.

Screenshot20200105java通过freemarker生成word文档你可以选择不平凡51CTO博客3.png

4、2、3其中自定义的页码也要手工替换,发现用WORD自带的页码不好用(页脚插入的图片也是不好用),暂时自定义,如果发现能搞,那么我会更新此博客。

Screenshot20200105java通过freemarker生成word文档你可以选择不平凡51CTO博客4.png

4、2、4修改图片标签内容,也就是4、1对应的BASE64编码的相关标签,

修改原理同4、2、1--4、2、3。其他图片字段修改同理。
Screenshot20200105java通过freemarker生成word文档你可以选择不平凡51CTO博客5.png

4、3把上述XML文件保存之后另存为ftl文件,最好黏到java项目里,见4、4。

4、4项目格式为

Screenshot20200105java通过freemarker生成word文档你可以选择不平凡51CTO博客6.png

4、5DocumentHandler.java工具处理代码示例:

package org.mbox.test;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import sun.misc.BASE64Encoder;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

public class DocumentHandler {

	private Configuration configuration = null;
	//注意这货Configuration一定要在这里初始化,不要在其他方法中手工实例化,切记!
	public DocumentHandler() {
		configuration = new Configuration();
		configuration.setDefaultEncoding("UTF-8");
	}

	private static String getImageStr(String imgFile) {
		InputStream in = null;
		byte[] data = null;
		try {
			in = new FileInputStream(imgFile);
			data = new byte[in.available()];
			in.read(data);
			in.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		BASE64Encoder encoder = new BASE64Encoder();
		return encoder.encode(data);
	}
	public static void main(String[] args) throws UnsupportedEncodingException {

		Map<String, Object> map = new HashMap<String,Object>();
		List<Map<String, Object>> listdataMap = new ArrayList<Map<String, Object>>();
		for (int i = 0; i < 5; i++) {
			Map<String, Object> dataMap = new HashMap<String, Object>();
			dataMap.put("mediaMark", getImageStr("F:/2.jpg"));
			dataMap.put("fileUrl", getImageStr("F:/1.jpg"));
			dataMap.put("logoMark", getImageStr("F:/3.jpg"));
			dataMap.put("mediaName", "试卷"+(i+1));
			dataMap.put("issueTime", "试卷"+(i+1));
			dataMap.put("eassyUrl", "试卷"+(i+1));
			dataMap.put("webUrl", "试卷"+(i+1));
			dataMap.put("issueChannel", "试卷"+(i+1));
			dataMap.put("pageNum", (i+1));
			listdataMap.add(dataMap);
		}
		map.put("listdataMap", listdataMap);

		DocumentHandler mdoc = new DocumentHandler();
		mdoc.createDoc(map, "F:/outFile2.doc");

	}
	
	public void createDoc(Map<String, Object> listdataMap,String fileName) throws UnsupportedEncodingException {
		//设置模本装置方法和路径,FreeMarker支持多种模板装载方法。可以从servlet,classpath,数据库装载,
		configuration.setClassForTemplateLoading(this.getClass(), "/com/jiayou/cps");
		Template t=null;
		try {
			//test.ftl为要装载的模板
			t = configuration.getTemplate("test.ftl","UTF-8");//不要缺这里的编码格式
		} catch (IOException e) {
			e.printStackTrace();
		}
		//输出文档路径及名称
		File outFile = new File(fileName);
		Writer out = null;
		try {
			//这个地方对流的编码不可或缺,使用main()单独调用时,应该可以,但是如果是web请求导出时导出后word文档就会打不开,并且包XML文件错误。主要是编码格式不正确,无法解析。
			out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),"UTF-8"));
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		}

		try {
			out.flush();
			t.process(listdataMap, out);
			out.close();
		} catch (TemplateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
}

#注意事项:可以发现在FTL文件中的循环标签用的是<#list listdataMap as dataMap>,意思是循环了listdataMap中的N个dataMap,在上述处理类中,listdataMap一定要放在MAP中。


标题:java通过freemarker生成word文档
作者:yazong
地址:https://blog.llyweb.com/articles/2016/08/26/1578158007607.html