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>

模版

在这里插入图片描述

代码

@Test
    public 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;
    }

    @Override
    public 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 row
                    XmlCursor newCursor = templateRow.getCtRow().newCursor();
                    newCursor.toPrevSibling();
                    XmlObject object = newCursor.getObject();
                    nextRow = new XWPFTableRow((CTRow) object, table);
                    if (!firstFlag) {
                        // update VMerge cells for non-first row
                        List<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);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/934313.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

记一次:使用C#创建一个串口工具

前言&#xff1a;公司的上位机打不开串口&#xff0c;发送的时候设备总是关机&#xff0c;因为和这个同事关系比较好&#xff0c;编写这款软件是用C#编写的&#xff0c;于是乎帮着解决了一下&#xff08;是真解决了&#xff09;&#xff0c;然后整理了一下自己的笔记 一、开发…

LLama系列模型简要概述

LLama-1&#xff08;7B, 13B, 33B, 65B参数量&#xff1b;1.4T tokens训练数据量&#xff09; 要做真正Open的AI Efficient&#xff1a;同等预算下&#xff0c;增大训练数据&#xff0c;比增大模型参数量&#xff0c;效果要更好 训练数据&#xff1a; 书、Wiki这种量少、质量高…

【OpenCV】模板匹配

理论 模板匹配是一种在较大图像中搜索和查找模板图像位置的方法。为此&#xff0c;OpenCV 带有一个函数 cv.matchTemplate&#xff08;&#xff09; 。它只是在输入图像上滑动模板图像&#xff08;如在 2D 卷积中&#xff09;&#xff0c;并比较模板图像下的模板和输入图像的补…

从 Zuul 迁移到 Spring Cloud Gateway:一步步实现服务网关的升级

从 Zuul 迁移到 Spring Cloud Gateway&#xff1a;一步步实现服务网关的升级 迁移前的准备工作迁移步骤详解第一步&#xff1a;查看源码第二步&#xff1a;启动类迁移第三步&#xff1a;引入 Gateway 依赖第四步 编写bootstrap.yaml第五步&#xff1a;替换路由配置第六步&#…

网站中的QQ在线客服接入

1. 开通QQ通讯组件 QQ通讯组件官网&#xff1a;https://shang.qq.com 默认未开通通讯组件&#xff0c;登陆上QQ之后会提示开通&#xff0c;点击开通即可 2. 唤起QQ临时会话&#xff08;对方不是自己的QQ好友也能唤起&#xff09; 复制链接地址 http://wpa.qq.com/msgrd?v3&…

赋能加速AI应用交付,F5 BIG-IP Next for Kubernetes方案解读

随着AI工作负载的爆炸式增长&#xff0c;服务提供商和企业需要加速计算&#xff0c;以安全高效地在大规模云上交付高性能的AI应用。前段时间&#xff0c;F5公司宣布推出一项全新的创新AI应用交付和应用安全解决方案&#xff0c;即BIG-IP Next for Kubernetes。那么该方案有何性…

域内DNS信息收集

目录 一、查询域内DNS记录 1. 使用 PowerView.ps1 2. 使用 adidnsdump 二、添加域内DNS记录 1. 使用 Invoke-DNSUpdate.ps1 在默认情况下,域内所有用户 都有权限读取 Active Directory 数据库中的 DNS 信息,包括所有记录。这是因为: DNS 记录被视为公共信息,用于解析域…

Odoo :一款免费且开源的食品生鲜领域ERP管理系统

文 / 贝思纳斯 Odoo金牌合作伙伴 引言 提供业财人资税的精益化管理&#xff0c;实现研产供销的融通、食品安全的追踪与溯源&#xff0c;达成渠道的扁平化以及直面消费者的 D2C 等数字化解决方案&#xff0c;以此提升运营效率与核心竞争力&#xff0c;支撑高质量的变速扩张。…

在星闪W63/W63E开发板上运行第一个OpenHarmony程序

目录 引言 demolink示例 程序修改 修改任务堆栈的大小 修改示例程序的build.gn 修改App的build.gn 修改ohos.cmake 修改config.py 编译程序 烧写程序 程序运行 结语 引言 在前面的博文星闪WS63E开发板的OpenHarmony环境构建-CSDN博客中介绍了如何构建W63E开发板的…

Spring——@Autowired和@Configuration注解区别

