word poi-tl 表格功能增强,实现表格功能垂直合并
目录
- 问题
- 解决问题
- poi-tl介绍
- 功能实现
- 引入依赖
- 模版
- 代码
- 效果图
- 附加(插件实现)
- MergeColumnData 对象
- MergeGroupData 类
- ServerMergeTableData 数据信息
- ServerMergeTablePolicy 合并插件
问题
由于在开发功能需求中,word文档需要垂直合并表格,而word模版引擎原有的插件功能只能做行循环,不满足需求;
解决问题
- 目前选择的poi-tl的模版引擎,在原有的基础上新增自定义插件来实现功能
poi-tl介绍
poi-tl 是一个基于Apache POI的Word模板引擎,也是一个免费开源的Java类库,你可以非常方便的加入到你的项目中;
| 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,也可以在指定位置进行合并 |
| 用户自定义函数(插件) | 插件化设计,在文档任何位置执行函数 |
功能实现
引入依赖
<dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.12.2</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml-full</artifactId><version>5.2.5</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.5</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.5</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.25</version></dependency><!-- spring el表达式 --><dependency><groupId>org.springframework</groupId><artifactId>spring-expression</artifactId><version>5.3.18</version></dependency>
模版

代码
@Testpublic void test() throws Exception {Configure config = Configure.builder().bind("precipitationInfoList", new ServerMergeTablePolicy()).useSpringEL(false).build();Map<String, Object> dataMap = new HashMap<String, Object>();dataMap.put("precipitationInfoList", getServerMergeTableData());ClassPathResource classPathResource = new ClassPathResource("static/word/template.docx");try (InputStream resourceInputStream = classPathResource.getInputStream();XWPFTemplate template = XWPFTemplate.compile(resourceInputStream, config);) {template.render(dataMap);template.writeAndClose(new FileOutputStream("output.docx"));} catch (Exception e) {e.printStackTrace();}}private ServerMergeTableData getServerMergeTableData() {List<Map<String,Object>> serverDataList = new ArrayList<>();Map<String,Object> serverData1 = new HashMap<>();serverData1.put("province","广东省");serverData1.put("city","深圳市");serverData1.put("precipitation","0.3");serverDataList.add(serverData1);Map<String,Object> serverData2 = new HashMap<>();serverData2.put("province","广东省");serverData2.put("city","广州市");serverData2.put("precipitation","5.1");serverDataList.add(serverData2);Map<String,Object> serverData3 = new HashMap<>();serverData3.put("province","广东省");serverData3.put("city","东莞市");serverData3.put("precipitation","10");serverDataList.add(serverData3);Map<String,Object> serverData4 = new HashMap<>();serverData4.put("province","湖南");serverData4.put("city","长沙");serverData4.put("precipitation","10");serverDataList.add(serverData4);Map<String,Object> serverData6 = new HashMap<>();serverData6.put("province","湖南");serverData6.put("city","湘潭");serverData6.put("precipitation","4.5");serverDataList.add(serverData6);List<MergeGroupData> groupDataList = new ArrayList<>();groupDataList.add(MergeGroupData.builder().indexList(Arrays.asList("0","1","2")).build());groupDataList.add(MergeGroupData.builder().indexList(Arrays.asList("3","4")).build());List<MergeColumnData> mergeColumns = new ArrayList<>();mergeColumns.add(MergeColumnData.builder().groupDataList(groupDataList).mergeColumn(0).build());ServerMergeTableData serverMergeTableData = new ServerMergeTableData();serverMergeTableData.setMergeColumns(mergeColumns);serverMergeTableData.setServerDataList(serverDataList);return serverMergeTableData;}
效果图

