diff --git a/src/java/org/apache/poi/ss/formula/FormulaShifter.java b/src/java/org/apache/poi/ss/formula/FormulaShifter.java
index 0f9625d623..d4d23135bc 100644
--- a/src/java/org/apache/poi/ss/formula/FormulaShifter.java
+++ b/src/java/org/apache/poi/ss/formula/FormulaShifter.java
@@ -40,10 +40,13 @@ import org.apache.poi.ss.formula.ptg.RefPtgBase;
*/
public final class FormulaShifter {
- private static enum ShiftMode {
+ private enum ShiftMode {
RowMove,
RowCopy,
+ /** @since POI 4.0.0 */
ColumnMove,
+ /** @since POI 4.0.0 */
+ ColumnCopy,
SheetMove,
}
@@ -124,6 +127,14 @@ public final class FormulaShifter {
SpreadsheetVersion version) {
return new FormulaShifter(externSheetIndex, sheetName, firstMovedColumnIndex, lastMovedColumnIndex, numberOfColumnsToMove, ShiftMode.ColumnMove, version);
}
+
+ /**
+ * @since POI 4.0.0
+ */
+ public static FormulaShifter createForColumnCopy(int externSheetIndex, String sheetName, int firstMovedColumnIndex, int lastMovedColumnIndex, int numberOfColumnsToMove,
+ SpreadsheetVersion version) {
+ return new FormulaShifter(externSheetIndex, sheetName, firstMovedColumnIndex, lastMovedColumnIndex, numberOfColumnsToMove, ShiftMode.ColumnCopy, version);
+ }
public static FormulaShifter createForSheetShift(int srcSheetIndex, int dstSheetIndex) {
return new FormulaShifter(srcSheetIndex, dstSheetIndex);
@@ -163,131 +174,32 @@ public final class FormulaShifter {
case RowCopy:
// Covered Scenarios:
// * row copy on same sheet
- // * row copy between different sheetsin the same workbook
+ // * row copy between different sheets in the same workbook
return adjustPtgDueToRowCopy(ptg);
case ColumnMove:
return adjustPtgDueToColumnMove(ptg, currentExternSheetIx);
+ case ColumnCopy:
+ return adjustPtgDueToColumnCopy(ptg);
case SheetMove:
return adjustPtgDueToSheetMove(ptg);
default:
throw new IllegalStateException("Unsupported shift mode: " + _mode);
}
}
- /**
- * @return in-place modified ptg (if row move would cause Ptg to change),
- * deleted ref ptg (if row move causes an error),
- * or null (if no Ptg change is needed)
- */
- private Ptg adjustPtgDueToRowMove(Ptg ptg, int currentExternSheetIx) {
- if(ptg instanceof RefPtg) {
- if (currentExternSheetIx != _externSheetIndex) {
- // local refs on other sheets are unaffected
- return null;
- }
- RefPtg rptg = (RefPtg)ptg;
- return rowMoveRefPtg(rptg);
- }
- if(ptg instanceof Ref3DPtg) {
- Ref3DPtg rptg = (Ref3DPtg)ptg;
- if (_externSheetIndex != rptg.getExternSheetIndex()) {
- // only move 3D refs that refer to the sheet with cells being moved
- // (currentExternSheetIx is irrelevant)
- return null;
- }
- return rowMoveRefPtg(rptg);
- }
- if(ptg instanceof Ref3DPxg) {
- Ref3DPxg rpxg = (Ref3DPxg)ptg;
- if (rpxg.getExternalWorkbookNumber() > 0 ||
- ! _sheetName.equals(rpxg.getSheetName())) {
- // only move 3D refs that refer to the sheet with cells being moved
- return null;
- }
- return rowMoveRefPtg(rpxg);
- }
- if(ptg instanceof Area2DPtgBase) {
- if (currentExternSheetIx != _externSheetIndex) {
- // local refs on other sheets are unaffected
- return ptg;
- }
- return rowMoveAreaPtg((Area2DPtgBase)ptg);
- }
- if(ptg instanceof Area3DPtg) {
- Area3DPtg aptg = (Area3DPtg)ptg;
- if (_externSheetIndex != aptg.getExternSheetIndex()) {
- // only move 3D refs that refer to the sheet with cells being moved
- // (currentExternSheetIx is irrelevant)
- return null;
- }
- return rowMoveAreaPtg(aptg);
- }
- if(ptg instanceof Area3DPxg) {
- Area3DPxg apxg = (Area3DPxg)ptg;
- if (apxg.getExternalWorkbookNumber() > 0 ||
- ! _sheetName.equals(apxg.getSheetName())) {
- // only move 3D refs that refer to the sheet with cells being moved
- return null;
- }
- return rowMoveAreaPtg(apxg);
- }
- return null;
- }
-
- /**
- * Call this on any ptg reference contained in a row of cells that was copied.
- * If the ptg reference is relative, the references will be shifted by the distance
- * that the rows were copied.
- * In the future similar functions could be written due to column copying or
- * individual cell copying. Just make sure to only call adjustPtgDueToRowCopy on
- * formula cells that are copied (unless row shifting, where references outside
- * of the shifted region need to be updated to reflect the shift, a copy is self-contained).
- *
- * @param ptg the ptg to shift
- * @return deleted ref ptg, in-place modified ptg, or null
- * If Ptg would be shifted off the first or last row of a sheet, return deleted ref
- * If Ptg needs to be changed, modifies Ptg in-place
- * If Ptg doesn't need to be changed, returns null
- */
- private Ptg adjustPtgDueToRowCopy(Ptg ptg) {
- if(ptg instanceof RefPtg) {
- RefPtg rptg = (RefPtg)ptg;
- return rowCopyRefPtg(rptg);
- }
- if(ptg instanceof Ref3DPtg) {
- Ref3DPtg rptg = (Ref3DPtg)ptg;
- return rowCopyRefPtg(rptg);
- }
- if(ptg instanceof Ref3DPxg) {
- Ref3DPxg rpxg = (Ref3DPxg)ptg;
- return rowCopyRefPtg(rpxg);
- }
- if(ptg instanceof Area2DPtgBase) {
- return rowCopyAreaPtg((Area2DPtgBase)ptg);
- }
- if(ptg instanceof Area3DPtg) {
- Area3DPtg aptg = (Area3DPtg)ptg;
- return rowCopyAreaPtg(aptg);
- }
- if(ptg instanceof Area3DPxg) {
- Area3DPxg apxg = (Area3DPxg)ptg;
- return rowCopyAreaPtg(apxg);
- }
- return null;
- }
/**
* @return in-place modified ptg (if column move would cause Ptg to change),
* deleted ref ptg (if column move causes an error),
* or null (if no Ptg change is needed)
*/
- private Ptg adjustPtgDueToColumnMove(Ptg ptg, int currentExternSheetIx) {
+ private Ptg adjustPtgDueToMove(Ptg ptg, int currentExternSheetIx, boolean isRowMove) {
if(ptg instanceof RefPtg) {
if (currentExternSheetIx != _externSheetIndex) {
// local refs on other sheets are unaffected
return null;
}
RefPtg rptg = (RefPtg)ptg;
- return columnMoveRefPtg(rptg);
+ return isRowMove ? rowMoveRefPtg(rptg) : columnMoveRefPtg(rptg);
}
if(ptg instanceof Ref3DPtg) {
Ref3DPtg rptg = (Ref3DPtg)ptg;
@@ -296,7 +208,7 @@ public final class FormulaShifter {
// (currentExternSheetIx is irrelevant)
return null;
}
- return columnMoveRefPtg(rptg);
+ return isRowMove ? rowMoveRefPtg(rptg) : columnMoveRefPtg(rptg);
}
if(ptg instanceof Ref3DPxg) {
Ref3DPxg rpxg = (Ref3DPxg)ptg;
@@ -305,14 +217,15 @@ public final class FormulaShifter {
// only move 3D refs that refer to the sheet with cells being moved
return null;
}
- return columnMoveRefPtg(rpxg);
+ return isRowMove ? rowMoveRefPtg(rpxg) : columnMoveRefPtg(rpxg);
}
if(ptg instanceof Area2DPtgBase) {
if (currentExternSheetIx != _externSheetIndex) {
// local refs on other sheets are unaffected
return ptg;
}
- return columnMoveAreaPtg((Area2DPtgBase)ptg);
+ Area2DPtgBase aptg = (Area2DPtgBase) ptg;
+ return isRowMove ? rowMoveAreaPtg(aptg) : columnMoveAreaPtg(aptg);
}
if(ptg instanceof Area3DPtg) {
Area3DPtg aptg = (Area3DPtg)ptg;
@@ -321,7 +234,7 @@ public final class FormulaShifter {
// (currentExternSheetIx is irrelevant)
return null;
}
- return columnMoveAreaPtg(aptg);
+ return isRowMove ? rowMoveAreaPtg(aptg) : columnMoveAreaPtg(aptg);
}
if(ptg instanceof Area3DPxg) {
Area3DPxg apxg = (Area3DPxg)ptg;
@@ -330,11 +243,98 @@ public final class FormulaShifter {
// only move 3D refs that refer to the sheet with cells being moved
return null;
}
- return columnMoveAreaPtg(apxg);
+ return isRowMove ? rowMoveAreaPtg(apxg) : columnMoveAreaPtg(apxg);
}
return null;
}
+ /**
+ * @return in-place modified ptg (if row move would cause Ptg to change),
+ * deleted ref ptg (if row move causes an error),
+ * or null (if no Ptg change is needed)
+ */
+ private Ptg adjustPtgDueToRowMove(Ptg ptg, int currentExternSheetIx) {
+ return adjustPtgDueToMove(ptg, currentExternSheetIx, true);
+ }
+
+ /**
+ * @return in-place modified ptg (if column move would cause Ptg to change),
+ * deleted ref ptg (if column move causes an error),
+ * or null (if no Ptg change is needed)
+ */
+ private Ptg adjustPtgDueToColumnMove(Ptg ptg, int currentExternSheetIx) {
+ return adjustPtgDueToMove(ptg, currentExternSheetIx, false);
+ }
+
+ /**
+ * Call this on any ptg reference contained in a row or column of cells that was copied.
+ * If the ptg reference is relative, the references will be shifted by the distance
+ * that the rows or columns were copied.
+ *
+ * @param ptg the ptg to shift
+ * @return deleted ref ptg, in-place modified ptg, or null
+ * If Ptg would be shifted off the first or last row or columns of a sheet, return deleted ref
+ * If Ptg needs to be changed, modifies Ptg in-place
+ * If Ptg doesn't need to be changed, returns null
+ */
+ private Ptg adjustPtgDueToCopy(Ptg ptg, boolean isRowCopy) {
+ if(ptg instanceof RefPtg) {
+ RefPtg rptg = (RefPtg)ptg;
+ return isRowCopy ? rowCopyRefPtg(rptg) : columnCopyRefPtg(rptg);
+ }
+ if(ptg instanceof Ref3DPtg) {
+ Ref3DPtg rptg = (Ref3DPtg)ptg;
+ return isRowCopy ? rowCopyRefPtg(rptg) : columnCopyRefPtg(rptg);
+ }
+ if(ptg instanceof Ref3DPxg) {
+ Ref3DPxg rpxg = (Ref3DPxg)ptg;
+ return isRowCopy ? rowCopyRefPtg(rpxg) : columnCopyRefPtg(rpxg);
+ }
+ if(ptg instanceof Area2DPtgBase) {
+ Area2DPtgBase aptg = (Area2DPtgBase) ptg;
+ return isRowCopy ? rowCopyAreaPtg(aptg) : columnCopyAreaPtg(aptg);
+ }
+ if(ptg instanceof Area3DPtg) {
+ Area3DPtg aptg = (Area3DPtg)ptg;
+ return isRowCopy ? rowCopyAreaPtg(aptg) : columnCopyAreaPtg(aptg);
+ }
+ if(ptg instanceof Area3DPxg) {
+ Area3DPxg apxg = (Area3DPxg)ptg;
+ return isRowCopy ? rowCopyAreaPtg(apxg) : columnCopyAreaPtg(apxg);
+ }
+ return null;
+ }
+
+ /**
+ * Call this on any ptg reference contained in a row of cells that was copied.
+ * If the ptg reference is relative, the references will be shifted by the distance
+ * that the rows were copied.
+ *
+ * @param ptg the ptg to shift
+ * @return deleted ref ptg, in-place modified ptg, or null
+ * If Ptg would be shifted off the first or last row of a sheet, return deleted ref
+ * If Ptg needs to be changed, modifies Ptg in-place
+ * If Ptg doesn't need to be changed, returns null
+ */
+ private Ptg adjustPtgDueToRowCopy(Ptg ptg) {
+ return adjustPtgDueToCopy(ptg, true);
+ }
+
+ /**
+ * Call this on any ptg reference contained in a column of cells that was copied.
+ * If the ptg reference is relative, the references will be shifted by the distance
+ * that the columns were copied.
+ *
+ * @param ptg the ptg to shift
+ * @return deleted ref ptg, in-place modified ptg, or null
+ * If Ptg would be shifted off the first or last column of a sheet, return deleted ref
+ * If Ptg needs to be changed, modifies Ptg in-place
+ * If Ptg doesn't need to be changed, returns null
+ */
+ private Ptg adjustPtgDueToColumnCopy(Ptg ptg) {
+ return adjustPtgDueToCopy(ptg, false);
+ }
+
private Ptg adjustPtgDueToSheetMove(Ptg ptg) {
if(ptg instanceof Ref3DPtg) {
@@ -747,6 +747,70 @@ public final class FormulaShifter {
throw new IllegalStateException("Situation not covered: (" + _firstMovedIndex + ", " +
_lastMovedIndex + ", " + _amountToMove + ", " + aFirstColumn + ", " + aLastColumn + ")");
}
+
+ /**
+ * Modifies rptg in-place and return a reference to rptg if the cell reference
+ * would move due to a column copy operation
+ * Returns null
or {@link RefErrorPtg} if no change was made
+ *
+ * @param rptg The REF that is copied
+ * @return The Ptg reference if the cell would move due to copy, otherwise null
+ */
+ private Ptg columnCopyRefPtg(RefPtgBase rptg) {
+ final int refColumn = rptg.getColumn();
+ if (rptg.isColRelative()) {
+ // check new location where the ref is located
+ final int destColumnIndex = _firstMovedIndex + _amountToMove;
+ if (destColumnIndex < 0 || _version.getLastColumnIndex() < destColumnIndex) {
+ return createDeletedRef(rptg);
+ }
+
+ // check new location where the ref points to
+ final int newColumnIndex = refColumn + _amountToMove;
+ if(newColumnIndex < 0 || _version.getLastColumnIndex() < newColumnIndex) {
+ return createDeletedRef(rptg);
+ }
+
+ rptg.setColumn(newColumnIndex);
+ return rptg;
+ }
+ return null;
+ }
+
+ /**
+ * Modifies aptg in-place and return a reference to aptg if the first or last column of
+ * of the Area reference would move due to a column copy operation
+ * Returns null
or {@link AreaErrPtg} if no change was made
+ *
+ * @param aptg The Area that is copied
+ * @return null, AreaErrPtg, or modified aptg
+ */
+ private Ptg columnCopyAreaPtg(AreaPtgBase aptg) {
+ boolean changed = false;
+
+ final int aFirstColumn = aptg.getFirstColumn();
+ final int aLastColumn = aptg.getLastColumn();
+
+ if (aptg.isFirstColRelative()) {
+ final int destFirstColumnIndex = aFirstColumn + _amountToMove;
+ if (destFirstColumnIndex < 0 || _version.getLastColumnIndex() < destFirstColumnIndex)
+ return createDeletedRef(aptg);
+ aptg.setFirstColumn(destFirstColumnIndex);
+ changed = true;
+ }
+ if (aptg.isLastColRelative()) {
+ final int destLastColumnIndex = aLastColumn + _amountToMove;
+ if (destLastColumnIndex < 0 || _version.getLastColumnIndex() < destLastColumnIndex)
+ return createDeletedRef(aptg);
+ aptg.setLastColumn(destLastColumnIndex);
+ changed = true;
+ }
+ if (changed) {
+ aptg.sortTopLeftToBottomRight();
+ }
+
+ return changed ? aptg : null;
+ }
private static Ptg createDeletedRef(Ptg ptg) {
if (ptg instanceof RefPtg) {
diff --git a/src/testcases/org/apache/poi/ss/formula/TestFormulaShifter.java b/src/testcases/org/apache/poi/ss/formula/TestFormulaShifter.java
index 25ff2da838..f4ceee0bcc 100644
--- a/src/testcases/org/apache/poi/ss/formula/TestFormulaShifter.java
+++ b/src/testcases/org/apache/poi/ss/formula/TestFormulaShifter.java
@@ -33,8 +33,6 @@ import org.junit.Test;
/**
* Tests for {@link FormulaShifter}.
- *
- * @author Josh Micich
*/
public final class TestFormulaShifter {
// Note - the expected result row coordinates here were determined/verified
@@ -176,6 +174,61 @@ public final class TestFormulaShifter {
confirmAreaRowCopy(aptg, 0, 30, 20, 10, 20, false);
confirmAreaRowCopy(aptg, 15, 25, -15, 10, 20, false);
}
+
+ @Test
+ public void testCopyAreasSourceColumnsRelRel() {
+
+ // all these operations are on an area ref spanning columns 10 to 20
+ final AreaPtg aptg = createAreaPtgColumn(10, 20, true, true);
+
+ confirmAreaColumnCopy(aptg, 0, 30, 20, 30, 40, true);
+ confirmAreaColumnCopy(aptg, 15, 25, -15, -1, -1, true); //DeletedRef
+ }
+
+ @Test
+ public void testCopyAreasSourceColumnsRelAbs() {
+
+ // all these operations are on an area ref spanning columns 10 to 20
+ final AreaPtg aptg = createAreaPtgColumn(10, 20, true, false);
+
+ // Only first column should move
+ confirmAreaColumnCopy(aptg, 0, 30, 20, 20, 30, true);
+ confirmAreaColumnCopy(aptg, 15, 25, -15, -1, -1, true); //DeletedRef
+ }
+
+ @Test
+ public void testCopyAreasSourceColumnsAbsRel() {
+ // aptg is part of a formula in a cell that was just copied to another column
+ // aptg column references should be updated by the difference in columns that the cell was copied
+ // No other references besides the cells that were involved in the copy need to be updated
+ // this makes the column copy significantly different from the column shift, where all references
+ // in the workbook need to track the column shift
+
+ // all these operations are on an area ref spanning columns 10 to 20
+ final AreaPtg aptg = createAreaPtgColumn(10, 20, false, true);
+
+ // Only last column should move
+ confirmAreaColumnCopy(aptg, 0, 30, 20, 10, 40, true);
+ confirmAreaColumnCopy(aptg, 15, 25, -15, 5, 10, true); //sortTopLeftToBottomRight swapped firstColumn and lastColumn because firstColumn is absolute
+ }
+
+ @Test
+ public void testCopyAreasSourceColumnsAbsAbs() {
+ // aptg is part of a formula in a cell that was just copied to another column
+ // aptg column references should be updated by the difference in columns that the cell was copied
+ // No other references besides the cells that were involved in the copy need to be updated
+ // this makes the column copy significantly different from the column shift, where all references
+ // in the workbook need to track the column shift
+
+ // all these operations are on an area ref spanning columns 10 to 20
+ final AreaPtg aptg = createAreaPtgColumn(10, 20, false, false);
+
+ //AbsFirstColumn AbsLastColumn references should't change when copied to a different column
+ confirmAreaColumnCopy(aptg, 0, 30, 20, 10, 20, false);
+ confirmAreaColumnCopy(aptg, 15, 25, -15, 10, 20, false);
+ }
+
+
/**
* Tests what happens to an area ref when some outside rows are moved to overlap
@@ -284,6 +337,29 @@ public final class TestFormulaShifter {
assertEquals("AreaPtg last row", expectedLastRow, copyPtg.getLastRow());
}
+
+ private static void confirmAreaColumnCopy(AreaPtg aptg,
+ int firstColumnCopied, int lastColumnCopied, int columnOffset,
+ int expectedFirstColumn, int expectedLastColumn, boolean expectedChanged) {
+
+ final AreaPtg copyPtg = (AreaPtg) aptg.copy(); // clone so we can re-use aptg in calling method
+ final Ptg[] ptgs = { copyPtg, };
+ final FormulaShifter fs = FormulaShifter.createForColumnCopy(0, null, firstColumnCopied, lastColumnCopied, columnOffset, SpreadsheetVersion.EXCEL2007);
+ final boolean actualChanged = fs.adjustFormula(ptgs, 0);
+
+ // DeletedAreaRef
+ if (expectedFirstColumn < 0 || expectedLastColumn < 0) {
+ assertEquals("Reference should have shifted off worksheet, producing #REF! error: " + ptgs[0],
+ AreaErrPtg.class, ptgs[0].getClass());
+ return;
+ }
+
+ assertEquals("Should this AreaPtg change due to column copy?", expectedChanged, actualChanged);
+ assertEquals("AreaPtgs should be modified in-place when a column containing the AreaPtg is copied", copyPtg, ptgs[0]); // expected to change in place (although this is not a strict requirement)
+ assertEquals("AreaPtg first column", expectedFirstColumn, copyPtg.getFirstColumn());
+ assertEquals("AreaPtg last column", expectedLastColumn, copyPtg.getLastColumn());
+
+ }
private static AreaPtg createAreaPtgRow(int initialAreaFirstRow, int initialAreaLastRow) {
return createAreaPtgRow(initialAreaFirstRow, initialAreaLastRow, false, false);