使用 Apache POI 实现 Java Word 模板占位符替换功能的方法和一些坑 使用 Apache POI 实现 Java Word 模板占位符替换功能的方法和一些坑

使用 Apache POI 实现 Java Word 模板占位符替换功能的方法和一些坑

使用场景

在日常开发中,我们经常会遇到生成 [Word 文档](https://so.csdn.net/so/search?q=Word 文档&spm=1001.2101.3001.7020)的需求,特别是在需要从模板导出 Word 文件时,比如生成合同、报告等。通过使用模板,开发者可以减少重复的工作,将预定义的占位符替换为实际的数据,生成定制化的 Word 文件。本文将介绍如何使用 Apache POI 库实现 Java 程序中的 Word 模板占位符替换功能,并最终导出定制化的 Word 文件。

开始使用

合同模板示例

前端(vue)数据示例

contractData: {
 mcc:'中文名',
 mce:'yingwen',
 jcc:'中简',
 jce:'yingjian',
 dz:'地址',
 llr:'联络人甲方',
 zw:'植物',
 wz:'网址',
 dh:'电话',
 dy:'电邮甲方',
 llr2:'联络人乙方',
 sj:'手机',
 dy2:'电邮乙方'
}

后端代码

后端maven中pom.xml配置,引入依赖

 org.apache.poi
 poi-ooxml
 5.2.3
 org.apache.poi
 poi
 5.2.3
资源文件编译pom.xml配置

 src/main/resources
 false
 
 **/*.docx
 

如果资源文件不是从resources中获取可以不用管这一块。

读取并遍历文件
import com.cust.trafficsupervise.common.utils.DataResult;
import org.apache.poi.xwpf.usermodel.*;
import org.springframework.web.bind.annotation.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
@RestController
@RequestMapping("/gethetong")
public class hetong {
 @PostMapping("/generateContract")
 //DataResult是封装好的返回结果类,根据实际业务更改对应的返回结果的封装类或者直接删除
 //contractData是前端传过来的数据,最好封装为实体接收。
 //业务逻辑应在service层完成,此处为了简便直接编写在controller层。
 public DataResult generateContract(@RequestBody Map contractData) {
 try {
 // 加载资源文件夹resources/tem文件夹下的合同模板
 //不在resources获取模板文件需要修改此处,通过这个方法只能获取resources中的资源文件
 InputStream templateInputStream = getClass().getClassLoader().getResourceAsStream("tem/x.docx");
 if (templateInputStream == null){
 return DataResult.error();
 }
 XWPFDocument document = new XWPFDocument(templateInputStream);
 // 遍历文档中的段落
 for (XWPFParagraph paragraph : document.getParagraphs()) {
 if (paragraph == null || paragraph.getRuns().size() == 0){
 continue;
 }
 //替换占位符
 change(paragraph,contractData);
 }
 // 遍历文档中的表格
 for (XWPFTable table : document.getTables()) {
 for (XWPFTableRow row : table.getRows()) {
 for (XWPFTableCell cell : row.getTableCells()) {
 // 遍历单元格中的段落
 for (XWPFParagraph cellParagraph : cell.getParagraphs()) {
 //替换占位符
 change(cellParagraph,contractData);
 }
 }
 }
 }
 // 遍历文档中的图片
 for (XWPFPictureData picture : document.getAllPictures()) {
 // 图片不会被修改,直接跳过
 //需要修改图片在此处
 System.out.println("图片: " + picture.getFileName());
 }
 //该路径为相对路径,默认创建保存位置为项目文件所在盘符的根目录下
 String path = "/file";
 // 保存填充后的合同(将毫秒数添加到文件名防止命名冲突)
 String filePath = path + "/contract_" + System.currentTimeMillis() + ".docx";
 //检查目录是否存在,不存在则创建
 File directory = new File(path);
 if (!directory.exists()) {
 boolean created = directory.mkdirs();
 if (!created) {
 throw new IOException("创建目录失败: " + path);
 }
 }
 // 使用 FileOutputStream 保存填充后的合同
 try (FileOutputStream out = new FileOutputStream(filePath)) {
 document.write(out); // 将内容写入文件
 }
	// 返回合同文件路径,将保存的文件路径传给前端即可,前端拿到路径下载文件。
 return DataResult.success(filePath); 
 
 } catch (Exception e) {
 e.printStackTrace();
 return DataResult.error("错误");
 }
 }
替换占位符工具类(change方法)
//替换占位符方法
 public static void change(XWPFParagraph paragraph,Map contractData){
 for (XWPFRun run : paragraph.getRuns()) {
 // 获取当前 run 的文本
 String runText = run.getText(0);
 if (runText == null){
 continue;
 }
 StringBuilder newText = new StringBuilder(runText);
 // 替换占位符
 for (Map.Entry entry : contractData.entrySet()) {
 //此处的占位符“{{”和“}}”可以任意更改,合同模板文件随着替换即可。
 String placeholder = "{{" + entry.getKey() + "}}";
 int startIndex = newText.indexOf(placeholder);
 while (startIndex != -1) {
 newText.replace(startIndex, startIndex + placeholder.length(), entry.getValue());
 startIndex = newText.indexOf(placeholder, startIndex + entry.getValue().length());
 }
 }
 // 更新替换后的文本
 run.setText(newText.toString(), 0);
 }
 }

问题,坑

文档中不同内容格式的处理

文档中段落、表格、图片等文字格式都是需要单独处理的,通过不同的对象接收,如果不进行接收处理则会丢失。导致文档不一致且格式发生变化。

POI版本和第三方软件导致的run划分问题

在POI3.9版本之前一个段落就是一个run,3.10之后获取到的一个段落会被不知什么规则分成一个List数组中有多个run,导致目标表占位符被分割

例如:合同名称(中文):{{mcc}} 被分割成:合同名称(中文):{{mcc}},如果对于每个run进行查找替换就会导致识别不到预设占位符**{{mcc}}**

解决方法

添加占位符时

因为合同模板一旦确认一般情况下不会更改和变动,所以在对合同模板添加占位符时候,将占位符整个( 例**{{mcc}}** )一口气输入完后保存(未保存可退格)。

如果中途保存,例如:输入 {{mcc 后保存,继续输入 }} 则会出现被划分到多个run中的情况。

如果输入错误保存后重新修改,例如:输入 {{mcc}$ 保存后发现输入错误,重新修改需要整个删除重新输入,或者修改成 {{mcc}} 后ctrl+x剪切重新整体粘贴。

将run合并

将分成多个run的List重新合并在一起,removeRun貌似可以,但是不确定格式是否会丢失,没进行尝试,可以参考文章:
POI不同版本替换Word模板时的问题 - yujj_cn - 博客园

作者:会飞的鱼^原文地址:https://blog.csdn.net/m0_73985202/article/details/145176313

%s 个评论

要回复文章请先登录注册