diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index f26223742..923209302 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -67,6 +67,7 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + 45780 - Fixed HSSFSheet.shiftRows to also update Area refs 45804 - Update HSMF to handle Outlook 3.0 msg files, which have a different string chunk type Expose the name of Named Cell Styles via HSSFCellStyle (normally held on the parent style though) 45978 - Fixed IOOBE in Ref3DPtg.toFormulaString() due eager initialisation of SheetReferences diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 8bf188faa..42633cbdf 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -64,6 +64,7 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + 45780 - Fixed HSSFSheet.shiftRows to also update Area refs 45804 - Update HSMF to handle Outlook 3.0 msg files, which have a different string chunk type Expose the name of Named Cell Styles via HSSFCellStyle (normally held on the parent style though) 45978 - Fixed IOOBE in Ref3DPtg.toFormulaString() due eager initialisation of SheetReferences diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index 8bbc30435..164399939 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -62,7 +62,6 @@ import org.apache.poi.hssf.record.aggregates.CFRecordsAggregate; import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; import org.apache.poi.hssf.record.aggregates.ConditionalFormattingTable; import org.apache.poi.hssf.record.aggregates.DataValidityTable; -import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; import org.apache.poi.hssf.record.aggregates.MergedCellsTable; import org.apache.poi.hssf.record.aggregates.PageSettingsBlock; import org.apache.poi.hssf.record.aggregates.RecordAggregate; @@ -127,7 +126,8 @@ public final class Sheet implements Model { /*package*/ColumnInfoRecordsAggregate _columnInfos; /** the DimensionsRecord is always present */ private DimensionsRecord _dimensions; - protected RowRecordsAggregate _rowsAggregate = null; + /** always present */ + protected RowRecordsAggregate _rowsAggregate; private DataValidityTable _dataValidityTable= null; private ConditionalFormattingTable condFormatting; @@ -329,10 +329,13 @@ public final class Sheet implements Model { if (retval.windowTwo == null) { throw new RuntimeException("WINDOW2 was not found"); } + if (retval._rowsAggregate == null) { + retval._rowsAggregate = new RowRecordsAggregate(); + records.add(retval.dimsloc + 1, retval._rowsAggregate); + } // put merged cells table in the right place (regardless of where the first MergedCellsRecord was found */ RecordOrderer.addNewSheetRecord(records, retval._mergedCellsTable); retval.records = records; - retval.checkRows(); if (log.check( POILogger.DEBUG )) log.log(POILogger.DEBUG, "sheet createSheet (existing file) exited"); return retval; @@ -441,6 +444,8 @@ public final class Sheet implements Model { retval._dimensions = createDimensions(); records.add(retval._dimensions); retval.dimsloc = records.size()-1; + retval._rowsAggregate = new RowRecordsAggregate(); + records.add(retval._rowsAggregate); // 'Sheet View Settings' records.add(retval.windowTwo = retval.createWindowTwo()); retval.selection = createSelection(); @@ -456,14 +461,10 @@ public final class Sheet implements Model { return retval; } - private void checkRows() - { - if (_rowsAggregate == null) - { - _rowsAggregate = new RowRecordsAggregate(); - records.add(dimsloc + 1, _rowsAggregate); - } + public RowRecordsAggregate getRowsAggregate() { + return _rowsAggregate; } + private MergedCellsTable getMergedRecords() { // always present return _mergedCellsTable; @@ -623,13 +624,6 @@ public final class Sheet implements Model { return result; } - /** - * Create a row record. (does not add it to the records contained in this sheet) - */ - private static RowRecord createRow(int row) { - return RowRecordsAggregate.createRow( row ); - } - /** * Adds a value record to the sheet's contained binary records * (i.e. LabelSSTRecord or NumberRecord). @@ -714,7 +708,6 @@ public final class Sheet implements Model { public void addRow(RowRecord row) { - checkRows(); if (log.check( POILogger.DEBUG )) log.log(POILogger.DEBUG, "addRow "); DimensionsRecord d = _dimensions; @@ -748,7 +741,6 @@ public final class Sheet implements Model { * @param row the row record to remove */ public void removeRow(RowRecord row) { - checkRows(); _rowsAggregate.removeRow(row); } @@ -1295,7 +1287,7 @@ public final class Sheet implements Model { } /** - * Returns the first occurance of a record matching a particular sid. + * Returns the first occurrence of a record matching a particular sid. */ public Record findFirstRecordBySid(short sid) @@ -1781,13 +1773,12 @@ public final class Sheet implements Model { public void groupRowRange(int fromRow, int toRow, boolean indent) { - checkRows(); for (int rowNum = fromRow; rowNum <= toRow; rowNum++) { RowRecord row = getRow( rowNum ); if (row == null) { - row = createRow( rowNum ); + row = RowRecordsAggregate.createRow(rowNum); addRow( row ); } int level = row.getOutlineLevel(); @@ -1817,17 +1808,6 @@ public final class Sheet implements Model { guts.setLeftRowGutter( (short) ( 29 + (12 * (maxLevel)) ) ); } - public void setRowGroupCollapsed( int row, boolean collapse ) - { - if (collapse) - { - _rowsAggregate.collapseRow( row ); - } - else - { - _rowsAggregate.expandRow( row ); - } - } public DataValidityTable getOrCreateDataValidityTable() { if (_dataValidityTable == null) { DataValidityTable result = new DataValidityTable(); @@ -1836,8 +1816,4 @@ public final class Sheet implements Model { } return _dataValidityTable; } - - public FormulaRecordAggregate createFormula(int row, int col) { - return _rowsAggregate.createFormula(row, col); - } } diff --git a/src/java/org/apache/poi/hssf/record/NameRecord.java b/src/java/org/apache/poi/hssf/record/NameRecord.java index 0716c448d..215c20fee 100644 --- a/src/java/org/apache/poi/hssf/record/NameRecord.java +++ b/src/java/org/apache/poi/hssf/record/NameRecord.java @@ -537,9 +537,7 @@ public final class NameRecord extends Record { temp.add(ptg); } } else { - Ptg ptg = new Ref3DPtg(); - ((Ref3DPtg) ptg).setExternSheetIndex(externSheetIndex); - ((Ref3DPtg) ptg).setArea(ref); + Ref3DPtg ptg = new Ref3DPtg(ra.getFromCell(), externSheetIndex); temp.add(ptg); } Ptg[] ptgs = new Ptg[temp.size()]; diff --git a/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java index c5bbc3119..c7610de1e 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java @@ -35,6 +35,7 @@ import org.apache.poi.hssf.record.RowRecord; import org.apache.poi.hssf.record.SharedFormulaRecord; import org.apache.poi.hssf.record.TableRecord; import org.apache.poi.hssf.record.UnknownRecord; +import org.apache.poi.hssf.record.formula.FormulaShifter; /** * @@ -507,4 +508,7 @@ public final class RowRecordsAggregate extends RecordAggregate { fr.setColumn((short) col); return new FormulaRecordAggregate(fr, null, _sharedValueManager); } + public void updateFormulasAfterRowShift(FormulaShifter formulaShifter, int currentExternSheetIndex) { + _valuesAgg.updateFormulasAfterRowShift(formulaShifter, currentExternSheetIndex); + } } diff --git a/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java index 4552be797..a9b1de253 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java @@ -28,6 +28,8 @@ import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RecordBase; import org.apache.poi.hssf.record.StringRecord; import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; +import org.apache.poi.hssf.record.formula.FormulaShifter; +import org.apache.poi.hssf.record.formula.Ptg; /** * @@ -225,6 +227,25 @@ public final class ValueRecordsAggregate { } } + public void updateFormulasAfterRowShift(FormulaShifter shifter, int currentExternSheetIndex) { + for (int i = 0; i < records.length; i++) { + CellValueRecordInterface[] rowCells = records[i]; + if (rowCells == null) { + continue; + } + for (int j = 0; j < rowCells.length; j++) { + CellValueRecordInterface cell = rowCells[j]; + if (cell instanceof FormulaRecordAggregate) { + FormulaRecord fr = ((FormulaRecordAggregate)cell).getFormulaRecord(); + Ptg[] ptgs = fr.getParsedExpression(); // needs clone() inside this getter? + if (shifter.adjustFormula(ptgs, currentExternSheetIndex)) { + fr.setParsedExpression(ptgs); + } + } + } + } + } + public CellValueRecordInterface[] getValueRecords() { List temp = new ArrayList(); diff --git a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java index 2183cc2dd..0c97d03d2 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java @@ -90,24 +90,6 @@ public final class Area3DPtg extends AreaPtgBase { * formulas. The sheet name will get properly delimited if required. */ public String toFormulaString(Workbook book) { - // First do the sheet name - StringBuffer retval = new StringBuffer(); - String sheetName = book.findSheetNameFromExternSheet(field_1_index_extern_sheet); - if(sheetName != null) { - if(sheetName.length() == 0) { - // What excel does if sheet has been deleted - sheetName = "#REF"; - retval.append(sheetName); - } else { - // Normal - SheetNameFormatter.appendFormat(retval, sheetName); - } - retval.append( '!' ); - } - - // Now the normal area bit - retval.append(formatReferenceAsString()); - - return retval.toString(); + return ExternSheetNameResolver.prependSheetName(book, field_1_index_extern_sheet, formatReferenceAsString()); } } diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaErrPtg.java b/src/java/org/apache/poi/hssf/record/formula/AreaErrPtg.java index f1af9b68d..1387e7685 100644 --- a/src/java/org/apache/poi/hssf/record/formula/AreaErrPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/AreaErrPtg.java @@ -18,6 +18,7 @@ package org.apache.poi.hssf.record.formula; import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.ss.usermodel.ErrorConstants; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.util.LittleEndian; @@ -27,23 +28,31 @@ import org.apache.poi.util.LittleEndian; * @author Daniel Noll (daniel at nuix dot com dot au) */ public final class AreaErrPtg extends OperandPtg { - public final static byte sid = 0x2b; + public final static byte sid = 0x2B; + private final int unused1; + private final int unused2; - public AreaErrPtg(RecordInputStream in) { - // 8 bytes unused: - in.readInt(); - in.readInt(); - } + public AreaErrPtg() { + unused1 = 0; + unused2 = 0; + } - public void writeBytes(byte [] array, int offset) { - array[offset] = (byte) (sid + getPtgClass()); - LittleEndian.putInt(array, offset+1, 0); - LittleEndian.putInt(array, offset+5, 0); - } + public AreaErrPtg(RecordInputStream in) { + // 8 bytes unused: + unused1 = in.readInt(); + unused2 = in.readInt(); + } - public String toFormulaString(Workbook book) { - return "#REF!"; - } + public void writeBytes(byte[] array, int offset) { + LittleEndian.putByte(array, offset + 0, sid + getPtgClass()); + LittleEndian.putInt(array, offset + 1, unused1); + LittleEndian.putInt(array, offset + 5, unused2); + } + + + public String toFormulaString(Workbook book) { + return ErrorConstants.getText(ErrorConstants.ERROR_REF); + } public byte getDefaultOperandClass() { return Ptg.CLASS_REF; diff --git a/src/java/org/apache/poi/hssf/record/formula/DeletedArea3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/DeletedArea3DPtg.java index a1c5b3db5..92506dfbe 100644 --- a/src/java/org/apache/poi/hssf/record/formula/DeletedArea3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/DeletedArea3DPtg.java @@ -35,13 +35,21 @@ public final class DeletedArea3DPtg extends OperandPtg { private final int unused1; private final int unused2; - public DeletedArea3DPtg( RecordInputStream in) { + public DeletedArea3DPtg(int externSheetIndex) { + field_1_index_extern_sheet = externSheetIndex; + unused1 = 0; + unused2 = 0; + } + + public DeletedArea3DPtg(RecordInputStream in) { field_1_index_extern_sheet = in.readUShort(); unused1 = in.readInt(); unused2 = in.readInt(); } + public String toFormulaString(Workbook book) { - return ErrorConstants.getText(ErrorConstants.ERROR_REF); + return ExternSheetNameResolver.prependSheetName(book, field_1_index_extern_sheet, + ErrorConstants.getText(ErrorConstants.ERROR_REF)); } public byte getDefaultOperandClass() { return Ptg.CLASS_REF; diff --git a/src/java/org/apache/poi/hssf/record/formula/DeletedRef3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/DeletedRef3DPtg.java index 9312b2d76..900a48fe8 100644 --- a/src/java/org/apache/poi/hssf/record/formula/DeletedRef3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/DeletedRef3DPtg.java @@ -41,8 +41,14 @@ public final class DeletedRef3DPtg extends OperandPtg { unused1 = in.readInt(); } + public DeletedRef3DPtg(int externSheetIndex) { + field_1_index_extern_sheet = externSheetIndex; + unused1 = 0; + } + public String toFormulaString(Workbook book) { - return ErrorConstants.getText(ErrorConstants.ERROR_REF); + return ExternSheetNameResolver.prependSheetName(book, field_1_index_extern_sheet, + ErrorConstants.getText(ErrorConstants.ERROR_REF)); } public byte getDefaultOperandClass() { return Ptg.CLASS_REF; diff --git a/src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java b/src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java new file mode 100644 index 000000000..b928931b0 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java @@ -0,0 +1,44 @@ +/* ==================================================================== + 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.record.formula; + +import org.apache.poi.ss.usermodel.Workbook; + +/** + * @author Josh Micich + */ +final class ExternSheetNameResolver { + + private ExternSheetNameResolver() { + // no instances of this class + } + + public static String prependSheetName(Workbook book, int field_1_index_extern_sheet, String cellRefText) { + String sheetName = book.findSheetNameFromExternSheet(field_1_index_extern_sheet); + StringBuffer sb = new StringBuffer(sheetName.length() + cellRefText.length() + 4); + if (sheetName.length() < 1) { + // What excel does if sheet has been deleted + sb.append("#REF"); // note - '!' added just once below + } else { + SheetNameFormatter.appendFormat(sb, sheetName); + } + sb.append('!'); + sb.append(cellRefText); + return sb.toString(); + } +} diff --git a/src/java/org/apache/poi/hssf/record/formula/FormulaShifter.java b/src/java/org/apache/poi/hssf/record/formula/FormulaShifter.java new file mode 100644 index 000000000..87683d64e --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/FormulaShifter.java @@ -0,0 +1,294 @@ +/* ==================================================================== + 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.record.formula; + + +/** + * @author Josh Micich + */ +public final class FormulaShifter { + + /** + * Extern sheet index of sheet where moving is occurring + */ + private final int _externSheetIndex; + private final int _firstMovedIndex; + private final int _lastMovedIndex; + private final int _amountToMove; + + private FormulaShifter(int externSheetIndex, int firstMovedIndex, int lastMovedIndex, int amountToMove) { + if (amountToMove == 0) { + throw new IllegalArgumentException("amountToMove must not be zero"); + } + if (firstMovedIndex > lastMovedIndex) { + throw new IllegalArgumentException("firstMovedIndex, lastMovedIndex out of order"); + } + _externSheetIndex = externSheetIndex; + _firstMovedIndex = firstMovedIndex; + _lastMovedIndex = lastMovedIndex; + _amountToMove = amountToMove; + } + + public static FormulaShifter createForRowShift(int externSheetIndex, int firstMovedRowIndex, int lastMovedRowIndex, int numberOfRowsToMove) { + return new FormulaShifter(externSheetIndex, firstMovedRowIndex, lastMovedRowIndex, numberOfRowsToMove); + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + + sb.append(getClass().getName()); + sb.append(" ["); + sb.append(_firstMovedIndex); + sb.append(_lastMovedIndex); + sb.append(_amountToMove); + return sb.toString(); + } + + /** + * @param ptgs - if necessary, will get modified by this method + * @param currentExternSheetIx - the extern sheet index of the sheet that contains the formula being adjusted + * @return true if a change was made to the formula tokens + */ + public boolean adjustFormula(Ptg[] ptgs, int currentExternSheetIx) { + boolean refsWereChanged = false; + for(int i=0; itrue if this Ptg needed to be changed + */ + 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 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); + } + return null; + } + + private Ptg rowMoveRefPtg(RefPtgBase rptg) { + int refRow = rptg.getRow(); + if (_firstMovedIndex <= refRow && refRow <= _lastMovedIndex) { + // Rows being moved completely enclose the ref. + // - move the area ref along with the rows regardless of destination + rptg.setRow(refRow + _amountToMove); + return rptg; + } + // else rules for adjusting area may also depend on the destination of the moved rows + + int destFirstRowIndex = _firstMovedIndex + _amountToMove; + int destLastRowIndex = _lastMovedIndex + _amountToMove; + + // ref is outside source rows + // check for clashes with destination + + if (destLastRowIndex < refRow || refRow < destFirstRowIndex) { + // destination rows are completely outside ref + return null; + } + + if (destFirstRowIndex <= refRow && refRow <= destLastRowIndex) { + // destination rows enclose the area (possibly exactly) + return createDeletedRef(rptg); + } + throw new IllegalStateException("Situation not covered: (" + _firstMovedIndex + ", " + + _lastMovedIndex + ", " + _amountToMove + ", " + refRow + ", " + refRow + ")"); + } + + private Ptg rowMoveAreaPtg(AreaPtgBase aptg) { + int aFirstRow = aptg.getFirstRow(); + int aLastRow = aptg.getLastRow(); + if (_firstMovedIndex <= aFirstRow && aLastRow <= _lastMovedIndex) { + // Rows being moved completely enclose the area ref. + // - move the area ref along with the rows regardless of destination + aptg.setFirstRow(aFirstRow + _amountToMove); + aptg.setLastRow(aLastRow + _amountToMove); + return aptg; + } + // else rules for adjusting area may also depend on the destination of the moved rows + + int destFirstRowIndex = _firstMovedIndex + _amountToMove; + int destLastRowIndex = _lastMovedIndex + _amountToMove; + + if (aFirstRow < _firstMovedIndex && _lastMovedIndex < aLastRow) { + // Rows moved were originally *completely* within the area ref + + // If the destination of the rows overlaps either the top + // or bottom of the area ref there will be a change + if (destFirstRowIndex < aFirstRow && aFirstRow <= destLastRowIndex) { + // truncate the top of the area by the moved rows + aptg.setFirstRow(destLastRowIndex+1); + return aptg; + } else if (destFirstRowIndex <= aLastRow && aLastRow < destLastRowIndex) { + // truncate the bottom of the area by the moved rows + aptg.setLastRow(destFirstRowIndex-1); + return aptg; + } + // else - rows have moved completely outside the area ref, + // or still remain completely within the area ref + return null; // - no change to the area + } + if (_firstMovedIndex <= aFirstRow && aFirstRow <= _lastMovedIndex) { + // Rows moved include the first row of the area ref, but not the last row + // btw: (aLastRow > _lastMovedIndex) + if (_amountToMove < 0) { + // simple case - expand area by shifting top upward + aptg.setFirstRow(aFirstRow + _amountToMove); + return aptg; + } + if (destFirstRowIndex > aLastRow) { + // in this case, excel ignores the row move + return null; + } + int newFirstRowIx = aFirstRow + _amountToMove; + if (destLastRowIndex < aLastRow) { + // end of area is preserved (will remain exact same row) + // the top area row is moved simply + aptg.setFirstRow(newFirstRowIx); + return aptg; + } + // else - bottom area row has been replaced - both area top and bottom may move now + int areaRemainingTopRowIx = _lastMovedIndex + 1; + if (destFirstRowIndex > areaRemainingTopRowIx) { + // old top row of area has moved deep within the area, and exposed a new top row + newFirstRowIx = areaRemainingTopRowIx; + } + aptg.setFirstRow(newFirstRowIx); + aptg.setLastRow(Math.max(aLastRow, destLastRowIndex)); + return aptg; + } + if (_firstMovedIndex <= aLastRow && aLastRow <= _lastMovedIndex) { + // Rows moved include the last row of the area ref, but not the first + // btw: (aFirstRow < _firstMovedIndex) + if (_amountToMove > 0) { + // simple case - expand area by shifting bottom downward + aptg.setLastRow(aLastRow + _amountToMove); + return aptg; + } + if (destLastRowIndex < aFirstRow) { + // in this case, excel ignores the row move + return null; + } + int newLastRowIx = aLastRow + _amountToMove; + if (destFirstRowIndex > aFirstRow) { + // top of area is preserved (will remain exact same row) + // the bottom area row is moved simply + aptg.setLastRow(newLastRowIx); + return aptg; + } + // else - top area row has been replaced - both area top and bottom may move now + int areaRemainingBottomRowIx = _firstMovedIndex - 1; + if (destLastRowIndex < areaRemainingBottomRowIx) { + // old bottom row of area has moved up deep within the area, and exposed a new bottom row + newLastRowIx = areaRemainingBottomRowIx; + } + aptg.setFirstRow(Math.min(aFirstRow, destFirstRowIndex)); + aptg.setLastRow(newLastRowIx); + return aptg; + } + // else source rows include none of the rows of the area ref + // check for clashes with destination + + if (destLastRowIndex < aFirstRow || aLastRow < destFirstRowIndex) { + // destination rows are completely outside area ref + return null; + } + + if (destFirstRowIndex <= aFirstRow && aLastRow <= destLastRowIndex) { + // destination rows enclose the area (possibly exactly) + return createDeletedRef(aptg); + } + + if (aFirstRow <= destFirstRowIndex && destLastRowIndex <= aLastRow) { + // destination rows are within area ref (possibly exact on top or bottom, but not both) + return null; // - no change to area + } + + if (destFirstRowIndex < aFirstRow && aFirstRow <= destLastRowIndex) { + // dest rows overlap top of area + // - truncate the top + aptg.setFirstRow(destLastRowIndex+1); + return aptg; + } + if (destFirstRowIndex < aLastRow && aLastRow <= destLastRowIndex) { + // dest rows overlap bottom of area + // - truncate the bottom + aptg.setLastRow(destFirstRowIndex-1); + return aptg; + } + throw new IllegalStateException("Situation not covered: (" + _firstMovedIndex + ", " + + _lastMovedIndex + ", " + _amountToMove + ", " + aFirstRow + ", " + aLastRow + ")"); + } + + private static Ptg createDeletedRef(Ptg ptg) { + if (ptg instanceof RefPtg) { + return new RefErrorPtg(); + } + if (ptg instanceof Ref3DPtg) { + Ref3DPtg rptg = (Ref3DPtg) ptg; + return new DeletedRef3DPtg(rptg.getExternSheetIndex()); + } + if (ptg instanceof AreaPtg) { + return new AreaErrPtg(); + } + if (ptg instanceof Area3DPtg) { + Area3DPtg area3DPtg = (Area3DPtg) ptg; + return new DeletedArea3DPtg(area3DPtg.getExternSheetIndex()); + } + + throw new IllegalArgumentException("Unexpected ref ptg class (" + ptg.getClass().getName() + ")"); + } +} diff --git a/src/java/org/apache/poi/hssf/record/formula/Ref2DPtgBase.java b/src/java/org/apache/poi/hssf/record/formula/Ref2DPtgBase.java new file mode 100644 index 000000000..2fff41d6b --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/Ref2DPtgBase.java @@ -0,0 +1,68 @@ +/* ==================================================================== + 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.record.formula; + +import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.util.LittleEndian; + +/** + * @author Josh Micich + */ +abstract class Ref2DPtgBase extends RefPtgBase { + private final static int SIZE = 5; + + /** + * Takes in a String representation of a cell reference and fills out the + * numeric fields. + */ + protected Ref2DPtgBase(String cellref) { + super(cellref); + } + + protected Ref2DPtgBase(int row, int column, boolean isRowRelative, boolean isColumnRelative) { + setRow(row); + setColumn(column); + setRowRelative(isRowRelative); + setColRelative(isColumnRelative); + } + + protected Ref2DPtgBase(RecordInputStream in) { + readCoordinates(in); + } + public final void writeBytes(byte [] array, int offset) { + LittleEndian.putByte(array, offset+0, getSid() + getPtgClass()); + writeCoordinates(array, offset+1); + } + public final String toFormulaString(Workbook book) { + return formatReferenceAsString(); + } + + protected abstract byte getSid(); + public final int getSize() { + return SIZE; + } + public final String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()); + sb.append(" ["); + sb.append(formatReferenceAsString()); + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java index e72cbbc30..308e9f0b5 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java @@ -18,12 +18,8 @@ package org.apache.poi.hssf.record.formula; import org.apache.poi.hssf.record.RecordInputStream; -import org.apache.poi.hssf.util.RangeAddress; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellReference; -import org.apache.poi.ss.util.SheetReferences; -import org.apache.poi.util.BitField; -import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.LittleEndian; /** @@ -34,49 +30,36 @@ import org.apache.poi.util.LittleEndian; * @author Jason Height (jheight at chariot dot net dot au) * @version 1.0-pre */ -public final class Ref3DPtg extends OperandPtg { +public final class Ref3DPtg extends RefPtgBase { public final static byte sid = 0x3a; - private static final BitField rowRelative = BitFieldFactory.getInstance(0x8000); - private static final BitField colRelative = BitFieldFactory.getInstance(0x4000); - private final static int SIZE = 7; // 6 + 1 for Ptg private int field_1_index_extern_sheet; - /** The row index - zero based unsigned 16 bit value */ - private int field_2_row; - /** Field 2 - * - lower 8 bits is the zero based unsigned byte column index - * - bit 16 - isRowRelative - * - bit 15 - isColumnRelative - */ - private int field_3_column; /** Creates new AreaPtg */ public Ref3DPtg() {} public Ref3DPtg(RecordInputStream in) { field_1_index_extern_sheet = in.readShort(); - field_2_row = in.readShort(); - field_3_column = in.readShort(); + readCoordinates(in); } public Ref3DPtg(String cellref, short externIdx ) { CellReference c= new CellReference(cellref); setRow(c.getRow()); - setColumn((short)c.getCol()); + setColumn(c.getCol()); setColRelative(!c.isColAbsolute()); setRowRelative(!c.isRowAbsolute()); setExternSheetIndex(externIdx); } public String toString() { - CellReference cr = new CellReference(getRow(), getColumn(), !isRowRelative(),!isColRelative()); StringBuffer sb = new StringBuffer(); sb.append(getClass().getName()); sb.append(" ["); sb.append("sheetIx=").append(getExternSheetIndex()); sb.append(" ! "); - sb.append(cr.formatAsString()); + sb.append(formatReferenceAsString()); sb.append("]"); return sb.toString(); } @@ -84,8 +67,7 @@ public final class Ref3DPtg extends OperandPtg { public void writeBytes(byte [] array, int offset) { array[ 0 + offset ] = (byte) (sid + getPtgClass()); LittleEndian.putShort(array, 1 + offset , getExternSheetIndex()); - LittleEndian.putShort(array, 3 + offset , (short)getRow()); - LittleEndian.putShort(array, 5 + offset , (short)getColumnRaw()); + writeCoordinates(array, offset+3); } public int getSize() { @@ -100,83 +82,11 @@ public final class Ref3DPtg extends OperandPtg { field_1_index_extern_sheet = index; } - public int getRow() { - return field_2_row; - } - - public void setRow(int row) { - field_2_row = row; - } - - public int getColumn() { - return field_3_column & 0xFF; - } - - public int getColumnRaw() { - return field_3_column; - } - - public boolean isRowRelative() - { - return rowRelative.isSet(field_3_column); - } - - public void setRowRelative(boolean rel) { - field_3_column=rowRelative.setBoolean(field_3_column,rel); - } - - public boolean isColRelative() - { - return colRelative.isSet(field_3_column); - } - - public void setColRelative(boolean rel) { - field_3_column=colRelative.setBoolean(field_3_column,rel); - } - public void setColumn(short column) { - field_3_column &= 0xFF00; - field_3_column |= column & 0xFF; - } - - public void setColumnRaw(short column) { - field_3_column = column; - } - - /* public String getArea(){ - RangeAddress ra = new RangeAddress(""); - - String result = (ra.numTo26Sys(getColumn()) + (getRow() + 1)); - - return result; - }*/ - - public void setArea(String ref){ - RangeAddress ra = new RangeAddress(ref); - - String from = ra.getFromCell(); - - setColumn((short) (ra.getXPosition(from) -1)); - setRow((short) (ra.getYPosition(from) -1)); - - } - /** * @return text representation of this cell reference that can be used in text * formulas. The sheet name will get properly delimited if required. */ - public String toFormulaString(Workbook book) - { - StringBuffer retval = new StringBuffer(); - String sheetName = book.findSheetNameFromExternSheet(field_1_index_extern_sheet); - if(sheetName != null) { - SheetNameFormatter.appendFormat(retval, sheetName); - retval.append( '!' ); - } - retval.append((new CellReference(getRow(),getColumn(),!isRowRelative(),!isColRelative())).formatAsString()); - return retval.toString(); + public String toFormulaString(Workbook book) { + return ExternSheetNameResolver.prependSheetName(book, field_1_index_extern_sheet, formatReferenceAsString()); } - - public byte getDefaultOperandClass() { - return Ptg.CLASS_REF; - } } diff --git a/src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java b/src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java index 6adbe43c5..a3030e1bd 100755 --- a/src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java @@ -18,6 +18,7 @@ package org.apache.poi.hssf.record.formula; import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.ss.usermodel.ErrorConstants; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.util.LittleEndian; @@ -28,48 +29,32 @@ import org.apache.poi.util.LittleEndian; public final class RefErrorPtg extends OperandPtg { private final static int SIZE = 5; - public final static byte sid = 0x2a; + public final static byte sid = 0x2A; private int field_1_reserved; - public RefErrorPtg(RecordInputStream in) - { + public RefErrorPtg() { + field_1_reserved = 0; + } + public RefErrorPtg(RecordInputStream in) { field_1_reserved = in.readInt(); - } - public String toString() - { - StringBuffer buffer = new StringBuffer("[RefError]\n"); - - buffer.append("reserved = ").append(getReserved()).append("\n"); - return buffer.toString(); + public String toString() { + return getClass().getName(); } - public void writeBytes(byte [] array, int offset) - { - array[offset] = (byte) (sid + getPtgClass()); + public void writeBytes(byte [] array, int offset) { + LittleEndian.putByte(array, offset+0, sid + getPtgClass()); LittleEndian.putInt(array,offset+1,field_1_reserved); } - public void setReserved(int reserved) - { - field_1_reserved = reserved; - } - - public int getReserved() - { - return field_1_reserved; - } - public int getSize() { return SIZE; } - public String toFormulaString(Workbook book) - { - //TODO -- should we store a cellreference instance in this ptg?? but .. memory is an issue, i believe! - return "#REF!"; + public String toFormulaString(Workbook book) { + return ErrorConstants.getText(ErrorConstants.ERROR_REF); } public byte getDefaultOperandClass() { diff --git a/src/java/org/apache/poi/hssf/record/formula/RefNPtg.java b/src/java/org/apache/poi/hssf/record/formula/RefNPtg.java index 5ef4a413b..07114265d 100644 --- a/src/java/org/apache/poi/hssf/record/formula/RefNPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/RefNPtg.java @@ -23,16 +23,14 @@ import org.apache.poi.hssf.record.RecordInputStream; * RefNPtg * @author Jason Height (jheight at apache dot com) */ -public final class RefNPtg extends RefPtgBase { - public final static byte sid = 0x2C; +public final class RefNPtg extends Ref2DPtgBase { + public final static byte sid = 0x2C; - /** Creates new ValueReferencePtg */ + public RefNPtg(RecordInputStream in) { + super(in); + } - public RefNPtg(RecordInputStream in) { - super(in); - } - - protected byte getSid() { - return sid; - } + protected byte getSid() { + return sid; + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/RefPtg.java b/src/java/org/apache/poi/hssf/record/formula/RefPtg.java index a324ce70f..94a1b3301 100644 --- a/src/java/org/apache/poi/hssf/record/formula/RefPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/RefPtg.java @@ -24,30 +24,26 @@ import org.apache.poi.hssf.record.RecordInputStream; * @author Andrew C. Oliver (acoliver@apache.org) * @author Jason Height (jheight at chariot dot net dot au) */ -public final class RefPtg extends RefPtgBase { - public final static byte sid = 0x24; +public final class RefPtg extends Ref2DPtgBase { + public final static byte sid = 0x24; - /** - * Takes in a String representation of a cell reference and fills out the - * numeric fields. - */ - public RefPtg(String cellref) { - super(cellref); - } + /** + * Takes in a String representation of a cell reference and fills out the + * numeric fields. + */ + public RefPtg(String cellref) { + super(cellref); + } - public RefPtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) { - setRow(row); - setColumn(column); - setRowRelative(isRowRelative); - setColRelative(isColumnRelative); - } + public RefPtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) { + super(row, column, isRowRelative, isColumnRelative); + } - public RefPtg(RecordInputStream in) { - super(in); - } + public RefPtg(RecordInputStream in) { + super(in); + } protected byte getSid() { return sid; } - } diff --git a/src/java/org/apache/poi/hssf/record/formula/RefPtgBase.java b/src/java/org/apache/poi/hssf/record/formula/RefPtgBase.java index 33dff9ec0..f04ec0ac8 100644 --- a/src/java/org/apache/poi/hssf/record/formula/RefPtgBase.java +++ b/src/java/org/apache/poi/hssf/record/formula/RefPtgBase.java @@ -17,13 +17,11 @@ package org.apache.poi.hssf.record.formula; -import org.apache.poi.util.LittleEndian; +import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.hssf.util.CellReference; import org.apache.poi.util.BitField; import org.apache.poi.util.BitFieldFactory; - -import org.apache.poi.ss.util.CellReference; -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.util.LittleEndian; /** * ReferencePtgBase - handles references (such as A1, A2, IA4) @@ -32,7 +30,6 @@ import org.apache.poi.hssf.record.RecordInputStream; */ public abstract class RefPtgBase extends OperandPtg { - private final static int SIZE = 5; private final static int MAX_ROW_NUMBER = 65536; /** The row index - zero based unsigned 16 bit value */ @@ -70,30 +67,15 @@ public abstract class RefPtgBase extends OperandPtg { setColRelative(isColumnRelative); } - protected RefPtgBase(RecordInputStream in) { + protected final void readCoordinates(RecordInputStream in) { field_1_row = in.readUShort(); field_2_col = in.readUShort(); } - - public final String toString() { - CellReference cr = new CellReference(getRow(), getColumn(), !isRowRelative(),!isColRelative()); - StringBuffer sb = new StringBuffer(); - sb.append(getClass().getName()); - sb.append(" ["); - sb.append(cr.formatAsString()); - sb.append("]"); - return sb.toString(); + protected final void writeCoordinates(byte[] array, int offset) { + LittleEndian.putUShort(array, offset + 0, field_1_row); + LittleEndian.putUShort(array, offset + 2, field_2_col); } - public final void writeBytes(byte [] array, int offset) { - array[offset] = (byte) (getSid() + getPtgClass()); - - LittleEndian.putShort(array, offset+1, (short)field_1_row); - LittleEndian.putShort(array, offset+3, (short)field_2_col); - } - - protected abstract byte getSid(); - public final void setRow(int row) { if(row < 0 || row >= MAX_ROW_NUMBER) { throw new IllegalArgumentException("The row number, when specified as an integer, must be between 0 and " + MAX_ROW_NUMBER); @@ -102,18 +84,11 @@ public abstract class RefPtgBase extends OperandPtg { } /** - * Returns the row number as a short, which will be - * wrapped (negative) for values between 32769 and 65535 + * @return the row number as an int, between 0 and 65535 */ public final int getRow(){ return field_1_row; } - /** - * Returns the row number as an int, between 0 and 65535 - */ - public final int getRowAsInt() { - return field_1_row; - } public final boolean isRowRelative() { return rowRelative.isSet(field_2_col); @@ -135,20 +110,16 @@ public abstract class RefPtgBase extends OperandPtg { if(col < 0 || col >= 0x100) { throw new IllegalArgumentException("Specified colIx (" + col + ") is out of range"); } - field_2_col = column.setValue(field_2_col, col); + field_2_col = column.setValue(field_2_col, col); } public final int getColumn() { - return column.getValue(field_2_col); + return column.getValue(field_2_col); } - - public final int getSize() { - return SIZE; - } - - public final String toFormulaString(Workbook book) { - //TODO -- should we store a cellreference instance in this ptg?? but .. memory is an issue, i believe! - return (new CellReference(getRowAsInt(),getColumn(),!isRowRelative(),!isColRelative())).formatAsString(); + protected final String formatReferenceAsString() { + // Only make cell references as needed. Memory is an issue + CellReference cr = new CellReference(getRow(), getColumn(), !isRowRelative(), !isColRelative()); + return cr.formatAsString(); } public final byte getDefaultOperandClass() { diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 1f88cce2b..4fbedb874 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -300,7 +300,7 @@ public class HSSFCell implements Cell { FormulaRecordAggregate frec; if (cellType != this.cellType) { - frec = sheet.getSheet().createFormula(row, col); + frec = sheet.getSheet().getRowsAggregate().createFormula(row, col); } else { frec = (FormulaRecordAggregate) record; frec.setRow(row); @@ -600,12 +600,6 @@ public class HSSFCell implements Cell { Ptg[] ptgs = FormulaParser.parse(formula, book); frec.setParsedExpression(ptgs); } - /* package */ void setFormulaOnly(Ptg[] ptgs) { - if (ptgs == null) { - throw new IllegalArgumentException("ptgs must not be null"); - } - ((FormulaRecordAggregate)record).getFormulaRecord().setParsedExpression(ptgs); - } public String getCellFormula() { return FormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression()); diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFName.java b/src/java/org/apache/poi/hssf/usermodel/HSSFName.java index 4fde9e6cc..6de229379 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFName.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFName.java @@ -126,8 +126,16 @@ public class HSSFName implements Name { * @return true if the name refers to a deleted cell, false otherwise */ public boolean isDeleted(){ - String ref = getReference(); - return "#REF!".endsWith(ref); + String formulaText = getReference(); + if (formulaText.startsWith("#REF!")) { + // sheet deleted + return true; + } + if (formulaText.endsWith("#REF!")) { + // cell range deleted + return true; + } + return false; } public boolean isFunctionName() { return _definedNameRec.isFunctionName(); diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index 4d6a1f725..231942f44 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -31,7 +31,6 @@ import java.util.List; import java.util.TreeMap; import org.apache.poi.ddf.EscherRecord; -import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.record.CellValueRecordInterface; @@ -43,8 +42,7 @@ import org.apache.poi.hssf.record.SCLRecord; import org.apache.poi.hssf.record.WSBoolRecord; import org.apache.poi.hssf.record.WindowTwoRecord; import org.apache.poi.hssf.record.aggregates.DataValidityTable; -import org.apache.poi.hssf.record.formula.Ptg; -import org.apache.poi.hssf.record.formula.RefPtg; +import org.apache.poi.hssf.record.formula.FormulaShifter; import org.apache.poi.hssf.util.PaneInformation; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Row; @@ -1180,33 +1178,26 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet * @param resetOriginalRowHeight whether to set the original row's height to the default * @param moveComments whether to move comments at the same time as the cells they are attached to */ - public void shiftRows( int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight, boolean moveComments) - { - int s, e, inc; - if ( n < 0 ) - { + public void shiftRows(int startRow, int endRow, int n, + boolean copyRowHeight, boolean resetOriginalRowHeight, boolean moveComments) { + int s, inc; + if (n < 0) { s = startRow; - e = endRow; inc = 1; - } - else - { + } else { s = endRow; - e = startRow; inc = -1; } shiftMerged(startRow, endRow, n, true); sheet.getPageSettings().shiftRowBreaks(startRow, endRow, n); - for ( int rowNum = s; rowNum >= startRow && rowNum <= endRow && rowNum >= 0 && rowNum < 65536; rowNum += inc ) - { + for ( int rowNum = s; rowNum >= startRow && rowNum <= endRow && rowNum >= 0 && rowNum < 65536; rowNum += inc ) { HSSFRow row = getRow( rowNum ); HSSFRow row2Replace = getRow( rowNum + n ); if ( row2Replace == null ) row2Replace = createRow( rowNum + n ); - HSSFCell cell; // Remove all the old cells from the row we'll // be writing too, before we start overwriting @@ -1236,7 +1227,7 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet // Copy each cell from the source row to // the destination row for(Iterator cells = row.cellIterator(); cells.hasNext(); ) { - cell = (HSSFCell)cells.next(); + HSSFCell cell = (HSSFCell)cells.next(); row.removeCell( cell ); CellValueRecordInterface cellRecord = cell.getCellValueRecord(); cellRecord.setRow( rowNum + n ); @@ -1263,49 +1254,21 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet // Update any formulas on this sheet that point to // rows which have been moved - updateFormulasAfterShift(startRow, endRow, n); - } + int sheetIndex = workbook.getSheetIndex(this); + short externSheetIndex = book.checkExternSheet(sheetIndex); + FormulaShifter shifter = FormulaShifter.createForRowShift(externSheetIndex, startRow, endRow, n); + sheet.getRowsAggregate().updateFormulasAfterRowShift(shifter, externSheetIndex); - /** - * Called by shiftRows to update formulas on this sheet - * to point to the new location of moved rows - */ - private void updateFormulasAfterShift(int startRow, int endRow, int n) { - // Need to look at every cell on the sheet - // Not just those that were moved - Iterator ri = rowIterator(); - while(ri.hasNext()) { - HSSFRow r = (HSSFRow)ri.next(); - Iterator ci = r.cellIterator(); - while(ci.hasNext()) { - HSSFCell c = (HSSFCell)ci.next(); - if(c.getCellType() == HSSFCell.CELL_TYPE_FORMULA) { - // Since it's a formula cell, process the - // formula string, and look to see if - // it contains any references - - // Look for references, and update if needed - Ptg[] ptgs = FormulaParser.parse(c.getCellFormula(), workbook); - boolean changed = false; - for(int i=0; i * @@ -1384,20 +1400,6 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm return result; } - /** - * @deprecated for POI internal use only (formula rendering). This method is likely to - * be removed in future versions of POI. - * - * @param refIndex Index to REF entry in EXTERNSHEET record in the Link Table - * @param definedNameIndex zero-based to DEFINEDNAME or EXTERNALNAME record - * @return the string representation of the defined or external name - */ - public String resolveNameXText(int refIndex, int definedNameIndex) { - // TODO - make this less cryptic / move elsewhere - return workbook.resolveNameXText(refIndex, definedNameIndex); - } - - /** * Sets the printarea for the sheet provided *

diff --git a/src/testcases/org/apache/poi/hssf/data/ForShifting.xls b/src/testcases/org/apache/poi/hssf/data/ForShifting.xls index 13e5920e5..7cb3ebd7d 100755 Binary files a/src/testcases/org/apache/poi/hssf/data/ForShifting.xls and b/src/testcases/org/apache/poi/hssf/data/ForShifting.xls differ diff --git a/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java b/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java index a685fd2cb..5f63f31a0 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java @@ -43,6 +43,7 @@ public final class AllFormulaTests { result.addTestSuite(TestArrayPtg.class); result.addTestSuite(TestErrPtg.class); result.addTestSuite(TestExternalFunctionFormulas.class); + result.addTestSuite(TestFormulaShifter.class); result.addTestSuite(TestFuncPtg.class); result.addTestSuite(TestFuncVarPtg.class); result.addTestSuite(TestIntersectionPtg.class); diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestFormulaShifter.java b/src/testcases/org/apache/poi/hssf/record/formula/TestFormulaShifter.java new file mode 100644 index 000000000..55f79e6df --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/formula/TestFormulaShifter.java @@ -0,0 +1,115 @@ +/* ==================================================================== + 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.record.formula; + +import junit.framework.TestCase; + +/** + * Tests for {@link FormulaShifter}. + * + * @author Josh Micich + */ +public final class TestFormulaShifter extends TestCase { + // Note - the expected result row coordinates here were determined/verified + // in Excel 2007 by manually testing. + + /** + * Tests what happens to area refs when a range of rows from inside, or overlapping are + * moved + */ + public void testShiftAreasSourceRows() { + + // all these operations are on an area ref spanning rows 10 to 20 + AreaPtg aptg = createAreaPtg(10, 20); + + confirmAreaShift(aptg, 9, 21, 20, 30, 40); + confirmAreaShift(aptg, 10, 21, 20, 30, 40); + confirmAreaShift(aptg, 9, 20, 20, 30, 40); + + confirmAreaShift(aptg, 8, 11, -3, 7, 20); // simple expansion of top + // rows containing area top being shifted down: + confirmAreaShift(aptg, 8, 11, 3, 13, 20); + confirmAreaShift(aptg, 8, 11, 7, 17, 20); + confirmAreaShift(aptg, 8, 11, 8, 18, 20); + confirmAreaShift(aptg, 8, 11, 9, 12, 20); // note behaviour changes here + confirmAreaShift(aptg, 8, 11, 10, 12, 21); + confirmAreaShift(aptg, 8, 11, 12, 12, 23); + confirmAreaShift(aptg, 8, 11, 13, 10, 20); // ignored + + // rows from within being moved: + confirmAreaShift(aptg, 12, 16, 3, 10, 20); // stay within - no change + confirmAreaShift(aptg, 11, 19, 20, 10, 20); // move completely out - no change + confirmAreaShift(aptg, 16, 17, -6, 10, 20); // moved exactly to top - no change + confirmAreaShift(aptg, 16, 17, -7, 11, 20); // truncation at top + confirmAreaShift(aptg, 12, 16, 4, 10, 20); // moved exactly to bottom - no change + confirmAreaShift(aptg, 12, 16, 6, 10, 17); // truncation at bottom + + // rows containing area bottom being shifted up: + confirmAreaShift(aptg, 18, 22, -1, 10, 19); // simple contraction at bottom + confirmAreaShift(aptg, 18, 22, -7, 10, 13); // simple contraction at bottom + confirmAreaShift(aptg, 18, 22, -8, 10, 17); // top calculated differently here + confirmAreaShift(aptg, 18, 22, -9, 9, 17); + confirmAreaShift(aptg, 18, 22,-15, 10, 20); // no change because range would be turned inside out + confirmAreaShift(aptg, 15, 19, -7, 13, 20); // dest truncates top (even though src is from inside range) + confirmAreaShift(aptg, 19, 23,-12, 7, 18); // complex: src encloses bottom, dest encloses top + + confirmAreaShift(aptg, 18, 22, 5, 10, 25); // simple expansion at bottom + } + /** + * Tests what happens to an area ref when some outside rows are moved to overlap + * that area ref + */ + public void testShiftAreasDestRows() { + // all these operations are on an area ref spanning rows 20 to 25 + AreaPtg aptg = createAreaPtg(20, 25); + + // no change because no overlap: + confirmAreaShift(aptg, 5, 10, 9, 20, 25); + confirmAreaShift(aptg, 5, 10, 21, 20, 25); + + confirmAreaShift(aptg, 11, 14, 10, 20, 25); + + confirmAreaShift(aptg, 7, 17, 10, -1, -1); // converted to DeletedAreaRef + confirmAreaShift(aptg, 5, 15, 7, 23, 25); // truncation at top + confirmAreaShift(aptg, 13, 16, 10, 20, 22); // truncation at bottom + } + + private static void confirmAreaShift(AreaPtg aptg, + int firstRowMoved, int lastRowMoved, int numberRowsMoved, + int expectedAreaFirstRow, int expectedAreaLastRow) { + + FormulaShifter fs = FormulaShifter.createForRowShift(0, firstRowMoved, lastRowMoved, numberRowsMoved); + boolean expectedChanged = aptg.getFirstRow() != expectedAreaFirstRow || aptg.getLastRow() != expectedAreaLastRow; + + AreaPtg copyPtg = (AreaPtg) aptg.copy(); // clone so we can re-use aptg in calling method + Ptg[] ptgs = { copyPtg, }; + boolean actualChanged = fs.adjustFormula(ptgs, 0); + if (expectedAreaFirstRow < 0) { + assertEquals(AreaErrPtg.class, ptgs[0].getClass()); + return; + } + assertEquals(expectedChanged, actualChanged); + assertEquals(copyPtg, ptgs[0]); // expected to change in place (although this is not a strict requirement) + assertEquals(expectedAreaFirstRow, copyPtg.getFirstRow()); + assertEquals(expectedAreaLastRow, copyPtg.getLastRow()); + + } + private static AreaPtg createAreaPtg(int initialAreaFirstRow, int initialAreaLastRow) { + return new AreaPtg(initialAreaFirstRow, initialAreaLastRow, 2, 5, false, false, false, false); + } +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java b/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java index 36eaf92f3..397c1e52c 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java @@ -1052,25 +1052,25 @@ public final class TestFormulas extends TestCase { sb.write(new FileOutputStream(file)); } - /*Unknown Ptg 3C*/ + /** Unknown Ptg 3C*/ public void test27272_1() throws Exception { HSSFWorkbook wb = openSample("27272_1.xls"); wb.getSheetAt(0); - assertEquals("Reference for named range ", "#REF!",wb.getNameAt(0).getReference()); + assertEquals("Reference for named range ", "Compliance!#REF!",wb.getNameAt(0).getReference()); File outF = File.createTempFile("bug27272_1",".xls"); wb.write(new FileOutputStream(outF)); System.out.println("Open "+outF.getAbsolutePath()+" in Excel"); } - /*Unknown Ptg 3D*/ + /** Unknown Ptg 3D*/ public void test27272_2() throws Exception { HSSFWorkbook wb = openSample("27272_2.xls"); - assertEquals("Reference for named range ", "#REF!",wb.getNameAt(0).getReference()); + assertEquals("Reference for named range ", "'LOAD.POD_HISTORIES'!#REF!",wb.getNameAt(0).getReference()); File outF = File.createTempFile("bug27272_2",".xls"); wb.write(new FileOutputStream(outF)); System.out.println("Open "+outF.getAbsolutePath()+" in Excel"); } - /* MissingArgPtg */ + /** MissingArgPtg */ public void testMissingArgPtg() throws Exception { HSSFWorkbook wb = new HSSFWorkbook(); HSSFCell cell = wb.createSheet("Sheet1").createRow(4).createCell(0); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java b/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java index f3430d8bc..b3afee5bf 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java @@ -476,7 +476,7 @@ public final class TestNamedRange extends TestCase { HSSFName name2 = wb.getNameAt(1); assertEquals("b", name2.getNameName()); - assertEquals("#REF!", name2.getReference()); + assertEquals("Sheet1!#REF!", name2.getReference()); assertTrue(name2.isDeleted()); try { AreaReference ref2 = new AreaReference(name2.getReference()); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestSheetShiftRows.java b/src/testcases/org/apache/poi/hssf/usermodel/TestSheetShiftRows.java index 101bd325d..ccd92298d 100755 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestSheetShiftRows.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestSheetShiftRows.java @@ -19,6 +19,10 @@ package org.apache.poi.hssf.usermodel; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import junit.framework.TestCase; @@ -201,36 +205,69 @@ public final class TestSheetShiftRows extends TestCase { HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("ForShifting.xls"); HSSFSheet sheet = wb.getSheet("Sheet1"); - assertEquals(19, sheet.getLastRowNum()); - - assertEquals("cell B1 (ref)", sheet.getRow(0).getCell(3).getRichStringCellValue().toString()); - assertEquals("CONCATENATE(B1,\" (ref)\")", sheet.getRow(0).getCell(3).getCellFormula()); - assertEquals("cell B2 (ref)", sheet.getRow(1).getCell(3).getRichStringCellValue().toString()); - assertEquals("CONCATENATE(B2,\" (ref)\")", sheet.getRow(1).getCell(3).getCellFormula()); - assertEquals("cell B3 (ref)", sheet.getRow(2).getCell(3).getRichStringCellValue().toString()); - assertEquals("CONCATENATE(B3,\" (ref)\")", sheet.getRow(2).getCell(3).getCellFormula()); - assertEquals("cell B2 (ref)", sheet.getRow(6).getCell(1).getRichStringCellValue().toString()); - assertEquals("CONCATENATE(B2,\" (ref)\")", sheet.getRow(6).getCell(1).getCellFormula()); + assertEquals(20, sheet.getLastRowNum()); + + confirmRow(sheet, 0, 1, 171, 1, "ROW(D1)", "100+B1", "COUNT(D1:E1)"); + confirmRow(sheet, 1, 2, 172, 1, "ROW(D2)", "100+B2", "COUNT(D2:E2)"); + confirmRow(sheet, 2, 3, 173, 1, "ROW(D3)", "100+B3", "COUNT(D3:E3)"); + + confirmCell(sheet, 6, 1, 271, "200+B1"); + confirmCell(sheet, 7, 1, 272, "200+B2"); + confirmCell(sheet, 8, 1, 273, "200+B3"); + confirmCell(sheet, 14, 0, 0.0, "A12"); // the cell referred to by this formula will be replaced + + // ----------- + // Row index 1 -> 11 (row "2" -> row "12") sheet.shiftRows(1, 1, 10); + + // Now check what sheet looks like after move - // Row 1 => Row 11 - // So strings on row 11 unchanged, but reference in formula is - assertEquals("cell B1 (ref)", sheet.getRow(0).getCell(3).getRichStringCellValue().toString()); - assertEquals("CONCATENATE(B1,\" (ref)\")", sheet.getRow(0).getCell(3).getCellFormula()); + // no changes on row "1" + confirmRow(sheet, 0, 1, 171, 1, "ROW(D1)", "100+B1", "COUNT(D1:E1)"); + + // row "2" is now empty assertEquals(0, sheet.getRow(1).getPhysicalNumberOfCells()); - // still save b2 - assertEquals("cell B2 (ref)", sheet.getRow(11).getCell(3).getRichStringCellValue().toString()); - // but points to b12 - assertEquals("CONCATENATE(B12,\" (ref)\")", sheet.getRow(11).getCell(3).getCellFormula()); + // Row "2" moved to row "12", and the formula has been updated. + // note however that the cached formula result (2) has not been updated. (POI differs from Excel here) + confirmRow(sheet, 11, 2, 172, 1, "ROW(D12)", "100+B12", "COUNT(D12:E12)"); - assertEquals("cell B3 (ref)", sheet.getRow(2).getCell(3).getRichStringCellValue().toString()); - assertEquals("CONCATENATE(B3,\" (ref)\")", sheet.getRow(2).getCell(3).getCellFormula()); + // no changes on row "3" + confirmRow(sheet, 2, 3, 173, 1, "ROW(D3)", "100+B3", "COUNT(D3:E3)"); - // one on a non-shifted row also updated - assertEquals("cell B2 (ref)", sheet.getRow(6).getCell(1).getRichStringCellValue().toString()); - assertEquals("CONCATENATE(B12,\" (ref)\")", sheet.getRow(6).getCell(1).getCellFormula()); + + confirmCell(sheet, 14, 0, 0.0, "#REF!"); + + + // Formulas on rows that weren't shifted: + confirmCell(sheet, 6, 1, 271, "200+B1"); + confirmCell(sheet, 7, 1, 272, "200+B12"); // this one moved + confirmCell(sheet, 8, 1, 273, "200+B3"); + + // check formulas on other sheets + HSSFSheet sheet2 = wb.getSheet("Sheet2"); + confirmCell(sheet2, 0, 0, 371, "300+Sheet1!B1"); + confirmCell(sheet2, 1, 0, 372, "300+Sheet1!B12"); + confirmCell(sheet2, 2, 0, 373, "300+Sheet1!B3"); + + confirmCell(sheet2, 11, 0, 300, "300+Sheet1!#REF!"); + + + // Note - named ranges formulas have not been updated + } + + private static void confirmRow(HSSFSheet sheet, int rowIx, double valA, double valB, double valC, + String formulaA, String formulaB, String formulaC) { + confirmCell(sheet, rowIx, 4, valA, formulaA); + confirmCell(sheet, rowIx, 5, valB, formulaB); + confirmCell(sheet, rowIx, 6, valC, formulaC); + } + + private static void confirmCell(HSSFSheet sheet, int rowIx, int colIx, + double expectedValue, String expectedFormula) { + HSSFCell cell = sheet.getRow(rowIx).getCell(colIx); + assertEquals(expectedValue, cell.getNumericCellValue(), 0.0); + assertEquals(expectedFormula, cell.getCellFormula()); } } -