| /* |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * ==================================================================== |
| */ |
| |
| package org.apache.poi.ss.usermodel; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import org.apache.poi.ss.formula.FormulaShifter; |
| import org.apache.poi.ss.formula.ptg.Ptg; |
| import org.apache.poi.ss.util.CellRangeAddress; |
| import org.apache.poi.util.Beta; |
| |
| @Beta |
| public abstract class RangeCopier { |
| private Sheet sourceSheet; |
| private Sheet destSheet; |
| private FormulaShifter horizontalFormulaShifter; |
| private FormulaShifter verticalFormulaShifter; |
| |
| public RangeCopier(Sheet sourceSheet, Sheet destSheet) { |
| this.sourceSheet = sourceSheet; |
| this.destSheet = destSheet; |
| } |
| |
| public RangeCopier(Sheet sheet) { |
| this(sheet, sheet); |
| } |
| |
| /** Uses input pattern to tile destination region, overwriting existing content. Works in following manner : |
| * 1.Start from top-left of destination. |
| * 2.Paste source but only inside of destination borders. |
| * 3.If there is space left on right or bottom side of copy, process it as in step 2. |
| * @param tilePatternRange source range which should be copied in tiled manner |
| * @param tileDestRange destination range, which should be overridden |
| */ |
| public void copyRange(CellRangeAddress tilePatternRange, CellRangeAddress tileDestRange) { |
| copyRange(tilePatternRange, tileDestRange, false, false); |
| } |
| |
| /** Uses input pattern to tile destination region, overwriting existing content. Works in following manner : |
| * 1.Start from top-left of destination. |
| * 2.Paste source but only inside of destination borders. |
| * 3.If there is space left on right or bottom side of copy, process it as in step 2. |
| * @param tilePatternRange source range which should be copied in tiled manner |
| * @param tileDestRange destination range, which should be overridden |
| * @param copyStyles whether to copy the cell styles |
| * @param copyMergedRanges whether to copy merged ranges |
| * @since 5.0.0 |
| */ |
| public void copyRange(CellRangeAddress tilePatternRange, CellRangeAddress tileDestRange, boolean copyStyles, boolean copyMergedRanges) { |
| Sheet sourceCopy = sourceSheet.getWorkbook().cloneSheet(sourceSheet.getWorkbook().getSheetIndex(sourceSheet)); |
| Map<Integer, CellStyle> styleMap = copyStyles ? new HashMap<Integer, CellStyle>() {} : null; |
| int sourceWidthMinus1 = tilePatternRange.getLastColumn() - tilePatternRange.getFirstColumn(); |
| int sourceHeightMinus1 = tilePatternRange.getLastRow() - tilePatternRange.getFirstRow(); |
| int rightLimitToCopy; |
| int bottomLimitToCopy; |
| |
| int nextRowIndexToCopy = tileDestRange.getFirstRow(); |
| do { |
| int nextCellIndexInRowToCopy = tileDestRange.getFirstColumn(); |
| int heightToCopyMinus1 = Math.min(sourceHeightMinus1, tileDestRange.getLastRow() - nextRowIndexToCopy); |
| bottomLimitToCopy = tilePatternRange.getFirstRow() + heightToCopyMinus1; |
| do { |
| int widthToCopyMinus1 = Math.min(sourceWidthMinus1, tileDestRange.getLastColumn() - nextCellIndexInRowToCopy); |
| rightLimitToCopy = tilePatternRange.getFirstColumn() + widthToCopyMinus1; |
| CellRangeAddress rangeToCopy = new CellRangeAddress( |
| tilePatternRange.getFirstRow(), bottomLimitToCopy, |
| tilePatternRange.getFirstColumn(), rightLimitToCopy |
| ); |
| copyRange(rangeToCopy, nextCellIndexInRowToCopy - rangeToCopy.getFirstColumn(), nextRowIndexToCopy - rangeToCopy.getFirstRow(), sourceCopy, styleMap); |
| nextCellIndexInRowToCopy += widthToCopyMinus1 + 1; |
| } while (nextCellIndexInRowToCopy <= tileDestRange.getLastColumn()); |
| nextRowIndexToCopy += heightToCopyMinus1 + 1; |
| } while (nextRowIndexToCopy <= tileDestRange.getLastRow()); |
| |
| if (copyMergedRanges) { |
| sourceSheet.getMergedRegions().forEach((mergedRangeAddress) -> destSheet.addMergedRegion(mergedRangeAddress)); |
| } |
| |
| int tempCopyIndex = sourceSheet.getWorkbook().getSheetIndex(sourceCopy); |
| sourceSheet.getWorkbook().removeSheetAt(tempCopyIndex); |
| } |
| |
| private void copyRange(CellRangeAddress sourceRange, int deltaX, int deltaY, Sheet sourceClone, Map<Integer, CellStyle> styleMap) { //NOSONAR, it's a bit complex but monolith method, does not make much sense to divide it |
| if(deltaX != 0) |
| horizontalFormulaShifter = FormulaShifter.createForColumnCopy(sourceSheet.getWorkbook().getSheetIndex(sourceSheet), |
| sourceSheet.getSheetName(), sourceRange.getFirstColumn(), sourceRange.getLastColumn(), deltaX, sourceSheet.getWorkbook().getSpreadsheetVersion()); |
| if(deltaY != 0) |
| verticalFormulaShifter = FormulaShifter.createForRowCopy(sourceSheet.getWorkbook().getSheetIndex(sourceSheet), |
| sourceSheet.getSheetName(), sourceRange.getFirstRow(), sourceRange.getLastRow(), deltaY, sourceSheet.getWorkbook().getSpreadsheetVersion()); |
| |
| for(int rowNo = sourceRange.getFirstRow(); rowNo <= sourceRange.getLastRow(); rowNo++) { |
| // copy from source copy, original source might be overridden in process! |
| Row sourceRow = sourceClone.getRow(rowNo); |
| if(sourceRow == null) { |
| continue; |
| } |
| |
| for (int columnIndex = sourceRange.getFirstColumn(); columnIndex <= sourceRange.getLastColumn(); columnIndex++) { |
| Cell sourceCell = sourceRow.getCell(columnIndex); |
| if(sourceCell == null) |
| continue; |
| Row destRow = destSheet.getRow(rowNo + deltaY); |
| if(destRow == null) |
| destRow = destSheet.createRow(rowNo + deltaY); |
| |
| Cell newCell = destRow.getCell(columnIndex + deltaX); |
| if(newCell == null) { |
| newCell = destRow.createCell(columnIndex + deltaX); |
| } |
| |
| cloneCellContent(sourceCell, newCell, styleMap); |
| if(newCell.getCellType() == CellType.FORMULA) |
| adjustCellReferencesInsideFormula(newCell, destSheet, deltaX, deltaY); |
| } |
| } |
| } |
| |
| protected abstract void adjustCellReferencesInsideFormula(Cell cell, Sheet destSheet, int deltaX, int deltaY); // this part is different for HSSF and XSSF |
| |
| protected boolean adjustInBothDirections(Ptg[] ptgs, int sheetIndex, int deltaX, int deltaY) { |
| boolean adjustSucceeded = true; |
| if(deltaY != 0) |
| adjustSucceeded = verticalFormulaShifter.adjustFormula(ptgs, sheetIndex); |
| if(deltaX != 0) |
| adjustSucceeded = adjustSucceeded && horizontalFormulaShifter.adjustFormula(ptgs, sheetIndex); |
| return adjustSucceeded; |
| } |
| |
| // TODO clone some more properties ? |
| public static void cloneCellContent(Cell srcCell, Cell destCell, Map<Integer, CellStyle> styleMap) { |
| if(styleMap != null) { |
| if(srcCell.getSheet().getWorkbook() == destCell.getSheet().getWorkbook()){ |
| destCell.setCellStyle(srcCell.getCellStyle()); |
| } else { |
| int stHashCode = srcCell.getCellStyle().hashCode(); |
| CellStyle newCellStyle = styleMap.get(stHashCode); |
| if(newCellStyle == null){ |
| newCellStyle = destCell.getSheet().getWorkbook().createCellStyle(); |
| newCellStyle.cloneStyleFrom(srcCell.getCellStyle()); |
| styleMap.put(stHashCode, newCellStyle); |
| } |
| destCell.setCellStyle(newCellStyle); |
| } |
| } |
| switch(srcCell.getCellType()) { |
| case STRING: |
| destCell.setCellValue(srcCell.getStringCellValue()); |
| break; |
| case NUMERIC: |
| destCell.setCellValue(srcCell.getNumericCellValue()); |
| break; |
| case BLANK: |
| destCell.setBlank(); |
| break; |
| case BOOLEAN: |
| destCell.setCellValue(srcCell.getBooleanCellValue()); |
| break; |
| case ERROR: |
| destCell.setCellErrorValue(srcCell.getErrorCellValue()); |
| break; |
| case FORMULA: |
| String oldFormula = srcCell.getCellFormula(); |
| destCell.setCellFormula(oldFormula); |
| break; |
| default: |
| break; |
| } |
| } |
| } |