You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FormulaShifter.java 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.ss.formula;
  16. import org.apache.poi.ss.SpreadsheetVersion;
  17. import org.apache.poi.ss.formula.ptg.Area2DPtgBase;
  18. import org.apache.poi.ss.formula.ptg.Area3DPtg;
  19. import org.apache.poi.ss.formula.ptg.Area3DPxg;
  20. import org.apache.poi.ss.formula.ptg.AreaErrPtg;
  21. import org.apache.poi.ss.formula.ptg.AreaPtg;
  22. import org.apache.poi.ss.formula.ptg.AreaPtgBase;
  23. import org.apache.poi.ss.formula.ptg.Deleted3DPxg;
  24. import org.apache.poi.ss.formula.ptg.DeletedArea3DPtg;
  25. import org.apache.poi.ss.formula.ptg.DeletedRef3DPtg;
  26. import org.apache.poi.ss.formula.ptg.Ptg;
  27. import org.apache.poi.ss.formula.ptg.Ref3DPtg;
  28. import org.apache.poi.ss.formula.ptg.Ref3DPxg;
  29. import org.apache.poi.ss.formula.ptg.RefErrorPtg;
  30. import org.apache.poi.ss.formula.ptg.RefPtg;
  31. import org.apache.poi.ss.formula.ptg.RefPtgBase;
  32. /**
  33. * Updates Formulas as rows or sheets are shifted
  34. */
  35. public final class FormulaShifter {
  36. private enum ShiftMode {
  37. RowMove,
  38. RowCopy,
  39. /** @since POI 4.0.0 */
  40. ColumnMove,
  41. /** @since POI 4.0.0 */
  42. ColumnCopy,
  43. SheetMove,
  44. }
  45. /**
  46. * Extern sheet index of sheet where moving is occurring,
  47. * used for updating HSSF style 3D references
  48. */
  49. private final int _externSheetIndex;
  50. /**
  51. * Sheet name of the sheet where moving is occurring,
  52. * used for updating XSSF style 3D references on row shifts.
  53. */
  54. private final String _sheetName;
  55. private final int _firstMovedIndex;
  56. private final int _lastMovedIndex;
  57. private final int _amountToMove;
  58. private final int _srcSheetIndex;
  59. private final int _dstSheetIndex;
  60. private final SpreadsheetVersion _version;
  61. private final ShiftMode _mode;
  62. /**
  63. * Create an instance for shifting row.
  64. *
  65. * For example, this will be called on {@link org.apache.poi.hssf.usermodel.HSSFSheet#shiftRows(int, int, int)} }
  66. */
  67. private FormulaShifter(int externSheetIndex, String sheetName, int firstMovedIndex, int lastMovedIndex, int amountToMove,
  68. ShiftMode mode, SpreadsheetVersion version) {
  69. if (amountToMove == 0) {
  70. throw new IllegalArgumentException("amountToMove must not be zero");
  71. }
  72. if (firstMovedIndex > lastMovedIndex) {
  73. throw new IllegalArgumentException("firstMovedIndex, lastMovedIndex out of order");
  74. }
  75. _externSheetIndex = externSheetIndex;
  76. _sheetName = sheetName;
  77. _firstMovedIndex = firstMovedIndex;
  78. _lastMovedIndex = lastMovedIndex;
  79. _amountToMove = amountToMove;
  80. _mode = mode;
  81. _version = version;
  82. _srcSheetIndex = _dstSheetIndex = -1;
  83. }
  84. /**
  85. * Create an instance for shifting sheets.
  86. *
  87. * For example, this will be called on {@link org.apache.poi.hssf.usermodel.HSSFWorkbook#setSheetOrder(String, int)}
  88. */
  89. private FormulaShifter(int srcSheetIndex, int dstSheetIndex) {
  90. _externSheetIndex = _firstMovedIndex = _lastMovedIndex = _amountToMove = -1;
  91. _sheetName = null;
  92. _version = null;
  93. _srcSheetIndex = srcSheetIndex;
  94. _dstSheetIndex = dstSheetIndex;
  95. _mode = ShiftMode.SheetMove;
  96. }
  97. public static FormulaShifter createForRowShift(int externSheetIndex, String sheetName, int firstMovedRowIndex, int lastMovedRowIndex, int numberOfRowsToMove,
  98. SpreadsheetVersion version) {
  99. return new FormulaShifter(externSheetIndex, sheetName, firstMovedRowIndex, lastMovedRowIndex, numberOfRowsToMove, ShiftMode.RowMove, version);
  100. }
  101. public static FormulaShifter createForRowCopy(int externSheetIndex, String sheetName, int firstMovedRowIndex, int lastMovedRowIndex, int numberOfRowsToMove,
  102. SpreadsheetVersion version) {
  103. return new FormulaShifter(externSheetIndex, sheetName, firstMovedRowIndex, lastMovedRowIndex, numberOfRowsToMove, ShiftMode.RowCopy, version);
  104. }
  105. /**
  106. * @since POI 4.0.0
  107. */
  108. public static FormulaShifter createForColumnShift(int externSheetIndex, String sheetName, int firstMovedColumnIndex, int lastMovedColumnIndex, int numberOfColumnsToMove,
  109. SpreadsheetVersion version) {
  110. return new FormulaShifter(externSheetIndex, sheetName, firstMovedColumnIndex, lastMovedColumnIndex, numberOfColumnsToMove, ShiftMode.ColumnMove, version);
  111. }
  112. /**
  113. * @since POI 4.0.0
  114. */
  115. public static FormulaShifter createForColumnCopy(int externSheetIndex, String sheetName, int firstMovedColumnIndex, int lastMovedColumnIndex, int numberOfColumnsToMove,
  116. SpreadsheetVersion version) {
  117. return new FormulaShifter(externSheetIndex, sheetName, firstMovedColumnIndex, lastMovedColumnIndex, numberOfColumnsToMove, ShiftMode.ColumnCopy, version);
  118. }
  119. public static FormulaShifter createForSheetShift(int srcSheetIndex, int dstSheetIndex) {
  120. return new FormulaShifter(srcSheetIndex, dstSheetIndex);
  121. }
  122. @Override
  123. public String toString() {
  124. return getClass().getName() +
  125. " [" +
  126. _firstMovedIndex +
  127. _lastMovedIndex +
  128. _amountToMove +
  129. "]";
  130. }
  131. /**
  132. * @param ptgs - if necessary, will get modified by this method
  133. * @param currentExternSheetIx - the extern sheet index of the sheet that contains the formula being adjusted
  134. * @return <code>true</code> if a change was made to the formula tokens
  135. */
  136. public boolean adjustFormula(Ptg[] ptgs, int currentExternSheetIx) {
  137. boolean refsWereChanged = false;
  138. for(int i=0; i<ptgs.length; i++) {
  139. Ptg newPtg = adjustPtg(ptgs[i], currentExternSheetIx);
  140. if (newPtg != null) {
  141. refsWereChanged = true;
  142. ptgs[i] = newPtg;
  143. }
  144. }
  145. return refsWereChanged;
  146. }
  147. private Ptg adjustPtg(Ptg ptg, int currentExternSheetIx) {
  148. switch(_mode){
  149. case RowMove:
  150. return adjustPtgDueToRowMove(ptg, currentExternSheetIx);
  151. case RowCopy:
  152. // Covered Scenarios:
  153. // * row copy on same sheet
  154. // * row copy between different sheets in the same workbook
  155. return adjustPtgDueToRowCopy(ptg);
  156. case ColumnMove:
  157. return adjustPtgDueToColumnMove(ptg, currentExternSheetIx);
  158. case ColumnCopy:
  159. return adjustPtgDueToColumnCopy(ptg);
  160. case SheetMove:
  161. return adjustPtgDueToSheetMove(ptg);
  162. default:
  163. throw new IllegalStateException("Unsupported shift mode: " + _mode);
  164. }
  165. }
  166. /**
  167. * @return in-place modified ptg (if column move would cause Ptg to change),
  168. * deleted ref ptg (if column move causes an error),
  169. * or null (if no Ptg change is needed)
  170. */
  171. private Ptg adjustPtgDueToMove(Ptg ptg, int currentExternSheetIx, boolean isRowMove) {
  172. if(ptg instanceof RefPtg) {
  173. if (currentExternSheetIx != _externSheetIndex) {
  174. // local refs on other sheets are unaffected
  175. return null;
  176. }
  177. RefPtg rptg = (RefPtg)ptg;
  178. return isRowMove ? rowMoveRefPtg(rptg) : columnMoveRefPtg(rptg);
  179. }
  180. if(ptg instanceof Ref3DPtg) {
  181. Ref3DPtg rptg = (Ref3DPtg)ptg;
  182. if (_externSheetIndex != rptg.getExternSheetIndex()) {
  183. // only move 3D refs that refer to the sheet with cells being moved
  184. // (currentExternSheetIx is irrelevant)
  185. return null;
  186. }
  187. return isRowMove ? rowMoveRefPtg(rptg) : columnMoveRefPtg(rptg);
  188. }
  189. if(ptg instanceof Ref3DPxg) {
  190. Ref3DPxg rpxg = (Ref3DPxg)ptg;
  191. if (rpxg.getExternalWorkbookNumber() > 0 ||
  192. ! _sheetName.equalsIgnoreCase(rpxg.getSheetName())) {
  193. // only move 3D refs that refer to the sheet with cells being moved
  194. return null;
  195. }
  196. return isRowMove ? rowMoveRefPtg(rpxg) : columnMoveRefPtg(rpxg);
  197. }
  198. if(ptg instanceof Area2DPtgBase) {
  199. if (currentExternSheetIx != _externSheetIndex) {
  200. // local refs on other sheets are unaffected
  201. return ptg;
  202. }
  203. Area2DPtgBase aptg = (Area2DPtgBase) ptg;
  204. return isRowMove ? rowMoveAreaPtg(aptg) : columnMoveAreaPtg(aptg);
  205. }
  206. if(ptg instanceof Area3DPtg) {
  207. Area3DPtg aptg = (Area3DPtg)ptg;
  208. if (_externSheetIndex != aptg.getExternSheetIndex()) {
  209. // only move 3D refs that refer to the sheet with cells being moved
  210. // (currentExternSheetIx is irrelevant)
  211. return null;
  212. }
  213. return isRowMove ? rowMoveAreaPtg(aptg) : columnMoveAreaPtg(aptg);
  214. }
  215. if(ptg instanceof Area3DPxg) {
  216. Area3DPxg apxg = (Area3DPxg)ptg;
  217. if (apxg.getExternalWorkbookNumber() > 0 ||
  218. ! _sheetName.equalsIgnoreCase(apxg.getSheetName())) {
  219. // only move 3D refs that refer to the sheet with cells being moved
  220. return null;
  221. }
  222. return isRowMove ? rowMoveAreaPtg(apxg) : columnMoveAreaPtg(apxg);
  223. }
  224. return null;
  225. }
  226. /**
  227. * @return in-place modified ptg (if row move would cause Ptg to change),
  228. * deleted ref ptg (if row move causes an error),
  229. * or null (if no Ptg change is needed)
  230. */
  231. private Ptg adjustPtgDueToRowMove(Ptg ptg, int currentExternSheetIx) {
  232. return adjustPtgDueToMove(ptg, currentExternSheetIx, true);
  233. }
  234. /**
  235. * @return in-place modified ptg (if column move would cause Ptg to change),
  236. * deleted ref ptg (if column move causes an error),
  237. * or null (if no Ptg change is needed)
  238. */
  239. private Ptg adjustPtgDueToColumnMove(Ptg ptg, int currentExternSheetIx) {
  240. return adjustPtgDueToMove(ptg, currentExternSheetIx, false);
  241. }
  242. /**
  243. * Call this on any ptg reference contained in a row or column of cells that was copied.
  244. * If the ptg reference is relative, the references will be shifted by the distance
  245. * that the rows or columns were copied.
  246. *
  247. * @param ptg the ptg to shift
  248. * @return deleted ref ptg, in-place modified ptg, or null
  249. * If Ptg would be shifted off the first or last row or columns of a sheet, return deleted ref
  250. * If Ptg needs to be changed, modifies Ptg in-place
  251. * If Ptg doesn't need to be changed, returns <code>null</code>
  252. */
  253. private Ptg adjustPtgDueToCopy(Ptg ptg, boolean isRowCopy) {
  254. if(ptg instanceof RefPtg) {
  255. RefPtg rptg = (RefPtg)ptg;
  256. return isRowCopy ? rowCopyRefPtg(rptg) : columnCopyRefPtg(rptg);
  257. }
  258. if(ptg instanceof Ref3DPtg) {
  259. Ref3DPtg rptg = (Ref3DPtg)ptg;
  260. return isRowCopy ? rowCopyRefPtg(rptg) : columnCopyRefPtg(rptg);
  261. }
  262. if(ptg instanceof Ref3DPxg) {
  263. Ref3DPxg rpxg = (Ref3DPxg)ptg;
  264. return isRowCopy ? rowCopyRefPtg(rpxg) : columnCopyRefPtg(rpxg);
  265. }
  266. if(ptg instanceof Area2DPtgBase) {
  267. Area2DPtgBase aptg = (Area2DPtgBase) ptg;
  268. return isRowCopy ? rowCopyAreaPtg(aptg) : columnCopyAreaPtg(aptg);
  269. }
  270. if(ptg instanceof Area3DPtg) {
  271. Area3DPtg aptg = (Area3DPtg)ptg;
  272. return isRowCopy ? rowCopyAreaPtg(aptg) : columnCopyAreaPtg(aptg);
  273. }
  274. if(ptg instanceof Area3DPxg) {
  275. Area3DPxg apxg = (Area3DPxg)ptg;
  276. return isRowCopy ? rowCopyAreaPtg(apxg) : columnCopyAreaPtg(apxg);
  277. }
  278. return null;
  279. }
  280. /**
  281. * Call this on any ptg reference contained in a row of cells that was copied.
  282. * If the ptg reference is relative, the references will be shifted by the distance
  283. * that the rows were copied.
  284. *
  285. * @param ptg the ptg to shift
  286. * @return deleted ref ptg, in-place modified ptg, or null
  287. * If Ptg would be shifted off the first or last row of a sheet, return deleted ref
  288. * If Ptg needs to be changed, modifies Ptg in-place
  289. * If Ptg doesn't need to be changed, returns <code>null</code>
  290. */
  291. private Ptg adjustPtgDueToRowCopy(Ptg ptg) {
  292. return adjustPtgDueToCopy(ptg, true);
  293. }
  294. /**
  295. * Call this on any ptg reference contained in a column of cells that was copied.
  296. * If the ptg reference is relative, the references will be shifted by the distance
  297. * that the columns were copied.
  298. *
  299. * @param ptg the ptg to shift
  300. * @return deleted ref ptg, in-place modified ptg, or null
  301. * If Ptg would be shifted off the first or last column of a sheet, return deleted ref
  302. * If Ptg needs to be changed, modifies Ptg in-place
  303. * If Ptg doesn't need to be changed, returns <code>null</code>
  304. */
  305. private Ptg adjustPtgDueToColumnCopy(Ptg ptg) {
  306. return adjustPtgDueToCopy(ptg, false);
  307. }
  308. private Ptg adjustPtgDueToSheetMove(Ptg ptg) {
  309. if(ptg instanceof Ref3DPtg) {
  310. Ref3DPtg ref = (Ref3DPtg)ptg;
  311. int oldSheetIndex = ref.getExternSheetIndex();
  312. // we have to handle a few cases here
  313. // 1. sheet is outside moved sheets, no change necessary
  314. if(oldSheetIndex < _srcSheetIndex &&
  315. oldSheetIndex < _dstSheetIndex) {
  316. return null;
  317. }
  318. if(oldSheetIndex > _srcSheetIndex &&
  319. oldSheetIndex > _dstSheetIndex) {
  320. return null;
  321. }
  322. // 2. ptg refers to the moved sheet
  323. if(oldSheetIndex == _srcSheetIndex) {
  324. ref.setExternSheetIndex(_dstSheetIndex);
  325. return ref;
  326. }
  327. // 3. new index is lower than old one => sheets get moved up
  328. if (_dstSheetIndex < _srcSheetIndex) {
  329. ref.setExternSheetIndex(oldSheetIndex+1);
  330. return ref;
  331. }
  332. // 4. new index is higher than old one => sheets get moved down
  333. if (_dstSheetIndex > _srcSheetIndex) {
  334. ref.setExternSheetIndex(oldSheetIndex-1);
  335. return ref;
  336. }
  337. }
  338. return null;
  339. }
  340. private Ptg rowMoveRefPtg(RefPtgBase rptg) {
  341. int refRow = rptg.getRow();
  342. if (_firstMovedIndex <= refRow && refRow <= _lastMovedIndex) {
  343. // Rows being moved completely enclose the ref.
  344. // - move the area ref along with the rows regardless of destination
  345. rptg.setRow(refRow + _amountToMove);
  346. return rptg;
  347. }
  348. // else rules for adjusting area may also depend on the destination of the moved rows
  349. int destFirstRowIndex = _firstMovedIndex + _amountToMove;
  350. int destLastRowIndex = _lastMovedIndex + _amountToMove;
  351. // ref is outside source rows
  352. // check for clashes with destination
  353. if (destLastRowIndex < refRow || refRow < destFirstRowIndex) {
  354. // destination rows are completely outside ref
  355. return null;
  356. }
  357. if (destFirstRowIndex <= refRow && refRow <= destLastRowIndex) {
  358. // destination rows enclose the area (possibly exactly)
  359. return createDeletedRef(rptg);
  360. }
  361. throw new IllegalStateException("Situation not covered: (" + _firstMovedIndex + ", " +
  362. _lastMovedIndex + ", " + _amountToMove + ", " + refRow + ", " + refRow + ")");
  363. }
  364. private Ptg rowMoveAreaPtg(AreaPtgBase aptg) {
  365. int aFirstRow = aptg.getFirstRow();
  366. int aLastRow = aptg.getLastRow();
  367. if (_firstMovedIndex <= aFirstRow && aLastRow <= _lastMovedIndex) {
  368. // Rows being moved completely enclose the area ref.
  369. // - move the area ref along with the rows regardless of destination
  370. aptg.setFirstRow(aFirstRow + _amountToMove);
  371. aptg.setLastRow(aLastRow + _amountToMove);
  372. return aptg;
  373. }
  374. // else rules for adjusting area may also depend on the destination of the moved rows
  375. int destFirstRowIndex = _firstMovedIndex + _amountToMove;
  376. int destLastRowIndex = _lastMovedIndex + _amountToMove;
  377. if (aFirstRow < _firstMovedIndex && _lastMovedIndex < aLastRow) {
  378. // Rows moved were originally *completely* within the area ref
  379. // If the destination of the rows overlaps either the top
  380. // or bottom of the area ref there will be a change
  381. if (destFirstRowIndex < aFirstRow && aFirstRow <= destLastRowIndex) {
  382. // truncate the top of the area by the moved rows
  383. aptg.setFirstRow(destLastRowIndex+1);
  384. return aptg;
  385. } else if (destFirstRowIndex <= aLastRow && aLastRow < destLastRowIndex) {
  386. // truncate the bottom of the area by the moved rows
  387. aptg.setLastRow(destFirstRowIndex-1);
  388. return aptg;
  389. }
  390. // else - rows have moved completely outside the area ref,
  391. // or still remain completely within the area ref
  392. return null; // - no change to the area
  393. }
  394. if (_firstMovedIndex <= aFirstRow && aFirstRow <= _lastMovedIndex) {
  395. // Rows moved include the first row of the area ref, but not the last row
  396. // btw: (aLastRow > _lastMovedIndex)
  397. if (_amountToMove < 0) {
  398. // simple case - expand area by shifting top upward
  399. aptg.setFirstRow(aFirstRow + _amountToMove);
  400. return aptg;
  401. }
  402. if (destFirstRowIndex > aLastRow) {
  403. // in this case, excel ignores the row move
  404. return null;
  405. }
  406. int newFirstRowIx = aFirstRow + _amountToMove;
  407. if (destLastRowIndex < aLastRow) {
  408. // end of area is preserved (will remain exact same row)
  409. // the top area row is moved simply
  410. aptg.setFirstRow(newFirstRowIx);
  411. return aptg;
  412. }
  413. // else - bottom area row has been replaced - both area top and bottom may move now
  414. int areaRemainingTopRowIx = _lastMovedIndex + 1;
  415. if (destFirstRowIndex > areaRemainingTopRowIx) {
  416. // old top row of area has moved deep within the area, and exposed a new top row
  417. newFirstRowIx = areaRemainingTopRowIx;
  418. }
  419. aptg.setFirstRow(newFirstRowIx);
  420. aptg.setLastRow(Math.max(aLastRow, destLastRowIndex));
  421. return aptg;
  422. }
  423. if (_firstMovedIndex <= aLastRow && aLastRow <= _lastMovedIndex) {
  424. // Rows moved include the last row of the area ref, but not the first
  425. // btw: (aFirstRow < _firstMovedIndex)
  426. if (_amountToMove > 0) {
  427. // simple case - expand area by shifting bottom downward
  428. aptg.setLastRow(aLastRow + _amountToMove);
  429. return aptg;
  430. }
  431. if (destLastRowIndex < aFirstRow) {
  432. // in this case, excel ignores the row move
  433. return null;
  434. }
  435. int newLastRowIx = aLastRow + _amountToMove;
  436. if (destFirstRowIndex > aFirstRow) {
  437. // top of area is preserved (will remain exact same row)
  438. // the bottom area row is moved simply
  439. aptg.setLastRow(newLastRowIx);
  440. return aptg;
  441. }
  442. // else - top area row has been replaced - both area top and bottom may move now
  443. int areaRemainingBottomRowIx = _firstMovedIndex - 1;
  444. if (destLastRowIndex < areaRemainingBottomRowIx) {
  445. // old bottom row of area has moved up deep within the area, and exposed a new bottom row
  446. newLastRowIx = areaRemainingBottomRowIx;
  447. }
  448. aptg.setFirstRow(Math.min(aFirstRow, destFirstRowIndex));
  449. aptg.setLastRow(newLastRowIx);
  450. return aptg;
  451. }
  452. // else source rows include none of the rows of the area ref
  453. // check for clashes with destination
  454. if (destLastRowIndex < aFirstRow || aLastRow < destFirstRowIndex) {
  455. // destination rows are completely outside area ref
  456. return null;
  457. }
  458. if (destFirstRowIndex <= aFirstRow && aLastRow <= destLastRowIndex) {
  459. // destination rows enclose the area (possibly exactly)
  460. return createDeletedRef(aptg);
  461. }
  462. if (aFirstRow <= destFirstRowIndex && destLastRowIndex <= aLastRow) {
  463. // destination rows are within area ref (possibly exact on top or bottom, but not both)
  464. return null; // - no change to area
  465. }
  466. if (destFirstRowIndex < aFirstRow && aFirstRow <= destLastRowIndex) {
  467. // dest rows overlap top of area
  468. // - truncate the top
  469. aptg.setFirstRow(destLastRowIndex+1);
  470. return aptg;
  471. }
  472. if (destFirstRowIndex <= aLastRow && aLastRow < destLastRowIndex) {
  473. // dest rows overlap bottom of area
  474. // - truncate the bottom
  475. aptg.setLastRow(destFirstRowIndex-1);
  476. return aptg;
  477. }
  478. throw new IllegalStateException("Situation not covered: (" + _firstMovedIndex + ", " +
  479. _lastMovedIndex + ", " + _amountToMove + ", " + aFirstRow + ", " + aLastRow + ")");
  480. }
  481. /**
  482. * Modifies rptg in-place and return a reference to rptg if the cell reference
  483. * would move due to a row copy operation
  484. * Returns <code>null</code> or {@link RefErrorPtg} if no change was made
  485. *
  486. * @param rptg The REF that is copied
  487. * @return The Ptg reference if the cell would move due to copy, otherwise null
  488. */
  489. private Ptg rowCopyRefPtg(RefPtgBase rptg) {
  490. final int refRow = rptg.getRow();
  491. if (rptg.isRowRelative()) {
  492. // check new location where the ref is located
  493. final int destRowIndex = _firstMovedIndex + _amountToMove;
  494. if (destRowIndex < 0 || _version.getLastRowIndex() < destRowIndex) {
  495. return createDeletedRef(rptg);
  496. }
  497. // check new location where the ref points to
  498. final int newRowIndex = refRow + _amountToMove;
  499. if(newRowIndex < 0 || _version.getLastRowIndex() < newRowIndex) {
  500. return createDeletedRef(rptg);
  501. }
  502. rptg.setRow(newRowIndex);
  503. return rptg;
  504. }
  505. return null;
  506. }
  507. /**
  508. * Modifies aptg in-place and return a reference to aptg if the first or last row of
  509. * of the Area reference would move due to a row copy operation
  510. * Returns <code>null</code> or {@link AreaErrPtg} if no change was made
  511. *
  512. * @param aptg The Area that is copied
  513. * @return null, AreaErrPtg, or modified aptg
  514. */
  515. private Ptg rowCopyAreaPtg(AreaPtgBase aptg) {
  516. boolean changed = false;
  517. final int aFirstRow = aptg.getFirstRow();
  518. final int aLastRow = aptg.getLastRow();
  519. if (aptg.isFirstRowRelative()) {
  520. final int destFirstRowIndex = aFirstRow + _amountToMove;
  521. if (destFirstRowIndex < 0 || _version.getLastRowIndex() < destFirstRowIndex)
  522. return createDeletedRef(aptg);
  523. aptg.setFirstRow(destFirstRowIndex);
  524. changed = true;
  525. }
  526. if (aptg.isLastRowRelative()) {
  527. final int destLastRowIndex = aLastRow + _amountToMove;
  528. if (destLastRowIndex < 0 || _version.getLastRowIndex() < destLastRowIndex)
  529. return createDeletedRef(aptg);
  530. aptg.setLastRow(destLastRowIndex);
  531. changed = true;
  532. }
  533. if (changed) {
  534. aptg.sortTopLeftToBottomRight();
  535. }
  536. return changed ? aptg : null;
  537. }
  538. private Ptg columnMoveRefPtg(RefPtgBase rptg) {
  539. int refColumn = rptg.getColumn();
  540. if (_firstMovedIndex <= refColumn && refColumn <= _lastMovedIndex) {
  541. // Columns being moved completely enclose the ref.
  542. // - move the area ref along with the columns regardless of destination
  543. rptg.setColumn(refColumn + _amountToMove);
  544. return rptg;
  545. }
  546. // else rules for adjusting area may also depend on the destination of the moved columns
  547. int destFirstColumnIndex = _firstMovedIndex + _amountToMove;
  548. int destLastColumnIndex = _lastMovedIndex + _amountToMove;
  549. // ref is outside source columns
  550. // check for clashes with destination
  551. if (destLastColumnIndex < refColumn || refColumn < destFirstColumnIndex) {
  552. // destination columns are completely outside ref
  553. return null;
  554. }
  555. if (destFirstColumnIndex <= refColumn && refColumn <= destLastColumnIndex) {
  556. // destination columns enclose the area (possibly exactly)
  557. return createDeletedRef(rptg);
  558. }
  559. throw new IllegalStateException("Situation not covered: (" + _firstMovedIndex + ", " +
  560. _lastMovedIndex + ", " + _amountToMove + ", " + refColumn + ", " + refColumn + ")");
  561. }
  562. private Ptg columnMoveAreaPtg(AreaPtgBase aptg) {
  563. int aFirstColumn = aptg.getFirstColumn();
  564. int aLastColumn = aptg.getLastColumn();
  565. if (_firstMovedIndex <= aFirstColumn && aLastColumn <= _lastMovedIndex) {
  566. // Columns being moved completely enclose the area ref.
  567. // - move the area ref along with the columns regardless of destination
  568. aptg.setFirstColumn(aFirstColumn + _amountToMove);
  569. aptg.setLastColumn(aLastColumn + _amountToMove);
  570. return aptg;
  571. }
  572. // else rules for adjusting area may also depend on the destination of the moved columns
  573. int destFirstColumnIndex = _firstMovedIndex + _amountToMove;
  574. int destLastColumnIndex = _lastMovedIndex + _amountToMove;
  575. if (aFirstColumn < _firstMovedIndex && _lastMovedIndex < aLastColumn) {
  576. // Columns moved were originally *completely* within the area ref
  577. // If the destination of the columns overlaps either the top
  578. // or bottom of the area ref there will be a change
  579. if (destFirstColumnIndex < aFirstColumn && aFirstColumn <= destLastColumnIndex) {
  580. // truncate the top of the area by the moved columns
  581. aptg.setFirstColumn(destLastColumnIndex+1);
  582. return aptg;
  583. } else if (destFirstColumnIndex <= aLastColumn && aLastColumn < destLastColumnIndex) {
  584. // truncate the bottom of the area by the moved columns
  585. aptg.setLastColumn(destFirstColumnIndex-1);
  586. return aptg;
  587. }
  588. // else - columns have moved completely outside the area ref,
  589. // or still remain completely within the area ref
  590. return null; // - no change to the area
  591. }
  592. if (_firstMovedIndex <= aFirstColumn && aFirstColumn <= _lastMovedIndex) {
  593. // Columns moved include the first column of the area ref, but not the last column
  594. // btw: (aLastColumn > _lastMovedIndex)
  595. if (_amountToMove < 0) {
  596. // simple case - expand area by shifting top upward
  597. aptg.setFirstColumn(aFirstColumn + _amountToMove);
  598. return aptg;
  599. }
  600. if (destFirstColumnIndex > aLastColumn) {
  601. // in this case, excel ignores the column move
  602. return null;
  603. }
  604. int newFirstColumnIx = aFirstColumn + _amountToMove;
  605. if (destLastColumnIndex < aLastColumn) {
  606. // end of area is preserved (will remain exact same column)
  607. // the top area column is moved simply
  608. aptg.setFirstColumn(newFirstColumnIx);
  609. return aptg;
  610. }
  611. // else - bottom area column has been replaced - both area top and bottom may move now
  612. int areaRemainingTopColumnIx = _lastMovedIndex + 1;
  613. if (destFirstColumnIndex > areaRemainingTopColumnIx) {
  614. // old top column of area has moved deep within the area, and exposed a new top column
  615. newFirstColumnIx = areaRemainingTopColumnIx;
  616. }
  617. aptg.setFirstColumn(newFirstColumnIx);
  618. aptg.setLastColumn(Math.max(aLastColumn, destLastColumnIndex));
  619. return aptg;
  620. }
  621. if (_firstMovedIndex <= aLastColumn && aLastColumn <= _lastMovedIndex) {
  622. // Columns moved include the last column of the area ref, but not the first
  623. // btw: (aFirstColumn < _firstMovedIndex)
  624. if (_amountToMove > 0) {
  625. // simple case - expand area by shifting bottom downward
  626. aptg.setLastColumn(aLastColumn + _amountToMove);
  627. return aptg;
  628. }
  629. if (destLastColumnIndex < aFirstColumn) {
  630. // in this case, excel ignores the column move
  631. return null;
  632. }
  633. int newLastColumnIx = aLastColumn + _amountToMove;
  634. if (destFirstColumnIndex > aFirstColumn) {
  635. // top of area is preserved (will remain exact same column)
  636. // the bottom area column is moved simply
  637. aptg.setLastColumn(newLastColumnIx);
  638. return aptg;
  639. }
  640. // else - top area column has been replaced - both area top and bottom may move now
  641. int areaRemainingBottomColumnIx = _firstMovedIndex - 1;
  642. if (destLastColumnIndex < areaRemainingBottomColumnIx) {
  643. // old bottom column of area has moved up deep within the area, and exposed a new bottom column
  644. newLastColumnIx = areaRemainingBottomColumnIx;
  645. }
  646. aptg.setFirstColumn(Math.min(aFirstColumn, destFirstColumnIndex));
  647. aptg.setLastColumn(newLastColumnIx);
  648. return aptg;
  649. }
  650. // else source columns include none of the columns of the area ref
  651. // check for clashes with destination
  652. if (destLastColumnIndex < aFirstColumn || aLastColumn < destFirstColumnIndex) {
  653. // destination columns are completely outside area ref
  654. return null;
  655. }
  656. if (destFirstColumnIndex <= aFirstColumn && aLastColumn <= destLastColumnIndex) {
  657. // destination columns enclose the area (possibly exactly)
  658. return createDeletedRef(aptg);
  659. }
  660. if (aFirstColumn <= destFirstColumnIndex && destLastColumnIndex <= aLastColumn) {
  661. // destination columns are within area ref (possibly exact on top or bottom, but not both)
  662. return null; // - no change to area
  663. }
  664. if (destFirstColumnIndex < aFirstColumn && aFirstColumn <= destLastColumnIndex) {
  665. // dest columns overlap top of area
  666. // - truncate the top
  667. aptg.setFirstColumn(destLastColumnIndex+1);
  668. return aptg;
  669. }
  670. if (destFirstColumnIndex <= aLastColumn && aLastColumn < destLastColumnIndex) {
  671. // dest columns overlap bottom of area
  672. // - truncate the bottom
  673. aptg.setLastColumn(destFirstColumnIndex-1);
  674. return aptg;
  675. }
  676. throw new IllegalStateException("Situation not covered: (" + _firstMovedIndex + ", " +
  677. _lastMovedIndex + ", " + _amountToMove + ", " + aFirstColumn + ", " + aLastColumn + ")");
  678. }
  679. /**
  680. * Modifies rptg in-place and return a reference to rptg if the cell reference
  681. * would move due to a column copy operation
  682. * Returns <code>null</code> or {@link RefErrorPtg} if no change was made
  683. *
  684. * @param rptg The REF that is copied
  685. * @return The Ptg reference if the cell would move due to copy, otherwise null
  686. */
  687. private Ptg columnCopyRefPtg(RefPtgBase rptg) {
  688. final int refColumn = rptg.getColumn();
  689. if (rptg.isColRelative()) {
  690. // check new location where the ref is located
  691. final int destColumnIndex = _firstMovedIndex + _amountToMove;
  692. if (destColumnIndex < 0 || _version.getLastColumnIndex() < destColumnIndex) {
  693. return createDeletedRef(rptg);
  694. }
  695. // check new location where the ref points to
  696. final int newColumnIndex = refColumn + _amountToMove;
  697. if(newColumnIndex < 0 || _version.getLastColumnIndex() < newColumnIndex) {
  698. return createDeletedRef(rptg);
  699. }
  700. rptg.setColumn(newColumnIndex);
  701. return rptg;
  702. }
  703. return null;
  704. }
  705. /**
  706. * Modifies aptg in-place and return a reference to aptg if the first or last column of
  707. * of the Area reference would move due to a column copy operation
  708. * Returns <code>null</code> or {@link AreaErrPtg} if no change was made
  709. *
  710. * @param aptg The Area that is copied
  711. * @return null, AreaErrPtg, or modified aptg
  712. */
  713. private Ptg columnCopyAreaPtg(AreaPtgBase aptg) {
  714. boolean changed = false;
  715. final int aFirstColumn = aptg.getFirstColumn();
  716. final int aLastColumn = aptg.getLastColumn();
  717. if (aptg.isFirstColRelative()) {
  718. final int destFirstColumnIndex = aFirstColumn + _amountToMove;
  719. if (destFirstColumnIndex < 0 || _version.getLastColumnIndex() < destFirstColumnIndex)
  720. return createDeletedRef(aptg);
  721. aptg.setFirstColumn(destFirstColumnIndex);
  722. changed = true;
  723. }
  724. if (aptg.isLastColRelative()) {
  725. final int destLastColumnIndex = aLastColumn + _amountToMove;
  726. if (destLastColumnIndex < 0 || _version.getLastColumnIndex() < destLastColumnIndex)
  727. return createDeletedRef(aptg);
  728. aptg.setLastColumn(destLastColumnIndex);
  729. changed = true;
  730. }
  731. if (changed) {
  732. aptg.sortTopLeftToBottomRight();
  733. }
  734. return changed ? aptg : null;
  735. }
  736. private static Ptg createDeletedRef(Ptg ptg) {
  737. if (ptg instanceof RefPtg) {
  738. return new RefErrorPtg();
  739. }
  740. if (ptg instanceof Ref3DPtg) {
  741. Ref3DPtg rptg = (Ref3DPtg) ptg;
  742. return new DeletedRef3DPtg(rptg.getExternSheetIndex());
  743. }
  744. if (ptg instanceof AreaPtg) {
  745. return new AreaErrPtg();
  746. }
  747. if (ptg instanceof Area3DPtg) {
  748. Area3DPtg area3DPtg = (Area3DPtg) ptg;
  749. return new DeletedArea3DPtg(area3DPtg.getExternSheetIndex());
  750. }
  751. if (ptg instanceof Ref3DPxg) {
  752. Ref3DPxg pxg = (Ref3DPxg)ptg;
  753. return new Deleted3DPxg(pxg.getExternalWorkbookNumber(), pxg.getSheetName());
  754. }
  755. if (ptg instanceof Area3DPxg) {
  756. Area3DPxg pxg = (Area3DPxg)ptg;
  757. return new Deleted3DPxg(pxg.getExternalWorkbookNumber(), pxg.getSheetName());
  758. }
  759. throw new IllegalArgumentException("Unexpected ref ptg class (" + ptg.getClass().getName() + ")");
  760. }
  761. }