poi-tl的详细教程(动态表格、单元格合并)

news2025/1/22 18:01:08

前提了解poi-tl

链接: springboot整合poi-tl

创建word模板

在这里插入图片描述

实现效果

在这里插入图片描述

代码实现

ServerTableData

import com.deepoove.poi.data.RowRenderData;

import java.util.List;

public class ServerTableData {

    /**
     * 携带表格中真实数据
     */
    private List<RowRenderData> serverDataList;

    public List<RowRenderData> getServerDataList() {
        return serverDataList;
    }

    public void setServerDataList(List<RowRenderData> serverDataList) {
        this.serverDataList = serverDataList;
    }

}

MyPoiUtil

import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBorder;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblBorders;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;

public class MyPoiUtil {

    /**
     * 设置表格边框样式为黑色实线
     */
    public static void setTableBorder(XWPFTable table) {
        CTTblPr tblPr = table.getCTTbl().getTblPr();
        CTTblBorders borders = tblPr.isSetTblBorders() ? tblPr.getTblBorders() : tblPr.addNewTblBorders();

        CTBorder hBorder = borders.addNewInsideH();
        hBorder.setVal(STBorder.SINGLE);  // 线条类型
        hBorder.setColor("000000"); // 设置颜色

        CTBorder vBorder = borders.addNewInsideV();
        vBorder.setVal(STBorder.SINGLE);
        vBorder.setColor("000000");

        CTBorder lBorder = borders.addNewLeft();
        lBorder.setVal(STBorder.SINGLE);
        lBorder.setColor("000000");

        CTBorder rBorder = borders.addNewRight();
        rBorder.setVal(STBorder.SINGLE);
        rBorder.setColor("000000");

        CTBorder tBorder = borders.addNewTop();
        tBorder.setVal(STBorder.SINGLE);
        tBorder.setColor("000000");

        CTBorder bBorder = borders.addNewBottom();
        bBorder.setVal(STBorder.SINGLE);
        bBorder.setColor("000000");
    }


}

ConsecutiveDoublesUtil

import java.util.*;

/**
 * 重复且连续的角标集合
 *
 * @author zhou
 */
public class ConsecutiveDoublesUtil {

    public static Map<String, List<List<Integer>>> getConsecutiveDoubles(List<String> strings) {
        Map<String, List<List<Integer>>> consecutiveDoublesMap = new HashMap<>();
        // Start index of a potential consecutive sequence
        int startIndex = -1;
        // Last seen string
        String lastStr = null;
        for (int i = 0; i < strings.size(); i++) {
            String str = strings.get(i);
            if (str.equals(lastStr)) {
                // If the current string is the same as the last seen one and we're in a sequence,
                // continue the sequence.
                if (startIndex >= 0) {
                    // Ensure we have a start index for the sequence.
                    consecutiveDoublesMap.computeIfAbsent(lastStr, k -> new ArrayList<>()).get(consecutiveDoublesMap.get(lastStr).size() - 1).add(i);
                } else {
                    // Start a new sequence.
                    startIndex = i - 1;
                    consecutiveDoublesMap.computeIfAbsent(lastStr, k -> new ArrayList<>()).add(new ArrayList<>(Arrays.asList(startIndex, i)));
                }
            } else {
                // If we reach the end of a sequence or a new string is found, check if we had a valid sequence.
                if (startIndex >= 0 && i - startIndex > 1) {
                    // We had a sequence of length at least 2, so it's valid.
                } else if (startIndex >= 0) {
                    // Sequence was too short, remove it.
                    consecutiveDoublesMap.get(lastStr).remove(consecutiveDoublesMap.get(lastStr).size() - 1);
                }
                // Reset for the next potential sequence.
                startIndex = -1;
                lastStr = str;
            }
        }
        // Remove entries with no valid sequences
        consecutiveDoublesMap.values().removeIf(List::isEmpty);
        return consecutiveDoublesMap;
    }


}

ServerTablePolicy

import com.chinadci.framework.bean.ServerTableData;
import com.chinadci.fzjc.utils.ConsecutiveDoublesUtil;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import org.apache.commons.collections4.CollectionUtils;
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 java.util.*;

/**
 * 动态合并单元格
 */
public class ServerTablePolicy extends DynamicTableRenderPolicy {

    /**
     * 填充数据所在行数
     */
    private final int laborsStartRow;

