1 使用场景
基于模板生成 Word 文档是一种常见的需求,特别是在需要生成带有固定格式和动态数据的报表或文档时,常见的使用场景有:
(1)生成标准化的报告,如业务报告等;
(2)合同生成,根据不同客户信息自动生成合同文档;
(3)证书制作,如制作证书或奖状等;
(4)表单填充,将表单数据填充到预定义格式的 Word 模板中,以生成个性化的表单;
(5)自定义文档,根据用户输入或者系统数据生成自定义格式的文档。
2 实现思路
(1)准备模板文件:首先需要创建模板文件,其中包含文档的结构、样式和占位符(如变量或标记);
(2)技术选型:使用 Java 中的一些库来处理模板文件,比如 Apache POI、Docx4j、Freemarker、Thymeleaf 等。本篇文章以使用 Freemarker 模板引擎为例;
(3)填充模板数据:准备好需要填充的数据,在 Java 中读取模板文件,将需要填充的数据替换掉模板中的占位符;
(4)生成新文档:将填充完数据的文件保存或输出为新的 Word 文档。
3 操作步骤
3.1 准备模板文件
3.1.1 编写 Word 模板
3.1.2 将 Word 另存为 xml 文件
3.1.3 格式化 xml 文件
上述操作得到的 xml 文件中的数据基本都糅合在一行里,不方便阅读和更改,可以通过在线工具进行格式化。
- 在线 XML 格式化工具:https://www.jyshare.com/front-end/710
注:如果只是需要查看 xml 文件内容,可以直接选择浏览器打开,主流的浏览器基本都会对 xml 进行格式化显示
3.1.4 注意事项
在 Word 中使用变量时需要确保在生成 xml 文件后变量是完整的。因为在 Word 的 Open XML 格式设计中,为了提供对文档内容和样式的细致控制,即使在视觉上看起来连续的文本也可能由多个元素构成。例如:
Word 拼写检查
Word 拼写错误时会有下划曲线,如果放任不管的话,生成的 xml 里变量就会被拆分开,可以右键忽略拼写检查解决。
Word 修订标识
Word 文档有修订标识符(Revision Identifier,RSID)用来标识每个修订片段。例如我在某次编辑中之改动了变量 ${cardNum} 的一部分,如下图所示,生成的 xml 里的变量就会被修订标识给拆分开。所以修改变量时需要整个一起改,避免在 xml 中变量被拆分。
除了上述例子之外,Word 文档还有其他情况也会将变量给拆分开,因此建议 xml 模板生成后,检查一下各个变量的格式是否正确,不正确的也可以直接参照 xml 格式手动编辑一下,总之需要确保变量名在 xml 模板文件中是一个整体,例如:${name}
3.2 Java 测试 Demo
3.2.1 引入 Freemarker 依赖
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.33</version>
</dependency>
3.2.2 准备 xml 模板文件
xml 文件需要放在项目能读取到的地方,这里直接放到项目中的 resources 目录下的 templates 文件夹下,确保加载模板时能正常读取到即可
3.2.3 Demo 代码示例 1
package com.demo.wordex.controller;
import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
/**
* 基于模板生成 Word 文档
*
* @param response response
*/
@GetMapping("/generate_word")
public void generateWord(HttpServletResponse response){
Map<String, String> templateParam = new HashMap<>(5);
templateParam.put("name", "小明");
templateParam.put("sex", "男");
templateParam.put("age", "18");
templateParam.put("birthday", "2024-07-22");
templateParam.put("cardNum", "440000000000001111");
try {
// 设置 HTTP 响应的内容类型为 Microsoft Word 文档
response.setContentType("application/msword");
// 设置响应字符编码为 UTF-8
response.setCharacterEncoding("utf-8");
// 使用 URLEncoder 对文件名进行编码,以防止中文文件名在不同浏览器和操作系统下出现乱码问题
String filename = URLEncoder.encode("测试Word_" + System.currentTimeMillis(), "utf-8");
// 设置响应头,指定文档将以附件的形式下载,并定义文件名
response.setHeader("Content-disposition", "attachment;filename=" + filename + ".doc");
// 创建 Freemarker 的 Configuration 对象,设置默认的不兼容改进选项
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
configuration.setDefaultEncoding("utf-8");
// 设置模板加载器,加载模板文件
configuration.setTemplateLoader(new ClassTemplateLoader(getClass(), "/templates"));
Template t = configuration.getTemplate("generate_word_test.xml", "utf-8");
// 创建 Writer 对象,用于将生成的文档写到输出流中,缓存区大小设为 10KB
Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8), 10240);
// 将模型数据与模板结合生成 Word 文档,写入到 Writer 对象中
t.process(templateParam, out);
out.close();
} catch (Exception e) {
log.info("生成Word文档异常,异常原因:{}", e.getMessage(), e);
throw new RuntimeException("生成Word文档异常,异常原因:" + e.getMessage());
}
}
}
测试结果
- 浏览器访问
localhost:8090/test/generate_word
下载 Word 文档
3.2.4 基于示例 1 提取工具类
package com.demo.wordex.utils;
import freemarker.cache.ClassTemplateLoader