使用word模板生成word文档的各类方案
生成word的各种方案
项目要求可以从一个word文档里,定好word的模板,然后后台读取数据后生成pdf并提供下载。本文章着重讲word模板到生成word文档的部分,由于自己也是刚接触这个内容,欢迎纠错,并且网上有好几种方式,在这里我都会讲个人的观点。
当前项目要求:
1、word模板是可变的,即更换模板,尽量不改变代码。
2、保留word模板本身样式,即本身字体颜色图片大小等样式保留。
3、跨平台,毕竟项目是要部署在linux上的。
4、可以实现超链接(网址url或者)
一、由word转xml编写key后另存ftl格式,后台代码用map进行填充生成word文档
1、优点:
保证稳定性,这种方式一定完成效果。保留字体样式等效果。
2、缺点:
①过程复杂,并且word转xml会导致部分文字被拆分多个标签,需要先做xml解析合并相同的xml标签后才能进行填充。即需要多做一个xml解析的功能。
②不可动态扩展表格,我的项目想根据传过来的数据进行动态拓展表格,其实也不是不可以,只需要在xml解析部分中将单元格部分的标签复制就好了,但是这明显会增加工作量。
③版本兼容问题,2003(.doc)和2007(.docx)版本,在这个方式下完全不同,需要做不同版本的写法,或者要求版本不变。
备注:ftl模板完成一次后即可反复利用,所以如果没有更换模板的需求下,xml合并部分和编写key部分可以人工完成,这样就会省下许多工作量。
二、直接利用poi等工具读取word文档自己定义的key替换相应的值
1、优点:
工作量小
2、缺点:
①不太严谨,毕竟key是自己在文本定义的,本质就是文本替换,只是做了特殊标记而已,难免可能出现不是用于标记的但是却被替换的情况。
②替换图片和超链接困难(具体实施情况本人并没有细探究)
word另存xml进行后续处理
比较多人使用的方式是word另存xml,然后在xml里做key的标记,另存成freemarker的.ftl格式文件,在代码里用map的方式根据key的不同去赋值。这里会有个问题,因为word文档是分.doc和.docx格式,即2003和2007版本。
2003版本word(.doc)的xml处理并生成word
测试例子:
如图所示,本人需要替换的是两行文本以及一个表格,一张图片,一个指向网站的超链接,一个指向一个excel文件的超链接。
另存2003版本的xml格式,进行编辑。
可见2003版本xml的结构,其中fonts标签记录了字体库,styles记录了样式,在body标签里记录了文本主体信息。
可见${name1}仍然会被拆分成2个<w:r>标签,虽然本人是先在文档里写key再生成xml去查看,实际上先写成实际文本信息再在此处替换成key,一样会被拆分,尤其是英文和中文和符号三类相连的部分会被断开。此时需要合并同样样式的<w:t>标签,使key完整。如果有更换模板的需求,就得写一个xml解析的功能,满足合并<w:t>标签的功能。
关于图片处理:
<w:binData>记录了图片的base64信息,因此代码中实际需要替换的是这串base64信息。
<w:binData w:name=“wordml://02000001.jpg” xml:space=“preserve”>${data}</w:binData>
将base64信息用key去标记。
关于超链接处理:
可见,实际的url是在w:dest=“http://www.baidu.com"或w:dest=“C:\Users\lenovo\Desktop\test.xlsx"里。将引号里的url标记成key即可。即<w:hlink w:dest=”${url}”>注意保留引号。
完成后另存ftl后缀名即可。
public Map<String,Object> completeData(Map<String,Object> map)throws Exception{
try{
map.put("name1","666");
map.put("name2","777");
map.put("title1","666title1");
map.put("title2","66title2");
map.put("title3","66title3");
map.put("pro1","999");
map.put("pro2","1000");
map.put("pro3","1001");
File file=new File("C:\\Users\\lenovo\\Desktop\\pdftest\\firstpdf\\src\\main\\resources\\static\\test.jpg");
String imageBase64 = encodeBase64File(file);
map.put("data",imageBase64);
map.put("github","http://www.github.com");
map.put("excel","test.xlsx");
}catch(Exception e){
logger.error("ProjectController.completeData error",e);
throw e;
}
return map;
}
/**
* 将文件转化为Base64编码
* @param file
* @return
* @throws Exception
*/
public static String encodeBase64File(File file) throws Exception {
FileInputStream inputFile = new FileInputStream(file);
byte[] buffer = new byte[(int) file.length()];
inputFile.read(buffer);
inputFile.close();
return new BASE64Encoder().encode(buffer);
}
/**
* 修改文件模板
* @param fileName
* @param map
* @return
* @throws Exception
*/
public File writeFileTemplate(String fileName,Map<String,Object> map) throws Exception{
try{
Configuration configuration=new Configuration();
configuration.setDefaultEncoding("utf-8");
String tempPath="src/main/resources/";
String templatePath="src/main/resources/templates/";
configuration.setDirectoryForTemplateLoading(new File(templatePath));
Template template=configuration.getTemplate("model.ftl");
String outPath=tempPath+fileName;
File outFile=new File(outPath);
Writer writer=null;
try{
writer=new OutputStreamWriter(new FileOutputStream(outFile), "utf-8");
template.process(map,writer);
}catch(Exception e){
throw e;
}finally{
writer.close();
}
return outFile;
}catch(Exception e){
throw e;
}
核心思路就是读取ftl文件,读取map的值填入模板中生成word。具体完整代码网上很多,这里不再赘述。
2007版本word(.docx)的xml处理
之所以分版本说明,是因为两个文件格式的结构不同。
上述xml的方式生成的文档只能是.doc,将生成的文件名改成.docx将打不开文件。
原因:将.dox文件后缀名改为.zip可以发现,本质是一个压缩包
打开word文件夹
可以发现文档的主要信息都在这里,并且页眉页脚字体样式什么的都分了不同的xml进行存储。
图片则存储在了media文件夹中,不需要base64码了。
这就是为什么网上主流的方式都是.doc,因为.docx的格式过于复杂。虽然document.xml的更改方式与.doc版本的方式是一样的。
但是代码功能复杂了,即还需要一个压缩与解压的功能,因此本人并没有细究下去,而且也没找到超链接的修改位置,可能方式变了我没找到,于是直接弃用。
poi直接操作word对象进行文本替换生成word
来自简书的一个文档:POI操作word模板并生成新的word.docx
如有侵权,请联系。
核心思路网上大同小异,都是利用poi的功能去找到自己要替换的位置然后进行替换,本质就是文本替换。缺点就是代码会十分不灵活,因为对于表格部分处理会变得十分复杂且不够灵活动态。优点就是工作量比较小。
备注:.doc和.docx的方式同样不同,但是思路一样
使用poi-tl库对模板进行动态生成word文档(本人的最终方案)
poi-tl是国人对poi库的再次封装的一个库,特点是针对模板化word文档改良的,在github上已经有1k的star了
此处奉上相关链接
poi-tl的官方地址
poi-tl的github地址
由作者github提供的示例图可以看出,poi-tl可以十分灵活的在多个地方用规范的key进行标记,包括图片、表格、文档、文本等信息都可以以一个key标记。
此处以官方提供的示例demo为例。
对比可以看出,文章、带编号的段落、表格、图片、超链接,都可以以key的方式去标记。不同类型的用不同的标记,代码也是十分简洁,只需要读取模板文档然后输入一个map就完成了。实际上不仅仅是map,有不同属性的一个完整的对象也是可以作为输入参数的。此处不附代码了,毕竟已经贴了几个作者的原图,此处仅以宣传作用,若有侵权,请联系。详细代码作者github上有完整的,或者从官网里看。
优点:模板可以满足所有要求,即超链接、保留样式、动态拓展表格、跨平台(因为基于poi)、代码工作量小、制作模板工作量小,支持.docx文件
缺点:表格样式需要代码去写,不能用word模板里的表格样式去动态拓展(或者本人暂时没研究出来)、由于几乎全是key,所以不方便给制作模板的人有预览的效果,即几千字的文章,哪怕有分段等操作,在poi-tl中,也是一个key就完成了,对于操作的人来说工作量小,但是不方便想象实际效果。
综合方案评价
这里借助poi-tl的官方的一个表格图
上述xml的方式即是freemarker的方式,简书作者的则是poi的方式,我最终采取的是poi-tl的方式,上述外用的链接如有侵权请联系我。