    /**
     * 表格的列数
     */
    private final int headerColumnIndex;

    /**
     * 合并的列
     */
    private List<Integer> colArr;

    /**
     * 表格行高
     */
    private int height = 250;

    public ServerTablePolicy(int laborsStartRow, int headerColumnIndex) {
        this.laborsStartRow = laborsStartRow;
        this.headerColumnIndex = headerColumnIndex;
    }

    public void setColArr(List<Integer> colArr) {
        this.colArr = colArr;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    @Override
    public void render(XWPFTable xwpfTable, Object tableData) throws Exception {
        if (null == tableData) {
            return;
        }
        ServerTableData serverTableData = (ServerTableData) tableData;
        List<RowRenderData> serverDataList = serverTableData.getServerDataList();
        if (CollectionUtils.isNotEmpty(serverDataList)) {
            // 先删除一行, demo中第一行是为了调整 三线表 样式
            xwpfTable.removeRow(laborsStartRow);
            // 行从中间插入, 因此采用倒序渲染数据
            for (int i = serverDataList.size() - 1; i >= 0; i--) {
                // 从表单的哪行开始插入数据,一般表单有一个标题 循环新增行 这样就会把数据往下层压
                XWPFTableRow newRow = xwpfTable.insertNewTableRow(laborsStartRow);
                newRow.setHeight(height);
                for (int j = 0; j < headerColumnIndex; j++) {
                    newRow.createCell();
                }
                int fromCol = 0;
                int toCol = 1;
                String typeNameData = serverDataList.get(i).getCells().get(fromCol).getParagraphs().get(0).getContents().get(0).toString();
                String typeNameData1 = serverDataList.get(i).getCells().get(toCol).getParagraphs().get(0).getContents().get(0).toString();
                // 渲染一行数据
                TableRenderPolicy.Helper.renderRow(newRow, serverDataList.get(i));
                boolean equals = typeNameData.equals(typeNameData1);
                // 合并水平单元格
                if (equals) {
                    // TableTools.mergeCellsHorizonal(table, 0, 0, 3) 合并第0行的第0列到第3列单元格
                    TableTools.mergeCellsHorizonal(xwpfTable, laborsStartRow, fromCol, toCol);
                    // 加粗
                    // 遍历行中的所有单元格
                    for (int k = 0; k < newRow.getTableCells().size(); k++) {
                        XWPFTableCell tableCell = newRow.getTableCells().get(k);
                        List<XWPFRun> runs = tableCell.getParagraphArray(0).getRuns();
                        for (XWPFRun run1 : runs) {
                            run1.setBold(true);
                        }
                    }
                }
            }
            for (Integer integer : colArr) {
                List<String> strings = new ArrayList<>();
                // 处理行合并
                for (int i = 0; i < serverDataList.size(); i++) {
                    String typeNameData = serverDataList.get(i).getCells().get(integer).getParagraphs().get(0).getContents().get(0).toString();
                    strings.add(typeNameData);
                }
                Map<String, List<List<Integer>>> result = ConsecutiveDoublesUtil.getConsecutiveDoubles(strings);
                for (Map.Entry<String, List<List<Integer>>> entry : result.entrySet()) {
                    List<List<Integer>> value = entry.getValue();
                    for (List<Integer> integers : value) {
                        // TableTools.mergeCellsVertically(table, 0, 0, 3) 合并第0列的第0行到第3行的单元格
                        TableTools.mergeCellsVertically(xwpfTable, integer, integers.get(0) + laborsStartRow, integers.get(integers.size() - 1) + laborsStartRow);
                    }
                }
            }
        }

    }


}

main


public class Test{
 private static ServerTableData getServerTableData() {
        ServerTableData serverTableData = new ServerTableData();
        List<RowRenderData> serverDataList = Arrays.asList(
                Rows.of("天河", "天河", "0.3").textFontSize(14).center().create(),
                Rows.of("广州", "白云", "0.2").textFontSize(14).center().create(),
                Rows.of("天河", "天河", "0.3").textFontSize(14).center().create(),
                Rows.of("广州", "东山1", "0.1").textFontSize(14).center().create(),
                Rows.of("广州", "东山2", "0.2").textFontSize(14).center().create(),
                Rows.of("广州", "东山2", "0.2").textFontSize(14).center().create(),
                Rows.of("佛山", "禅城", "0.3").textFontSize(14).center().create(),
                Rows.of("深圳", "南山", "0.3").textFontSize(14).center().create(),
                Rows.of("深圳", "福田", "0.3").textFontSize(14).center().create()
        );
        serverTableData.setServerDataList(serverDataList);
        return serverTableData;
    }