摘要 本文主要介绍了Spring框架中Autowired和Configuration注解的区别。Autowired用于自动注入依赖&#xff0c;支持属性、构造器和方法注入。Configuration则用于定义配置类&#xff0c;允许在类中使用Bean注解声明Bean。文章详细解释了这两个注解的作用、使用场景和核心特性…

机器学习--张量

机器学习–张量 机器学习的数据结构–张量 张量是机器学习程序中的数字容器&#xff0c;本质上就是各种不同维度的数组&#xff0c;如下图所示。 张量的维度称为轴&#xff08;axis&#xff09;&#xff0c;轴的个数称为阶&#xff08;rank&#xff09; 标量–0D张量 impor…

标记数据集生成模型助力无数据情况下的大模型指令微调

在构建大模型应用时&#xff0c;通常有两种方式来改进效果&#xff0c;一种是构建外部知识库&#xff0c;利用RAG来完成。但RAG并不是万能的&#xff0c;对于特定领域的LLM应用&#xff0c;以及无需示例&#xff0c;就能完成特定任务等场合就需要进行微调。然而&#xff0c;微调…

nvm安装指定版本显示不存在及nvm ls-remote 列表只出现 iojs 而没有 node.js 解决办法

在使用 nvm install 18.20.3 安装 node 时会发现一直显示不存在此版本 Version 18.20.3 not found - try nvm ls-remote to browse available versions.使用 nvm ls-remote 查看可安装列表时发现&#xff0c;列表中只有 iojs 解决方法&#xff1a; 可以使用以下命令查看可安装…

5.ABAP结构体和内表

总学习目录请点击下面连接 SAP ABAP开发从0到入职&#xff0c;冷冬备战-CSDN博客 目录 5.1.结构化数据对象 定义 如何引用结构化的数据对象 拷贝 实战练习 创建 拷贝 调试代码 5.2.内表 行类型 键 表种类 存取类型 表类型 如何在本地定义表类型 内表三种可能的…

C#搭建WebApi服务

1&#xff0c;OWIN的介绍 OWIN 的全称是 “Open Web Interface for .NET”&#xff0c; OWIN 在 .NET Web 服务器和 .NET Web 应用之间定义了一套标准的接口&#xff0c; 其目的是为了实现服务器与应用之间的解耦&#xff0c;使得便携式 .NET Web 应用以及跨平台的愿望成为现实…

unity 2D像素种田游戏学习记录(自用)

一、透明度排序轴 改变sprite的排序方式&#xff0c;默认按照z轴进行排序&#xff08;离摄像机的远近&#xff09;。可以将其改变成y轴的排序方式&#xff0c;这样可以使2D人物走在草丛的下方就不被遮挡&#xff0c;走在草丛上方就被遮挡&#xff0c;如下图。 在项目设置-图形…

K8S服务突然中断无法访问:报The node had condition: [DiskPressure]异常

一、背景 程序在运行过程中&#xff0c;突然无法访问&#xff0c;发现后台接口也无法访问&#xff1b;查看kuboard&#xff0c;发现报如下异常&#xff1a;The node had condition: [DiskPressure]. 继续查看磁盘使用率&#xff0c;发现系统盘使用率已经高达93%。问题前后呼应…

VLDB 2024 | 时空数据(Spatial-temporal)论文总结

VLDB 2024于2024年8月26号-8月30号在中国广州举行。 本文总结了VLDB 2024有关时空数据&#xff08;time series data&#xff09;的相关论文&#xff0c;主要包含如有疏漏&#xff0c;欢迎大家补充。 &#x1f31f;【紧跟前沿】“时空探索之旅”与你一起探索时空奥秘&#xf…

GTC2024 回顾 | 优阅达携手 HubSpot 亮相上海,赋能企业数字营销与全球业务增长

从初创企业入门到成长型企业拓展&#xff0c;再到 AI 驱动智能化运营&#xff0c;HubSpot 为企业的每步成长提供了全方位支持。 2024 年 11 月下旬&#xff0c;备受瞩目的 GTC2024 全球流量大会&#xff08;上海&#xff09;成功举办。本次大会汇聚了全国内多家跨境出海领域企业…

如何使用go语言的gin库来搭建一个属于自己的网页版GPT

我们将会使用go语言的gin库来搭建一个属于自己的网页版GPT 一、准备工作 我们需要使用到ollama&#xff0c;如何下载和使用[ollama](Ollama完整教程&#xff1a;本地LLM管理、WebUI对话、Python/Java客户端API应用 - 老牛啊 - 博客园)请看这个文档 有过gin环境的直接运行就可…