blob: c6b82a6860ef7a32ca8ef8c8ceca5ed7bf150fa6 [file] [log] [blame]
/*
* ====================================================================
* 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;
}
}
}