此方案利用 word 本身可以保存为 xml 的特性,将设计好的 word 文档另存为 xml(或 .docx 解压后的 main document 部分),然后在 xml 中嵌入模板标记,最后通过模板引擎生成最终的 word 文档。
实现原理
- 使用 microsoft word 设计好文档样式和固定内容,保存为 word 2003 xml 文档(.xml)或直接使用 .docx 包内的
document.xml。 - 在 xml 文件中需要动态插入数据的地方添加模板引擎的占位符(如
${name}、<#list>等)。 - 在 java 中读取该模板文件,使用 freemarker 或 velocity 渲染,将动态数据填充进去。
- 将渲染后的 xml 内容重新打包成 .docx(如果是操作 .docx 内部 xml)或直接保存为 .doc 格式(如果是 word 2003 xml)。
示例步骤(以 freemarker + docx 为例)
- 设计一个
.docx模板,解压得到word/document.xml。 - 编辑
document.xml,插入 freemarker 语法(注意避免破坏 xml 结构,可使用 cdata 或特殊处理)。 - 在 java 中,读取
document.xml作为 freemarker 模板,传入数据模型生成填充后的 xml 字符串。 - 将该字符串替换回原 .docx 的
document.xml,重新打包为 .docx 文件。
# beanutil使用的是hutool中的工具类
map<string, object> datamap = beanutil.beantomap(report);
try {
// 1. 创建 freemarker 配置
configuration cfg = new configuration(configuration.version_2_3_31);
// 模板所在目录
cfg.setdirectoryfortemplateloading(new file(templatedir));
cfg.setdefaultencoding("utf-8");
cfg.settemplateexceptionhandler(templateexceptionhandler.rethrow_handler);
cfg.setlogtemplateexceptions(false);
cfg.setwrapuncheckedexceptions(true);
cfg.setfallbackonnullloopvariable(false);
// 2. 加载模板(已按上述要求修改的 xml 文件)
template template = cfg.gettemplate(templatepath);
// 4. 渲染模板
try (writer out = new bufferedwriter(new outputstreamwriter(
new fileoutputstream(templateoutputpath), standardcharsets.utf_8))) {
template.process(datamap, out);
response.setcontenttype("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
string filename = report.getunitname() + "年度自评报告.docx";
string encodedfilename = urlencoder.encode(filename, standardcharsets.utf_8.tostring()).replace("+", "%20");
response.setheader("content-disposition", "attachment; filename*=utf-8''" + encodedfilename);
# yaml配置
#yearselfevaluation:
# # 模板目录
# templatedir: g:\\templates
# # 模板路径
# templatepath: template\\word\\document.xml
# # 输出路径
# templateoutputpath: g:\\templates\\output2.xml
# # 模板docx路径
# templatedocxpath: g:\\templates\\template.docx
xmltodocx.convert(templatedocxpath, templateoutputpath, response);
}
} catch (exception e) {
log.error("渲染模板失败", e);
}
# xmltodocx
import javax.servlet.http.httpservletresponse;
import java.io.*;
import java.nio.charset.standardcharsets;
import java.nio.file.files;
import java.nio.file.path;
import java.nio.file.paths;
import java.util.zip.zipentry;
import java.util.zip.zipinputstream;
import java.util.zip.zipoutputstream;
public class xmltodocx {
/**
* 将渲染后的 word 2003 xml 文件内容替换到 docx 模板中,并将生成的 docx 写入 httpservletresponse 输出流
* @param docxtemplate 原始的 docx 模板路径(由 xml 另存得来)
* @param renderedxmlpath 渲染后的 xml 文件路径
* @param response httpservletresponse 对象,用于输出 docx
*/
public static void convert(string docxtemplate, string renderedxmlpath, httpservletresponse response) throws ioexception {
// 读取渲染后的 xml 文件内容(java 8 兼容)
path xmlpath = paths.get(renderedxmlpath);
byte[] xmlbytes = files.readallbytes(xmlpath);
string renderedxml = new string(xmlbytes, standardcharsets.utf_8);
// 创建临时目录存放解压后的文件
path tempdir = files.createtempdirectory("docx");
try (zipinputstream zis = new zipinputstream(new fileinputstream(docxtemplate))) {
zipentry entry;
while ((entry = zis.getnextentry()) != null) {
file outfile = new file(tempdir.tofile(), entry.getname());
if (entry.isdirectory()) {
outfile.mkdirs();
} else {
outfile.getparentfile().mkdirs();
try (fileoutputstream fos = new fileoutputstream(outfile)) {
byte[] buffer = new byte[8192];
int len;
while ((len = zis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}
}
zis.closeentry();
}
}
// 替换 document.xml
path docxmlpath = tempdir.resolve("word/document.xml");
files.write(docxmlpath, renderedxml.getbytes(standardcharsets.utf_8));
// 重新打包为 docx 并直接写入 response 输出流
try (zipoutputstream zos = new zipoutputstream(response.getoutputstream())) {
files.walk(tempdir).foreach(path -> {
if (files.isregularfile(path)) {
string entryname = tempdir.relativize(path).tostring().replace('\\', '/');
try {
zos.putnextentry(new zipentry(entryname));
files.copy(path, zos);
zos.closeentry();
} catch (ioexception e) {
throw new uncheckedioexception(e);
}
}
});
zos.finish(); // 确保 zip 文件正确结束
} finally {
// 清理临时目录
files.walk(tempdir)
.map(path::tofile)
.foreach(file::delete);
}
}
/**
* 将渲染后的 word 2003 xml 文件内容替换到 docx 模板中,生成最终的 docx 文件
* @param docxtemplate 原始的 docx 模板路径(由 xml 另存得来)
* @param renderedxmlpath 渲染后的 xml 文件路径
* @param outputdocx 输出 docx 文件路径
*/
public static void convert(string docxtemplate, string renderedxmlpath, string outputdocx) throws ioexception {
// 读取渲染后的 xml 文件内容(java 8 兼容)
path xmlpath = paths.get(renderedxmlpath);
byte[] xmlbytes = files.readallbytes(xmlpath);
string renderedxml = new string(xmlbytes, standardcharsets.utf_8);
// 创建临时目录存放解压后的文件
path tempdir = files.createtempdirectory("docx");
try (zipinputstream zis = new zipinputstream(new fileinputstream(docxtemplate))) {
zipentry entry;
while ((entry = zis.getnextentry()) != null) {
file outfile = new file(tempdir.tofile(), entry.getname());
if (entry.isdirectory()) {
outfile.mkdirs();
} else {
outfile.getparentfile().mkdirs();
try (fileoutputstream fos = new fileoutputstream(outfile)) {
byte[] buffer = new byte[8192];
int len;
while ((len = zis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}
}
zis.closeentry();
}
}
// 替换 document.xml
path docxmlpath = tempdir.resolve("word/document.xml");
files.write(docxmlpath, renderedxml.getbytes(standardcharsets.utf_8));
// 重新打包为 docx
try (zipoutputstream zos = new zipoutputstream(new fileoutputstream(outputdocx))) {
files.walk(tempdir).foreach(path -> {
if (files.isregularfile(path)) {
string entryname = tempdir.relativize(path).tostring().replace('\\', '/');
try {
zos.putnextentry(new zipentry(entryname));
files.copy(path, zos);
zos.closeentry();
} catch (ioexception e) {
throw new uncheckedioexception(e);
}
}
});
} finally {
// 清理临时目录
files.walk(tempdir)
.map(path::tofile)
.foreach(file::delete);
}
}
public static void main(string[] args) throws ioexception {
convert("g:\\templates\\template.docx", "g:\\templates\\output2.xml", "f:\\test2.docx");
system.out.println("docx 文件已生成, haha ");
}
}到此这篇关于java中使用模板引擎+word xml导出复杂word的步骤的文章就介绍到这了,更多相关java导出复杂word内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论