Java實(shí)現(xiàn)解析Excel復(fù)雜表頭
在Java中如果想解析復(fù)雜的Excel表頭,可以使用Apache POI庫(kù)。
廢話不多說(shuō),直接上源碼。前后端都有哦
后端
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工具類(復(fù)雜表頭解析) * @Author: sh * @Date: 2025/1/2 09:29 */ public class ExcelComplexUtil { /** * 導(dǎo)入Excel文件,逐行讀取數(shù)據(jù),數(shù)據(jù)格式二維數(shù)組 * @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); // 獲取第一個(gè)工作表 // 獲取表頭行 Row headerRow = checkHeaderRow(sheet); if (headerRow != null) { //封裝表頭數(shù)據(jù) warpHeaderData(headerRow, dataList); } else { throw new RuntimeException("Excel 文件中沒(méi)有找到表頭行,請(qǐng)修改表格"); } // 讀取數(shù)據(jù)行 for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) { Row row = sheet.getRow(rowIndex); if (row == null) { continue; // 跳過(guò)空行 } //封裝行數(shù)據(jù) warpRowData(headerRow, row, dataList); } } // 將 List<String[]> 轉(zhuǎn)換為 String[][] 數(shù)組 String[][] result = new String[dataList.size()][]; for (int i = 0; i < dataList.size(); i++) { result[i] = dataList.get(i); } return result; // 返回二維數(shù)組 } /** * 檢查表頭行,表頭行必須在前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; } /** * 數(shù)據(jù)遍歷 * @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]; // 創(chuàng)建一維數(shù)組存儲(chǔ)表頭數(shù)據(jù) for (int colIndex = 0; colIndex < columnCount; colIndex++) { Cell cell = headerRow.getCell(lastCellNum - columnCount + colIndex); String cellValue = (cell != null) ? cell.toString() : ""; // 處理空單元格 data[colIndex] = cellValue; // 將單元格值放入表頭數(shù)組中 } dataList.add(data); // 將表頭數(shù)組添加到列表中 } public void warpRowData(Row headerRow, Row row, List<String[]> dataList) { int columnCount = headerRow.getPhysicalNumberOfCells(); short lastCellNum = headerRow.getLastCellNum(); String[] data = new String[columnCount]; // 創(chuàng)建一維數(shù)組存儲(chǔ)表頭數(shù)據(jù) for (int colIndex = 0; colIndex < columnCount; colIndex++) { Cell cell = row.getCell(lastCellNum - columnCount + colIndex); String cellValue = (cell != null) ? cell.toString() : ""; // 處理空單元格 data[colIndex] = cellValue; // 將單元格值放入表頭數(shù)組中 } dataList.add(data); // 將表頭數(shù)組添加到列表中 } /** * 獲取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); // 獲取第一個(gè)工作表 int numberOfMergedRegions = sheet.getNumMergedRegions(); // 獲取合并單元格的數(shù)量 List<MergedCell> mergedCellArray = new ArrayList<>(); for (int i = 0; i < numberOfMergedRegions; i++) { MergedCell mergedCell = new MergedCell(); CellRangeAddress range = sheet.getMergedRegion(i); // 獲取合并單元格區(qū)域 mergedCell.setRange(range.formatAsString()); // 獲取合并單元格區(qū)域的起始單元格 int firstRow = range.getFirstRow(); int firstCol = range.getFirstColumn(); // 獲取合并單元格的內(nèi)容 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; // 如果該單元格在合并區(qū)內(nèi),返回 true } } return false; // 如果不在任何合并區(qū)內(nèi),返回 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)); // 以逗號(hào)為分隔符打印每一行 // } // List<MergedCell> mergedCells = checkMergedCells(filePath, 0); // for (MergedCell row : mergedCells) { // System.out.println(row); // 以逗號(hào)為分隔符打印每一行 // } // // } 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; /* 設(shè)置表格寬度 */ } td { border: 1px solid black; padding: 0; /* 清除內(nèi)邊距 */ text-align: center; vertical-align: middle; /* 垂直居中 */ height: 50px; /* 設(shè)置每個(gè)單元格的高度 */ } .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: [ ["監(jiān)督員地區(qū)", "總數(shù)", "有效", "", "有效政治類", ""], ["平谷區(qū)監(jiān)督員", "244", "", "197", "28", "169"], ["", "244", "", "197", "28", ""], ["數(shù)據(jù) 16", "數(shù)據(jù) 17", "數(shù)據(jù) 18", "數(shù)據(jù) 19", "數(shù)據(jù) 20"], ["數(shù)據(jù) 21", "數(shù)據(jù) 22", "數(shù)據(jù) 23", "數(shù)據(jù) 24", "數(shù)據(jù) 25"], ["", "", "", "", ""] ], occupiedCells: [] }; }, computed: { // 生成完整表格數(shù)據(jù) 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; // 標(biāo)記占用 } } } }); // 填充普通單元格的數(shù)據(jù) 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>
到此這篇關(guān)于Java實(shí)現(xiàn)解析Excel復(fù)雜表頭的文章就介紹到這了,更多相關(guān)Java解析Excel內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot處理form-data表單接收對(duì)象數(shù)組的方法
form-data則是一種更加靈活的編碼方式,它可以處理二進(jìn)制數(shù)據(jù)(如圖片、文件等)以及文本數(shù)據(jù),這篇文章主要介紹了SpringBoot處理form-data表單接收對(duì)象數(shù)組,需要的朋友可以參考下2023-11-11Java?根據(jù)XPATH批量替換XML節(jié)點(diǎn)中的值
這篇文章主要介紹了Java根據(jù)XPATH批量替換XML節(jié)點(diǎn)中的值,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09使用Jackson進(jìn)行JSON生成與解析的新手指南
這篇文章主要為大家詳細(xì)介紹了如何使用Jackson進(jìn)行JSON生成與解析處理,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-04-04Java Spring的數(shù)據(jù)庫(kù)開(kāi)發(fā)詳解
這篇文章主要介紹了Spring的數(shù)據(jù)庫(kù)開(kāi)發(fā),主要圍繞SpringJDBC和Spring Jdbc Template兩個(gè)技術(shù)來(lái)講解,文中有詳細(xì)的代碼示例,需要的小伙伴可以參考一下2023-04-04java高并發(fā)的volatile與Java內(nèi)存模型詳解
這篇文章主要介紹了java高并發(fā)的volatile與Java內(nèi)存模型,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2021-10-10