From 51f99aa616de1806e5c92906888347a2b8ce10f7 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Wed, 14 Feb 2018 20:30:01 +0000 Subject: [PATCH] =?UTF-8?q?[github-94]=20Add=20Range=20Copier.=20Thanks=20?= =?UTF-8?q?to=20Dragan=20Jovanovi=C4=87.=20This=20closes=20#94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1824266 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/hssf/usermodel/HSSFRangeCopier.java | 42 +++++ .../apache/poi/ss/usermodel/RangeCopier.java | 159 ++++++++++++++++++ .../poi/xssf/usermodel/XSSFRangeCopier.java | 46 +++++ .../poi/ss/usermodel/TestXSSFRangeCopier.java | 56 ++++++ .../usermodel/TestXSSFSheetShiftColumns.java | 2 +- .../helpers/TestXSSFColumnShifting.java | 1 - .../hssf/usermodel/TestHSSFRangeCopier.java | 42 +++++ .../usermodel/TestHSSFSheetShiftColumns.java | 1 - .../usermodel/BaseTestSheetShiftColumns.java | 4 - .../poi/ss/usermodel/TestRangeCopier.java | 99 +++++++++++ test-data/spreadsheet/tile-range-test.xls | Bin 0 -> 20992 bytes test-data/spreadsheet/tile-range-test.xlsx | Bin 0 -> 6524 bytes 12 files changed, 445 insertions(+), 7 deletions(-) create mode 100644 src/java/org/apache/poi/hssf/usermodel/HSSFRangeCopier.java create mode 100644 src/java/org/apache/poi/ss/usermodel/RangeCopier.java create mode 100644 src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRangeCopier.java create mode 100644 src/ooxml/testcases/org/apache/poi/ss/usermodel/TestXSSFRangeCopier.java create mode 100644 src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRangeCopier.java create mode 100644 src/testcases/org/apache/poi/ss/usermodel/TestRangeCopier.java create mode 100644 test-data/spreadsheet/tile-range-test.xls create mode 100644 test-data/spreadsheet/tile-range-test.xlsx diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFRangeCopier.java b/src/java/org/apache/poi/hssf/usermodel/HSSFRangeCopier.java new file mode 100644 index 000000000..f6d3fb381 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFRangeCopier.java @@ -0,0 +1,42 @@ +/* + * ==================================================================== + * 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.hssf.usermodel; + +import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; +import org.apache.poi.ss.formula.ptg.Ptg; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.RangeCopier; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.util.Beta; + +@Beta +public class HSSFRangeCopier extends RangeCopier { + public HSSFRangeCopier(Sheet sourceSheet, Sheet destSheet) { + super(sourceSheet, destSheet); + } + + protected void adjustCellReferencesInsideFormula(Cell cell, Sheet destSheet, int deltaX, int deltaY) { + FormulaRecordAggregate fra = (FormulaRecordAggregate)((HSSFCell)cell).getCellValueRecord(); + int destSheetIndex = destSheet.getWorkbook().getSheetIndex(destSheet); + Ptg[] ptgs = fra.getFormulaTokens(); + if(adjustInBothDirections(ptgs, destSheetIndex, deltaX, deltaY)) + fra.setParsedExpression(ptgs); + } +} diff --git a/src/java/org/apache/poi/ss/usermodel/RangeCopier.java b/src/java/org/apache/poi/ss/usermodel/RangeCopier.java new file mode 100644 index 000000000..ea3bc4e22 --- /dev/null +++ b/src/java/org/apache/poi/ss/usermodel/RangeCopier.java @@ -0,0 +1,159 @@ +/* + * ==================================================================== + * 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.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) { + Sheet sourceCopy = sourceSheet.getWorkbook().cloneSheet(sourceSheet.getWorkbook().getSheetIndex(sourceSheet)); + 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); + nextCellIndexInRowToCopy += widthToCopyMinus1 + 1; + } while (nextCellIndexInRowToCopy <= tileDestRange.getLastColumn()); + nextRowIndexToCopy += heightToCopyMinus1 + 1; + } while (nextRowIndexToCopy <= tileDestRange.getLastRow()); + + int tempCopyIndex = sourceSheet.getWorkbook().getSheetIndex(sourceCopy); + sourceSheet.getWorkbook().removeSheetAt(tempCopyIndex); + } + + private void copyRange(CellRangeAddress sourceRange, int deltaX, int deltaY, Sheet sourceClone) { //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++) { + Row sourceRow = sourceClone.getRow(rowNo); // copy from source copy, original source might be overridden in process! + 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.setCellType(sourceCell.getCellType()); + else newCell = destRow.createCell(columnIndex + deltaX, sourceCell.getCellType()); + + cloneCellContent(sourceCell, newCell, null); + 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 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.setCellType(CellType.BLANK); + 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; + } + } +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRangeCopier.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRangeCopier.java new file mode 100644 index 000000000..926984382 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRangeCopier.java @@ -0,0 +1,46 @@ +/* + * ==================================================================== + * 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.xssf.usermodel; + +import org.apache.poi.ss.formula.FormulaParser; +import org.apache.poi.ss.formula.FormulaRenderer; +import org.apache.poi.ss.formula.FormulaType; +import org.apache.poi.ss.formula.ptg.Ptg; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.RangeCopier; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.util.Beta; + +@Beta +public class XSSFRangeCopier extends RangeCopier { + + public XSSFRangeCopier(Sheet sourceSheet, Sheet destSheet){ + super(sourceSheet, destSheet); + } + + protected void adjustCellReferencesInsideFormula(Cell cell, Sheet destSheet, int deltaX, int deltaY){ + XSSFWorkbook hostWorkbook = (XSSFWorkbook) destSheet.getWorkbook(); + XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(hostWorkbook); + Ptg[] ptgs = FormulaParser.parse(cell.getCellFormula(), fpb, FormulaType.CELL, 0); + int destSheetIndex = hostWorkbook.getSheetIndex(destSheet); + if(adjustInBothDirections(ptgs, destSheetIndex, deltaX, deltaY)) + cell.setCellFormula(FormulaRenderer.toFormulaString(fpb, ptgs)); + } +} diff --git a/src/ooxml/testcases/org/apache/poi/ss/usermodel/TestXSSFRangeCopier.java b/src/ooxml/testcases/org/apache/poi/ss/usermodel/TestXSSFRangeCopier.java new file mode 100644 index 000000000..405390fbd --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/ss/usermodel/TestXSSFRangeCopier.java @@ -0,0 +1,56 @@ +/* + * ==================================================================== + * 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 static org.junit.Assert.assertEquals; + +import org.apache.poi.xssf.XSSFITestDataProvider; +import org.apache.poi.xssf.XSSFTestDataSamples; +import org.apache.poi.xssf.usermodel.XSSFRangeCopier; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.Before; +import org.junit.Test; + +public class TestXSSFRangeCopier extends TestRangeCopier { + public TestXSSFRangeCopier() { + super(); + workbook = new XSSFWorkbook(); + testDataProvider = XSSFITestDataProvider.instance; + } + + @Before + public void init() { + workbook = XSSFTestDataSamples.openSampleWorkbook("tile-range-test.xlsx"); + initSheets(); + rangeCopier = new XSSFRangeCopier(sheet1, sheet1); + transSheetRangeCopier = new XSSFRangeCopier(sheet1, sheet2); + } + + @Test // XSSF only. HSSF version wouldn't be so simple. And also this test is contained in following, more complex tests, so it's not really important. + public void copyRow() { + Row existingRow = sheet1.getRow(4); + XSSFRow newRow = (XSSFRow)sheet1.getRow(5); + CellCopyPolicy policy = new CellCopyPolicy(); + newRow.copyRowFrom(existingRow, policy); + assertEquals("$C2+B$2", newRow.getCell(1).getCellFormula()); + } + +} diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetShiftColumns.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetShiftColumns.java index f912b4390..45bb31e3a 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetShiftColumns.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetShiftColumns.java @@ -40,4 +40,4 @@ public class TestXSSFSheetShiftColumns extends BaseTestSheetShiftColumns { return XSSFTestDataSamples.writeOutAndReadBack(wb); } -} +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/helpers/TestXSSFColumnShifting.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/helpers/TestXSSFColumnShifting.java index e976294f1..67767aea5 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/helpers/TestXSSFColumnShifting.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/helpers/TestXSSFColumnShifting.java @@ -31,5 +31,4 @@ public class TestXSSFColumnShifting extends BaseTestColumnShifting { protected void initColumnShifter(){ columnShifter = new XSSFColumnShifter((XSSFSheet)sheet1); } - } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRangeCopier.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRangeCopier.java new file mode 100644 index 000000000..8f6b67851 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRangeCopier.java @@ -0,0 +1,42 @@ +/* + * ==================================================================== + * 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.hssf.usermodel; + +import org.apache.poi.hssf.HSSFITestDataProvider; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.ss.usermodel.TestRangeCopier; +import org.junit.Before; + +public class TestHSSFRangeCopier extends TestRangeCopier { + + public TestHSSFRangeCopier() { + super(); + workbook = new HSSFWorkbook(); + testDataProvider = HSSFITestDataProvider.instance; + } + + @Before + public void init() { + workbook = HSSFTestDataSamples.openSampleWorkbook("tile-range-test.xls"); + initSheets(); + rangeCopier = new HSSFRangeCopier(sheet1, sheet1); + transSheetRangeCopier = new HSSFRangeCopier(sheet1, sheet2); + } +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheetShiftColumns.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheetShiftColumns.java index b9afa51a1..f5be8470d 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheetShiftColumns.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheetShiftColumns.java @@ -79,7 +79,6 @@ public class TestHSSFSheetShiftColumns extends BaseTestSheetShiftColumns { // so that original method from BaseTestSheetShiftColumns can be executed. // After removing, you can re-add 'final' keyword to specification of original method. } - @Override @Ignore("see ") @Test diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetShiftColumns.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetShiftColumns.java index 884e3a811..3f146a8fb 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetShiftColumns.java +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetShiftColumns.java @@ -94,7 +94,6 @@ public abstract class BaseTestSheetShiftColumns { style.setVerticalAlignment(VerticalAlignment.BOTTOM); return style; } - @Test public void testShiftOneColumnRight() { sheet1.shiftColumns(1, 2, 1); @@ -271,7 +270,6 @@ public abstract class BaseTestSheetShiftColumns { // A1:A5 should be moved to B1:B5 // B1:B3 will be removed sheet.shiftColumns(0, 0, 1); - assertEquals(1, sheet.getNumMergedRegions()); assertEquals(CellRangeAddress.valueOf("B1:B5"), sheet.getMergedRegion(0)); @@ -398,6 +396,4 @@ public abstract class BaseTestSheetShiftColumns { assertEquals("X", cell.getStringCellValue()); wb.close(); } - - } diff --git a/src/testcases/org/apache/poi/ss/usermodel/TestRangeCopier.java b/src/testcases/org/apache/poi/ss/usermodel/TestRangeCopier.java new file mode 100644 index 000000000..def64a32c --- /dev/null +++ b/src/testcases/org/apache/poi/ss/usermodel/TestRangeCopier.java @@ -0,0 +1,99 @@ +/* + * ==================================================================== + * 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 static org.junit.Assert.assertEquals; + +import org.junit.Ignore; +import org.junit.Test; +import org.apache.poi.ss.ITestDataProvider; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellReference; + +@Ignore +public abstract class TestRangeCopier { + protected Sheet sheet1; + protected Sheet sheet2; + protected Workbook workbook; + protected RangeCopier rangeCopier; + protected RangeCopier transSheetRangeCopier; + protected ITestDataProvider testDataProvider; + + protected void initSheets() { + sheet1 = workbook.getSheet("sheet1"); + sheet2 = workbook.getSheet("sheet2"); + } + + @Test + public void copySheetRangeWithoutFormulas() { + CellRangeAddress rangeToCopy = CellRangeAddress.valueOf("B1:C2"); //2x2 + CellRangeAddress destRange = CellRangeAddress.valueOf("C2:D3"); //2x2 + rangeCopier.copyRange(rangeToCopy, destRange); + assertEquals("1.1", sheet1.getRow(2).getCell(2).toString()); + assertEquals("2.1", sheet1.getRow(2).getCell(3).toString()); + } + + @Test + public void tileTheRangeAway() { + CellRangeAddress tileRange = CellRangeAddress.valueOf("C4:D5"); + CellRangeAddress destRange = CellRangeAddress.valueOf("F4:K5"); + rangeCopier.copyRange(tileRange, destRange); + assertEquals("1.3", getCellContent(sheet1, "H4")); + assertEquals("1.3", getCellContent(sheet1, "J4")); + assertEquals("$C1+G$2", getCellContent(sheet1, "G5")); + assertEquals("SUM(G3:I3)", getCellContent(sheet1, "H5")); + assertEquals("$C1+I$2", getCellContent(sheet1, "I5")); + assertEquals("", getCellContent(sheet1, "L5")); //out of borders + assertEquals("", getCellContent(sheet1, "G7")); //out of borders + } + + @Test + public void tileTheRangeOver() { + CellRangeAddress tileRange = CellRangeAddress.valueOf("C4:D5"); + CellRangeAddress destRange = CellRangeAddress.valueOf("A4:C5"); + rangeCopier.copyRange(tileRange, destRange); + assertEquals("1.3", getCellContent(sheet1, "A4")); + assertEquals("$C1+B$2", getCellContent(sheet1, "B5")); + assertEquals("SUM(B3:D3)", getCellContent(sheet1, "C5")); + } + + @Test + public void copyRangeToOtherSheet() { + Sheet destSheet = sheet2; + CellRangeAddress tileRange = CellRangeAddress.valueOf("C4:D5"); // on sheet1 + CellRangeAddress destRange = CellRangeAddress.valueOf("F4:J6"); // on sheet2 + transSheetRangeCopier.copyRange(tileRange, destRange); + assertEquals("1.3", getCellContent(destSheet, "H4")); + assertEquals("1.3", getCellContent(destSheet, "J4")); + assertEquals("$C1+G$2", getCellContent(destSheet, "G5")); + assertEquals("SUM(G3:I3)", getCellContent(destSheet, "H5")); + assertEquals("$C1+I$2", getCellContent(destSheet, "I5")); + } + + protected static String getCellContent(Sheet sheet, String coordinates) { + try { + CellReference p = new CellReference(coordinates); + return sheet.getRow(p.getRow()).getCell(p.getCol()).toString(); + } + catch (NullPointerException e) { // row or cell does not exist + return ""; + } + } +} diff --git a/test-data/spreadsheet/tile-range-test.xls b/test-data/spreadsheet/tile-range-test.xls new file mode 100644 index 0000000000000000000000000000000000000000..231fe65e34d55ecc82e26b4bf3a5953ab9d2533d GIT binary patch literal 20992 zcmeHv2Ut|e(soY(1`r7X0=hCp0Rc$@DhlQV5(ITc7ZCWK3Pe?i?I5Z_Y!6W#BA5Wtgs26v14M0zIuLasc7&)0Q6HiK#7+=9L;L|^ z7l?)s`4EjD8bdUJXbKT+31io$`+txs|B<5lkr?<(fVvn&qTw@+B!lML9(2KCL3$g0LKvkJq$H3C zxR0ijO1)Y%@y9Z?l|R;#23{awTNCLIF|Zs+YfJmbblCp!^S|MrsR1wpy1_y{{yU)& zS{z5E$(Y0dkaB|@44L|PI=>`N9v#6D*i zMC^A8AY$KB0ulS4a){X9+<=I}Yd~j#eG2wBUJ)U2G4U~B34HIcu!s-=KOkNZ2XuUo z_HFM%-(z3YlGZCYBqBN?CVn!1P*R8>65e@0OdXr;<=1o%A5`$bYv}25E?+LQ@IEUt zrP-(9tA91f(jS(KUHxbEM%zvrt9}FhS%1zO2s8x-4HbDD=%`23=}?H+_o8l5cc@$J zrzb*0olS;_{c|it)TJ^H-YP1P*Wu+#8mSzj2o3)MXmvmL6s6mTkY+rjhJL+8U1F-` z|A#_`7IAOZjdVF!#OXUJ2&9l-78dv*Q4L7vf?i2BhJ5SkLf%5vc9fGJ zQ?Q^Ne@_PG?_i)WkR@dot|RpD9SO^r?Bg55pxYXv39%zV#FF%ehHgQaZ^px$F5Esa zDd;RKTOjd+5mYFdAP2x5W)wfP2CxO*^0?FQX&Ht=!HnfY$d^PMEP_}O2f_+!F0mCf zGp0Ty-=cO}QlLz|L2n|BVSZy`1CiDxzVV>$yKrqGttBBr7FK){YZDW@Aj{r(Ki-0R z8c#nN+Za=Hk08q~++?VkUb4j#SDT~=l0vqWSh}e0(6U;K>rA?VpJ@@-){Y@D5L$$G zcNd9$ziCwaQOF0+APOaXq@zov=pWifM^`8G#Q?%aUiUjP&be&}HG%^5FV}q<+>>IIZclJgw=p zJgw=pJgw=!%fOck-g9Ivw{D7qly0r|tl~g*8Whs1yKf!r~r2Km*`EgB) zqF=?KaVks|XnB+*=r}Z{={TNlO3#N|n0_$m48Ax&q0%?&hqfPFCu_o)2)D2lM&sbv zoT4|kQ`&xT{zK6>YHI4L>Vlr>c86ni5k9SFTK_m|q38@f<2;_CGvw43(XYY~lluEmIYi1skvV5RW-F)~2+ z0!d|MVDp-gaKYp}4}> zTqr-jD0_2BD7*X?5+ECiqI3}9Ie8#3A8x7%WVlfvLlWW3jbf6lWK?Gl$f`tDCxW=U zPnRJ{d1f<+Qd1G<%$Xw;jij*J5ddj8lEUGMvKS4ADnW~QY!xvYB!}?J{S+QksGdL< z7s_3_P<3^6t%P!ScW)&W(wIUajVTn;D4C=R1;aowp}wTZP`5Op{_ix!Y>mz!6x|w8 z`>li$Rf{l{)vT%3>b7bw8Mc26QcXHLJG&V~WCQSi{~J|G6&FtKD%FOoKp!b@UU zGOigYz)BHqh(cj`a|V&H2M!z%lBQCKG(*vvN+B}XW*Qic$Y7gkU^K10Xm<%=vAQKR zn8FKS2qH!66YOq#kN6ZKx7TEZt9|?g!0kvOEPb63{K~v%i!tT8M1Us>Ck1Yl|doysyY-FR! zWaH49jjn7K@%IEAnfLYI_Ye5m~5O|vvH8ah913A zrBN1yJ$!RZIvZVN<0RkG41tYP8!WBdv-tXJ>1;YO*+{W86FF?eEv?+M{$j3lHhN4p zQY@{j95&*XR_<9-QzM;?K9h|UOY0_wjku+idkUXsN@ruhWFy7Wy31iBZfWJ71!d2q zv+2ZSBgN7j<**UAv~tg~`vub3bVfGL@-2-IY@FL*X%3DXPCupDphJVu?9+Z=vXNqG zrgGSbTbhI8hy4qrv+2TQBgN7zOxxYmZkTmr#AGAI(wyY55w|o4$K91_(n`|~mPlaS)!c9PWAvLp zz$qSCeI+4*8>*#)nzVp|MKM8*7@*=-M98ZYd+DI2EudgOOi(ihsJP7#YO+oB>XKS+ z2LpoU;)As?K`j}e;uZo;sf0+E)Vu{0Y=a4E!vGbx2SNtMl}ZQg+5!rezy!5tfQr{X z9M#AjA`O%WBRZIb_YV%u;PsTOc~&POhxCV4#n9#+SOM2QU?C9oi1)Z!*yKqxU{wv4 zqXz^>h9pJ?Cq%?VXYl4q0^pi*``e_xc&2~%W2s3zmNl_Fyz+?cN)}znz zfdmB8TBj;PS_?*6ML1dAl9o>bp(mJtS*ScVsa(io&d9?R@*qzloCt}d#=ap>61HoVLz0&c>5iv^1|`Kt21f@c#Ka}@{RK%08N4BqQ0yK;4LBg01P6hz z?xU&t$Ag>6pmTo^CkgBUSK36ikCokIUdm85Se_XY69b0YTN0OLAmoxE)X`5#Q^kdE z5f(5LVYP>SX_!F_6$FPyM2GWXuD}qKqXC*73Id_+M}VN=ARs+QpiBT7%s~GsG#r|u z5=cfn!d2|{Fv$=D2j9m8*OUTI`#%}rLic^;FQ6a@?kElpK9C8nB?X-JgEGMBp#hr$ zcM=B&U&wF>oDNdJ(I3(^rX;SGRj|C?1DCOeMaL$BDI`i(8Cx65gQrwu!4|R7TN;`W zmiI-KhI;`2%jT~E10?WEo)OVg1))vte*{AqJ@89-I4T-iKOR95_W+z`Yi#yKBJYDM z*@~dJcEknbCnE)k330*TUKoy<)fpV~NC3u?#(_ls|<%5m9+J!p2FH!GlSq z5@?RblCrW0upHTZv=^bUxhoS_Y#t+H5(F8%Ns?LED$qPIO9IG%ON%(kp*X|3w8(?u znsSR@rJqC|jD8Y%F#2f~Tb1-Au!iNGm_SwV3b8*z6HvGXD z5}=+?U#*Rsg~shhLBu3rg)^2KIQkR>Dx9utE@eX!%L%A=k=vk4D|$kU(jwqc zxM=iHv^RB^6qKzBj(H4lhy%M7IVovg6#HyS!)d&+Bu#8hBpphp34i_oGKnhfC=fZB zy7Pjvqwm90YB;?&NHn|3QuZiVF zdA>n^jA=1~rH3X)MI{Si6vQJNyV0khLwGtQL@bvMq{P5p%j?{Jt=J1B`i$UkwV@zL zHX<8pW4*t@U1#9b0X_o<#6%~+np=>6a;zYJf^AY%`r<}3V81!jjpuO^+jg}Udto&9a(VHm@?GN%%lf^tH-EPK^4{Z> zDi^+#`Hp#B@#l!?`2mx%6cxS4J~uHh&dbmL<4^mEe%p5XKi}NbO>b4?3X>hTl}vXR z^i3;?TfRwo;^E4zM{Sn&4(VgB74DW?TQuvm?sU%HXY<{*3>}zP{b+a0(mhTK>|9l? zv%>bQKUlPQKoS4;sQ1fk1JB3#UV6HFhS42sS>HWQHL7)mDjF=^rm3=MeSqZ#xhN{5R z8bNV_$oTHIcwMlf0+iJ5mp2QG=U1ipUOuB|Hobdx`hf1+Q^*60Ig^b$)pUAU<9$w1 zO&@`LGpPR^NkVd0J(GhSd)?4s>6YYqh+ z_o>?M_NRTP?4gBrYqZoC-}FB2m)AeE>Vn4Q;e%?NCYjF3Svf7}y5YLb`uCkZuYAh*$~SwTnv-w+WDqynWwmF-<4Ie0b$>LfIDMC3&($uKhmL%{)&NCm zUC^n?Yjx*C0hdC=ow9_;uYK~~m>0bGUUwh}ri)%w?YCfBI zae9XNOy!5g1<5U&#Kd*C4RZpv7!-Lqc`VA{<&jsB!zgko_USn-q)HC1fg|P+)7Vp^bdqee;ZH@&t zM!p6{fB3l-9v*um$nEo!?p?OW>p$WK^0zFFcPb3AJox^gqvmNltrcSzjjE6SGijdb z?YcjIEUi+j^{V|bzvg3w)2=wD%R6Zq zbehmp#i-CY>vqxLgvc{)n#Yco9==>?p`~>~uVz9+_?AA&vld+%VPv)9yg{65!eJqzy1nJ=p}uM!!MtuZk@kr{U#UuXx@IiLe|%x> zX0@=CoVhL^KB-4;PI_^2?)9wxCugPxS+n0x;^g;JxZAtr*z2s0N7Rlh>Ldw?d+o@u z*IAHgUB-tO58k($x~D@3y>Vz2;n;O}{t#LBxY) znPGb9vGbX!+WmEv7H8;my*A>&5~Ftxs~uQp?))^P>eZQ?FEc)_`J314-1+p;)90E! zoH+Dm$ddF`o@uXw=8mpOJ$^Aa$Ft1TI=p(gfmVf2k-gdM`j>{n_s{MH3LC!$&949A zeRjv!q~M3)u6MpI%KC85Jhl4u?Y9js_Zr?C-b)d_GHm>KI6v@%x9hmfw<9tp2B$2E z+2>+C_w1p4B)|ULu<>P!olJe&6$ScNoJur*d(mcF)DcV9Q>DdEO6;Oaaz1}ulzVaA zvy6iBZ^k1HN>s~gSHByrblfLizsrg8D_&n8ySYq@(I|@E(Ay)zr1ZTTXNp;r)qFjd z@>yQKR#l2QW5>PdJ$>P{i!bhKUVi&hLAhs#NSoXlrR$9^zsMMIV2bJGk+u56__j69 z9HXmh_r@PTwDZ@(+@BZaZy0(*Z*igCoFx%wmV5i!`d;2yr)73Ep?-wvBWIJ}0=uj~ zax`7zx!TV1^+U!kcFr8r`Px)&gTnq1GjDXSb&fDTYC0yWSCDa;=i^@|DA*4_;QUv{ zj^CH0*yiyEt^BzDf^)sX)Ylq`>eI+wSKZf#xh|(uTr_7XlulV+Fhb*_*QLNopS2GT zZnQm|uzY`*{{05UOJx}2OBKlJ8I+naW%wkq-5=FU+!t}nQpecSJw zU+v}z9$rgz?RGu>aK?>4@0eUVwfSD*Vh`uNN0M{j>MQY~=5(Pw7$i$jMxHT*hW zKP%L|j2CiXr)Gn$iDGqDT*ho;rJO^0TgUu8Y@gntvG)Ei_WJxVmbJ6mcka#$^F~jN zoN2o_Rl{w#(NYtg9OJ7235j;@f*mV*9$d+)?B}n)f7yqjH-An@o{~6d&nAuMi!=up z^;llHxvGa}sl{o*^gUIh%Ij~8T`}D#YF*Cwt(`N?)w{%hb!3H~2E1drIk;^-=a%V-C^{d>_uk2-y_SyQ=Lk1=n9Z^`+{m8bD zfi|<}?9T}8xnQRL5SMF9pMF!Dr~O_rEpFmK?(x5;sl*KC-VM1nu<+u^;?w?@^FAB< zKJ&3po2 zQ!!3&+?bT?zYF@lwW~0_@uE-qxpsFOpBA`gYv|wJy-)k*)acyktgZ9=YW`$sKfyTq zx?9z;l;XnZ0M4YSi_YA_s@#J9dI_nH!B;K!&N0(^+W+*ib~b5ws(t4j8eFZjbno7` z&bhWd*M*;&#`ENMW^Z|+U0OK$;|~4f`Bo+S$Hgx#yQHCJY+0A(RzK|YsFAa^O7rXX zb#O?!VtQezkMZFz8s`f$-hTV6c=uSAPQd24@yB}k#ogvq5BsS5PIFhUkS~?HdW~kM zj6Ce-&+hR|JI3e8gs{%rhnDJHO!Y0>lB22Eb;X{$f9$(C=#*h{pErF47eDw`N9g_% z;y%jobM^0DCLPn9tv5c2*zw$2aYer~FnJrzuocK=U!DD+w?qEGr@fBqp$Nn zJFUC4NqvfA|D~m-OJjRow@IzPcV%R!ZEMzyt`0~Tb2Xs1a_{{XFB*bpIQhBkw+ZN$ zQ(ryV>XS;f7q%?iS>YEl`*@e}2ekmA=#`tF z+e^RnNAJMPR$hknhn|Es%nf?e`0-Tuv+YW=tCw4U8~wQSKx0Y1yUxkA1uC91Z(L@7 zjpydqul-^ZSY`cs%Df+cK6bZM?dP8=pSLgWx%#KWHTplVnjQWyIVJpc&pS5@a)#&s z+TmQc*?kLALjsCyM&x&K&wX|!_ei7B$}7Da2N*9KSgBcGe0G=n`Y)S&=Nza#a`5?n zN0o08;ZK$3-<+hIbf)Uz{HO_2_xFCTl;>_ZeO6aque^H!Pt}KPnp^YI>-MQo*BM<6 z|G1^H`DXr#TX|h!k||{zrdE}{r4YtnonVxPhXcfiGW6h+qKJ*gng*j@DyQwV7krvG zgBKHXrAT9?`JKg;xmP(09`{glb~p9?>y=ubHP7~!;1ih<$9~&%p+ktnUHi#<40yes zubH;~RMe5b(mechM=rV9J9PiVd1)~&uXMs!j{R`<*{lIrLLvO4hp#tXXGGizjK!tS0(vebx&PHnTd2At+rf(fu1PF`U#gAL0G>eRg^T=DCqJo0V# zyUSjaeB6U5x)nw5MqTGolvHZQNn@J9G7lU=WX**)r(ia!gzH`A1I+vB05~kTnh)@S z=>^~1XFdh6fV!Fk0LMt9xx&e z<5t)$V?j|^)ZaXKXU!6E5Ph;p0B~0!DY%9M+4k`8{Qf+cT;um2=!0zZc}gA|k2m5y z7r`N|IzbvfFlneXCJomjm^4aF3_S1@66uPmZ#m=dZy>5Vv>&|Uw>H>hHY{!-e|5N; zdT>ByEUP+MpNZj}JN=%G?>(k?6gPc`7E?QU;PodD7QtL>_!H$~d0;M-7IRJXP=sX! zTmcomzi<*)L^+MBWFVv|83>cWOEI;s;6)+SmW)G~Z#0?Zy>IIklK7fTE>%i$>~PW z4w6G?Xy!>u*r{mY4PY*GE5!HL!CPqpfE#>WqBDHs9}O3|g5gr1l;L7R;z3>MiyXrR zrwme{;VLV)$#BJB|9kt7(trXu;TYIu!QBG-MCHHsdo-?KVd%m{p)G|+A378b6auSh zA3PSeoFVGKsEp#^+ zd?2DHeGU;{zk`Ufuz-l}Ul~4eJcPauZ3qYU$h!x8VyD`t=^67P$9}P~qXlvCaG-^P z!D0@K2^l2_NsNm~NTwjs^ACm1kGM$KWsV;(IXF685K1w@{gDx)ynW(gLK8z0`X@$2 zh5{JH7srCQD;*yY>mM^PI6**3fgSj$kl;wcfVh}gN**lNsL8=`0^r)LTvP`NYwv7p z=V)iwr0fmI3 literal 0 HcmV?d00001 diff --git a/test-data/spreadsheet/tile-range-test.xlsx b/test-data/spreadsheet/tile-range-test.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..fd6057b5cd165ad7a73b7f032cf0ce2208baf0a4 GIT binary patch literal 6524 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 literal 0 HcmV?d00001