使用 Freemarker 生成 DOCX 文档的核心思路是:将 DOCX 文件视为 ZIP 压缩包,替换其中的 XML 模板内容。以下是详细步骤和代码示例:
步骤 1:准备 DOCX 模板
-
创建模板 DOCX 文件
用 Microsoft Word 或 LibreOffice 编写一个 DOCX 文件,在需要动态填充的位置插入 Freemarker 占位符(如${name}
)。 -
解压 DOCX 文件
DOCX 本质是 ZIP 文件,解压后得到以下结构:/word/document.xml --> 主内容文件 /word/ --> 其他资源(图片、样式等)
-
修改
document.xml
在document.xml
中插入 Freemarker 变量(如${userName}
),然后重新压缩为 DOCX(临时模板备用)。
步骤 2:使用 Freemarker 动态生成 XML
将 document.xml
作为 Freemarker 模板,动态填充数据后重新打包为 DOCX。
示例代码
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.io.FileUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class DocxGeneratorWithFreemarker {
public static void main(String[] args) throws Exception {
// 1. 数据模型
Map<String, Object> data = new HashMap<>();
data.put("userName", "张三");
data.put("date", "2023-10-10");
// 2. 生成替换后的 document.xml
String processedXml = processTemplate(data);
// 3. 将生成的 XML 重新打包为 DOCX
generateDocx(processedXml, "output.docx");
}
/**
* 使用 Freemarker 处理 document.xml 模板
*/
private static String processTemplate(Map<String, Object> data) throws Exception {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
cfg.setClassForTemplateLoading(DocxGeneratorWithFreemarker.class, "/");
cfg.setDefaultEncoding("UTF-8");
// 从资源目录加载 document.xml.ftl 模板
Template template = cfg.getTemplate("document.xml.ftl");
StringWriter writer = new StringWriter();
template.process(data, writer);
return writer.toString();
}
/**
* 将生成的 XML 打包为 DOCX
*/
private static void generateDocx(String xmlContent, String outputPath) throws Exception {
// 临时解压目录(需提前准备好 DOCX 模板的其他文件)
File tempDir = new File("path/to/unzipped_template_dir");
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputPath))) {
// 遍历模板目录中的所有文件
for (File file : FileUtils.listFiles(tempDir, null, true)) {
String entryName = file.getAbsolutePath().replace(tempDir.getAbsolutePath() + File.separator, "")
.replace(File.separator, "/");
// 如果是 document.xml,替换为动态生成的内容
if (entryName.equals("word/document.xml")) {
zos.putNextEntry(new ZipEntry(entryName));
zos.write(xmlContent.getBytes(StandardCharsets.UTF_8));
} else {
// 其他文件直接复制
zos.putNextEntry(new ZipEntry(entryName));
FileUtils.copyFile(file, zos);
}
zos.closeEntry();
}
}
}
}
关键文件说明
-
document.xml.ftl
这是从 DOCX 模板中提取的 Freemarker 模板文件,内容类似:<w:document ...> <w:body> <w:p> <w:r> <w:t>用户名:${userName}</w:t> </w:r> </w:p> <w:p> <w:r> <w:t>日期:${date}</w:t> </w:r> </w:p> </w:body> </w:document>
-
模板目录结构
解压后的 DOCX 模板需包含所有必要文件(如样式、字体等),仅替换document.xml
。
注意事项
-
模板预处理
- 用工具(如 Word)生成的 DOCX 可能包含复杂 XML 结构,建议先用简单模板测试。
- 确保 XML 中的 Freemarker 变量放在
<w:t>
标签内。
-
依赖库
<!-- Maven 依赖 --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> </dependency>
-
复杂格式处理
- 表格、图片等动态内容需在 XML 中构造对应的 WordML 标签。
- 使用
Apache POI
生成 DOCX 更简单(但需直接操作 Java API)。
替代方案:结合 Apache POI
如果需动态生成复杂 DOCX,建议 使用 Apache POI + Freemarker:
// 示例:使用 POI 生成 DOCX
XWPFDocument doc = new XWPFDocument();
XWPFParagraph p = doc.createParagraph();
p.createRun().setText("Hello ${name}!");
// 结合 Freemarker 渲染内容
String xml = processFreemarkerTemplate();
doc = new XWPFDocument(new ByteArrayInputStream(xml.getBytes()));
这种方式更直接,但需熟悉 POI 的 API。