场景
在开发工作中可能会遇到一种需求就是需要把数据库或者动态的把字段值去把某个word文档中定义好的通配符给替换掉,比如签署一个文件模板,有些产品名称或者姓名都可以让系统给动态赋值上去等
那么开始直接上代码:
1.导入相关依赖 Apache POI
<!-- Apache POI -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
使用 XWPFDocument 去处理.docx文件,使用 HWPFDocument 去处理.doc文件
(word的老版本新版本处理方式不同)
2.具体实现:
import cn.sckr.overseas.funds.common.base.constant.HttpStatus;
import cn.sckr.overseas.funds.common.base.utils.exception.BusinessException;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.CharacterRun;
import org.apache.poi.hwpf.usermodel.Range;
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 java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* @author yfq
*/
public class TemplateReplaceUtil {
/**
* XWPFParagraph:处理文档段落相关的
* XWPFTable:处理表格相关的
* XWPFPicture :处理图片相关的
* XWPFSDT:代表Structured Document Tag(SDT),这是一种结构化文档标记,可以包含模板控件、元数据等。
* XWPFChart:代表图表,即插入在 Word 文档中的图表对象。
* XWPFFootnote:代表脚注,表示文档中的脚注内容。
* XWPFHyperlinkRun:代表超链接文本,表示文档中的超链接。
* XWPFFooter 和 XWPFHeader:分别代表文档的页脚和页眉。
* XWPFFootingPart 和 XWPFHeaderPart:这些类是页脚和页眉的具体部分,它们包含了实际的内容。
* XWPFAbstractNum 和 XWPFFootnote:用于处理编号和脚注的抽象类。
* @param bytes 文件的字节数组
* @param placeholderMap 需要替换的键值对 key为通配符,value为要把通配符替换的值 如:key:'&*',value:'名字',就是把word中的 &* 替换成 名字
* @return InputStream
*/
public static InputStream selectCheckboxByPlaceholder(byte[] bytes, HashMap<Object, Object> placeholderMap) throws IOException {
try {
InputStream inputStream = new ByteArrayInputStream(bytes);
// 使用 try-with-resources 确保资源关闭
try (InputStream stream = inputStream) {
// 尝试使用 XWPFDocument 处理
try {
XWPFDocument document = new XWPFDocument(stream);
processDocument(document, placeholderMap);
// 将修改后的文档保存回Word文件
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
document.write(byteArrayOutputStream);
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
} catch (Exception xwpfException) {
// 如果 XWPFDocument 处理失败,尝试使用 HWPFDocument 处理
try {
HWPFDocument document = new HWPFDocument(stream);
processDocument(document, placeholderMap);
// 将修改后的文档保存回Word文件
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
document.write(byteArrayOutputStream);
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
} catch (Exception hwpfException) {
throw new BusinessException(HttpStatus.ERROR, "填充失败");
}
}
}
} catch (IOException e) {
throw new BusinessException(HttpStatus.ERROR, "填充失败");
}
}
private static void processDocument(XWPFDocument document, HashMap<Object, Object> placeholderMap) {
// 遍历段落和运行,查找和替换占位符
for (XWPFParagraph paragraph : document.getParagraphs()) {
for (XWPFRun run : paragraph.getRuns()) {
String text = run.getText(0);
for (Map.Entry<Object, Object> entry : placeholderMap.entrySet()) {
if (text != null && text.contains(String.valueOf(entry.getKey()))) {
// 用自定义字符表示选中状态替换占位符
text = text.replace(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
run.setText(text, 0);
}
}
}
}
// 遍历所有table
for (XWPFTable table : document.getTables()) {
for (XWPFTableRow row : table.getRows()) {
for (XWPFTableCell cell : row.getTableCells()) {
for (XWPFParagraph paragraph : cell.getParagraphs()) {
for (XWPFRun run : paragraph.getRuns()) {
String text = run.getText(0);
for (Map.Entry<Object, Object> entry : placeholderMap.entrySet()) {
if (text != null && text.contains(String.valueOf(entry.getKey()))) {
// 用自定义字符表示选中状态替换占位符
text = text.replace(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
run.setText(text, 0);
}
}
}
}
}
}
}
}
private static void processDocument(HWPFDocument document, HashMap<Object, Object> placeholderMap) {
Range range = document.getRange();
// 遍历段落和运行,查找和替换占位符
for (int i = 0; i < range.numParagraphs(); i++) {
org.apache.poi.hwpf.usermodel.Paragraph paragraph = range.getParagraph(i);
for (int j = 0; j < paragraph.numCharacterRuns(); j++) {
CharacterRun run = paragraph.getCharacterRun(j);
String text = run.text();
for (Map.Entry<Object, Object> entry : placeholderMap.entrySet()) {
if (text != null && text.contains(String.valueOf(entry.getKey()))) {
// 用自定义字符表示选中状态替换占位符
text = text.replace(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
run.replaceText(text,true);
}
}
}
}
}
3.代码解释
4.测试代码(因为这是业务需求,其实可以入参就是一个文件路径不必是byte[],处理完可以直接输出去)
public static void main(String[] args) throws FileNotFoundException {
String inputWordPath = "D:/test/test.docx";
String outputWordPath = "D:/test/test1.docx";
HashMap<Object, Object> placeholderMap = new HashMap<>();
placeholderMap.put("&*",'☑');
placeholderMap.put("&#",'☐');
placeholderMap.put("#{name}","???????????????");
placeholderMap.put("#{code}","11111111111111");
File file = new File(inputWordPath);
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bytes = new byte[(int) file.length()];
try {
InputStream inputStream = selectCheckboxByPlaceholder(bytes, placeholderMap);
FileOutputStream fileOutputStream = new FileOutputStream(outputWordPath);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
inputStream.close();
byteArrayOutputStream.close();
byte[] bytess = byteArrayOutputStream.toByteArray();
fileOutputStream.write(bytess);
// saveModifiedDocument(inputWordPath, outputWordPath);
} catch (IOException e) {
throw new RuntimeException(e);
}
}