关于docx文件字符串替换导出

43 阅读3分钟

前言

最近公司有业务需求要求替换模板并导出docx文件。但是在网上并没有找到完整的后端和前端的代码,经过自己的查找和大量的询问AI后总结出一个还算可以的版本,故在此分享出来,此代码可以简单批量替换docx中的占位符并导出,不受换行的影响。

正文

后端代码

 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 public class WordTemplateReplacer {
     public static void main(String[] args) {
         try {
             // 模板文件路径
             FileInputStream fis = new FileInputStream("模版.docx");
             // 输出文件路径
             FileOutputStream fos = new FileOutputStream("output.docx");
 ​
             // 创建XWPFDocument对象
             XWPFDocument document = new XWPFDocument(fis);
 ​
             // 定义一个Map来存放替换的键值对
             Map<String, String> replacements = new HashMap<>();
             replacements.put("name", "haha");
 ​
             Pattern pattern = Pattern.compile("\{(.+?)\}");
 ​
             // 新增:合并相邻的运行,以避免因分隔导致的替换不全
             mergeAdjacentRuns(document);
 ​
             // 遍历文档中的所有段落
             for (XWPFParagraph paragraph : document.getParagraphs()) {
                 for (XWPFRun run : paragraph.getRuns()) {
                     String text = run.getText(0);
                     if (text != null) {
                         Matcher matcher = pattern.matcher(text);
                         StringBuffer sb = new StringBuffer();
                         while (matcher.find()) {
                             String placeholderWithoutBraces = matcher.group(1);
                             if (replacements.containsKey(placeholderWithoutBraces)) {
                                 matcher.appendReplacement(sb, replacements.get(placeholderWithoutBraces));
                             } else {
                                 matcher.appendReplacement(sb, matcher.group());
                             }
                         }
                         matcher.appendTail(sb);
                         run.setText(sb.toString(), 0);
                     }
                 }
             }
 ​
             document.write(fos);
             fos.close();
             fis.close();
             document.close();
 ​
             System.out.println("Word文档处理完成并已保存。");
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 ​
     /**
      * 合并段落中的相邻XWPFRun,以减少因文本分隔导致的占位符匹配问题。
      * 注意:这可能会影响原始文档中的格式设置。
      */
     private static void mergeAdjacentRuns(XWPFDocument document) {
         for (XWPFParagraph paragraph : document.getParagraphs()) {
             List<XWPFRun> runs = paragraph.getRuns();
             if (runs.size() > 1) {
                 // 从第二个运行开始,将其文本合并到前一个运行中
                 for (int i = 1; i < runs.size(); i++) {
                     XWPFRun currentRun = runs.get(i);
                     String text = currentRun.getText(0);
                     if (text != null) {
                         runs.get(i - 1).setText(runs.get(i - 1).getText(0) + text, 0);
                     }
                     // 移除当前运行,因为它已被合并
                     paragraph.removeRun(i);
                     // 由于移除元素,需要调整索引
                     i--;
                 }
             }
         }
     }
 }
 @PostMapping(value = "/exportDOCX")
 public Boolean exportDOCX(@RequestBody DTO dto, HttpServletRequest request, HttpServletResponse response) {
     // 处理数据
     Map<String, Object> map =service.getMap(dto.getID());
     service.exportDOCX((HashMap<String, Object>) map, request, response);
     return true;
 }
 @Override
 public void exportAnalysisReportDOCX(HashMap<String, Object> params, HttpServletRequest request,HttpServletResponse response) {
     WordUtils.writeWord(response, params, fileName);
 }
 import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
 import org.apache.poi.xwpf.usermodel.XWPFParagraph;
 import org.apache.poi.xwpf.usermodel.XWPFRun;
 import org.springframework.http.HttpHeaders;
 ​
 import javax.servlet.http.HttpServletResponse;
 import java.io.*;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 ​
 @Slf4j
 public class WordUtils {
     private final static String DOC = "doc";
     private final static String DOCX = "docx";
 ​
     // 模板地址
     private final static String PATH = "file.docx";
     private static final Pattern PATTERN = Pattern.compile("\{(.+?)\}");
     /**
      * 导出word
      */
     public static <T> void  writeWord(HttpServletResponse response, Map<String, Object> replacements, String fileName){
 ​
         XWPFDocument document = null;
         FileInputStream fis = null;
         OutputStream fos = null;
         ByteArrayOutputStream bos = null;
         try {
             // 模板文件路径
             fis = new FileInputStream(PATH);
             bos = new ByteArrayOutputStream();
             // 创建XWPFDocument对象
             document = new XWPFDocument(fis);
 ​
             fos = getOutputStream(response, fileName);
 ​
             // 新增:合并相邻的运行,以避免因分隔导致的替换不全
             mergeAdjacentRuns(document);
             // 遍历文档中的所有段落
             for (XWPFParagraph paragraph : document.getParagraphs()) {
                 for (XWPFRun run : paragraph.getRuns()) {
                     String text = run.getText(0);
                     if (text != null) {
                         Matcher matcher = PATTERN.matcher(text);
                         StringBuffer sb = new StringBuffer();
                         while (matcher.find()) {
                             String placeholderWithoutBraces = matcher.group(1);
                             if (replacements.containsKey(placeholderWithoutBraces)) {
                                 matcher.appendReplacement(sb, String.valueOf(replacements.get(placeholderWithoutBraces)));
                             } else {
                                 matcher.appendReplacement(sb, matcher.group());
                             }
                         }
                         matcher.appendTail(sb);
                         run.setText(sb.toString(), 0);
                     }
                 }
             }
             document.write(bos);
             // 获取字节数据
             byte[] bytes = bos.toByteArray();
 ​
             // 设置内容长度
             response.setContentLength(bytes.length);
             // 写入字节数据到响应输出流
             fos.write(bytes);
 ​
             log.info("Word文档处理完成并已保存。");
 ​
         } catch (IOException e) {
             e.printStackTrace();
         }finally {
             if(fos!=null){
                 try {
                     fos.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
             if(fis!=null){
                 try {
                     fis.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
             if(document!=null){
                 try {
                     document.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
             if(bos!=null){
                 try {
                     bos.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }
     }
 ​
     /**
      * 合并段落中的相邻XWPFRun,以减少因文本分隔导致的占位符匹配问题。
      * 注意:这可能会影响原始文档中的格式设置。
      */
     private static void mergeAdjacentRuns(XWPFDocument document) {
         for (XWPFParagraph paragraph : document.getParagraphs()) {
             List<XWPFRun> runs = paragraph.getRuns();
             if (runs.size() > 1) {
                 // 从第二个运行开始,将其文本合并到前一个运行中
                 for (int i = 1; i < runs.size(); i++) {
                     XWPFRun currentRun = runs.get(i);
                     String text = currentRun.getText(0);
                     if (text != null) {
                         runs.get(i - 1).setText(runs.get(i - 1).getText(0) + text, 0);
                     }
                     // 移除当前运行,因为它已被合并
                     paragraph.removeRun(i);
                     // 由于移除元素,需要调整索引
                     i--;
                 }
             }
         }
     }
 ​
     /**
      * 导出时生成OutputStream
      */
     private static OutputStream getOutputStream(HttpServletResponse response, String fileName) {
         fileName = fileName + "." + DOCX;
         try {
             fileName = java.net.URLEncoder.encode(fileName, "UTF-8");
 ​
             // 设置响应头,指示浏览器这是一个文件下载响应
             response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
 //            response.setHeader("Content-Disposition", "attachment; filename=export.docx");
             response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
             return response.getOutputStream();
         } catch (IOException e) {
             log.info("导出docx失败", e);
         }
         return null;
     }
 }

前端代码

其他的部分与导出excel类似

 if (res.status == 200) {
     // 创建一个Blob对象,包含返回的二进制数据
     const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
 ​
     // 创建一个临时的URL表示这个Blob对象
     const url = window.URL.createObjectURL(blob);
 ​
     // 创建隐藏的可下载链接
     const a = document.createElement('a');
     a.href = url;
 ​
     const fileName = res.headers['content-disposition'].split('=')[1];
     a.download = decodeURIComponent(fileName);
 ​
     document.body.appendChild(a);
     a.click();
 ​
     // 清理
     window.URL.revokeObjectURL(url);
     document.body.removeChild(a);
 } else {
     message.error("下载失败");
 }

后记

笔者的需求也只是匹配字符串替换带出,如果读者的需求更为复杂,可能需要在writeWord代码中进一步修改,建议搭配AI使用,最后感谢您的阅读。