Spring Boot 中使用 Poi-tl 渲染数据并生成 Word 文档

news2025/1/24 22:48:56

本文 Demo 已收录到 demo-for-all-in-java 项目中,欢迎大家 star 支持!后续将持续更新!

前言

产品经理急冲冲地走了过来。「现在需要将按这些数据生成一个 Word 报告文档,你来安排下」

项目中有这么一个需求,需要将用户填写的数据填充到一个 Word 文档中,而这个 Word 文档是人家给定了的。换句话说,让你按照这个文档的内容格式生成新的文档。

什么是 Poi-tl ?

官网:http://deepoove.com/poi-tl/1.9.x/

poi-tl(poi template language)是一种 Word 模板引擎,可以基于 Word 模板和数据生成新的文档,它的底层是通过 Apache POI 来实现的。

Apache POI 不仅封装了易用的文档 API (文本、图片、表格、页眉、页脚、图表等),也可以在底层直接操作文档XML结构。

poi-tl 拥有如下特性(了解瞄一眼就行):

内容描述
文本将标签渲染为文本
图片将标签渲染为图片
表格将标签渲染为表格
列表将标签渲染为列表
图表条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、饼图(3D饼图)等图表渲染
If Condition判断隐藏或者显示某些文档内容(包括文本、段落、图片、表格、列表、图表等)
Foreach Loop循环循环某些文档内容(包括文本、段落、图片、表格、列表、图表等)
Loop表格行循环渲染表格的某一行
Loop表格列循环渲染表格的某一列
Loop有序列表支持有序列表的循环,同时支持多级列表
图片替换将原有图片替换成另一张图片
书签、锚点、超链接支持设置书签,文档内锚点和超链接功能
强大的表达式完全支持SpringEL表达式,可以扩展更多的表达式:OGNL, MVEL…
标签定制支持自定义标签前后缀
文本框文本框内标签支持
样式模板即样式,同时代码也可以设置样式
模板嵌套模板包含子模板,子模板再包含子模板
合并Word合并Merge,也可以在指定位置进行合并
用户自定义函数(插件)在文档任何位置执行函数

我们就可以使用这个它来实现这个需求。

如何使用 Poi-tl ?

本篇文章将以 Spring Boot 项目作为演示,屏幕前的朋友们可以一起跟着我的步骤来,实践一番!

  1. 首先创建一个 Spring Boot 项目,版本目前我的 Demo 是 2.2.1,你可以更改你的 Spring Boot 版本,那现在我这里已经创建好了。

其中, pom.xml 只有两个依赖项,一个 web 和一个 test :

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Test 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  1. 接着在 pom.xml 中引入 Poi-tl 的依赖项
<!-- Poi-tl Word 模板引擎-->
<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.9.1</version>
</dependency>
  1. 准备一个 Word 模板

这一步你可以自己动手做一个 Word 模板,这里我先演示下。就先创建一个名为 Hello World.docx 的 Word 文档,模板内容如下:

找一个你喜欢的位置存放这个模板,我现在把它放到项目的 resource 目录。

{{title}} 这种由两个大括号包住的,目前可以看成占位符,这个模板中有 4 个占位符,后续的数据就渲染到这些地方上。

  1. 获取模板所在的路径,并将数据渲染到模板上

渲染只需一行代码,就是使用 XWPFTemplate 的 API 就可以了,通过 complierender 方法,就可以将数据渲染到模板中,得到渲染好的新文档。

@SpringBootTest
public class PoiTlApplicationTest {