附加(插件实现)
MergeColumnData 对象
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.List;@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MergeColumnData {/*** 携带要分组的信息*/private List<MergeGroupData> groupDataList;/*** 需要合并的列,从0开始*/private Integer mergeColumn;
}
MergeGroupData 类
/*** 合并对象信息*/
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MergeGroupData {/*** 名称*/private List<String> indexList;public Integer getListSize() {if (Objects.isNull(indexList)) {return 0;}return indexList.size();}
}
ServerMergeTableData 数据信息
import lombok.Data;import java.util.List;@Data
public class ServerMergeTableData {private List<?> serverDataList;/*** 列合并信息*/private List<MergeColumnData> mergeColumns;
}
ServerMergeTablePolicy 合并插件
import cn.hutool.core.collection.CollUtil;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.exception.RenderException;
import com.deepoove.poi.policy.AbstractRenderPolicy;
import com.deepoove.poi.render.RenderContext;
import com.deepoove.poi.render.compute.EnvModel;
import com.deepoove.poi.render.compute.RenderDataCompute;
import com.deepoove.poi.render.processor.DocumentProcessor;
import com.deepoove.poi.render.processor.EnvIterator;
import com.deepoove.poi.resolver.TemplateResolver;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.MetaTemplate;
import com.deepoove.poi.template.run.RunTemplate;
import com.deepoove.poi.util.ReflectionUtils;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;import java.util.Iterator;
import java.util.List;public class ServerMergeTablePolicy extends AbstractRenderPolicy<ServerMergeTableData> {private String prefix;private String suffix;private boolean onSameLine;public ServerMergeTablePolicy() {this(false);}public ServerMergeTablePolicy(boolean onSameLine) {this("[", "]", onSameLine);}public ServerMergeTablePolicy(String prefix, String suffix) {this(prefix, suffix, false);}public ServerMergeTablePolicy(String prefix, String suffix, boolean onSameLine) {this.prefix = prefix;this.suffix = suffix;this.onSameLine = onSameLine;}@Overridepublic void doRender(RenderContext<ServerMergeTableData> context) throws Exception {XWPFTemplate template = context.getTemplate();ServerMergeTableData mergeTableData = context.getData();ElementTemplate eleTemplate = context.getEleTemplate();this.renderTo(eleTemplate, mergeTableData, template);}public void renderTo(ElementTemplate eleTemplate, Object tableData, XWPFTemplate template) {RunTemplate runTemplate = (RunTemplate) eleTemplate;XWPFRun run = runTemplate.getRun();try {XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();XWPFTable table = tagCell.getTableRow().getTable();run.setText("", 0);if (null == tableData) {return;}ServerMergeTableData serverTableData = (ServerMergeTableData) tableData;List data = serverTableData.getServerDataList();if (!TableTools.isInsideTable(run)) {throw new IllegalStateException("The template tag " + runTemplate.getSource() + " must be inside a table");}int templateRowIndex = getTemplateRowIndex(tagCell);int tempStartRow = templateRowIndex;if (null != data && data instanceof Iterable) {Iterator<?> iterator = ((Iterable<?>) data).iterator();XWPFTableRow templateRow = table.getRow(templateRowIndex);int insertPosition = templateRowIndex;TemplateResolver resolver = new TemplateResolver(template.getConfig().copy(prefix, suffix));boolean firstFlag = true;int index = 0;boolean hasNext = iterator.hasNext();while (hasNext) {Object root = iterator.next();hasNext = iterator.hasNext();insertPosition = templateRowIndex++;XWPFTableRow nextRow = table.insertNewTableRow(insertPosition);setTableRow(table, templateRow, insertPosition);// double set rowXmlCursor newCursor = templateRow.getCtRow().newCursor();newCursor.toPrevSibling();XmlObject object = newCursor.getObject();nextRow = new XWPFTableRow((CTRow) object, table);if (!firstFlag) {// update VMerge cells for non-first rowList<XWPFTableCell> tableCells = nextRow.getTableCells();for (XWPFTableCell cell : tableCells) {CTTcPr tcPr = TableTools.getTcPr(cell);CTVMerge vMerge = tcPr.getVMerge();if (null == vMerge) continue;if (STMerge.RESTART == vMerge.getVal()) {vMerge.setVal(STMerge.CONTINUE);}}} else {firstFlag = false;}setTableRow(table, nextRow, insertPosition);RenderDataCompute dataCompute = template.getConfig().getRenderDataComputeFactory().newCompute(EnvModel.of(root, EnvIterator.makeEnv(index++, hasNext)));List<XWPFTableCell> cells = nextRow.getTableCells();cells.forEach(cell -> {List<MetaTemplate> templates = resolver.resolveBodyElements(cell.getBodyElements());new DocumentProcessor(template, resolver, dataCompute).process(templates);});}}table.removeRow(templateRowIndex);afterloop(table, data);//合并表格信息mergeTable(serverTableData, tempStartRow, table);} catch (Exception e) {throw new RenderException("HackLoopTable for " + eleTemplate + " error: " + e.getMessage(), e);}}private void mergeTable(ServerMergeTableData serverMergeTableData, int startRow, XWPFTable xwpfTable) {List serverDataList = serverMergeTableData.getServerDataList();List<MergeColumnData> mergeColumns = serverMergeTableData.getMergeColumns();if (CollUtil.isNotEmpty(mergeColumns)) {for (int i = 0; i < serverDataList.size(); i++) {for (MergeColumnData mergeColumnData : mergeColumns) {Integer mergeColumn = mergeColumnData.getMergeColumn();List<MergeGroupData> groupDataList = mergeColumnData.getGroupDataList();for (int j = 0; j < groupDataList.size(); j++) {MergeGroupData mergeGroupData = groupDataList.get(j);List<String> indexList = mergeGroupData.getIndexList();int listSize = mergeGroupData.getListSize();if (listSize == 1) {continue;}// 若匹配上 就直接合并if (indexList.contains(i + "")) {int col = i + startRow;int fromRow = i + (startRow - 1) + listSize;TableTools.mergeCellsVertically(xwpfTable, mergeColumn, col, fromRow);groupDataList.remove(j);break;}}}}}}private int getTemplateRowIndex(XWPFTableCell tagCell) {XWPFTableRow tagRow = tagCell.getTableRow();return onSameLine ? getRowIndex(tagRow) : (getRowIndex(tagRow) + 1);}protected void afterloop(XWPFTable table, Object data) {}@SuppressWarnings("unchecked")private void setTableRow(XWPFTable table, XWPFTableRow templateRow, int pos) {List<XWPFTableRow> rows = (List<XWPFTableRow>) ReflectionUtils.getValue("tableRows", table);rows.set(pos, templateRow);table.getCTTbl().setTrArray(pos, templateRow.getCtRow());}private int getRowIndex(XWPFTableRow row) {List<XWPFTableRow> rows = row.getTable().getRows();return rows.indexOf(row);}
}
相关文章:
word poi-tl 表格功能增强,实现表格功能垂直合并
目录 问题解决问题poi-tl介绍 功能实现引入依赖模版代码效果图 附加(插件实现)MergeColumnData 对象MergeGroupData 类ServerMergeTableData 数据信息ServerMergeTablePolicy 合并插件 问题 由于在开发功能需求中,word文档需要垂直合并表格&…...
LSTM-CNN-BP-RF-SVM五模型咖喱融合策略混合预测模型
目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 LSTM-CNN-BP-RF-SVM五模型咖喱融合策略混合预测模型 Matlab代码注释清晰。 程序设计 完整程序和数据获取方式:私信博主回复LSTM-CNN-BP-RF-SVM五模型咖喱融合策略混合预测模型(Matlab&#…...
《鸿蒙开发-答案之书》 怎么设置Json字段的别名
《鸿蒙开发-答案之书》 怎么设置Json字段的别名 Android设置别名用的是SerializedName(“msg”),那鸿蒙用的是啥,有点懵不知道。 鸿蒙得引入第三方库:ohpm install class-transformer 然后用Expose({ name: ‘first-name’ }) 示例代码&…...
ftp服务器搭建-安装、配置及验证
ftp服务器搭建-安装、配置及验证 #安装 sudo apt-get install vsftpd #配置文件 cat > /etc/vsftpd.conf << "EOF" listenNO listen_ipv6YES anonymous_enableNO local_enableYES write_enableYES dirmessage_enableYES use_localtimeYES xferlog_enable…...
鸿蒙应用获取wifi连接的ip地址(官方文档获取的格式转换成192.168.1.xxx格式)
目录 一.背景 二.官网流程 wifiManager.getLinkedInfo9+ 三.转换成192.168.xxx.xxx格式 一.背景 本次来学习如何获取到鸿蒙设备连接wifi后的ip地址,由于官网文档中获取的ip地址和我们平时看到的192:168:xxx:xxx有所不同,需要进行下转换,所以记录下,如下的流程是在OpenH…...
c++数据结构算法复习基础--11--高级排序算法-快速排序-归并排序-堆排序
高阶排序 1、快速排序 冒泡排序的升级算法 每次选择一个基准数,把小于基准数的放到基准数的左边,把大于基准数的放到基准数的右边,采用 “ 分治算法 ”处理剩余元素,直到整个序列变为有序序列。 最好和平均的复杂度:…...
人工智能学习路线详细规划
一、引言 在当今科技飞速发展的时代,人工智能已成为引领未来的关键技术之一。无论是为了追求职业发展的新机遇,还是出于对这一前沿领域的浓厚兴趣,深入学习人工智能都是一个极具价值的选择。本文将为大家精心规划一条人工智能学习路线&#…...
深度学习之视觉处理
CNN 视觉处理三大任务:分类、目标检测、图像分割上游:提取特征,CNN下游:分类、目标、分割等,具体的任务 概述 卷积神经网络是深度学习在计算机视觉领域的突破性成果。在计算机视觉领域, 往往我们输入的图像都很大&am…...
遇到问题:hive中的数据库和sparksql 操作的数据库不是同一个。
遇到的问题: 1、hive中的数据库和sparksql 操作的数据库不同步。 观察上面的数据库看是否同步 !!! 2、查询服务器中MySQL中hive的数据库,发现创建的位置没有在hdfs上,而是在本地。 这个错误产生的原因是&…...
Spring Boot与Spring Security集成:前后分离认证流程的优化实践
在当前的Web开发领域,前后分离架构已经成为一种流行趋势。这种架构将前端和后端进行解耦,前端负责用户界面和交互逻辑,后端则负责数据处理和业务逻辑。在前后分离的项目中,如何安全、高效地实现用户认证是一个关键问题。本文将深入…...
设计模式——Chain(责任链)设计模式
摘要 责任链设计模式是一种行为设计模式,通过链式调用将请求逐一传递给一系列处理器,直到某个处理器处理了请求或所有处理器都未能处理。它解耦了请求的发送者和接收者,允许动态地将请求处理职责分配给多个对象,支持请求的灵活传…...
HarmonyOS(63) ArkUI 自定义占位组件NodeContainer
NodeContainer 1、前言2、NodeContainer和NodeController3、示例代码3.1、创建@Builder3.2、 创建NodeController3.3、 使用NodeCtroller4、NodeContainer的作用5、FrameNode简介6、BuilderNode简介7、参考资料1、前言 在HarmonyOS(62) ArkUI @Reusable组件复用原理讲了组件复…...
Python深度强化学习对冲策略:衍生品投资组合套期保值Black-Scholes、Heston模型分析...
全文链接:https://tecdat.cn/?p38463 本文提出了一个在存在交易成本、市场冲击、流动性约束或风险限制等市场摩擦的情况下,使用现代深度强化学习方法对衍生品投资组合进行套期保值的框架。我们讨论了标准强化学习方法如何应用于非线性奖励结构ÿ…...
【opencv入门教程】2. Point()类用法
文章选自: void Samples::PointFunc() {//输入二维点Point2f point2f(6, 2);cout << "【2维点】p " << point2f << ";\n" << endl;// 输入三维点Point3f point3f(8, 2, 0);cout << "【3维点】p3f "…...
前端导出excel实战(xlsx库和exceljs库)
一. 概览 前端导出excel是比较常见的需求,比如下载excel模板和批量导出excel。目前比较常用的库有xlsx和excel,接下来就着两种方式进行梳理。 二. 下载模板 xlsx库实现: 示例核心代码如下: const excelColumn {details: {ma…...
【附源码】基于环信鸿蒙IM SDK实现一个聊天Demo
项目背景 本项目基于环信IM 鸿蒙SDK 打造的鸿蒙IM Demo,完全适配HarmonyOS NEXT系统,实现了发送消息,添加好友等基础功能。代码开源,功能简洁,如果您有类似开发需求可以参考。 源码地址:https://github.c…...
Python库常用函数-数据分析
Python库常用函数 1.pandas库 (1)数据读取与写入 读取 CSV 文件: data pd.read_csv(file.csv)读取 Excel 文件: data pd.read_excel(file.xlsx, sheet_nameSheet1)写入 CSV 文件: data.to_csv(new_file.csv, ind…...
汽车EEA架构:架构的简介
1.架构的定义 汽车领域谈论的架构一词,来源于英文单词Architecture。在《系统架构:复杂系统的产品设计与开发》一书中对架构的定义如下:系统架构是一种概念的具象化,是物理或信息功能到形式元素的分配,是系统之内的元素之间的关系与周边环境…...
渗透测试--数据库攻击
这篇文章瘾小生其实想了很久,到底是放在何处,最终还是想着单拎出来总结,因为数据库攻击对我们而言非常重要,而且内容众多。本篇文章将讲述在各位获取数据库权限的情况下,各个数据库会被如何滥用,以及能够滥…...
反向路径转发(RPF)
本文介绍了反向路径转发(RPF)是如何在FortiGate上实现的。 它还解释了特定于VDOM的CLI设置“config system settings -> set strict-src-check”如何修改RPF行为。 测试场景中使用了以下设置 反向路径过滤器(又名RPF)是一种安…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
