最近在做word模板导出的需求,本来意为是很简单,做起来才发现细节上有很多东西处理起来还是比较麻烦的(客户要求太多!!!)
因此我把涉及到基于word模板导出的这部分整理了一下,大家直接取经,内容有点多分了三部分写:文本段落、图表、表格。这三部分应该涵盖大部分应用场景了。
Poi实现根据word模板导出-图表篇
Poi实现根据word模板导出-表格篇
(需要完整代码的直接看最后位置!!!)
前言:
poi操作word原理同excel一样,先获取word对象,通过对象操作文件内容。poi实际上是将word解析成xml,然后在读取里面的内容。
逻辑:
1、获取word对象:
// 获取docx解析对象
//获取word模板
InputStream is = WordController.class.getResourceAsStream("/template/wordChartTemplate.docx");
XWPFDocument document = new XWPFDocument(is);
2、修改word模板,将需要封装的数据通过占位符代替
比如这里的${xxxx}就是占位符,我们通过代码可以将这部分替换成我们需要的内容。注意格式是自己定义的,并不是固定这种,自己选择什么格式,就按照对应格式处理,下面给指出。
3、封装我们的业务数据,根据你的具体业务填充数据,Map中的key是我们占位符中的值,value就是要展示在页面的值
// 替换word模板中占位符数据,根据业务自行封装
Map<String, String> params = new HashMap<>();
params.put("area1", "山东省");
params.put("area2", "河南省");
params.put("area3", "北京市");
params.put("area4", "天津市");
params.put("area5", "陕西省");
params.put("areaRate1", "42.15");
params.put("areaRate2", "22.35");
params.put("areaRate3", "42.35");
params.put("areaRate4", "23.11");
params.put("areaRate5", "15.34");
4、操作document文件对象,先获取段落,在获取段落中每段内容,根据我们的占位符格式判断是否存在,存在则更新我们占位符位置的内容。
private void changeText(XWPFDocument document, Map<String, String> params) {//获取段落集合List<XWPFParagraph> paragraphs = document.getParagraphs();for (XWPFParagraph paragraph : paragraphs) {//判断此段落时候需要进行替换String text = paragraph.getText();if(checkText(text)){List<XWPFRun> runs = paragraph.getRuns();for (XWPFRun run : runs) {//替换模板原来位置String value = changeValue(run.toString(), params);if (Objects.nonNull(value)) {run.setText(value, 0);}}}}
}/*** 匹配传入信息集合与模板* @param value 模板需要替换的区域* @param textMap 传入信息集合* @return 模板需要替换区域信息集合对应值*/
public static String changeValue(String value, Map<String, String> textMap){Set<Map.Entry<String, String>> textSets = textMap.entrySet();for (Map.Entry<String, String> textSet : textSets) {//匹配模板与替换值 格式${key}String key = "${"+textSet.getKey()+"}";if(value.indexOf(key)!= -1){value = value.replace(key, textSet.getValue());}}//模板未匹配到区域替换为空if(checkText(value)){value = "";}return value;
}/*** 判断文本中时候包含$* @param text 文本* @return 包含返回true,不包含返回false*/
public static boolean checkText(String text){boolean check = false;if(text.indexOf("$")!= -1){check = true;}return check;
}
解释:String key = "${"+textSet.getKey()+"}";这段代码就是我们自定义的占位符格式,如果你是其他格式在这里做相应修改
ok,完成!!!
但是这时大部分情况可能会遇到下面问题:
可以看到有的成功、有的失败。之前说过poi本质就是把word解析成xml,那我们直接把word导出为xml看一下,用office可以快速把word导出为xml格式,这里我们把我们的模板导出为xml看下同样的占位符有啥不同???
在idea里打开看一下,发现即使我们占位符都是通过复制粘贴在word里,他的xml格式还是会发生变化。
有两种解决方案:
1、最简单的办法把模板里的占位符粘贴到文本,在复制粘贴到word。
2、或者直接修改xml,在把xml导出为word。可以在idea中看xml文件,格式化一下,这种方式只要没有把xml格式改错,基本解析就不会再出问题了。
修改后再次执行程序:
成功!!!
完整代码:
package com.javacoding.controller;import cn.hutool.core.util.RandomUtil;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.usermodel.XWPFChart;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.openxmlformats.schemas.drawingml.x2006.chart.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.*;
import java.util.*;@RestController
@RequestMapping("/word")
public class WordController {@RequestMapping("/export")public void exportWord() throws Exception {//获取word模板InputStream is = WordController.class.getResourceAsStream("/template/wordChartTemplate.docx");try {ZipSecureFile.setMinInflateRatio(-1.0d);// 获取docx解析对象XWPFDocument document = new XWPFDocument(is);// 解析替换文本段落对象// 替换word模板中占位符数据,根据业务自行封装Map<String, String> params = new HashMap<>();params.put("area1", "山东省");params.put("area2", "河南省");params.put("area3", "北京市");params.put("area4", "天津市");params.put("area5", "陕西省");params.put("areaRate1", "42.15");params.put("areaRate2", "22.35");params.put("areaRate3", "42.35");params.put("areaRate4", "23.11");params.put("areaRate5", "15.34");changeText(document, params);// 输出新文件FileOutputStream fos = new FileOutputStream("D:\\test\\test.docx");document.write(fos);document.close();fos.close();} catch (Exception e) {e.printStackTrace();}}private void changeText(XWPFDocument document, Map<String, String> params) {//获取段落集合List<XWPFParagraph> paragraphs = document.getParagraphs();for (XWPFParagraph paragraph : paragraphs) {//判断此段落时候需要进行替换String text = paragraph.getText();if(checkText(text)){List<XWPFRun> runs = paragraph.getRuns();for (XWPFRun run : runs) {//替换模板原来位置String value = changeValue(run.toString(), params);if (Objects.nonNull(value)) {run.setText(value, 0);}}}}}/*** 判断文本中时候包含$* @param text 文本* @return 包含返回true,不包含返回false*/public static boolean checkText(String text){boolean check = false;if(text.indexOf("$")!= -1){check = true;}return check;}/*** 匹配传入信息集合与模板* @param value 模板需要替换的区域* @param textMap 传入信息集合* @return 模板需要替换区域信息集合对应值*/public static String changeValue(String value, Map<String, String> textMap){Set<Map.Entry<String, String>> textSets = textMap.entrySet();for (Map.Entry<String, String> textSet : textSets) {//匹配模板与替换值 格式${key}String key = "${"+textSet.getKey()+"}";if(value.indexOf(key)!= -1){value = value.replace(key, textSet.getValue());}}//模板未匹配到区域替换为空if(checkText(value)){value = "";}return value;}}