核心思想:设计占位符号,插入到word模板中占位,当生成word文件时,通过正则表达式使用数据替换预定的占位符号。
环境介绍
本文以springboot的demo为例,构建工具采用gradle,需要依赖的jar如下:
dependencies {
compile group: 'org.apache.poi', name: 'poi', version: '3.17'
compile group: 'org.apache.poi', name: 'poi-ooxml', version: '3.17'
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.8.1'
compile group: 'commons-beanutils', name: 'commons-beanutils', version: '1.9.3'
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
设计占位符
本文以三种占位符为例:
- 普通变量: ${变量名称}
- IF 控制语句:if{变量名称},变量的值为 Boolean 类型,控制 table 的显示与隐藏。
- For 循环语句:for{变量名称;占位符行的索引值} ,表格的行是动态创建的,值与占位符行对应。
设计模板
自定义一个模板,如下:
替换占位符
核心代码,如下:
package com.example.demo.utils;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.usermodel.*;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* POI 模板生成word工具类
* @author 赏花归去
* @Date 2019/8/26 11:45
*/
public class WordUtil {
//占位符
public static final String[] param_placeholder = {"${","}"};
private static final String[] if_placeholder = {"if{","}"};
private static final String[] for_placeholder = {"for{","}"};
//表格控制行的索引下标
private static final int tableControlIndex = 0;
//缓存文件的前缀和后缀
private static final String tempFilePrefix = "word_temp_";
private static final String tempFileSuffix = ".docx";
//获取占位符的正则表达式
public static Pattern getPattern(String[] placeholder){
String[] characters = {"$","(",")","*","+",".","[","?","^","{","|"};
String start = placeholder[0];
String end = placeholder[1];
for (String character : characters) {
String content = "\\" + character;
start = start.replace(character,content);
end = end.replace(character,content);
}
String reg = start + ".+?" + end;
return Pattern.compile(reg);
}
/**
* 生成word文件
* @param objParam
* @param listParam
* @param template
* @return
* @throws Exception
*/
public static File generateWordFile(Object objParam, Map<String, List> listParam, String template) {
try {
//获取模板文档对象
XWPFDocument document = new XWPFDocument(new FileInputStream(new ClassPathResource(template).getFile()));
//对象转Map<String,String>
Map<String, String> paramMap = getMap(objParam);
//替换段落中的变量
replaceParagraphs(document, paramMap);
// 替换表格中的变量
Iterator<XWPFTable> tableIte = document.getTablesIterator();
while (tableIte.hasNext()) {
XWPFTable table = tableIte.next();
//获取第一个单元格内容
XWPFTableRow firstRow = table.getRow(tableControlIndex);
XWPFTableCell firstCell = firstRow.getCell(0);
String firstText = firstCell.getText();
System.out.println("firstText = " + firstText);
boolean isHaveRegex = false;
//判断是否包含IF表达式
Matcher ifMatcher = getPattern(if_placeholder).matcher(firstText);
if (ifMatcher.find()) {
isHaveRegex = true;
String ifGroup = ifMatcher.group();
String key = ifGroup.substring(if_placeholder[0].length(), ifGroup.length() - if_placeholder[1].length()).trim();
String value = paramMap.get(key);
//判断是否显示表格
if (StringUtils.isBlank(value) || StringUtils.equals(value, "false")) {
deleteTable(table);
continue;
}
}
//判断是固定表还是动态表
Matcher forMatcher = getPattern(for_placeholder).matcher(firstText);
if (forMatcher.find()) {
isHaveRegex = true;
String forGroup = forMatcher.group();
String content = forGroup.substring(for_placeholder[0].length(), forGroup.length() - for_placeholder[1].length()).trim();
String key = content.split(";")[0];
int tempRowIndex = Integer.valueOf(content.split(";")[1]) - 1;
List dataList = listParam.get(key);
if (CollectionUtils.isNotEmpty(dataList)) {
for (int i = 0; i < dataList.size(); i++) {
Map<String, String> params = getMap(dataList.get(i));
params.put("index", (i + 1) + "");
copyReplace(table, table.getRow(tempRowIndex), tempRowIndex + i + 1, params);
}
}
//删除模板行
table.removeRow(tempRowIndex);
}
//替换表格剩余的所有占位符
replaceTableParam(table, paramMap);
//如果第一行存在表达式则删除表格第一行,否则按固定表替换变量
if (isHaveRegex) {
table.removeRow(tableControlIndex);
}
}
//生成文件
File file = File.createTempFile(tempFilePrefix, tempFileSuffix);
document.write(new FileOutputStream(file));
return file;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("文件生成失败!");
}
}
//参数转map类型
public static Map<String,String> getMap(Object objParam){
Map<String, String> paramMap = new HashMap<>();
if (objParam instanceof Map) {
paramMap = (Map<String, String>) objParam;
} else {
try {
paramMap = BeanUtils.describe(objParam);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
return paramMap;
}
/**
* 删除表格
* @param table 表格对象
*/
private static void deleteTable(XWPFTable table) {
List<XWPFTableRow> rows = table.getRows();
int rowLength = rows.size();
for (int i = 0; i < rowLength; i++) {
table.removeRow(0);
}
}
/**
* 复制行并替换变量
* @param table
* @param sourceRow
* @param rowIndex
* @param params
*/
private static void copyReplace(XWPFTable table, XWPFTableRow sourceRow, int rowIndex, Map<String, String> params) {
//在表格指定位置新增一行
XWPFTableRow targetRow = table.insertNewTableRow(rowIndex);
//复制行属性
targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());
List<XWPFTableCell> cellList = sourceRow.getTableCells();
if (CollectionUtils.isEmpty(cellList)) {
return;
}
//复制列及其属性和内容
XWPFTableCell targetCell = null;
for (XWPFTableCell sourceCell : cellList) {
targetCell = targetRow.addNewTableCell();
//列属性
targetCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());
//段落属性
if (sourceCell.getParagraphs() != null && sourceCell.getParagraphs().size() > 0) {
targetCell.getParagraphs().get(0).getCTP().setPPr(sourceCell.getParagraphs().get(0).getCTP().getPPr());
if (sourceCell.getParagraphs().get(0).getRuns() != null && sourceCell.getParagraphs().get(0).getRuns().size() > 0) {
XWPFRun cellR = targetCell.getParagraphs().get(0).createRun();
cellR.setText(sourceCell.getText());
cellR.setBold(sourceCell.getParagraphs().get(0).getRuns().get(0).isBold());
} else {
targetCell.setText(sourceCell.getText());
}
} else {
targetCell.setText(sourceCell.getText());
}
}
//替换变量
if (Objects.nonNull(params)) {
for (XWPFTableCell cell : targetRow.getTableCells()) {
String value = StringUtils.isNotBlank(cell.getText()) ? cell.getText().trim() : "";
Matcher matcher = getPattern(param_placeholder).matcher(value);
if (!matcher.find()) continue;
//替换变量
String key = value.substring(param_placeholder[0].length(), value.length() - param_placeholder[1].length()).trim();
cell.removeParagraph(0);
cell.setText(getValue(params, key));
}
}
}
/**
* 替换表格变量
* @param table
* @param parametersMap
*/
private static void replaceTableParam(XWPFTable table, Map<String, String> parametersMap) {
for (int i = 0; i < table.getNumberOfRows(); i++) {
XWPFTableRow row = table.getRow(i);
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
boolean isFind = false;
String cellText = cell.getText();
Matcher matcher = getPattern(param_placeholder).matcher(cellText);
while (matcher.find()) {
String paramTemp = matcher.group();
String key = paramTemp.substring(param_placeholder[0].length(), paramTemp.length() - param_placeholder[1].length()).trim();
//替换变量
String value = getValue(parametersMap, key);
cellText = cellText.replace(paramTemp, value);
isFind = true;
}
if (isFind) {
cell.removeParagraph(0);
cell.setText(cellText);
}
}
}
}
/**
* 替换所有段落中的变量
* @param document
* @param parametersMap
*/
private static void replaceParagraphs(XWPFDocument document, Map<String, String> parametersMap) {
// 替换文本中的变量
Iterator<XWPFParagraph> paragraphIte = document.getParagraphsIterator();
while (paragraphIte.hasNext()) {
XWPFParagraph paragraph = paragraphIte.next();
replaceParagraphParam(paragraph, parametersMap);
}
}
/**
* 替换某个段落中的变量
* @param xWPFParagraph
* @param parametersMap
*/
private static void replaceParagraphParam(XWPFParagraph xWPFParagraph, Map<String, String> parametersMap) {
List<XWPFRun> runs = xWPFParagraph.getRuns();
String xWPFParagraphText = xWPFParagraph.getText();
Matcher matcher = getPattern(param_placeholder).matcher(xWPFParagraphText);
if (!matcher.find()) return;
//标签开始和结束run位置(注:getBeginRun 和 getEndRun 针对的是搜索内容分散在多个run的情况,本例因为是单个字符,所以只会在一个run中)
int beginRunIndex = xWPFParagraph.searchText(param_placeholder[0], new PositionInParagraph()).getBeginRun();
int endRunIndex = xWPFParagraph.searchText(param_placeholder[1], new PositionInParagraph()).getEndRun();
// 在一个run标签内
if (beginRunIndex == endRunIndex) {
XWPFRun beginRun = runs.get(beginRunIndex);
String beginRunText = beginRun.text();
Matcher paramMatcher = getPattern(param_placeholder).matcher(beginRunText);
if (paramMatcher.find()) {
String group = paramMatcher.group();
String key = group.substring(param_placeholder[0].length(), group.length() - param_placeholder[1].length()).trim();
beginRunText = beginRunText.replace(group, getValue(parametersMap, key));
//0 标识替换run的所有内容
beginRun.setText(beginRunText, 0);
}
} else {
//如果跨多个run标签,获取多个run的内容
XWPFRun beginRun = runs.get(beginRunIndex);
StringBuffer runSb = new StringBuffer();
for (int i = beginRunIndex; i <= endRunIndex; i++) {
XWPFRun run = runs.get(i);
runSb.append(run.text());
}
//替换变量
String runText = runSb.toString();
Matcher paramMatcher = getPattern(param_placeholder).matcher(runText);
if (paramMatcher.find()) {
String group = paramMatcher.group();
String key = group.substring(param_placeholder[0].length(), group.length() - param_placeholder[1].length()).trim();
runText = runText.replace(group, getValue(parametersMap, key));
}
//创建一个新的run
XWPFRun insertNewRun = xWPFParagraph.insertNewRun(endRunIndex + 1);
insertNewRun.getCTR().setRPr(beginRun.getCTR().getRPr());
insertNewRun.setText(runText);
//删除原有的run
for (int i = beginRunIndex; i <= endRunIndex; i++) {
xWPFParagraph.removeRun(beginRunIndex);
}
}
//递归方式替换所有的变量
replaceParagraphParam(xWPFParagraph, parametersMap);
}
/**
* 从map中取出对应key的值
* @param parametersMap
* @param key
* @return
*/
private static String getValue(Map<String, String> parametersMap, String key) {
String value = parametersMap.get(key);
if (StringUtils.isNotBlank(value) && !StringUtils.equals("null", value)) {
if (StringUtils.equals("true", value)) {
return "是";
} else if (StringUtils.equals("false", value)) {
return "否";
}
return value;
}
return "";
}
}
测试数据:
package com.example.demo;
import com.example.demo.utils.WordUtil;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 测试类
* @author 赏花归去
* @Date 2019/8/26 17:34
*/
public class WordUtilTest extends DemoApplicationTests {
public static void main(String[] args) {
File file = WordUtil.generateWordFile(getParamMap(),getListMap(),"/template.docx");
System.out.println("file.getPath() = " + file.getPath());
}
public static Map<String,String> getParamMap(){
Map<String,String> paramMap = new HashMap<>();
paramMap.put("title","赏花");
paramMap.put("author","苏轼");
paramMap.put("dynasty","北宋");
paramMap.put("date","嘉祐二年");
paramMap.put("isShow","true");
return paramMap;
}
public static Map<String,List> getListMap(){
Map<String,String> paramMap1 = new HashMap<>();
paramMap1.put("name","将进酒");
paramMap1.put("author","李白");
Map<String,String> paramMap2 = new HashMap<>();
paramMap2.put("name","侠客行");
paramMap2.put("author","李白");
Map<String,List> listMap = new HashMap<>();
listMap.put("poetry",Arrays.asList(paramMap1,paramMap2));
return listMap;
}
}
结果
总结
虽然目前能够解决一些问题,但是作为一个工具类,还有很多不足之处,例如复杂表格的兼容性、复制行字体样式等,如果您有更好的idea,欢迎评论!