Java根据word模板生成word文件

1.简介

处理word的方式有许多种:

  1. 使用 Hutool工具类 ,但是只能处理简单的word,不能处理表格,动态图片,替换文字等
  2. 使用Apache POI,可以处理复杂的Word文档,但是处理过程复杂,word转xml,xml再转ftl才可进行操作,不适合经常处理word
  3. 其他第三方工具
  4. XDocReport +FreeMarker,该技术组合既简单又高效可实现word模板的编辑,docx和doc均可处理

本文将实现动态文本替换、动态图片替换、动态表格填充。

2.pom引入

<dependency>
     <groupId>fr.opensagres.xdocreport</groupId>
     <artifactId>fr.opensagres.xdocreport.core</artifactId>
     <version>2.0.2</version>
</dependency>
<dependency>
     <groupId>fr.opensagres.xdocreport</groupId>
     <artifactId>fr.opensagres.xdocreport.document</artifactId>
     <version>2.0.2</version>
</dependency>
<dependency>
      <groupId>fr.opensagres.xdocreport</groupId>
      <artifactId>fr.opensagres.xdocreport.template</artifactId>
      <version>2.0.2</version>
</dependency>
<dependency>
     <groupId>fr.opensagres.xdocreport</groupId>
     <artifactId>fr.opensagres.xdocreport.document.docx</artifactId>
     <version>2.0.2</version>
</dependency>
<dependency>
      <groupId>fr.opensagres.xdocreport</groupId>
      <artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId>
      <version>2.0.2</version>
</dependency>
<dependency>
     <groupId>org.freemarker</groupId>
     <artifactId>freemarker</artifactId>
     <version>2.3.23</version>
</dependency>

3.动态文本替换

比如说我们有个邮件word模板,需要动态的在横线处填入相关信息,然后生成完整的word文档。

我们做以下操作:

  1. 将横线位置替换成域
  2. 给每个要替换的位置取个名字
  3. FreeMarker域格式为—— ${……}
https://www.zhihu.com/video/1538182184493428736

代码实现

import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
 * @author: YSL
 * @date: 2022/8/2 17:42
