关于文档:v1.6.x并非最新版本,推荐使用poi-tl最新版本:poi-tl 最新文档。 poi-tl(poi template language)是基于Apache POI的Word模板引擎,纯Java组件,跨平台,代码短小精悍,通过插件机制使其具有高度扩展性。
1. Requirements
Apache poi已经进入4.0.0+时代,如果你仍希望使用低版本的Apache poi(3.16+),请查阅poi-tl v1.5.x 文档。 2. Maven<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.6.0</version>
</dependency> 3. Gradle
4. 快速开始4.1. 2min入门新建Word模板template.docx,包含内容{{title}}
{{title}}
代码示例
XWPFTemplate template = XWPFTemplate.compile('~/template.docx').render(new HashMap<String, Object>(){{ (1) (2)
put('title', 'Poi-tl 模板引擎');
}});
FileOutputStream out = new FileOutputStream('out_template.docx');
template.write(out); (3)
out.flush();
out.close();
template.close();
TDO模式:Template + data-model = output 4.2. Template:模板Word模板支持DOCX格式,所有的标签都是以
poi-tl深知“所见即所得”的道理,文本的样式继承模板标签的样式,即如果模板标签{{title}}是蓝色微软雅黑加粗四号字体,则替换后的文本也是蓝色微软雅黑加粗四号字体。 “所见即所得”也体现在图片上,如果模板中是一个长200宽300布局样式为衬于文字下方的占位图片,那么替换后的图片样式也保持不变。 4.3. Data-Model:数据模型如果模板标签定义了'anywhere',那么数据模型定义的就是'anything'。 我们需要指定每个模板标签对应的数据模型,它可以是一个Map,其中key是标签名称:
可以是一个对象,属性名称是标签名称(可以通过注解@Name设置标签名称): public class MyDataModel {
// 对应模板标签{{name}}
private String name;
// 对应模板标签{{start_time}}
@Name('start_time')
private String startTime;
// 对应模板标签{{author.XXX}},XXX是Author的属性名
private Author author;
} 如上所示,poi-tl模板标签支持对象点缀式表达式,比如{{author.name}}的模板标签对应的数据是author对象的name属性值。
poi-tl内置了若干数据模型,使用这些类型的数据,可以在模板标签位置提供更丰富的操作,这些数据模型都实现了接口
4.4. output:流可以将最终结果渲染到任意输出流中,比如输出到文件流FileOutputStream生成新文档,输出到网络流ServletOutputStream供浏览器下载。
5. 语法poi-tl內建了五种模板。 5.1. 文本模板{{var}}
{{var}}
代码示例
put('author', new TextRenderData('000000', 'Sayi卅一'));
put('introduce', 'http://www.');
put('link', new HyperLinkTextRenderData('website.', 'http://www.')); 除了所见即所得的继承模板标签样式,也提供了通过代码设定文本样式的方式。 TextRenderData 的结构体
注:HyperLinkTextRenderData继承于TextRenderData,实现了超链接文本的功能。
5.2. 图片模板{{@var}}
{{@var}}
代码示例
// 本地图片
put('localPicture', new PictureRenderData(120, 120, './sayi.png'));
// 图片流文件
put('localBytePicture', new PictureRenderData(100, 120, '.png', new FileInputStream('./logo.png')));
// 网络图片
put('urlPicture', new PictureRenderData(100, 100, '.png', BytePictureUtils.getUrlBufferedImage('https://avatars3./u/1394854')));
// java 图片
put('bufferImagePicture', new PictureRenderData(100, 120, '.png', bufferImage))); 可以指定图片的宽度和高度,也支持 PictureRenderData 的结构体
5.3. 表格模板{{#var}}
{{#var}}
poi-tl默认实现了N行N列的样式(如下图),同时提供了当数据为空时,展示一行空数据的文案(如下图中的No Data Descs),数据模型是 MiniTableRenderData 的结构体{
'rowDatas': [ (1)
{
'cellDatas': [ (2)
{
'renderData': [TextRenderData],
'cellStyle': { (3)
'align': 'center',
'backgroundColor': 'ff9800'
}
}
],
'rowStyle': { (4)
'align': 'center',
'backgroundColor': 'ff9800'
}
}
],
'header': { (5)
'cellDatas': [
{
'renderData': [TextRenderData],
'cellStyle': {
'align': 'center',
'backgroundColor': 'ff9800'
}
}
],
'rowStyle': { (4)
'align': 'center',
'backgroundColor': 'ff9800'
}
},
'noDatadesc': 'No Data Desc', (6)
'style': { (7)
'align': 'center'
}
'width': 14.65 (8)
}
代码示例
需求的丰富多彩往往是默认表格样式无法满足的,我们通常会遇到以下两个场景: 场景一: 完全由自己掌控整个表格的生成:参见插件-开发一个插件。 场景二: 在一个已有的表格中,动态处理某些单元格数据:提供了抽象表格策略DynamicTableRenderPolicy或者引用渲染策略插件OptionalTextTableRefRenderPolicy,参见示例-付款通知书。 5.4. 列表模板{{*var}}
{{*var}}
代码示例
put('feature', new NumbericRenderData(new ArrayList<TextRenderData>() {
{
add(new TextRenderData('Plug-in grammar'));
add(new TextRenderData('Supports word text, header...'));
add(new TextRenderData('Not just templates, but also style templates'));
}
})); 列表样式支持罗马字符、有序无序等。参见NumbericRenderData.FMT_*。
5.5. 文档模板{{+var}}
{{+var}}
代码示例
List<SegmentData> segments = new ArrayList<SegmentData>();
SegmentData s1 = new SegmentData();
s1.setTitle('经常抱怨的自己');
s1.setContent('每个人生活得都不容易,经常向别人抱怨的人,说白了就是把对方当做“垃圾场”,你一股脑地将自己的埋怨与不满倒给别人,自己倒是爽了,你有考虑过对方的感受吗?对方的脸上可能一笑了之,但是心里可能有一万只草泥马奔腾而过。');
segments.add(s1);
SegmentData s2 = new SegmentData();
s2.setTitle('拖拖拉拉的自己');
s2.setContent('能够今天做完的事情,不要拖到明天,你的事情没有任何人有义务去帮你做;不要做“宅男”、不要当“宅女”,放假的日子约上三五好友出去转转;经常动手做家务,既能分担伴侣的负担,又有一个干净舒适的环境何乐而不为呢?');
segments.add(s2);
put('docx_word', new DocxRenderData(new File('~/segment.docx'), segments)); (1) (2)
参见示例-一篇文章 6. 配置poi-tl提供了配置类
6.2. 自定义语法默认的图片模板语法是{{@var}},如果我们希望使用新语法{{%var}}作为图片模板:
高度扩展性体现在连自己的默认语法都可以任意更改: builder.addPlugin('@', new MiniTableRenderPolicy());
builder.addPlugin('#', new PictureRenderPolicy()); 这样{{@var}}就变成了表格模板,{{#var}}变成了图片模板,虽然不建议改变內建模板语法,但是从中可以看到poi-tl插件的设计思想,深藏功与名。 6.3. 标签规则模板标签支持中文、字母、数字、下划线的组合,比如{{客户手机号}},我们可以通过正则表达式来配置标签的规则,比如不允许中文:
6.4. 静默行为poi-tl在模板标签表达式无法计算结果时默认会认为标签对应的数据为null,当标签对应的数据为null或者不合法时,模板标签默认会被清空而不是保留,这种行为被称为静默模式,我们可以通过配置来改变这种行为。 一个模板标签表达式的结果无法被计算的时候,可以通过ElMode来配置行为: // 默认行为,EL静默模式,表达式计算错误的情况下结果置为null
builder.setElMode(ELMode.POI_TL_STANDARD_MODE);
// 严格EL模式,表达式计算错误的情况下抛出异常,这种情况下要求表达式必须可被计算
builder.setElMode(ELMode.POI_TL_STICT_MODE); 模板标签表达式找不到对应数据、计算结果为null或者数据不合法的时候,可以配置模板标签的行为:
6.5. SpingEL表达式Spring Expression Language (SpEL)是一个强大的表达式语言,支持在运行时查询和操作对象图。在使用SpEL前需要引入相应的依赖: <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.3.6.RELEASE</version>
</dependency> poi-tl的表达式模板支持切换到SpEL模式:
关于SpEL的写法可以参见官网,下面给出一些典型的示例。 {{name}}
{{name.toUpperCase()}} (1)
{{empty?:'这个字段为空'}}
{{sex ? '男' : '女'}} (2)
{{new java.text.SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(time)}} (3)
{{new java.text.SimpleDateFormat('yyyy-MM-dd hh:mm').format(time)}}
{{price/10000 + '万元'}} (4)
{{dogs[0].name}} (5)
{{dogs[0].age}}
6.6. 模板生成模板模板引擎不仅仅可以生成文档,也可以生成新的模板,比如我们想构造这样的新模板:把原先的一个模板标签分成两个模板标签:
6.7. 日志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文件,可以配置日志级别和格式:
debug级别的日志会打印解析渲染过程中的信息,有利于程序调试,另外在模板引擎执行结束后会打印耗时信息: Successfully Render the template file in 13 millis 7. 插件插件是poi-tl的核心,插件的核心逻辑是在模板的基础上通过poi-tl和poi提供的API操作word文档,从而抵达 Do Anything Anywhere 的星辰大海。
7.1. 原生插件poi-tl默认提供了五个策略插件,用来处理文本、图片、列表、表格、文档合并等:
由于这五个插件如此通用,因此将这些插件注册为语法,从而搭建了poi-tl的五大內建模板语法,也构筑了poi-tl高度自由的语法插件体系。 7.2. 开发一个插件一个插件的实现就是要告诉我们在模板的某个地方用某些数据做某些事情,我们可以通过实现 public interface RenderPolicy {
void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template); (1) (2) (3)
}
按照行业习俗,我们先写一个Hello, world插件:
poi-tl提供了抽象模板类 public class HelloWorldRenderPolicy extends AbstractRenderPolicy<String> {
@Override
public void doRender(RenderContext<String> context) throws Exception {
// anywhere delegate (1)
WhereDelegate where = context.getWhereDelegate();
// any thing
//String thing = context.getThing();
String thing = 'Hello, world';
// do
where.renderText(thing);
}
}
接下来我们再写一个更复杂的插件,在模板标签位置完完全全使用代码创建一个表格,这样我们就可以随心所欲的操作表格:
CustomTableRenderPolicy通过
7.3. 将插件应用到模板当我们有个模板标签为{{report}},如果希望在这个位置做些不一样或者更复杂的事情,我们可以将插件应用到这个模板标签: ConfigureBuilder builder = Configure.newBuilder();
builder.bind('report', new CustomTableRenderPolicy()); ConfigureBuilder采用了链式调用的方式,可以一次性设置多个模板的插件:
7.4. 将插件注册为语法当我们的插件具有一定的通用能力就可以将其注册为语法。比如增加%语法:{{%var}},对应自定义的渲染策略 builder.addPlugin('%', new HelloWorldRenderPolicy()); 7.5. 辅助类Helper在內建策略插件中,通常会提供一个静态Helper辅助类,在我们实现自己的RenderPolicy时,也可以通过这些辅助类操作文档。
7.6. Plugin Code Example我想用一个完整的代码示例向你展示 Do Anything Anywhere 的想法,它不使用任何poi-tl的默认插件,完全使用匿名类创建新插件完成。 插件可以看成是一个函数,它的入参是anywhere和anything,函数体就是do something。 // where绑定policy
Configure config = Configure.newBuilder().bind('sea', new AbstractRenderPolicy<String>() { (1)
@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>() { (2)
@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, 500, 300);
}
finally {
IOUtils.closeQuietly(stream);
}
// clear
clearPlaceholder(context, false);
}
}).bind('sea_feature', new AbstractRenderPolicy<List<String>>() { (3)
@Override
public void doRender(RenderContext<List<String>> context) throws Exception {
// anywhere delegate
WhereDelegate where = context.getWhereDelegate();
// anything
List<String> thing = context.getThing();
// do 列表
where.renderNumberic(NumbericRenderData.build(thing.toArray(new String[] {})));
// clear
clearPlaceholder(context, true);
}
}).build();
// 初始化where的数据
HashMap<String, Object> args = new HashMap<String, Object>();
args.put('sea', 'Hello, world!');
args.put('sea_img', 'src/test/resources/sea.jpg');
args.put('sea_feature', Arrays.asList('面朝大海春暖花开', '今朝有酒今朝醉'));
args.put('sea_location', Arrays.asList('日落:日落山花红四海', '花海:你想要的都在这里'));
// 一行代码
XWPFTemplate.compile('src/test/resources/sea.docx', config).render(args)
.writeToFile('out_sea.docx');
7.7. 引用渲染策略插件对于文档中的元素(元素包括不限于表格、图片),很多时候我们只想改变它的一点点属性,比如对于一个模板中布局好的图片我们只想替换图片内容,普通的渲染策略如果做到这一点可能需要重新创建整个图片,然后再设置期望的布局… 引用渲染策略ReferenceRenderPolicy就这样诞生了,它提供了直接引用文档中的元素句柄的能力,这个重要的特性在我们只想改变文档中某个元素极小一部分样式和属性的时候特别有用,因为其余样式和属性都可以在模板中预置好,真正的所见即所得。
locate抽象方法是用来定位具体的文档元素的,这个方法的实现充满了想象空间。poi-tl默认提供了两种方式:一种是通过元素在文档的位置,一种是匹配元素的可选文字,推荐使用可选文字引用元素。 我们以poi-tl内置的引用渲染策略插件 首先在模板中,任意设置图片布局和格式(比如衬于文字下方),可选文字在标题或说明中填写'let’s img'(文字内容没有任何要求,可以输入任何字符) 接下来就可以绑定引用渲染策略替换图片了: Configure configure = Configure.newBuilder()
.referencePolicy(new ReplaceOptionalTextPictureRefRenderPolicy('let's img', (1) (2)
new FileInputStream('sayi.png'),
XWPFDocument.PICTURE_TYPE_PNG))
.build();
XWPFTemplate template = XWPFTemplate.compile('template.docx', configure)
.render(new HashMap<>());
template.writeToFile('out.docx');
最终运行的结果是图片布局格式皆不变,只把图片替换成了另一个图片。 7.8. 可选插件列表除了五个通用的策略插件外,还内置了一些额外用途的插件。
8. 示例接下来的示例采取三段式output+template+data-model来说明,首先直接展示生成后的文档,然后一览模板的样子,最后我们对数据模型做个介绍。 8.1. 软件说明文档output
需要生成这样的一份软件说明书:拥有封面和页眉,正文含有不同样式的文本,还有表格,列表和图片。下载最终生成的文件poi_tl.docx template
使用poi-tl语法制作模板,可以看到模板标签不仅仅是模板,同样也是样式标签。 这个示例向我们展示了poi-tl最基本的能力,它在模板标签位置,插入基本的数据模型。同时也向我们展示了无需编码设置样式:模板,不仅仅是标签模板还是样式模板,所见即所得。 8.2. 付款通知书output
需要生成这样的一份流行的通知书:大部分数据是由表格构成的,需要创建一个订单的表格,还需要在一个已有表格中,填充货物明细和人工费数据。下载最终生成的文件payment.docx template
使用{{#order}}生成poi-tl提供的默认样式的表格,设置{{detail_table}}为自定义模板渲染策略(继承抽象表格策略DynamicTableRenderPolicy),自定义已有表格中部分单元格的渲染。 这个示例向我们展示了poi-tl在表格操作上的一些思考。示例中货物明细和人工费的表格就是一个相当复杂的表格,货物明细是由7列组成,行数不定,人工费是由4列组成,行数不定。 默认表格数据模型(MiniTableRenderData)实现了最基本的样式,当需求中的表格更加复杂的时候,我们完全可以设计好那些固定的部分,将需要动态渲染的部分单元格交给自定义模板渲染策略。 poi-tl提供了抽象表格策略DynamicTableRenderPolicy来实现这样的功能,{{detail_table}}标签可以在表格内的任意单元格内,DynamicTableRenderPolicy会获取XWPFTable对象进而获得操作整个表格的能力。
新建渲染策略DetailTablePolicy,继承于抽象表格策略。 public class DetailTablePolicy extends DynamicTableRenderPolicy {
// 货品填充数据所在行数
int goodsStartRow = 2;
// 人工费填充数据所在行数
int laborsStartRow = 5;
@Override
public void render(XWPFTable table, Object data) {
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);
// 渲染单行人工费数据
MiniTableRenderPolicy.Helper.renderRow(table, 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();
// 渲染单行货品明细数据
MiniTableRenderPolicy.Helper.renderRow(table, goodsStartRow, goods.get(i));
}
}
}
} 将模板标签{{detail_table}}设置成此策略。
8.3. 一篇文章output
需要生成这样的一系列文章:除了标题作者之外,它的内容是有规律的,内容是由一行蓝色的标题,一段文字,一张图片构成。下载最终生成的文件story.docx template
文章的内容是个典型的文档模板类型,我们制作一个待合并的文档模板segment.docx(下图右侧),主模板story.docx看起来很简单,其中{{+segment}}标签将会被文档模板循环合并。 这个示例充分展示了poi-tl的文档模板和循环功能。当有一段固定样式的段落,根据集合数据循环填充后展示。示例中标题+文字+图片就是这样的可重复段落。 基本原理是后台提供数据模型的集合,不断渲染segment.docx,将渲染结果合并到story.docx文档中。
8.4. 个人简历output
需要生成这样的一份流行的个人简历:左侧是个人的基本信息,技术栈是个典型的列表,右侧是个人的工作经历,数量不定。下载最终生成的文件resume.docx template
工作经历是个典型的文档模板类型,我们制作两个模板,一套主模板简历.docx(下图左侧),一套为文档模板segment.docx(下图右侧)。 看起来很复杂的简历,其实对于模版引擎来说,和普通的Word文档没有什么区别,我们只需要制作好一份简历,将需要替换的内容用模版标签代替。 因为模版即样式,模版引擎无需考虑样式,只关心数据,我们甚至可以制作10种不同样式的简历模板,用同一份数据去渲染。
9. LicenseApache License 2.0 11. 打赏个小费poi-tl开源的初衷是希望让所有有需要的人享受Word模板引擎的功能,如果你觉得它节省了你的时间,给你带来了方便和灵感,或者认同这个开源项目,可以为我的付出打赏点小费哦。 12. 常见问题
|
|
来自: 芥子c1yw3tb42g > 《谋生之道-自律自由》