Word模板引擎poi-tl


poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建Word文档。

◆ 方案对比

方案 移植性 功能性 易用性
Poi-tl Java跨平台 Word模板引擎 基于Apache POI,更友好的API
Apache POI Java跨平台 Apache项目,不仅封装了易用的文档API(文本、图片、表格、页眉、页脚、图表等),也可以在底层直接操作XML结构 文档不全,推荐教程:Apache POI Word快速入门
Freemarker XML跨平台 仅支持文本,很大的局限性 不推荐,需要维护XML结构,代码后期不可维护
OpenOffice 部署OpenOffice,移植性较差 - 需要了解OpenOffice的API
HTML浏览器导出 依赖浏览器的实现,移植性较差 HTML不能很好的兼容Word的格式 -
Jacob、winlib Windows平台 - 复杂,完全不推荐使用

◆ 版本

截止2023-06-14,poi-tl版本迭代:

  • 1.12.1:Apache POI5.2.2+,JDK1.8+
  • 1.11.x:Apache POI5.1.0+,JDK1.8+
  • 1.10.x:Apache POI4.1.2,JDK1.8+
  • 1.9.x:Apache POI4.1.2,JDK1.8+
  • 1.8.x:Apache POI4.1.2,JDK1.8+
  • 1.7.x:Apache POI4.0.0+,JDK1.8+
  • 1.6.x:Apache POI4.0.0+,JDK1.8+
  • 1.5.x:Apache POI3.16+,JDK1.6+

注意:1.12.x版本作了一个不兼容的改动,升级的时候需要注意:

  • 重构了PictureRenderData,改为抽象类,建议使用Pictures工厂方法来创建图片数据

下文使用的版本为1.10.5

<dependency>
  <groupId>com.deepoove</groupId>
  <artifactId>poi-tl</artifactId>
  <version>1.10.5</version>
</dependency>

◆ 特性

poi-tl是一个基于Apache POI的Word模板引擎。

引擎功能 描述
文本 将标签渲染为文本
图片 将标签渲染为图片
表格 将标签渲染为表格
列表 将标签渲染为列表
图表 条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、饼图(3D饼图)、散点图等图表渲染
If Condition判断 隐藏或者显示某些文档内容(包括文本、段落、图片、表格、列表、图表等)
Foreach Loop循环 循环某些文档内容(包括文本、段落、图片、表格、列表、图表等)
Loop表格行 循环复制渲染表格的某一行
Loop表格列 循环复制渲染表格的某一列
Loop有序列表 支持有序列表的循环,同时支持多级列表
代码高亮 word中代码块高亮展示,支持26种语言和上百种着色样式
Markdown 将Markdown渲染为word文档
Word批注 完整的批注功能,创建批注、修改批注等
Word附件 Word中插入附件
SDT内容控件 内容控件内标签支持
图片替换 将原有图片替换成另一张图片
书签、锚点、超链接 支持设置书签,文档内锚点和超链接功能
Expression Language 完全支持SpringEL表达式,可以扩展更多的表达式:OGNL, MVEL…​
标签定制 支持自定义标签前后缀
文本框 文本框内标签支持
样式 模板即样式,同时代码也可以设置样式
模板嵌套 模板包含子模板,子模板再包含子模板
合并 Word合并Merge,也可以在指定位置进行合并
用户自定义函数(插件) 在文档任何位置执行函数

◆ 模板

模板为Docx格式的Word文档:

  • 可以使用Microsoft office、WPS Office、Pages等软件制作模板
  • 可以使用Apache POI代码来生成模板

◆ 数据

数据类似于哈希或者字典:

  • 可以是Map结构
  • 可以是对象

◆ 输出

以流的方式进行输出,可以写到任意输出流中。

使用完毕记得关闭这些流。可以使用PoitlIOUtils工具类来关闭流:

扫描二维码关注公众号,回复: 15864222 查看本文章
PoitlIOUtils.closeQuietlyMulti(final Closeable... cls);

◆ 数据模型

所有的数据模型都实现了RenderData接口。

RenderData接口是用来表示POI-TL模板中的数据渲染方式的接口.

