1poi 是什么?
最近项目中需要导出一个word格式的报告,经过调研后发现po 非常好用,特此总结。
poi-tl(poi template language)是Word模板引擎,使用模板和数据创建很棒的Word文档。
官网地址:中文文档地址
源码地址:https://github.com/Sayi/poi-tl
poi-tl是一个基于Apache POI的Word模板引擎,也是一个免费开源的Java类库,你可以非常方便的加入到你的项目中,并且拥有着让人喜悦的特性。
引入依赖
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.2</version>
</dependency>
2使用
简单说poi-tl就是在模板文件中填充数据
2.1 模板
模板是Docx格式的Word文档,你可以使用Microsoft office、WPS Office、Pages等任何你喜欢的软件制作模板,也可以使用Apache POI代码来生成模板。
所有的标签都是以{{
开头,以}}
结尾,标签可以出现在任何位置,包括页眉,页脚,表格内部,文本框等,表格布局可以设计出很多优秀专业的文档,推荐使用表格布局。
poi-tl模板遵循“所见即所得”的设计,模板和标签的样式会被完全保留。
2.2 数据
数据类似于哈希或者字典,可以是Map结构(key是标签名称):
Map<String, Object> data = new HashMap<>();
data.put("name", "Sayi");
data.put("start_time", "2019-08-04");
可以是对象(属性名是标签名称):
public class Data {
private String name;
private String startTime;
private Author author;
}
数据可以是树结构,每级之间用点来分隔开,比如{{author.name}}
标签对应的数据是author对象的name属性值。
FreeMarker、Velocity文本模板中可以通过三个标签设置图片路径、宽和高:<img src="{{path}}" width="{{width}}" height="{{height}}"> , 但是Word模板不是由简单的文本表示,所以在渲染图片、表格等元素时提供了数据模型,它们都实现了接口 RenderData ,比如图片数据模型 PictureRenderData 包含图片路径、宽、高三个属性。 |
2.3 输出
以流的方式进行输出:
template.write(OutputStream stream);
比如文件流:
template.write(new FileOutputStream("output.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);
3 标签
3.1 文本
{{var}}
String
:文本TextRenderData
:有样式的文本HyperlinkTextRenderData
:超链接和锚点文本Object
:调用 toString() 方法转化为文本
推荐使用工厂 Texts
构建文本模型。
代码示例
put("name", "Sayi");
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());
所见即所得,标签的样式会应用到替换后的文本上,也可以通过代码设定文本的样式。
TextRenderData的结构体
{
"text": "Sayi",
"style": {
"strike": false,
"bold": true,
"italic": false,
"color": "00FF00",
"underLine": false,
"fontFamily": "微软雅黑",
"fontSize": 12,
"highlightColor": "green",
"vertAlign": "superscript",
"characterSpacing" : 20
}
}
删除线 | |
粗体 | |
斜体 | |
颜色 | |
下划线 | |
字体 | |
字号 | |
背景高亮色 | |
上标或者下标 | |
间距 |
示例代码
// 使用ClassPathResource加载资源文件,把模板文件放在resource中 这样打包的时候会带上这个文件
ClassPathResource template = new ClassPathResource("template.docx");
//获取当前项目所在路径
String projectPath = System.getProperty("user.dir");
String fileName = "template.docx";
File file = new File(projectPath + File.separator + fileName);
FileUtils.copyInputStreamToFile(template.getInputStream(), file);
XWPFTemplate xwpfTemplate = XWPFTemplate.compile(file);
Map<String, Object> map = new HashMap<>();
Style style = new Style();
style.setStrike(true);
style.setColor("00FF00");
style.setBold(true);
TextRenderData textRenderData = Texts.of("测试").fontFamily("黑体").style(style).create();
map.put("title",textRenderData);
xwpfTemplate.render(map);
xwpfTemplate.writeAndClose(new FileOutputStream("D:\\Coding\\poi\\src\\main\\java\\cn\\dxsc\\output.docx"));
结果
3.2 图片
图片标签以@开始:{{@var}}
数据模型:
String
:图片url或者本地路径,默认使用图片自身尺寸ByteArrayPictureRenderData
FilePictureRenderData
UrlPictureRenderData
推荐使用工厂 Pictures
构建图片模型。
data.put("images1", Pictures.ofLocal("D:\\1\\test.jpg").size(300,400).create());
data.put("streamImg",Pictures.ofStream(new FileInputStream("D:\\1\\test.jpg")).size(300,400).create());
data.put("urlImg",Pictures.ofUrl("http://xx:9000/xxx/xx_1722915490116.jpg").size(300,400).create());
3.3 表格
表格标签以#开始:{{#var}}
数据模型:
TableRenderData
推荐使用工厂 Tables
、 Rows
和 Cells
构建表格模型。
TableRenderData tableRenderData = Tables.of(new String[][]{
new String[]{"列1", "列2"},
new String[]{"列3", "列4"}
}).create();
// 创建表格
data.put("t0", tableRenderData);
RowRenderData r1 = Rows.of("姓名", "学历", "年龄", "其他").textColor("FFFFFF").bgColor("4472C4").center().create();
RowRenderData r2 = Rows.of("张三", "本科", "18", "无").center().create();
data.put("t1", Tables.of(r1, r2).create());
//合并表格
RowRenderData r3 = Rows.of("李四", "高中", null,null).create();
MergeCellRule rule = MergeCellRule.builder().map(MergeCellRule.Grid.of(1, 2), MergeCellRule.Grid.of(1, 3)).build();
data.put("t2", Tables.of(r1, r3).mergeRule(rule).create());
列表 区块对 嵌套 用的不多,具体参看官方文档
4 引用标签
引用标签是一种特殊位置的特殊标签,提供了直接引用文档中的元素句柄的能力,这个重要的特性在我们只想改变文档中某个元素极小一部分样式和属性的时候特别有用,因为其余样式和属性都可以在模板中预置好,真正的所见即所得。
4.1 图片
引用图片标签是一个文本:{{var}},标签位置在:设置图片格式—可选文字—标题或者说明(新版本Microsoft Office标签位置在:编辑替换文字-替换文字)。
他这个文档说的非常模糊,具体在
4.2 图表
多系列图表指的是条形图(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);
模板中的图表格式
运行代码之后
4.3 单系列图标
单系列图表指的是饼图(3D饼图)、圆环图等。
单系列图表的标签是一个文本:{{var}},标签位置在:图表区格式—可选文字—标题(新版本Microsoft Office标签位置在:编辑替换文字-替换文字)。
ChartSingleSeriesRenderData pie = Charts
.ofSingleSeries("ChartTitle", new String[] { "美国", "中国" })
.series("countries", new Integer[] { 9826675, 9596961 })
.create();
put("pieChart", pie);
效果如下
4.4 组合图标
组合图表指的是由多系列图表(柱形图、折线图、面积图)组合而成的图表。
组合图表的标签是一个文本:{{var}},标签位置在:图表区格式—可选文字—标题(新版本Microsoft Office标签位置在:编辑替换文字-替换文字)。
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);
5 配置
图表标题 | |
种类 | |
所有系列 | |
当前系列名称 | |
当前系列的图表类型comboType:柱形图BAR、折线图LINE、面积图AREA | |
当前系列对应每个种类的值 |
5. 配置
poi-tl提供了类 Configure
来配置常用的设置,使用方式如下:
ConfigureBuilder builder = Configure.builder();
XWPFTemplate.compile("template.docx", builder.buid());
5.1. 前后缀
我一直使用 {{}}
的方式来致敬Google CTemplate,如果你更偏爱freemarker ${}
的方式:
builder.buildGramer("${", "}");
5.2. 标签类型
默认的图片标签是以@开始,如果你希望使用%开始作为图片标签:
builder.addPlugin('%', new PictureRenderPolicy());
如果你不是很喜欢默认的标签标识类型,你也可以自由更改:
builder.addPlugin('@', new TableRenderPolicy());
builder.addPlugin('#', new PictureRenderPolicy());
这样{{@var}}就变成了表格标签,{{#var}}变成了图片标签,虽然不建议改变默认标签标识,但是从中可以看到poi-tl插件的灵活度,在插件章节中我们将会看到如何自定义自己的标签。
5.3. 标签格式
标签默认支持中文、字母、数字、下划线的组合,我们可以通过正则表达式来配置标签的规则,比如不允许中文:
builder.buildGrammerRegex("[\\w]+(\\.[\\w]+)*");
比如允许除了标签前后缀外的任意字符:
builder.buildGrammerRegex(RegexUtils.createGeneral("{{", "}}"));
5.4. Spring表达式
Spring Expression Language 是一个强大的表达式语言,支持在运行时查询和操作对象图,可作为独立组件使用,需要引入相应的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.18</version>
</dependency>
为了在模板标签中使用SpringEL表达式,需要将标签配置为SpringEL模式:
builder.useSpringEL();
5.4.1. 基本使用
关于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日'))}}
类方法调用,转大写 | |
判断条件 | |
三目运算符 | |
类方法调用,时间格式化 | |
运算符 | |
数组列表使用下标访问 | |
使用静态类方法 |
5.4.2. SpringEL作为区块对的条件
Spring表达式与区块对结合可以实现更强大的功能,示例如下:
data-model
{
"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
使用SpringEL时区块对的结束标签可以是:{{/}}。 |
5.5. 错误处理
poi-tl支持在发生错误的时候定制引擎的行为。
5.5.1. 标签无法被计算
标签无法被计算的场景有几种,比如模板中引用了一个不存在的变量,或者级联的前置结果不是一个哈希,如 {{author.name}}
中author的值为null,此时就无法计算name的值。
poi-tl可以在发生这种错误时对计算结果进行配置,默认会认为标签值为null
。当我们需要严格校验模板是否有人为失误时,可以抛出异常:
builder.useDefaultEL(true);
注意的是,如果使用SpringEL表达式,可以通过参数来配置是否抛出异常:
builder.useSpringEL(true);
5.5.2. 标签数据类型不合法
我们知道渲染图片、表格等标签时对数据模型是有要求的,如果数据不合法(为NULL或者是一个错误的数据类型),可以配置模板标签的渲染行为。
poi-tl默认的行为会清空标签,如果希望对标签不作任何处理:
builder.setValidErrorHandler(new DiscardHandler());
如果希望执行严格的校验,直接抛出异常:
builder.setValidErrorHandler(new AbortHandler());
5.6. 模板生成模板
模板引擎不仅仅可以生成文档,也可以生成新的模板,比如我们把原先的一个文本标签分成一个文本标签和一个表格标签:
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);
5.7. 无模板创建文档
使用 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);
5.8. 日志
poi-tl使用slf4j作为日志门面,你可以自由选择日志实现,比如logback、log4j等。我们以logback为例:
首先在项目中添加logback依赖:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.13</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.13</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
6 插件
看官网吧 到这里基本上能覆盖业务的90%了