Word template engine poi-tl


poi-tl (poi template language) is a Word template engine that uses Word templates and data to create Word documents.

◆ Scheme comparison

plan Portability Feature ease of use
Poi-tl JavaCross-platform Word template engine Based on Apache POI, more friendly API
Apache POI JavaCross-platform The Apache project not only encapsulates easy-to-use document APIs (text, pictures, tables, headers, footers, charts, etc.), but also directly manipulates the XML structure at the bottom Incomplete documentation, recommended tutorial: Apache POI Word Quick Start
Freemarker XML cross-platform Only supports text, very limited Not recommended, the XML structure needs to be maintained, and the code cannot be maintained later
OpenOffice Deploy OpenOffice, poor portability - Need to understand OpenOffice API
HTML browser export Rely on browser implementation, poor portability HTML is not well compatible with Word format -
Jacob、winlib Windows platform - Complicated and not recommended at all

◆ version

As of 2023-06-14, poi-tl version iteration:

  • 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+

Note: The 1.12.x version has made an incompatible change, you need to pay attention when upgrading:

  • Refactored PictureRenderData and changed it to an abstract class. It is recommended to use the Pictures factory method to create picture data

下文使用的版本为1.10.5

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

◆ Features

poi-tl is a Word template engine based on Apache POI.

engine function describe
text render label as text
picture Render labels as images
sheet Render the label as a table
the list Render labels as a list
chart Bar chart (3D bar chart), column chart (3D column chart), area chart (3D area chart), line chart (3D line chart), radar chart, pie chart (3D pie chart), scatter chart and other chart rendering
If Condition Judgment Hide or display certain document content (including text, paragraphs, pictures, tables, lists, charts, etc.)
Foreach Loop Loop some document content (including text, paragraphs, images, tables, lists, charts, etc.)
Loop table row Loop to copy a row of the rendered table
Loop table columns Cyclic copy rendering a column of the table
Loop ordered list Supports looping of ordered lists and multi-level lists
code highlighting Code block highlighting in Word, supports 26 languages ​​and hundreds of coloring styles
Markdown Render Markdown as a word document
Word comments Complete annotation function, creating annotations, modifying annotations, etc.
Word attachment Insert attachments in Word
SDT Content Control Label support in content controls
picture replacement Replace the original picture with another picture
bookmarks, anchors, hyperlinks Support for setting bookmarks, anchor points and hyperlinks in documents
Expression Language Fully supports SpringEL expressions, and more expressions can be extended: OGNL, MVEL…​
label customization Support custom label prefix and suffix
text box Label support in textbox
style The template is the style, and the code can also set the style
template nesting Templates contain subtemplates, and subtemplates contain subtemplates
merge Word Merge Merge can also be merged at a specified location
User-defined functions (plugins) Execute function anywhere in the document

◆ template

The template is a Word document in Docx format:

  • You can use Microsoft office, WPS Office, Pages and other software to make templates
  • Templates can be generated using Apache POI code

◆ data

Data is like a hash or dictionary:

  • Can be a Map structure
  • can be an object

◆ output

The output is streamed, and can be written to any output stream.

Remember to close these streams after use. You can use the PoitlIOUtils utility class to close the stream:

PoitlIOUtils.closeQuietlyMulti(final Closeable... cls);

◆ Data model

All data models implement the RenderData interface.

The RenderData interface is an interface used to represent the data rendering method in the POI-TL template.

Several common implementation classes:

  1. ImageRenderData

    This class is used to add image rendering in POI-TL templates . It includes the following properties:

    attribute name describe
    String uri The address of the picture, which can be a network address or a local file path
    int width The width of the image, in pixels
    int height The height of the image, in pixels
  2. TextRenderData

    This class is used to add how text is rendered in POI-TL templates . It includes the following properties:

    attribute name describe
    String text text content
    FontRenderData font Text font style rendering method

    Among them, FontRenderData is another class that implements the RenderData interface, which is used to set the style of the text font.

  3. TableRenderData

    This class is used to add how tables are rendered in POI-TL templates . It includes the following properties:

    attribute name describe
    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/

Guess you like

Origin blog.csdn.net/JokerLJG/article/details/131193979