    @Test
    public void test() {
        // 获取 Word 模板所在路径
        String filepath = this.getClass().getClassLoader().getResource("hello-world.docx").getPath();
        // 通过 XWPFTemplate 编译文件并渲染数据到模板中
        XWPFTemplate template = XWPFTemplate.compile(filepath).render(
                new HashMap<String, Object>(){{
                    put("title", "Hello, poi-tl Word模板引擎");
                    put("text", "Hello World");
                    put("author", "god23bin");
                    put("description", "这还不关注 god23bin ?再不关注我可要求你关注了!");
                }});
        try {
            // 将完成数据渲染的文档写出
            template.writeAndClose(new FileOutputStream("output.docx"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
}

执行这个单元测试,就可以看到在项目所在目录下输出了新的文档 output.docx,打开这个文档,我们就可以看到如下图所示的内容:

大功告成,这就是渲染好的新文档了,是不是很简单,一行代码就完成了根据模板进行数据的渲染!

相关概念

模板

模板是 Docx 格式的 Word 文档,我们可以使用 Microsoft office、WPS Office 等软件来制作模板。

标签

上面说到 {{title}} 这种理解成占位符,实际上,官方是称之为「标签」。

所有的标签都是以 {{ 开头,以 }} 结尾,标签可以出现在任何位置,包括页眉,页脚,表格内部,文本框等。

表格布局可以设计出很多优秀专业的文档,推荐使用表格布局。

poi-tl 模板遵循 所见即所得 的设计,模板和标签的样式会被完全保留,就如我上面演示的,一级标题和字体颜色的样式就被保留下来了。

数据模型

数据模型,也就是我们需要渲染到模板中的数据,可以是哈希表,也可以是普通的 Java 对象。

  1. 哈希表(key 名是标签名):
Map<String, Object> data = new HashMap<>();
data.put("title", "Hello, poi-tl Word模板引擎");
data.put("text", "Hello World");
data.put("author", "god23bin");
data.put("description", "这还不关注 god23bin ?再不关注我可要求你关注了!");
  1. Java 对象(属性名是标签名):
public class DataModel {
    private String title;
    private String text;
    private String author;
    private String description;
    // 省略 getter 和 setter
}

DataModel data = new DataModel();
data.setTitle("Hello, poi-tl Word模板引擎");
data.setText("Hello World");
data.setAuthor("god23bin");
data.setDescription("这还不关注 god23bin ?再不关注我可要求你关注了!");

有了哈希表和或者 Java 对象的数据模型后,将这个数据丢给渲染的 API,就可以完成数据的渲染了。

标签的写法

poi-tl 里只有标签,那么我们需要知道标签的写法是怎样的。在 Word 文档里,可以有:文本、图片、表格、列表等元素,那么对应的,咱们的标签也有这些。

文本标签 {{var}}

简单粗暴,直接 {{标签名}} 就是文本标签了。

图片标签 {{@var}}

{{@标签名}} 就是图片标签,@ 标识了这个标签的类型是图片,其他的也是同理,不同的符号标识不同类型的标签。

表格标签 {{#var}}

使用 # 标识这是一个表格标签。

列表标签 {{*var}}

使用 * 标识这是一个列表标签。

其余的标签

剩下的标签还有很多,详细的内容你可以阅读官方文档,这里就不一一介绍了。

下面我将写下我用过的内容。

插件

插件,又称为自定义函数,它允许我们在模板标签位置处执行预先定义好的函数。由于插件机制的存在,我们几乎可以在模板的任何位置执行任意操作。

插件是 poi-tl 的核心,默认的标签和引用标签都是通过插件加载。

默认插件

poi-tl 默认提供了八个策略插件,用来处理文本、图片、列表、表格、文档嵌套、引用图片、引用多系列图表、引用单系列图表等:

  • TextRenderPolicy
  • PictureRenderPolicy
  • NumberingRenderPolicy
  • TableRenderPolicy
  • DocxRenderPolicy
  • MultiSeriesChartTemplateRenderPolicy
  • SingleSeriesChartTemplateRenderPolicy
  • DefaultPictureTemplateRenderPolicy

由于这 8 个插件是经常用到的,所以这些插件被注册为不同的标签类型,也就是我们看到过的 {{var}}、{{@var}}、{{#var}} 等不同类型的标签,从而搭建了 poi-tl 的标签体系。

除了这 8 个通用的策略插件外,还内置了一些额外用途的插件:

DynamicTableRenderPolicy动态表格插件,允许直接操作表格对象示例-动态表格
HackLoopTableRenderPolicy循环表格行,下文会详细介绍示例-表格行循环
LoopColumnTableRenderPolicy循环表格列示例-表格列循环
BookmarkRenderPolicy书签和锚点示例-Swagger文档
JSONRenderPolicy高亮显示JSON代码块示例-Swagger文档
AbstractChartTemplateRenderPolicy引用图表插件,允许直接操作图表对象
ParagraphRenderPolicy渲染一个段落,可以包含不同样式文本,图片等
DocumentRenderPolicy渲染多个段落和表格
TOCRenderPolicyBeta实验功能:目录,打开文档时需要更新域

使用插件

为了让插件在某个标签处执行,我们需要将插件与标签绑定

当我们有个模板标签为 {{description}},默认是文本标签,如果希望在这个位置做些不一样或者更复杂的事情,我们可以将插件应用到这个模板标签,比如渲染 HTML:

ConfigureBuilder builder = Configure.builder();
builder.bind("description", new HtmlRenderPolicy());

此时,{{description}} 将不再是一个文本标签,而是一个自定义的支持 HTML 渲染的标签。

当然,这里的 HTML 渲染的插件,默认是没有提供的,需要引入以下的依赖项,才能支持 HTML 的渲染。

<!-- 支持渲染 HTML 的插件 -->
<dependency>
    <groupId>io.github.draco1023</groupId>
    <artifactId>poi-tl-ext</artifactId>
    <version>0.3.3</version>
</dependency>

示例:我们对 {{author}} 这个标签绑定上支持 HTML 渲染的插件,这样就能渲染 HTML 的文本了。

@SpringBootTest
public class PoiTlApplicationTest {

    @Test
    public void test() {
        // 获取 Word 模板所在路径
        String filepath = this.getClass().getClassLoader().getResource("hello-world.docx").getPath();
        // 给标签绑定插件
        Configure configure = Configure.builder().bind("author", new HtmlRenderPolicy()).build();
        // 通过 XWPFTemplate 编译文件并渲染数据到模板中
        XWPFTemplate template = XWPFTemplate.compile(filepath, configure).render(
                new HashMap<String, Object>(){{
                    put("title", "Hello, poi-tl Word模板引擎");
                    put("text", "Hello World");
                    put("author", "<h2>god23bin</h2>");
                    put("description", "这还不关注 god23bin ?再不关注我可要求你关注了!");
                }});
        try {
            // 将完成数据渲染的文档写出
            template.writeAndClose(new FileOutputStream("output.docx"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
}

生成的 Word 文档如下图所示,可以看到 {{author}} 这个标签的 HTML 文本已经渲染成功了!(蓝框框)

表格行循环

当有类似如下需求的时候,在表格里展示多行同类型的数据,那么就需要用到表格的行循环了。

这里也是涉及到插件的,具体就是使用 HackLoopTableRenderPolicy 这个插件策略,这个策略能够根据集合数据进行循环渲染,这样就渲染数据到表格的行上了,集合有多少个元素,那么就有多少行。

我们来看下这个表格行循环的模板是怎样写的,是这样的:

可以看到 {{articles}}{{columns}} 是标准的文本标签,我们这里将这两个标签置于循环行的上一行,循环行里设置要循环的标签和内容,注意这里的标签是使用 [] 的,以此来区分标准的标签语法。

同时 {{articles}}{{columns}} 标签对应的数据就是文章和专栏的集合。

我们写一个该模板的数据模型,以 Java 对象来写,同时模拟数据从数据库中读取。

数据模型:AcWordModel

public class AcWordModel {
    /**
     * 文章明细数据模型-表格行循环
     */
    private List<Article> articles;
    /**
     * 专栏明细数据模型
     */
    private List<SpecialColumn> columns;
    // 省略 getter 和 setter
}

其中的 Article 和 SpecialColumn 模型如下:

public class Article {
    private String title;
    private String tags;
    private Integer reading;
    private Integer likes;
    // 省略 getter 和 setter
}
public class SpecialColumn {
    private String name;
    private Integer subscription;
    private Integer nums;
    // 省略 getter 和 setter
}

进行测试,获取数据和模板,让标签和表格行循环的插件进行绑定

    @Test
    public void rowLoopTest() {
        // 获取数据,这里假装是从数据库中查询得到的
        AcWordModel data = getFromDB();
        // 获取 Word 模板所在路径
        String filepath = this.getClass().getClassLoader().getResource("table-row-loop.docx").getPath();
        // 给标签绑定插件,这里就绑定表格行循环的插件
        Configure configure = Configure.builder()
                .bind("articles", new HackLoopTableRenderPolicy())
                .bind("columns", new HackLoopTableRenderPolicy())
                .build();
        // 通过 XWPFTemplate 编译文件并渲染数据到模板中
        XWPFTemplate template = XWPFTemplate.compile(filepath, configure).render(data);
        try {
            // 将完成数据渲染的文档写出
            template.writeAndClose(new FileOutputStream("ac-word.docx"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

这样,就能实现表格的行循环了!

封装 Word 渲染生成新文档的工具

我们可以再封装下这个 API,写一个工具类,如下:

package cn.god23bin.demo.util;

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;

public class WordUtil {

    /**
     * 生成 word 文档
     * @param wordTemplatePath    word 模板路径
     * @param targetWordFilePath  生成目标文档路径
     * @param data                待渲染的数据模型-哈希表形式
     */
    public static void generateWordFile(String wordTemplatePath, String targetWordFilePath, Map<String, Object> data) {
        XWPFTemplate template = XWPFTemplate.compile(wordTemplatePath).render(data);
        try {
            template.writeAndClose(new FileOutputStream(targetWordFilePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成 word 文档
     * @param wordTemplatePath    word 模板路径
     * @param targetWordFilePath  生成目标文档路径
     * @param data                待渲染的数据模型-Java对象形式
     */
    public static void generateWordFile(String wordTemplatePath, String targetWordFilePath, Object data) {
        XWPFTemplate template = XWPFTemplate.compile(wordTemplatePath).render(data);
        try {
            template.writeAndClose(new FileOutputStream(targetWordFilePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成 word 文档
     * @param wordTemplatePath    word 模板路径
     * @param targetWordFilePath  生成目标文档路径
     * @param data                待渲染的数据模型-哈希表形式
     * @param configure           渲染配置
     */
    public static void generateWordFile(String wordTemplatePath, String targetWordFilePath, Map<String, Object> data, Configure configure) {
        XWPFTemplate template = XWPFTemplate.compile(wordTemplatePath, configure).render(data);
        try {
            template.writeAndClose(new FileOutputStream(targetWordFilePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成 word 文档
     * @param wordTemplatePath    word 模板路径
     * @param targetWordFilePath  生成目标文档路径
     * @param data                待渲染的数据模型-Java对象形式
     * @param configure           渲染配置
     */
    public static void generateWordFile(String wordTemplatePath, String targetWordFilePath, Object data, Configure configure) {
        XWPFTemplate template = XWPFTemplate.compile(wordTemplatePath, configure).render(data);
        try {
            template.writeAndClose(new FileOutputStream(targetWordFilePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

最后的最后

希望各位屏幕前的靓仔靓女们给个三连!你轻轻地点了个赞,那将在我的心里世界增添一颗明亮而耀眼的星!

咱们下期再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/995996.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux平台如何实现采集音视频数据并注入轻量级RTSP服务?

技术背景 好多开发者&#xff0c;问我们最多的问题是&#xff0c;为什么要设计轻量级RTSP服务&#xff1f;轻量级RTSP服务&#xff0c;和RTSP服务有什么区别&#xff1f; 针对这个问题&#xff0c;我们的回答是&#xff1a;轻量级RTSP服务解决的核心痛点是避免用户或者开发者…

59、SpringBoot 自定义JSON的序列化器和反序列化器

Serialization(序列化)&#xff1a; 将java对象以一连串的字节码保存在磁盘文件中的过程&#xff0c;也可以说是保存java对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中)。 deserialization(反序列化)&#xff1a; 将保存在磁盘文件中的java字节码重新转…

关于µC/OS-III 多任务的基本理解

关于C/OS-III 多任务的基本理解 任务和任务管理是 RTOS 的核心&#xff0c;且大多数项目使用 RTOS 的目的就是为了使用 RTOS 的多任务管理能力。 C/OS-III作为经典的RTOS&#xff0c;了解并学习其任务管理机制&#xff0c;是非常有必要的。 文章目录 关于C/OS-III 多任务的基本…

进阶C语言-指针的进阶(中)

指针的进阶 &#x1f4d6;5.函数指针&#x1f4d6;6.函数指针数组&#x1f4d6;7.指向函数指针数组的指针&#x1f4d6;8.回调函数 &#x1f4d6;5.函数指针 数组指针 - 指向数组的指针 - 存放的是数组的地址 - &数组名就是数组的地址。 函数指针 - 指向函数的指针 - 存放的…

【Java】session的工作原理

文章目录 一、session的概念及特点session概念session主要有以下的这些特点 二、为什么要使用session三、session的工作原理四、session的生命周期Session何时生效Session何时失效 五、session的性能瓶颈六、session实现登录状态的控制 一、session的概念及特点 session概念 …

无涯教程-JavaScript - COUPDAYSNC函数

描述 COUPDAYSNC函数返回从结算日期到下一个息票日期的天数。 语法 COUPDAYSNC (settlement, maturity, frequency, [basis])争论 Argument描述Required/OptionalSettlement 证券的结算日期。 证券结算日期是指在发行日期之后将证券交易给买方的日期。 RequiredMaturity 证…

基于第二代 ChatGLM2-6B P-Tuning v2 微调训练医疗问答任务

今天是教师节&#xff0c;恭祝全体老师们节日快乐&#xff01;&#x1f60a; 一、ChatGLM2-6B 在本专栏前面文章中实验了使用 ChatYuan-large-v2 Freeze 微调训练医疗问答任务&#xff0c;训练后效果整体还可以&#xff0c;这篇文章继续探索使用最近比较火的 ChatGLM 官方推出…

docker镜像详解

目录 什么是docker镜像镜像相关命令docker pulldocker imagesdocker searchdocker rmi导出 / 导入镜像 镜像分层镜像摘要镜像摘要的作用分发散列值 什么是docker镜像 Docker镜像是Docker容器的基础组件&#xff0c;它包含了运行一个应用程序所需的一切&#xff0c;包括代码、运…

Sharding-Jdbc(2):Sharding-Jdbc入门案例

1 前置条件 搭建读写分离的数据库环境,搭建方法如下文,目标数据库test Mysql性能优化(5):主从同步原理与实现_mysql主从配置优化_不死鸟.亚历山大.狼崽子的博客-CSDN博客 2 新建maven项目 3 pom引入依赖 <?xml version="1.0" encoding="UTF-8"…

《protobuf》基础语法2

文章目录 枚举类型ANY 类型oneof 类型map 类型改进通讯录实例 枚举类型 protobuf里有枚举类型&#xff0c;定义如下 enum PhoneType {string home_addr 0;string work_addr 1; }同message一样&#xff0c;可分为 嵌套定义&#xff0c;文件内定义&#xff0c;文件外定义。不…

二维码智慧门牌管理系统:智能化地址管理,提升社会治理效率

文章目录 前言一、地址管理挑战二、二维码智慧门牌管理系统解决方案 前言 随着科技的飞速发展&#xff0c;我们的生活正经历前所未有的变革。尤其是智能化技术&#xff0c;已经深刻影响了我们的日常生活和工作环境。然而&#xff0c;在某些领域&#xff0c;如地址管理和社会治…

十大免费好用的视频软件推荐,新手小白必备

很多人都在使用视频软件进行编辑&#xff0c;那么你们知道哪些视频软件是免费的而且还好用&#xff1f; 现在很多小伙伴比较喜欢用手机编辑视频&#xff0c;而这些剪辑软件具有领先的智能化AI技术&#xff0c;让你不用绿幕就能一键挖出图像&#xff0c;不仅可以一键挖出三维人…

获取板块分类并展示

板块分类也会变动&#xff0c;偶尔看下&#xff0c;利于总体分析大盘 https:dapanyuntu.com/ 该网站含有板块信息 分析接口 搜素关键字 拷贝curl到curl解析工具&#xff0c;去掉无用的参数&#xff0c;生成requests代码 尝试nginx反代接口 server {listen 443;loca…

汇率失守7.3关口

号外&#xff1a;9.8教链内参《被判入狱1万年》。 拉锯多时的离岸人民币汇率USDCNH失守7.3关口&#xff0c;隔夜冲破7.36的高位。 为什么7.3是一个关键关口&#xff1f;因为这里是2022年10月底、11月初时曾经测试过的支撑位&#xff08;从CNH的角度说&#xff09;。 如果支撑位…

微信视频号挂公众号文章链接新方法:不限次数,不限号

当看到自己身边的人&#xff0c;通过我分享的方法绑定成功&#xff0c;那是开心到起飞了。 因为我知道&#xff1a;外面不靠谱的人很多&#xff01;分享不靠谱方法的人&#xff0c;可谓是更多。 为什么我要主动分享视频号评论区挂公众号文章链接&#xff1f;总有人伸张正义&a…

java中log使用总结

目录 一、概述1.1. 核心日志框架1.2 门面日志框架 二、最佳实践2.1 核心日志框架API包2.2 门面日志框架依赖2.3 集成使用2.3.1 集成jcl2.3.2 集成slf4j2.3.2.1 slf4j集成单一框架2.3.2.2 slf4j整合混合框架 三、总结3.1 所有相关包3.1.1 核心日志框架包3.1.2 门面日志框架3.1.3…

Python元类(metaclass)

Python 是一种强大的编程语言&#xff0c;一部分得益于其语言设计中独特的元类&#xff08;Metaclass&#xff09;机制。尽管元类的概念在刚开始接触时可能会让人感到困惑&#xff0c;但一旦理解了它们的工作原理和应用方式&#xff0c;我们就可以用它们做出强大且灵活的抽象。…

无涯教程-JavaScript - COUPDAYS函数

描述 COUPDAYS函数返回包含结算日期的息票期限内的天数。 语法 COUPDAYS (settlement, maturity, frequency, [basis])争论 Argument描述Required/OptionalSettlement 证券的结算日期。 证券结算日期是指在发行日期之后将证券交易给买方的日期。 RequiredMaturity 证券的到…

Nginx重写功能

Nginx重写功能 一、Nginx常见模块二、访问路由location2.1location常用正则表达式2.2、location的分类2.3、location常用的匹配规则2.4、location优先级排列说明2.5、location示例2.6、location优先级总结2.7、实例2.7.1、location/{}与location/{}2.7.2、location/index.html{…

joplin更新后找不到文章

Joplin的数据默认是存储在C:\Users\Username.config\joplin-desktop下的。我修改为了D:\joplinnotes 这样就导致在升级覆盖安装的时候&#xff0c;笔记丢失路径。如果记不起之前笔记保存在哪里&#xff0c;也可以搜索类似文件来回忆之前自己保存笔记的位置 cache\ plugins\ re…