# 一、处理Word工具
下表对一些处理Word的解决方案作了一些比较:
方案 | 跨平台 | 样式处理 | 易用性 |
---|---|---|---|
Poi-tl | 纯Java组件,跨平台 | 不需要编码,模板即样式 | 简单:模板引擎,对POI进行封装,支持Word文档合并、表格处理等 |
Apache POI | 纯Java组件,跨平台 | 编码 | 简单,没有模板引擎功能 |
Freemarker | XML操作,跨平台 | 无 | 复杂,需要理解XML结构,基于XML构造模板 |
OpenOffice | 需要安装OpenOffice软件 | 编码 | 复杂,需要了解OpenOffice的API |
Jacob、winlib | Windows平台 | 编码 | 复杂,不推荐使用 |
# 二、poi-tl模板引擎
poi-tl官方使用文档API地址:http://deepoove.com/poi-tl (opens new window)
poi-tl中文文档 (opens new window) or English-tutorial Wiki (opens new window)
Java Word的模板引擎,对docx格式的文档增加模板语法,支持对段落、页眉、页脚、表格等模板替换,并且提供了插件机制,在文档的任何地方做任何事情。
poi-tl是基于Apache POI的一套拥有简洁API的跨平台的模板引擎,纯Java组件,是一个免费开源的Java类库。
根据poi-tl 可以操作含有多种类型的复杂 Word 文档,包括:
- 文本
- 表格
- 图片
- 附件
- markdown
并且支持表格行循环,表格列循环,动态表格,批注,附件,高亮等等。
# poi-tl功能点
Word模板引擎功能 | 描述 |
---|---|
文本 | 将标签渲染为文本 |
图片 | 将标签渲染为图片 |
表格 | 将标签渲染为表格 |
列表 | 将标签渲染为列表 |
图表 | 条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、饼图(3D饼图)、散点图等图表渲染 |
If Condition判断 | 根据条件隐藏或者显示某些文档内容(包括文本、段落、图片、表格、列表、图表等) |
Foreach Loop循环 | 根据集合循环某些文档内容(包括文本、段落、图片、表格、列表、图表等) |
Loop表格行 | 循环复制渲染表格的某一行 |
Loop表格列 | 循环复制渲染表格的某一列 |
Loop有序列表 | 支持有序列表的循环,同时支持多级列表 |
Highlight代码高亮 | word中代码块高亮展示,支持26种语言和上百种着色样式 |
Markdown | 将Markdown渲染为word文档 |
Word批注 | 完整的批注功能,创建批注、修改批注等 |
Word附件 | Word中插入附件 |
SDT内容控件 | 内容控件内标签支持 |
Textbox文本框 | 文本框内标签支持 |
图片替换 | 将原有图片替换成另一张图片 |
书签、锚点、超链接 | 支持设置书签,文档内锚点和超链接功能 |
样式 | 模板即样式,同时代码也可以设置样式 |
模板嵌套 | 模板包含子模板,子模板再包含子模板 |
合并 | Word合并Merge,也可以在指定位置进行合并 |
Expression Language | 完全支持SpringEL表达式,可以扩展更多的表达式:OGNL, MVEL… |
用户自定义函数(插件) | 插件化设计,在文档任何位置执行函数 |
# 架构设计
模板和插件构建了整个Poi-tl的核心。 Poi-tl通过极简的架构实现了模板功能并且支持最大的扩展性,JAR包体积仅有几十KB。
整体设计采用了Template + data-model = output
模式。
Configure提供了模板配置功能,比如语法配置和插件配置;
Visitor提供了模板解析功能;
RenderPolicy是渲染策略扩展点;
Render模块提供了RenderDataCompute表达式计算扩展点,通过RenderPolicy对每个标签进行渲染。
# Template模板
模板是Docx格式的Word文档,你可以使用Microsoft office、WPS Office、Pages等任何你喜欢的软件制作模板,也可以使用Apache POI代码来生成模板。
所有的标签都是以{{
开头,以}}
结尾,标签可以出现在任何位置,包括页眉,页脚,表格内部,文本框等,表格布局可以设计出很多优秀专业的文档,推荐使用表格布局。
poi-tl模板遵循**“所见即所得”**的设计,模板和标签的样式会被完全保留。
# data-model数据
数据类似于哈希或者字典,可以是Map结构(key是标签名称):
Map<String, Object> data = new HashMap<>();
data.put("name", "Sayi");
data.put("start_time", "2019-08-04");
2
3
可以是对象(属性名是标签名称)
public class Data {
private String name;
private String startTime;
private Author author;
}
2
3
4
5
数据也可以是树结构,每级之间用点来分隔开。比如
{{author.name}} 标签对应的数据是author对象的name属性值。
Word模板不是由简单的文本表示,所以在渲染图片、表格等元素时提供了数据模型,它们都实现了接口RenderData,比如图片数据模型PictureRenderData包含图片路径、宽、高三个属性。
# output输出
以流的方式进行输出:
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);
2
3
4
5
6
7
8
9
10
最后不要忘记关闭这些流。
# 语法结构
所有的语法结构都是以{{
开始,以}}
结束。
{{ 标记类型 }}
标记类型 | 描述 |
---|---|
template | 普通文本 |
@template | 图片 |
#template | 表格 |
*template | 列表 |
+template | Word文档合并 |
# 文本
{{template}}
数据模型:
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"));
2
3
4
除了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());
2
3
# 名词解释
Word模板引擎功能 | 描述 |
---|---|
文本 | 将标签渲染为文本 |
图片 | 将标签渲染为图片 |
表格 | 将标签渲染为表格 |
列表 | 将标签渲染为列表 |
图表 | 条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、饼图(3D饼图)、散点图等图表渲染 |
If Condition判断 | 根据条件隐藏或者显示某些文档内容(包括文本、段落、图片、表格、列表、图表等) |
Foreach Loop循环 | 根据集合循环某些文档内容(包括文本、段落、图片、表格、列表、图表等) |
Loop表格行 | 循环复制渲染表格的某一行 |
Loop表格列 | 循环复制渲染表格的某一列 |
Loop有序列表 | 支持有序列表的循环,同时支持多级列表 |
Highlight代码高亮 | word中代码块高亮展示,支持26种语言和上百种着色样式 |
Markdown | 将Markdown渲染为word文档 |
Word批注 | 完整的批注功能,创建批注、修改批注等 |
Word附件 | Word中插入附件 |
SDT内容控件 | 内容控件内标签支持 |
Textbox文本框 | 文本框内标签支持 |
图片替换 | 将原有图片替换成另一张图片 |
书签、锚点、超链接 | 支持设置书签,文档内锚点和超链接功能 |
Expression Language | 完全支持SpringEL表达式,可以扩展更多的表达式:OGNL, MVEL… |
样式 | 模板即样式,同时代码也可以设置样式 |
模板嵌套 | 模板包含子模板,子模板再包含子模板 |
合并 | Word合并Merge,也可以在指定位置进行合并 |
用户自定义函数(插件) | 插件化设计,在文档任何位置执行函数 |
注意:只能操作.docx
格式的word,不能操作.doc
格式的word. 只能操作word中的表格, 不能操作Excel中的表格。
# 核心API
XWPFTemplate
,核心API只需要一行代码。
XWPFTemplate template = XWPFTemplate.compile("~/file.docx").render(datas);
# 版本说明
在使用poi-tl时, 需要注意版本之间的冲突问题。
- 1.12.0 Documentation(当前版本) (opens new window),Apache POI5.2.2+,JDK1.8+
- 1.11.x Documentation (opens new window),Apache POI5.1.0+,JDK1.8+
- 1.10.x Documentation (opens new window),Apache POI4.1.2,JDK1.8+
- 1.10.3 Documentation (opens new window),Apache POI4.1.2,JDK1.8+
- 1.9.x Documentation (opens new window),Apache POI4.1.2,JDK1.8+
V1.12.0版本作了一个不兼容的改动,升级的时候需要注意:
- 重构了PictureRenderData,改为抽象类,建议使用Pictures工厂方法来创建图片数据
# 三、poi-tl开发
# Maven依赖
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.1</version>
</dependency>
2
3
4
5
NOTE: poi-tl
1.12.x
requires POI version5.2.2+
.
# 快速开始
从一个超级简单的例子开始:
把`{{title}}`替换成"Poi-tl 模板引擎"。
1. 新建文档template.docx,包含文本`{{title}}`
2. TDO模式:Template + data-model = output
2
3
4
代码示例:
//核心API采用了极简设计,只需要一行代码
XWPFTemplate.compile("template.docx").render(new HashMap<String, Object>(){{
put("title", "Poi-tl 模板引擎");
}}).writeToFile("out_template.docx");
2
3
4
# 对象属性赋值
map方式(最简单实用),数据是Map结构,KEY是标签名称,VALUE是对应的值。
单值
Map<String, Object> content = new HashMap<>();
content.put("name", "Sapi");
2
也可以是对象,属性名是标签名称。
public class User {
private Long id;
private String name;
}
2
3
4
Map<String, Object> content = new HashMap<>();
content.put("user", new User(1, "法外狂徒张三"));
2
doc文档中获取数据语法结构
{{user.name}}
# 四、poi-tl扩展
poi-tl 更多插件 (opens new window)
# 表格行循环
需要在Configure对象中绑定需要循环的list对象。
public class TestTableServiceImpl {
public static final String ASSET_CHANGE_FILE_PATH = "/doc/word_table.docx";
public XWPFTemplate exportDoc() throws IOException {
//1 获取模板文件流
File resourceFile = new ClassPathResource(ASSET_CHANGE_FILE_PATH).getFile();
//2 构建word数据
Map<String, Object> content = new HashMap<>();
// content.put("assets", fillAssets());
content.put("assets", getTableDatas());
//3 创建行循环策略
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy(true);
// 告诉模板引擎,要在tagName做行循环,绑定行循环策略
Configure config = Configure.builder().bind("assets", policy).build();
// 编译渲染
return XWPFTemplate.compile(resourceFile, config).render(content);
}
private List<TableData> getTableDatas() {
List<TableData> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
TableData table = new TableData();
table.setIndex(i + 1);
table.setCode("TMP" + i);
table.setName("测试数据" + i);
table.setRemark("备注" + i);
list.add(table);
}
return list;
}
}
@Data
public class TableData {
private Integer index;
private String code;
private String name;
private String remark;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
创建表格模板
导出填充后的效果
# SpringEL表达式 (opens new window)
Spring Expression Language
是一个强大的表达式语言,支持在运行时查询和操作对象图,可作为独立组件使用,也可作为poi-tl模板上, 用于模板填充时参数的引用。
单独使用时需要引入相应的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.18</version>
</dependency>
2
3
4
5
关于SpringEL的写法可以参见Spring官方文档 (opens new window),下面给出一些典型的示例
{{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日'))}} 使用静态类方法
2
3
4
5
6
7
8
9
# 插件poi-tl-ext
插件描述:在poi-tl的基础上扩展渲染HTML,目前实现了富文本编辑器可实现的大部分效果
插件源码地址:https://github.com/draco1023/poi-tl-ext
# 五、poi-tl示例
Spring Boot项目集成poi-tl示例。
# 1、普通文本示例
# Maven依赖
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.1</version>
</dependency>
2
3
4
5
NOTE: poi-tl
1.12.x
requires POI version5.2.2+
.
# DOC模板绘制
- 绘制将要导出的DOC文档,数据值用SpringEL表达式替换。
- 存放在项目的
resources/doc
目录,文件名为myTextDoc.docx
。
# Controller
@GetMapping("/exportDoc/{id}")
public void exportDoc(@PathVariable("id") Long id, HttpServletResponse response) {
try {
String fileName = "申请表_" + DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss") + ".docx";
XWPFTemplate document = myService.exportDoc(id);
response.reset();
response.setContentType("application/octet-stream;charset=UTF-8");
response.setHeader("Content-disposition",
"attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
OutputStream os = response.getOutputStream();
document.write(os);
os.close();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Service
public static final String ALLOCATE_FILE_PATH = "/doc/myTextDoc.docx";
public XWPFTemplate generateWordXWPFTemplate(ProcessAllocate data, ProcessAllocateOpinionVO opinion) throws IOException {
Map<String, Object> content = new HashMap<>();
content.put("data", data);
content.put("opinion", opinion);
// 日期处理
Date requestDate = data.getRequestDate();
content.put("year", DateUtil.year(requestDate));
content.put("month", DateUtil.month(requestDate) + 1);
content.put("day", DateUtil.dayOfMonth(requestDate));
content.put("requestDate", DateUtil.format(data.getRequestDate(), DatePattern.NORM_DATE_PATTERN));
return XWPFTemplate.compile(new ClassPathResource(ALLOCATE_FILE_PATH).getFile()).render(content);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2、表格示例
# Maven依赖
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.1</version>
</dependency>
2
3
4
5
NOTE: poi-tl
1.12.x
requires POI version5.2.2+
.
# DOC模板绘制
- 绘制将要导出的DOC文档,数据值用SpringEL表达式替换。
- 存放在项目的
resources/doc
目录,文件名为myTableDoc.docx
。
# Controller
@GetMapping("/exportDoc/{id}")
public void exportDoc(@PathVariable("id") Long id, HttpServletResponse response) {
try {
String fileName = "变更表_" + DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss") + ".docx";
XWPFTemplate document = myService.exportDoc(id);
response.reset();
response.setContentType("application/octet-stream;charset=UTF-8");
response.setHeader("Content-disposition",
"attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
OutputStream os = response.getOutputStream();
document.write(os);
os.close();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Service
public static final String ALLOCATE_FILE_PATH = "/doc/myTableDoc.docx";
public XWPFTemplate exportDoc(Long id) throws IOException {
//1 获取模板文件流
File resourceFile = new ClassPathResource(ASSET_CHANGE_FILE_PATH).getFile();
//2 word数据
ProcessChangeData data = getInfo(id);
Map<String, Object> content = new HashMap<>();
content.put("change", data.getChange());
// 填充表格数据
content.put("assets", fillAssets(data.getAssets()));
Date requestTime = data.getChange().getRequestTime();
content.put("year", DateUtil.year(requestTime));
content.put("month", DateUtil.month(requestTime) + 1);
content.put("day", DateUtil.dayOfMonth(requestTime));
//3 表格指定插件
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy(true);
Configure config = Configure.builder().bind("assets", policy).build();
return XWPFTemplate.compile(resourceFile, config).render(content);
}
/**
* 转换填充表格数据
*/
private List<Map<String, Object>> fillAssets(List<ProcessChangeAnnex> assets) {
List<Map<String, Object>> list = new ArrayList<>();
for (int i = 0; i < assets.size(); i++) {
ProcessChangeAnnex annex = assets.get(i);
Map<String, Object> table = new HashMap<>();
table.put("index", i + 1);
table.put("assetCode", annex.getAssetCode());
table.put("assetName", annex.getAssetName());
table.put("categoryName", annex.getCategoryName());
// ...
list.add(table);
}
return list;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 3、图片示例
# DOC模板绘制
- 绘制将要导出的DOC文档,数据值用SpringEL表达式替换。
- 存放在项目的
resources/doc
目录,文件名为myImgDoc.docx
。
# Service**
public static final String ALLOCATE_FILE_PATH = "/doc/myImgDoc.docx";
public XWPFTemplate exportDoc(Long id) throws IOException {
//1 获取模板文件流
File resourceFile = new ClassPathResource(ASSET_CHANGE_FILE_PATH).getFile();
//2 word数据
ProcessChangeData data = getInfo(id);
Map<String, Object> content = new HashMap<>();
content.put("change", data.getChange());
// 二维码图片
try {
BufferedImage bufferImage = QrCodeUtils.defaultBufferedImage(data.getAid().toString());
PictureRenderData pictureRenderData = Pictures.ofBufferedImage(bufferImage, PictureType.PNG).size(100, 100).create();
content.put("qrImg", pictureRenderData);
} catch (Exception e) {
log.error("生成二维码发生异常", e);
}
return XWPFTemplate.compile(resourceFile, config).render(content);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21