网上大多教程都是将word另存为.xml格式的文件,然后再重命名为ftl的模板文件,经过freemarker填充后输出成.docx格式的文件。但实际上输出的文件本质还是配置文件,只不过能够被wps或word所识别,下面讲的是直接转成真正的.docx格式的方法。
1、重命名word文档
将需要作为模板的文档重命名,如xxxx.doc重命名为xxxx.zip
2、解压
将已经重命名的文档进行解压
重命名后的文档解压后的目录基本如下:
3、配置模板
打开word文件夹,里面有个document.xml文件,该文件就是word文档内容。
将document.xml文件复制出来,重命名为.ftl格式,打开文件(我这里用的是vscode打开,用idea打开会很卡,内容太多了)。打开的内容是被压缩过的xml文件内容。没有排版,所有可以将文件内容格式化(格式化后内容或样式可能会有些变化,但可以调整,大多是原本有空格,但格式化后就没有了,后期可以慢慢调整,可以直接百度xml格式化,直接在线格式化)。
格式化后的样子
在需要的地方进行模板数据填充(使用freemarker,不清楚的百度)
4、代码编写
maven依赖
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
工具类
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;
import java.io.*;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class WordUtils {
/**
* 使用FreeMarker自动生成Word文档
* @param dataMap word文档数据
* @param fileName 生成文档的路径包括文件名称
* @param ftlPath 模板路径
* @param ftlName 模板文件名称
* @throws Exception
*/
public static void generateWord(Map<String, Object> dataMap, String fileName, String ftlPath, String ftlName) throws Exception {
// 设置FreeMarker的版本和编码格式
Configuration configuration = new Configuration(new Version("2.3.28"));
configuration.setDefaultEncoding("UTF-8");
// 设置FreeMarker生成Word文档所需要的模板的路径
configuration.setDirectoryForTemplateLoading(new File(ftlPath));
// 设置FreeMarker生成Word文档所需要的模板
Template t = configuration.getTemplate(ftlName, "UTF-8");
// 创建一个Word文档的输出流
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(fileName)), "UTF-8"));
//FreeMarker使用Word模板和数据生成Word文档
t.process(dataMap, out);
out.flush();
out.close();
}
/**
* 压缩文件或文件夹(包括所有子目录文件)
* @param sourceFile 源文件
* @param format 格式(zip或rar)
* @param outPath 输入路径
* @throws IOException
*/
public static void zipFileTree(File sourceFile, String format,String outPath) throws IOException {
ZipOutputStream zipOutputStream = null;
try {
String zipFileName;
if (sourceFile.isDirectory()) { // 目录
zipFileName = sourceFile.getParent() + File.separator + sourceFile.getName() + "."
+ format;
} else { // 单个文件
zipFileName = sourceFile.getParent()
+ sourceFile.getName().substring(0, sourceFile.getName().lastIndexOf("."))
+ "." + format;
}
// 压缩输出流
zipOutputStream = new ZipOutputStream(new FileOutputStream(outPath));
zip(sourceFile, zipOutputStream, "");
} finally {
if (null != zipOutputStream) {
// 关闭流
try {
zipOutputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
/**
* 递归压缩文件
*
* @param file 当前文件
* @param zipOutputStream 压缩输出流
* @param relativePath 相对路径
* @throws IOException IO异常
*/
public static void zip(File file, ZipOutputStream zipOutputStream, String relativePath)
throws IOException {
FileInputStream fileInputStream = null;
try {
if (file.isDirectory()) { // 当前为文件夹
// 当前文件夹下的所有文件
File[] list = file.listFiles();
if (null != list) {
// 计算当前的相对路径
relativePath += (relativePath.length() == 0 ? "" : "/") + file.getName();
// 递归压缩每个文件
for (File f : list) {
zip(f, zipOutputStream, relativePath);
}
}
} else { // 压缩文件
// 计算文件的相对路径
relativePath += (relativePath.length() == 0 ? "" : "/") + file.getName();
// 写入单个文件
String tmp = relativePath.substring(relativePath.indexOf("/")+1,relativePath.length());
zipOutputStream.putNextEntry(new ZipEntry(tmp));
fileInputStream = new FileInputStream(file);
int readLen;
byte[] buffer = new byte[1024];
while ((readLen = fileInputStream.read(buffer)) != -1) {
zipOutputStream.write(buffer, 0, readLen);
}
zipOutputStream.closeEntry();
}
} finally {
// 关闭流
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
测试main
String fileName = "报告解压后,document.xml用模板生成的document.xml替换掉里面的document.xml";//转换后的路径地址 如 docx重命名zip解压后的目录是 c:/tmp/xxx/word/document.xml
String ftlPath = "模板地址";//模板地址 转成模板的地址 如document.ftl地址是 c:/tmp/document.ftl 则填写 c:/tmp
String ftlName = "document.ftl";//模板名称 document.ftl
//填充数据
WordUtils.generateWord(objMap, fileName,ftlPath,ftlName);
String needZipPath = "需要压缩的路径";// 如 docx重命名zip解压后的目录是 c:/tmp/xxx/word/document.xml 则填写 c:/tmp/xxx/
String zipPath = "文件生成路径";//如 c:/tmp/xxx/生成的.docx
//压缩
WordUtils.zipFileTree(new File(needZipPath),"zip",zipPath);
4、思路
.docx文件重命名.zip,解压,将里面内容文件拷贝出来,再通过freemarker填充数据后覆盖原来的内容文件,再将整个文件压缩。
可以做到替换图片(覆盖media文件里的图片,如若要添加图片,就需要修改_rels里的配置文件并向media中添加图片,还要修改document里的内容)、页眉等,麻烦点在于要理清楚xml里面的格式对应的word的内容。
优点:不死板,如标题列表可以使用for循环进行生成,包括表格,也可以选择生成
缺点:需要理清xml格式的内容,对应的文档内容