package com.casic.missiles.utils; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class ExcelCopyUtil { /** * 从源Excel文件中复制指定的行,并粘贴到目标Excel文件的指定Sheet页中对应的行 * * @param sourceFilePath 源Excel文件路径 * @param sourceSheetName 源Excel文件的Sheet页名称 * @param targetFilePath 目标Excel文件路径 * @param targetSheetName 目标Excel文件的Sheet页名称 * @param sourceRowIndices 需要复制的源文件行索引范围(起始和结束,从0开始) * @param targetRowIndices 需要粘贴的目标文件行索引范围(起始和结束,从0开始) * @throws IOException 文件读写异常 */ public static void copyRowsToExcel(String sourceFilePath, String sourceSheetName, String targetFilePath, String targetSheetName, List<Integer> sourceRowIndices, List<Integer> targetRowIndices, List<String> linkedSheetNames) throws IOException { // 参数校验 if (sourceRowIndices.size() != 2 || targetRowIndices.size() != 2) { throw new IllegalArgumentException("源行索引范围和目标行索引范围必须包含起始和结束两个值。"); } int sourceStart = sourceRowIndices.get(0); int sourceEnd = sourceRowIndices.get(1); int targetStart = targetRowIndices.get(0); int targetEnd = targetRowIndices.get(1); if (sourceEnd < sourceStart || targetEnd < targetStart) { throw new IllegalArgumentException("起始索引必须小于或等于结束索引。"); } if ((sourceEnd - sourceStart) != (targetEnd - targetStart)) { throw new IllegalArgumentException("源行索引范围和目标行索引范围的长度必须相同。"); } // 生成行索引列表 List<Integer> sourceRows = new ArrayList<>(); List<Integer> targetRows = new ArrayList<>(); for (int i = 0; i <= sourceEnd - sourceStart; i++) { sourceRows.add(sourceStart + i); targetRows.add(targetStart + i); } // 调用现有的复制逻辑 copyRowsToAnotherExcel(sourceFilePath, sourceSheetName, targetFilePath, targetSheetName, sourceRows, targetRows, linkedSheetNames); } private static void copyRowsToAnotherExcel(String sourceFilePath, String sourceSheetName, String targetFilePath, String targetSheetName, List<Integer> sourceRowIndices, List<Integer> targetRowIndices, List<String> linkedSheetNames) throws IOException { if (sourceRowIndices.size() != targetRowIndices.size()) { throw new IllegalArgumentException("源行索引列表和目标行索引列表的长度必须相同。"); } try (FileInputStream fis = new FileInputStream(sourceFilePath); Workbook sourceWorkbook = WorkbookFactory.create(fis); FileInputStream targetFis = new FileInputStream(targetFilePath); Workbook targetWorkbook = WorkbookFactory.create(targetFis); FileOutputStream fos = new FileOutputStream(targetFilePath)) { // 获取源Sheet页 Sheet sourceSheet = sourceWorkbook.getSheet(sourceSheetName); if (sourceSheet == null) { throw new IllegalArgumentException("Sheet '" + sourceSheetName + "' not found in source file."); } // 获取目标Sheet页 Sheet targetSheet = targetWorkbook.getSheet(targetSheetName); if (targetSheet == null) { throw new IllegalArgumentException("Sheet '" + targetSheetName + "' not found in target file."); } // 创建线程池 ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); // 遍历需要复制的行 for (int i = 0; i < sourceRowIndices.size(); i++) { int sourceRowIndex = sourceRowIndices.get(i); int targetRowIndex = targetRowIndices.get(i); // 提交任务到线程池 executor.submit(() -> { try { copyRow(sourceSheet, targetSheet, sourceRowIndex, targetRowIndex, targetWorkbook); } catch (Exception e) { e.printStackTrace(); } }); } // 关闭线程池并等待所有任务完成 executor.shutdown(); executor.awaitTermination(1, TimeUnit.HOURS); // 复制合并单元格(避免重叠) copyMergedRegions(sourceSheet, targetSheet, sourceRowIndices, targetRowIndices); // 更新指定 Sheet 页的链接内容 updateLinkedReferences(targetWorkbook, targetSheetName, targetRowIndices, sourceRowIndices, linkedSheetNames); // 强制Excel在打开文件时重新计算公式 targetWorkbook.setForceFormulaRecalculation(true); // 写入目标文件 targetWorkbook.write(fos); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Thread interrupted", e); } } private static void copyRow(Sheet sourceSheet, Sheet targetSheet, int sourceRowIndex, int targetRowIndex, Workbook targetWorkbook) { // 获取源行 Row sourceRow = sourceSheet.getRow(sourceRowIndex); if (sourceRow == null) { return; // 如果行不存在,跳过 } // 获取或创建目标行 Row targetRow = targetSheet.getRow(targetRowIndex); if (targetRow == null) { targetRow = targetSheet.createRow(targetRowIndex); } // 复制行高 targetRow.setHeight(sourceRow.getHeight()); // 复制每个单元格 for (int cellIndex = 0; cellIndex < sourceRow.getLastCellNum(); cellIndex++) { Cell sourceCell = sourceRow.getCell(cellIndex); if (sourceCell == null) { continue; // 如果单元格不存在,跳过 } // 获取或创建目标单元格 Cell targetCell = targetRow.getCell(cellIndex); if (targetCell == null) { targetCell = targetRow.createCell(cellIndex); } // 复制单元格内容、样式和边框 copyCell(sourceCell, targetCell, targetWorkbook, sourceRowIndex, targetRowIndex); } // 复制行样式(如果有) if (sourceRow.getRowStyle() != null) { targetRow.setRowStyle(sourceRow.getRowStyle()); } } /** * 修复公式中的无效函数名称和语法 * * @param formula 原始公式 * @return 修复后的公式 */ private static String fixFormula(String formula) { // 使用正则表达式匹配所有类似 IF5、IF10、IF15、IF45 的无效函数名称,并替换为 IF formula = formula.replaceAll("IF\\d+", "IF"); // 修复 IF 函数的语法(确保符合 IF(condition, value_if_true, value_if_false) 格式) if (formula.startsWith("IF(")) { formula = formula.replace("IF(", "IF("); } return formula; } private static void copyCell(Cell sourceCell, Cell targetCell, Workbook targetWorkbook, int sourceRowIndex, int targetRowIndex) { // 复制单元格样式 CellStyle sourceCellStyle = sourceCell.getCellStyle(); if (sourceCellStyle != null) { // 如果源单元格有样式,则克隆样式 CellStyle targetCellStyle = targetWorkbook.createCellStyle(); targetCellStyle.cloneStyleFrom(sourceCellStyle); targetCell.setCellStyle(targetCellStyle); } else { // 如果源单元格没有样式,则设置为默认样式 targetCell.setCellStyle(targetWorkbook.createCellStyle()); } // 复制单元格内容 switch (sourceCell.getCellType()) { case STRING: targetCell.setCellValue(sourceCell.getStringCellValue()); break; case NUMERIC: targetCell.setCellValue(sourceCell.getNumericCellValue()); break; case BOOLEAN: targetCell.setCellValue(sourceCell.getBooleanCellValue()); break; case FORMULA: // 调整公式中的单元格引用 String adjustedFormula = adjustFormulaReferences(sourceCell.getCellFormula(), sourceRowIndex, targetRowIndex); // 修复公式中的无效函数名称 String fixedFormula = fixFormula(adjustedFormula); try { // 尝试设置公式 targetCell.setCellFormula(fixedFormula); } catch (Exception e) { // 如果公式解析失败,记录错误并保留原始公式 System.err.println("Failed to parse formula: " + fixedFormula); targetCell.setCellFormula(adjustedFormula); } break; case BLANK: targetCell.setBlank(); break; default: throw new IllegalStateException("Unsupported cell type: " + sourceCell.getCellType()); } // 复制单元格注释(如果有) if (sourceCell.getCellComment() != null) { targetCell.setCellComment(sourceCell.getCellComment()); } // 复制单元格超链接(如果有) if (sourceCell.getHyperlink() != null) { targetCell.setHyperlink(sourceCell.getHyperlink()); } } /** * 调整公式中的单元格引用 * * @param formula 源公式 * @param sourceRowIndex 源行索引 * @param targetRowIndex 目标行索引 * @return 调整后的公式 */ private static String adjustFormulaReferences(String formula, int sourceRowIndex, int targetRowIndex) { // 计算行偏移量 int rowOffset = targetRowIndex - sourceRowIndex; // 调整公式中的行号 StringBuilder adjustedFormula = new StringBuilder(); int length = formula.length(); for (int i = 0; i < length; i++) { char c = formula.charAt(i); if (c == '$') { // 处理绝对引用符号 adjustedFormula.append(c); i++; if (i < length) { c = formula.charAt(i); if (Character.isLetter(c)) { // 处理列绝对引用(例如 $A) adjustedFormula.append(c); while (i + 1 < length && Character.isLetterOrDigit(formula.charAt(i + 1))) { adjustedFormula.append(formula.charAt(++i)); } } else if (Character.isDigit(c)) { // 处理行绝对引用(例如 $1) adjustedFormula.append(c); while (i + 1 < length && Character.isDigit(formula.charAt(i + 1))) { adjustedFormula.append(formula.charAt(++i)); } } } } else if (Character.isLetter(c)) { // 处理相对引用(例如 A1) StringBuilder cellRef = new StringBuilder(); cellRef.append(c); while (i + 1 < length && Character.isLetterOrDigit(formula.charAt(i + 1))) { cellRef.append(formula.charAt(++i)); } // 解析行号并调整 String cellRefStr = cellRef.toString(); int rowNumber = 0; int colNumber = 0; for (int j = 0; j < cellRefStr.length(); j++) { if (Character.isDigit(cellRefStr.charAt(j))) { rowNumber = rowNumber * 10 + (cellRefStr.charAt(j) - '0'); } else { colNumber = colNumber * 26 + (cellRefStr.charAt(j) - 'A' + 1); } } // 调整行号 rowNumber += rowOffset; // 重新构建单元格引用 adjustedFormula.append(getCellReference(colNumber, rowNumber)); } else { adjustedFormula.append(c); } } return adjustedFormula.toString(); } /** * 根据列号和行号获取单元格引用(例如 A1, B2) * * @param colNumber 列号(从1开始) * @param rowNumber 行号(从1开始) * @return 单元格引用 */ private static String getCellReference(int colNumber, int rowNumber) { StringBuilder cellRef = new StringBuilder(); while (colNumber > 0) { colNumber--; cellRef.insert(0, (char) ('A' + (colNumber % 26))); colNumber /= 26; } cellRef.append(rowNumber); return cellRef.toString(); } /** * 复制合并单元格(避免重叠) * * @param sourceSheet 源Sheet页 * @param targetSheet 目标Sheet页 * @param sourceRowIndices 源行索引列表 * @param targetRowIndices 目标行索引列表 */ private static void copyMergedRegions(Sheet sourceSheet, Sheet targetSheet, List<Integer> sourceRowIndices, List<Integer> targetRowIndices) { for (int i = 0; i < sourceSheet.getNumMergedRegions(); i++) { CellRangeAddress mergedRegion = sourceSheet.getMergedRegion(i); for (int j = 0; j < sourceRowIndices.size(); j++) { int sourceRowIndex = sourceRowIndices.get(j); int targetRowIndex = targetRowIndices.get(j); // 如果合并区域包含源行,则在目标Sheet中创建对应的合并区域 if (mergedRegion.getFirstRow() <= sourceRowIndex && mergedRegion.getLastRow() >= sourceRowIndex) { // 计算目标合并区域的行范围 int firstRow = targetRowIndex - (sourceRowIndex - mergedRegion.getFirstRow()); int lastRow = targetRowIndex + (mergedRegion.getLastRow() - sourceRowIndex); // 创建新的合并区域 CellRangeAddress newMergedRegion = new CellRangeAddress( firstRow, lastRow, mergedRegion.getFirstColumn(), mergedRegion.getLastColumn() ); // 检查目标Sheet中是否存在重叠的合并区域 if (!isMergedRegionOverlapping(targetSheet, newMergedRegion)) { targetSheet.addMergedRegion(newMergedRegion); } } } } } /** * 检查目标Sheet中是否存在与指定合并区域重叠的区域 * * @param sheet 目标Sheet页 * @param mergedRegion 需要检查的合并区域 * @return 是否存在重叠 */ private static boolean isMergedRegionOverlapping(Sheet sheet, CellRangeAddress mergedRegion) { for (int i = 0; i < sheet.getNumMergedRegions(); i++) { CellRangeAddress existingRegion = sheet.getMergedRegion(i); if (existingRegion.intersects(mergedRegion)) { return true; // 存在重叠 } } return false; // 不存在重叠 } /** * 更新目标文件中指定 Sheet 页的链接内容,使其随偏移量变化 * * @param targetWorkbook 目标工作簿 * @param targetSheetName 目标 Sheet 页名称 * @param targetRowIndices 目标行索引列表 * @param sourceRowIndices 源行索引列表 * @param linkedSheetNames 需要更新链接的 Sheet 页名称列表 */ private static void updateLinkedReferences(Workbook targetWorkbook, String targetSheetName, List<Integer> targetRowIndices, List<Integer> sourceRowIndices, List<String> linkedSheetNames) { // 获取目标 Sheet 页 Sheet targetSheet = targetWorkbook.getSheet(targetSheetName); if (targetSheet == null) { throw new IllegalArgumentException("Sheet '" + targetSheetName + "' not found in target file."); } // 遍历指定的 Sheet 页 for (String sheetName : linkedSheetNames) { Sheet sheet = targetWorkbook.getSheet(sheetName); if (sheet == null) { System.err.println("Sheet '" + sheetName + "' not found in target file. Skipping..."); continue; } // 遍历 Sheet 页中的所有行和单元格 for (Row row : sheet) { if (row == null) { continue; } for (Cell cell : row) { if (cell == null || cell.getCellType() != CellType.FORMULA) { continue; // 跳过非公式单元格 } // 获取公式并调整引用 String formula = cell.getCellFormula(); for (int i = 0; i < sourceRowIndices.size(); i++) { int sourceRowIndex = sourceRowIndices.get(i); int targetRowIndex = targetRowIndices.get(i); // 调整公式中的单元格引用 String adjustedFormula = adjustFormulaReferences(formula, sourceRowIndex, targetRowIndex); // 修复公式中的无效函数名称和语法 String fixedFormula = fixFormula(adjustedFormula); // 尝试设置公式 try { cell.setCellFormula(fixedFormula); } catch (Exception e) { // 如果公式解析失败,记录错误并保留原始公式 System.err.println("Failed to parse formula: " + fixedFormula); System.err.println("Error: " + e.getMessage()); cell.setCellFormula(adjustedFormula); // 保留调整后的公式 } formula = adjustedFormula; // 更新公式,以便后续调整 } } } } } }