技术标签: spring boot word
官方文档地址:http://deepoove.com/poi-tl/
源码地址:https://github.com/Sayi/poi-tl
poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建很棒的Word文档。
最近在做项目时候有一个关于导出Word的文件的需求,需要导出的word文件较大,并且格式比较复杂,使用poi-tl可以很好的解决。在这里记录一下关于复杂表格的合并与生成。
poi-tl的优势
poi-tl 是基于 Apache POI ,使用时请注意poi的版本依赖冲突问题
1. 添加依赖
<!--poi-tl-->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.0</version>
</dependency>
2.快速入门
新建Word文档template.docx,这里模板文件存放resources/word目录下,模板包含标签 {
{title}}
代码示例
@GetMapping("/export")
public void export(HttpServletResponse response) throws IOException {
// 获取模板文件流
InputStream resourceAsStream =
this.getClass().getResourceAsStream("/word/template.docx");
//poi-tl 配置
ConfigureBuilder builder = Configure.builder();
builder.useSpringEL(false);
Map<String,Object> map = new HashMap<>();
map.put("title","hello,poi-tl!");
XWPFTemplate template = XWPFTemplate.compile(Objects.requireNonNull(resourceAsStream), builder.build()).render(map);
//输出文件流
template.writeAndClose(new FileOutputStream("D:\\output.docx"));
}
3.输出
可以写到任意输出流中,比如文件流:
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);
导出的word中存在
单个
表格, 或动态的多个
表格
poi-tl提供了抽象表格策略类
DynamicTableRenderPolicy
我们可以自定义模板渲染策略类,继承即可,从而动态渲染的部分单元格,实现我们需求
1.新建数据存储实体类-
ServerTableData
@Data
public class ServerTableData {
/**
* 携带表格中真实数据
*/
private List<RowRenderData> serverDataList;
/**
* 携带要分组的信息
*/
private List<Map<String, Object>> groupDataList;
/**
* 需要合并的列,从0开始
*/
private Integer mergeColumn;
}
2.新建自定义表格渲染策略类-
ServerTablePolicy
-
public class ServerTablePolicy extends DynamicTableRenderPolicy {
@Override
public void render(XWPFTable xwpfTable, Object tableData) throws Exception {
if (null == tableData) {
return;
}
// 参数数据声明
ServerTableData serverTableData = (ServerTableData) tableData;
List<RowRenderData> serverDataList = serverTableData.getServerDataList();
List<Map<String, Object>> groupDataList = serverTableData.getGroupDataList();
Integer mergeColumn = serverTableData.getMergeColumn();
if (CollectionUtils.isNotEmpty(serverDataList)) {
// 先删除一行, demo中第一行是为了调整 三线表 样式
xwpfTable.removeRow(1);
// 行从中间插入, 因此采用倒序渲染数据
for (int i = serverDataList.size() - 1; i >= 0; i--) {
XWPFTableRow newRow = xwpfTable.insertNewTableRow(1);
newRow.setHeight(400);
for (int j = 0; j < 4; j++) {
newRow.createCell();
}
// 渲染一行数据
TableRenderPolicy.Helper.renderRow(newRow, serverDataList.get(i));
}
// 处理合并
for (int i = 0; i < serverDataList.size(); i++) {
// 获取要合并的名称那一列数据 mergeColumn代表要合并的列,从0开始
String typeNameData = serverDataList.get(i).getCells().get(mergeColumn).getParagraphs().get(0).getContents().get(0).toString();
for (int j = 0; j < groupDataList.size(); j++) {
String typeNameTemplate = String.valueOf(groupDataList.get(j).get("typeName"));
int listSize = Integer.parseInt(String.valueOf(groupDataList.get(j).get("listSize")));
// 若匹配上 就直接合并
if (typeNameTemplate.equals(typeNameData)) {
TableTools.mergeCellsVertically(xwpfTable, 0, i + 1, i + listSize);
groupDataList.remove(j);
break;
}
}
}
}
}
}
3.接口类
@GetMapping("/export")
public void export(HttpServletResponse response) throws IOException {
// 获取模板文件流
InputStream resourceAsStream =
this.getClass().getResourceAsStream("/word/template.docx");
//poi-tl 配置
ConfigureBuilder builder = Configure.builder();
builder.useSpringEL(false);
Map<String,Object> map = new HashMap<>();
// 伪造一个表格数据
//单个表格
ServerTableData oneTable = getServerTableData();
map.put("oneTable",oneTable);
builder.bind("oneTable",new ServerTablePolicy());
//多个表格
List<Map<String, Object>> dynamicFlag = new ArrayList<>();
// 伪造3个表格数据
for (int i = 0; i < 3; i++) {
ServerTableData tableData = getServerTableData();
Map<String, Object> dynamicTableMap = new HashMap<>();
dynamicTableMap.put("serverListTable", tableData);
dynamicTableMap.put("tableName", "表名");
dynamicFlag.add(dynamicTableMap);
}
map.put("listTable",dynamicFlag);
builder.bind("serverListTable",new ServerTablePolicy());
XWPFTemplate template = XWPFTemplate.compile(Objects.requireNonNull(resourceAsStream), builder.build()).render(map);
//输出网络流
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);
}
private ServerTableData getServerTableData() {
ServerTableData serverTableData = new ServerTableData();
List<RowRenderData> serverDataList = new ArrayList<>();
for (int j = 0; j < 4; j++) {
String typeName;
RowRenderData serverData;
if (j > 1) {
typeName = "索隆";
serverData = Rows.of(typeName, "喝酒", "三千世界", "无").center().create();
}else {
typeName = "路飞";
serverData = Rows.of(typeName, "大鸡腿", "巨人手枪", "橡胶果实").center().create();
}
serverDataList.add(serverData);
}
List<Map<String, Object>> groupDataList = new ArrayList<>();
Map<String, Object> groupData1 = new HashMap<>();
groupData1.put("typeName", "索隆");
groupData1.put("listSize", "2");
Map<String, Object> groupData2 = new HashMap<>();
groupData2.put("typeName", "路飞");
groupData2.put("listSize", "2");
groupDataList.add(groupData1);
groupDataList.add(groupData2);
serverTableData.setServerDataList(serverDataList);
serverTableData.setGroupDataList(groupDataList);
serverTableData.setMergeColumn(0);
return serverTableData;
}
4.效果图
文章浏览阅读2.6k次,点赞7次,收藏9次。关于java集合框架的理解_java框架理解思路
文章浏览阅读633次,点赞9次,收藏6次。是 JavaScript 数组的一个内置方法,用。于查找数组中满足指定条件的元素的索引。第一个满足条件的元素的索引。_封装函数,查找元素在数组中的索引。 如果找到该元素(第一个元素即可),则返回该元
文章浏览阅读139次。当我们通过yarn向集群提交自定义任务时,若传入队列参数会报错此时我们需要Tool工具,进行动态传参控制。_yarn脚本后缀传值
文章浏览阅读4.3k次。最近在折腾面试,然后就有些组件的原理需要看源码,之前也弄过,都是用的Source Insight在win10里面搞的,今天试了一下Mac,源码60G,我这个版本是Android 11,目前是最新版的代码。用AS看源码首先要构建项目配置文件,就是ipr和iml工具,用到Android源码里面的idegen工具,但是你用官方的mmm命令方法会报错,说文件格式不对,本来是可以用mmm命令直接生成的,但是Mac的文件系统跟官方要求的有问题,所以就放弃了,有想试试的同学可以执行下面的命令。cd ~/aosp_win查看安卓源码
文章浏览阅读1k次。在编程中,我们常常需要对给定的URL地址进行合法性检测。函数来判断URL地址是否合法。该函数接受一个URL地址作为参数,并返回一个布尔值,表示该URL地址是否合法。总之,在Python中使用正则表达式判断URL地址是否合法非常简单,只需要编写一个正则表达式,然后使用。在函数内部,我们使用正则表达式来匹配URL地址的各个部分,并将它们拼接在一起。函数来判断该URL地址是否符合我们定义的正则表达式规则。匹配URL地址的路径部分。在示例代码中,我们将一个合法的URL地址。使用正则表达式判断URL地址是否合法。_判断是否是正常链接正则表达式
文章浏览阅读260次。古诗词是中国文化的重要组成部分,具有悠久的历史和独特的艺术价值。但是,由于其语言难度较高、时代背景和文化背景的复杂性等原因,使得普通读者很难深入理解古诗词的内涵和价值。此时,基于知识图谱的古诗词问答系统应运而生,其背景意义包括以下几个方面:一、促进文化传承古诗词是中华文化的瑰宝,它们承载了丰富的历史文化信息。而传统的古诗词学习方式主要依靠课堂教学和自学,但这种学习方式往往难以满足大众的需求。基..._古诗问答系统
文章浏览阅读857次。iOS中的URL Scheme 在iOS的SDK中提供了一个非常有意思的功能,它能将iOS的Application同自定义的URL Schema绑定,同时可以通过URL Scheme在浏览器或者是其他应用中启动这个Application。本文主要介绍如何通过URL Scheme的方式启动应用和参数的传递。 对应的源码配置文件为:CFBundleURLTypes_苹果计算器url scheme
文章浏览阅读335次。下载好Demo解压。1.是按照aar的集成方式。1.1导入aar包到libs里面1.2 add的gradle添加defaultConfig{ndk{abiFilters 'armeabi-v7a', 'armeabi'}}dependencies添加:/腾讯直播compile(name: 'LiteAVSDK_Professional_5.3.6004', ext: 'aar')1.3项目的gra..._android 腾讯直播达到试用版限制liteavsdk_trtc dau reaches the trial edition l
文章浏览阅读670次。一: 官网介绍地址:http://www.nordicsemi.com/Products/Bluetooth-low-energy/nRF5-SDK-for-MeshNordic offers a complete solution for the Bluetooth mesh specification with the nRF5 SDK for Mesh and the nRF..._nrf mesh下载
文章浏览阅读1.7k次。linux - rpm _rpm命令
文章浏览阅读26次。用户可以在首页访问小学生课外知识学习网站方面信息,首页上面有导航栏,导航栏上面有课外知识,试卷列表,学习资讯,个人中心,后台管理等,点击导航栏课外知识可以看到很多信息,点击某个信息可以查看对应详情信息,用户登录后可以对小学生课外知识学习网站文章信息进行评论操作。管理员可以对课外知识进行添加,查询,修改,删除操作。下图就是课外知识管理页面。管理员可以对知识分类信息进行删除,查询和修改操作。前端技术:JavaScript、VUE.js(2.X)、css3。数据库管理工具:Navicat或sqlyog。
文章浏览阅读1.7w次,点赞3次,收藏9次。转自:https://www.cnblogs.com/zhishuai/p/7942273.htmljetbrains goland 跳到上一个光标处查了下是 :Ctrl + Alt + 左右 mac下面是:Command+ Alt + 左右键但是我用下来是切上面打开文档页摸索了下是:Ctrl +Win+ Alt + 左右 我的键的映射是Default 。另外jb确实也..._goland进入ctrl+b函数后,什么快捷键返回上一个函数