最近工作上碰到了这个问题,就研究了一下。
结合了网上几位大哥的成果,我自己又优化了一下。
除了基础的导入参数之外,还优化了参数识别能力,添加了页面复制能力,并且保留了样式。
下面是我测试的word模板:
你好,现在是${time} ,我是${userName} 。
表格1
##{foreachTable}##
table1
序号
姓名
年龄
##{foreachRows}##
${number}
${name}
${age}${unit}
第二页,整个页面复制的模板 ,其实就是用一个上面的表格,然后把第三行内容行单元格的边隐藏掉就行了。
##{foreachTable}##
page1 ##{foreachRows}##
页面复制测试。
本页介绍${name},详情看下表:
姓名
${name}
年龄
${age}
思想派别
${philosophy}
能力
${skill}
先是maven的依赖包:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.0</version>
</dependency>
然后是我的代码,工具类的代码:
package com.zyy.testPro.word;
import org.apache.poi.ooxml.POIXMLDocument;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import java.io.*;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @ClassName:
* @Description: POI的word文档模板工具类
* @CreateDate: 2020-01-08 14:39
* @Author:zhaoyangyang
*/
public class WordTemplateUtils {
/**
* 根据模板生成word
* @param inputUrl 模板的路径
* @param params 需要替换的参数
*/
public static void getWord(String inputUrl,String outputUrl, Map<String, Object> params){
try {
XWPFDocument doc = new XWPFDocument(POIXMLDocument.openPackage(inputUrl));
@SuppressWarnings("unchecked")
Map<String, Object> parametersMap = (Map<String, Object>) params.get("parametersMap");
replaceInPara(doc, parametersMap); //替换文本里面的变量
replaceInTable(doc, params); //替换表格里面的变量
OutputStream os = new FileOutputStream(outputUrl);
doc.write(os);
close(os);
} catch (IOException e) {
e.printStackTrace();
}
}
private static List<XWPFRun> getFormatRuns(XWPFParagraph paragraph){
//遍历获取段落中所有的runs
List<XWPFRun> runs = paragraph.getRuns();
//合并逻辑
for (Integer i = 0; i < runs.size(); i++) {
String text0 = runs.get(i).getText(runs.get(i).getTextPosition());
if (text0 != null && text0.contains("$")) {
if((text0.length()>text0.indexOf("$")+1 && '{'==text0.charAt(text0.indexOf("$")+1)) || (runs.get(i+1)!=null && runs.get(i+1).getText(-1).startsWith("{"))){
//记录分隔符中间跨越的runs数量,用于字符串拼接和替换
int j = i;
boolean flag=false;
for (; j < runs.size(); j++) {
String text1 = runs.get(j).getText(runs.get(j).getTextPosition());
if (text1 != null && text1.contains("}")) {
flag=true;
break;
}
}
if (flag) {
//将中间设计的run拼成一个大字符串,并将所有run设为null
StringBuilder newText = new StringBuilder();
for (int k = i; k <= j; k++) {
String text2 = runs.get(k).text();
newText.append(text2);
runs.get(k).setText(null, 0);
}
//处理这种以${结尾的情况,不然这个会丢失
if(newText.toString().trim().endsWith("${")){
if(runs.size()>j){
runs.get(j+1).setText("${"+runs.get(j+1).text(),0);
}
}
//将新字符串分割为理想中的替换字符串和普通字符串
String[] newArray= newText.toString().split("\\$\\{");
for(String str1:newArray){
if(str1.contains("}")){//包含替换字符串
String[] newArray2= str1.split("}");
if(str1.startsWith("}")){//替换字符串为空,}后面可能跟有普通字符串
if(newArray2.length>0){
XWPFRun run=paragraph.insertNewRun(i++);
run.setText(newArray2[0]);
run.getCTR().setRPr(runs.get(i).getCTR().getRPr());
}
}else{//替换字符串不为空,可能有普通字符床
if(newArray2.length>1){
XWPFRun run1=paragraph.insertNewRun(i++);
run1.setText("${"+newArray2[0]+"}");
run1.getCTR().setRPr(runs.get(i).getCTR().getRPr());
XWPFRun run2=paragraph.insertNewRun(i++);
run2.setText(newArray2[1]);
run2.getCTR().setRPr(runs.get(i).getCTR().getRPr());
}else{
XWPFRun run=paragraph.insertNewRun(i++);
run.setText("${"+newArray2[0]+"}");
run.getCTR().setRPr(runs.get(i).getCTR().getRPr());
}
}
}else{//普通字符串
XWPFRun run=paragraph.insertNewRun(i++);
run.setText(str1);
run.getCTR().setRPr(runs.get(i).getCTR().getRPr());
}
}
}
}
}
}
return runs;
}
/**
* 替换段落里面的变量
* @param doc 要替换的文档
* @param params 参数
*/
private static void replaceInPara(XWPFDocument doc, Map<String, Object> params) {
Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
XWPFParagraph para;
while (iterator.hasNext()) {
para = iterator.next();
replaceInPara(para, params);
}
}
/**
* 替换段落里面的变量
*
* @param para 要替换的段落
* @param params 参数
*/
private static void replaceInPara(XWPFParagraph para, Map<String, Object> params) {
List<XWPFRun> runs;
if (matcher(para.getParagraphText()).find()) {
runs = getFormatRuns(para);
for (int i = 0; i < runs.size(); i++) {
XWPFRun run = runs.get(i);
if(run!=null){
String runText = run.toString();
if (runText.length()>1 && '$' == runText.charAt(0) && '{' == runText.charAt(1)) {
String key=runText.replace("${","").replace("}","").trim();
Object value=params.get(key);
if (value instanceof String) {//文字
run.setText((String)value,0);
}else{
run.setText("",0);
}
}
}
}
}
}
/**
* 替换表格里面的变量
* @param doc 要替换的文档
* @param params 参数
*/
private static void replaceInTable(XWPFDocument doc, Map<String, Object> params) {
Iterator<XWPFTable> iterator = doc.getTablesIterator();
XWPFTable table;
@SuppressWarnings("unchecked")
Map<String, Object> parametersMap = (Map<String, Object>) params
.get("parametersMap");
while (iterator.hasNext()) {
table = iterator.next();
String tableText = table.getText();
List<XWPFTableCell> tableCells = table.getRows().get(0).getTableCells();// 获取到模板表格第一行,用来判断表格类型
// 查找到##{foreach标签,该表格需要处理循环
if (tableText.indexOf("##{foreachTable}##") > -1) {//循环处理表格
// 查找到##{foreach标签,该表格需要处理循环
if (tableCells.size() != 2
|| tableCells.get(0).getText().indexOf("##{foreachTable}##") < 0
|| tableCells.get(0).getText().trim().length() == 0) {
System.out
.println("文档中有"
+ "表格模板错误,模板表格第一行需要设置2个单元格,"
+ "第一个单元格存储表格类型(##{foreachTable}## 或者 ##{foreachTableRow}##),第二个单元格定义数据源。");
return;
}
String dataSource = tableCells.get(1).getText();
System.out.println("读取到数据源:"+dataSource);
if (!params.containsKey(dataSource)) {
System.out.println("文档中" + dataSource + "表格模板数据源缺失");
return;
}
@SuppressWarnings("unchecked")
List<Map<String, Object>> tableDataList = (List<Map<String, Object>>) params
.get(dataSource);
// System.out.println("循环生成表格内部的行");
insertTable(table, tableDataList,parametersMap); //插入数据
}else if(matcher(tableText).find()){//静态表格,替换数据
replaceTable(table,parametersMap);
}
}
}
/**
* 为表格替换数据
* @param table
* @param parametersMap
*/
private static void replaceTable(XWPFTable table, Map<String, Object> parametersMap){
List<XWPFTableRow> rows = table.getRows();
for (XWPFTableRow row : rows) {
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
List<XWPFParagraph> paras = cell.getParagraphs();
for (XWPFParagraph para : paras) {
replaceInPara(para, parametersMap);
}
}
}
}
/**
* 为表格插入数据,行数不够添加新行
*
* @param table 需要插入数据的表格
* @param tableList 插入数据集合
*/
private static void insertTable(XWPFTable table, List<Map<String,Object>> tableList, Map<String, Object> parametersMap) {
table.removeRow(0);//删除第一行
List<XWPFTableRow> TempTableRows = table.getRows();// 获取模板表格所有行
int tagRowsIndex=0;
for(int i=0;i<TempTableRows.size();i++){
String rowText = TempTableRows.get(i).getCell(0).getText();// 获取到表格行的第一个单元格
if (rowText.indexOf("##{foreachRows}##") > -1) {
tagRowsIndex = i;
i++;//跳过下一行模板行。
}else{
replaceTableRow(TempTableRows.get(i), parametersMap);// 处理标签替换
}
}
/* 循环生成模板行 */
XWPFTableRow tempRow = TempTableRows.get(tagRowsIndex + 1);// 获取到模板行
for (int i = tableList.size()-1; i >= 0; i--) {
//指定位置,复制行
XWPFTableRow newCreateRow=copyRow(table,tempRow,tagRowsIndex + 2);
replaceTableRow(newCreateRow, tableList.get(i));// 处理标签替换
}
table.removeRow(tagRowsIndex);//删除标签行
table.removeRow(tagRowsIndex);//删除模板行
}
/**
* 正则匹配字符串
*
* @param str
* @return
*/
private static Matcher matcher(String str) {
Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
return pattern.matcher(str);
}
/**
* 将输入流中的数据写入字节数组
*
* @param in
* @return
*/
public static byte[] inputStream2ByteArray(InputStream in, boolean isClose) {
byte[] byteArray = null;
try {
int total = in.available();
byteArray = new byte[total];
in.read(byteArray);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (isClose) {
try {
in.close();
} catch (Exception e2) {
e2.getStackTrace();
}
}
}
return byteArray;
}
/**
* 关闭输入流
*
* @param is
*/
private static void close(InputStream is) {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 关闭输出流
*
* @param os
*/
private static void close(OutputStream os) {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static XWPFTableRow copyRow(XWPFTable table, XWPFTableRow sourceRow, int rowIndex){
//在表格指定位置新增一行
XWPFTableRow targetRow = table.insertNewTableRow(rowIndex);
//复制行属性
targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());
List<XWPFTableCell> cellList = sourceRow.getTableCells();
if (null == cellList) {
return null;
}
//复制列及其属性和内容
for (XWPFTableCell sourceCell : cellList) {
XWPFTableCell targetCell = targetRow.addNewTableCell();
//列属性
targetCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());
//段落属性
if(sourceCell.getParagraphs()!=null&&sourceCell.getParagraphs().size()>0){
List<IBodyElement> bodyElements = sourceCell.getBodyElements();// 所有对象(段落+表格)
int curT = 0;// 当前操作表格对象的索引
int curP = 0;// 当前操作段落对象的索引
IBodyElement currentElement=targetCell.getParagraphs().get(0);
for(int i=0;i<bodyElements.size();i++){
IBodyElement element=bodyElements.get(i);
if(BodyElementType.TABLE.equals(element.getElementType())){// 处理表格
//位置指针
XmlCursor cursor;
if(currentElement instanceof XWPFParagraph){
cursor=((XWPFParagraph) currentElement).getCTP().newCursor();
}else{
cursor=((XWPFTable)currentElement).getCTTbl().newCursor();
}
cursor.toNextSibling();
XWPFTable targetTable = targetCell.insertNewTbl(cursor);
XWPFTable sourceTable = element.getBody().getTables().get(curT);
//复制表格
copyTable(targetTable,sourceTable);
currentElement=targetTable;//更改当前元素
curT++;
}else{
XmlCursor cursor;
if(currentElement instanceof XWPFParagraph){
cursor=((XWPFParagraph) currentElement).getCTP().newCursor();
}else{
cursor=((XWPFTable)currentElement).getCTTbl().newCursor();
}
cursor.toNextSibling();
XWPFParagraph targetParagraph=targetCell.insertNewParagraph(cursor);
XWPFParagraph sourceParagraph=sourceCell.getParagraphs().get(curP);
//复制段落
targetParagraph.getCTP().setPPr(sourceParagraph.getCTP().getPPr());// 设置段落样式
for (XWPFRun sourceRun : sourceParagraph.getRuns()) {
XWPFRun targetRun = targetParagraph.createRun();
targetRun.getCTR().setRPr(sourceRun.getCTR().getRPr());
// 设置文本
targetRun.setText(sourceRun.text());
}
currentElement=targetParagraph;//更改当前元素
curP++;
}
}
}else{
targetCell.setText(sourceCell.getText());
}
}
return targetRow;
}
/**
* 复制表格
* @param targetTable
* @param sourceTable
*/
public static void copyTable(XWPFTable targetTable, XWPFTable sourceTable) {
targetTable.getCTTbl().setTblPr(sourceTable.getCTTbl().getTblPr());
for(int i=0;i<sourceTable.getRows().size();i++){
XWPFTableRow row=sourceTable.getRows().get(i);
copyRow(targetTable,row,i);
}
}
/**
* 根据参数parametersMap对表格的一行进行标签的替换
*
* @author Juveniless
* @date 2017年11月23日 下午2:09:24
* @param tableRow
* 表格行
* @param parametersMap
* 参数map
*
*/
public static void replaceTableRow(XWPFTableRow tableRow, Map<String, Object> parametersMap) {
List<XWPFTableCell> tableCells = tableRow.getTableCells();
for (XWPFTableCell xWPFTableCell : tableCells) {
List<XWPFParagraph> paragraphs = xWPFTableCell.getParagraphs();
for (XWPFParagraph xwpfParagraph : paragraphs) {
if(xwpfParagraph.getText().indexOf("${")>-1){
replaceInPara(xwpfParagraph, parametersMap);
}
}
List<XWPFTable> tables=xWPFTableCell.getTables();
for (XWPFTable xwpfTable : tables) {
replaceTable(xwpfTable,parametersMap);
}
}
}
}
测试类的代码:
package com.zyy.testPro.word;
/**
* @ClassName:
* @Description:
* @CreateDate: 2020-01-02 16:34
* @Author:zhaoyangyang
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Test {
public static void main(String[] args) throws IOException {
Map<String, Object> wordDataMap = new HashMap<String, Object>();// 存储报表全部数据
Map<String, Object> parametersMap = new HashMap<String, Object>();// 存储报表中不循环的数据
parametersMap.put("time", "2020-1-14");
parametersMap.put("userName", "valhalla");
List<Map<String, Object>> table1 = new ArrayList<Map<String, Object>>();
Map<String, Object> map1=new HashMap<>();
map1.put("number", "1");
map1.put("name", "孔子");
map1.put("age", "71");
map1.put("unit", "岁");
Map<String, Object> map2=new HashMap<>();
map2.put("number", "2");
map2.put("name", "墨子");
map2.put("age", "91");
map2.put("unit", "岁");
Map<String, Object> map3=new HashMap<>();
map3.put("number", "3");
map3.put("name", "庄子");
map3.put("age", "83");
map3.put("unit", "岁");
Map<String, Object> map4=new HashMap<>();
map4.put("number", "4");
map4.put("name", "韩非子");
map4.put("age", "47");
map4.put("unit", "岁");
table1.add(map1);
table1.add(map2);
table1.add(map3);
table1.add(map4);
wordDataMap.put("table1", table1);
List<Map<String, Object>> page1 = new ArrayList<Map<String, Object>>();
Map<String, Object> obj1=new HashMap<>();
obj1.put("name", "孔子");
obj1.put("age", "71");
obj1.put("philosophy", "儒家");
obj1.put("skill", "教学");
Map<String, Object> obj2=new HashMap<>();
obj2.put("name", "墨子");
obj2.put("age", "91");
obj2.put("philosophy", "墨家");
obj2.put("skill", "木工");
Map<String, Object> obj3=new HashMap<>();
obj3.put("name", "庄子");
obj3.put("age", "83");
obj3.put("philosophy", "道家");
obj3.put("skill", "讲故事");
Map<String, Object> obj4=new HashMap<>();
obj4.put("name", "韩非子");
obj4.put("age", "47");
obj4.put("philosophy", "法家");
obj4.put("skill", "权谋");
page1.add(obj1);
page1.add(obj2);
page1.add(obj3);
page1.add(obj4);
wordDataMap.put("page1", page1);
wordDataMap.put("parametersMap", parametersMap);
WordTemplateUtils.getWord("C:\\ZhaoYangyang\\new doc.docx","C:\\ZhaoYangyang\\new doc2.docx",wordDataMap);
}
}
然后,就会看到,生成的文档。当然你也可以把它转换成byte[],传到浏览器下载。
就是这样。欢迎大家交流技术