常见的几个实现类:

  1. ImageRenderData

    此类用于在POI-TL模板中添加图片的渲染方式。它包括以下属性:

    属性名 描述
    String uri 图片的地址,可以是网络地址或本地文件路径
    int width 图片的宽度,单位是像素
    int height 图片的高度,单位是像素
  2. TextRenderData

    此类用于在POI-TL模板中添加文本的渲染方式。它包括以下属性:

    属性名 描述
    String text 文本内容
    FontRenderData font 文本字体样式渲染方式

    其中,FontRenderData是另外一个实现RenderData接口的类,用于设置文本字体的样式。

  3. TableRenderData

    此类用于在POI-TL模板中添加表格的渲染方式。它包括以下属性:

    属性名 描述
    List<? extends TableRowRenderData> rows 表格行数据列表
    TableStyle style 表格样式

    其中,TableRowRenderData是另外一个实现RenderData接口的类,用于设置表格每一行的数据和样式。TableStyle是用于设置表格样式的类。

  4. ChartRenderData

    此类用于在POI-TL模板中添加图表的渲染方式。它包括以下属性:

    属性名 描述
    ChartData chartData 图表数据
    String title 图表标题
    int width 图表宽度,单位是像素
    int height 图表高度,单位是像素

    其中,ChartData是另外一个实现RenderData接口的类,用于设置图表数据。

  5. HyperLinkRenderData

    此类用于在POI-TL模板中添加超链接的渲染方式。它包括以下属性:

    属性名 描述
    String text 超链接文本
    String url 超链接地址
  6. StringRenderData

    此类用于在POI-TL模板中添加字符串的渲染方式。

◆ 标签

poi-tl是一种无逻辑「logic-less」的模板引擎,没有复杂的控制结构和变量赋值,只有标签。标签由前后两个大括号组成。

1. 文本

格式:{ {var}}

支持的数据模型:

  • String:文本
  • Object:文本,调用 toString() 方法转化为文本
  • TextRenderData :有样式的文本
  • HyperlinkTextRenderData :超链接和锚点文本

2. 图片

图片标签以@开始

格式:{ {@var}}

支持的数据模型:

  • String:图片url或者本地路径(默认使用图片自身尺寸)
  • PictureRenderData:
  • FilePictureRenderData:继承PictureRenderData
  • UrlPictureRenderData:继承PictureRenderData
  • ByteArrayPictureRenderData:继承PictureRenderData

3. 表格

表格标签以#开始

