Spring Boot 中使用 Poitl 渲染数据并生成 Word 文档

2023年 9月 12日 244.3k 0

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

前言

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

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

什么是 Poi-tl ?

官网:deepoove.com/poi-tl/1.9.…

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 项目作为演示,屏幕前的朋友们可以一起跟着我的步骤来,实践一番!

  • 首先创建一个 Spring Boot 项目,版本目前我的 Demo 是 2.2.1,你可以更改你的 Spring Boot 版本,那现在我这里已经创建好了。
  • 其中, pom.xml 只有两个依赖项,一个 web 和一个 test :

    
        org.springframework.boot
        spring-boot-starter-web
    
    
    
        org.springframework.boot
        spring-boot-starter-test
        test
    
    
  • 接着在 pom.xml 中引入 Poi-tl 的依赖项:
  • 
    
        com.deepoove
        poi-tl
        1.9.1
    
    
  • 准备一个 Word 模板
  • 这一步你可以自己动手做一个 Word 模板,这里我先演示下。就先创建一个名为 Hello World.docx 的 Word 文档,模板内容如下:

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

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

  • 获取模板所在的路径,并将数据渲染到模板上
  • 渲染只需一行代码,就是使用 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(){{
                        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 对象。

  • 哈希表(key 名是标签名):
  • Map data = new HashMap();
    data.put("title", "Hello, poi-tl Word模板引擎");
    data.put("text", "Hello World");
    data.put("author", "god23bin");
    data.put("description", "这还不关注 god23bin ?再不关注我可要求你关注了!");
    
  • 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 渲染多个段落和表格
    TOCRenderPolicy Beta实验功能:目录,打开文档时需要更新域

    使用插件

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

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

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

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

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

    
    
        io.github.draco1023
        poi-tl-ext
        0.3.3
    
    

    示例:我们对 {{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(){{
                        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(); } } }

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

    表格行循环

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

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

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

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

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

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

    数据模型:AcWordModel

    public class AcWordModel {
        /**
         * 文章明细数据模型-表格行循环
         */
        private List articles;
        /**
         * 专栏明细数据模型
         */
        private List 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 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 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();
            }
        }
    }
    
    

    最后的最后

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

    咱们下期再见!

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论