Word模板引擎poi-tl

news2024/9/20 19:21:06

文章目录

  • ◆ 方案对比
  • ◆ 版本
  • ◆ 特性
  • ◆ 模板
  • ◆ 数据
  • ◆ 输出
  • ◆ 数据模型
  • ◆ 标签
    • 1. 文本
    • 2. 图片
    • 3. 表格
    • 4. 列表
    • 5. 嵌套
    • 6. 区块对
  • ◆ SpingEL
    • 2. 单系列图标
    • 3. 多系列图标
    • 4. 组合图表
  • ◆ 配置
    • 1. 标签前后缀
    • 2. 标签类型
    • 3. 标签匹配值
    • 4. 标签值计算
    • 5. SpringEL
    • 6. 数据模型序列化
    • 7. 错误处理
    • 8. 模板生成模板
    • 9. 无模板创建文档
    • 10. 日志
  • ◆ 插件
    • 1. 表格行循环
    • 2. 表格列循环
    • 3. 动态表格
    • 4. 批注
    • 5. 插入附件
    • 6. 代码高亮
    • 7. Markdown
  • ◆ 自定义插件
    • - 定义插件
    • - 使用插件
  • ◆ 自定义函数


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

◆ 方案对比

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

◆ 版本

截止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工具类来关闭流:

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可以用于所有任意区块中,其它都是集合相关,只能用于循环中。

变量类型说明
#thisobject引用当前对象,由于#和已有表格标签标识冲突,所以在文本标签中需要使用=号标识来输出文本
_indexint返回当前迭代从0开始的索引
_is_firstboolean辨别循环项是否是当前迭代的第一项
_is_lastboolean辨别循环项是否是当前迭代的最后一项
_has_nextboolean辨别循环项是否是有下一项
_is_even_itemboolean辨别循环项是否是当前迭代间隔1的奇数项
_is_odd_itemboolean辨别循环项是否是当前迭代间隔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 <john.smith@example.com>\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/

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

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

相关文章

设计模式之抽象工厂笔记

设计模式之抽象工厂模式笔记 说明Abstract Factory(抽象工厂)目录UML抽象工厂示例类图甜品抽象类甜品提拉米苏类甜品抹茶慕斯类 咖啡抽象类美式咖啡类拿铁咖啡类 甜品工厂接口美式风味的甜品工厂意大利风味的甜品工厂 测试类模式扩展 说明 记录下学习设计模式-抽象工厂模式的写…

吴恩达471机器学习入门课程3第1周——K-means

K-means 聚类 1 - 实现 K-means1.1 找到最近的质心练习11.2 计算质心均值练习2 2 - K-means在样本数据集上的应用3 - 随机初始化4 - K-means图像压缩4.1 数据集可视化处理数据 4.2图像像素上的 K-mean4.3 压缩图片 实现 K-means 算法&#xff0c;并将其用于图像压缩。 您将从一…

Autoware 跑 Demo(踩坑指南)

Autoware 跑 Demo&#xff08;踩坑指南&#xff09; 网上的博客和官方的教程&#xff0c;几乎都是一样的&#xff0c;但实际上跑不起来 Autoware 1.12学习整理–01–运行rosbag示例 Autoware入门学习&#xff08;三&#xff09;——Autoware软件功能使用介绍&#xff08;1/3&a…

MySQL的IF(exp1, exp2, exp3)、IFNULL(exp1, exp2)函数的用法

本章主要是讲解一下mysql的常用方法if()和ifnull()的使用 1、if(exp1, exp2, exp3) 如果表达式exp1成立&#xff0c;则返回的结果是表达式exp2&#xff0c;否则返回的是表达式exp3 案例&#xff1a;现在有一个星印类型表xingyin_type 通过这个表来介绍一下这个函数的使用 sel…

轻松掌握Seata源码分析之AT模式整体大纲流程跟踪

如下为订单和库存的实例代码&#xff0c;在事务开启处即订单服务处使用GlobalTransactional即可。当添加了异常代码使订单无法加一&#xff0c;这时减库存服务也会回滚&#xff0c;根据的就是undolog。回滚完undolog记录会被释放删除。 AT模式整体大纲流程跟踪如下&#xff1…

第37步 深度学习图像识别:CNN建模(Tensorflow)

基于WIN10的64位系统演示 一、写在前面 &#xff08;1&#xff09;深度学习图像识别的原理 我们思考一下&#xff0c;当你看到一张椅子的图片&#xff0c;你的大脑会告诉你这是个椅子&#xff0c;但你有没有想过&#xff1a;为什么你知道这是椅子&#xff0c;你的大脑是怎么…

【Spring Cloud 系列】Eureka控制台参数说明

【Spring Cloud 系列】Eureka控制台参数说明 前面我们在《Eureka使用详解》一文中介绍了Eureka的使用。本文将介绍Eureka控制板面各参数&#xff1a; System Status 编号名称说明1Environment环境&#xff0c;默认为test&#xff0c;该参数在实际使用过程中&#xff0c;可以不…

单目标应用:Tiki-taka算法(TTA)求解太阳能光伏模型MATLAB