public class WordTemplate {
    private static final Logger logger = LoggerFactory.getLogger(WordTemplate.class);
    public static void test() {
        InputStream ins = null;
        OutputStream out = null;
        try {
            //获取Word模板,模板存放路径
            ins = new FileInputStream("C:\\Users\\she52\\Desktop\\演示.docx");
            //注册xdocreport实例并加载FreeMarker模板引擎
            IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker);
            //创建xdocreport上下文对象,用于存放具体数据
            IContext context = report.createContext();
            //创建要替换的文本变量
            context.put("name", "李梅");
            context.put("sendName", "张三");
            context.put("year", "2022");
            context.put("month", "08");
            context.put("day", "03");
            //输出到本地目录
            String filePath = "C:\\Users\\she52\\Desktop\\结果.docx";
            out = new FileOutputStream(new File(filePath));
            report.process(context, out);
        } catch (Exception e) {
            logger.info("生成word发生异常", e);
        } finally {
            try {
                if (ins != null){
                    ins.close();
                if (out != null){
                    out.close();
            } catch (IOException e) {
                logger.info("文件流关闭失败", e);
}

注意,执行代码时要把word模板关掉,否则模板加载不成功,会报错

执行结果:

4.动态表格

如果表格是标准的几行几列的列表,使用以下方法。如果不是标准的,比如说合并单元格,则需要使用上 面文本替换的方法,挨个起名赋值。

我们做以下操作:

  1. 除了标题行,保留一行空白行,删掉其余的空白行(为什么这么做,因为表格填充,本质是列表循环填写,列表大小是不确定的,我们只保留一行,让它自己根据列表大小创建行数)
  2. 起一个列表名,比如上面的示例表是用户信息表,则起名为userInfo
  3. 对用户信息属性挨个起名,姓名name、年龄age、性别sex
  4. 第2和第3步骤,等同于新建一个Java实体类
  5. 添加域,命名规范-----${userInfo.name}、${userInfo.age}、${userInfo.sex},自己总结规律
  6. 修改完后记得保存,然后关闭word
https://www.zhihu.com/video/1538181287793315840

代码实现:

import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import fr.opensagres.xdocreport.template.formatter.FieldsMetadata;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
 * @author: YSL
 * @date: 2022/8/2 17:42
public class WordTemplate {
    private static final Logger logger = LoggerFactory.getLogger(WordTemplate.class);
    public static void test() {
        InputStream ins = null;
        OutputStream out = null;
        try {
            //获取Word模板,模板存放路径
            ins = new FileInputStream("C:\\Users\\she52\\Desktop\\演示.docx");
            //注册xdocreport实例并加载FreeMarker模板引擎
            IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker);
            //创建xdocreport上下文对象,用于存放具体数据
            IContext context = report.createContext();
            //创建要替换的文本变量
            List<UserInfo> userInfos = new ArrayList<>();
            UserInfo userInfo = new UserInfo();
            userInfo.setName("张三");
            userInfo.setAge("15");
            userInfo.setSex("男");
            userInfos.add(userInfo);
            UserInfo userInfo1 = new UserInfo();
            userInfo1.setName("李四");
            userInfo1.setAge("14");
            userInfo1.setSex("男");
            userInfos.add(userInfo1);
            UserInfo userInfo2 = new UserInfo();
            userInfo2.setName("李梅");
            userInfo2.setAge("16");
            userInfo2.setSex("女");
            userInfos.add(userInfo2);
            //此处的userInfo是word中命名的列表名
            context.put("userInfo", userInfos);
            //创建字段元数据
            FieldsMetadata fm = report.createFieldsMetadata();
            //Word模板中的表格数据对应的集合类型
            fm.load("userInfo", UserInfo.class, true);
            //输出到本地目录
            String filePath = "C:\\Users\\she52\\Desktop\\结果.docx";
            out = new FileOutputStream(new File(filePath));
            report.process(context, out);
        } catch (Exception e) {
            logger.info("生成word发生异常", e);
        }finally {
            try {
                if (ins != null){
                    ins.close();
                if (out != null){
                    out.close();
            } catch (IOException e) {
                logger.info("文件流关闭失败", e);
@Data
public class UserInfo{
    private String name;
    private String age;
    private String sex;
}

执行结果

注意,代码中创建的UserInfo实体类必须是public公共的,否则赋不到值

5.动态图片

比如说word文档中有,勾选框, 同意则打对勾。我们可以将勾选框变成图片,再令准备一张打好对勾的图片,如果勾选则将其替换,否则不替换,就可以完美解决勾选问题。

做以下操作:

  1. 将勾选的圆圈替换成大小相同的图片,可以让UI帮忙整一个
  2. 给图片添加标签并命名,直接写名字就行,不用加${}
https://www.zhihu.com/video/1538179775591518209

代码实现:

import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.images.ByteArrayImageProvider;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import fr.opensagres.xdocreport.template.formatter.FieldsMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
 * @author: YSL
 * @date: 2022/8/2 17:42
public class WordTemplate {
    private static final Logger logger = LoggerFactory.getLogger(WordTemplate.class);
    public static void test() {
        InputStream in = null;
        OutputStream out = null;
        FileInputStream fins = null;
        try {
            //获取Word模板,模板存放路径
            in = new FileInputStream("C:\\Users\\she52\\Desktop\\演示.docx");
            //注册xdocreport实例并加载FreeMarker模板引擎
            IXDocReport report = XDocReportRegistry.getRegistry().loadReport(in, TemplateEngineKind.Freemarker);
            //创建xdocreport上下文对象,用于存放具体数据
            IContext context = report.createContext();
            //图片元数据
            FieldsMetadata metadata = report.createFieldsMetadata();
            //读取对勾圆形图片
            fins = new FileInputStream(new File("C:\Users\she52\Desktop\hookRound.png"));
            metadata.addFieldAsImage("accept");
            //只在同意上打对勾,不同意那个图片则不用管
            context.put("accept", new ByteArrayImageProvider(fins));
            report.setFieldsMetadata(metadata);
            //输出到本地目录
            String filePath = "C:\\Users\\she52\\Desktop\\结果.docx";
            out = new FileOutputStream(new File(filePath));
            report.process(context, out);
        } catch (Exception e) {
            logger.info("生成word发生异常", e);
        }finally {
            try {
                if (in != null){
                    in.close();
                if(fins != null){
                    fins.close();
                if(out != null){
                    out.close();
            } catch (IOException e) {
                logger.info("文件流关闭失败",e);