    public static void main(String[] args) {

        Map<String,Object> map = new HashMap<>();
        // 伪造一个表格数据
        ServerTableData oneTable = getServerTableData();
        map.put("oneTable", oneTable);
        map.put("title", "2023");

        ServerTablePolicy serverTablePolicy = new ServerTablePolicy(2, 3);
        // 需要合并的列
        serverTablePolicy.setColArr(Arrays.asList(0,1));
        serverTablePolicy.setHeight(230);
        // 配置策略
        ConfigureBuilder builder = Configure.builder()
                .useSpringEL(false)
                .bind("oneTable", serverTablePolicy);

        // 获取模板文件流
        String inputFilePath = "C:/Users/zhou/Desktop/1.docx";
        String outFilePath = "C:/Users/zhou/Desktop/2.docx";
        try(InputStream resourceAsStream = new FileInputStream(new File(inputFilePath));
            // HttpServletResponse response
            OutputStream out = new FileOutputStream(new File(outFilePath));
            BufferedOutputStream bos = new BufferedOutputStream(out);
            XWPFTemplate template = XWPFTemplate.compile(Objects.requireNonNull(resourceAsStream), builder.build()).render(map);) {

            // 获取第一个表格
            XWPFTable table = template.getXWPFDocument().getTableArray(0);

            // 设置表格边框样式为黑色实线
            MyPoiUtil.setTableBorder(table);

            // 这里删除的是原先空白的第一行
            table.removeRow(1);

            template.write(bos);
            bos.flush();
            out.flush();
            PoitlIOUtils.closeQuietlyMulti(template, bos, out);

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

   

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python常用模块】_PyMySQL模块详解

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)教程合集 👈👈…

window.open()地址栏隐藏问题解决方案

问题 使用window.open打开一个页面时&#xff0c;想要隐藏地址栏&#xff0c;但是无效。 window.open (test.html,newwindow,height100,width400,top0,left0,toolbarno,menubarno,scrollbarsno, resizableno,locationno, statusno)由于浏览器区别和安全问题&#xff0c;浏览器…

最新动态一致的文生视频大模型FancyVideo部署

FancyVideo是一个由360AI团队和中山大学联合开发并开源的视频生成模型。 FancyVideo的创新之处在于它能够实现帧特定的文本指导&#xff0c;使得生成的视频既动态又具有一致性。 FancyVideo模型通过精心设计的跨帧文本引导模块&#xff08;Cross-frame Textual Guidance Modu…

数据结构里的栈和队列

栈的笑话 程序员A对程序员B说&#xff1a;“你知道为什么程序员喜欢栈吗&#xff1f;” 程序员B疑惑地问&#xff1a;“为什么&#xff1f;” 程序员A回答&#xff1a;“因为它没有bug&#xff08;bug的谐音在英语中也是‘虫子’的意思&#xff0c;而栈的英文是stack&#x…

Elasticsearch:一次生产集群 ES Watcher 失效的深度排查与分析 - 全过程剖析与解决方案

作者&#xff1a;尚雷&#xff0c;TechTalk 技术交流社区创办者 一次生产集群 ES Watcher 失效的深度排查与分析 全过程剖析与解决方案​​ 一、Elasticsearch Watcher 介绍 1.1 Watcher 概念概述 Watcher 是 Elasticsearch 提供的一项监控和告警服务&#xff0c;允许用户定义…

Mybatis 快速入门(maven)

文章目录 需求建表新建了数据库但是navicat界面没有显示 新建maven项目 注意导入依赖 总结 黑马学习笔记 需求 建表 注意&#xff1a;设置字符集 减少出错 drop database mybatis; create database mybatis charset utf8; use mybatis;drop table if exists tb_user;create…

【IDEA】自定义注解

1.给Java类加上注解 包含一个代码片段&#xff0c;可以在 #parse 指令的帮助下将其包含在 文件模板&#xff08;Templates 标签页&#xff09;中。 此内置模板是可编辑的。 除了静态文本、代码和注释外&#xff0c;您还可以使用预定义变量&#xff0c;这些变量随后将像宏一样被…

Android 如何实现搜索功能:本地搜索?数据模型如何设计?数据如何展示和保存?

目录 效果图为什么需要搜索功能如何设计搜索本地的功能&#xff0c;如何维护呢&#xff1f;总结 一、效果图 二、为什么需要搜索功能 找一个选项&#xff0c;需要花非常多的时间&#xff0c;并且每次都需要指导客户在哪里&#xff0c;现在只要让他们搜索一下就可以。这也是模…

react hooks--useMemo

概述 相当于计算属性!!! useMemo实际的目的也是为了进行性能的优化。 ◼ 如何进行性能的优化呢&#xff1f;  useMemo返回的也是一个 memoized&#xff08;记忆的&#xff09; 值&#xff1b;  在依赖不变的情况下&#xff0c;多次定义的时候&#xff0c;返回的值是相同…

HarmonyOS Next开发----使用XComponent自定义绘制

XComponent组件作为一种绘制组件&#xff0c;通常用于满足用户复杂的自定义绘制需求&#xff0c;其主要有两种类型"surface和component。对于surface类型可以将相关数据传入XComponent单独拥有的NativeWindow来渲染画面。 由于上层UI是采用arkTS开发&#xff0c;那么想要…

Nexpose 6.6.269 发布下载,新增功能概览

Nexpose 6.6.269 for Linux & Windows - 漏洞扫描 Rapid7 Vulnerability Management, release Sep 11, 2024 请访问原文链接&#xff1a;https://sysin.org/blog/nexpose-6/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.or…

【Python爬虫系列】_024.MySQL数据库

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)教程合集 👈👈

六、JSON

文章目录 1. 什么是JSON1.1 JSON 在 JavaScript 中的使用1.1.1 json 的定义1.1.2 json 的访问1.1.3 json 的两个常用方法 1.2、JSON 在 java 中的使用1.2.1、javaBean 和 json 的互转1.2.2、List 和 json 的互转1.2.3、map 和 json 的互转 1. 什么是JSON 1.1 JSON 在 JavaScrip…

8.1差分边缘检测

基本概念 差分边缘检测是一种图像处理技术&#xff0c;用于检测图像中的边缘。边缘是指图像中灰度值发生显著变化的区域。差分边缘检测通常通过计算图像的梯度来实现&#xff0c;梯度反映了灰度值的变化率。在OpenCV中&#xff0c;可以使用不同的算子来检测不同方向的边缘&…

PDP 和 ICE 图的终极指南

部分依赖图和单独条件期望图背后的直觉、数学和代码(R 和 Python) PDP 和 ICE 图都可以帮助我们了解我们的模型如何做出预测。 使用个人显示面板我们可以将模型特征和目标变量之间的关系可视化。它们可以告诉我们某种关系是线性的、非线性的还是没有关系。 同样,当特征之间…

006.MySQL_查询数据

课 程 推 荐我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448;入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448;虚 拟 环 境 搭 建 &#xff1a;&#x1…

FPGA随记-二进制转格雷码

反射二进制码&#xff08;RBC&#xff09;&#xff0c;也称为反射二进制&#xff08;RB&#xff09;或格雷码&#xff08;Gray code&#xff09;&#xff0c;得名于Frank Gray&#xff0c;是二进制数制的一种排列方式&#xff0c;使得连续两个值之间仅有一个比特&#xff08;二…

EmguCV学习笔记 VB.Net 12.1 二维码解析

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

算法.图论-建图/拓扑排序及其拓展

文章目录 建图的三种方式邻接矩阵邻接表链式前向星 拓扑排序拓扑排序基础原理介绍拓扑排序步骤解析拓扑排序模板leetcode-课程表 拓扑排序拓展食物链计数喧闹与富有并行课程 建图的三种方式 我们建图的三种方式分别是邻接矩阵, 邻接矩阵, 链式前向星 邻接矩阵 假设我们的点的…

自定义复杂AntV/G6案例

一、效果图 二、源码 /** * * Author: me * CreatDate: 2024-08-22 * * Description: 复杂G6案例 * */ <template><div class"moreG6-wapper"><div id"graphContainer" ref"graphRef" class"graph-content"></d…