自学内容网 自学内容网

JAVA 基于模板生成 Word 文件

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;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

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;

@Slf4j
@Component
public class WordUtil {

    /**
     * 基于模板生成 Word 文档
     *
     * @param response         response
     * @param basePackagePath  resources 目录下模板所在包路径
     * @param templateFileName 模板文件名
     * @param templateParam    模板参数
     * @param fileName         文件名
     */
    public void generate(HttpServletResponse response, String basePackagePath, String templateFileName, Object templateParam, String fileName) {
        try {
            // 设置 HTTP 响应的内容类型为 Microsoft Word 文档
            response.setContentType("application/msword");
            // 设置响应字符编码为 UTF-8
            response.setCharacterEncoding("utf-8");
            // 使用 URLEncoder 对文件名进行编码,以防止中文文件名在不同浏览器和操作系统下出现乱码问题
            String filename = URLEncoder.encode(fileName + "_" + 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(), basePackagePath));
            Template t = configuration.getTemplate(templateFileName, "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文档异常,异常原因:{}", templateFileName, e.getMessage(), e);
            throw new RuntimeException("生成Word文档异常,异常原因:" + e.getMessage());
        }
    }

}

3.2.5 Demo 代码示例 2

package com.demo.wordex.controller;

import com.demo.wordex.utils.WordUtil;
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.annotation.Resource;
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 {

    @Resource
    private WordUtil wordUtil;

    /**
     * 基于模板生成 Word 文档(基于示例 1 提取 Word 工具类)
     *
     * @param response response
     */
    @GetMapping("/generate_word_2")
    public void generateWord2(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");

        wordUtil.generate(response, "/templates", "generate_word_test.xml", templateParam, "测试Word");
    }

}

3.2.6 Demo 代码示例 3(使用对象入参)

创建用户信息类

package com.demo.wordex.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
    private String name;
    private String sex;
    private String age;
    private String birthday;
    private String cardNum;
}

使用对象入参

package com.demo.wordex.controller;

import com.demo.wordex.model.UserInfo;
import com.demo.wordex.utils.WordUtil;
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.annotation.Resource;
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 {

    @Resource
    private WordUtil wordUtil;

    /**
     * 基于模板生成 Word 文档(使用对象入参)
     *
     * @param response response
     */
    @GetMapping("/generate_word_3")
    public void generateWord3(HttpServletResponse response) {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("小明");
        userInfo.setSex("男");
        userInfo.setAge("18");
        userInfo.setBirthday("2024-07-22");
        userInfo.setCardNum("440000000000001111");

        wordUtil.generate(response, "/templates", "generate_word_test.xml", userInfo, "测试Word");
    }

}

4 拓展:动态表格

4.1 准备模板文件

4.1.1 编写 Word 模板

在这里插入图片描述

4.1.2 在动态列表范围处添加 list 标签

示例

<#list userList as user>
    ...
    ${user.name}
    ${user.sex}
    ${user.age}
    ...
</#list>

xml 节选

