Java中使用模板引擎+Word XML导出复杂Word的步骤

 更新时间:2026年04月15日 09:34:50   作者:一叶龙洲  
这篇文章主要介绍了如何利用Word XML及模板引擎生成复杂Word文档,先设计样式和固定内容的Word文档,保存为XML格式;在需要插入动态数据的地方添加模板占位符;使用Java读取XML文件,通过模板引擎如FreeMarker或Velocity填充数据,最终生成新的Word文档

此方案利用 Word 本身可以保存为 XML 的特性,将设计好的 Word 文档另存为 XML(或 .docx 解压后的 main document 部分),然后在 XML 中嵌入模板标记,最后通过模板引擎生成最终的 Word 文档。

实现原理

  1. 使用 Microsoft Word 设计好文档样式和固定内容,保存为 Word 2003 XML 文档(.xml)或直接使用 .docx 包内的 document.xml
  2. 在 XML 文件中需要动态插入数据的地方添加模板引擎的占位符(如 ${name}<#list> 等)。
  3. 在 Java 中读取该模板文件,使用 FreeMarker 或 Velocity 渲染,将动态数据填充进去。
  4. 将渲染后的 XML 内容重新打包成 .docx(如果是操作 .docx 内部 XML)或直接保存为 .doc 格式(如果是 Word 2003 XML)。

示例步骤(以 FreeMarker + docx 为例)

  1. 设计一个 .docx 模板,解压得到 word/document.xml
  2. 编辑 document.xml,插入 FreeMarker 语法(注意避免破坏 XML 结构,可使用 CDATA 或特殊处理)。
  3. 在 Java 中,读取 document.xml 作为 FreeMarker 模板,传入数据模型生成填充后的 XML 字符串。
  4. 将该字符串替换回原 .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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java 面向对象通过new揭开对象实例化

    Java 面向对象通过new揭开对象实例化

    各位铁汁们大家好呀,我们上次博客讲了,通过 Student student1 = new Student();就可以实例化一个对象,这个对象就有Student类中的所以成员变量。可是 对象student1 和 类Student到底是怎样建立联系的,在内存中到底发生了什么
    2022-04-04
  • Java中try catch 的基本用法示例

    Java中try catch 的基本用法示例

    这篇文章主要给大家介绍了关于Java中try catch 的基本用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • JAVA使用quartz添加定时任务,并依赖注入对象操作

    JAVA使用quartz添加定时任务,并依赖注入对象操作

    这篇文章主要介绍了JAVA使用quartz添加定时任务,并依赖注入对象操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • JDK1.7中HashMap的死循环问题及解决方案

    JDK1.7中HashMap的死循环问题及解决方案

    这篇文章主要为大家介绍了JDK1.7中HashMap的死循环问题及解决方案,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • IDEA EasyCode 一键帮你生成所需代码

    IDEA EasyCode 一键帮你生成所需代码

    这篇文章主要介绍了IDEA EasyCode 一键帮你生成所需代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • Java19新特性中结构化并发的使用

    Java19新特性中结构化并发的使用

    Java19在并发编程领域引入了一个全新的概念:结构化并发,这一特性旨在简化并发任务的管理,提升多线程程序的可维护性和安全性,使其生命周期和控制流更加有序和明确,感兴趣的可以了解一下
    2024-09-09
  • Maven Plugins报错的正确解决方案

    Maven Plugins报错的正确解决方案

    文章介绍了如何解决Maven插件报错的问题,建议在本地Maven仓库目录中删除报错的插件文件,然后在IDEA中刷新Maven以重新加载,同时,作者还提到了一个无效的方法,即通过修改Maven的setting.xml文件添加镜像源
    2026-03-03
  • Elasticsearch中store field与non-store field的区别说明

    Elasticsearch中store field与non-store field的区别说明

    这篇文章主要介绍了Elasticsearch中store field与non-store field的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • RocketMQ 消息Message的结构和使用方式详解

    RocketMQ 消息Message的结构和使用方式详解

    Message是RocketMQ的数据包,它不仅是业务数据的载体,更是路由、过滤、追踪、延迟、事务等功能的基础,掌握Message,你就掌握了RocketMQ的语言,本文给大家介绍什么是 Message及理解Message的结构、属性、生命周期和使用方式,感兴趣的朋友一起看看吧
    2025-08-08
  • 浅谈SpringBoot自动配置的坑

    浅谈SpringBoot自动配置的坑

    SpringBoot的自动配置简化了开发,但也会带来一些问题,本文通过几个实际案例探讨了一些自动配置可能带来的问题,下面就来详细的介绍下
    2026-05-05

最新评论