前言
- word生成是较为常见的功能,通常解决方案有freemarker、poi-tl等。
- 一般常规的word模板替换可采用第三方封装好的工具实现。
- 本文涉及动态复杂表格的插入,最好还是采用poi-tl。
- poi-tl版本差异性较大,如果被版本束缚,较难找到对应版本的copy代码,这时候可以参考其他版本再对照自己版本的jar包文件来对应实现。
poi 与 poi-tl 版本对应关系 http://deepoove.com/poi-tl
一、需求
根据模板生成对应的word文件。
- 包含文本替换
- 动态表格,涉及动态合并单元格
- word中包含分割线的图片
前期难点在于动态表格的绘制和锚点替换,后期难点在于poi-tl版本约束(由于某些原因,必须用1.9.1版本)。
二、方案
- poi 从头到尾画(即时这个word并不大,code硬编码起来也较为复杂,主要是比较难受,还要考虑各种样式,图片插入等)
- 拼html写入word(同上的硬编码较为麻烦,但样式结构可以word另存html后直接用,代码和word打开后的模式不友好,默认为web模式)
- 死磕poi-tl
三、实现
1.生成word 关键代码
//构建模板替换的map对象
Map<String, Object> dataMap = wordMaps(findDTO);
//获取模板信息等
String fileName = UUIDUtil.uuid() + ".docx";
String midPath = "uploadFile" + File.separator + "duty" + File.separator;
String filePath = fileUploadRoot + midPath;
String templatePath = this.getFullPathName("evaluate.docx");
//主要执行代码 指定替换内容形式为${xxxx},指定${table}标签的渲染规则为DetailTablePolicy
Configure builder = Configure.newBuilder().buildGramer("${", "}").bind("table", new DetailTablePolicy()).build();
XWPFTemplate template = XWPFTemplate.compile(templatePath, builder).render(dataMap);
try {
template.writeToFile(filePath + fileName);
template.close();
} catch (IOException e) {
e.printStackTrace();
}
2.自定义规则类
package com.xxx.gemp.evaluate.manage.rule.impl;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.MergeCellRule;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.data.TableRenderData;
import com.deepoove.poi.data.Tables;
import com.deepoove.poi.data.style.TableStyle;
import com.deepoove.poi.policy.RenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.policy.TextRenderPolicy;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.run.RunTemplate;
import com.deepoove.poi.xwpf.NiceXWPFDocument;
import lombok.SneakyThrows;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Mr.wanter
* @time 2022-9-7 0007
* @description
*/
public class DetailTablePolicy implements RenderPolicy {
@SneakyThrows
@Override
public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
NiceXWPFDocument doc = template.getXWPFDocument();
RunTemplate runTemplate = (RunTemplate) eleTemplate;
XWPFRun run = runTemplate.getRun();
if (null == data) {
return;
}
List<RowRenderData> rows = (List<RowRenderData>) data;
TableStyle.BorderStyle borderStyle = new TableStyle.BorderStyle();
borderStyle.setColor("A6A6A6");
borderStyle.setSize(4);
borderStyle.setType(XWPFTable.XWPFBorderType.SINGLE);
TableRenderData tableRenderData = Tables.ofA4MediumWidth()
.border(borderStyle).center()
.create();
rows.forEach(r -> {
tableRenderData.addRow(r);
});
MergeCellRule.MergeCellRuleBuilder mergeCellRuleBuilder = MergeCellRule.builder();
/**
* 设置表格合并规则
* 1.起始行 MergeCellRule.Grid.of(i, j) i: 行 j: 列
* 2.结束行 MergeCellRule.Grid.of(i, j) i: 行 j: 列
*/
//评估对象合并
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(1, 0), MergeCellRule.Grid.of(rows.size() - 1, 0));
List<String> subTargetContent = new ArrayList<>();
rows.forEach(r -> {
subTargetContent.add(r.getCells().get(1).getParagraphs().get(0).getContents().get(0).toString());
});
Set<String> set = subTargetContent.stream().collect(Collectors.toSet());
set.forEach(s -> {
//只有重复的合并,获取到需要合并项的列的行起始索引后进行合并
if (subTargetContent.stream().filter(st -> s.equals(st)).count() > 1) {
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(subTargetContent.indexOf(s), 1), MergeCellRule.Grid.of(subTargetContent.lastIndexOf(s), 1));//评估项合并
mergeCellRuleBuilder.map(MergeCellRule.Grid.of(subTargetContent.indexOf(s), 4), MergeCellRule.Grid.of(subTargetContent.lastIndexOf(s), 4));//权重合并
}
});
/**
* MergeCellRule支持多合并规则,会以Map的形式存入可以看一下源码
* !!! 一定要设置完规则后再调用 MergeCellRule的build方法进行构建
*/
tableRenderData.setMergeRule(mergeCellRuleBuilder.build());
TableRenderPolicy.Helper.renderTable(run, tableRenderData);
//todo 如果不加入下面代码生成的word会保留模板中的${table}
TextRenderPolicy.Helper.renderTextRun(run, "");
}
}
3.效果展示
总结
poi版本差异较大,很难拿来即用。
参考:https://blog.csdn.net/weixin_45051216/article/details/112471339解决了:
- table样式
- RowRenderData的构建方式。
- 合并单元格
表格渲染失败(不知道是不是版本问题),最终还是要仔细看jar包中都有哪些类和方法,根据文件名一点一点研究出来。渲染table的关键:TableRenderPolicy.Helper.renderTable(run, tableRenderData)
。