<#list userList as user>
<w:tr w:rsidR="00D062FB" w14:paraId="49CA5E91" w14:textId="77777777" w:rsidTr="00827A33">
    <w:tc>
        <w:tcPr>
            <w:tcW w:w="1405" w:type="dxa"/>
        </w:tcPr>
        <w:p w14:paraId="0CBA5021" w14:textId="4EA7152F" w:rsidR="00D062FB" w:rsidRDefault="00886BBD" w:rsidP="00D062FB">
            <w:pPr>
                <w:jc w:val="center"/>
                <w:rPr>
                    <w:rFonts w:hint="eastAsia"/>
                </w:rPr>
            </w:pPr>
            <w:r>
                <w:rPr>
                    <w:rFonts w:hint="eastAsia"/>
                </w:rPr>
                <w:t>${user.name}</w:t>
            </w:r>
        </w:p>
    </w:tc>
    <w:tc>
        <w:tcPr>
            <w:tcW w:w="1161" w:type="dxa"/>
        </w:tcPr>
        <w:p w14:paraId="62A0C0C7" w14:textId="18F50C90" w:rsidR="00D062FB" w:rsidRDefault="00827A33" w:rsidP="00D062FB">
            <w:pPr>
                <w:jc w:val="center"/>
                <w:rPr>
                    <w:rFonts w:hint="eastAsia"/>
                </w:rPr>
            </w:pPr>
            <w:r>
                <w:rPr>
                    <w:rFonts w:hint="eastAsia"/>
                </w:rPr>
                <w:t>${user.sex}</w:t>
            </w:r>
        </w:p>
    </w:tc>
    <w:tc>
        <w:tcPr>
            <w:tcW w:w="1209" w:type="dxa"/>
        </w:tcPr>
        <w:p w14:paraId="4C107460" w14:textId="5F88AA2B" w:rsidR="00D062FB" w:rsidRDefault="00827A33" w:rsidP="00D062FB">
            <w:pPr>
                <w:jc w:val="center"/>
                <w:rPr>
                    <w:rFonts w:hint="eastAsia"/>
                </w:rPr>
            </w:pPr>
            <w:r>
                <w:rPr>
                    <w:rFonts w:hint="eastAsia"/>
                </w:rPr>
                <w:t>${user.age}</w:t>
            </w:r>
        </w:p>
    </w:tc>
    <w:tc>
        <w:tcPr>
            <w:tcW w:w="1465" w:type="dxa"/>
        </w:tcPr>
        <w:p w14:paraId="2E4D57CD" w14:textId="160F2EF2" w:rsidR="00D062FB" w:rsidRDefault="00827A33" w:rsidP="00D062FB">
            <w:pPr>
                <w:jc w:val="center"/>
                <w:rPr>
                    <w:rFonts w:hint="eastAsia"/>
                </w:rPr>
            </w:pPr>
            <w:r>
                <w:rPr>
                    <w:rFonts w:hint="eastAsia"/>
                </w:rPr>
                <w:t>${user.birthday}</w:t>
            </w:r>
        </w:p>
    </w:tc>
    <w:tc>
        <w:tcPr>
            <w:tcW w:w="3056" w:type="dxa"/>
        </w:tcPr>
        <w:p w14:paraId="682E0D8A" w14:textId="075D0180" w:rsidR="00D062FB" w:rsidRDefault="00827A33" w:rsidP="00D062FB">
            <w:pPr>
                <w:jc w:val="center"/>
                <w:rPr>
                    <w:rFonts w:hint="eastAsia"/>
                </w:rPr>
            </w:pPr>
            <w:r>
                <w:rPr>
                    <w:rFonts w:hint="eastAsia"/>
                </w:rPr>
                <w:t>${user.cardNum}</w:t>
            </w:r>
        </w:p>
    </w:tc>
</w:tr>
</#list>

4.2 Java 测试 Demo

4.2.1 Demo 代码示例

package com.demo.wordex.controller;

import com.demo.wordex.model.UserInfo;
import com.demo.wordex.utils.WordUtil;
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.annotation.Resource;
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.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

    @Resource
    private WordUtil wordUtil;

    /**
     * 基于模板生成 Word 文档(生成动态列表)
     *
     * @param response response
     */
    @GetMapping("/generate_dynamic_word")
    public void generateDynamicWord(HttpServletResponse response) {
        List<UserInfo> userList = new ArrayList<>();
        userList.add(new UserInfo("小明", "男", "18", "2024-07-22", "440000000000001111"));
        userList.add(new UserInfo("小红", "女", "20", "2024-07-26", "440000000000002222"));
        userList.add(new UserInfo("小白", "男", "17", "2024-07-24", "440000000000003333"));
        userList.add(new UserInfo("小蓝", "女", "19", "2024-07-23", "440000000000004444"));
        userList.add(new UserInfo("小黑", "男", "18", "2024-07-21", "440000000000005555"));
        Map<String, Object> templateParam = new HashMap<>(5);
        templateParam.put("date", LocalDate.now());
        templateParam.put("total", userList.size());
        templateParam.put("userList", userList);

        wordUtil.generate(response, "/templates", "dynamic_userlist.xml", templateParam, "测试动态列表Word");
    }

}

4.2.2 测试结果

  • 浏览器访问 localhost:8090/test/generate_dynamic_word 下载 Word 文档

在这里插入图片描述

5 附测试 Demo 完整代码


原文地址:https://blog.csdn.net/weixin_42001592/article/details/140715067

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!