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

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;
        }

        @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.kler.cn/a/469217.html

相关文章:

  • 路由组件与一般组件的区别
  • Vue笔记-001-声明式渲染
  • nginx-灰度发布策略(split_clients)
  • 标题:利用Spring Boot构建JWT刷新令牌应用
  • 鸿蒙操作系统(HarmonyOS)
  • 如何使用脚手架工具开始,快速搭建一个 Express 项目的基础架构
  • HTML——66.单选框
  • Unity3D 搭建ILRuntime开发环境详解
  • security框架的安全登录
  • 【Cesium】九、Cesium点击地图获取点击位置的坐标,并在地图上添加图标
  • Chrome 查看 session 信息
  • 后端Java开发:第七天
  • ref() 和 reactive() 区别
  • Genome Research | 俄亥俄州立于忠堂组-结合深度学习与蛋白质数据库系统探究反刍动物真核微生物...
  • 解决docker: Error response from daemon: Get “https://registry-1.docker.io/v2/“: net/http: request canc
  • 如何在 Ubuntu 22.04 上安装 Cassandra NoSQL 数据库教程
  • SQL Server 中生成等差数列的方法研究
  • 【操作系统不挂科】<内存管理-文件系统-磁盘调度(19)>选择题+简答题(带答案与解析)
  • 挖掘建模之分类与预测
  • Jmeter进阶篇(31)解决java.net.BindException: Address already in use: connect报错
  • Dexcap复现代码数据预处理全流程(一)——replay_human_traj_vis.py
  • leecode1143.最长公共子序列
  • 成语接龙游戏生成器:结合 ZhipuAI 的 Python 实现
  • MySql核心面试面试问题解析
  • Redis - 4 ( 9000 字 Redis 入门级教程 )
  • VSCode突然消失,只好重新下载安装