JAVA解析Excel复杂表头

news2025/1/7 18:11:17

废话不多说,直接上源码。前后端都有哦~~~~~~~~

能帮到你记得点赞收藏哦~~~~~~~~~

后端: 

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @Description:Excel工具类(复杂表头解析)
 * @Author: sh
 * @Date: 2025/1/2 09:29
 */
public class ExcelComplexUtil {
    /**
     * 导入Excel文件,逐行读取数据,数据格式二维数组
     * @param filePath
     * @param sheetIndex
     * @return
     * @throws IOException
     */
    public String[][] importExcel(String filePath, int sheetIndex) throws IOException {
        List<String[]> dataList = new ArrayList<>();

        try (FileInputStream fis = new FileInputStream(new File(filePath));
             Workbook workbook = new XSSFWorkbook(fis)) {

            Sheet sheet = workbook.getSheetAt(sheetIndex); // 获取第一个工作表
            // 获取表头行
            Row headerRow = checkHeaderRow(sheet);

            if (headerRow != null) {
                //封装表头数据
                warpHeaderData(headerRow, dataList);
            } else {
                throw new RuntimeException("Excel 文件中没有找到表头行,请修改表格");
            }

            // 读取数据行
            for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
                Row row = sheet.getRow(rowIndex);

                if (row == null) {
                    continue; // 跳过空行
                }
                //封装行数据
                warpRowData(headerRow, row, dataList);
            }
        }

        // 将 List<String[]> 转换为 String[][] 数组
        String[][] result = new String[dataList.size()][];
        for (int i = 0; i < dataList.size(); i++) {
            result[i] = dataList.get(i);
        }

        return result; // 返回二维数组
    }

    /**
     * 检查表头行,表头行必须在前10行中
     * @param sheet
     * @return
     */
    private Row checkHeaderRow(Sheet sheet) {
        int i = 0;
        Row headerRow = null;

        while (i < 10) {
            headerRow = sheet.getRow(i);
            if (headerRow != null) {
                break;
            }

            i++;
        }

        return headerRow;
    }

    /**
     * 数据遍历
     * @param headerRow
     * @param dataList
     * @throws IOException
     */
    public void warpHeaderData(Row headerRow, List<String[]> dataList) {
        int columnCount = headerRow.getPhysicalNumberOfCells();
        short lastCellNum = headerRow.getLastCellNum();

        String[] data = new String[columnCount]; // 创建一维数组存储表头数据
        for (int colIndex = 0; colIndex < columnCount; colIndex++) {
            Cell cell = headerRow.getCell(lastCellNum - columnCount + colIndex);
            String cellValue = (cell != null) ? cell.toString() : ""; // 处理空单元格
            data[colIndex] = cellValue; // 将单元格值放入表头数组中
        }

        dataList.add(data); // 将表头数组添加到列表中
    }

    public void warpRowData(Row headerRow, Row row, List<String[]> dataList) {
        int columnCount = headerRow.getPhysicalNumberOfCells();
        short lastCellNum = headerRow.getLastCellNum();

        String[] data = new String[columnCount]; // 创建一维数组存储表头数据
        for (int colIndex = 0; colIndex < columnCount; colIndex++) {
            Cell cell = row.getCell(lastCellNum - columnCount + colIndex);
            String cellValue = (cell != null) ? cell.toString() : ""; // 处理空单元格
            data[colIndex] = cellValue; // 将单元格值放入表头数组中
        }

        dataList.add(data); // 将表头数组添加到列表中
    }

    /**
     * 获取excel中所有合并单元格
     * @param filePath
     * @param sheetIndex
     * @throws IOException
     */
    public List<MergedCell> checkMergedCells(String filePath, int sheetIndex) throws IOException {
        try (FileInputStream fis = new FileInputStream(new File(filePath));
             Workbook workbook = new XSSFWorkbook(fis)) {

            Sheet sheet = workbook.getSheetAt(sheetIndex); // 获取第一个工作表
            int numberOfMergedRegions = sheet.getNumMergedRegions(); // 获取合并单元格的数量

            List<MergedCell> mergedCellArray = new ArrayList<>();

            for (int i = 0; i < numberOfMergedRegions; i++) {
                MergedCell mergedCell = new MergedCell();

                CellRangeAddress range = sheet.getMergedRegion(i); // 获取合并单元格区域
                mergedCell.setRange(range.formatAsString());
                // 获取合并单元格区域的起始单元格
                int firstRow = range.getFirstRow();
                int firstCol = range.getFirstColumn();

                // 获取合并单元格的内容
                Row row = sheet.getRow(firstRow);
                Cell cell = row.getCell(firstCol);
                String cellValue = (cell != null) ? cell.toString() : ""; // 处理空单元格
                mergedCell.setValue(cellValue);

                mergedCellArray.add(mergedCell);
            }

            return mergedCellArray;
        }
    }

    /**
     * 检查特定单元格是否是合并单元格
     * @param sheet
     * @param row
     * @param col
     * @return
     */
    private boolean isMergedCell(Sheet sheet, int row, int col) {
        int numberOfMergedRegions = sheet.getNumMergedRegions();
        for (int i = 0; i < numberOfMergedRegions; i++) {
            CellRangeAddress range = sheet.getMergedRegion(i);
            if (range.isInRange(row, col)) {
                return true; // 如果该单元格在合并区内,返回 true
            }
        }
        return false; // 如果不在任何合并区内,返回 false
    }

    class MergedCell {
        private String range;
        private String value;

        public String getRange() {
            return range;
        }

        public void setRange(String range) {
            this.range = range;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return "MergedCell{" +
                    "range='" + range + '\'' +
                    ", value='" + value + '\'' +
                    '}';
        }
    }

    //    public static void main(String[] args) {