格式:{ {#var}}

支持的数据模型:

  • TableRenderData

4. 列表

列表标签以*开始

格式:{ {*var}}

支持的数据模型:

  • List
  • NumberingRenderData

5. 嵌套

嵌套又称为导入、包含或者合并,以+*开始

格式:{ {*var}}

支持的数据模型:

  • DocxRenderData:推荐使用工厂 Includes 构建嵌套模型。

6. 区块对

区块对由前后两个标签组成,以?开始,以/结束。

格式:{ {?var}}{ {/var}}

区块对开始和结束标签中间可以包含多个图片、表格、段落、列表、图表等,开始和结束标签可以跨多个段落,也可以在同一个段落,但是如果在表格中使用区块对,开始和结束标签必须在同一个单元格内,因为跨多个单元格的渲染行为是未知的。

区块对在处理一系列文档元素的时候非常有用,位于区块对中的文档元素可以被渲染零次,一次或N次,这取决于区块对的取值。

区块对判断:

可以把区块对理解为条件判断。判断:null、true、false、是否集合、空集合、非空集合等。

  1. false或空集合

    隐藏区块中的所有文档元素

    如果区块对的值是 null 、false 或者空的集合,位于区块中的所有文档元素将不会显示,这就等同于if语句的条件为 false。

  2. 非false且不是集合

    显示区块中的文档元素,渲染一次

    如果区块对的值不为 null 、 false ,且不是集合,位于区块中的文档元素会被渲染一次,这就等同于if语句的条件为 true。

  3. 非空集合

    根据集合的大小,循环渲染区块中的文档元素

    如果区块对的值是一个非空集合,位于区块中的文档元素会被迭代渲染一次或者N次,这取决于集合的大小,类似于foreach语法。

循环内置变量:

poi-tl提供了一些内置变量,这些内置变量只能用于区块对中

下面的内置变量,#this可以用于所有任意区块中,其它都是集合相关,只能用于循环中。

变量 类型 说明
#this object 引用当前对象,由于#和已有表格标签标识冲突,所以在文本标签中需要使用=号标识来输出文本
_index int 返回当前迭代从0开始的索引
_is_first boolean 辨别循环项是否是当前迭代的第一项
_is_last boolean 辨别循环项是否是当前迭代的最后一项
_has_next boolean 辨别循环项是否是有下一项
_is_even_item boolean 辨别循环项是否是当前迭代间隔1的奇数项
_is_odd_item boolean 辨别循环项是否是当前迭代间隔1的偶数项

◆ SpingEL

支持在标签中使用SpringEL表达式,需要将标签配置为SpringEL模式。

需要引入相应的依赖:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-expression</artifactId>
  <version>5.3.18</version>
</dependency>
Configure configure = Configure.builder()
        .useSpringEL()
        .build();

SpringEL使用示例:

{
    
    {
    
    name}}						
{
    
    {
    
    name.toUpperCase()}}		// 类方法调用,转大写
{
    
    {
    
    name == 'poi-tl'}}		// 	判断条件	
{
    
    {
    
    sex ? '男' : '女'}} 		// 三元运算
{
    
    {
    
    new java.text.SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(time)}}	// 类方法调用,时间格式化
{
    
    {
    
    price/10000 + '万元'}} 	// 运算符
{
    
    {
    
    dogs[0].name}} 			// 	数组列表使用下标访问
{
    
    {
    
    localDate.format(T(java.time.format.DateTimeFormatter).ofPattern('yyyy年MM月dd日'))}}	// 使用静态类方法

SpringEL作为区块对:

使用SpringEL时区块对的结束标签是:{ {/}}。

使用示例:

data-model:

{
    
    
  "desc": "",
  "summary": "哈哈",
  "produces": [
    "application/xml"
  ]
}

template.docx:

{
    
    {
    
    ?desc == null or desc == ''}}{
    
    {
    
    summary}}{
    
    {
    
    /}}

{
    
    {
    
    ?produces == null or produces.size() == 0}}{
    
    {
    
    /}}

output.docx:

哈哈
# ◆ 引用标签
引用标签是一种特殊位置的特殊标签,提供了直接引用文档中的元素句柄的能力,这个重要的特性在我们只想改变文档中某个元素极小一部分样式和属性的时候特别有用,因为其余样式和属性都可以在模板中预置好,真正的所见即所得。

## 1. 图片
引用图片标签是一个文本:{
    
    {
    
    var}},标签位置在:设置图片格式—​可选文字—​标题或者说明(新版本Microsoft Office标签位置在:编辑替换文字-替换文字)。
![在这里插入图片描述](https://img-blog.csdnimg.cn/8d7177b3c55c467b9a573cd98ec8936d.png)
引用图片标签只会替换图片而不会改变图片尺寸和布局,数据模型和图片标签一致:PictureRenderData 。

代码:
```java
put("img", Pictures.ofLocal("sayi.png").create());

2. 单系列图标

单系列图表指的是饼图(3D饼图)、圆环图等。

单系列图表的标签是一个文本:{ {var}},标签位置在:图表区格式—​可选文字—​标题(新版本Microsoft Office标签位置在:编辑替换文字-替换文字)。

在这里插入图片描述

使用数据模型ChartSingleSeriesRenderData。

推荐使用工厂 Charts 构建图表模型。

代码:

ChartSingleSeriesRenderData pie = Charts
                .ofSingleSeries("ChartTitle", new String[] {
    
     "美国", "中国" })
                .series("countries", new Integer[] {
    
     9826675, 9596961 })
                .create();

put("pieChart", pie);
{
    
    
  "chartTitle": "ChartTitle", # 图表标题
  "categories": [ # 种类
    "美国",
    "中国"
  ],
  "seriesData": {
    
     # 单系列
    "name": "countries", # 单系列名称
    "values": [ # 单系列对应每个种类的值

      9826675,
      9596961
    ]
  }
}

3. 多系列图标

多系列图表指的是条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图等。

多系列图表的标签是一个文本:{ {var}},标签位置在:图表区格式—​可选文字—​标题(新版本Microsoft Office标签位置在:编辑替换文字-替换文字)。

在这里插入图片描述
使用数据模型ChartMultiSeriesRenderData。

推荐使用工厂 Charts 构建图表模型。

代码:

ChartMultiSeriesRenderData chart = Charts
                .ofMultiSeries("ChartTitle", new String[] {
    
     "中文", "English" })
                .addSeries("countries", new Double[] {
    
     15.0, 6.0 })
                .addSeries("speakers", new Double[] {
    
     223.0, 119.0 })
                .create();

put("barChart", chart);

新的图表系列数据会完全替换原有图表数据,而原有图表的样式都会被保留。

{
    
    
  "chartTitle": "ChartTitle", # 图表标题
  "categories": [ # 种类
    "中文", "English"
  ],
  "seriesDatas": [ # 所有系列
    {
    
    
      "name": "countries", # 当前系列名称
      "values": [ # 当前系列对应每个种类的值
        15, 6
      ]
    },
    {
    
    
      "name": "speakers",
      "values": [
        223, 119
      ]
    }
  ]
}

4. 组合图表

组合图表指的是由多系列图表(柱形图、折线图、面积图)组合而成的图表。

组合图表的标签是一个文本:{ {var}},标签位置在:图表区格式—​可选文字—​标题(新版本Microsoft Office标签位置在:编辑替换文字-替换文字)。

在这里插入图片描述
同多系列图表 ChartMultiSeriesRenderData 数据模型。

代码:

ChartSingleSeriesRenderData comb = Charts
                .ofComboSeries("MyChart", new String[] {
    
     "中文", "English" })
                .addBarSeries("countries", new Double[] {
    
     15.0, 6.0 })
                .addBarSeries("speakers", new Double[] {
    
     223.0, 119.0 })
                .addBarSeries("NewBar", new Double[] {
    
     223.0, 119.0 })
                .addLineSeries("youngs", new Double[] {
    
     323.0, 89.0 })
                .addLineSeries("NewLine", new Double[] {
    
     123.0, 59.0 }).create();

put("combChart", comb);
{
    
    
  "chartTitle": "MyChart", # 图表标题
  "categories": [ # 种类
    "中文", "English"
  ],
  "seriesDatas": [#  所有系列
    {
    
    
      "name": "countries", # 当前系列名称
      "comboType": "BAR", # 当前系列的图表类型comboType:柱形图BAR、折线图LINE、面积图AREA
      "values": [ # 当前系列对应每个种类的值
        15, 6
      ]
    },
    {
    
    
      "name": "speakers",
      "comboType": "LINE",
      "values": [
        223, 119
      ]
    }
  ]
}

◆ 配置

poi-tl提供了类 Configure 来配置常用的设置。

1. 标签前后缀

默认的标签前缀是{ {,后缀是}}。可以通过配置自定义标签前后缀。

Configure configure = Configure.builder()
        .buildGramer("${", "}")
        .build();

2. 标签类型

默认的图片标签以@开始、表格标签以#开始、列表标签以*开始等。可以通过配置自定义标签类型以任何字符开始。

Configure configure = Configure.builder()
        .addPlugin('~', new PictureRenderPolicy())  // 图片标签以~开始
        .addPlugin('^', new TableRenderPolicy())    // 表格标签以^开始
        .build();

3. 标签匹配值

标签默认支持中文、字母、数字、下划线的组合,可以通过正则表达式来配置标签的规则。

Configure configure = Configure.builder()
        .buildGrammerRegex("[\\w]+(\\.[\\w]+)*")    // 标签不支持中文
        .build();

4. 标签值计算

标签值计算是指如何在数据模型中索引标签Key的值,可以自定义获取标签值的方式。

Configure configure = Configure.builder()
        .setRenderDataComputeFactory(new RenderDataComputeFactory())
        .build();

注意:RenderDataComputeFactory是一个抽象工厂,可以定义自己的工厂提供标签表达式计算接口 RenderDataCompute 的实现。

通过此方式支持任何的表达式引擎,Spring表达式正是通过 SpELRenderDataCompute 实现。

5. SpringEL

可以在模板标签中使用SpringEL表达式,需要将标签配置为SpringEL模式。

需要引入相应的依赖:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-expression</artifactId>
  <version>5.3.18</version>
</dependency>
Configure configure = Configure.builder()
        .useSpringEL()
        .build();

6. 数据模型序列化

数据模型支持JSON字符串序列化,可以方便的构造远程HTTP或者RPC服务。

需要引入相应依赖,并配置数据模型前置转化器:

<dependency>
	<groupId>com.deepoove</groupId>
	<artifactId>poi-tl-jsonmodel-support</artifactId>
	<version>1.0.0</version>
</dependency>
Configure configure = Configure.builder()
        .addPreRenderDataCastor(new GsonPreRenderDataCastor())
        .build();

7. 错误处理

支持在发生错误的时候定制引擎的行为。

标签无法被计算

  • 模板中引用了一个不存在的变量

  • 级联的前置结果不是一个哈希

    如 { {author.name}} 中author的值为null,此时就无法计算name的值

poi-tl可以在发生这种错误时对计算结果进行配置,默认会认为标签值为null。当我们需要严格校验模板是否有人为失误时,可以抛出异常,如果使用SpringEL表达式,可以通过参数来配置是否抛出异常。

标签数据类型不合法
渲染图片、表格等标签时对数据模型是有要求的,如果数据不合法(为空或者是一个错误的数据类型),可以配置模板标签的渲染行为。
poi-tl默认的行为会清空标签。

  • 如果希望对标签不作任何处理:

    Configure configure = Configure.builder()
            .setValidErrorHandler(new Configure.DiscardHandler())
            .build();
    
  • 如果希望执行严格的校验,直接抛出异常:

    Configure configure = Configure.builder()
            .setValidErrorHandler(new Configure.AbortHandler())
            .build();
    

8. 模板生成模板

模板引擎不仅仅可以生成文档,也可以生成新的模板。

如,把原先的一个文本标签分成一个文本标签和一个表格标签:

Configure config = Configure.builder().bind("title", new DocumentRenderPolicy()).build();

Map<String, Object> data = new HashMap<>();

DocumentRenderData document = Documents.of()
        .addParagraph(Paragraphs.of("{
    
    {title}}").create())
        .addParagraph(Paragraphs.of("{
    
    {#table}}").create())
        .create();
data.put("title", document);

9. 无模板创建文档

使用 XWPFTemplate.create 在无需模板的情况下创建文档,可以充分利用poi-tl友好的API来生成文档元素。

String text = "this a paragraph";
DocumentRenderData data = Documents.of().addParagraph(Paragraphs.of(text).create()).create();
XWPFTemplate template = XWPFTemplate.create(data);

10. 日志

poi-tl使用slf4j作为日志门面,可以自由选择日志实现,比如logback、log4j等,以logback为例:

在项目中添加logback依赖:

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-core</artifactId>
  <version>1.2.3</version>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
</dependency>

配置logback.xml文件,可以配置日志级别和格式:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{
    
    HH:mm:ss.SSS} [%thread] %-5level %logger{
    
    36} - %msg%n</pattern>
    </encoder>
  </appender>

  <logger name="com.deepoove.poi" level="debug" additivity="false">
    <appender-ref ref="STDOUT" />
  </logger>
  <root level="info">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

debug级别的日志会打印解析渲染过程中的信息,有利于程序调试。在模板引擎执行结束后会打印耗时信息,如:

Successfully Render the template file in 13 millis

◆ 插件

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

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

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

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

这八个插件注册为不同的标签类型,从而搭建了poi-tl的标签体系,也构筑了poi-tl高度自由的插件机制。

除了八个通用的策略插件外,还内置了一些其它插件:

  • ParagraphRenderPolicy:渲染一个段落,可以包含不同样式文本,图片等
  • DocumentRenderPolicy:渲染整个word文档
  • CommentRenderPolicy:完整的批注功能
  • AttachmentRenderPolicy:插入附件功能
  • LoopRowTableRenderPolicy:循环表格行,下文会详细介绍
  • LoopColumnTableRenderPolicy:循环表格列
  • DynamicTableRenderPolicy:动态表格插件,允许直接操作表格对象
  • BookmarkRenderPolicy:书签和锚点
  • AbstractChartTemplateRenderPolicy:引用图表插件,允许直接操作图表对象
  • TOCRenderPolicy:Beta实验功能:目录,打开文档时会提示更新域
  • HighlightRenderPolicy:Word支持代码高亮(需要引入对应Maven依赖)
  • MarkdownRenderPolicy:使用Markdown来渲染word(需要引入对应Maven依赖)

1. 表格行循环

LoopRowTableRenderPolicy 是一个特定场景的插件,根据集合数据循环表格行。

示例:

货物明细和人工费在同一个表格中,货物明细需要展示所有货物,人工费需要展示所有费用。{ {goods}} 是个标准的标签,将 { {goods}} 置于循环行的上一行,循环行设置要循环的标签和内容,注意此时的标签应该使用 [] ,以此来区别poi-tl的默认标签语法。同理,{ {labors}} 也置于循环行的上一行。

template.docx:
在这里插入图片描述{ {goods}} 和 { {labors}} 标签对应的数据分别是货物集合和人工费集合,如果集合为空则会删除循环行。

class Goods {
    
    
  private int count;
  private String name;
  private String desc;
  private int discount;
  private int tax;
  private int price;
  private int totalPrice;
  // getter setter
}

class Labor {
    
    
  private String category;
  private int people;
  private int price;
  private int totalPrice;
  // getter setter
}

代码:

List<Goods> goods = new ArrayList<>();
List<Labor> labors = new ArrayList<>();
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();

Configure config = Configure.builder()
        .bind("goods", policy)	// 	绑定插件
        .bind("labors", policy)	// 	绑定插件
        .build(); 

XWPFTemplate template = XWPFTemplate.compile(resource, config).render(
  new HashMap<String, Object>() {
    
    {
    
    
      put("goods", goods);
      put("labors", labors);
    }}
);

output.docx:
在这里插入图片描述

2. 表格列循环

LoopColumnTableRenderPolicy 是一个特定场景的插件,根据集合数据循环表格列。要注意的是,由于文档宽度有限,因此模板列必须设置宽度,所有循环列将平分模板列的宽度。

template.docx:
在这里插入图片描述
LoopColumnTableRenderPolicy 循环列的使用方式和插件 LoopRowTableRenderPolicy 是一样的,需要将占位标签放在循环列的前一列。

代码:

LoopColumnTableRenderPolicy policy = new LoopColumnTableRenderPolicy();

Configure config = Configure.builder().bind("goods", policy).build();

XWPFTemplate template = XWPFTemplate.compile(resource, config).render(
  new HashMap<String, Object>() {
    
    {
    
    
      put("goods", goods);
    }}
);

output.docx:
在这里插入图片描述

3. 动态表格

当需求中的表格更加复杂的时候,我们完全可以设计好那些固定的部分,将需要动态渲染的部分单元格交给自定义模板渲染策略。poi-tl提供了抽象表格策略 DynamicTableRenderPolicy 来实现这样的功能。

template.docx:
在这里插入图片描述{ {detail_table}}标签可以在表格内的任意单元格内,DynamicTableRenderPolicy会获取XWPFTable对象进而获得操作整个表格的能力。

代码:

新建渲染策略DetailTablePolicy,继承于抽象表格策略。

public class DetailTablePolicy extends DynamicTableRenderPolicy {
    
    

  // 货品填充数据所在行数
  int goodsStartRow = 2;
  // 人工费填充数据所在行数
  int laborsStartRow = 5;

  @Override
  public void render(XWPFTable table, Object data) throws Exception {
    
    
    if (null == data) return;
    DetailData detailData = (DetailData) data;

    // 人工费
    List<RowRenderData> labors = detailData.getLabors();
    if (null != labors) {
    
    
      table.removeRow(laborsStartRow);
      // 循环插入行
      for (int i = 0; i < labors.size(); i++) {
    
    
        XWPFTableRow insertNewTableRow = table.insertNewTableRow(laborsStartRow);
        for (int j = 0; j < 7; j++) insertNewTableRow.createCell();

        // 合并单元格
        TableTools.mergeCellsHorizonal(table, laborsStartRow, 0, 3);
        // 单行渲染
        TableRenderPolicy.Helper.renderRow(table.getRow(laborsStartRow), labors.get(i));
      }
    }

    // 货物
    List<RowRenderData> goods = detailData.getGoods();
    if (null != goods) {
    
    
      table.removeRow(goodsStartRow);
      for (int i = 0; i < goods.size(); i++) {
    
    
        XWPFTableRow insertNewTableRow = table.insertNewTableRow(goodsStartRow);
        for (int j = 0; j < 7; j++) insertNewTableRow.createCell();
        TableRenderPolicy.Helper.renderRow(table.getRow(goodsStartRow), goods.get(i));
      }
    }
  }
}

将模板标签{ {detail_table}}设置成此策略

Configure config = Configure.builder().bind("detail_table", new DetailTablePolicy()).build();

output.docx:
在这里插入图片描述

4. 批注

CommentRenderPolicy 是内置插件,提供了对批注完整功能的支持。

批注使用数据模型CommentRenderData。

代码:

CommentRenderData comment = Comments.of("鹅")
                .signature("Sayi", "s", LocaleUtil.getLocaleCalendar())
                .comment("鹅,是一种动物")	// 批注内容
                .create(); 
Map<String, Object> data = new HashMap<>();
data.put("comment", comment);
Configure config = Configure.builder()
		.bind("comment", new CommentRenderPolicy())// 将批注插件和comment标签绑定
		.build(); 

XWPFTemplate.compile("comment_template.docx", config).render(data);

output.docx:
在这里插入图片描述

5. 插入附件

AttachmentRenderPolicy 是内置插件,提供了插入附件功能的支持。

插入附件使用数据模型AttachmentRenderData。

代码:

AttachmentRenderData attach = Attachments
		.ofLocal("attachment.xlsx", AttachmentType.XLSX)	// 附件文档,Word或者Excel
		.create(); 

Map<String, Object> data = new HashMap<>();
data.put("attachment", attach);
Configure config = Configure.builder()
		.bind("attachment", new AttachmentRenderPolicy())	// 绑定标签和附件插件
		.build(); 

XWPFTemplate.compile("attachment_template.docx", config).render(data);

output.docx:
在这里插入图片描述

6. 代码高亮

HighlightRenderPolicy 插件对Word代码块进行高亮展示。代码高亮插件支持20多种编程语言和几十种主题样式。

需引入依赖:

<dependency>
  <groupId>com.deepoove</groupId>
  <artifactId>poi-tl-plugin-highlight</artifactId>
  <version>1.0.0</version>
</dependency>

代码高亮使用数据模型HighlightRenderData。

代码:

HighlightRenderData code = new HighlightRenderData();
code.setCode("/**\n"
        + " * @author John Smith <[email protected]>\n"
        + "*/\n"
        + "package l2f.gameserver.model;\n"
        + "\n"
        + "public abstract strictfp class L2Char extends L2Object {\n"
        + "  public static final Short ERROR = 0x0001;\n"
        + "\n"
        + "  public void moveTo(int x, int y, int z) {\n"
        + "    _ai = null;\n"
        + "    log(\"Should not be called\");\n"
        + "    if (1 > 5) { // wtf!?\n"
        + "      return;\n"
        + "    }\n"
        + "  }\n"
        + "}");
code.setLanguage("java"); 	// 代码语言
code.setStyle(HighlightStyle.builder().withShowLine(true).withTheme("zenburn").build();// 设置主题样式
Map<String, Object> data = new HashMap<>();
data.put("code", code);

Configure config = Configure.builder()
		.bind("code", new HighlightRenderPolicy())	// 将代码高亮插件和code标签绑定
		.build(); 
XWPFTemplate.compile("highlight_template.docx", config).render(data);

output.docx:
在这里插入图片描述

7. Markdown

MarkdownRenderPolicy插件支持通过Markdown生成word文档。通过Markdown插件将poi-tl根目录下的README.md内容转为word文档的结果。

需引入依赖:

<dependency>
  <groupId>com.deepoove</groupId>
  <artifactId>poi-tl-plugin-markdown</artifactId>
  <version>1.0.3</version>
</dependency>

Markdown使用数据模型MarkdownRenderData。

代码:

MarkdownRenderData code = new MarkdownRenderData();
code.setMarkdown(new String(Files.readAllBytes(Paths.get("README.md"))));
code.setStyle(MarkdownStyle.newStyle()); // 定制markdown转为word的样式

Map<String, Object> data = new HashMap<>();
data.put("md", code);

Configure config = Configure.builder()
		.bind("md", new MarkdownRenderPolicy())// 将Markdown插件和md标签绑定
		.build(); 
XWPFTemplate.compile("markdown_template.docx", config).render(data);

output.docx:
在这里插入图片描述

◆ 自定义插件

实现一个插件就是要告诉我们在模板的某个地方用某些数据做某些事情。

- 定义插件

1. 通过实现RenderPolicy接口自定义插件:

public interface RenderPolicy {
    
    
  void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template);   
}
  • eleTemplate:当前标签位置
  • data:数据模型
  • template:模板

定义一个将标签替换为Hello, world的插件:

public class HelloWorldRenderPolicy implements RenderPolicy {
    
    

  @Override
  public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
    
    
    XWPFRun run = ((RunTemplate) eleTemplate).getRun(); // XWPFRun是Apache POI的类,表示当前位置
    // String thing = String.valueOf(data);
    String thing = "Hello, world";
    run.setText(thing, 0); // 渲染文本hello, world
  }

}

2. 通过继承抽象模板类AbstractRenderPolicy自定义插件:

poi-tl提供了抽象模板类 AbstractRenderPolicy ,它定义了一些骨架步骤并且将数据模型的校验和渲染逻辑分开,使用泛型约束数据类型,让插件开发起来更简单。

接下来我们再写一个更复杂的插件,在模板标签位置完完全全使用代码创建一个表格,这样我们就可以随心所欲的操作表格:

public class CustomTableRenderPolicy extends AbstractRenderPolicy<Object> {
    
    

  @Override
  protected void afterRender(RenderContext<Object> context) {
    
    
    // 清空标签
    clearPlaceholder(context, true);
  }

  @Override
  public void doRender(RenderContext<Object> context) throws Exception {
    
    
    XWPFRun run = context.getRun();
    BodyContainer bodyContainer = BodyContainerFactory.getBodyContainer(run);
    // 定义行列
    int row = 10, col = 8;
    // 插入表格
    XWPFTable table = bodyContainer.insertNewTable(run, row, col);

    // 表格宽度
    TableTools.setWidth(table, UnitUtils.cm2Twips(14.63f) + "", null);
    // 边框和样式
    TableTools.borderTable(table, BorderStyle.DEFAULT);

    // 1) 调用XWPFTable API操作表格
    // 2) 调用TableRenderPolicy.Helper.renderRow方法快速方便的渲染一行数据
    // 3) 调用TableTools类方法操作表格,比如合并单元格
    // ......
    TableTools.mergeCellsHorizonal(table, 0, 0, 7);
    TableTools.mergeCellsVertically(table, 0, 1, 9);
  }

}

通过 bodyContainer.insertNewTable 在当前标签位置插入表格,使用XWPFTable API来操作表格。

- 使用插件

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

1. 将插件应用到标签:

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

ConfigureBuilder builder = Configure.builder();
builder.bind("report", new CustomTableRenderPolicy());

此时,{ {report}} 将不再是一个文本标签,而是一个自定义标签。

ConfigureBuilder采用了链式调用的方式,可以一次性设置多个标签的插件:

builder.bind("report", new CustomTableRenderPolicy()).bind("name", new MyRenderPolicy());

2. 将插件注册为新标签类型:

当开发的插件具有一定的通用能力就可以将其注册为新的标签类型。比如增加%标识:{ {%var}},对应自定义的渲染策略 HelloWorldRenderPolicy:

builder.addPlugin('%', new HelloWorldRenderPolicy());

此时,{ {%var}} 将成为一种新的标签类型,它的执行函数是 HelloWorldRenderPolicy。

◆ 自定义函数

poi-tl可以不使用任何poi-tl的默认插件,完全使用自定义函数完成。

插件是一个函数,它的入参是anywhere和anything,函数体就是do something。

// where绑定policy
Configure config = Configure.builder()
		.bind("sea", new AbstractRenderPolicy<String>() {
    
    // 自定义文本插件
		  @Override
		  public void doRender(RenderContext<String> context) throws Exception {
    
     
		      // anywhere
		      XWPFRun where = context.getWhere();
		      // anything
		      String thing = context.getThing();
		      // do 文本
		      where.setText(thing, 0);
		  }
		})
		.bind("sea_img", new AbstractRenderPolicy<String>() {
    
    // 自定义图片插件
		  @Override
		  public void doRender(RenderContext<String> context) throws Exception {
    
     
		      // anywhere delegate
		      WhereDelegate where = context.getWhereDelegate();
		      // any thing
		      String thing = context.getThing();
		      // do 图片
		      FileInputStream stream = null;
		      try {
    
    
		          stream = new FileInputStream(thing);
		          where.addPicture(stream, XWPFDocument.PICTURE_TYPE_JPEG, 400, 450);
		      } finally {
    
    
		          IOUtils.closeQuietly(stream);
		      }
		      // clear
		      clearPlaceholder(context, false);
		  }
		})
		.bind("sea_feature", new AbstractRenderPolicy<List<String>>() {
    
    自定义列表插件
		  @Override
		  public void doRender(RenderContext<List<String>> context) throws Exception {
    
     
		      // anywhere delegate
		      WhereDelegate where = context.getWhereDelegate();
		      // anything
		      List<String> thing = context.getThing();
		      // do 列表
		      where.renderNumbering(Numberings.of(thing.toArray(new String[] {
    
    })).create());
		      // clear
		      clearPlaceholder(context, true);
		  }
		})
		.build();

// 初始化where的数据
HashMap<String, Object> args = new HashMap<String, Object>();
args.put("sea", "Hello, world!");
args.put("sea_img", "sea.jpg");
args.put("sea_feature", Arrays.asList("面朝大海春暖花开", "今朝有酒今朝醉"));
args.put("sea_location", Arrays.asList("日落:日落山花红四海", "花海:你想要的都在这里"));

// 一行代码
XWPFTemplate.compile("sea.docx", config).render(args).writeToFile("out_sea.docx");





参考文章:
http://deepoove.com/poi-tl/1.10.x/

猜你喜欢

转载自blog.csdn.net/JokerLJG/article/details/131193979