1.前言
参考文章:
【1】.
Java根据word模板生成word文件 XDocReport +FreeMarker,该技术组合既简单又高效可实现word模板的编辑,docx和doc均可处理
【2】.
java 实现根据word模板生成word文件 word转pdf 生产wode文档用的是freemarker方式,网上推荐的方式有 itexpdf(失真很厉害用不成);poi包方式(各种包的报错,Poi引用的一些类包里没有,将其他包引入后这些包又有引用类没有,解决不完,用不成); docx4j(比上面两种好一些,但是也失真厉害达不到生产使用的标准,生成pdf的时间也长)aspose-words(pj 版包,效果不错)
【3】.
java 根据word模板生成word文件 Java可以使用Apache POI库来生成Word文件,并且也可以使用freemarker等模板引擎来实现根据Word模板生成Word文件的功能。CTRL+f9生成域——》鼠标右键编辑域——》选择邮件合并—–》在域代码后面加上英文${跟代码内的一致}。
【4】.
Java Apache POI根据 word 模板 template 生成新的 word 文档 要操作 Office Word 文档,就需要先知道是如何进行操作的。Office2003以上,Word可以以XML文本格式存储,这样可以使用外部程序创建Word文件,而不需要使用Word的对象。也能够自由的打开分析Word文件,或者发布到自己的Web页面,或者其他更多应用。
2.编辑模版
首先需要准备模版,在 wps 中,依次点击 插入”—“文档部件”—“域”,选择 “邮件合并”,即可。刚开始我插入域的时候,遇到了 “错误!未指定书签”,这玩意用 wps,用 wrod编辑都是一样的,问题还不好找,我修改了域名称,结果无法确定了。
后来我知道了,需要直接在 域代码后面添上变量的内容,比如添加一个 name 变量,需要填写: MERGEFIELD ${name}
3.引入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.core</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.document</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.template</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.document.docx</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.23</version> </dependency>
|
3.编写测试代码
编写测试代码,填入相关参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public void exportXcbg(HttpServletResponse response){
InputStream ins = null; ByteArrayOutputStream out = null; try { String fileName="xxx.docx"; ins = new FileInputStream(resources+File.separator+fileName); IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker); IContext context = report.createContext(); context.put("name", "李梅");
out=new ByteArrayOutputStream(); report.process(context, out); response.setContentType("application/octet-stream"); response.addHeader("Content-Disposition", "attachment;fileName=" + new String(fileName.getBytes("UTF-8"),"iso-8859-1")); response.setContentLength(out.size());
ServletOutputStream servletOutputStream=response.getOutputStream(); out.writeTo(servletOutputStream);
servletOutputStream.flush(); servletOutputStream.close(); } catch (Exception e) { log.error("生成word发生异常", e); } finally { try { if (ins != null){ ins.close(); } if (out != null){ out.close(); } } catch (IOException e) { log.error("文件流关闭失败", e); } } }
|
4.插入图片
(1) 首先编辑模版,在模版中插入一张占位图,然后选中图片,选择 “插入” -> 选择 “书签”,插入一张书签,不需要加入 $ 符号。
(2) 编写代码
创建图片元数据,我加入的是xchdztt这个书签,然后将相关的字段进行填充。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| String fileName="xxx.docx";
ins = new FileInputStream(resources+File.separator+fileName);
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker);
IContext context = report.createContext();
FieldsMetadata metadata = report.createFieldsMetadata(); InputStream fins = new FileInputStream(new File(resources+File.separator+"word_1.png")); metadata.addFieldAsImage("xchdztt"); context.put("xchdztt", new ByteArrayImageProvider(fins)); report.setFieldsMetadata(metadata);
|
5.列表循环
(1) 编辑模版,插入域,编辑列表头: MERGEFIELD [#list lonlist as item]
编辑列表尾:MERGEFIELD [/#list]
中间就用:MERGEFIELD ${item.lat},调用列表中的变量
(2) 编写代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| String fileName="xxx.docx";
ins = new FileInputStream(resources+File.separator+fileName);
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker);
IContext context = report.createContext();
List<JSONObject> lonlist=new ArrayList<>(); JSONObject temp=new JSONObject(); temp.put("lon",123.123); temp.put("lat",30.12); lonlist.add(temp);
temp=new JSONObject(); temp.put("lon",112.33); temp.put("lat",31.12); lonlist.add(temp);
temp=new JSONObject(); temp.put("lon",123.083); temp.put("lat",33.12); lonlist.add(temp);
context.put("lonlist",lonlist);
|
6.循环图片
(1) 修改模版
在需要添加列表循环的地方,增加了一个图片的书签,我命名为 listimg,和单独添加图片书签是一样的,列表的域代码 :[#list lonlist as item]。
(2) 编写代码
这里和普通的列表有两个区别,一个就是增加了 ByteArrayImageProvider 属性,另外一个就是使用了原数据图片转换: metadata.addFieldAsImage(“listimg”,”item.listimg”),
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| List<JSONObject> lonlist=new ArrayList<>(); JSONObject temp=new JSONObject(); temp.put("lon",123.123); temp.put("lat",30.12); InputStream fins1 = new FileInputStream(new File(resources+File.separator+"word_1.png")); temp.put("listimg",new ByteArrayImageProvider(fins1)); lonlist.add(temp);
temp=new JSONObject(); temp.put("lon",112.33); temp.put("lat",31.12); InputStream fins2 = new FileInputStream(new File(resources+File.separator+"word_2.png")); temp.put("listimg",new ByteArrayImageProvider(fins2));
lonlist.add(temp);
temp=new JSONObject(); temp.put("lon",123.083); temp.put("lat",33.12); InputStream fins3 = new FileInputStream(new File(resources+File.separator+"word_3.png")); temp.put("listimg",new ByteArrayImageProvider(fins3)); lonlist.add(temp);
context.put("lonlist",lonlist);
metadata.addFieldAsImage("listimg","item.listimg"); report.setFieldsMetadata(metadata);
out=new ByteArrayOutputStream(); report.process(context, out);
|
7.嵌套循环
除了一层循环之外,就是在循环内嵌套一层循环。
1
| «[#list pinlist as pinitem]»«[#list pinlist as pinitem]»ddd«[/#list]»东经:«${pinitem.lon}»;北纬:«${pinitem.lat}»。«[/#list]»
|
生成的域代码如下:
这里有一个比较难的地方,就是关于嵌套循环列表中有图片的问题。我尝试了很多的方法,弄了很久,始终找不到到底如何生成一个嵌套的循环图片。
8.嵌套循环图片
(1) 模版
修改word模版,创建一个循环嵌套的列表域,如下。这里的图片标签,一定要定位到图片上,然后再添加书签 xczp。 我吃亏就吃亏在这里,我尝试了很多方法,弄了很多次,最后都是无法生成word文档,甚至处理了好几天,让我非常的苦恼。
1 2 3 4 5
| MERGEFIELD [#list pinlist as pinitem] MERGEFIELD [#list pinitem.imglist as imgitem] 图片书签【xczp】 MERGEFIELD [/#list] MERGEFIELD [/#list]
|
(2) 代码
代码里面就是创建两个循环,一个是 pinlist 列表,一个是 pinitem.imglist 列表,其中关键的代码就是 metadata.addFieldAsImage(“xczp”,”imgitem.xczp”), 这里的 imgitem 要用内循环的变量,而不是外循环的其他的一些东西。我尝试过 pinitem.imglist.xczp,尝试过其他的东西,基本上也都是不行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| List<JSONObject> pinItemList=new ArrayList<>(); int dwNumber=4; int imgNumber=3; int pinCount=pinList.size();
for(int count=0;count<pinCount;count++){ Pin pin=pinList.get(count); JSONObject temp=new JSONObject(); temp.put("lon",pin.getLon()); temp.put("lat",pin.getLat()); temp.put("wtms",pin.getWtms()); temp.put("dwNumber",dwNumber); dwNumber+=1;
try {
String dwtp=pin.getDwtp(); if(!dwtp.isEmpty()){ InputStream photoImg=MinioUtil.getMinioFile(MinioUtil.getBucketName(),dwtp); temp.put("kjfb",new ByteArrayImageProvider(photoImg)); temp.put("imgNumber",imgNumber); imgNumber+=1; }
String xczp=pin.getXczp(); String[] xczpArray=xczp.split(","); int xczpCount=xczpArray.length; JSONArray imgList=new JSONArray(); for (int i=0;i<xczpCount;i++){ String xczpPath=xczpArray[i]; if(!xczpPath.isEmpty()){ JSONObject xczpTemp=new JSONObject(); InputStream photoImg=MinioUtil.getMinioFile(MinioUtil.getBucketName(),xczpPath); xczpTemp.put("xczp",new ByteArrayImageProvider(photoImg));
xczpTemp.put("number",imgList.size()+1); xczpTemp.put("imgnumber",imgNumber+imgList.size()+1); imgList.add(xczpTemp); } } temp.put("imglist",imgList); temp.put("imgNumber",imgNumber); imgNumber+=1; }catch (Exception e){ log.error("xczp error",e); } pinItemList.add(temp); }
metadata.addFieldAsImage("xczp","imgitem.xczp");
metadata.addFieldAsImage("kjfb","pinitem.kjfb"); report.setFieldsMetadata(metadata);
context.put("pinlist",pinItemList);
|
9.循环表格
我另外一个需求就是,根据列表中的内容,循环生成不同的表格。
(1)在表格的开头编辑域 @before-row[#list pinlist as pinitem]
(2)在表格的结尾编辑域 @after-row[/#list]
10.判断
IF 判断的写法,需要三个域,每一个的创建方式和上面相同 内容为 “[#if 1 == 1]” 文档内容 “ [#else]” 文档内容 “ [/#if]” , 注意要加中括号,两端最好在加上引号。
问题
(1) InputStream is not a zip.
这是我加载一个 doc 文档的时候出现的问题。
【解决方案】
将 doc 文档另存为 docx 文档。
(2) Encountered “[/#if]”, but at this place only this can be closed: “#list”.
参考文章:
【1】.[Encountered “[/#if]”, but at this place only this can be closed: “#list”.]