最近接到個需求,需要將數據導出生成word文件,剛開始是打算通過操作poi生成一個word文件的,但是發現太麻煩了,在網上查了一圈,發現可以通過模板替換的形式就能實現效果。於是根據自身的實際情況做了一個.docx的word模板,如下

因為word模板文件是2007版.docx格式的,所以使用 XWPFDocument 對文檔進行操作,若是03版后綴名為.doc的,就得使用HWPFDocument了(需要單獨引入依賴,這里以07版演示)
在pom文件中引入依賴
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.0</version>
</dependency>
添加Word工具類
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.*;
import java.io.*;
import java.rmi.RemoteException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class WordUtils {
/** * 對word進行參數替換並生成新的文件 * * @param inputPath word模板路徑和名稱 * @param outPath word模板路徑和名稱 * @param params 待填充的數據 params.put("key",value) 文檔中對應為 ${key} */
public static void readwriteWord(String inputPath, String outPath, Map<String, Object> params) throws IOException, InvalidFormatException {
InputStream is = new FileInputStream(inputPath);
XWPFDocument document = new XWPFDocument(OPCPackage.open(is));
replaceParams(document, params);
OutputStream os = new FileOutputStream(outPath);
document.write(os);
close(os);
close(is);
}
/** * 替換word中的參數與動態新增表格行數 * * @param in 模板輸入流 * @param os 修改后的輸出流 * @param params 待填充的數據 params.put("key",value) 文檔中對應為 ${key} * @param tableIndex 表格下標,從0開始,如Word中3個表格,僅對1、3個表格進行修改,參數為 int[]{0,2} * @param tables 新增表格的數據,數據格式為 [表[行[單元格]]] * @throws InvalidFormatException * @throws IOException */
public static void readwriteWord(InputStream in, OutputStream os, Map<String, Object> params, int[] tableIndex, List<List<String[]>> tables) throws IOException, InvalidFormatException {
XWPFDocument document = new XWPFDocument(OPCPackage.open(in));
replaceParams(document, params);
if (tableIndex.length != tables.size()) throw new RemoteException("表格下標數量與表格參數數量不一致!");
for (int i = 0; i < tableIndex.length; i++) {
changeTable(document, params, i, tables.get(i));
}
document.write(os);
}
/** * 實現對word讀取和修改操作 * * @param in 模板輸入流 * @param out 輸出流 * @param params 待填充的數據,從數據庫讀取 * @throws IOException * @throws InvalidFormatException */
public static void readwriteWord(InputStream in, OutputStream out, Map<String, Object> params) {
try {
XWPFDocument document;
document = new XWPFDocument(OPCPackage.open(in));
replaceParams(document, params);
document.write(out);
} catch (Exception e) {
e.printStackTrace();
}
}
/** * 替換段落里面的變量 * * @param doc 要替換的文檔 * @param params 參數 */
private static void replaceParams(XWPFDocument doc, Map<String, Object> params) {
Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
XWPFParagraph paragraph;
while (iterator.hasNext()) {
paragraph = iterator.next();
replaceParam(paragraph, params);
}
}
/** * @param doc docx解析對象 * @param params 需要替換的信息集合 * @param tableIndex 第幾個表格 * @param tableList 需要插入的表格信息集合 */
public static void changeTable(XWPFDocument doc, Map<String, Object> params, int tableIndex, List<String[]> tableList) {
//獲取表格對象集合
List<XWPFTable> tables = doc.getTables();
//獲取第一個表格 根據實際模板情況 決定去第幾個word中的表格
XWPFTable table = tables.get(tableIndex);
//替換表格中的參數
replaceTableParams(doc, params);
//在表格中插入數據
insertTable(table, tableList);
}
/** * 替換表格里面的變量 * * @param doc 要替換的文檔 * @param params 參數 */
private static void replaceTableParams(XWPFDocument doc, Map<String, Object> params) {
Iterator<XWPFTable> iterator = doc.getTablesIterator();
XWPFTable table;
List<XWPFTableRow> rows;
List<XWPFTableCell> cells;
List<XWPFParagraph> paras;
while (iterator.hasNext()) {
table = iterator.next();
//判斷表格是需要替換還是需要插入,判斷邏輯有$為替換,表格無$為插入
if (matcher(table.getText()).find()) {
rows = table.getRows();
for (XWPFTableRow row : rows) {
cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
paras = cell.getParagraphs();
for (XWPFParagraph para : paras) {
replaceParam(para, params);
}
}
}
}
}
}
/** * 為表格插入行數,此處不處理表頭,所以從第二行開始 * * @param table 需要插入數據的表格 * @param tableList 插入數據集合 */
private static void insertTable(XWPFTable table, List<String[]> tableList) {
//創建與數據一致的行數
for (int i = 0; i < tableList.size(); i++) {
table.createRow();
}
int length = table.getRows().size();
for (int i = 1; i < length; i++) {
XWPFTableRow newRow = table.getRow(i);
List<XWPFTableCell> cells = newRow.getTableCells();
for (int j = 0; j < cells.size(); j++) {
XWPFTableCell cell = cells.get(j);
cell.setText(tableList.get(i - 1)[j]);
}
}
}
/** * 替換段落里面的變量 * * @param paragraph 要替換的段落 * @param params 參數 */
private static void replaceParam(XWPFParagraph paragraph, Map<String, Object> params) {
List<XWPFRun> runs;
Matcher matcher;
String runText = "";
if (matcher(paragraph.getParagraphText()).find()) {
runs = paragraph.getRuns();
int j = runs.size();
for (int i = 0; i < j; i++) {
runText += runs.get(0).toString();
//保留最后一個段落,在這段落中替換值,保留段落樣式
if (!((j - 1) == i)) {
paragraph.removeRun(0);
}
}
matcher = matcher(runText);
if (matcher.find()) {
while ((matcher = matcher(runText)).find()) {
runText = matcher.replaceFirst(String.valueOf(params.get(matcher.group(1))));
}
runs.get(0).setText(runText, 0);
}
}
}
/** * 正則匹配字符串 * * @param str * @return */
private static Matcher matcher(String str) {
Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(str);
return matcher;
}
/** * 關閉輸入流 * * @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();
}
}
}
}
測試
import java.io.*;
import java.util.*;
/** * @author f * @date 2021-05-06 23:05 */
public class WordTest {
public static void main(String[] args) {
Map<String, Object> params = new HashMap<>();
params.put("name", "XXXWord");
params.put("startDate", "2021-04-21");
params.put("endDate", "2021-04-28");
params.put("header1", "表頭1");
params.put("header2", "表頭2");
params.put("header3", "表頭3");
List<List<String[]>> tableList = new ArrayList<>();
List<String[]> table1 = new ArrayList<>();
table1.add(new String[]{"1","張三","男","22","186xxxxxxxx","北"});
table1.add(new String[]{"2","李四","女","23","187xxxxxxxx","上"});
table1.add(new String[]{"3","王五","男","24","188xxxxxxxx","廣"});
table1.add(new String[]{"4","趙六","女","25","189xxxxxxxx","深"});
List<String[]> table2 = new ArrayList<>();
table2.add(new String[]{"1","鵝廠","T3","xxx","5"});
table2.add(new String[]{"2","菊廠","18","xxx","5"});
table2.add(new String[]{"3","動物園","P8","xxx","5"});
List<String[]> table3 = new ArrayList<>();
table3.add(new String[]{"1","a1","a2","a3"});
table3.add(new String[]{"2","b1","b2","b3"});
table3.add(new String[]{"3","c1","c2","c3"});
tableList.add(table1);
tableList.add(table2);
tableList.add(table3);
String filePath = "D:\\word模板.docx";
String outpath = "D:\\word文件.docx";
try {
InputStream is = new FileInputStream(filePath);
OutputStream os = new FileOutputStream(outpath);
WordUtils.readwriteWord(is,os,params,new int[]{0,1,2},tableList);
} catch (Exception e) {
e.printStackTrace();
}
}
}
生成的文件內容如下

因XWPFRun 獲取的數據有時候會有問題,不能完整的識別參數 ${xxx},因此先把一段的字符全部讀取,再用正則表達式去匹配替換
參考文檔
https://www.jianshu.com/p/6603b1ea3ad1