//        String filePath = "/ceshi/ceshi.xlsx"; // Excel 文件路径
//        try {
//            String[][] strings = importExcel(filePath, 0);
//            for (String[] row : strings) {
//                System.out.println(String.join(", ", row)); // 以逗号为分隔符打印每一行
//            }
//            List<MergedCell> mergedCells = checkMergedCells(filePath, 0);
//            for (MergedCell row : mergedCells) {
//                System.out.println(row); // 以逗号为分隔符打印每一行
//            }
//
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }
}

前端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>合并单元格查看器</title>
    <style>
        table {
            border-collapse: collapse;
            width: 500px; /* 设置表格宽度 */
        }
        td {
            border: 1px solid black;
            padding: 0; /* 清除内边距 */
            text-align: center;
            vertical-align: middle; /* 垂直居中 */
            height: 50px; /* 设置每个单元格的高度 */
        }
        .merged {
            background-color: #f0f0f0; /* 合并单元格的背景颜色 */
            font-weight: bold; /* 合并单元格字体加粗 */
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>合并单元格查看器</h1>
        <table>
            <tr v-for="(row, rowIndex) in tableData" :key="rowIndex">
                <td
                    v-for="(cell, colIndex) in row"
                    :key="colIndex"
                    v-if="!isCellOccupied(rowIndex, colIndex)"
                    :rowspan="getRowSpan(rowIndex, colIndex)"
                    :colspan="getColSpan(rowIndex, colIndex)"
                    :class="{ merged: isMergedCell(rowIndex, colIndex) }"
                >
                    {{ getCellValue(rowIndex, colIndex) }}
                </td>
            </tr>
        </table>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    mergedCells: [
                        { range: "C1:D1", value: "合并单元格 1" },
                        { range: "B2:C2", value: "合并单元格 2" },
                        { range: "B3:C3", value: "合并单元格 " },
                        { range: "A2:A3", value: "合并单元格 " },
                        { range: "F2:F3", value: "合并单元格 " } 
                    ],
                    normalData: [
                        ["监督员地区", "总数", "有效", "", "有效政治类", ""],
                        ["平谷区监督员", "244", "", "197", "28", "169"],
                        ["", "244", "", "197", "28", ""],
                        ["数据 16", "数据 17", "数据 18", "数据 19", "数据 20"],
                        ["数据 21", "数据 22", "数据 23", "数据 24", "数据 25"],
                        ["", "", "", "", ""]
                    ],
                    occupiedCells: []
                };
            },
            computed: {
                // 生成完整表格数据
                tableData() {
                    const rows = this.normalData.length;
                    const cols = this.normalData[0].length;
                    const emptyTable = Array.from({ length: rows }, () => Array(cols).fill(null));

                    // 填充合并单元格
                    this.mergedCells.forEach(({ range, value }) => {
                        const [start, end] = range.split(':');
                        const startRow = parseInt(start.match(/\d+/)[0]) - 1;
                        const startCol = start.charCodeAt(0) - 65;
                        const endRow = parseInt(end.match(/\d+/)[0]) - 1;
                        const endCol = end.charCodeAt(0) - 65;

                        // 填充合并单元格的位置
                        for (let r = startRow; r <= endRow; r++) {
                            for (let c = startCol; c <= endCol; c++) {
                                if (r === startRow && c === startCol) {
                                    emptyTable[r][c] = value; // 合并单元格的值
                                } else {
                                    this.occupiedCells[r] = this.occupiedCells[r] || [];
                                    this.occupiedCells[r][c] = true; // 标记占用
                                }
                            }
                        }
                    });

                    // 填充普通单元格的数据
                    this.normalData.forEach((row, r) => {
                        row.forEach((cell, c) => {
                            if (!this.occupiedCells[r] || !this.occupiedCells[r][c]) {
                                emptyTable[r][c] = cell;
                            }
                        });
                    });

                    return emptyTable;
                }
            },
            methods: {
                isCellOccupied(rowIndex, colIndex) {
                    return this.occupiedCells[rowIndex] && this.occupiedCells[rowIndex][colIndex];
                },
                getCellValue(rowIndex, colIndex) {
                    return this.tableData[rowIndex][colIndex];
                },
                getRowSpan(rowIndex, colIndex) {
                    let rowspan = 1;
                    const firstValue = this.getCellValue(rowIndex, colIndex);
                    const mergedCell = this.mergedCells.find(({ range }) => {
                        const [start, end] = range.split(':');
                        const startRow = parseInt(start.match(/\d+/)[0]) - 1;
                        const startCol = start.charCodeAt(0) - 65;
                        const endRow = parseInt(end.match(/\d+/)[0]) - 1;
                        const endCol = end.charCodeAt(0) - 65;
                        return rowIndex === startRow && colIndex === startCol;
                    });
                    if (mergedCell) {
                        const [start, end] = mergedCell.range.split(':');
                        const startRow = parseInt(start.match(/\d+/)[0]) - 1;
                        const endRow = parseInt(end.match(/\d+/)[0]) - 1;
                        rowspan = endRow - startRow + 1;
                    }
                    return rowspan;
                },
                getColSpan(rowIndex, colIndex) {
                    let colspan = 1;
                    const firstValue = this.getCellValue(rowIndex, colIndex);
                    const mergedCell = this.mergedCells.find(({ range }) => {
                        const [start, end] = range.split(':');
                        const startRow = parseInt(start.match(/\d+/)[0]) - 1;
                        const startCol = start.charCodeAt(0) - 65;
                        const endRow = parseInt(end.match(/\d+/)[0]) - 1;
                        const endCol = end.charCodeAt(0) - 65;
                        return rowIndex === startRow && colIndex === startCol;
                    });
                    if (mergedCell) {
                        const [start, end] = mergedCell.range.split(':');
                        const startCol = start.charCodeAt(0) - 65;
                        const endCol = end.charCodeAt(0) - 65;
                        colspan = endCol - startCol + 1;
                    }
                    return colspan;
                },
                isMergedCell(rowIndex, colIndex) {
                    return this.mergedCells.some(({ range }) => {
                        const [start, end] = range.split(':');
                        const startRow = parseInt(start.match(/\d+/)[0]) - 1;
                        const startCol = start.charCodeAt(0) - 65;
                        return rowIndex === startRow && colIndex === startCol;
                    });
                }
            }
        });
    </script>
