目录
如果您也有类似的需求,可以参考这篇文章进行实现并扩展。
一、实现效果
1、重要说明:
①普通文本使用 ${字段名} 进行标注,有换行的文本使用 $${字段名} 进行标注。
②图片使用 #{字段名} 进行标注。
③${ }、$${ }、#{ } 相当于一个占位符,需要在其他地方编辑好然后一整块复制进来word文档,否则会出现不识别为一个整体的情况!!(建议在电脑版微信的聊天框里输入好后再复制到word文档,我是这样做的)
2、操作前:
3、操作后:
补充:为什么要特意加一类占位符,来表示有换行的字符串?
这是因为在Java里面字符串中的 \n ,插入word时显示的效果只是有一个空格。
有换行的文本在word里面使用 $${字段名} 标注,在Java代码里面跟普通文本一样,Map的key值使用 ${字段名} 即可,这是为了方便用工具类生成替换文本的Map时,不需要特殊处理。
二、实现部分
1、导入依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.16</version>
</dependency>
2、工具类
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import javax.imageio.ImageIO;
public class WordUtil {
/**
* 用于直接根据对象生成需要替换的文本Map:${属性名}和值
*
* @param obj
* @return
*/
public static Map<String, String> getTextFieldMap(Object obj) {
Map<String, String> map = new HashMap<>();
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
// 如果字段是私有的,也需要访问
field.setAccessible(true);
try {
String value = (String) field.get(obj);
map.put("${" + field.getName() + "}", Objects.nonNull(value) ? value : " ");
} catch (IllegalAccessException e) {
System.out.println("字段解析失败:" + e);
}
}
return map;
}
/**
* 根据模板生成新word文档
*
* @param inputUrl 模板存放地址
* @param outputUrl 新文档存放地址
* @param textMap 需要替换的文本信息集合
* @param imgMap 需要替换的图片路径集合
*/
public static void wordToNewWord(String inputUrl, String outputUrl, Map<String, String> textMap,
Map<String, List<String>> imgMap) throws Exception {
if (Objects.isNull(textMap)) {
textMap = new HashMap<>();
}
if (Objects.isNull(imgMap)) {
imgMap = new HashMap<>();
}
try (FileInputStream fileInputStream = new FileInputStream(inputUrl)) {
XWPFDocument document = new XWPFDocument(fileInputStream);
// 解析替换文本段落对象
WordUtil.changeParagraphs(document.getParagraphs(), textMap, imgMap);
// 解析替换表格对象
WordUtil.changeTables(document.getTables(), textMap, imgMap);
// 生成新的word
try (FileOutputStream stream = new FileOutputStream(outputUrl)) {
document.write(stream);
}
}
}
/**
* 解析所有段落,将占位符替换成具体的文本/图片
*
* @param paragraphs 所有表格集合
* @param textMap 需要替换的文本集合
* @param imgMap 需要替换的图片路径(本地)集合
* @throws Exception
*/
public static void changeParagraphs(List<XWPFParagraph> paragraphs,
Map<String, String> textMap, Map<String, List<String>> imgMap) throws Exception {
for (XWPFParagraph paragraph : paragraphs) {
String text = paragraph.getText();
// 检查是否需要替换
if (!containsTextPlaceholder(text) && !containsImgPlaceholder(text)) {
continue;
}
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
// 是否需要:占位符 $${}-->有换行的文本
if (textMap.containsKey(run.toString().substring(1))) {
changeRun(run, textMap.get(run.toString().substring(1)));
}
// 是否需要:占位符 ${}-->文本
if (textMap.containsKey(run.toString())) {
run.setText(textMap.get(run.toString()), 0);
}
// 是否需要:占位符 #{}-->图片
if (imgMap.containsKey(run.toString())) {
setCellImg(run, imgMap.get(run.toString()));
}
}
}
}
/**
* 解析所有表格,将占位符替换成具体的文本/图片
*
* @param tables 所有表格集合
* @param textMap 需要替换的文本集合
* @param imgMap 需要替换的图片路径(本地)集合
* @throws Exception
*/
public static void changeTables(List<XWPFTable> tables,
Map<String, String> textMap,
Map<String, List<String>> imgMap) throws Exception {
// 获取表格对象集合
for (XWPFTable table : tables) {
// 判断表格内容是否需要替换
if (!containsTextPlaceholder(table.getText()) && !containsImgPlaceholder(table.getText())) {
continue;
}
// 需要替换,则遍历表格
List<XWPFTableRow> rows = table.getRows();
for (XWPFTableRow row : rows) {
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
// 判断单元格是否需要替换
if (!containsTextPlaceholder(cell.getText()) && !containsImgPlaceholder(cell.getText())) {
continue;
}
List<XWPFParagraph> paragraphs = cell.getParagraphs();
changeParagraphs(paragraphs, textMap, imgMap);
}
}
}
}
/**
* 处理有换行的文本
*
* @param run
* @param text
* @throws Exception
*/
private static void changeRun(XWPFRun run, String text) throws Exception {
run.setText("", 0);
// 如果内容为空,则插入TAB占位
if (text == null) {
run.addTab();
}
// 处理有换行的内容
String[] texts = text.split("\n");
for (int i = 0; i < texts.length-1; i++){
run.setText(texts[i]);
run.addCarriageReturn();
}
// 最后一个不需要换行
run.setText(texts[texts.length-1]);
}
/**
* 批量插入多张图片,一张一行
*
* @param run
* @param urls
* @throws Exception
*/
private static void setCellImg(XWPFRun run, List<String> urls) throws Exception {
// 清空占位符内容
run.setText("", 0);
// 数字格式化器:保留0位小数、向上取整
NumberFormat numberFormat = NumberFormat.getNumberInstance();
numberFormat.setMaximumFractionDigits(0);
numberFormat.setRoundingMode(RoundingMode.UP);
if (Objects.isNull(urls) || urls.isEmpty()) {
return;
}
// 循环插入图片
for (String path : urls) {
File imageFile = new File(path);
//判断图片是否存在
if (!imageFile.exists()) {
continue;
}
// 获取图片格式
int format = getImageFormat(path);
if (format == 0) {
continue;
}
// 读取图片
try (FileInputStream is = new FileInputStream(imageFile)) {
// 计算适合文档宽高的图片EMU数值
BufferedImage read = ImageIO.read(imageFile);
int width = Units.toEMU(read.getWidth());
int height = Units.toEMU(read.getHeight());
// 1 EMU = 1/914400英寸= 1/36000 mm,15是word文档中图片能设置的最大宽度cm
double scaleFactor = width / 360000.0 / 14.5d;
width = Integer.parseInt(numberFormat.format(width / scaleFactor).replace(",", ""));
height = Integer.parseInt(numberFormat.format(height / scaleFactor).replace(",", ""));
// 插入图片
run.addPicture(is, format, imageFile.getName(), width, height);
}
// 换行
run.addBreak();
}
}
/**
* 获取图片格式
*
* @param path 图片路径
* @return 图片格式枚举数字
*/
private static int getImageFormat(String path) {
int format = 0;
if (path.endsWith(".emf")) {
format = XWPFDocument.PICTURE_TYPE_EMF;
} else if (path.endsWith(".wmf")) {
format = XWPFDocument.PICTURE_TYPE_WMF;
} else if (path.endsWith(".pict")) {
format = XWPFDocument.PICTURE_TYPE_PICT;
} else if (path.endsWith(".jpeg") || path.endsWith(".jpg")) {
format = XWPFDocument.PICTURE_TYPE_JPEG;
} else if (path.endsWith(".png")) {
format = XWPFDocument.PICTURE_TYPE_PNG;
} else if (path.endsWith(".dib")) {
format = XWPFDocument.PICTURE_TYPE_DIB;
} else if (path.endsWith(".gif")) {
format = XWPFDocument.PICTURE_TYPE_GIF;
} else if (path.endsWith(".tiff")) {
format = XWPFDocument.PICTURE_TYPE_TIFF;
} else if (path.endsWith(".eps")) {
format = XWPFDocument.PICTURE_TYPE_EPS;
} else if (path.endsWith(".bmp")) {
format = XWPFDocument.PICTURE_TYPE_BMP;
} else if (path.endsWith(".wpg")) {
format = XWPFDocument.PICTURE_TYPE_WPG;
}
return format;
}
/**
* 判断文本中是否包含: ${
*/
private static boolean containsTextPlaceholder(String text) {
return text.contains("${");
}
/**
* 判断文本中是否包含: #{
*/
private static boolean containsImgPlaceholder(String text) {
return text.contains("#{");
}
}
3、实体类
public class User {
private String name;
private String gender;
private String phone;
private String address;
private String province;
private String city;
public User(String name, String gender, String phone, String address, String province, String city) {
this.name = name;
this.gender = gender;
this.phone = phone;
this.address = address;
this.province = province;
this.city = city;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
4、测试代码
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TestWordUtil {
@Test
public void test() {
String inputUrl = "D:\\test-poi-word.docx";
String outputUrl = "D:\\new-poi-word.docx";
User user = new User("张三", "男", "12345678901", "广东省广州市\n这是具体地址", "广东省", "广州市");
// 使用工具类生成字段与值的映射,也可以自己手动写,对于字段多的这样比较方便
Map<String, String> textMap = WordUtil.getTextFieldMap(user);
// 需要插入的图片
Map<String, List<String>> imgMap = new HashMap<>();
imgMap.put("#{image}", List.of("D:\\test-poi-word1.jpeg","D:\\test-poi-word2.jpg"));
try {
WordUtil.wordToNewWord(inputUrl, outputUrl, textMap, imgMap);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}