Java中使用模板引擎+Word XML導出復雜Word的步驟
此方案利用 Word 本身可以保存為 XML 的特性,將設(shè)計好的 Word 文檔另存為 XML(或 .docx 解壓后的 main document 部分),然后在 XML 中嵌入模板標記,最后通過模板引擎生成最終的 Word 文檔。
實現(xiàn)原理
- 使用 Microsoft Word 設(shè)計好文檔樣式和固定內(nèi)容,保存為 Word 2003 XML 文檔(.xml)或直接使用 .docx 包內(nèi)的
document.xml。 - 在 XML 文件中需要動態(tài)插入數(shù)據(jù)的地方添加模板引擎的占位符(如
${name}、<#list>等)。 - 在 Java 中讀取該模板文件,使用 FreeMarker 或 Velocity 渲染,將動態(tài)數(shù)據(jù)填充進去。
- 將渲染后的 XML 內(nèi)容重新打包成 .docx(如果是操作 .docx 內(nèi)部 XML)或直接保存為 .doc 格式(如果是 Word 2003 XML)。
示例步驟(以 FreeMarker + docx 為例)
- 設(shè)計一個
.docx模板,解壓得到word/document.xml。 - 編輯
document.xml,插入 FreeMarker 語法(注意避免破壞 XML 結(jié)構(gòu),可使用 CDATA 或特殊處理)。 - 在 Java 中,讀取
document.xml作為 FreeMarker 模板,傳入數(shù)據(jù)模型生成填充后的 XML 字符串。 - 將該字符串替換回原 .docx 的
document.xml,重新打包為 .docx 文件。
# BeanUtil使用的是Hutool中的工具類
Map<String, Object> dataMap = BeanUtil.beanToMap(report);
try {
// 1. 創(chuàng)建 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 文件內(nèi)容替換到 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 文件內(nèi)容(Java 8 兼容)
Path xmlPath = Paths.get(renderedXmlPath);
byte[] xmlBytes = Files.readAllBytes(xmlPath);
String renderedXml = new String(xmlBytes, StandardCharsets.UTF_8);
// 創(chuàng)建臨時目錄存放解壓后的文件
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 文件正確結(jié)束
} finally {
// 清理臨時目錄
Files.walk(tempDir)
.map(Path::toFile)
.forEach(File::delete);
}
}
/**
* 將渲染后的 Word 2003 XML 文件內(nèi)容替換到 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 文件內(nèi)容(Java 8 兼容)
Path xmlPath = Paths.get(renderedXmlPath);
byte[] xmlBytes = Files.readAllBytes(xmlPath);
String renderedXml = new String(xmlBytes, StandardCharsets.UTF_8);
// 創(chuàng)建臨時目錄存放解壓后的文件
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 ");
}
}到此這篇關(guān)于Java中使用模板引擎+Word XML導出復雜Word的步驟的文章就介紹到這了,更多相關(guān)Java導出復雜Word內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA使用quartz添加定時任務(wù),并依賴注入對象操作
這篇文章主要介紹了JAVA使用quartz添加定時任務(wù),并依賴注入對象操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
JDK1.7中HashMap的死循環(huán)問題及解決方案
這篇文章主要為大家介紹了JDK1.7中HashMap的死循環(huán)問題及解決方案,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10
Java19新特性中結(jié)構(gòu)化并發(fā)的使用
Java19在并發(fā)編程領(lǐng)域引入了一個全新的概念:結(jié)構(gòu)化并發(fā),這一特性旨在簡化并發(fā)任務(wù)的管理,提升多線程程序的可維護性和安全性,使其生命周期和控制流更加有序和明確,感興趣的可以了解一下2024-09-09
Elasticsearch中store field與non-store field的區(qū)別說明
這篇文章主要介紹了Elasticsearch中store field與non-store field的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
RocketMQ?消息Message的結(jié)構(gòu)和使用方式詳解
Message是RocketMQ的數(shù)據(jù)包,它不僅是業(yè)務(wù)數(shù)據(jù)的載體,更是路由、過濾、追蹤、延遲、事務(wù)等功能的基礎(chǔ),掌握Message,你就掌握了RocketMQ的語言,本文給大家介紹什么是?Message及理解Message的結(jié)構(gòu)、屬性、生命周期和使用方式,感興趣的朋友一起看看吧2025-08-08