</body>
</html>

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

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

相关文章

uni-app:实现普通选择器,时间选择器,日期选择器,多列选择器

效果 选择前效果 1、时间选择器 2、日期选择器 3、普通选择器 4、多列选择器 选择后效果 代码 <template><!-- 时间选择器 --><view class"line"><view classitem1><view classleft>时间</view><view class"right&quo…

centos,789使用mamba快速安装R及语言包devtools

如何进入R语言运行环境请参考&#xff1a;Centos7_miniconda_devtools安装_R语言入门之R包的安装_r语言devtools包怎么安装-CSDN博客 在R里面使用安装devtools经常遇到依赖问题&#xff0c;排除过程过于费时&#xff0c;使用conda安装包等待时间长等。下面演示centos,789都是一…

STM32第十一课:STM32-基于标准库的42步进电机的简单IO控制(附电机教程,看到即赚到)

一&#xff1a;步进电机简介 步进电机又称为脉冲电机&#xff0c;简而言之&#xff0c;就是一步一步前进的电机。基于最基本的电磁铁原理,它是一种可以自由回转的电磁铁,其动作原理是依靠气隙磁导的变化来产生电磁转矩&#xff0c;步进电机的角位移量与输入的脉冲个数严格成正…

kafka使用以及基于zookeeper集群搭建集群环境