一、四种太阳能光伏模型 随着石油、煤炭、天然气等不可再生能源的快速枯竭&#xff0c;以及空气环境的严重污染&#xff0c;可持续、无污染的能源供应成为热点和关键问题。风能、太阳能、水能、潮汐能等可再生能源的开发利用&#xff0c;必然在未来的可持续发展中发挥至关重要…

德国企业数据统计分析【1】-基于pandas的GENESIS ONLINE数据简单统计与柱状图可视化

引言: 德国拥有很多年销售额不超过50亿美元的中小企业,但他们却是某些细分制造、工业领域的翘楚。并且隐身于大众视野之外。此处,隐形冠军指的就是细分领域行业处于绝对领先地位并且年销售额不超过50亿美元的中小企业。这一概念是由德国著名中小企业管理学家赫尔曼西蒙创立的…

电脑开机密码忘记了怎么办?使用优盘重装系统

大家可以在网上搜索&#xff0c;其他方法。尽量找回密码。我这是因为已经很久没有使用这个电脑&#xff0c;而且c盘也没有怎么重要资料的情况下。我才选择重装系统的。 请慎重。 前期准备&#xff1a; 1、准备一个4G以上的U盘 2、备份U盘重要文件&#xff0c;制作过程中会格式…

「深度学习之优化算法」(五)差分进化算法

1. 差分进化算法简介 &#xff08;以下描述&#xff0c;均不是学术用语&#xff0c;仅供大家快乐的阅读&#xff09; 差分进化算法&#xff08;Differential Evolution Algorithm&#xff0c;DE&#xff09;是一种基于群体的进化算法&#xff0c;它模拟了群体中的个体的合作与竞…

黑马点评短信登录功能

一、基于session实现短信登录 1、发送短信验证码 流程图如下&#xff1a; 1、实现UserController下的sendCode方法&#xff1a; /*** 发送手机验证码*/PostMapping("/code")public Result sendCode(RequestParam("phone") String phone, HttpSession se…

微博粉丝清理工具丨2023年最新粉丝批量清理_微博怎么批量清理粉丝

2023年最新微博怎么批量清理粉丝&#xff1f;可能还有不少小伙伴不太清楚 接下来就为大家带来微博批量清理僵尸粉方法 有需要的朋友可以来了解一下&#xff0c;希望下文可以帮到大家 第一种&#xff1a;客服界面清粉方法 然后在客服中心界面选择修正粉丝; 最后点击一下确认就…

const修饰的成员函数

const修饰的成员函数 问题: 哪里出现编译报错了, 如何修改&#xff1f; class A { public:const int get1() const{a1 10;return a1;}private: int a1 0; }; int main() {A a;a.get1();return 0; }当时以为是a是一个非const对象&#xff0c;调用了const成员函数导致编译错误…

关于guacamole项目中的一点感悟与理解

关于guacamole项目中的一点想法 前言一、guacd模块启动相关二、一些感悟与理解参考 前言 Guacamole 是基于 Web 的 VNC 客户端&#xff0c;使用它可以通过web浏览器访问远程服务器终端并进行操作。它的基本架构如下图所示。 巧合之下&#xff0c;前段时间了解了项目中guacd模块…

构建大数据环境:Hadoop、MySQL、Hive、Scala和Spark的安装与配置

前言 在当今的数据驱动时代&#xff0c;构建一个强大的大数据环境对于企业和组织来说至关重要。本文将介绍如何安装和配置Hadoop、MySQL、Hive、Scala和Spark&#xff0c;以搭建一个完整的大数据环境。 简介 安装Hadoop 首先&#xff0c;从Apache Hadoop的官方网站下载所需的…

测试(一)

1.用户需求 可以简单理解为甲方提出的需求&#xff0c;如果没有甲方&#xff0c;那么就是终端用户使用产品时必须要完成的任务。该需求一般比较简略。 2.软件需求 或者叫功能需求&#xff0c;该需求会详细描述开发人员必须实现的软件功能(所谓的测试文档)。 大多数公司在进行…

05- c语言函数 (C语言)

一 函数的概念 1、在程序设计过程中&#xff0c;为了实现某个功能需要编写多行代码&#xff0c;例如求一个二维数组中的最大值&#xff0c;如果 该功能需要被多次使用&#xff0c;我们可以在每次使用时将原来的代码重复编写&#xff0c;但是这样未免有“凑代码”的嫌疑&#x…

大数据治理.数据储存技术

hive是基于Hadoop的一个数据仓库工具&#xff0c;用来进行数据提取、转化、加载&#xff0c;这是一种可以存储、查询和分析存储在Hadoop中的大规模数据的机制。hive数据仓库工具能将结构化的数据文件映射为一张数据库表&#xff0c;并提供SQL查询功能&#xff0c;能将SQL语句转…

Spark大数据处理学习笔记(3.8.3) Spark RDD典型案例-利用RDD实现分组排行榜

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/Twpwe】 文章目录 一、任务目标二、准备工作2.1 在本地创建成绩文件2.2 将成绩文件上传到HDFS上指定目录 三、完成任务3.1 在Spark Shell里完成任务3.1.1 读取成绩文件得到RDD3.1.2 利用映射算子生…