docx格式的文件本质上是一个XML文件,只要用占位符在指定的地方标记,然后替换掉标记出的内容,就能达到我们的目的
我们把maven项目的resources目录下的test.docx文件像上图一样用{{name}}和{{content}}打了2处标记,当然你也可以用其他的标记,我们的目的是用对应的内容替换掉标记处的内容。
话不多说,直接看代码(代码写的很粗糙,只是提供一个思路)
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* Author Da
* Description: <br/>
* 三十年生死两茫茫,写程序,到天亮。
* 千行代码,Bug何处藏。
* 纵使上线又怎样,朝令改,夕断肠。
* 领导每天新想法,天天改,日日忙。
* 相顾无言,惟有泪千行。
* 每晚灯火阑珊处,夜难寐,又加班。
* Date: 2022-05-05
* Time: 11:25
*/
public class App {
public static void main(String[] args) throws IOException {
// 解压docx文件
unzip("test.docx");
// 获取解压出来的word文件中的document.xml文件
Map<String, Object> data = new HashMap<>();
data.put("name", "你好");
data.put("content", "我为什么这么帅");
replaceDocAndWrite("test", data);
// 把换好后的文件压缩回去
zip("test");
System.out.println("生成成功!");
}
// 把文件压缩回.docx文件
private static void zip(String zipPath) throws IOException {
System.out.println("把替换好后的文件重新压缩为.docx");
String root = System.getProperty("user.dir") + File.separator + zipPath;
// 压缩后文件保存的地方
FileOutputStream fos = new FileOutputStream(root + ".docx");
ZipOutputStream zos = new ZipOutputStream(new DataOutputStream(fos));
// 获取要压缩的文件
Path path = Paths.get(root);
Files.walkFileTree(path, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
// 获取当前文件的父目录的名字
String parent = file.getParent().getFileName().toString();
// 压缩文件的名字
String zipFileName = "";
// 如果父目录是根目录名字就是当前的名字否则就是夫目录加上当前的名字
if (parent.equals(path.toFile().getName())) {
zipFileName = file.toFile().getName();
} else {
zipFileName = parent + File.separator + file.toFile().getName();
}
// 把文件添加到压缩包中
zos.putNextEntry(new ZipEntry(zipFileName));
// 获取当前文件的输入流
DataInputStream zis = new DataInputStream(new FileInputStream(file.toFile()));
// 写入内容到当前的压缩文件
int len;
byte[] buff = new byte[1024 * 10];
while ((len = zis.read(buff)) != -1) {
zos.write(buff, 0, len);
}
zis.close();
return super.visitFile(file, attrs);
}
});
zos.close();
fos.close();
// 删除解压的文件目录
deleteZip(path.toFile());
}
// 删除临时解压的文件目录
private static void deleteZip(File zipPath) {
// 先删文件夹里面的文件
for (File file : Objects.requireNonNull(zipPath.listFiles())) {
// 是文件夹就递归调用
if (file.isDirectory()) {
deleteZip(file);
} else {
// 是文件就直接删
file.delete();
}
}
// 删掉自己
zipPath.delete();
}
// 替换document.xml中对应的内容
private static void replaceDocAndWrite(String zipPath, Map<String, Object> data) throws IOException {
// 解压出来的document.xml文件的位置
String doc = System.getProperty("user.dir") + File.separator + zipPath + File.separator + "word" + File.separator + "document.xml";
FileInputStream is = new FileInputStream(doc);
// 一次读取完document.xml的内容
int length = is.available();
byte[] buff = new byte[length];
is.read(buff);
String content = new String(buff);
// 遍历数据集,替换
for (String key : data.keySet()) {
String value = data.get(key).toString();
System.out.println("替换" + key + "处的内容为: " + value);
content = content.replaceAll("\\{\\{" + key + "\\}\\}", value);
}
FileOutputStream os = new FileOutputStream(doc);
os.write(content.getBytes(StandardCharsets.UTF_8));
os.close();
is.close();
}
// 解压缩
private static void unzip(String path) throws IOException {
// 当前项目的根路径
final String root = System.getProperty("user.dir");
// 压缩包的路径
String zipPath = root + File.separator + path.split("\\.")[0];
// 解压.docx文件后存放的目录
File cacheDir = new File(zipPath);
if (!cacheDir.exists()) {
Files.createDirectory(Paths.get(cacheDir.getPath()));
}
// 获取要解压的.docx文件
FileInputStream is = new FileInputStream("src/main/resources/" + path);
// 压缩包输入流
ZipInputStream zis = new ZipInputStream(is);
// 获取第一个要解压的文件
ZipEntry entry = zis.getNextEntry();
// 内容缓冲区
byte[] buff = new byte[2048];
// 循环解压
while (entry != null) {
// 判断当前的压缩文件是不是文件
if (!entry.isDirectory()) {
System.out.println("解压出文件 => " + entry.getName());
// 当前要解压的文件
File file = new File(cacheDir, entry.getName());
FileOutputStream fos = new FileOutputStream(file);
// 写入文件
int len;
while ((len = zis.read(buff)) != -1) {
fos.write(buff, 0, len);
}
fos.close();
} else {
// 压缩包中的目录
Path tempDir = Paths.get(zipPath + File.separator + entry.getName());
// 不存在就创建
if (!Files.exists(tempDir)) {
Files.createDirectory(tempDir);
}
}
// 获取下一个文件
entry = zis.getNextEntry();
}
System.out.println("解压完成");
zis.close();
is.close();
}
}
替换结果
封装成工具类
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* Author Da
* Description: <br/>
* 三十年生死两茫茫,写程序,到天亮。
* 千行代码,Bug何处藏。
* 纵使上线又怎样,朝令改,夕断肠。
* 领导每天新想法,天天改,日日忙。
* 相顾无言,惟有泪千行。
* 每晚灯火阑珊处,夜难寐,又加班。
* Date: 2022-05-05
* Time: 15:44
*/
//替换.docx文件的工具类
public class DocxUtil {
// 保存随机生的解压出.docx文件的保存目录
private static final String cachePath = System.getProperty("user.dir") + File.separator + UUID.randomUUID().toString().replaceAll("-", "");
// 保存模板文件的输入流和模板压缩输入流
private static final List<InputStream> iss = new ArrayList<>();
// 替换docx文件中的内容
public static void compile(FileInputStream is, Map<String, String> data, FileOutputStream os) throws IOException {
// 获取模板保存的目录
Path path = Paths.get(cachePath);
// 如果不存在或者当前的模板输入流不在输入流集合中就解压模板
if (!Files.exists(path) || !iss.contains(is)) {
// 解压docx文件
unDocx(is);
}
// 替换document.xml的内容
replaceXmlContent(data);
// 把解压的文件重新压缩成docx文件
buildDocx(os);
}
// 解压docx文件
private static void unDocx(FileInputStream template) throws IOException {
// 解压出来文件保存的目录
File cacheDir = new File(cachePath);
if (!cacheDir.exists()) {
// 不存在就先创建该文件夹
Files.createDirectory(Paths.get(cacheDir.getPath()));
}
// 创建压缩包输入流
ZipInputStream zis = new ZipInputStream(template);
// 把需要关闭的模板输入流存到集合中
iss.add(template);
iss.add(zis);
// 获取首个压缩文件中的文件
ZipEntry entry = zis.getNextEntry();
// 内容缓冲区
byte[] buff = new byte[1024 * 10];
// 循环获取压缩包中的压缩文件
while (entry != null) {
// 判断当前的压缩文件是文件夹还是文件
if (entry.isDirectory()) {
// 如果是文件夹就要创建对应的文件夹
Path tempDir = Paths.get(cacheDir.getPath() + File.separator + entry.getName());
// 不存在就创建
if (!Files.exists(tempDir)) {
Files.createDirectory(tempDir);
}
} else {
// 如果是文件就创建文件写入内容
File file = new File(cacheDir, entry.getName());
// 创建文件的输出流
FileOutputStream fos = new FileOutputStream(file);
// 写入内容到输出流
int len;
while ((len = zis.read(buff)) != -1) {
fos.write(buff, 0, len);
}
// zis.transferTo(fos); // jdk9及更高版本可以用这个
// 关闭当前文件的输出流
fos.close();
}
// 获取下一个压缩文件
entry = zis.getNextEntry();
}
System.out.println("===== 模板docx文件解压完成 =====");
}
// 替换document.xml的内容
private static void replaceXmlContent(Map<String, String> data) throws IOException {
// document.xml的位置
String documentXmlPath = cachePath + File.separator + "word" + File.separator + "document.xml";
// 获取document.xml文件的输入流
FileInputStream fis = new FileInputStream(documentXmlPath);
// 读取xml中的内容
byte[] buff = new byte[fis.available()];
fis.read(buff);
// 获取xml文件中的内容
String content = new String(buff);
// 拿到Map集合中的数据和值,替换掉对应位置的内容
for (String key : data.keySet()) {
String value = data.get(key);
System.out.println("替换 [ {{" + key + "}} ] 为 => [ " + value + " ]");
content = content.replaceAll("\\{\\{" + key + "\\}\\}", value);
}
// 把替换好的内容写入原来的document.xml文件中
FileOutputStream fos = new FileOutputStream(documentXmlPath);
// 写入内容
fos.write(content.getBytes(StandardCharsets.UTF_8));
// 关闭流
fos.close();
fis.close();
System.out.println("===== 替换完成 =====");
}
// 把替换好document.xml内容的文件夹重新压缩成docx文件
private static void buildDocx(FileOutputStream template) throws IOException {
// 创建压缩文件输出流
ZipOutputStream zos = new ZipOutputStream(new DataOutputStream(template));
// 获取要压缩文件的路径
Path path = Paths.get(cachePath);
// 遍历当前压缩文件路径下的所有文件
Files.walkFileTree(path, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
// 获取当前文件的父目录的名字
String parent = file.getParent().getFileName().toString();
// 压缩文件的名字
String zipFileName;
// 如果父目录是根目录名字就是当前的名字否则就是夫目录加上当前的名字
if (parent.equals(path.toFile().getName())) {
zipFileName = file.toFile().getName();
} else {
// 如果父目录的夫目录是word就需要word+当前文件的父目录+当前文件的名字
if ("word".equals(file.getParent().getParent().toFile().getName())) {
zipFileName = "word" + File.separator + parent + File.separator + file.toFile().getName();
} else {
// 如果不是就是当前文件的父目录+当前文件的名字
zipFileName = parent + File.separator + file.toFile().getName();
}
}
// 把文件添加到压缩包中
zos.putNextEntry(new ZipEntry(zipFileName));
// 获取当前文件的输入流
DataInputStream zis = new DataInputStream(new FileInputStream(file.toFile()));
// 写入内容到当前的压缩文件
int len;
byte[] buff = new byte[1024 * 10];
while ((len = zis.read(buff)) != -1) {
zos.write(buff, 0, len);
}
zis.close();
// zis.transferTo(zos); // jdk9及更高版本可以用这个
return super.visitFile(file, attrs);
}
});
// 关闭流
zos.close();
template.close();
System.out.println("===== 把解压的文件重新压缩成.docx ====");
}
// 关闭模板输入流和清除缓存文件
public static void closeTemplateAndClearCache() {
// 关闭集合中的所有流
for (InputStream inputStream : iss) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 删除缓存文件
deleteCache(new File(cachePath));
System.out.println("===== 关闭了模板,清理了缓存 =====");
}
// 删除不用的缓存解压文件目录
private static void deleteCache(File cachePath) {
// 先删文件夹里面的文件
for (File file : Objects.requireNonNull(cachePath.listFiles())) {
// 是文件夹就递归调用
if (file.isDirectory()) {
deleteCache(file);
} else {
// 是文件就直接删
file.delete();
}
}
// 删掉自己
cachePath.delete();
}
}
使用工具类
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Author Da
* Description: <br/>
* 三十年生死两茫茫,写程序,到天亮。
* 千行代码,Bug何处藏。
* 纵使上线又怎样,朝令改,夕断肠。
* 领导每天新想法,天天改,日日忙。
* 相顾无言,惟有泪千行。
* 每晚灯火阑珊处,夜难寐,又加班。
* Date: 2022-05-05
* Time: 11:25
*/
public class App {
public static void main(String[] args) throws IOException {
// 填充的数据
Map<String, String> data = new HashMap<>();
data.put("name", "hello world");
data.put("time", "2022年10月1日");
// 模板输入流
FileInputStream is = new FileInputStream("src/main/resources/one.docx");
// 模板输出流
FileOutputStream os = new FileOutputStream("src/main/resources/test1.docx");
FileOutputStream os1 = new FileOutputStream("src/main/resources/test2.docx");
// 生成模板
DocxUtil.compile(is, data, os);
DocxUtil.compile(is, data, os1);
// 关闭模板输入流和清除缓存文件
DocxUtil.closeTemplateAndClearCache();
}
}