前言
在工作中有遇到需要在一个固定的word 模板中填充相关的数据,最终形成一份完成word 文档形式的报告。简单一些的可能只是对模板进行文字和数值的填充,稍微复杂一些的是模板中除了文本数值之外,还混合的有表格(可以循环遍历填充)、不规则表格(无序的,只能按照格子进行填充)、图片、条形图柱状图等。
本次主要介绍的插件是一个开源的操作word文档的组件,名为 POI-tl ,是一款基于Apache poi 打造的,官网地址为:https://deepoove.com/poi-tl/ 。
简述Apache Poi
在以往用Apache poi 进行操作word 文档时,我们通常这样操作:
- 根据模板存放的路径,获取到文件的输入流,然后创建XWPFDocument 对象。
- 获取word 文档中的所有段落,getParagraphs()。
- 循环遍历所有段落,获取到每个段落中的每一句话。getRuns()。
- 替换runs 中的文本text
以下代码为简单实例, poi 操作word 相对比较繁琐,word模板中的占位符需要自己进行定义,我习惯使用${}的形式,也有人直接使用{}。
String path = "";
File file = new File(path);
FileOutputStream os = null;
FileInputStream is = new FileInputStream(file);
XWPFDocument doc = new XWPFDocument(is);
// 替换文本
List<XWPFParagraph> paragraphs = doc.getParagraphs();
if (paragraphs != null && paragraphs.size() > 0){
for(XWPFParagraph paragraph:paragraphs){
List<XWPFRun> runs=paragraph.getRuns();
for(int i=0;i<runs.size();i++){
String text=runs.get(i).getText(0);
text=text.replace("${name}","创作不易,给个鼓励");
runs.get(i).setText(text,0);
}
}
}
//替换表格内容
List<XWPFTable> tables = doc.getTables();
for (int i = 0; i < tables.size(); i++){
XWPFTable xwpfTable=tables.get(i);
XWPFTableRow defRow=xwpfTable.getRow(3);
setTextColorAndFont(row,"张三"),0);
setTextColorAndFont(row,"北京",1);
setTextColorAndFont(row,"程序员",2);
setTextColorAndFont(row,"25",3);
setTextColorAndFont(row,"138336287882"),4);
}
/**
*row 行对象
*tbText 单元格要填充的内容
*cellIndex 单元格的下标
*/
public void setTextColorAndFont(XWPFTableRow row,String tbText, int cellIndex) {
tbText = checkValueIsNull(tbText);
XWPFTableCell cell = row.getCell(cellIndex);
List<XWPFParagraph> paragraphs = cell.getParagraphs();
XWPFParagraph xwpfParagraph = paragraphs.get(0);
XWPFRun xpfr = xwpfParagraph.createRun();
xpfr.setFontFamily("宋体");
xpfr.setFontSize(12);
xpfr.setText(tbText);
}
Poi-tl 上手实操
安装使用步骤可以详见API文档,这里不做过多的赘述。https://deepoove.com/poi-tl/#_%E5%89%8D%E6%8F%90
简单的一些操作直接看在线文档就可以搞定, 我在这里主要说一下复杂一些的表格生成,以及在开发过程遇到的坑。
- poi-tl 中, word 模板中的占位符需要设置为{{name}} 这种形式。
- 如果word 模板中内容很多,那么在一个类里面要写的代码就会很多,也没有办法进行协同编码,因为都在一个类中写代码的话,每次合并代码就会很麻烦,所以我们采用讲word模板分段来实现的方式。
-
AutoAwareZgybHandlerFactory.getTemplateHandler(param.getClass()); 通过获取TemplateHandler 的这种方式, 需要自己实现一个Handler,实现 WordContextChain<ReqeustParam>接口,泛型中的类需要和下面代码段中的请求入参实体类一致。
-
public void generatorWord(Requestparam param){ List<WordContextChain> contextChains = AutoAwareZgybHandlerFactory.getTemplateHandler(param.getClass()); Map<String, Object> context = new HashMap<>(); for (WordContextChain contextChain : contextChains) { contextChain.process(param, context); // 执行process 的时候,代码会进入自己定义的handler 中,如果有多个handerl, 执行的顺序是按照Order中的序号执行 } generateZgybReport(context); } public static generateZgybReport(Map<String, Object> context) { ExpResult er = new ExpResult(); String templateFilePath = "/download/expfile/ZG5C_ZGYB_TEMPLATE.docx"; String tempFileName = FileIeUtil.getRandomName()+".docx"; try (InputStream inputStream = this.getClass().getResourceAsStream(templateFilePath)) { byte[] bytes = replaceText(inputStream, context); String tempFilePath = FileIeUtil.getRandomName(); String filePath = FileIeUtil.getExportFileDirePath(tempFilePath, tempFileName); filePath = filePath.replaceAll("\\\\", "/"); FileUtils.writeByteArrayToFile(new File(filePath), bytes); er = readTempFileRenderTable(filePath, tempFilePath); } catch (IOException e) { log.error("生成税收征管质量月报失败!", e); } return er; } public static byte[] replaceText(InputStream wordTemplateInputStream, Map<String, Object> model) { LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy(); TableColorPolicy colorPolicy = new TableColorPolicy(); Configure configure = Configure.builder().useSpringEL(false).bind("tableList1",policy)// 此处,list 对应的key 是需要做绑定操作的,详细的可以看官方文档。 .bind("tableList8",policy).bind("tableList2",colorPolicy) .bind("tableList10",policy).build(); try (XWPFTemplate template = XWPFTemplate.compile(wordTemplateInputStream, configure)) { template.render(model); ByteArrayOutputStream out = new ByteArrayOutputStream(); template.write(out); byte[] bytes = out.toByteArray(); out.close(); return bytes; } catch (IOException e) { throw new Zg5cRunTimeException("模板word生成失败", e); } }
复杂word 表格操作
假如遇到如下情况,要根据年龄, 对年龄超过25的单元格设置填充色,或者其他格式操作等。
序号 | 身份信息 | ||||
姓名 | 年龄 | 地址 | 身份证 | 学历 | |
1 | 张三 | 17 | Xx | Xx | Xx |
2 | 李四 | 19 | Xx | Xx | Xx |
3 | 王五 | 28 | Xx | Xx | Xx |
4 | 赵六 | 21 | Xx | Xx | Xx |
5 | 点赞转发 | 22 | Xx | Xx | Xx |
6 | 感激不尽 | 34 | Xx | Xx | Xx |
此时,我们需要借助 DynamicTableRenderPolicy 类, 此时,我们需要自己实现一个处理类,继承DynamicTableRenderPolicy,重写render 方法,对表格进行颜色的或者其他形式的渲染
/**
* 渲染表格颜色
*/
@Slf4j
public class TableColorPolicy extends DynamicTableRenderPolicy {
@Override
public void render(XWPFTable xwpfTable, Object data) throws Exception {
if (data == null) {
return;
}
List<Map<String, String>> list = (List<Map<String, String>>) data;
xwpfTable.removeRow(3);
String sxbm = "";
int i = 0;
for (Map<String, String> map : list) {
XWPFTableRow xwpfTableRow = xwpfTable.insertNewTableRow(3 + i); //从哪行开始插入新的数据(除去表头后的第一行)
RowRenderData renderData = null;
TextRenderData textRenderData = new TextRenderData();
Style style = new Style();
style.setCharacterSpacing(0);
style.setVertAlign("CENTER");
textRenderData.setStyle(style);
for (int j = 0; j < 9; j++) xwpfTableRow.createCell();
renderData = Rows.of().rowAtleastHeight(0.16).textFontFamily("宋体").textFontSize(9).create()
.addCell(getHearderCellRenderData(map.get("SWJG_DM").getSwjgMc()))
.addCell(getCellRenderData(map.get("num"),"1"))
.addCell(getCellRenderData(map.get("name"),"1"))
.addCell(getCellRenderData(map.get("age"),)"-1")
.addCell(getCellRenderData(map.get("address"),"1"))
.addCell(getCellRenderData(map.get("sfzhm"),"1"))
.addCell(getCellRenderData(map.get("edu"),"1"));
TableRenderPolicy.Helper.renderRow(xwpfTable.getRow(3+i),renderData);
i++;
}
log.info("动态表格渲染,表格:"+sxbm+"--渲染完成----------------------------------");
}
/**
* 设置表格的样式
*/
private static CellRenderData getCellRenderData(String text,String color) {
if (StringUtils.isEmpty(text)) {
text = "";
}
String bgColor = Constants.COLOR_WHITE;
if (StringUtils.isNotEmpty(color) && color.equals("1")) {
bgColor = Constants.COLOR_BLUE;
} else if (StringUtils.isNotEmpty(color) && color.equals("-1")) {
bgColor = Constants.COLOR_RED;
}
return Cells.of().addParagraph(getParagraphRenderData(text)).bgColor(bgColor).create();
}
/**
* 表头样式
*
* @param text
* @return
*/
private static CellRenderData getHearderCellRenderData(String text) {
return Cells.of().addParagraph(getParagraphRenderData(text, true)).bgColor("DDEBF7").create();
}
private static ParagraphRenderData getParagraphRenderData(String text) {
return getParagraphRenderData(text, false);
}
private static ParagraphRenderData getParagraphRenderData(String text, boolean title) {
//设置表格字体大小
Style style = Style.builder().buildFontSize(9).build();
//设置表格行距
ParagraphStyle build = ParagraphStyle
.builder().withIndentLeftChars(0).withIndentRightChars(0).withAllowWordBreak(false).withSpacingAfter(0).withSpacingBefore(0).withSpacing(15).withSpacingRule(LineSpacingRule.EXACT).withIndentFirstLineChars(-2).build();
if (title) {
return Paragraphs.of(Texts.of(text).style(style).create()).paraStyle(build).center().create();
}
return Paragraphs.of(Texts.of(text).style(style).create()).paraStyle(build).right().create();
}
}
坑坑坑坑: 在这个设置颜色的功能中,遇到了这么个情况,原本表格是有边框线的,但是通过这个功能插入数据之后,边框线死活设置不上,然后我就用原始的POI 重新读取了一次生成后的word 文档, 对指定的talbe 进行边框线的设置。
public static ExpResult readTempFileRenderTable(String filePath,String tempFilePath){
String today = DateUtils.getYYYYMMDD(new Date());
String wordFilename = "税收征管质量月报_"+today+".docx";
ExpResult er = new ExpResult();
File file = new File(filePath);
XWPFDocument doc = null;
FileOutputStream os = null;
FileInputStream is = null;
try {
is = new FileInputStream(file);
doc = new XWPFDocument(is);
List<XWPFTable> tables = doc.getTables();
int tIndex = 0;
for (int i = 0; i < tables.size(); i++) {
if (i == 6 || i==9 || i==13 || i==21 || i==26) {
XWPFTable xwpfTable = tables.get(i);
String bolderType = "single";
CTTblBorders borders = xwpfTable.getCTTbl().getTblPr().addNewTblBorders();
CTBorder hBorder = borders.addNewInsideH();
hBorder.setVal(STBorder.Enum.forString(bolderType));
hBorder.setSz(new BigInteger("1")); // 线条大小
hBorder.setColor("000000"); // 设置颜色
CTBorder vBorder = borders.addNewInsideV();
vBorder.setVal(STBorder.Enum.forString(bolderType));
vBorder.setSz(new BigInteger("1"));
vBorder.setColor("000000");
CTBorder lBorder = borders.addNewLeft();
lBorder.setVal(STBorder.Enum.forString(bolderType));
lBorder.setSz(new BigInteger("3"));
lBorder.setColor("000000");
CTBorder rBorder = borders.addNewRight();
rBorder.setVal(STBorder.Enum.forString(bolderType));
rBorder.setSz(new BigInteger("3"));
rBorder.setColor("000000");
CTBorder tBorder = borders.addNewTop();
tBorder.setVal(STBorder.Enum.forString(bolderType));
tBorder.setSz(new BigInteger("3"));
tBorder.setColor("000000");
CTBorder bBorder = borders.addNewBottom();
bBorder.setVal(STBorder.Enum.forString(bolderType));
bBorder.setSz(new BigInteger("3"));
bBorder.setColor("000000");
}
}
filePath = FileIeUtil.getExportFileDirePath(tempFilePath, wordFilename);
filePath = filePath.replaceAll("\\\\", "/");
os = new FileOutputStream(filePath);
doc.write(os);
List<ExpResult.FilePathBean> filePathBeans = new ArrayList<>();
filePath = filePath.substring(FileIeUtil.getBASEPATH().length());
filePathBeans.add(new ExpResult.FilePathBean(wordFilename, filePath));
er.setFilePathList(filePathBeans);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
os.close();
doc.close();
is.close();
file.delete();
} catch (IOException e) {
e.printStackTrace();
}
}
return er;
}
以上便是近期对 POI-tl 使用的心得, 希望可以对你有所帮助。