当前位置: 首页 > news >正文

JAVA解析Excel复杂表头

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

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

后端: 

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;}@Overridepublic 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"><tdv-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>

相关文章:

JAVA解析Excel复杂表头

废话不多说&#xff0c;直接上源码。前后端都有哦&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e; 能帮到你记得点赞收藏哦&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#…...

jmeter 中 BeanShell 预处理程序、JSR223后置处理程序使用示例

1. 各个组件如何新建的&#xff1f; 2. "http请求" 组件内容样例&#xff1a; "消息体数据" 源码&#xff1a; {"task_tag": "face_detect","image_type": "base64","extra_args": [{"model"…...

我的创作纪念日——《惊变128天》

我的创作纪念日——《惊变128天》 机缘收获日常成就憧憬 机缘 时光飞逝&#xff0c;转眼间&#xff0c;我已在这条创作之路上走过了 128 天。回顾起 2024 年 8 月 29 日&#xff0c;我满怀忐忑与期待&#xff0c;撰写了第一篇技术博客《讲解LeetCode第1题&#xff1a;两数之和…...

vuedraggable 选项介绍

vuedraggable 是基于 SortableJS 的 Vue 组件&#xff0c;提供了丰富的选项来定制拖拽行为。以下是 vuedraggable 常用的选项和它们的详细说明&#xff1a; 常用选项介绍 group 配置拖拽分组。多个列表可以共享同一个分组&#xff0c;允许它们之间的项目互相拖拽。 group: { na…...

微信小程序获取后端数据

在小程序中获取后端接口数据 通常可以使用 wx.request 方法&#xff0c;以下是一个基本示例&#xff1a; // pages/index/index.js Page({data: {// 用于存储后端返回的数据resultData: [] },onLoad() {this.fetchData();},fetchData() {wx.request({url: https://your-backe…...