一、环境介绍 zookeeper下载地址&#xff1a;https://zookeeper.apache.org/releases.html kafka下载地址&#xff1a;https://kafka.apache.org/downloads 192.168.142.129 apache-zookeeper-3.8.4-bin.tar.gz kafka_2.13-3.6.0.tgz 192.168.142.130 apache-zookee…

JSON结构快捷转XML结构API集成指南

JSON结构快捷转XML结构API集成指南 引言 在当今的软件开发世界中&#xff0c;数据交换格式的选择对于系统的互操作性和效率至关重要。JSON&#xff08;JavaScript Object Notation&#xff09;和XML&#xff08;eXtensible Markup Language&#xff09;是两种广泛使用的数据表…

Android14 CTS-R6和GTS-12-R2不能同时测试的解决方法

背景 Android14 CTS r6和GTS 12-r1之后&#xff0c;tf-console默认会带起OLC Server&#xff0c;看起来olc server可能是想适配ATS(android-test-station)&#xff0c;一种网页版可视化、可配置的跑XTS的方式。这种网页版ATS对测试人员是比较友好的&#xff0c;网页上简单配置下…

BurpSuite工具安装

BurpSuite介绍&#xff1a; BurpSuite是由PortSwigger开发的一款集成化的Web应用安全检测工具&#xff0c;广泛应用于Web应用的漏洞扫描和攻击模拟&#xff0c;主要用于抓包该包(消息拦截与构造) 一、Burp suite安装 windows系统需要提前配置好java环境&#xff0c;前面博客…

Win11+WLS Ubuntu 鸿蒙开发环境搭建(一)

参考文章 Windows11安装linux子系统 WSL子系统迁移、备份与导入全攻略 如何扩展 WSL 2 虚拟硬盘的大小 Win10安装的WSL子系统占用磁盘空间过大如何释放 《Ubuntu — 调整文件系统大小命令resize2fs》 penHarmony南向开发笔记&#xff08;一&#xff09;开发环境搭建 一&a…

flink cdc oceanbase(binlog模式)

接上文&#xff1a;一文说清flink从编码到部署上线 环境&#xff1a;①操作系统&#xff1a;阿里龙蜥 7.9&#xff08;平替CentOS7.9&#xff09;&#xff1b;②CPU&#xff1a;x86&#xff1b;③用户&#xff1a;root。 预研初衷&#xff1a;现在很多项目有国产化的要求&#…

和为0的四元组-蛮力枚举(C语言实现)

目录 一、问题描述 二、蛮力枚举思路 1.初始化&#xff1a; 2.遍历所有可能的四元组&#xff1a; 3.检查和&#xff1a; 4.避免重复&#xff1a; 5.更新计数器&#xff1a; 三、代码实现 四、运行结果 五、 算法复杂度分析 一、问题描述 给定一个整数数组 nums&…

