前言
最近公司有业务需求要求替换模板并导出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使用,最后感谢您的阅读。