ThreadLocal` 的工作原理

ThreadLocal 的工作原理&#xff1a; ThreadLocal 是 Java 提供的一个类&#xff0c;它用于为每个线程提供独立的变量副本。也就是说&#xff0c;多个线程访问同一个 ThreadLocal 变量时&#xff0c;每个线程看到的值都是不同的&#xff0c;相互隔离&#xff0c;互不干扰。 T…...

数据挖掘教学指南:从基础到应用

数据挖掘教学指南&#xff1a;从基础到应用 引言 数据挖掘是大数据时代的核心技术之一&#xff0c;它从大量数据中提取有用信息和知识。本教学文章旨在为学生和初学者提供一个全面的数据挖掘学习指南&#xff0c;涵盖数据挖掘的基本概念、流程、常用技术、工具以及教学建议。…...

大模型搜索引擎增强问答demo-纯python实现

流程概览 本文使用python语言,实现了大模型搜索引擎增强问答demo。 大模型搜索引擎增强问答定义:根据问题搜索得到相关内容,拼接prompt=问题+搜索结果,将这个prompt传入大模型,得到最终的结果。 优势在于搜索引擎可以返回实时性信息,例如明日双色球开奖信息、最新八卦…...

【C语言程序设计——选择结构程序设计】按从小到大排序三个数(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 编程要求 相关知识 1. 选择结构 2. 主要语句类型 3. 比较操作 4. 交换操作 测试说明 通关代码 测试结果 任务描述 本关任务&#xff1a;从键盘上输入三个数&#xff0c;请按从小到大的顺序排序并打印输出排序后的结果。 编程要求 根据提示…...

简洁安装配置在Windows环境下使用vscode开发pytorch

简洁安装配置在Windows环境下使用vscode开发pytorch 使用anaconda安装pytorch&#xff0c;通过vscode集成环境开发pytorch 下载 anaconda 下载网址&#xff0c;选择对应系统的版本 https://repo.anaconda.com/archive/ windows可以选择Anaconda3-2024.10-1-Windows-x86_64.e…...

conda安装及demo:SadTalker实现图片+音频生成高质量视频

1.安装conda 下载各个版本地址&#xff1a;https://repo.anaconda.com/archive/ win10版本&#xff1a; Anaconda3-2023.03-1-Windows-x86_64 linux版本&#xff1a; Anaconda3-2023.03-1-Linux-x86_64 Windows安装 环境变量 conda -V2.配置conda镜像源 安装pip conda…...

【面试】后端开发面试中常见数据结构及应用场景、原理总结

在后端开发面试中&#xff0c;常见的数据结构包括数组、链表、栈、队列、二叉树、平衡树、堆、图和哈希表等。以下是这些数据结构的总结&#xff0c;包括它们的应用场景、优缺点。 常见数据结构及其应用场景 数据结构应用场景数组存储固定大小的数据集合&#xff0c;如学生成…...

141.《mac m系列芯片安装mongodb详细教程》

文章目录 下载从官网下载安装包 下载后双击解压出文件夹安装文件名修改为 mongodb配置data存放位置和日志log的存放位置启动方式一方式二方式二:输入mongo报错以及解决办法 本人电脑 m2 pro,属于 arm 架构 下载 官网地址: mongodb官网 怎么查看自己电脑应该下载哪个版本,输入…...

Java 23 集合框架详解:ArrayList、LinkedList、Vector

&#x1f4da; Java 23 集合框架详解&#xff1a;ArrayList、LinkedList、Vector 在 Java 集合框架中&#xff0c;ArrayList、LinkedList 和 Vector 是三种最常用的 List 接口实现类。它们都可以存储有序的、可重复的元素&#xff0c;但它们在 底层实现、性能 和 多线程安全 等…...

03、MySQL安全管理和特性解析(DBA运维专用)

03、MySQL安全管理和特性解析 本节主要讲MySQL的安全管理、角色使用、特定场景下的数据库对象、各版本特性以及存储引擎 目录 03、MySQL安全管理和特性解析 1、 用户和权限管理 2、 MySQL角色管理 3、 MySQL密码管理 4、 用户资源限制 5、 忘记root密码处理办法 6、 SQ…...

创建型模式5.单例模式

创建型模式 工厂方法模式&#xff08;Factory Method Pattern&#xff09;抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;建造者模式&#xff08;Builder Pattern&#xff09;原型模式&#xff08;Prototype Pattern&#xff09;单例模式&#xff08;Singleto…...

用户界面软件02

基于表单的用户界面 在“基于表单的用户界面”里面&#xff0c;用户开始时选中某个业务处理&#xff08;模块&#xff09;&#xff0c;然后应用程序就使用一系列的表单来引导用户完成整个处理过程。大型机系统上的大部分用户界面都是这样子的。[Cok97]中有更为详细的讨论。 面…...

VTK 鼠标+键盘重构

1、鼠标事件 如果有鼠标事件处理等相应的需求,可以重写该事件。 void OnMouseMove() override; //鼠标移动事件 void OnLeftButtonDown() override;//左键按下事件 void OnLeftButtonUp() override;//左键抬起事件 void OnMiddleButtonDown() override;//滚轮按下事件 …...

go语言处理JSON数据详解

一、结构体与json之间的转换 Go语言处理JSON数据通常涉及到将JSON数据解析成Go结构体,或者将Go结构体序列化为JSON格式。Go提供了内置的encoding/json包来实现这些操作。下面详细介绍如何在Go中处理JSON数据。 1. Go结构体与JSON映射 Go语言的encoding/json包可以将JSON数据…...

基于gin一个还算比较优雅的controller实现

看了两天时间的go&#xff0c;对于go的编码风格还不是很了解&#xff0c;但是了解到go并未有Java那样成体系的编码风格规范&#xff0c;所以自己浅尝试了一下&#xff0c;风格无对错&#xff0c;欢迎交流讨论&#xff5e; controller层&#xff1a; package …...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...