方案 | 移植性 | 功能性 | 易用性 |
---|---|---|---|
Poi-tl | Java跨平台 | Word模板引擎,基于Apache POI,提供更友好的API | 低代码,准备文档模板和数据即可 |
Apache POI | Java跨平台 | Apache项目,封装了常见的文档操作,也可以操作底层XML结构 | 文档不全,这里有一个教程:Apache POI Word快速入门 |
Freemarker | XML跨平台 | 仅支持文本,很大的局限性 | 不推荐,XML结构的代码几乎无法维护 |
OpenOffice | 部署OpenOffice,移植性较差 | - | 需要了解OpenOffice的API |
HTML浏览器导出 | 依赖浏览器的实现,移植性较差 | HTML不能很好的兼容Word的格式,样式糟糕 | - |
Jacob、winlib | Windows平台 | - | 复杂,完全不推荐使用 |
FreeMarker或Velocity基于文本模板和数据生成新的html页面或配置文件。poi-tl是一个基于Word模板和数据生成新文档的Word模板引擎。
Word模板样式丰富。Poi-tl将在生成的文档中完美地保留模板中的样式。您还可以设置标签的样式。标签的样式将应用于替换的文本,因此您可以专注于模板设计。
poi-tl是一个“无逻辑”的模板引擎。没有复杂的控制结构和变量赋值,只有标签,有些标签可以用文字、图片、表格等代替。,一些标签会隐藏某些文档内容,而另一些标签会循环一系列文档内容。
poi-tl支持自定义函数(插件),函数可以在Word模板的任何地方执行,在文档的任何地方做任何事情是poi-tl的目标。
特性 | 描述 |
---|---|
Text | 将标签呈现为文本 |
Picture | 将标签渲染为图片 |
Table | 将标签呈现为表格 |
Numbering | 将标签呈现为编号 |
Chart | 条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、饼状图(3D饼状图)等图表渲染 |
If Condition | 根据条件隐藏或显示某些文档内容(包括文本、段落、图片、表格、列表、图表等。) |
Foreach Loop | 循环浏览某些文档内容(包括文本、段落、图片、表格、列表、图表等。)根据收藏 |
Loop table row | 循环来复制呈现的表格中的一行 |
Loop table column | 循环复制并呈现表中的一列 |
Loop ordered list | 支持有序列表的循环,同时支持多级列表 |
Highlight code | 代码块的单词高亮显示,支持26种语言和数百种颜色样式 |
Markdown | 将Markdown转换为word文档 |
Word attachment | 在Word中插入附件 |
Word Comments | 完成支持评论、创建评论、修改评论等。 |
Word SDT | 完全支持结构化文档标签 |
Textbox | 支持文本框中的标签 |
Picture replacement | 用另一张图片替换原始图片 |
bookmarks, anchors, hyperlinks | 支持在文档中设置书签、锚点和超链接 |
Expression Language | 完全支持SpringEL表达式,并可以扩展更多的表达式:OGNL,MVEL… |
Style | 模板就是样式,代码也可以设置样式 |
Template nesting | 模板包含子模板,子模板又包含子模板 |
Merge | Word 合并,也可以在指定位置合并 |
custom functions (plug-ins) | 插件设计,在文档的任何地方执行功能 |
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.0</version>
</dependency>
NOTE: poi-tl
1.12.x
要求 POI 版本在5.2.2+
.Apache POI已经进入5.0.0+时代,如果你仍希望使用低版本的Apache POI,请查阅历史版本。
- 当前版本 1.12.0 Documentation,Apache POI5.2.2+,JDK1.8+
- 1.11.x Documentation,Apache POI5.1.0+,JDK1.8+
- 1.10.x Documentation,Apache POI4.1.2,JDK1.8+
- 1.10.3 Documentation,Apache POI4.1.2,JDK1.8+
- 1.9.x Documentation,Apache POI4.1.2,JDK1.8+
- 1.8.x Documentation,Apache POI4.1.2,JDK1.8+
- 1.7.x Documentation,Apache POI4.0.0+,JDK1.8+
- 1.6.x Documentation,Apache POI4.0.0+,JDK1.8+
- 1.5.x Documentation,Apache POI3.16+,JDK1.6+
V1.12.0版本作了一个不兼容的改动,升级的时候需要注意:
- 重构了PictureRenderData,改为抽象类,建议使用Pictures工厂方法来创建图片数据
简单的使用案例:我们使用poi-tl模板引擎替换word模板中的{{title}}
1、创建一个新的word文档template.docx,里面包含了一个标签{{title}}
2、TDO模式:Template + data-model = output
public static void main(String[] args) throws IOException {
XWPFTemplate.compile("D:\\template.docx").render(new HashMap<String, Object>(){{
put("title", "poi-tl template engine");
}}).writeToFile("D:\\out_template.docx");
}
# 网络流输出
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition","attachment;filename=\""+"out_template.docx"+"\"");
// HttpServletResponse response
OutputStream out = response.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(out);
template.write(bos);
bos.flush();
out.flush();
PoitlIOUtils.closeQuietlyMulti(template, bos, out);
标签由两个花括号组成,{{title}}是标签,{{?title}}也是标签,title是标签的名称,而?标识标签的类型。接下来,我们来认识一下有哪些标签类型。
文本标签{{var}}
数据模型:
String
:文本TextRenderData
:有样式的文本HyperlinkTextRenderData
:超链接和锚点文本Object
:调用 toString() 方法转化为文本put("name", "Sayi");
put("author", new TextRenderData("000000", "Sayi"));
put("link", new HyperlinkTextRenderData("website", "http://deepoove.com"));
put("anchor", new HyperlinkTextRenderData("anchortxt", "anchor:appendix1"));
除了new操作符,还提供了更加优雅的工厂 Texts
和链式调用的方式轻松构建文本模型。
put("author", Texts.of("Sayi").color("000000").create());
put("link", Texts.of("website").link("http://deepoove.com").create());
put("anchor", Texts.of("anchortxt").anchor("appendix1").create());
所见即所得,标签的样式会应用到替换后的文本上,也可以通过代码设定文本的样式。
{
"text": "Sayi",
"style": {
"strike": false,
"bold": true,
"italic": false,
"color": "00FF00",
"underLine": false,
"fontFamily": "微软雅黑",
"fontSize": 12,
"highlightColor": "green",
"vertAlign": "superscript",
"characterSpacing" : 20
}
}
图片标签以@开始:{{@var}}
数据模型:
String
:图片url或者本地路径,默认使用图片自身尺寸PictureRenderData
ByteArrayPictureRenderData
FilePictureRenderData
UrlPictureRenderData
推荐使用工厂 Pictures
构建图片模型。
// 指定图片路径
put("image", "logo.png");
// svg图片
put("svg", "https://img.shields.io/badge/jdk-1.6%2B-orange.svg");
// 设置图片宽高
put("image1", Pictures.ofLocal("logo.png").size(120, 120).create());
// 图片流
put("streamImg", Pictures.ofStream(new FileInputStream("logo.jpeg"), PictureType.JPEG)
.size(100, 120).create());
// 网络图片(注意网络耗时对系统可能的性能影响)
put("urlImg", Pictures.ofUrl("http://deepoove.com/images/icecream.png")
.size(100, 100).create());
// java图片
put("buffered", Pictures.ofBufferedImage(bufferImage, PictureType.PNG)
.size(100, 100).create());
图片支持BufferedImage,这意味着我们可以利用Java生成图表插入到word文档中。
{
"pictureType" : "PNG",
"path": "logo.png",
"pictureStyle": {
"width": 100,
"height": 100
},
"altMeta": "图片不存在"
}
表格标签以#开始:{{#var}}
数据模型:
TableRenderData
推荐使用工厂 Tables
、 Rows
和 Cells
构建表格模型。
// 一个2行2列的表格
put("table0", Tables.of(new String[][] {
new String[] { "00", "01" },
new String[] { "10", "11" }
}).border(BorderStyle.DEFAULT).create());
00 | 01 |
---|---|
10 | 11 |
// 第0行居中且背景为蓝色的表格
RowRenderData row0 = Rows.of("姓名", "学历").textColor("FFFFFF")
.bgColor("4472C4").center().create();
RowRenderData row1 = Rows.create("李四", "博士");
put("table1", Tables.create(row0, row1));
姓名 | 学历 |
---|---|
李四 | 博士 |
// 合并第1行所有单元格的表格
RowRenderData row0 = Rows.of("列0", "列1", "列2").center().bgColor("4472C4").create();
RowRenderData row1 = Rows.create("没有数据", null, null);
MergeCellRule rule = MergeCellRule.builder().map(Grid.of(1, 0), Grid.of(1, 2)).build();
put("table3", Tables.of(row0, row1).mergeRule(rule).create());
列0 | 列1 | 列2 |
---|---|---|
没有数据 |
TableRenderData表格模型在单元格内可以展示文本和图片,同时也可以指定表格样式、行样式和单元格样式,而且在N行N列渲染完成后可以应用单元格合并规则 MergeCellRule ,从而实现更复杂的表格。
{
"rows": [
{
"cells": [
{
"paragraphs": [
{
"contents": [
{
[TextRenderData]
},
{
[PictureRenderData]
}
],
"paragraphStyle": null
}
],
"cellStyle": {
"backgroundColor": "00000",
"vertAlign": "CENTER"
}
}
],
"rowStyle": {
"height": 2.0f
}
}
],
"tableStyle": {
"width": 14.63f,
"colWidths": null
},
"mergeRule": {
"mapping": {
"0-0": "1-2"
}
}
}
列表标签以*
开始:{{*var}}
数据模型:
List<String>
NumberingRenderData
推荐使用工厂 Numberings
构建列表模型。
put("list", Numberings.create("Plug-in grammar",
"Supports word text, pictures, table...","Not just templates"));
编号样式支持罗马字符、有序无序等,可以通过 Numberings.of(NumberingFormat)
来指定。
DECIMAL //1. 2. 3.
DECIMAL_PARENTHESES //1) 2) 3)
BULLET //● ● ●
LOWER_LETTER //a. b. c.
LOWER_ROMAN //i ⅱ ⅲ
UPPER_LETTER //A. B. C.
区块对由前后两个标签组成,开始标签以?标识,结束标签以/标识:{{?sections}}{{/sections}}
区块对开始和结束标签中间可以包含多个图片、表格、段落、列表、图表等,开始和结束标签可以跨多个段落,也可以在同一个段落,但是如果在表格中使用区块对,开始和结束标签必须在同一个单元格内,因为跨多个单元格的渲染行为是未知的。
区块对在处理一系列文档元素的时候非常有用,位于区块对中的文档元素可以被渲染零次,一次或N次,这取决于区块对的取值。
False或空集合
隐藏区块中的所有文档元素
非False且不是集合
显示区块中的文档元素,渲染一次
非空集合
根据集合的大小,循环渲染区块中的文档元素
如果区块对的值是 null 、false 或者空的集合,位于区块中的所有文档元素将不会显示,这就等同于if语句的条件为 false。
{
"announce": false
}
template.docx
Made it,Ma!{{?announce}}Top of the world!{{/announce}}
Made it,Ma!
{{?announce}}
Top of the world!
{{/announce}}
output.docx
Made it,Ma!
Made it,Ma!
如果区块对的值不为 null 、 false ,且不是集合,位于区块中的所有文档元素会被渲染一次,这就等同于if语句的条件为 true。
{
"person": { "name": "Sayi" }
}
template.docx
{{?person}}
Hi {{name}}!
{{/person}}
output.docx
Hi Sayi!
如果区块对的值是一个非空集合,区块中的文档元素会被迭代渲染一次或者N次,这取决于集合的大小,类似于foreach语法。
{
"songs": [
{ "name": "Memories" },
{ "name": "Sugar" },
{ "name": "Last Dance" }
]
}
template.docx
{{?songs}}
{{name}}
{{/songs}}
output.docx
Memories
Sugar
Last Dance
在循环中提供了一些内置变量,这些内置变量只能用于区块对中。
变量 | 类型 | 说明 |
---|---|---|
_index | int | 返回当前迭代从0开始的索引 |
_is_first | boolean | 辨别循环项是否是当前迭代的第一项。 |
_is_last | boolean | 辨别循环项是否是当前迭代的最后一项。 |
_has_next | boolean | 辨别循环项是否是有下一项。 |
_is_even_item | boolean | 辨别循环项是否是当前迭代间隔1的奇数项。 |
_is_odd_item | boolean | 辨别循环项是否是当前迭代间隔1的偶数项。 |
#this | object | 引用当前对象,由于#和已有表格标签标识冲突,所以在文本标签中需要使用=号标识来输出文本。 |
{
"produces": [
"application/json",
"application/xml"
]
}
{{?produces}}
{{_index + 1}}. {{=#this}}
{{/produces}}
1. application/json
2. application/xml
嵌套又称为导入、包含或者合并,以+标识:{{+var}}
数据模型:
DocxRenderData
推荐使用工厂 Includes
构建嵌套模型。
class AddrModel {
private String addr;
public AddrModel(String addr) {
this.addr = addr;
}
// Getter/Setter
}
List<AddrModel> subData = new ArrayList<>();
subData.add(new AddrModel("Hangzhou,China"));
subData.add(new AddrModel("Shanghai,China"));
put("nested", Includes.ofLocal("sub.docx").setRenderModel(subData).create());
引用标签是一种特殊位置的特殊标签,提供了直接引用文档中的元素句柄的能力,这个重要的特性在我们只想改变文档中某个元素极小一部分样式和属性的时候特别有用,因为其余样式和属性都可以在模板中预置好,真正的所见即所得。
引用图片标签是一个文本:{{var}},标签位置在:设置图片格式—可选文字—标题或者说明(新版本Microsoft Office标签位置在:编辑替换文字-替换文字)。
引用图片标签只会替换图片而不会改变图片尺寸和布局,数据模型和图片标签一致:PictureRenderData
。
put("img", Pictures.ofLocal("sayi.png").create());
多系列图表指的是条形图(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
]
}
]
}
单系列图表指的是饼图(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
]
}
}
组合图表指的是由多系列图表(柱形图、折线图、面积图)组合而成的图表。
组合图表的标签是一个文本:{{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",
"values": [
15, 6
]
},
{
"name": "speakers",
"comboType": "LINE",
"values": [
223, 119
]
}
]
}
poi-tl提供了类 Configure
来配置常用的设置,使用方式如下:
ConfigureBuilder builder = Configure.builder();
XWPFTemplate.compile("template.docx", builder.buid());
poi-tl使用 {{}}
的方式来标识对Google CTemplate的尊敬,如果你更偏爱freemarker ${}
的方式,可以自定义:
builder.buildGramer("${", "}");
默认的图片标签是以@开始,如果你希望使用%开始作为图片标签:
builder.addPlugin('%', new PictureRenderPolicy());
如果你不是很喜欢默认的标签标识类型,你也可以自由更改:
builder.addPlugin('@', new TableRenderPolicy());
builder.addPlugin('#', new PictureRenderPolicy());
这样{{@var}}就变成了表格标签,{{#var}}变成了图片标签,虽然不建议改变默认标签标识,但是从中可以看到poi-tl插件的灵活度,在插件章节中我们将会看到如何自定义自己的标签。
标签默认支持中文、字母、数字、下划线的组合,我们可以通过正则表达式来配置标签的规则,比如不允许中文:
builder.buildGrammerRegex("[\\w]+(\\.[\\w]+)*");
比如允许除了标签前后缀外的任意字符:
builder.buildGrammerRegex(RegexUtils.createGeneral("{{", "}}"));
计算标签值是指如何在数据模型中索引标签Key的值,可以完全自定义获取标签值的方式。
builder.setRenderDataComputeFactory(new RenderDataComputeFactory());
RenderDataComputeFactory是一个抽象工厂,你可以定义自己的工厂提供标签表达式计算接口 RenderDataCompute
的实现。
我们可以通过此方式支持任何的表达式引擎,Spring表达式正是通过 SpELRenderDataCompute
实现。
Spring Expression Language 是一个强大的表达式语言,支持在运行时查询和操作对象图,可作为独立组件使用,需要引入相应的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.18</version>
</dependency>
为了在模板标签中使用SpringEL表达式,需要将标签配置为SpringEL模式:
builder.useSpringEL();
关于SpringEL的写法可以参见官方文档,下面给出一些典型的示例。
{{name}}
{{name.toUpperCase()}}
{{name == 'poi-tl'}}
{{empty?:'这个字段为空'}}
{{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日'))}}
Spring表达式与区块对结合可以实现更强大的功能,示例如下:
{
"desc": "",
"summary": "Find A Pet",
"produces": [
"application/xml"
]
}
template.docx
{{?desc == null or desc == ''}}{{summary}}{{/}}
{{?produces == null or produces.size() == 0}}无{{/}}
output.docx
Find A Pet
数据模型支持JSON字符串序列化,可以方便的构造远程HTTP或者RPC服务,需要引入相应依赖:
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl-jsonmodel-support</artifactId>
<version>1.0.0</version>
</dependency>
然后配置数据模型前置转化器即可:
builder.addPreRenderDataCastor(new GsonPreRenderDataCastor());
poi-tl支持在发生错误的时候定制引擎的行为。
标签无法被计算的场景有几种,比如模板中引用了一个不存在的变量,或者级联的前置结果不是一个哈希,如 {{author.name}}
中author的值为null,此时就无法计算name的值。
poi-tl可以在发生这种错误时对计算结果进行配置,默认会认为标签值为null
。当我们需要严格校验模板是否有人为失误时,可以抛出异常:
builder.useDefaultEL(true);
注意的是,如果使用SpringEL表达式,可以通过参数来配置是否抛出异常:
uilder.useSpringEL(true);
我们知道渲染图片、表格等标签时对数据模型是有要求的,如果数据不合法(为NULL或者是一个错误的数据类型),可以配置模板标签的渲染行为。
poi-tl默认的行为会清空标签,如果希望对标签不作任何处理:
builder.setValidErrorHandler(new DiscardHandler());
如果希望执行严格的校验,直接抛出异常:
builder.setValidErrorHandler(new AbortHandler());
模板引擎不仅仅可以生成文档,也可以生成新的模板,比如我们把原先的一个文本标签分成一个文本标签和一个表格标签:
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);
使用 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);
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级别的日志会打印解析渲染过程中的信息,有利于程序调试,另外在模板引擎执行结束后会打印耗时信息:
插件,又称为自定义函数,它允许用户在模板标签位置处执行预先定义好的函数。由于插件机制的存在,我们几乎可以在模板的任何位置执行任何操作。
插件是poi-tl的核心,默认的标签和引用标签都是通过插件加载。
poi-tl默认提供了八个策略插件,用来处理文本、图片、列表、表格、文档嵌套、引用图片、引用多系列图表、引用单系列图表等:
由于这八个插件如此通用,因此将这些插件注册为不同的标签类型,从而搭建了poi-tl的标签体系,也构筑了poi-tl高度自由的插件机制。
实现一个插件就是要告诉我们在模板的某个地方用某些数据做某些事情,我们可以通过实现RenderPolicy
接口开发自己的插件:
/ **
* ElementTemplate是当前标签位置
* data是数据模型
* XWPFTemplate代表整个模板
*
*/
public interface RenderPolicy {
void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template);
}
接下来我们写一个将标签替换为Hello, world的插件:
public class HelloWorldRenderPolicy implements RenderPolicy {
@Override
public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
XWPFRun run = ((RunTemplate) eleTemplate).getRun();
// String thing = String.valueOf(data);
String thing = "Hello, world";
run.setText(thing, 0);
}
}
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来操作表格。
插件开发好后,为了让插件在某个标签处执行,我们需要将插件与标签绑定。
当我们有个模板标签为 {{report}}
,默认是文本标签,如果希望在这个位置做些不一样或者更复杂的事情,我们可以将插件应用到这个模板标签:
ConfigureBuilder builder = Configure.builder();
builder.bind("report", new CustomTableRenderPolicy());
此时,{{report}}
将不再是一个文本标签,而是一个自定义标签。
ConfigureBuilder采用了链式调用的方式,可以一次性设置多个标签的插件:
builder.bind("report", new CustomTableRenderPolicy()).bind("name", new MyRenderPolicy());
当开发的插件具有一定的通用能力就可以将其注册为新的标签类型。比如增加%标识:{{%var}}
,对应自定义的渲染策略 HelloWorldRenderPolicy
:
builder.addPlugin('%', new HelloWorldRenderPolicy());
此时,{{%var}}
将成为一种新的标签类型,它的执行函数是 HelloWorldRenderPolicy
。
接下来用一个完整的代码示例向你展示 Do Anything Anywhere 的想法,它不使用任何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");
除了八个通用的策略插件外,还内置了一些非常有用的插件。
ParagraphRenderPolicy | 渲染一个段落,可以包含不同样式文本,图片等 | |
DocumentRenderPolicy | 渲染整个word文档 | |
CommentRenderPolicy | 完整的批注功能 | 示例-批注 |
AttachmentRenderPolicy | 插入附件功能 | 示例-插入附件 |
LoopRowTableRenderPolicy | 循环表格行,下文会详细介绍 | 示例-表格行循环 |
LoopColumnTableRenderPolicy | 循环表格列 | 示例-表格列循环 |
DynamicTableRenderPolicy | 动态表格插件,允许直接操作表格对象 | 示例-动态表格 |
BookmarkRenderPolicy | 书签和锚点 | 示例-Swagger文档 |
AbstractChartTemplateRenderPolicy | 引用图表插件,允许直接操作图表对象 | |
TOCRenderPolicy | Beta实验功能:目录,打开文档时会提示更新域 |
同时有更多的独立插件可以使用(需要引入对应Maven依赖):
HighlightRenderPolicy | Word支持代码高亮 | 示例-代码高亮 |
MarkdownRenderPolicy | 使用Markdown来渲染word | 示例-Markdown |