某xx到家app逆向

去官网下载app即可 https://www.dongjiaotn.com/#/home查壳 360的壳子 直接脱壳即可 抓包 请求地址 https://api.gateway.znjztfn.cn/server/user/index 请求参数 {"lng": "xxxx","lat": "xxxx","city_id": "1376&…

docker搭建gitlab和jenkins

搭建gitlab 搭建gitlab首先需要一个gitlab的镜像 其次最好为他设置一个单独的目录 然后编写一个docker-compose文件 version: 3.1 services:gitlab:image: gitlab_zh:latest //此处为你的镜像名称container_name: gitlab //容器名称restart: always …

嵌入式linux中socket控制与实现

一、概述 1、首先网络,一看到这个词,我们就会想到IP地址和端口号,那IP地址和端口各有什么作用呢? (1)IP地址如身份证一样,是标识的电脑的,一台电脑只有一个IP地址。 (2)端口提供了一种访问通道,服务器一般都是通过知名端口号来识别某个服务。例如,对于每个TCP/IP实…

推荐系统重排:MMR 多样性算法

和谐共存&#xff1a;相关性与多样性在MMR中共舞 推荐系统【多样性算法】系列文章&#xff08;置顶&#xff09; 1.推荐系统重排&#xff1a;MMR 多样性算法 2.推荐系统重排&#xff1a;DPP 多样性算法 引言 在信息检索和推荐系统中&#xff0c;提供既与用户查询高度相关的文…

概述(讲讲python基本语法和第三方库)

我是北子&#xff0c;这是我自己写的python教程&#xff0c;主要是记录自己的学习成果方便自己日后复习&#xff0c; 我先学了C/C&#xff0c;所以这套教程中可能会将很多概念和C/C去对比&#xff0c;所以该教程大概不适合零基础的人。 it seems that python nowadays 只在人工…

【python因果库实战16】双重稳健模型1

这里写目录标题 双重稳健模型数据简单双重稳健模型双重稳健 IP 特征模型 双重稳健模型 基本上&#xff0c;这些是利用加权模型增强结果模型的不同集合模型。 本笔记展示了不同的结果模型和倾向性模型组合方式&#xff0c; 但由于可能的组合非常多&#xff0c;本笔记并不打算展…

如何恢复已删除的 Telegram 消息 [iOSamp;Android]

Telegram 是一款功能强大的消息应用程序&#xff0c;因其易用性、隐私保护和众多炫酷功能而深受用户喜爱。然而&#xff0c;有时我们会不小心删除重要的消息。在这种情况下你应该做什么&#xff1f; 本文将为您提供简单有效的解决方案来恢复 Telegram 上已删除的消息&#xff…

第431场周赛:最长乘积等价子数组、计算字符串的镜像分数、收集连续 K 个袋子可以获得的最多硬币数量、不重叠区间的最大得分

Q1、最长乘积等价子数组 1、题目描述 给你一个由 正整数 组成的数组 nums。 如果一个数组 arr 满足 prod(arr) lcm(arr) * gcd(arr)&#xff0c;则称其为 乘积等价数组 &#xff0c;其中&#xff1a; prod(arr) 表示 arr 中所有元素的乘积。gcd(arr) 表示 arr 中所有元素的…

【微服务】2、网关

Spring Cloud微服务网关技术介绍 单体项目拆分微服务后的问题 服务地址问题&#xff1a;单体项目端口固定&#xff08;如黑马商城为8080&#xff09;&#xff0c;拆分微服务后端口各异&#xff08;如购物车808、商品8081、支付8086等&#xff09;且可能变化&#xff0c;前端难…

使用JMeter玩转tidb压测

作者&#xff1a; du拉松 原文来源&#xff1a; https://tidb.net/blog/3f1ada39 一、前言 tidb是mysql协议的&#xff0c;所以在使用过程中使用tidb的相关工具连接即可。因为jmeter是java开发的相关工具&#xff0c;直接使用mysql的jdbc驱动包即可。 二、linux下安装jmet…