此工具类使用Java-XWPFDocument类操作Word文档中的段落和表格,方便开发人员快速将电子表单数据写入Word文件并保存(已测试),主要功能包括:
- 根据索引获取Word文档中的段落、表格;
- 向文档段落中插入表单数据(占位符:&{name},支持对象和Map,下同);
- 向Word表格中写入表单数据;
- 向Word表格中写入多条同类型数据;
- 向指定单元格中插入图片、合并单元格。
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;
import org.springframework.util.ReflectionUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @Description: word工具类
* @Author: Michael
* @Date: 2024/1/30 8:23
* @Version: V1.0
**/
public class MjWord {
private final String path;
private FileInputStream fi;
private final XWPFDocument docx;
/**
* 构造函数
*
* @param path: 文件路径
* @author Michael
* @date 2024/1/25 8:45
* @return: null
**/
public MjWord(String path) throws Exception {
File file = new File(path);
if (!file.exists()) {
throw new Exception("非文件路径!");
}
if (!path.toLowerCase().contains(".docx")) {
throw new Exception("非docx文件!");
}
this.path = path.replace("/", File.separator);
this.fi = new FileInputStream(file);
this.docx = new XWPFDocument(this.fi);
}
/**
* 获取所有段落
*
* @param empty: 是否剔除空白段落
* @author Michael
* @date 2024/1/25 10:00
* @return: java.util.List<org.apache.poi.xwpf.usermodel.XWPFParagraph>
**/
public List<XWPFParagraph> getParagraphs(boolean empty) throws Exception {
List<XWPFParagraph> paragraphs = this.docx.getParagraphs();
if (!empty) {
return paragraphs;
}
//剔除空白段落
List<XWPFParagraph> list = new ArrayList<>();
for (XWPFParagraph paragraph : paragraphs) {
String text = paragraph.getParagraphText();
if (isNullStr(text)) {
continue;
}
list.add(paragraph);
}
return list;
}
/**
* 获取特定段落
*
* @param inx: 子字符串
* @author Michael
* @date 2024/1/25 10:02
* @return: org.apache.poi.xwpf.usermodel.XWPFParagraph
**/
public XWPFParagraph getParagraph(String inx) throws Exception {
List<XWPFParagraph> paragraphs = getParagraphs(false);
for (XWPFParagraph paragraph : paragraphs) {
if (paragraph.getParagraphText().equals(inx)) {
return paragraph;
}
}
return paragraphs.get(0);
}
/**
* 获取所有表格
*
* @author Michael
* @date 2024/1/25 10:25
* @return: java.util.List<org.apache.poi.xwpf.usermodel.XWPFTable>
**/
public List<XWPFTable> getTables() throws Exception {
return this.docx.getTables();
}
/**
* 获取特定表格
*
* @param inx: 索引
* @author Michael
* @date 2024/1/25 10:10
* @return: org.apache.poi.xwpf.usermodel.XWPFTable
**/
public XWPFTable getTable(int inx) throws Exception {
List<XWPFTable> tables = getTables();
return tables.get(inx);
}
/**
* 向段落里写入数据
*
* @param paragraph: 段落
* @param data: 数据
* @author Michael
* @date 2024/1/25 10:13
* @return: void
**/
public void writeDataToParagraph(XWPFParagraph paragraph,
Map<String, String> data) throws Exception {
String text = paragraph.getText();
String resultText = replaceText(text, data);
writeText(paragraph, resultText);
}
/**
* 向段落里写入数据
*
* @param paragraph: 段落
* @param data: 数据
* @param type: 类名路径
* @author Michael
* @date 2024/1/25 10:37
* @return: void
**/
public void writeDataToParagraph(XWPFParagraph paragraph,
Object data,
String type) throws Exception {
String text = paragraph.getText();
String resultText = replaceText(text, data, type);
writeText(paragraph, resultText);
}
/**
* 向表格里写入数据
*
* @param table: 表格
* @param data: 数据
* @author Michael
* @date 2024/1/25 17:46
* @return: void
**/
public void writeDataToTable(XWPFTable table,
Map<String, String> data) throws Exception {
List<XWPFTableRow> rows = table.getRows();
for (XWPFTableRow row : rows) {
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
// 不能使用该方法直接加内容,这样会在原内容后面追加,并且不能保证跟原字体样式一致
// cell.setText(resultText);
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
//向单元格的段落里写入数据
writeDataToParagraph(paragraph, data);
}
}
}
}
/**
* 向表格里写入数据
*
* @param table: 表格
* @param data: 数据
* @param type: 类名路径
* @author Michael
* @date 2024/1/25 10:50
* @return: void
**/
public void writeDataToTable(XWPFTable table,
Object data,
String type) throws Exception {
List<XWPFTableRow> rows = table.getRows();
for (XWPFTableRow row : rows) {
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
writeDataToParagraph(paragraph, data, type);
}
}
}
}
/**
* 向表格里写入多条数据
*
* @param table: 表格
* @param dataList: 数据列表
* @param startRow: 起始行索引
* @author Michael
* @date 2024/1/25 10:24
* @return: void
**/
public void writeDataListToTable(XWPFTable table,
List<Map<String, String>> dataList,
int startRow) throws Exception {
for (Map<String, String> data : dataList) {
//创建新的行
CTRow ctrow = CTRow.Factory.parse(table.getRow(startRow).getCtRow().newInputStream());
// 此方法可以使新增的行和模板样式一样,但是新行赋值是会将上面行的row也修改了
// XWPFTableRow newRow = new XWPFTableRow(table.getRow(startRow).getCtRow(), table);
XWPFTableRow newRow = new XWPFTableRow(ctrow, table);
List<XWPFTableCell> cells = newRow.getTableCells();
for (XWPFTableCell cell : cells) {
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
writeDataToParagraph(paragraph, data);
}
}
table.addRow(newRow);
}
table.removeRow(startRow);
}
/**
* 向表格里写入多条数据
*
* @param table: 表格
* @param dataList: 数据列表
* @param startRow: 起始行索引,模板行
* @param type: 类名路径
* @author Michael
* @date 2024/1/25 10:24
* @return: void
**/
public void writeDataListToTable(XWPFTable table,
List<Object> dataList,
int startRow,
String type) throws Exception {
for (Object data : dataList) {
CTRow ctrow = CTRow.Factory.parse(table.getRow(startRow).getCtRow().newInputStream());
XWPFTableRow newRow = new XWPFTableRow(ctrow, table);
List<XWPFTableCell> cells = newRow.getTableCells();
for (XWPFTableCell cell : cells) {
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
writeDataToParagraph(paragraph, data, type);
}
}
table.addRow(newRow);
}
table.removeRow(startRow);
}
/**
* 向单元格里写入数据
*
* @param tableInx: 表格索引
* @param row: 行索引
* @param col: 列索引
* @param text: 文本
* @author Michael
* @date 2024/1/25 15:30
* @return: void
**/
public void writeDataToCell(int tableInx,
int row,
int col,
String text) throws Exception {
XWPFTable table = this.getTable(tableInx);
writeDataToCell(table, row, col, text);
}
/**
* 向单元格里写入数据
*
* @param table: 表格
* @param row: 行索引
* @param col: 列索引
* @param text: 文本
* @author Michael
* @date 2024/1/25 15:30
* @return: void
**/
public void writeDataToCell(XWPFTable table,
int row,
int col,
String text) throws Exception {
XWPFTableCell cell = getCell(table, row, col);
cell.setText(text);
}
/**
* 向单元格里插入图片
*
* @param tableInx: 表格索引
* @param row: 行索引
* @param col: 列索引
* @param imgPath: 图片路径
* @param width: 宽
* @param height: 高
* @author Michael
* @date 2024/1/26 8:57
* @return: void
**/
private void writePictureToCell(int tableInx,
int row,
int col,
String imgPath,
int width,
int height) throws Exception {
XWPFTable table = this.getTable(tableInx);
writePictureToCell(table, row, col, imgPath, width, height);
}
/**
* 向单元格里插入图片
*
* @param table: 表格
* @param row: 行索引
* @param col: 列索引
* @param imgPath: 图片路径
* @param width: 宽
* @param height: 高
* @author Michael
* @date 2024/1/26 8:57
* @return: void
**/
private void writePictureToCell(XWPFTable table,
int row,
int col,
String imgPath,
int width,
int height) throws Exception {
XWPFTableCell cell = getCell(table, row, col);
//获取图片
File image = new File(imgPath);
if (!image.exists()) {
throw new Exception("未发现图片!");
}
byte format;
if (imgPath.endsWith(".emf")) {
format = 2;
} else if (imgPath.endsWith(".wmf")) {
format = 3;
} else if (imgPath.endsWith(".pict")) {
format = 4;
} else if (!imgPath.endsWith(".jpeg") && !imgPath.endsWith(".jpg")) {
if (imgPath.endsWith(".png")) {
format = 6;
} else if (imgPath.endsWith(".dib")) {
format = 7;
} else if (imgPath.endsWith(".gif")) {
format = 8;
} else if (imgPath.endsWith(".tiff")) {
format = 9;
} else if (imgPath.endsWith(".eps")) {
format = 10;
} else if (imgPath.endsWith(".bmp")) {
format = 11;
} else {
if (!imgPath.endsWith(".wpg")) {
throw new Exception("图片格式不正确!");
}
format = 12;
}
} else {
format = 5;
}
//插入图片
List<XWPFParagraph> paragraphs = cell.getParagraphs();
XWPFParagraph newPara = paragraphs.get(0);
XWPFRun imageCellRun = newPara.createRun();
FileInputStream is = new FileInputStream(imgPath);
imageCellRun.addPicture(is, format, image.getName(), Units.toEMU(width), Units.toEMU(height));
}
/**
* 合并单元格
*
* @param tableInx: 表格索引
* @param startRow: 左上角单元格行索引
* @param startCol: 左上角单元格列索引,(0, 0)
* @param endRow: 右下角单元格行索引
* @param endCol: 右下角单元格列索引,(1, 1)
* @author Michael
* @date 2024/1/25 14:15
* @return: void
**/
public XWPFTableCell mergeCells(int tableInx,
int startRow,
int startCol,
int endRow,
int endCol) throws Exception {
XWPFTable table = this.getTable(tableInx);
return mergeCells(table, startRow, startCol, endRow, endCol);
}
/**
* 合并单元格
*
* @param table: 表格
* @param startRow: 左上角单元格行索引
* @param startCol: 左上角单元格列索引,(0, 0)
* @param endRow: 右下角单元格行索引
* @param endCol: 右下角单元格列索引,(1, 1)
* @author Michael
* @date 2024/1/25 14:15
* @return: void
**/
public XWPFTableCell mergeCells(XWPFTable table,
int startRow,
int startCol,
int endRow,
int endCol) throws Exception {
//插入的表格行使用此方法合并无效果
if (startRow == endRow) {
//直接纵向合并
table.getRow(startRow).getCell(startCol).getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);
table.getRow(endRow).getCell(endCol).getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);
} else {
//先横向合并
for (int i = startRow; i <= endRow; i++) {
table.getRow(i).getCell(startCol).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.RESTART);
table.getRow(i).getCell(endCol).getCTTc().addNewTcPr().addNewHMerge().setVal(STMerge.CONTINUE);
}
//再纵向合并
table.getRow(startRow).getCell(startCol).getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.RESTART);
table.getRow(endRow).getCell(startCol).getCTTc().addNewTcPr().addNewVMerge().setVal(STMerge.CONTINUE);
}
return getCell(table, startRow, startCol);
}
/**
* 保存并关闭文件
*
* @param path: 保存路径
* @param close: 是否关闭文件
* @author Michael
* @date 2024/1/25 9:57
* @return: void
**/
public void saveAndClose(String path,
boolean close) throws Exception {
FileOutputStream fo = null;
try {
if (isNullStr(path)) {
fo = new FileOutputStream(this.path);
} else {
//另存为
fo = new FileOutputStream(path);
}
//保存
this.docx.write(fo);
//关闭
if (close) {
close();
}
} catch (Exception e) {
close();
throw new Exception("保存失败!", e);
} finally {
if (fo != null) {
fo.close();
}
}
}
//替换文本,我叫${name} -> 我叫michael
private String replaceText(String str,
Map<String, String> map) throws Exception {
if (isNullStr(str)) {
return "";
}
//匹配占位符${name}
String resultStr = str;
Pattern pattern = Pattern.compile("\\$\\{.*?}");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
String group = matcher.group();
//匹配字段名
//此正则匹配的文本带{}
//Pattern fieldPattern = Pattern.compile("\\{.*?}");
Pattern fieldPattern = Pattern.compile("(?<=\\{)[^}]+");
Matcher fieldMatcher = fieldPattern.matcher(group);
String field = "";
if (fieldMatcher.find()) {
field = fieldMatcher.group();
}
//获取数据
String s = map.getOrDefault(field, "");
//数据替换占位符
resultStr = resultStr.replace(group, toNotNull(s));
}
return resultStr;
}
//替换文本,我叫${name} -> 我叫michael
private String replaceText(String str,
Object object,
String type) throws Exception {
if (isNullStr(str)) {
return "";
}
//匹配占位符${name}
String resultStr = str;
Pattern pattern = Pattern.compile("\\$\\{.*?}");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
String group = matcher.group();
//匹配字段名
Pattern fieldPattern = Pattern.compile("(?<=\\{)[^}]+");
Matcher fieldMatcher = fieldPattern.matcher(group);
String field = "";
if (fieldMatcher.find()) {
field = fieldMatcher.group();
}
//反射获取数据
Class<?> aClass = Class.forName(type);
String s = "";
if (!isNullStr(field) && ReflectionUtils.findField(aClass, field) != null) {
String methodStr = "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
Method method = aClass.getMethod(methodStr);
s = String.valueOf(method.invoke(object));
//数据替换占位符
resultStr = resultStr.replace(group, toNotNull(s));
}
}
return resultStr;
}
//将替换后的文本写入段落
private void writeText(XWPFParagraph paragraph,
String str) throws Exception {
boolean tag = false;
for (XWPFRun run : paragraph.getRuns()) {
if (tag) {
//清除多余的run
run.setText("", 0);
} else {
//要深入到run替换内容才能保证样式一致
run.setText(str, 0);
tag = true;
}
}
}
//获取单元格
private XWPFTableCell getCell(XWPFTable table,
int row,
int col) throws Exception {
XWPFTableRow tableRow = table.getRow(row);
return tableRow.getCell(col);
}
//判断字符串是否为空
private Boolean isNullStr(String str) {
return str == null || Objects.equals(str, "");
}
//判断字符串是否为空
private String toNotNull(String str) {
return str == null ? "" : str;
}
//关闭文件
private void close() throws Exception {
if (this.fi != null) {
this.fi.close();
this.fi = null;
}
}
}
附:
此工具类封装较为匆忙,若存在BUG或优化建议,欢迎评论交流。