diff --git a/build.xml b/build.xml index 0285acae3..2eddf722f 100644 --- a/build.xml +++ b/build.xml @@ -17,7 +17,6 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> - @@ -169,6 +169,9 @@ under the License. + + @@ -192,8 +195,8 @@ under the License. value="${repository.m2}/maven2/org/apache/xmlbeans/xmlbeans/2.6.0/xmlbeans-2.6.0.jar"/> - - + + @@ -285,6 +288,7 @@ under the License. + @@ -303,6 +307,8 @@ under the License. + + @@ -310,6 +316,7 @@ under the License. + @@ -498,9 +505,7 @@ under the License. - - - - + @@ -531,12 +536,14 @@ under the License. + + @@ -587,6 +594,7 @@ under the License. + @@ -605,6 +613,7 @@ under the License. + @@ -1161,6 +1170,8 @@ under the License. + + @@ -1242,6 +1253,8 @@ under the License. + + @@ -1258,6 +1271,16 @@ under the License. + + + + + + + + + + @@ -1288,6 +1311,8 @@ under the License. + + @@ -1335,6 +1360,8 @@ under the License. and on Windows with jdk-1.5.22 --> + + @@ -1373,6 +1400,8 @@ under the License. + + @@ -1396,6 +1425,8 @@ under the License. + + @@ -1439,6 +1470,8 @@ under the License. + + @@ -1485,6 +1518,8 @@ under the License. + + @@ -1503,7 +1538,20 @@ under the License. - + + + + + + + + + + + + + @@ -1522,6 +1570,8 @@ under the License. + + @@ -1529,6 +1579,8 @@ under the License. + + @@ -1557,6 +1609,8 @@ under the License. + + @@ -1868,6 +1922,7 @@ under the License. + @@ -2083,7 +2138,7 @@ under the License. - + @@ -2103,11 +2158,13 @@ under the License. output="xml:withMessages" outputFile="build/findbugs.xml" effort="max" + failOnError="true" excludeFilter="src/resources/devtools/findbugs-filters.xml"> + @@ -2117,9 +2174,11 @@ under the License. + + diff --git a/maven/poi.pom b/maven/poi.pom index 7c1b4cf84..efd24cfe0 100644 --- a/maven/poi.pom +++ b/maven/poi.pom @@ -91,6 +91,11 @@ test 4.12 + + org.apache.commons + commons-collections4 + 4.1 + diff --git a/sonar/main/pom.xml b/sonar/main/pom.xml index 72c3eebfc..ffc9499d9 100644 --- a/sonar/main/pom.xml +++ b/sonar/main/pom.xml @@ -110,6 +110,11 @@ + + org.apache.commons + commons-collections4 + 4.1 + commons-codec commons-codec 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 0319af64e..5551639b4 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java @@ -34,184 +34,184 @@ import org.apache.poi.ss.formula.FormulaShifter; * @author Jason Height (jheight at chariot dot net dot au) */ public final class RowRecordsAggregate extends RecordAggregate { - private int _firstrow = -1; - private int _lastrow = -1; - private final Map _rowRecords; - private final ValueRecordsAggregate _valuesAgg; - private final List _unknownRecords; - private final SharedValueManager _sharedValueManager; + private int _firstrow = -1; + private int _lastrow = -1; + private final Map _rowRecords; + private final ValueRecordsAggregate _valuesAgg; + private final List _unknownRecords; + private final SharedValueManager _sharedValueManager; - // Cache values to speed up performance of + // Cache values to speed up performance of // getStartRowNumberForBlock / getEndRowNumberForBlock, see Bugzilla 47405 private RowRecord[] _rowRecordValues = null; - /** Creates a new instance of ValueRecordsAggregate */ - public RowRecordsAggregate() { - this(SharedValueManager.createEmpty()); - } - private RowRecordsAggregate(SharedValueManager svm) { - if (svm == null) { - throw new IllegalArgumentException("SharedValueManager must be provided."); - } - _rowRecords = new TreeMap(); - _valuesAgg = new ValueRecordsAggregate(); - _unknownRecords = new ArrayList(); - _sharedValueManager = svm; - } + /** Creates a new instance of ValueRecordsAggregate */ + public RowRecordsAggregate() { + this(SharedValueManager.createEmpty()); + } + private RowRecordsAggregate(SharedValueManager svm) { + if (svm == null) { + throw new IllegalArgumentException("SharedValueManager must be provided."); + } + _rowRecords = new TreeMap(); + _valuesAgg = new ValueRecordsAggregate(); + _unknownRecords = new ArrayList(); + _sharedValueManager = svm; + } - /** - * @param rs record stream with all {@link SharedFormulaRecord} - * {@link ArrayRecord}, {@link TableRecord} {@link MergeCellsRecord} Records removed - * @param svm an initialised {@link SharedValueManager} (from the shared formula, array - * and table records of the current sheet). Never null. - */ - public RowRecordsAggregate(RecordStream rs, SharedValueManager svm) { - this(svm); - while(rs.hasNext()) { - Record rec = rs.getNext(); - switch (rec.getSid()) { - case RowRecord.sid: - insertRow((RowRecord) rec); - continue; + /** + * @param rs record stream with all {@link SharedFormulaRecord} + * {@link ArrayRecord}, {@link TableRecord} {@link MergeCellsRecord} Records removed + * @param svm an initialised {@link SharedValueManager} (from the shared formula, array + * and table records of the current sheet). Never null. + */ + public RowRecordsAggregate(RecordStream rs, SharedValueManager svm) { + this(svm); + while(rs.hasNext()) { + Record rec = rs.getNext(); + switch (rec.getSid()) { + case RowRecord.sid: + insertRow((RowRecord) rec); + continue; case DConRefRecord.sid: addUnknownRecord(rec); continue; case DBCellRecord.sid: - // end of 'Row Block'. Should only occur after cell records - // ignore DBCELL records because POI generates them upon re-serialization - continue; - } - if (rec instanceof UnknownRecord) { - // might need to keep track of where exactly these belong - addUnknownRecord(rec); - while (rs.peekNextSid() == ContinueRecord.sid) { - addUnknownRecord(rs.getNext()); - } - continue; - } - if (rec instanceof MulBlankRecord) { - _valuesAgg.addMultipleBlanks((MulBlankRecord) rec); - continue; - } - if (!(rec instanceof CellValueRecordInterface)) { - throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")"); - } - _valuesAgg.construct((CellValueRecordInterface)rec, rs, svm); - } - } - /** - * Handles UnknownRecords which appear within the row/cell records - */ - private void addUnknownRecord(Record rec) { - // ony a few distinct record IDs are encountered by the existing POI test cases: - // 0x1065 // many - // 0x01C2 // several - // 0x0034 // few - // No documentation could be found for these + // end of 'Row Block'. Should only occur after cell records + // ignore DBCELL records because POI generates them upon re-serialization + continue; + } + if (rec instanceof UnknownRecord) { + // might need to keep track of where exactly these belong + addUnknownRecord(rec); + while (rs.peekNextSid() == ContinueRecord.sid) { + addUnknownRecord(rs.getNext()); + } + continue; + } + if (rec instanceof MulBlankRecord) { + _valuesAgg.addMultipleBlanks((MulBlankRecord) rec); + continue; + } + if (!(rec instanceof CellValueRecordInterface)) { + throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")"); + } + _valuesAgg.construct((CellValueRecordInterface)rec, rs, svm); + } + } + /** + * Handles UnknownRecords which appear within the row/cell records + */ + private void addUnknownRecord(Record rec) { + // ony a few distinct record IDs are encountered by the existing POI test cases: + // 0x1065 // many + // 0x01C2 // several + // 0x0034 // few + // No documentation could be found for these - // keep the unknown records for re-serialization - _unknownRecords.add(rec); - } - public void insertRow(RowRecord row) { - // Integer integer = Integer.valueOf(row.getRowNumber()); - _rowRecords.put(Integer.valueOf(row.getRowNumber()), row); - // Clear the cached values - _rowRecordValues = null; - if ((row.getRowNumber() < _firstrow) || (_firstrow == -1)) { - _firstrow = row.getRowNumber(); - } - if ((row.getRowNumber() > _lastrow) || (_lastrow == -1)) { - _lastrow = row.getRowNumber(); - } - } + // keep the unknown records for re-serialization + _unknownRecords.add(rec); + } + public void insertRow(RowRecord row) { + // Integer integer = Integer.valueOf(row.getRowNumber()); + _rowRecords.put(Integer.valueOf(row.getRowNumber()), row); + // Clear the cached values + _rowRecordValues = null; + if ((row.getRowNumber() < _firstrow) || (_firstrow == -1)) { + _firstrow = row.getRowNumber(); + } + if ((row.getRowNumber() > _lastrow) || (_lastrow == -1)) { + _lastrow = row.getRowNumber(); + } + } - public void removeRow(RowRecord row) { - int rowIndex = row.getRowNumber(); - _valuesAgg.removeAllCellsValuesForRow(rowIndex); - Integer key = Integer.valueOf(rowIndex); - RowRecord rr = _rowRecords.remove(key); - if (rr == null) { - throw new RuntimeException("Invalid row index (" + key.intValue() + ")"); - } - if (row != rr) { - _rowRecords.put(key, rr); - throw new RuntimeException("Attempt to remove row that does not belong to this sheet"); - } - - // Clear the cached values - _rowRecordValues = null; - } + public void removeRow(RowRecord row) { + int rowIndex = row.getRowNumber(); + _valuesAgg.removeAllCellsValuesForRow(rowIndex); + Integer key = Integer.valueOf(rowIndex); + RowRecord rr = _rowRecords.remove(key); + if (rr == null) { + throw new RuntimeException("Invalid row index (" + key.intValue() + ")"); + } + if (row != rr) { + _rowRecords.put(key, rr); + throw new RuntimeException("Attempt to remove row that does not belong to this sheet"); + } + + // Clear the cached values + _rowRecordValues = null; + } - public RowRecord getRow(int rowIndex) { + public RowRecord getRow(int rowIndex) { int maxrow = SpreadsheetVersion.EXCEL97.getLastRowIndex(); if (rowIndex < 0 || rowIndex > maxrow) { - throw new IllegalArgumentException("The row number must be between 0 and " + maxrow + ", but had: " + rowIndex); - } - return _rowRecords.get(Integer.valueOf(rowIndex)); - } + throw new IllegalArgumentException("The row number must be between 0 and " + maxrow + ", but had: " + rowIndex); + } + return _rowRecords.get(Integer.valueOf(rowIndex)); + } - public int getPhysicalNumberOfRows() - { - return _rowRecords.size(); - } + public int getPhysicalNumberOfRows() + { + return _rowRecords.size(); + } - public int getFirstRowNum() - { - return _firstrow; - } + public int getFirstRowNum() + { + return _firstrow; + } - public int getLastRowNum() - { - return _lastrow; - } + public int getLastRowNum() + { + return _lastrow; + } - /** Returns the number of row blocks. - *

The row blocks are goupings of rows that contain the DBCell record - * after them - */ - public int getRowBlockCount() { - int size = _rowRecords.size()/DBCellRecord.BLOCK_SIZE; - if ((_rowRecords.size() % DBCellRecord.BLOCK_SIZE) != 0) - size++; - return size; - } + /** Returns the number of row blocks. + *

The row blocks are goupings of rows that contain the DBCell record + * after them + */ + public int getRowBlockCount() { + int size = _rowRecords.size()/DBCellRecord.BLOCK_SIZE; + if ((_rowRecords.size() % DBCellRecord.BLOCK_SIZE) != 0) + size++; + return size; + } - private int getRowBlockSize(int block) { - return RowRecord.ENCODED_SIZE * getRowCountForBlock(block); - } + private int getRowBlockSize(int block) { + return RowRecord.ENCODED_SIZE * getRowCountForBlock(block); + } - /** Returns the number of physical rows within a block*/ - public int getRowCountForBlock(int block) { - int startIndex = block * DBCellRecord.BLOCK_SIZE; - int endIndex = startIndex + DBCellRecord.BLOCK_SIZE - 1; - if (endIndex >= _rowRecords.size()) - endIndex = _rowRecords.size()-1; + /** Returns the number of physical rows within a block*/ + public int getRowCountForBlock(int block) { + int startIndex = block * DBCellRecord.BLOCK_SIZE; + int endIndex = startIndex + DBCellRecord.BLOCK_SIZE - 1; + if (endIndex >= _rowRecords.size()) + endIndex = _rowRecords.size()-1; - return endIndex-startIndex+1; - } + return endIndex-startIndex+1; + } - /** Returns the physical row number of the first row in a block*/ - private int getStartRowNumberForBlock(int block) { - int startIndex = block * DBCellRecord.BLOCK_SIZE; + /** Returns the physical row number of the first row in a block*/ + private int getStartRowNumberForBlock(int block) { + int startIndex = block * DBCellRecord.BLOCK_SIZE; - if(_rowRecordValues == null){ + if (_rowRecordValues == null) { _rowRecordValues = _rowRecords.values().toArray(new RowRecord[_rowRecords.size()]); } try { return _rowRecordValues[startIndex].getRowNumber(); } catch(ArrayIndexOutOfBoundsException e) { - throw new RuntimeException("Did not find start row for block " + block); - } - } + throw new RuntimeException("Did not find start row for block " + block); + } + } - /** Returns the physical row number of the end row in a block*/ - private int getEndRowNumberForBlock(int block) { - int endIndex = ((block + 1)*DBCellRecord.BLOCK_SIZE)-1; - if (endIndex >= _rowRecords.size()) - endIndex = _rowRecords.size()-1; + /** Returns the physical row number of the end row in a block*/ + private int getEndRowNumberForBlock(int block) { + int endIndex = ((block + 1)*DBCellRecord.BLOCK_SIZE)-1; + if (endIndex >= _rowRecords.size()) + endIndex = _rowRecords.size()-1; - if(_rowRecordValues == null){ + if (_rowRecordValues == null){ _rowRecordValues = _rowRecords.values().toArray(new RowRecord[_rowRecords.size()]); } @@ -219,287 +219,287 @@ public final class RowRecordsAggregate extends RecordAggregate { return _rowRecordValues[endIndex].getRowNumber(); } catch(ArrayIndexOutOfBoundsException e) { throw new RuntimeException("Did not find end row for block " + block); - } - } + } + } - private int visitRowRecordsForBlock(int blockIndex, RecordVisitor rv) { - final int startIndex = blockIndex*DBCellRecord.BLOCK_SIZE; - final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE; + private int visitRowRecordsForBlock(int blockIndex, RecordVisitor rv) { + final int startIndex = blockIndex*DBCellRecord.BLOCK_SIZE; + final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE; - Iterator rowIterator = _rowRecords.values().iterator(); + Iterator rowIterator = _rowRecords.values().iterator(); - //Given that we basically iterate through the rows in order, - //For a performance improvement, it would be better to return an instance of - //an iterator and use that instance throughout, rather than recreating one and - //having to move it to the right position. - int i=0; - for (;i getIterator() { - return _rowRecords.values().iterator(); - } + public Iterator getIterator() { + return _rowRecords.values().iterator(); + } - public int findStartOfRowOutlineGroup(int row) { - // Find the start of the group. - RowRecord rowRecord = this.getRow( row ); - int level = rowRecord.getOutlineLevel(); - int currentRow = row; - while (currentRow >= 0 && this.getRow( currentRow ) != null) { - rowRecord = this.getRow( currentRow ); - if (rowRecord.getOutlineLevel() < level) { - return currentRow + 1; - } - currentRow--; - } + public int findStartOfRowOutlineGroup(int row) { + // Find the start of the group. + RowRecord rowRecord = this.getRow( row ); + int level = rowRecord.getOutlineLevel(); + int currentRow = row; + while (currentRow >= 0 && this.getRow( currentRow ) != null) { + rowRecord = this.getRow( currentRow ); + if (rowRecord.getOutlineLevel() < level) { + return currentRow + 1; + } + currentRow--; + } - return currentRow + 1; - } + return currentRow + 1; + } - public int findEndOfRowOutlineGroup(int row) { - int level = getRow( row ).getOutlineLevel(); - int currentRow; - for (currentRow = row; currentRow < getLastRowNum(); currentRow++) { - if (getRow(currentRow) == null || getRow(currentRow).getOutlineLevel() < level) { - break; - } - } + public int findEndOfRowOutlineGroup(int row) { + int level = getRow( row ).getOutlineLevel(); + int currentRow; + for (currentRow = row; currentRow < getLastRowNum(); currentRow++) { + if (getRow(currentRow) == null || getRow(currentRow).getOutlineLevel() < level) { + break; + } + } - return currentRow-1; - } + return currentRow-1; + } - /** - * Hide all rows at or below the current outline level - * @return index of the next row after the last row that gets hidden - */ - private int writeHidden(RowRecord pRowRecord, int row) { - int rowIx = row; - RowRecord rowRecord = pRowRecord; - int level = rowRecord.getOutlineLevel(); - while (rowRecord != null && getRow(rowIx).getOutlineLevel() >= level) { - rowRecord.setZeroHeight(true); - rowIx++; - rowRecord = getRow(rowIx); - } - return rowIx; - } + /** + * Hide all rows at or below the current outline level + * @return index of the next row after the last row that gets hidden + */ + private int writeHidden(RowRecord pRowRecord, int row) { + int rowIx = row; + RowRecord rowRecord = pRowRecord; + int level = rowRecord.getOutlineLevel(); + while (rowRecord != null && getRow(rowIx).getOutlineLevel() >= level) { + rowRecord.setZeroHeight(true); + rowIx++; + rowRecord = getRow(rowIx); + } + return rowIx; + } - public void collapseRow(int rowNumber) { + public void collapseRow(int rowNumber) { - // Find the start of the group. - int startRow = findStartOfRowOutlineGroup(rowNumber); - RowRecord rowRecord = getRow(startRow); + // Find the start of the group. + int startRow = findStartOfRowOutlineGroup(rowNumber); + RowRecord rowRecord = getRow(startRow); - // Hide all the columns until the end of the group - int nextRowIx = writeHidden(rowRecord, startRow); + // Hide all the columns until the end of the group + int nextRowIx = writeHidden(rowRecord, startRow); - RowRecord row = getRow(nextRowIx); - if (row == null) { - row = createRow(nextRowIx); - insertRow(row); - } - // Write collapse field - row.setColapsed(true); - } + RowRecord row = getRow(nextRowIx); + if (row == null) { + row = createRow(nextRowIx); + insertRow(row); + } + // Write collapse field + row.setColapsed(true); + } - /** - * Create a row record. - * - * @param rowNumber row number - * @return RowRecord created for the passed in row number - * @see org.apache.poi.hssf.record.RowRecord - */ - public static RowRecord createRow(int rowNumber) { - return new RowRecord(rowNumber); - } + /** + * Create a row record. + * + * @param rowNumber row number + * @return RowRecord created for the passed in row number + * @see org.apache.poi.hssf.record.RowRecord + */ + public static RowRecord createRow(int rowNumber) { + return new RowRecord(rowNumber); + } - public boolean isRowGroupCollapsed(int row) { - int collapseRow = findEndOfRowOutlineGroup(row) + 1; + public boolean isRowGroupCollapsed(int row) { + int collapseRow = findEndOfRowOutlineGroup(row) + 1; - return getRow(collapseRow) != null && getRow(collapseRow).getColapsed(); - } + return getRow(collapseRow) != null && getRow(collapseRow).getColapsed(); + } - public void expandRow(int rowNumber) { - if (rowNumber == -1) - return; + public void expandRow(int rowNumber) { + if (rowNumber == -1) + return; - // If it is already expanded do nothing. - if (!isRowGroupCollapsed(rowNumber)) { - return; - } + // If it is already expanded do nothing. + if (!isRowGroupCollapsed(rowNumber)) { + return; + } - // Find the start of the group. - int startIdx = findStartOfRowOutlineGroup(rowNumber); - RowRecord row = getRow(startIdx); + // Find the start of the group. + int startIdx = findStartOfRowOutlineGroup(rowNumber); + RowRecord row = getRow(startIdx); - // Find the end of the group. - int endIdx = findEndOfRowOutlineGroup(rowNumber); + // Find the end of the group. + int endIdx = findEndOfRowOutlineGroup(rowNumber); - // expand: - // collapsed bit must be unset - // hidden bit gets unset _if_ surrounding groups are expanded you can determine - // this by looking at the hidden bit of the enclosing group. You will have - // to look at the start and the end of the current group to determine which - // is the enclosing group - // hidden bit only is altered for this outline level. ie. don't un-collapse contained groups - if (!isRowGroupHiddenByParent(rowNumber)) { - for (int i = startIdx; i <= endIdx; i++) { - RowRecord otherRow = getRow(i); - if (row.getOutlineLevel() == otherRow.getOutlineLevel() || !isRowGroupCollapsed(i)) { - otherRow.setZeroHeight(false); - } - } - } + // expand: + // collapsed bit must be unset + // hidden bit gets unset _if_ surrounding groups are expanded you can determine + // this by looking at the hidden bit of the enclosing group. You will have + // to look at the start and the end of the current group to determine which + // is the enclosing group + // hidden bit only is altered for this outline level. ie. don't un-collapse contained groups + if (!isRowGroupHiddenByParent(rowNumber)) { + for (int i = startIdx; i <= endIdx; i++) { + RowRecord otherRow = getRow(i); + if (row.getOutlineLevel() == otherRow.getOutlineLevel() || !isRowGroupCollapsed(i)) { + otherRow.setZeroHeight(false); + } + } + } - // Write collapse field - getRow(endIdx + 1).setColapsed(false); - } + // Write collapse field + getRow(endIdx + 1).setColapsed(false); + } - public boolean isRowGroupHiddenByParent(int row) { - // Look out outline details of end - int endLevel; - boolean endHidden; - int endOfOutlineGroupIdx = findEndOfRowOutlineGroup(row); - if (getRow(endOfOutlineGroupIdx + 1) == null) { - endLevel = 0; - endHidden = false; - } else { - endLevel = getRow(endOfOutlineGroupIdx + 1).getOutlineLevel(); - endHidden = getRow(endOfOutlineGroupIdx + 1).getZeroHeight(); - } + public boolean isRowGroupHiddenByParent(int row) { + // Look out outline details of end + int endLevel; + boolean endHidden; + int endOfOutlineGroupIdx = findEndOfRowOutlineGroup(row); + if (getRow(endOfOutlineGroupIdx + 1) == null) { + endLevel = 0; + endHidden = false; + } else { + endLevel = getRow(endOfOutlineGroupIdx + 1).getOutlineLevel(); + endHidden = getRow(endOfOutlineGroupIdx + 1).getZeroHeight(); + } - // Look out outline details of start - int startLevel; - boolean startHidden; - int startOfOutlineGroupIdx = findStartOfRowOutlineGroup( row ); - if (startOfOutlineGroupIdx - 1 < 0 || getRow(startOfOutlineGroupIdx - 1) == null) { - startLevel = 0; - startHidden = false; - } else { - startLevel = getRow(startOfOutlineGroupIdx - 1).getOutlineLevel(); - startHidden = getRow(startOfOutlineGroupIdx - 1).getZeroHeight(); - } + // Look out outline details of start + int startLevel; + boolean startHidden; + int startOfOutlineGroupIdx = findStartOfRowOutlineGroup( row ); + if (startOfOutlineGroupIdx - 1 < 0 || getRow(startOfOutlineGroupIdx - 1) == null) { + startLevel = 0; + startHidden = false; + } else { + startLevel = getRow(startOfOutlineGroupIdx - 1).getOutlineLevel(); + startHidden = getRow(startOfOutlineGroupIdx - 1).getZeroHeight(); + } - if (endLevel > startLevel) { - return endHidden; - } + if (endLevel > startLevel) { + return endHidden; + } - return startHidden; - } - - /** - * Returns an iterator for the cell values - */ - public Iterator getCellValueIterator() { - return _valuesAgg.iterator(); - } + return startHidden; + } + + /** + * Returns an iterator for the cell values + */ + public Iterator getCellValueIterator() { + return _valuesAgg.iterator(); + } - public IndexRecord createIndexRecord(int indexRecordOffset, int sizeOfInitialSheetRecords) { - IndexRecord result = new IndexRecord(); - result.setFirstRow(_firstrow); - result.setLastRowAdd1(_lastrow + 1); - // Calculate the size of the records from the end of the BOF - // and up to the RowRecordsAggregate... + public IndexRecord createIndexRecord(int indexRecordOffset, int sizeOfInitialSheetRecords) { + IndexRecord result = new IndexRecord(); + result.setFirstRow(_firstrow); + result.setLastRowAdd1(_lastrow + 1); + // Calculate the size of the records from the end of the BOF + // and up to the RowRecordsAggregate... - // Add the references to the DBCells in the IndexRecord (one for each block) - // Note: The offsets are relative to the Workbook BOF. Assume that this is - // 0 for now..... + // Add the references to the DBCells in the IndexRecord (one for each block) + // Note: The offsets are relative to the Workbook BOF. Assume that this is + // 0 for now..... - int blockCount = getRowBlockCount(); - // Calculate the size of this IndexRecord - int indexRecSize = IndexRecord.getRecordSizeForBlockCount(blockCount); + int blockCount = getRowBlockCount(); + // Calculate the size of this IndexRecord + int indexRecSize = IndexRecord.getRecordSizeForBlockCount(blockCount); - int currentOffset = indexRecordOffset + indexRecSize + sizeOfInitialSheetRecords; + int currentOffset = indexRecordOffset + indexRecSize + sizeOfInitialSheetRecords; - for (int block = 0; block < blockCount; block++) { - // each row-block has a DBCELL record. - // The offset of each DBCELL record needs to be updated in the INDEX record + for (int block = 0; block < blockCount; block++) { + // each row-block has a DBCELL record. + // The offset of each DBCELL record needs to be updated in the INDEX record - // account for row records in this row-block - currentOffset += getRowBlockSize(block); - // account for cell value records after those - currentOffset += _valuesAgg.getRowCellBlockSize( - getStartRowNumberForBlock(block), getEndRowNumberForBlock(block)); + // account for row records in this row-block + currentOffset += getRowBlockSize(block); + // account for cell value records after those + currentOffset += _valuesAgg.getRowCellBlockSize( + getStartRowNumberForBlock(block), getEndRowNumberForBlock(block)); - // currentOffset is now the location of the DBCELL record for this row-block - result.addDbcell(currentOffset); - // Add space required to write the DBCELL record (whose reference was just added). - currentOffset += (8 + (getRowCountForBlock(block) * 2)); - } - return result; - } - public void insertCell(CellValueRecordInterface cvRec) { - _valuesAgg.insertCell(cvRec); - } - public void removeCell(CellValueRecordInterface cvRec) { - if (cvRec instanceof FormulaRecordAggregate) { - ((FormulaRecordAggregate)cvRec).notifyFormulaChanging(); - } - _valuesAgg.removeCell(cvRec); - } - public FormulaRecordAggregate createFormula(int row, int col) { - FormulaRecord fr = new FormulaRecord(); - fr.setRow(row); - fr.setColumn((short) col); - return new FormulaRecordAggregate(fr, null, _sharedValueManager); - } - public void updateFormulasAfterRowShift(FormulaShifter formulaShifter, int currentExternSheetIndex) { - _valuesAgg.updateFormulasAfterRowShift(formulaShifter, currentExternSheetIndex); - } - public DimensionsRecord createDimensions() { - DimensionsRecord result = new DimensionsRecord(); - result.setFirstRow(_firstrow); - result.setLastRow(_lastrow); - result.setFirstCol((short) _valuesAgg.getFirstCellNum()); - result.setLastCol((short) _valuesAgg.getLastCellNum()); - return result; - } + // currentOffset is now the location of the DBCELL record for this row-block + result.addDbcell(currentOffset); + // Add space required to write the DBCELL record (whose reference was just added). + currentOffset += (8 + (getRowCountForBlock(block) * 2)); + } + return result; + } + public void insertCell(CellValueRecordInterface cvRec) { + _valuesAgg.insertCell(cvRec); + } + public void removeCell(CellValueRecordInterface cvRec) { + if (cvRec instanceof FormulaRecordAggregate) { + ((FormulaRecordAggregate)cvRec).notifyFormulaChanging(); + } + _valuesAgg.removeCell(cvRec); + } + public FormulaRecordAggregate createFormula(int row, int col) { + FormulaRecord fr = new FormulaRecord(); + fr.setRow(row); + fr.setColumn((short) col); + return new FormulaRecordAggregate(fr, null, _sharedValueManager); + } + public void updateFormulasAfterRowShift(FormulaShifter formulaShifter, int currentExternSheetIndex) { + _valuesAgg.updateFormulasAfterRowShift(formulaShifter, currentExternSheetIndex); + } + public DimensionsRecord createDimensions() { + DimensionsRecord result = new DimensionsRecord(); + result.setFirstRow(_firstrow); + result.setLastRow(_lastrow); + result.setFirstCol((short) _valuesAgg.getFirstCellNum()); + result.setLastCol((short) _valuesAgg.getLastCellNum()); + return result; + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java index a6f39a5f0..8d7d781f9 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java @@ -19,10 +19,10 @@ package org.apache.poi.hssf.usermodel; import java.util.Map; +import org.apache.poi.ss.formula.BaseFormulaEvaluator; import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment; import org.apache.poi.ss.formula.IStabilityClassifier; import org.apache.poi.ss.formula.WorkbookEvaluator; -import org.apache.poi.ss.formula.WorkbookEvaluatorProvider; import org.apache.poi.ss.formula.eval.BoolEval; import org.apache.poi.ss.formula.eval.ErrorEval; import org.apache.poi.ss.formula.eval.NumericValueEval; @@ -33,8 +33,6 @@ import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.CellValue; import org.apache.poi.ss.usermodel.FormulaEvaluator; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.util.Internal; @@ -45,362 +43,251 @@ import org.apache.poi.util.Internal; * cell values. Be sure to call {@link #clearAllCachedResultValues()} if any workbook cells are changed between * calls to evaluate~ methods on this class. */ -public class HSSFFormulaEvaluator implements FormulaEvaluator, WorkbookEvaluatorProvider { +public class HSSFFormulaEvaluator extends BaseFormulaEvaluator { + private final HSSFWorkbook _book; - private final WorkbookEvaluator _bookEvaluator; - private final HSSFWorkbook _book; - - public HSSFFormulaEvaluator(HSSFWorkbook workbook) { - this(workbook, null); - } - /** - * @param workbook The workbook to perform the formula evaluations in - * @param stabilityClassifier used to optimise caching performance. Pass null - * for the (conservative) assumption that any cell may have its definition changed after - * evaluation begins. - */ - public HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) { - this(workbook, stabilityClassifier, null); - } - - /** - * @param workbook The workbook to perform the formula evaluations in + public HSSFFormulaEvaluator(HSSFWorkbook workbook) { + this(workbook, null); + } + /** + * @param workbook The workbook to perform the formula evaluations in * @param stabilityClassifier used to optimise caching performance. Pass null * for the (conservative) assumption that any cell may have its definition changed after * evaluation begins. - * @param udfFinder pass null for default (AnalysisToolPak only) - */ - private HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { - _book = workbook; - _bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook), stabilityClassifier, udfFinder); - } + */ + public HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) { + this(workbook, stabilityClassifier, null); + } - /** - * @param workbook The workbook to perform the formula evaluations in - * @param stabilityClassifier used to optimise caching performance. Pass null - * for the (conservative) assumption that any cell may have its definition changed after - * evaluation begins. - * @param udfFinder pass null for default (AnalysisToolPak only) - */ - public static HSSFFormulaEvaluator create(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { - return new HSSFFormulaEvaluator(workbook, stabilityClassifier, udfFinder); - } + /** + * @param workbook The workbook to perform the formula evaluations in + * @param stabilityClassifier used to optimise caching performance. Pass null + * for the (conservative) assumption that any cell may have its definition changed after + * evaluation begins. + * @param udfFinder pass null for default (AnalysisToolPak only) + */ + private HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { + super(new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook), stabilityClassifier, udfFinder)); + _book = workbook; + } + + /** + * @param workbook The workbook to perform the formula evaluations in + * @param stabilityClassifier used to optimise caching performance. Pass null + * for the (conservative) assumption that any cell may have its definition changed after + * evaluation begins. + * @param udfFinder pass null for default (AnalysisToolPak only) + */ + public static HSSFFormulaEvaluator create(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { + return new HSSFFormulaEvaluator(workbook, stabilityClassifier, udfFinder); + } - /** - * Coordinates several formula evaluators together so that formulas that involve external - * references can be evaluated. - * @param workbookNames the simple file names used to identify the workbooks in formulas - * with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1") - * @param evaluators all evaluators for the full set of workbooks required by the formulas. - */ - public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) { - WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length]; - for (int i = 0; i < wbEvals.length; i++) { - wbEvals[i] = evaluators[i]._bookEvaluator; - } - CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals); - } + /** + * Coordinates several formula evaluators together so that formulas that involve external + * references can be evaluated. + * @param workbookNames the simple file names used to identify the workbooks in formulas + * with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1") + * @param evaluators all evaluators for the full set of workbooks required by the formulas. + */ + public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) { + BaseFormulaEvaluator.setupEnvironment(workbookNames, evaluators); + } - @Override + @Override public void setupReferencedWorkbooks(Map evaluators) { CollaboratingWorkbooksEnvironment.setupFormulaEvaluator(evaluators); } - - @Override - public WorkbookEvaluator _getWorkbookEvaluator() { - return _bookEvaluator; + + /** + * Should be called to tell the cell value cache that the specified (value or formula) cell + * has changed. + * Failure to call this method after changing cell values will cause incorrect behaviour + * of the evaluate~ methods of this class + */ + public void notifyUpdateCell(HSSFCell cell) { + _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell(cell)); } - - /** - * Should be called whenever there are major changes (e.g. moving sheets) to input cells - * in the evaluated workbook. If performance is not critical, a single call to this method - * may be used instead of many specific calls to the notify~ methods. - * - * Failure to call this method after changing cell values will cause incorrect behaviour - * of the evaluate~ methods of this class - */ - @Override - public void clearAllCachedResultValues() { - _bookEvaluator.clearAllCachedResultValues(); - } - /** - * Should be called to tell the cell value cache that the specified (value or formula) cell - * has changed. - * Failure to call this method after changing cell values will cause incorrect behaviour - * of the evaluate~ methods of this class - */ - public void notifyUpdateCell(HSSFCell cell) { - _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell(cell)); - } @Override public void notifyUpdateCell(Cell cell) { _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell)); } - /** - * Should be called to tell the cell value cache that the specified cell has just been - * deleted. - * Failure to call this method after changing cell values will cause incorrect behaviour - * of the evaluate~ methods of this class - */ - public void notifyDeleteCell(HSSFCell cell) { - _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell)); - } - @Override + /** + * Should be called to tell the cell value cache that the specified cell has just been + * deleted. + * Failure to call this method after changing cell values will cause incorrect behaviour + * of the evaluate~ methods of this class + */ + public void notifyDeleteCell(HSSFCell cell) { + _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell)); + } + @Override public void notifyDeleteCell(Cell cell) { - _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell((HSSFCell)cell)); - } + _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell((HSSFCell)cell)); + } - /** - * Should be called to tell the cell value cache that the specified (value or formula) cell - * has changed. - * Failure to call this method after changing cell values will cause incorrect behaviour - * of the evaluate~ methods of this class - */ - @Override + /** + * Should be called to tell the cell value cache that the specified (value or formula) cell + * has changed. + * Failure to call this method after changing cell values will cause incorrect behaviour + * of the evaluate~ methods of this class + */ + @Override public void notifySetFormula(Cell cell) { - _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell)); - } + _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell)); + } - /** - * If cell contains a formula, the formula is evaluated and returned, - * else the CellValue simply copies the appropriate cell value from - * the cell and also its cell type. This method should be preferred over - * evaluateInCell() when the call should not modify the contents of the - * original cell. - * - * @param cell may be null signifying that the cell is not present (or blank) - * @return null if the supplied cell is null or blank - */ - @Override - public CellValue evaluate(Cell cell) { - if (cell == null) { - return null; - } + /** + * If cell contains formula, it evaluates the formula, and saves the result of the formula. The + * cell remains as a formula cell. If the cell does not contain formula, rather than throwing an + * exception, this method returns {@link CellType#_NONE} and leaves the cell unchanged. + * + * Note that the type of the formula result is returned, so you know what kind of + * cached formula result is also stored with the formula. + *

+     * CellType evaluatedCellType = evaluator.evaluateFormulaCell(cell);
+     * 
+ * Be aware that your cell will hold both the formula, and the result. If you want the cell + * replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)} + * @param cell The cell to evaluate + * @return {@link CellType#_NONE} for non-formula cells, or the type of the formula result + * @since POI 3.15 beta 3 + * @deprecated POI 3.15 beta 3. Will be deleted when we make the CellType enum transition. See bug 59791. + */ + @Internal + @Override + public CellType evaluateFormulaCellEnum(Cell cell) { + if (cell == null || cell.getCellTypeEnum() != CellType.FORMULA) { + return CellType._NONE; + } + CellValue cv = evaluateFormulaCellValue(cell); + // cell remains a formula cell, but the cached value is changed + setCellValue(cell, cv); + return cv.getCellType(); + } - switch (cell.getCellTypeEnum()) { - case BOOLEAN: - return CellValue.valueOf(cell.getBooleanCellValue()); - case ERROR: - return CellValue.getError(cell.getErrorCellValue()); - case FORMULA: - return evaluateFormulaCellValue(cell); - case NUMERIC: - return new CellValue(cell.getNumericCellValue()); - case STRING: - return new CellValue(cell.getRichStringCellValue().getString()); - case BLANK: - return null; - default: - throw new IllegalStateException("Bad cell type (" + cell.getCellTypeEnum() + ")"); - } - - } + /** + * If cell contains formula, it evaluates the formula, and + * puts the formula result back into the cell, in place + * of the old formula. + * Else if cell does not contain formula, this method leaves + * the cell unchanged. + * Note that the same instance of HSSFCell is returned to + * allow chained calls like: + *
+     * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();
+     * 
+ * Be aware that your cell value will be changed to hold the + * result of the formula. If you simply want the formula + * value computed for you, use {@link #evaluateFormulaCellEnum(Cell)}} + */ + @Override + public HSSFCell evaluateInCell(Cell cell) { + if (cell == null) { + return null; + } + HSSFCell result = (HSSFCell) cell; + if (cell.getCellTypeEnum() == CellType.FORMULA) { + CellValue cv = evaluateFormulaCellValue(cell); + setCellValue(cell, cv); + setCellType(cell, cv); // cell will no longer be a formula cell + } + return result; + } + private static void setCellValue(Cell cell, CellValue cv) { + CellType cellType = cv.getCellType(); + switch (cellType) { + case BOOLEAN: + cell.setCellValue(cv.getBooleanValue()); + break; + case ERROR: + cell.setCellErrorValue(cv.getErrorValue()); + break; + case NUMERIC: + cell.setCellValue(cv.getNumberValue()); + break; + case STRING: + cell.setCellValue(new HSSFRichTextString(cv.getStringValue())); + break; + case BLANK: + // never happens - blanks eventually get translated to zero + case FORMULA: + // this will never happen, we have already evaluated the formula + default: + throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); + } + } - /** - * If cell contains formula, it evaluates the formula, and saves the result of the formula. The - * cell remains as a formula cell. If the cell does not contain formula, this method returns -1 - * and leaves the cell unchanged. - * - * Note that the type of the formula result is returned, so you know what kind of - * cached formula result is also stored with the formula. - *
-	 * int evaluatedCellType = evaluator.evaluateFormulaCell(cell);
-	 * 
- * Be aware that your cell will hold both the formula, and the result. If you want the cell - * replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)} - * @param cell The cell to evaluate - * @return -1 for non-formula cells, or the type of the formula result - */ - @Override - public int evaluateFormulaCell(Cell cell) { - return evaluateFormulaCellEnum(cell).getCode(); - } - - /** - * If cell contains formula, it evaluates the formula, and saves the result of the formula. The - * cell remains as a formula cell. If the cell does not contain formula, rather than throwing an - * exception, this method returns {@link CellType#_NONE} and leaves the cell unchanged. - * - * Note that the type of the formula result is returned, so you know what kind of - * cached formula result is also stored with the formula. - *
-	 * CellType evaluatedCellType = evaluator.evaluateFormulaCell(cell);
-	 * 
- * Be aware that your cell will hold both the formula, and the result. If you want the cell - * replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)} - * @param cell The cell to evaluate - * @return {@link CellType#_NONE} for non-formula cells, or the type of the formula result - * @since POI 3.15 beta 3 - * @deprecated POI 3.15 beta 3. Will be deleted when we make the CellType enum transition. See bug 59791. - */ - @Internal - @Override - public CellType evaluateFormulaCellEnum(Cell cell) { - if (cell == null || cell.getCellTypeEnum() != CellType.FORMULA) { - return CellType._NONE; - } - CellValue cv = evaluateFormulaCellValue(cell); - // cell remains a formula cell, but the cached value is changed - setCellValue(cell, cv); - return cv.getCellType(); - } + /** + * Loops over all cells in all sheets of the supplied + * workbook. + * For cells that contain formulas, their formulas are + * evaluated, and the results are saved. These cells + * remain as formula cells. + * For cells that do not contain formulas, no changes + * are made. + * This is a helpful wrapper around looping over all + * cells, and calling evaluateFormulaCell on each one. + */ + public static void evaluateAllFormulaCells(HSSFWorkbook wb) { + evaluateAllFormulaCells(wb, new HSSFFormulaEvaluator(wb)); + } - /** - * If cell contains formula, it evaluates the formula, and - * puts the formula result back into the cell, in place - * of the old formula. - * Else if cell does not contain formula, this method leaves - * the cell unchanged. - * Note that the same instance of HSSFCell is returned to - * allow chained calls like: - *
-	 * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();
-	 * 
- * Be aware that your cell value will be changed to hold the - * result of the formula. If you simply want the formula - * value computed for you, use {@link #evaluateFormulaCellEnum(Cell)}} - */ - @Override - public HSSFCell evaluateInCell(Cell cell) { - if (cell == null) { - return null; - } - HSSFCell result = (HSSFCell) cell; - if (cell.getCellTypeEnum() == CellType.FORMULA) { - CellValue cv = evaluateFormulaCellValue(cell); - setCellValue(cell, cv); - setCellType(cell, cv); // cell will no longer be a formula cell - } - return result; - } - private static void setCellType(Cell cell, CellValue cv) { - CellType cellType = cv.getCellType(); - switch (cellType) { - case BOOLEAN: - case ERROR: - case NUMERIC: - case STRING: - cell.setCellType(cellType); - return; - case BLANK: - // never happens - blanks eventually get translated to zero - case FORMULA: - // this will never happen, we have already evaluated the formula - default: - throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); - } - - } + /** + * Loops over all cells in all sheets of the supplied + * workbook. + * For cells that contain formulas, their formulas are + * evaluated, and the results are saved. These cells + * remain as formula cells. + * For cells that do not contain formulas, no changes + * are made. + * This is a helpful wrapper around looping over all + * cells, and calling evaluateFormulaCell on each one. + */ + public static void evaluateAllFormulaCells(Workbook wb) { + BaseFormulaEvaluator.evaluateAllFormulaCells(wb); + } - private static void setCellValue(Cell cell, CellValue cv) { - CellType cellType = cv.getCellType(); - switch (cellType) { - case BOOLEAN: - cell.setCellValue(cv.getBooleanValue()); - break; - case ERROR: - cell.setCellErrorValue(cv.getErrorValue()); - break; - case NUMERIC: - cell.setCellValue(cv.getNumberValue()); - break; - case STRING: - cell.setCellValue(new HSSFRichTextString(cv.getStringValue())); - break; - case BLANK: - // never happens - blanks eventually get translated to zero - case FORMULA: - // this will never happen, we have already evaluated the formula - default: - throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); - } - } + /** + * Loops over all cells in all sheets of the supplied + * workbook. + * For cells that contain formulas, their formulas are + * evaluated, and the results are saved. These cells + * remain as formula cells. + * For cells that do not contain formulas, no changes + * are made. + * This is a helpful wrapper around looping over all + * cells, and calling evaluateFormulaCell on each one. + */ + @Override + public void evaluateAll() { + evaluateAllFormulaCells(_book, this); + } - /** - * Loops over all cells in all sheets of the supplied - * workbook. - * For cells that contain formulas, their formulas are - * evaluated, and the results are saved. These cells - * remain as formula cells. - * For cells that do not contain formulas, no changes - * are made. - * This is a helpful wrapper around looping over all - * cells, and calling evaluateFormulaCell on each one. - */ - public static void evaluateAllFormulaCells(HSSFWorkbook wb) { - evaluateAllFormulaCells(wb, new HSSFFormulaEvaluator(wb)); - } - - /** - * Loops over all cells in all sheets of the supplied - * workbook. - * For cells that contain formulas, their formulas are - * evaluated, and the results are saved. These cells - * remain as formula cells. - * For cells that do not contain formulas, no changes - * are made. - * This is a helpful wrapper around looping over all - * cells, and calling evaluateFormulaCell on each one. - */ - public static void evaluateAllFormulaCells(Workbook wb) { - FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); - evaluateAllFormulaCells(wb, evaluator); - } - private static void evaluateAllFormulaCells(Workbook wb, FormulaEvaluator evaluator) { - for(int i=0; iindexes. * - * @param indexes + * @param indexes Array of sheets to select, the index is 0-based. */ public void setSelectedTabs(int[] indexes) { Collection list = new ArrayList(indexes.length); @@ -563,7 +564,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss * the 'active' sheet (which is the sheet with focus). * Unselects sheets that are not in indexes. * - * @param indexes + * @param indexes Collection of sheets to select, the index is 0-based. */ public void setSelectedTabs(Collection indexes) { @@ -893,8 +894,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss */ @Override public Iterator sheetIterator() { - Iterator result = new SheetIterator(); - return result; + return new SheetIterator(); } /** @@ -1280,9 +1280,9 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss /** * Closes the underlying {@link NPOIFSFileSystem} from which - * the Workbook was read, if any. Has no effect on Workbooks - * opened from an InputStream, or newly created ones. - *

Once {@link #close()} has been called, no further + * the Workbook was read, if any. + * + *

Once this has been called, no further * operations, updates or reads should be performed on the * Workbook. */ @@ -1531,6 +1531,11 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss return names.get(nameIndex); } + @Override + public List getAllNames() { + return Collections.unmodifiableList(names); + } + public NameRecord getNameRecord(int nameIndex) { return getWorkbook().getNameRecord(nameIndex); } @@ -1702,8 +1707,9 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss * * @param name the name to remove. */ - void removeName(HSSFName name) { - int index = getNameIndex(name); + @Override + public void removeName(Name name) { + int index = getNameIndex((HSSFName) name); removeName(index); } diff --git a/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java b/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java index f681f3ad1..69f8d6768 100644 --- a/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java +++ b/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java @@ -374,20 +374,22 @@ public class CryptoFunctions { // SET Verifier TO 0x0000 short verifier = 0; - // FOR EACH PasswordByte IN PasswordArray IN REVERSE ORDER - for (int i = arrByteChars.length-1; i >= 0; i--) { - // SET Verifier TO Intermediate3 BITWISE XOR PasswordByte + if (!"".equals(password)) { + // FOR EACH PasswordByte IN PasswordArray IN REVERSE ORDER + for (int i = arrByteChars.length-1; i >= 0; i--) { + // SET Verifier TO Intermediate3 BITWISE XOR PasswordByte + verifier = rotateLeftBase15Bit(verifier); + verifier ^= arrByteChars[i]; + } + + // as we haven't prepended the password length into the input array + // we need to do it now separately ... verifier = rotateLeftBase15Bit(verifier); - verifier ^= arrByteChars[i]; + verifier ^= arrByteChars.length; + + // RETURN Verifier BITWISE XOR 0xCE4B + verifier ^= 0xCE4B; // (0x8000 | ('N' << 8) | 'K') } - - // as we haven't prepended the password length into the input array - // we need to do it now separately ... - verifier = rotateLeftBase15Bit(verifier); - verifier ^= arrByteChars.length; - - // RETURN Verifier BITWISE XOR 0xCE4B - verifier ^= 0xCE4B; // (0x8000 | ('N' << 8) | 'K') return verifier & 0xFFFF; } diff --git a/src/java/org/apache/poi/ss/formula/BaseFormulaEvaluator.java b/src/java/org/apache/poi/ss/formula/BaseFormulaEvaluator.java new file mode 100644 index 000000000..8746ba7fa --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/BaseFormulaEvaluator.java @@ -0,0 +1,194 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.ss.formula; + +import java.util.Map; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.CellValue; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +/** + * Common functionality across file formats for evaluating formula cells.

+ */ +public abstract class BaseFormulaEvaluator implements FormulaEvaluator, WorkbookEvaluatorProvider { + protected final WorkbookEvaluator _bookEvaluator; + + protected BaseFormulaEvaluator(WorkbookEvaluator bookEvaluator) { + this._bookEvaluator = bookEvaluator; + } + + /** + * Coordinates several formula evaluators together so that formulas that involve external + * references can be evaluated. + * @param workbookNames the simple file names used to identify the workbooks in formulas + * with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1") + * @param evaluators all evaluators for the full set of workbooks required by the formulas. + */ + public static void setupEnvironment(String[] workbookNames, BaseFormulaEvaluator[] evaluators) { + WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length]; + for (int i = 0; i < wbEvals.length; i++) { + wbEvals[i] = evaluators[i]._bookEvaluator; + } + CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals); + } + + @Override + public void setupReferencedWorkbooks(Map evaluators) { + CollaboratingWorkbooksEnvironment.setupFormulaEvaluator(evaluators); + } + + @Override + public WorkbookEvaluator _getWorkbookEvaluator() { + return _bookEvaluator; + } + + /** + * Should be called whenever there are major changes (e.g. moving sheets) to input cells + * in the evaluated workbook. If performance is not critical, a single call to this method + * may be used instead of many specific calls to the notify~ methods. + * + * Failure to call this method after changing cell values will cause incorrect behaviour + * of the evaluate~ methods of this class + */ + @Override + public void clearAllCachedResultValues() { + _bookEvaluator.clearAllCachedResultValues(); + } + + /** + * If cell contains a formula, the formula is evaluated and returned, + * else the CellValue simply copies the appropriate cell value from + * the cell and also its cell type. This method should be preferred over + * evaluateInCell() when the call should not modify the contents of the + * original cell. + * + * @param cell may be null signifying that the cell is not present (or blank) + * @return null if the supplied cell is null or blank + */ + @Override + public CellValue evaluate(Cell cell) { + if (cell == null) { + return null; + } + + switch (cell.getCellTypeEnum()) { + case BOOLEAN: + return CellValue.valueOf(cell.getBooleanCellValue()); + case ERROR: + return CellValue.getError(cell.getErrorCellValue()); + case FORMULA: + return evaluateFormulaCellValue(cell); + case NUMERIC: + return new CellValue(cell.getNumericCellValue()); + case STRING: + return new CellValue(cell.getRichStringCellValue().getString()); + case BLANK: + return null; + default: + throw new IllegalStateException("Bad cell type (" + cell.getCellTypeEnum() + ")"); + } + } + + protected abstract CellValue evaluateFormulaCellValue(Cell cell); + + /** + * If cell contains formula, it evaluates the formula, and saves the result of the formula. The + * cell remains as a formula cell. If the cell does not contain formula, this method returns -1 + * and leaves the cell unchanged. + * + * Note that the type of the formula result is returned, so you know what kind of + * cached formula result is also stored with the formula. + *

+     * int evaluatedCellType = evaluator.evaluateFormulaCell(cell);
+     * 
+ * Be aware that your cell will hold both the formula, and the result. If you want the cell + * replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)} + * @param cell The cell to evaluate + * @return -1 for non-formula cells, or the type of the formula result + */ + @Override + public int evaluateFormulaCell(Cell cell) { + return evaluateFormulaCellEnum(cell).getCode(); + } + + protected static void setCellType(Cell cell, CellValue cv) { + CellType cellType = cv.getCellType(); + switch (cellType) { + case BOOLEAN: + case ERROR: + case NUMERIC: + case STRING: + cell.setCellType(cellType); + return; + case BLANK: + // never happens - blanks eventually get translated to zero + throw new IllegalArgumentException("This should never happen. Blanks eventually get translated to zero."); + case FORMULA: + // this will never happen, we have already evaluated the formula + throw new IllegalArgumentException("This should never happen. Formulas should have already been evaluated."); + default: + throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); + } + } + + /** + * Loops over all cells in all sheets of the supplied + * workbook. + * For cells that contain formulas, their formulas are + * evaluated, and the results are saved. These cells + * remain as formula cells. + * For cells that do not contain formulas, no changes + * are made. + * This is a helpful wrapper around looping over all + * cells, and calling evaluateFormulaCell on each one. + */ + public static void evaluateAllFormulaCells(Workbook wb) { + FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); + evaluateAllFormulaCells(wb, evaluator); + } + protected static void evaluateAllFormulaCells(Workbook wb, FormulaEvaluator evaluator) { + for(int i=0; i * @@ -61,7 +67,6 @@ import org.apache.poi.ss.formula.eval.ValueEval; public class Subtotal implements Function { private static Function findFunction(int functionCode) throws EvaluationException { - Function func; switch (functionCode) { case 1: return subtotalInstance(AggregateFunction.AVERAGE); case 2: return Count.subtotalInstance(); @@ -87,7 +92,7 @@ public class Subtotal implements Function { return ErrorEval.VALUE_INVALID; } - Function innerFunc; + final Function innerFunc; try { ValueEval ve = OperandResolver.getSingleValue(args[0], srcRowIndex, srcColumnIndex); int functionCode = OperandResolver.coerceValueToInt(ve); @@ -96,9 +101,24 @@ public class Subtotal implements Function { return e.getErrorEval(); } - ValueEval[] innerArgs = new ValueEval[nInnerArgs]; - System.arraycopy(args, 1, innerArgs, 0, nInnerArgs); + // ignore the first arg, this is the function-type, we check for the length above + final List list = new ArrayList(Arrays.asList(args).subList(1, args.length)); - return innerFunc.evaluate(innerArgs, srcRowIndex, srcColumnIndex); + Iterator it = list.iterator(); + + // See https://support.office.com/en-us/article/SUBTOTAL-function-7b027003-f060-4ade-9040-e478765b9939 + // "If there are other subtotals within ref1, ref2,... (or nested subtotals), these nested subtotals are ignored to avoid double counting." + // For array references it is handled in other evaluation steps, but we need to handle this here for references to subtotal-functions + while(it.hasNext()) { + ValueEval eval = it.next(); + if(eval instanceof LazyRefEval) { + LazyRefEval lazyRefEval = (LazyRefEval) eval; + if(lazyRefEval.isSubTotal()) { + it.remove(); + } + } + } + + return innerFunc.evaluate(list.toArray(new ValueEval[list.size()]), srcRowIndex, srcColumnIndex); } } diff --git a/src/java/org/apache/poi/ss/usermodel/CellStyle.java b/src/java/org/apache/poi/ss/usermodel/CellStyle.java index c82ffb98e..b6c971979 100644 --- a/src/java/org/apache/poi/ss/usermodel/CellStyle.java +++ b/src/java/org/apache/poi/ss/usermodel/CellStyle.java @@ -81,7 +81,7 @@ public interface CellStyle { /** * vertically justified vertical alignment - * @deprecated POI 3.15 beta 3. Use {@link VerticalAlignment#TOP} instead. + * @deprecated POI 3.15 beta 3. Use {@link VerticalAlignment#JUSTIFY} instead. */ static final short VERTICAL_JUSTIFY = 0x3; //VerticalAlignment.JUSTIFY.getCode(); diff --git a/src/java/org/apache/poi/ss/usermodel/Workbook.java b/src/java/org/apache/poi/ss/usermodel/Workbook.java index 1fcc29a3e..e52615fb1 100644 --- a/src/java/org/apache/poi/ss/usermodel/Workbook.java +++ b/src/java/org/apache/poi/ss/usermodel/Workbook.java @@ -341,9 +341,11 @@ public interface Workbook extends Closeable, Iterable { /** * Close the underlying input resource (File or Stream), - * from which the Workbook was read. After closing, the - * Workbook should no longer be used. - *

This will have no effect newly created Workbooks. + * from which the Workbook was read. + * + *

Once this has been called, no further + * operations, updates or reads should be performed on the + * Workbook. */ @Override void close() throws IOException; @@ -367,6 +369,13 @@ public interface Workbook extends Closeable, Iterable { */ List getNames(String name); + /** + * Returns all defined names. + * + * @return a list of the defined names. An empty list is returned if none is found. + */ + List getAllNames(); + /** * @param nameIndex position of the named range (0-based) * @return the defined name at the specified index @@ -405,6 +414,13 @@ public interface Workbook extends Closeable, Iterable { */ void removeName(String name); + /** + * Remove a defined name + * + * @param name the name of the defined name + */ + void removeName(Name name); + /** * Adds the linking required to allow formulas referencing * the specified external workbook to be added to this one. diff --git a/src/java/org/apache/poi/util/CommonsLogger.java b/src/java/org/apache/poi/util/CommonsLogger.java index 16a48f28f..1cbb9628c 100644 --- a/src/java/org/apache/poi/util/CommonsLogger.java +++ b/src/java/org/apache/poi/util/CommonsLogger.java @@ -27,19 +27,13 @@ import org.apache.commons.logging.LogFactory; * developers to write log calls, while simultaneously making those * calls as cheap as possible by performing lazy evaluation of the log * message.

- * - * @author Marc Johnson (mjohnson at apache dot org) - * @author Glen Stampoultzis (glens at apache.org) - * @author Nicola Ken Barozzi (nicolaken at apache.org) */ - public class CommonsLogger extends POILogger { - private static LogFactory _creator = LogFactory.getFactory(); private Log log = null; - + @Override public void initialize(final String cat) { this.log = _creator.getInstance(cat); @@ -51,6 +45,7 @@ public class CommonsLogger extends POILogger * @param level One of DEBUG, INFO, WARN, ERROR, FATAL * @param obj1 The object to log. */ + @Override public void log(final int level, final Object obj1) { if(level==FATAL) @@ -104,6 +99,7 @@ public class CommonsLogger extends POILogger * @param obj1 The object to log. This is converted to a string. * @param exception An exception to be logged */ + @Override public void log(final int level, final Object obj1, final Throwable exception) { @@ -175,7 +171,7 @@ public class CommonsLogger extends POILogger * * @param level One of DEBUG, INFO, WARN, ERROR, FATAL */ - + @Override public boolean check(final int level) { if(level==FATAL) diff --git a/src/java/org/apache/poi/util/NullLogger.java b/src/java/org/apache/poi/util/NullLogger.java index 24643c8fd..fe0979cfe 100644 --- a/src/java/org/apache/poi/util/NullLogger.java +++ b/src/java/org/apache/poi/util/NullLogger.java @@ -22,14 +22,10 @@ package org.apache.poi.util; * developers to write log calls, while simultaneously making those * calls as cheap as possible by performing lazy evaluation of the log * message.

- * - * @author Marc Johnson (mjohnson at apache dot org) - * @author Glen Stampoultzis (glens at apache.org) - * @author Nicola Ken Barozzi (nicolaken at apache.org) */ public class NullLogger extends POILogger { @Override - public void initialize(final String cat){ + public void initialize(final String cat) { // do nothing } @@ -41,8 +37,7 @@ public class NullLogger extends POILogger { */ @Override - public void log(final int level, final Object obj1) - { + public void log(final int level, final Object obj1) { // do nothing } @@ -53,6 +48,7 @@ public class NullLogger extends POILogger { * @param obj1 The object to log. This is converted to a string. * @param exception An exception to be logged */ + @Override public void log(int level, Object obj1, final Throwable exception) { // do nothing } diff --git a/src/java/org/apache/poi/util/SystemOutLogger.java b/src/java/org/apache/poi/util/SystemOutLogger.java index e665ba632..36d96ca03 100644 --- a/src/java/org/apache/poi/util/SystemOutLogger.java +++ b/src/java/org/apache/poi/util/SystemOutLogger.java @@ -24,15 +24,12 @@ package org.apache.poi.util; * developers to write log calls, while simultaneously making those * calls as cheap as possible by performing lazy evaluation of the log * message. - * - * @author Marc Johnson (mjohnson at apache dot org) - * @author Glen Stampoultzis (glens at apache.org) - * @author Nicola Ken Barozzi (nicolaken at apache.org) */ public class SystemOutLogger extends POILogger { private String _cat; + @Override public void initialize(final String cat) { this._cat=cat; @@ -44,7 +41,7 @@ public class SystemOutLogger extends POILogger * @param level One of DEBUG, INFO, WARN, ERROR, FATAL * @param obj1 The object to log. */ - + @Override public void log(final int level, final Object obj1) { log(level, obj1, null); @@ -57,6 +54,7 @@ public class SystemOutLogger extends POILogger * @param obj1 The object to log. This is converted to a string. * @param exception An exception to be logged */ + @Override @SuppressForbidden("uses printStackTrace") public void log(final int level, final Object obj1, final Throwable exception) { @@ -78,6 +76,7 @@ public class SystemOutLogger extends POILogger * @see #ERROR * @see #FATAL */ + @Override public boolean check(final int level) { int currentLevel; diff --git a/src/ooxml/java/org/apache/poi/POIXMLDocument.java b/src/ooxml/java/org/apache/poi/POIXMLDocument.java index 55b1a4d18..4ec3d442e 100644 --- a/src/ooxml/java/org/apache/poi/POIXMLDocument.java +++ b/src/ooxml/java/org/apache/poi/POIXMLDocument.java @@ -193,8 +193,12 @@ public abstract class POIXMLDocument extends POIXMLDocumentPart implements Close /** * Closes the underlying {@link OPCPackage} from which this * document was read, if there is one - * - * @throws IOException for writable packages, if an IO exception occur during the saving process. + * + *

Once this has been called, no further + * operations, updates or reads should be performed on the + * document. + * + * @throws IOException for writable packages, if an IO exception occur during the saving process. */ @Override public void close() throws IOException { diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java index a438c061f..e029d7dec 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java @@ -382,8 +382,7 @@ public abstract class OPCPackage implements RelationshipSource, Closeable { } // Creates a new package - OPCPackage pkg = null; - pkg = new ZipPackage(); + OPCPackage pkg = new ZipPackage(); pkg.originalPackagePath = file.getAbsolutePath(); configurePackage(pkg); @@ -391,8 +390,7 @@ public abstract class OPCPackage implements RelationshipSource, Closeable { } public static OPCPackage create(OutputStream output) { - OPCPackage pkg = null; - pkg = new ZipPackage(); + OPCPackage pkg = new ZipPackage(); pkg.originalPackagePath = null; pkg.output = output; @@ -542,7 +540,7 @@ public abstract class OPCPackage implements RelationshipSource, Closeable { // Create the thumbnail part name String contentType = ContentTypes .getContentTypeFromFileExtension(filename); - PackagePartName thumbnailPartName = null; + PackagePartName thumbnailPartName; try { thumbnailPartName = PackagingURIHelper.createPartName("/docProps/" + filename); diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java index 537dd15e9..750f9cd71 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java @@ -29,10 +29,7 @@ import java.util.TreeMap; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.InvalidOperationException; import org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException; -import org.apache.poi.openxml4j.opc.OPCPackage; -import org.apache.poi.openxml4j.opc.PackagePart; -import org.apache.poi.openxml4j.opc.PackagePartName; -import org.apache.poi.openxml4j.opc.PackagingURIHelper; +import org.apache.poi.openxml4j.opc.*; import org.apache.poi.util.DocumentHelper; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -54,7 +51,7 @@ public abstract class ContentTypeManager { /** * Content type namespace */ - public static final String TYPES_NAMESPACE_URI = "http://schemas.openxmlformats.org/package/2006/content-types"; + public static final String TYPES_NAMESPACE_URI = PackageNamespaces.CONTENT_TYPES; /* Xml elements in content type part */ diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java index 3652b89b1..08022e976 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java @@ -304,13 +304,13 @@ implements XSLFShapeContainer, GroupShape { @Override public boolean getFlipHorizontal(){ CTGroupTransform2D xfrm = getXfrm(); - return (xfrm == null || !xfrm.isSetFlipH()) ? false : xfrm.getFlipH(); + return !(xfrm == null || !xfrm.isSetFlipH()) && xfrm.getFlipH(); } @Override public boolean getFlipVertical(){ CTGroupTransform2D xfrm = getXfrm(); - return (xfrm == null || !xfrm.isSetFlipV()) ? false : xfrm.getFlipV(); + return !(xfrm == null || !xfrm.isSetFlipV()) && xfrm.getFlipV(); } @Override @@ -333,7 +333,7 @@ implements XSLFShapeContainer, GroupShape { // recursively update each shape for(XSLFShape shape : gr.getShapes()) { - XSLFShape newShape = null; + XSLFShape newShape; if (shape instanceof XSLFTextBox) { newShape = createTextBox(); } else if (shape instanceof XSLFAutoShape) { diff --git a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java index abba81430..2277d600a 100644 --- a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java +++ b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExportToXml.java @@ -41,7 +41,6 @@ import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; -import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.util.DocumentHelper; @@ -55,7 +54,6 @@ import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFTable; import org.apache.poi.xssf.usermodel.helpers.XSSFSingleXmlCell; import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr; -import org.openxmlformats.schemas.spreadsheetml.x2006.main.STXmlDataType; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; @@ -117,8 +115,7 @@ public class XSSFExportToXml implements Comparator{ * @param validate if true, validates the XML againts the XML Schema * @throws SAXException * @throws ParserConfigurationException - * @throws TransformerException - * @throws InvalidFormatException + * @throws TransformerException */ public void exportToXML(OutputStream os, String encoding, boolean validate) throws SAXException, ParserConfigurationException, TransformerException{ List singleXMLCells = map.getRelatedSingleXMLCell(); @@ -128,10 +125,10 @@ public class XSSFExportToXml implements Comparator{ Document doc = DocumentHelper.createDocument(); - Element root = null; + final Element root; if (isNamespaceDeclared()) { - root=doc.createElementNS(getNamespace(),rootElement); + root = doc.createElementNS(getNamespace(),rootElement); } else { root = doc.createElementNS("", rootElement); } @@ -152,7 +149,6 @@ public class XSSFExportToXml implements Comparator{ tableMappings.put(commonXPath, table); } - Collections.sort(xpaths,this); for(String xpath : xpaths) { @@ -167,8 +163,7 @@ public class XSSFExportToXml implements Comparator{ XSSFCell cell = simpleXmlCell.getReferencedCell(); if (cell!=null) { Node currentNode = getNodeByXPath(xpath,doc.getFirstChild(),doc,false); - STXmlDataType.Enum dataType = simpleXmlCell.getXmlDataType(); - mapCellOnNode(cell,currentNode,dataType); + mapCellOnNode(cell,currentNode); //remove nodes which are empty in order to keep the output xml valid if("".equals(currentNode.getTextContent()) && currentNode.getParentNode() != null) { @@ -202,22 +197,15 @@ public class XSSFExportToXml implements Comparator{ XSSFXmlColumnPr pointer = tableColumns.get(j-startColumnIndex); String localXPath = pointer.getLocalXPath(); Node currentNode = getNodeByXPath(localXPath,tableRootNode,doc,false); - STXmlDataType.Enum dataType = pointer.getXmlDataType(); - - mapCellOnNode(cell,currentNode,dataType); + mapCellOnNode(cell,currentNode); } - } - } - - - } - } else { + } /*else { // TODO: implement filtering management in xpath - } + }*/ } boolean isValid = true; @@ -225,8 +213,6 @@ public class XSSFExportToXml implements Comparator{ isValid =isValid(doc); } - - if (isValid) { ///////////////// @@ -275,7 +261,7 @@ public class XSSFExportToXml implements Comparator{ } - private void mapCellOnNode(XSSFCell cell, Node node, STXmlDataType.Enum outputDataType) { + private void mapCellOnNode(XSSFCell cell, Node node) { String value =""; switch (cell.getCellTypeEnum()) { @@ -349,11 +335,7 @@ public class XSSFExportToXml implements Comparator{ } currentNode = selectedNode; } else { - - - Node attribute = createAttribute(doc, currentNode, axisName); - - currentNode = attribute; + currentNode = createAttribute(doc, currentNode, axisName); } } return currentNode; @@ -421,12 +403,11 @@ public class XSSFExportToXml implements Comparator{ for(int i =1;i { }if ( leftIndex > rightIndex) { return 1; } - } else { + } /*else { // NOTE: the xpath doesn't match correctly in the schema - } + }*/ } } @@ -483,7 +464,7 @@ public class XSSFExportToXml implements Comparator{ // Note: we expect that all the complex types are defined at root level Node complexTypeNode = null; if (!"".equals(complexTypeName)) { - complexTypeNode = getComplexTypeNodeFromSchemaChildren(xmlSchema, complexTypeNode, complexTypeName); + complexTypeNode = getComplexTypeNodeFromSchemaChildren(xmlSchema, null, complexTypeName); } return complexTypeNode; diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFCell.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFCell.java index 6c6574443..6174af54b 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFCell.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFCell.java @@ -338,7 +338,11 @@ public class SXSSFCell implements Cell { } if(_value.getType()==CellType.FORMULA) - ((StringFormulaValue)_value).setPreEvaluatedValue(value); + if(_value instanceof NumericFormulaValue) { + ((NumericFormulaValue) _value).setPreEvaluatedValue(Double.parseDouble(value)); + } else { + ((StringFormulaValue) _value).setPreEvaluatedValue(value); + } else ((PlainStringValue)_value).setValue(value); } else { @@ -956,6 +960,7 @@ public class SXSSFCell implements Cell { } /*package*/ void setFormulaType(CellType type) { + Value prevValue = _value; switch(type) { case NUMERIC: @@ -983,7 +988,13 @@ public class SXSSFCell implements Cell { throw new IllegalArgumentException("Illegal type " + type); } } + + // if we had a Formula before, we should copy over the _value of the formula + if(prevValue instanceof FormulaValue) { + ((FormulaValue)_value)._value = ((FormulaValue)prevValue)._value; + } } + //TODO: implement this correctly @NotImplemented /*package*/ CellType computeTypeFromFormula(String formula) diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java index 6993579cc..331ad9a0f 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java @@ -893,8 +893,11 @@ public class SXSSFWorkbook implements Workbook { /** * Closes the underlying {@link XSSFWorkbook} and {@link OPCPackage} - * on which this Workbook is based, if any. Has no effect on Workbooks - * created from scratch. + * on which this Workbook is based, if any. + * + *

Once this has been called, no further + * operations, updates or reads should be performed on the + * Workbook. */ @Override public void close() throws IOException { @@ -1003,12 +1006,25 @@ public class SXSSFWorkbook implements Workbook { return _wb.getNames(name); } + /** + * Returns all defined names + * + * @return all defined names + */ + @Override + public List getAllNames() + { + return _wb.getAllNames(); + } + /** * @param nameIndex position of the named range (0-based) * @return the defined name at the specified index * @throws IllegalArgumentException if the supplied index is invalid + * @deprecated 3.16. New projects should avoid accessing named ranges by index. */ @Override + @Deprecated public Name getNameAt(int nameIndex) { return _wb.getNameAt(nameIndex); @@ -1033,8 +1049,12 @@ public class SXSSFWorkbook implements Workbook { * * @param name the name of the defined name * @return zero based index of the defined name. -1 if not found. + * + * @deprecated 3.16. New projects should avoid accessing named ranges by index. + * Use {@link #getName(String)} instead. */ @Override + @Deprecated public int getNameIndex(String name) { return _wb.getNameIndex(name); @@ -1044,8 +1064,11 @@ public class SXSSFWorkbook implements Workbook { * Remove the defined name at the specified index * * @param index named range index (0 based) + * + * @deprecated 3.16. New projects should use {@link #removeName(Name)}. */ @Override + @Deprecated public void removeName(int index) { _wb.removeName(index); @@ -1054,10 +1077,24 @@ public class SXSSFWorkbook implements Workbook { /** * Remove a defined name by name * - * @param name the name of the defined name + * @param name the name of the defined name + * + * @deprecated 3.16. New projects should use {@link #removeName(Name)}. */ @Override + @Deprecated public void removeName(String name) + { + _wb.removeName(name); + } + + /** + * Remove the given defined name + * + * @param name the name to remove + */ + @Override + public void removeName(Name name) { _wb.removeName(name); } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java index f3c6bc267..9c5e6ffb1 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java @@ -232,7 +232,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork // Otherwise, try it as a named range if (sheet == null) { - if (_uBook.getNameIndex(name) > -1) { + if (!_uBook.getNames(name).isEmpty()) { return new NameXPxg(null, name); } return null; diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFFormulaEvaluator.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFFormulaEvaluator.java index 5fe8660b1..c6c030b95 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFFormulaEvaluator.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFFormulaEvaluator.java @@ -17,12 +17,9 @@ package org.apache.poi.xssf.usermodel; -import java.util.Map; - -import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment; +import org.apache.poi.ss.formula.BaseFormulaEvaluator; import org.apache.poi.ss.formula.EvaluationCell; import org.apache.poi.ss.formula.WorkbookEvaluator; -import org.apache.poi.ss.formula.WorkbookEvaluatorProvider; import org.apache.poi.ss.formula.eval.BoolEval; import org.apache.poi.ss.formula.eval.ErrorEval; import org.apache.poi.ss.formula.eval.NumberEval; @@ -31,28 +28,16 @@ import org.apache.poi.ss.formula.eval.ValueEval; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.CellValue; -import org.apache.poi.ss.usermodel.FormulaEvaluator; import org.apache.poi.util.Internal; /** * Internal POI use only - parent of XSSF and SXSSF formula evaluators */ -public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, WorkbookEvaluatorProvider { - private WorkbookEvaluator _bookEvaluator; - +public abstract class BaseXSSFFormulaEvaluator extends BaseFormulaEvaluator { protected BaseXSSFFormulaEvaluator(WorkbookEvaluator bookEvaluator) { - _bookEvaluator = bookEvaluator; + super(bookEvaluator); } - /** - * Should be called whenever there are major changes (e.g. moving sheets) to input cells - * in the evaluated workbook. - * Failure to call this method after changing cell values will cause incorrect behaviour - * of the evaluate~ methods of this class - */ - public void clearAllCachedResultValues() { - _bookEvaluator.clearAllCachedResultValues(); - } public void notifySetFormula(Cell cell) { _bookEvaluator.notifyUpdateCell(new XSSFEvaluationCell((XSSFCell)cell)); } @@ -63,60 +48,6 @@ public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, Work _bookEvaluator.notifyUpdateCell(new XSSFEvaluationCell((XSSFCell)cell)); } - /** - * If cell contains a formula, the formula is evaluated and returned, - * else the CellValue simply copies the appropriate cell value from - * the cell and also its cell type. This method should be preferred over - * evaluateInCell() when the call should not modify the contents of the - * original cell. - * @param cell - */ - public CellValue evaluate(Cell cell) { - if (cell == null) { - return null; - } - - switch (cell.getCellTypeEnum()) { - case BOOLEAN: - return CellValue.valueOf(cell.getBooleanCellValue()); - case ERROR: - return CellValue.getError(cell.getErrorCellValue()); - case FORMULA: - return evaluateFormulaCellValue(cell); - case NUMERIC: - return new CellValue(cell.getNumericCellValue()); - case STRING: - return new CellValue(cell.getRichStringCellValue().getString()); - case BLANK: - return null; - default: - throw new IllegalStateException("Bad cell type (" + cell.getCellTypeEnum() + ")"); - } - } - - - /** - * If cell contains formula, it evaluates the formula, - * and saves the result of the formula. The cell - * remains as a formula cell. - * Else if cell does not contain formula, this method leaves - * the cell unchanged. - * Note that the type of the formula result is returned, - * so you know what kind of value is also stored with - * the formula. - *

-     * int evaluatedCellType = evaluator.evaluateFormulaCell(cell);
-     * 
- * Be aware that your cell will hold both the formula, - * and the result. If you want the cell replaced with - * the result of the formula, use {@link #evaluate(org.apache.poi.ss.usermodel.Cell)} } - * @param cell The cell to evaluate - * @return The type of the formula result (the cell's type remains as CellType.FORMULA however) - */ - public int evaluateFormulaCell(Cell cell) { - return evaluateFormulaCellEnum(cell).getCode(); - } - /** * If cell contains formula, it evaluates the formula, * and saves the result of the formula. The cell @@ -164,27 +95,6 @@ public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, Work setCellValue(cell, cv); } } - private static void setCellType(Cell cell, CellValue cv) { - CellType cellType = cv.getCellType(); - switch (cellType) { - case BOOLEAN: - case ERROR: - case NUMERIC: - case STRING: - cell.setCellType(cellType); - return; - case BLANK: - // never happens - blanks eventually get translated to zero - throw new IllegalArgumentException("This should never happen. Blanks eventually get translated to zero."); - case FORMULA: - // this will never happen, we have already evaluated the formula - throw new IllegalArgumentException("This should never happen. Formulas should have already been evaluated."); - default: - throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); - - } - - } private static void setCellValue(Cell cell, CellValue cv) { CellType cellType = cv.getCellType(); @@ -218,7 +128,7 @@ public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, Work /** * Returns a CellValue wrapper around the supplied ValueEval instance. */ - private CellValue evaluateFormulaCellValue(Cell cell) { + protected CellValue evaluateFormulaCellValue(Cell cell) { EvaluationCell evalCell = toEvaluationCell(cell); ValueEval eval = _bookEvaluator.evaluate(evalCell); if (eval instanceof NumberEval) { @@ -238,22 +148,4 @@ public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, Work } throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")"); } - - public void setupReferencedWorkbooks(Map evaluators) { - CollaboratingWorkbooksEnvironment.setupFormulaEvaluator(evaluators); - } - - public WorkbookEvaluator _getWorkbookEvaluator() { - return _bookEvaluator; - } - - /** {@inheritDoc} */ - public void setIgnoreMissingWorkbooks(boolean ignore){ - _bookEvaluator.setIgnoreMissingWorkbooks(ignore); - } - - /** {@inheritDoc} */ - public void setDebugEvaluationOutputForNextEval(boolean value){ - _bookEvaluator.setDebugEvaluationOutputForNextEval(value); - } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java index ec9ecd462..fc456b7e5 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java @@ -17,7 +17,7 @@ package org.apache.poi.xssf.usermodel; -import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.ss.formula.BaseFormulaEvaluator; import org.apache.poi.ss.formula.EvaluationCell; import org.apache.poi.ss.formula.IStabilityClassifier; import org.apache.poi.ss.formula.WorkbookEvaluator; @@ -88,7 +88,7 @@ public final class XSSFFormulaEvaluator extends BaseXSSFFormulaEvaluator { * cells, and calling evaluateFormulaCell on each one. */ public static void evaluateAllFormulaCells(XSSFWorkbook wb) { - HSSFFormulaEvaluator.evaluateAllFormulaCells(wb); + BaseFormulaEvaluator.evaluateAllFormulaCells(wb); } /** * Loops over all cells in all sheets of the supplied @@ -102,7 +102,7 @@ public final class XSSFFormulaEvaluator extends BaseXSSFFormulaEvaluator { * cells, and calling evaluateFormulaCell on each one. */ public void evaluateAll() { - HSSFFormulaEvaluator.evaluateAllFormulaCells(_book); + evaluateAllFormulaCells(_book, this); } /** diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java index 462bd6517..071062620 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java @@ -167,19 +167,18 @@ public final class XSSFName implements Name { public void setNameName(String name) { validateName(name); + String oldName = getNameName(); int sheetIndex = getSheetIndex(); - int numberOfNames = _workbook.getNumberOfNames(); //Check to ensure no other names have the same case-insensitive name at the same scope - for (int i = 0; i < numberOfNames; i++) { - XSSFName nm = _workbook.getNameAt(i); - if ((nm != this) - && name.equalsIgnoreCase(nm.getNameName()) - && (sheetIndex == nm.getSheetIndex())) { + for (XSSFName foundName : _workbook.getNames(name)) { + if (foundName.getSheetIndex() == sheetIndex && foundName != this) { String msg = "The "+(sheetIndex == -1 ? "workbook" : "sheet")+" already contains this name: " + name; throw new IllegalArgumentException(msg); } } _ctName.setName(name); + //Need to update the name -> named ranges map + _workbook.updateName(this, oldName); } public String getRefersToFormula() { diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java index 2271e0b4d..5f1da12ec 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java @@ -18,8 +18,8 @@ package org.apache.poi.xssf.usermodel; import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; -import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.setPassword; -import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.validatePassword; +import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.setPassword; +import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.validatePassword; import java.io.IOException; import java.io.InputStream; diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index 1e9b3d6ce..afddcd9c4 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -18,8 +18,8 @@ package org.apache.poi.xssf.usermodel; import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; -import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.setPassword; -import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.validatePassword; +import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.setPassword; +import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.validatePassword; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -29,16 +29,20 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.regex.Pattern; import javax.xml.namespace.QName; +import org.apache.commons.collections4.ListValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.poi.POIXMLDocument; import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.POIXMLException; @@ -59,6 +63,7 @@ import org.apache.poi.ss.formula.SheetNameFormatter; import org.apache.poi.ss.formula.udf.AggregatingUDFFinder; import org.apache.poi.ss.formula.udf.IndexedUDFFinder; import org.apache.poi.ss.formula.udf.UDFFinder; +import org.apache.poi.ss.usermodel.Name; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; import org.apache.poi.ss.usermodel.Sheet; @@ -140,6 +145,11 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { */ private List sheets; + /** + * this holds the XSSFName objects attached to this workbook, keyed by lower-case name + */ + private ListValuedMap namedRangesByName; + /** * this holds the XSSFName objects attached to this workbook */ @@ -442,6 +452,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { stylesSource.setWorkbook(this); namedRanges = new ArrayList(); + namedRangesByName = new ArrayListValuedHashMap(); sheets = new ArrayList(); pivotTables = new ArrayList(); } @@ -733,8 +744,13 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { public XSSFName createName() { CTDefinedName ctName = CTDefinedName.Factory.newInstance(); ctName.setName(""); + return createAndStoreName(ctName); + } + + private XSSFName createAndStoreName(CTDefinedName ctName) { XSSFName name = new XSSFName(ctName, this); namedRanges.add(name); + namedRangesByName.put(ctName.getName().toLowerCase(Locale.ENGLISH), name); return name; } @@ -938,28 +954,47 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { return stylesSource.getFontAt(idx); } + /** + * Get the first named range with the given name. + * + * Note: names of named ranges are not unique as they are scoped by sheet. + * {@link #getNames(String name)} returns all named ranges with the given name. + * + * @param name named range name + * @return XSSFName with the given name. null is returned no named range could be found. + */ @Override public XSSFName getName(String name) { - int nameIndex = getNameIndex(name); - if (nameIndex < 0) { + Collection list = getNames(name); + if (list.isEmpty()) { return null; } - return namedRanges.get(nameIndex); + return list.iterator().next(); } + /** + * Get the named ranges with the given name. + * Note:Excel named ranges are case-insensitive and + * this method performs a case-insensitive search. + * + * @param name named range name + * @return list of XSSFNames with the given name. An empty list if no named ranges could be found + */ @Override public List getNames(String name) { - List names = new ArrayList(); - for(XSSFName nr : namedRanges) { - if(nr.getNameName().equals(name)) { - names.add(nr); - } - } - - return names; + return Collections.unmodifiableList(namedRangesByName.get(name.toLowerCase(Locale.ENGLISH))); } + /** + * Get the named range at the given index. + * + * @param nameIndex the index of the named range + * @return the XSSFName at the given index + * + * @deprecated 3.16. New projects should avoid accessing named ranges by index. + */ @Override + @Deprecated public XSSFName getNameAt(int nameIndex) { int nNames = namedRanges.size(); if (nNames < 1) { @@ -973,21 +1008,30 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { } /** - * Gets the named range index by his name - * Note:Excel named ranges are case-insensitive and - * this method performs a case-insensitive search. + * Get a list of all the named ranges in the workbook. * - * @param name named range name - * @return named range index + * @return list of XSSFNames in the workbook */ @Override + public List getAllNames() { + return Collections.unmodifiableList(namedRanges); + } + + /** + * Gets the named range index by name. + * + * @param name named range name + * @return named range index. -1 is returned if no named ranges could be found. + * + * @deprecated 3.16. New projects should avoid accessing named ranges by index. + * Use {@link #getName(String)} instead. + */ + @Override + @Deprecated public int getNameIndex(String name) { - int i = 0; - for(XSSFName nr : namedRanges) { - if(nr.getNameName().equals(name)) { - return i; - } - i++; + XSSFName nm = getName(name); + if (nm != null) { + return namedRanges.indexOf(nm); } return -1; } @@ -1258,22 +1302,40 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { return getPackagePart().getContentType().equals(XSSFRelation.MACROS_WORKBOOK.getContentType()); } + /** + * Remove the named range at the given index. + * + * @param nameIndex the index of the named range name to remove + * + * @deprecated 3.16. New projects should use {@link #removeName(Name)}. + */ @Override + @Deprecated public void removeName(int nameIndex) { - namedRanges.remove(nameIndex); + removeName(getNameAt(nameIndex)); } + /** + * Remove the first named range found with the given name. + * + * Note: names of named ranges are not unique (name + sheet + * index is unique), so {@link #removeName(Name)} should + * be used if possible. + * + * @param name the named range name to remove + * + * @throws IllegalArgumentException if no named range could be found + * + * @deprecated 3.16. New projects should use {@link #removeName(Name)}. + */ @Override + @Deprecated public void removeName(String name) { - int idx = 0; - for (XSSFName nm : namedRanges) { - if(nm.getNameName().equalsIgnoreCase(name)) { - removeName(idx); - return; - } - idx++; + List names = namedRangesByName.get(name.toLowerCase(Locale.ENGLISH)); + if (names.isEmpty()) { + throw new IllegalArgumentException("Named range was not found: " + name); } - throw new IllegalArgumentException("Named range was not found: " + name); + removeName(names.get(0)); } @@ -1282,13 +1344,24 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { * (name + sheet index is unique), this method is more accurate. * * @param name the name to remove. + * + * @throws IllegalArgumentException if the named range is not a part of this XSSFWorkbook */ - void removeName(XSSFName name) { - if (!namedRanges.remove(name)) { + @Override + public void removeName(Name name) { + if (!namedRangesByName.removeMapping(name.getNameName().toLowerCase(Locale.ENGLISH), name) + || !namedRanges.remove(name)) { throw new IllegalArgumentException("Name was not found: " + name); } } + void updateName(XSSFName name, String oldName) { + if (!namedRangesByName.removeMapping(oldName.toLowerCase(Locale.ENGLISH), name)) { + throw new IllegalArgumentException("Name was not found: " + name); + } + namedRangesByName.put(name.getNameName().toLowerCase(Locale.ENGLISH), name); + } + /** * Delete the printarea for the sheet specified @@ -1297,13 +1370,9 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { */ @Override public void removePrintArea(int sheetIndex) { - int cont = 0; - for (XSSFName name : namedRanges) { - if (name.getNameName().equals(XSSFName.BUILTIN_PRINT_AREA) && name.getSheetIndex() == sheetIndex) { - namedRanges.remove(cont); - break; - } - cont++; + XSSFName name = getBuiltInName(XSSFName.BUILTIN_PRINT_AREA, sheetIndex); + if (name != null) { + removeName(name); } } @@ -1369,17 +1438,20 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { } //adjust indices of names ranges - for (Iterator it = namedRanges.iterator(); it.hasNext();) { - XSSFName nm = it.next(); + List toRemove = new ArrayList(); + for (XSSFName nm : namedRanges) { CTDefinedName ct = nm.getCTName(); if(!ct.isSetLocalSheetId()) continue; if (ct.getLocalSheetId() == index) { - it.remove(); + toRemove.add(nm); } else if (ct.getLocalSheetId() > index){ // Bump down by one, so still points at the same sheet ct.setLocalSheetId(ct.getLocalSheetId()-1); } } + for (XSSFName nm : toRemove) { + removeName(nm); + } } /** @@ -1514,8 +1586,8 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { } XSSFName getBuiltInName(String builtInCode, int sheetNumber) { - for (XSSFName name : namedRanges) { - if (name.getNameName().equalsIgnoreCase(builtInCode) && name.getSheetIndex() == sheetNumber) { + for (XSSFName name : namedRangesByName.get(builtInCode.toLowerCase(Locale.ENGLISH))) { + if (name.getSheetIndex() == sheetNumber) { return name; } } @@ -1537,15 +1609,12 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { nameRecord.setName(builtInName); nameRecord.setLocalSheetId(sheetNumber); - XSSFName name = new XSSFName(nameRecord, this); - for (XSSFName nr : namedRanges) { - if (nr.equals(name)) - throw new POIXMLException("Builtin (" + builtInName - + ") already exists for sheet (" + sheetNumber + ")"); + if (getBuiltInName(builtInName, sheetNumber) != null) { + throw new POIXMLException("Builtin (" + builtInName + + ") already exists for sheet (" + sheetNumber + ")"); } - namedRanges.add(name); - return name; + return createAndStoreName(nameRecord); } /** @@ -1665,10 +1734,11 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { } private void reprocessNamedRanges() { + namedRangesByName = new ArrayListValuedHashMap(); namedRanges = new ArrayList(); if(workbook.isSetDefinedNames()) { for(CTDefinedName ctName : workbook.getDefinedNames().getDefinedNameArray()) { - namedRanges.add(new XSSFName(ctName, this)); + createAndStoreName(ctName); } } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java index 1d146c09c..ef0c5ea63 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java @@ -65,9 +65,7 @@ public final class XSSFFormulaUtils { */ public void updateSheetName(final int sheetIndex, final String oldName, final String newName) { // update named ranges - final int numberOfNames = _wb.getNumberOfNames(); - for (int i = 0; i < numberOfNames; i++) { - XSSFName nm = _wb.getNameAt(i); + for (XSSFName nm : _wb.getAllNames()) { if (nm.getSheetIndex() == -1 || nm.getSheetIndex() == sheetIndex) { updateName(nm, oldName, newName); } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPasswordHelper.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPasswordHelper.java new file mode 100644 index 000000000..46e47f688 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPasswordHelper.java @@ -0,0 +1,136 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ + +package org.apache.poi.xssf.usermodel.helpers; + +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Locale; + +import javax.xml.bind.DatatypeConverter; +import javax.xml.namespace.QName; + +import org.apache.poi.poifs.crypt.CryptoFunctions; +import org.apache.poi.poifs.crypt.HashAlgorithm; +import org.apache.poi.util.Internal; +import org.apache.xmlbeans.XmlCursor; +import org.apache.xmlbeans.XmlObject; + +@Internal(since="3.15 beta 3") +public final class XSSFPasswordHelper { + private XSSFPasswordHelper() { + // no instances of this static class + } + + /** + * Sets the XORed or hashed password + * + * @param xobj the xmlbeans object which contains the password attributes + * @param password the password, if null, the password attributes will be removed + * @param hashAlgo the hash algorithm, if null the password will be XORed + * @param prefix the prefix of the password attributes, may be null + */ + public static void setPassword(XmlObject xobj, String password, HashAlgorithm hashAlgo, String prefix) { + XmlCursor cur = xobj.newCursor(); + + if (password == null) { + cur.removeAttribute(getAttrName(prefix, "password")); + cur.removeAttribute(getAttrName(prefix, "algorithmName")); + cur.removeAttribute(getAttrName(prefix, "hashValue")); + cur.removeAttribute(getAttrName(prefix, "saltValue")); + cur.removeAttribute(getAttrName(prefix, "spinCount")); + return; + } + + cur.toFirstContentToken(); + if (hashAlgo == null) { + int hash = CryptoFunctions.createXorVerifier1(password); + cur.insertAttributeWithValue(getAttrName(prefix, "password"), + String.format(Locale.ROOT, "%04X", hash).toUpperCase(Locale.ROOT)); + } else { + SecureRandom random = new SecureRandom(); + byte salt[] = random.generateSeed(16); + + // Iterations specifies the number of times the hashing function shall be iteratively run (using each + // iteration's result as the input for the next iteration). + int spinCount = 100000; + + // Implementation Notes List: + // --> In this third stage, the reversed byte order legacy hash from the second stage shall + // be converted to Unicode hex string representation + byte hash[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCount, false); + + cur.insertAttributeWithValue(getAttrName(prefix, "algorithmName"), hashAlgo.jceId); + cur.insertAttributeWithValue(getAttrName(prefix, "hashValue"), DatatypeConverter.printBase64Binary(hash)); + cur.insertAttributeWithValue(getAttrName(prefix, "saltValue"), DatatypeConverter.printBase64Binary(salt)); + cur.insertAttributeWithValue(getAttrName(prefix, "spinCount"), ""+spinCount); + } + cur.dispose(); + } + + /** + * Validates the password, i.e. + * calculates the hash of the given password and compares it against the stored hash + * + * @param xobj the xmlbeans object which contains the password attributes + * @param password the password, if null the method will always return false, + * even if there's no password set + * @param prefix the prefix of the password attributes, may be null + * + * @return true, if the hashes match + */ + public static boolean validatePassword(XmlObject xobj, String password, String prefix) { + // TODO: is "velvetSweatshop" the default password? + if (password == null) return false; + + XmlCursor cur = xobj.newCursor(); + String xorHashVal = cur.getAttributeText(getAttrName(prefix, "password")); + String algoName = cur.getAttributeText(getAttrName(prefix, "algorithmName")); + String hashVal = cur.getAttributeText(getAttrName(prefix, "hashValue")); + String saltVal = cur.getAttributeText(getAttrName(prefix, "saltValue")); + String spinCount = cur.getAttributeText(getAttrName(prefix, "spinCount")); + cur.dispose(); + + if (xorHashVal != null) { + int hash1 = Integer.parseInt(xorHashVal, 16); + int hash2 = CryptoFunctions.createXorVerifier1(password); + return hash1 == hash2; + } else { + if (hashVal == null || algoName == null || saltVal == null || spinCount == null) { + return false; + } + + byte hash1[] = DatatypeConverter.parseBase64Binary(hashVal); + HashAlgorithm hashAlgo = HashAlgorithm.fromString(algoName); + byte salt[] = DatatypeConverter.parseBase64Binary(saltVal); + int spinCnt = Integer.parseInt(spinCount); + byte hash2[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCnt, false); + return Arrays.equals(hash1, hash2); + } + } + + + private static QName getAttrName(String prefix, String name) { + if (prefix == null || "".equals(prefix)) { + return new QName(name); + } else { + return new QName(prefix+Character.toUpperCase(name.charAt(0))+name.substring(1)); + } + } +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java index c91049948..4e3c90819 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFPaswordHelper.java @@ -1,130 +1,60 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - */ - -package org.apache.poi.xssf.usermodel.helpers; - -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Locale; - -import javax.xml.bind.DatatypeConverter; -import javax.xml.namespace.QName; - -import org.apache.poi.poifs.crypt.CryptoFunctions; -import org.apache.poi.poifs.crypt.HashAlgorithm; -import org.apache.xmlbeans.XmlCursor; -import org.apache.xmlbeans.XmlObject; - -public class XSSFPaswordHelper { - /** - * Sets the XORed or hashed password - * - * @param xobj the xmlbeans object which contains the password attributes - * @param password the password, if null, the password attributes will be removed - * @param hashAlgo the hash algorithm, if null the password will be XORed - * @param prefix the prefix of the password attributes, may be null - */ - public static void setPassword(XmlObject xobj, String password, HashAlgorithm hashAlgo, String prefix) { - XmlCursor cur = xobj.newCursor(); - - if (password == null) { - cur.removeAttribute(getAttrName(prefix, "password")); - cur.removeAttribute(getAttrName(prefix, "algorithmName")); - cur.removeAttribute(getAttrName(prefix, "hashValue")); - cur.removeAttribute(getAttrName(prefix, "saltValue")); - cur.removeAttribute(getAttrName(prefix, "spinCount")); - return; - } - - cur.toFirstContentToken(); - if (hashAlgo == null) { - int hash = CryptoFunctions.createXorVerifier1(password); - cur.insertAttributeWithValue(getAttrName(prefix, "password"), - Integer.toHexString(hash).toUpperCase(Locale.ROOT)); - } else { - SecureRandom random = new SecureRandom(); - byte salt[] = random.generateSeed(16); - - // Iterations specifies the number of times the hashing function shall be iteratively run (using each - // iteration's result as the input for the next iteration). - int spinCount = 100000; - - // Implementation Notes List: - // --> In this third stage, the reversed byte order legacy hash from the second stage shall - // be converted to Unicode hex string representation - byte hash[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCount, false); - - cur.insertAttributeWithValue(getAttrName(prefix, "algorithmName"), hashAlgo.jceId); - cur.insertAttributeWithValue(getAttrName(prefix, "hashValue"), DatatypeConverter.printBase64Binary(hash)); - cur.insertAttributeWithValue(getAttrName(prefix, "saltValue"), DatatypeConverter.printBase64Binary(salt)); - cur.insertAttributeWithValue(getAttrName(prefix, "spinCount"), ""+spinCount); - } - cur.dispose(); - } - - /** - * Validates the password, i.e. - * calculates the hash of the given password and compares it against the stored hash - * - * @param xobj the xmlbeans object which contains the password attributes - * @param password the password, if null the method will always return false, - * even if there's no password set - * @param prefix the prefix of the password attributes, may be null - * - * @return true, if the hashes match - */ - public static boolean validatePassword(XmlObject xobj, String password, String prefix) { - // TODO: is "velvetSweatshop" the default password? - if (password == null) return false; - - XmlCursor cur = xobj.newCursor(); - String xorHashVal = cur.getAttributeText(getAttrName(prefix, "password")); - String algoName = cur.getAttributeText(getAttrName(prefix, "algorithmName")); - String hashVal = cur.getAttributeText(getAttrName(prefix, "hashValue")); - String saltVal = cur.getAttributeText(getAttrName(prefix, "saltValue")); - String spinCount = cur.getAttributeText(getAttrName(prefix, "spinCount")); - cur.dispose(); - - if (xorHashVal != null) { - int hash1 = Integer.parseInt(xorHashVal, 16); - int hash2 = CryptoFunctions.createXorVerifier1(password); - return hash1 == hash2; - } else { - if (hashVal == null || algoName == null || saltVal == null || spinCount == null) { - return false; - } - - byte hash1[] = DatatypeConverter.parseBase64Binary(hashVal); - HashAlgorithm hashAlgo = HashAlgorithm.fromString(algoName); - byte salt[] = DatatypeConverter.parseBase64Binary(saltVal); - int spinCnt = Integer.parseInt(spinCount); - byte hash2[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCnt, false); - return Arrays.equals(hash1, hash2); - } - } - - - private static QName getAttrName(String prefix, String name) { - if (prefix == null || "".equals(prefix)) { - return new QName(name); - } else { - return new QName(prefix+Character.toUpperCase(name.charAt(0))+name.substring(1)); - } - } -} +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ + +package org.apache.poi.xssf.usermodel.helpers; + +import org.apache.poi.poifs.crypt.HashAlgorithm; +import org.apache.poi.util.Internal; +import org.apache.poi.util.Removal; +import org.apache.xmlbeans.XmlObject; + +/** + * @deprecated POI 3.15 beta 3. Use {@link XSSFPasswordHelper} instead. + */ +@Internal(since="3.15 beta 3") +@Deprecated +@Removal(version="3.17") +public class XSSFPaswordHelper { + /** + * Sets the XORed or hashed password + * + * @param xobj the xmlbeans object which contains the password attributes + * @param password the password, if null, the password attributes will be removed + * @param hashAlgo the hash algorithm, if null the password will be XORed + * @param prefix the prefix of the password attributes, may be null + */ + public static void setPassword(XmlObject xobj, String password, HashAlgorithm hashAlgo, String prefix) { + XSSFPasswordHelper.setPassword(xobj, password, hashAlgo, prefix); + } + + /** + * Validates the password, i.e. + * calculates the hash of the given password and compares it against the stored hash + * + * @param xobj the xmlbeans object which contains the password attributes + * @param password the password, if null the method will always return false, + * even if there's no password set + * @param prefix the prefix of the password attributes, may be null + * + * @return true, if the hashes match + */ + public static boolean validatePassword(XmlObject xobj, String password, String prefix) { + return XSSFPasswordHelper.validatePassword(xobj, password, prefix); + } +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java index 63104dd9e..d11ed1fa8 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java @@ -83,9 +83,7 @@ public final class XSSFRowShifter extends RowShifter { public void updateNamedRanges(FormulaShifter shifter) { Workbook wb = sheet.getWorkbook(); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create((XSSFWorkbook) wb); - final int numberOfNames = wb.getNumberOfNames(); - for (int i = 0; i < numberOfNames; i++) { - Name name = wb.getNameAt(i); + for (Name name : wb.getAllNames()) { String formula = name.getRefersToFormula(); int sheetIndex = name.getSheetIndex(); final int rowIndex = -1; //don't care, named ranges are not allowed to include structured references diff --git a/src/ooxml/testcases/org/apache/poi/openxml4j/opc/internal/TestContentTypeManager.java b/src/ooxml/testcases/org/apache/poi/openxml4j/opc/internal/TestContentTypeManager.java index 9acf0d10b..936d0f9e8 100644 --- a/src/ooxml/testcases/org/apache/poi/openxml4j/opc/internal/TestContentTypeManager.java +++ b/src/ooxml/testcases/org/apache/poi/openxml4j/opc/internal/TestContentTypeManager.java @@ -18,6 +18,7 @@ package org.apache.poi.openxml4j.opc.internal; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; @@ -44,16 +45,21 @@ public final class TestContentTypeManager { // Retrieves core properties part OPCPackage p = OPCPackage.open(filepath, PackageAccess.READ); - PackageRelationshipCollection rels = p.getRelationshipsByType(PackageRelationshipTypes.CORE_PROPERTIES); - PackageRelationship corePropertiesRelationship = rels.getRelationship(0); - PackagePart coreDocument = p.getPart(corePropertiesRelationship); - - assertEquals("application/vnd.openxmlformats-package.core-properties+xml", coreDocument.getContentType()); + try { + PackageRelationshipCollection rels = p.getRelationshipsByType(PackageRelationshipTypes.CORE_PROPERTIES); + PackageRelationship corePropertiesRelationship = rels.getRelationship(0); + PackagePart coreDocument = p.getPart(corePropertiesRelationship); - // TODO - finish writing this test - assumeTrue("finish writing this test", false); - - ContentTypeManager ctm = new ZipContentTypeManager(coreDocument.getInputStream(), p); + assertEquals("application/vnd.openxmlformats-package.core-properties+xml", coreDocument.getContentType()); + + // TODO - finish writing this test + assumeTrue("finish writing this test", false); + + ContentTypeManager ctm = new ZipContentTypeManager(coreDocument.getInputStream(), p); + assertNotNull(ctm); + } finally { + p.close(); + } } /** diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java index 3280a9121..2be21e830 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java @@ -115,25 +115,25 @@ public final class TestXSSFBugs extends BaseTestBugzillaIssues { assertFalse(wb.isMacroEnabled()); assertEquals(3, wb.getNumberOfNames()); - assertEquals(0, wb.getNameAt(0).getCTName().getLocalSheetId()); - assertFalse(wb.getNameAt(0).getCTName().isSetLocalSheetId()); - assertEquals("SheetA!$A$1", wb.getNameAt(0).getRefersToFormula()); - assertEquals("SheetA", wb.getNameAt(0).getSheetName()); + assertEquals(0, wb.getName("SheetAA1").getCTName().getLocalSheetId()); + assertFalse(wb.getName("SheetAA1").getCTName().isSetLocalSheetId()); + assertEquals("SheetA!$A$1", wb.getName("SheetAA1").getRefersToFormula()); + assertEquals("SheetA", wb.getName("SheetAA1").getSheetName()); - assertEquals(0, wb.getNameAt(1).getCTName().getLocalSheetId()); - assertFalse(wb.getNameAt(1).getCTName().isSetLocalSheetId()); - assertEquals("SheetB!$A$1", wb.getNameAt(1).getRefersToFormula()); - assertEquals("SheetB", wb.getNameAt(1).getSheetName()); + assertEquals(0, wb.getName("SheetBA1").getCTName().getLocalSheetId()); + assertFalse(wb.getName("SheetBA1").getCTName().isSetLocalSheetId()); + assertEquals("SheetB!$A$1", wb.getName("SheetBA1").getRefersToFormula()); + assertEquals("SheetB", wb.getName("SheetBA1").getSheetName()); - assertEquals(0, wb.getNameAt(2).getCTName().getLocalSheetId()); - assertFalse(wb.getNameAt(2).getCTName().isSetLocalSheetId()); - assertEquals("SheetC!$A$1", wb.getNameAt(2).getRefersToFormula()); - assertEquals("SheetC", wb.getNameAt(2).getSheetName()); + assertEquals(0, wb.getName("SheetCA1").getCTName().getLocalSheetId()); + assertFalse(wb.getName("SheetCA1").getCTName().isSetLocalSheetId()); + assertEquals("SheetC!$A$1", wb.getName("SheetCA1").getRefersToFormula()); + assertEquals("SheetC", wb.getName("SheetCA1").getSheetName()); // Save and re-load, still there XSSFWorkbook nwb = XSSFTestDataSamples.writeOutAndReadBack(wb); assertEquals(3, nwb.getNumberOfNames()); - assertEquals("SheetA!$A$1", nwb.getNameAt(0).getRefersToFormula()); + assertEquals("SheetA!$A$1", nwb.getName("SheetAA1").getRefersToFormula()); nwb.close(); wb.close(); diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java index 566944d18..6dcaef960 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java @@ -154,7 +154,9 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator { evaluator.evaluate(cXSL_cell); fail("Without a fix for #56752, shouldn't be able to evaluate a " + "reference to a non-provided linked workbook"); - } catch(Exception e) {} + } catch(Exception e) { + // expected here + } // Setup the environment Map evaluators = new HashMap(); @@ -171,8 +173,19 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator { evaluator.evaluate(c); } } + // And evaluate the other way too + evaluator.evaluateAll(); - // Evaluate and check results + // Static evaluator won't work, as no references passed in + try { + XSSFFormulaEvaluator.evaluateAllFormulaCells(wb); + fail("Static method lacks references, shouldn't work"); + } catch(Exception e) { + // expected here + } + + + // Evaluate specific cells and check results assertEquals("\"Hello!\"", evaluator.evaluate(cXSLX_cell).formatAsString()); assertEquals("\"Test A1\"", evaluator.evaluate(cXSLX_sNR).formatAsString()); assertEquals("142.0", evaluator.evaluate(cXSLX_gNR).formatAsString()); @@ -196,7 +209,9 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator { try { cXSLX_nw_cell.setCellFormula("[alt.xlsx]Sheet1!$A$1"); fail("New workbook not linked, shouldn't be able to add"); - } catch (Exception e) {} + } catch (Exception e) { + // expected here + } // Link and re-try Workbook alt = new XSSFWorkbook(); @@ -651,4 +666,20 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator { private Cell getCell(Sheet sheet, int rowNo, int column) { return sheet.getRow(rowNo).getCell(column); } + + @Test + public void test59736() { + Workbook wb = XSSFTestDataSamples.openSampleWorkbook("59736.xlsx"); + FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); + Cell cell = wb.getSheetAt(0).getRow(0).getCell(0); + assertEquals(1, cell.getNumericCellValue(), 0.001); + + cell = wb.getSheetAt(0).getRow(1).getCell(0); + CellValue value = evaluator.evaluate(cell); + assertEquals(1, value.getNumberValue(), 0.001); + + cell = wb.getSheetAt(0).getRow(2).getCell(0); + value = evaluator.evaluate(cell); + assertEquals(1, value.getNumberValue(), 0.001); + } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFName.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFName.java index 3e0f0b165..a188a11ed 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFName.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFName.java @@ -53,9 +53,8 @@ public final class TestXSSFName extends BaseTestNamedRange { //sheet.createFreezePane(0, 3); } assertEquals(1, wb.getNumberOfNames()); - XSSFName nr1 = wb.getNameAt(0); + XSSFName nr1 = wb.getName(XSSFName.BUILTIN_PRINT_TITLE); - assertEquals(XSSFName.BUILTIN_PRINT_TITLE, nr1.getNameName()); assertEquals("'First Sheet'!$A:$A,'First Sheet'!$1:$4", nr1.getRefersToFormula()); //remove the columns part @@ -77,9 +76,8 @@ public final class TestXSSFName extends BaseTestNamedRange { wb.close(); assertEquals(1, nwb.getNumberOfNames()); - nr1 = nwb.getNameAt(0); + nr1 = nwb.getName(XSSFName.BUILTIN_PRINT_TITLE); - assertEquals(XSSFName.BUILTIN_PRINT_TITLE, nr1.getNameName()); assertEquals("'First Sheet'!$A:$A,'First Sheet'!$1:$4", nr1.getRefersToFormula()); // check that setting RR&C on a second sheet causes a new Print_Titles built-in @@ -89,7 +87,7 @@ public final class TestXSSFName extends BaseTestNamedRange { sheet2.setRepeatingColumns(CellRangeAddress.valueOf("B:C")); assertEquals(2, nwb.getNumberOfNames()); - XSSFName nr2 = nwb.getNameAt(1); + XSSFName nr2 = nwb.getNames(XSSFName.BUILTIN_PRINT_TITLE).get(1); assertEquals(XSSFName.BUILTIN_PRINT_TITLE, nr2.getNameName()); assertEquals("SecondSheet!$B:$C,SecondSheet!$1:$1", nr2.getRefersToFormula()); @@ -98,4 +96,38 @@ public final class TestXSSFName extends BaseTestNamedRange { sheet2.setRepeatingColumns(null); nwb.close(); } + + @Test + public void testSetNameName() throws Exception { + // Test that renaming named ranges doesn't break our new named range map + XSSFWorkbook wb = new XSSFWorkbook(); + wb.createSheet("First Sheet"); + + // Two named ranges called "name1", one scoped to sheet1 and one globally + XSSFName nameSheet1 = wb.createName(); + nameSheet1.setNameName("name1"); + nameSheet1.setRefersToFormula("'First Sheet'!$A$1"); + nameSheet1.setSheetIndex(0); + + XSSFName nameGlobal = wb.createName(); + nameGlobal.setNameName("name1"); + nameGlobal.setRefersToFormula("'First Sheet'!$B$1"); + + // Rename sheet-scoped name to "name2", check everything is updated properly + // and that the other name is unaffected + nameSheet1.setNameName("name2"); + assertEquals(1, wb.getNames("name1").size()); + assertEquals(1, wb.getNames("name2").size()); + assertEquals(nameGlobal, wb.getName("name1")); + assertEquals(nameSheet1, wb.getName("name2")); + + // Rename the other name to "name" and check everything again + nameGlobal.setNameName("name2"); + assertEquals(0, wb.getNames("name1").size()); + assertEquals(2, wb.getNames("name2").size()); + assertTrue(wb.getNames("name2").contains(nameGlobal)); + assertTrue(wb.getNames("name2").contains(nameSheet1)); + + wb.close(); + } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java index a9b0d1f0b..689e999bc 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java @@ -80,6 +80,7 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXf; import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCalcMode; import org.openxmlformats.schemas.spreadsheetml.x2006.main.STPane; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.STUnsignedShortHex; public final class TestXSSFSheet extends BaseTestXSheet { @@ -1099,6 +1100,30 @@ public final class TestXSSFSheet extends BaseTestXSheet { wb.close(); } + @Test + public void protectSheet_emptyPassword() throws IOException { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet sheet = wb.createSheet(); + CTSheetProtection pr = sheet.getCTWorksheet().getSheetProtection(); + assertNull("CTSheetProtection should be null by default", pr); + String password = ""; + sheet.protectSheet(password); + pr = sheet.getCTWorksheet().getSheetProtection(); + assertNotNull("CTSheetProtection should be not null", pr); + assertTrue("sheet protection should be on", pr.isSetSheet()); + assertTrue("object protection should be on", pr.isSetObjects()); + assertTrue("scenario protection should be on", pr.isSetScenarios()); + int hashVal = CryptoFunctions.createXorVerifier1(password); + STUnsignedShortHex xpassword = pr.xgetPassword(); + int actualVal = Integer.parseInt(xpassword.getStringValue(),16); + assertEquals("well known value for top secret hash should match", hashVal, actualVal); + + sheet.protectSheet(null); + assertNull("protectSheet(null) should unset CTSheetProtection", sheet.getCTWorksheet().getSheetProtection()); + + wb.close(); + } + @Test public void protectSheet_lowlevel_2013() throws IOException { String password = "test"; diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java index c3420c780..7f832c4d9 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java @@ -1140,4 +1140,44 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook { wb.close(); } + + @Test + public void testRemoveSheet() throws IOException { + // Test removing a sheet maintains the named ranges correctly + XSSFWorkbook wb = new XSSFWorkbook(); + wb.createSheet("Sheet1"); + wb.createSheet("Sheet2"); + + XSSFName sheet1Name = wb.createName(); + sheet1Name.setNameName("name1"); + sheet1Name.setSheetIndex(0); + sheet1Name.setRefersToFormula("Sheet1!$A$1"); + + XSSFName sheet2Name = wb.createName(); + sheet2Name.setNameName("name1"); + sheet2Name.setSheetIndex(1); + sheet2Name.setRefersToFormula("Sheet2!$A$1"); + + assertTrue(wb.getAllNames().contains(sheet1Name)); + assertTrue(wb.getAllNames().contains(sheet2Name)); + + assertEquals(2, wb.getNames("name1").size()); + assertEquals(sheet1Name, wb.getNames("name1").get(0)); + assertEquals(sheet2Name, wb.getNames("name1").get(1)); + + // Remove sheet1, we should only have sheet2Name now + wb.removeSheetAt(0); + + assertFalse(wb.getAllNames().contains(sheet1Name)); + assertTrue(wb.getAllNames().contains(sheet2Name)); + assertEquals(1, wb.getNames("name1").size()); + assertEquals(sheet2Name, wb.getNames("name1").get(0)); + + // Check by index as well for sanity + assertEquals(1, wb.getNumberOfNames()); + assertEquals(0, wb.getNameIndex("name1")); + assertEquals(sheet2Name, wb.getNameAt(0)); + + wb.close(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java index f9d53dcbd..fe22c03ef 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java @@ -571,20 +571,39 @@ public final class HWPFDocument extends HWPFDocumentCore { return _fields; } + /** + * Warning - not currently implemented for HWPF! + */ @Override public void write() throws IOException { + // TODO Implement throw new IllegalStateException("Coming soon!"); } + + /** + * Writes out the word file that is represented by an instance of this class. + * + * If the {@link File} exists, it will be replaced, otherwise a new one + * will be created + * + * @param newFile The File to write to. + * @throws IOException If there is an unexpected IOException from writing + * to the File. + * + * @since 3.15 beta 3 + */ @Override public void write(File newFile) throws IOException { - throw new IllegalStateException("Coming soon!"); + NPOIFSFileSystem pfs = POIFSFileSystem.create(newFile); + write(pfs, true); + pfs.writeFilesystem(); } /** * Writes out the word file that is represented by an instance of this class. * - * If {@code stream} is a {@link java.io.FileOutputStream} on a networked drive - * or has a high cost/latency associated with each written byte, + * For better performance when writing to files, use {@link #write(File)}. + * If {@code stream} has a high cost/latency associated with each written byte, * consider wrapping the OutputStream in a {@link java.io.BufferedOutputStream} * to improve write performance. * @@ -592,9 +611,12 @@ public final class HWPFDocument extends HWPFDocumentCore { * @throws IOException If there is an unexpected IOException from the passed * in OutputStream. */ - public void write(OutputStream out) - throws IOException - { + public void write(OutputStream out) throws IOException { + NPOIFSFileSystem pfs = new NPOIFSFileSystem(); + write(pfs, true); + pfs.writeFilesystem( out ); + } + private void write(NPOIFSFileSystem pfs, boolean copyOtherEntries) throws IOException { // initialize our streams for writing. HWPFFileSystem docSys = new HWPFFileSystem(); HWPFOutputStream wordDocumentStream = docSys.getStream(STREAM_WORD_DOCUMENT); @@ -891,7 +913,8 @@ public final class HWPFDocument extends HWPFDocumentCore { } // create new document preserving order of entries - NPOIFSFileSystem pfs = new NPOIFSFileSystem(); + // TODO Check "copyOtherEntries" and tweak behaviour based on that + // TODO That's needed for in-place write boolean docWritten = false; boolean dataWritten = false; boolean objectPoolWritten = false; @@ -967,7 +990,6 @@ public final class HWPFDocument extends HWPFDocumentCore { if ( !objectPoolWritten ) _objectPool.writeTo( pfs.getRoot() ); - pfs.writeFilesystem( out ); this.directory = pfs.getRoot(); /* diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestHWPFWrite.java b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestHWPFWrite.java new file mode 100644 index 000000000..95d7919e3 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestHWPFWrite.java @@ -0,0 +1,81 @@ +/* ==================================================================== + 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.hwpf.usermodel; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; + +import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.hwpf.HWPFTestCase; +import org.apache.poi.hwpf.HWPFTestDataSamples; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.util.TempFile; + +/** + * Test various write situations + */ +public final class TestHWPFWrite extends HWPFTestCase { + /** + * Write to a stream + */ + public void testWriteStream() throws Exception { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile("SampleDoc.doc"); + + Range r = doc.getRange(); + assertEquals("I am a test document\r", r.getParagraph(0).text()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + doc.write(baos); + doc.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + doc = new HWPFDocument(bais); + r = doc.getRange(); + assertEquals("I am a test document\r", r.getParagraph(0).text()); + doc.close(); + } + + /** + * Write to a new file + */ + public void testWriteNewFile() throws Exception { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile("SampleDoc.doc"); + + Range r = doc.getRange(); + assertEquals("I am a test document\r", r.getParagraph(0).text()); + + File file = TempFile.createTempFile("TestDocument", ".doc"); + doc.write(file); + doc.close(); + + // Check reading from File and Stream + doc = new HWPFDocument(new FileInputStream(file)); + r = doc.getRange(); + assertEquals("I am a test document\r", r.getParagraph(0).text()); + doc.close(); + + doc = new HWPFDocument(new POIFSFileSystem(file)); + r = doc.getRange(); + assertEquals("I am a test document\r", r.getParagraph(0).text()); + doc.close(); + } + + // TODO In-place write positive and negative checks +} diff --git a/src/testcases/org/apache/poi/sl/usermodel/BaseTestSlideShow.java b/src/testcases/org/apache/poi/sl/usermodel/BaseTestSlideShow.java index 404e0da07..e02260844 100644 --- a/src/testcases/org/apache/poi/sl/usermodel/BaseTestSlideShow.java +++ b/src/testcases/org/apache/poi/sl/usermodel/BaseTestSlideShow.java @@ -50,14 +50,20 @@ public abstract class BaseTestSlideShow { @Test public void addPicture_Stream() throws IOException { SlideShow show = createSlideShow(); - InputStream stream = slTests.openResourceAsStream("clock.jpg"); - - assertEquals(0, show.getPictureData().size()); - PictureData picture = show.addPicture(stream, PictureType.JPEG); - assertEquals(1, show.getPictureData().size()); - assertSame(picture, show.getPictureData().get(0)); - - show.close(); + try { + InputStream stream = slTests.openResourceAsStream("clock.jpg"); + try { + assertEquals(0, show.getPictureData().size()); + PictureData picture = show.addPicture(stream, PictureType.JPEG); + assertEquals(1, show.getPictureData().size()); + assertSame(picture, show.getPictureData().get(0)); + + } finally { + stream.close(); + } + } finally { + show.close(); + } } @Test diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestIndirect.java b/src/testcases/org/apache/poi/ss/formula/functions/TestIndirect.java index 8e93152c8..ea5ac9c7a 100644 --- a/src/testcases/org/apache/poi/ss/formula/functions/TestIndirect.java +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestIndirect.java @@ -27,6 +27,7 @@ import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.formula.eval.ValueEval; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.CellValue; @@ -105,6 +106,7 @@ public final class TestIndirect { // non-error cases confirm(feA, c, "INDIRECT(\"C2\")", 23); + confirm(feA, c, "INDIRECT(\"C2\", TRUE)", 23); confirm(feA, c, "INDIRECT(\"$C2\")", 23); confirm(feA, c, "INDIRECT(\"C$2\")", 23); confirm(feA, c, "SUM(INDIRECT(\"Sheet2!B1:C3\"))", 351); // area ref @@ -149,7 +151,7 @@ public final class TestIndirect { // confirm(feA, c, "INDIRECT(\"Sheet1!A65537\")", ErrorEval.REF_INVALID); // bad row // } confirm(feA, c, "INDIRECT(\"Sheet1!A 1\")", ErrorEval.REF_INVALID); // space in cell ref - + wbA.close(); } @@ -203,4 +205,9 @@ public final class TestIndirect { + "' but got '" + cv.formatAsString() + "'."); } } + + @Test + public void testInvalidInput() { + assertEquals(ErrorEval.VALUE_INVALID, Indirect.instance.evaluate(new ValueEval[] {}, null)); + } } diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestSubtotal.java b/src/testcases/org/apache/poi/ss/formula/functions/TestSubtotal.java index 7b63eaf79..f2750b591 100644 --- a/src/testcases/org/apache/poi/ss/formula/functions/TestSubtotal.java +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestSubtotal.java @@ -20,9 +20,8 @@ package org.apache.poi.ss.formula.functions; import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.ss.formula.eval.AreaEval; -import org.apache.poi.ss.formula.eval.NumberEval; -import org.apache.poi.ss.formula.eval.ValueEval; +import org.apache.poi.ss.formula.FormulaParseException; +import org.apache.poi.ss.formula.eval.*; import junit.framework.TestCase; import org.apache.poi.ss.usermodel.*; @@ -75,7 +74,6 @@ public final class TestSubtotal extends TestCase { } public void testAvg(){ - Workbook wb = new HSSFWorkbook(); FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator(); @@ -95,16 +93,18 @@ public final class TestSubtotal extends TestCase { a6.setCellFormula("SUBTOTAL(1,B2:B6)*2 + 2"); Cell a7 = sh.createRow(7).createCell(1); a7.setCellFormula("SUBTOTAL(1,B2:B7)"); + Cell a8 = sh.createRow(8).createCell(1); + a8.setCellFormula("SUBTOTAL(1,B2,B3,B4,B5,B6,B7,B8)"); fe.evaluateAll(); assertEquals(2.0, a3.getNumericCellValue()); assertEquals(8.0, a6.getNumericCellValue()); assertEquals(3.0, a7.getNumericCellValue()); + assertEquals(3.0, a8.getNumericCellValue()); } public void testSum(){ - Workbook wb = new HSSFWorkbook(); FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator(); @@ -124,12 +124,15 @@ public final class TestSubtotal extends TestCase { a6.setCellFormula("SUBTOTAL(9,B2:B6)*2 + 2"); Cell a7 = sh.createRow(7).createCell(1); a7.setCellFormula("SUBTOTAL(9,B2:B7)"); + Cell a8 = sh.createRow(8).createCell(1); + a8.setCellFormula("SUBTOTAL(9,B2,B3,B4,B5,B6,B7,B8)"); fe.evaluateAll(); assertEquals(4.0, a3.getNumericCellValue()); assertEquals(26.0, a6.getNumericCellValue()); assertEquals(12.0, a7.getNumericCellValue()); + assertEquals(12.0, a8.getNumericCellValue()); } public void testCount(){ @@ -147,18 +150,21 @@ public final class TestSubtotal extends TestCase { a3.setCellFormula("SUBTOTAL(2,B2:B3)"); Cell a4 = sh.createRow(4).createCell(1); a4.setCellValue("POI"); // A4 is string and not counted - Cell a5 = sh.createRow(5).createCell(1); // A5 is blank and not counted + /*Cell a5 =*/ sh.createRow(5).createCell(1); // A5 is blank and not counted Cell a6 = sh.createRow(6).createCell(1); a6.setCellFormula("SUBTOTAL(2,B2:B6)*2 + 2"); Cell a7 = sh.createRow(7).createCell(1); a7.setCellFormula("SUBTOTAL(2,B2:B7)"); + Cell a8 = sh.createRow(8).createCell(1); + a8.setCellFormula("SUBTOTAL(2,B2,B3,B4,B5,B6,B7,B8)"); fe.evaluateAll(); assertEquals(2.0, a3.getNumericCellValue()); assertEquals(6.0, a6.getNumericCellValue()); assertEquals(2.0, a7.getNumericCellValue()); + assertEquals(2.0, a8.getNumericCellValue()); } public void testCounta(){ @@ -176,18 +182,21 @@ public final class TestSubtotal extends TestCase { a3.setCellFormula("SUBTOTAL(3,B2:B3)"); Cell a4 = sh.createRow(4).createCell(1); a4.setCellValue("POI"); // A4 is string and not counted - Cell a5 = sh.createRow(5).createCell(1); // A5 is blank and not counted + /*Cell a5 =*/ sh.createRow(5).createCell(1); // A5 is blank and not counted Cell a6 = sh.createRow(6).createCell(1); a6.setCellFormula("SUBTOTAL(3,B2:B6)*2 + 2"); Cell a7 = sh.createRow(7).createCell(1); a7.setCellFormula("SUBTOTAL(3,B2:B7)"); + Cell a8 = sh.createRow(8).createCell(1); + a8.setCellFormula("SUBTOTAL(3,B2,B3,B4,B5,B6,B7,B8)"); fe.evaluateAll(); assertEquals(2.0, a3.getNumericCellValue()); assertEquals(8.0, a6.getNumericCellValue()); assertEquals(3.0, a7.getNumericCellValue()); + assertEquals(3.0, a8.getNumericCellValue()); } public void testMax(){ @@ -211,12 +220,15 @@ public final class TestSubtotal extends TestCase { a6.setCellFormula("SUBTOTAL(4,B2:B6)*2 + 2"); Cell a7 = sh.createRow(7).createCell(1); a7.setCellFormula("SUBTOTAL(4,B2:B7)"); + Cell a8 = sh.createRow(8).createCell(1); + a8.setCellFormula("SUBTOTAL(4,B2,B3,B4,B5,B6,B7,B8)"); fe.evaluateAll(); assertEquals(3.0, a3.getNumericCellValue()); assertEquals(16.0, a6.getNumericCellValue()); assertEquals(7.0, a7.getNumericCellValue()); + assertEquals(7.0, a8.getNumericCellValue()); } public void testMin(){ @@ -240,12 +252,15 @@ public final class TestSubtotal extends TestCase { a6.setCellFormula("SUBTOTAL(5,B2:B6)*2 + 2"); Cell a7 = sh.createRow(7).createCell(1); a7.setCellFormula("SUBTOTAL(5,B2:B7)"); + Cell a8 = sh.createRow(8).createCell(1); + a8.setCellFormula("SUBTOTAL(5,B2,B3,B4,B5,B6,B7,B8)"); fe.evaluateAll(); assertEquals(1.0, a3.getNumericCellValue()); assertEquals(4.0, a6.getNumericCellValue()); assertEquals(1.0, a7.getNumericCellValue()); + assertEquals(1.0, a8.getNumericCellValue()); } public void testStdev(){ @@ -269,12 +284,15 @@ public final class TestSubtotal extends TestCase { a6.setCellFormula("SUBTOTAL(7,B2:B6)*2 + 2"); Cell a7 = sh.createRow(7).createCell(1); a7.setCellFormula("SUBTOTAL(7,B2:B7)"); + Cell a8 = sh.createRow(8).createCell(1); + a8.setCellFormula("SUBTOTAL(7,B2,B3,B4,B5,B6,B7,B8)"); fe.evaluateAll(); assertEquals(1.41421, a3.getNumericCellValue(), 0.0001); assertEquals(7.65685, a6.getNumericCellValue(), 0.0001); assertEquals(2.82842, a7.getNumericCellValue(), 0.0001); + assertEquals(2.82842, a8.getNumericCellValue(), 0.0001); } public void test50209(){ @@ -328,4 +346,69 @@ public final class TestSubtotal extends TestCase { confirmExpectedResult(evaluator, "SUBTOTAL(COUNT;B2:B8,C2:C8)", cellC2, 3.0); confirmExpectedResult(evaluator, "SUBTOTAL(COUNTA;B2:B8,C2:C8)", cellC3, 5.0); } + + public void testUnimplemented(){ + Workbook wb = new HSSFWorkbook(); + + FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator(); + + Sheet sh = wb.createSheet(); + Cell a3 = sh.createRow(3).createCell(1); + a3.setCellFormula("SUBTOTAL(8,B2:B3)"); + + try { + fe.evaluateAll(); + fail("Should catch an NotImplementedFunctionException here, adjust these tests if it was actually implemented"); + } catch (NotImplementedException e) { + // expected here + } + + a3.setCellFormula("SUBTOTAL(10,B2:B3)"); + + try { + fe.evaluateAll(); + fail("Should catch an NotImplementedFunctionException here, adjust these tests if it was actually implemented"); + } catch (NotImplementedException e) { + // expected here + } + + a3.setCellFormula("SUBTOTAL(11,B2:B3)"); + + try { + fe.evaluateAll(); + fail("Should catch an NotImplementedFunctionException here, adjust these tests if it was actually implemented"); + } catch (NotImplementedException e) { + // expected here + } + + a3.setCellFormula("SUBTOTAL(107,B2:B3)"); + + try { + fe.evaluateAll(); + fail("Should catch an NotImplementedFunctionException here, adjust these tests if it was actually implemented"); + } catch (NotImplementedException e) { + // expected here + } + + a3.setCellFormula("SUBTOTAL(0,B2:B3)"); + fe.evaluateAll(); + assertEquals(FormulaError.VALUE.getCode(), a3.getErrorCellValue()); + + try { + a3.setCellFormula("SUBTOTAL(9)"); + fail("Should catch an exception here"); + } catch (FormulaParseException e) { + // expected here + } + + try { + a3.setCellFormula("SUBTOTAL()"); + fail("Should catch an exception here"); + } catch (FormulaParseException e) { + // expected here + } + + Subtotal subtotal = new Subtotal(); + assertEquals(ErrorEval.VALUE_INVALID, subtotal.evaluate(new ValueEval[] {}, 0, 0)); + } } diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestWeekdayFunc.java b/src/testcases/org/apache/poi/ss/formula/functions/TestWeekdayFunc.java new file mode 100644 index 000000000..c6c797c18 --- /dev/null +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestWeekdayFunc.java @@ -0,0 +1,66 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.ss.formula.functions; + +import org.apache.poi.ss.formula.eval.*; +import org.junit.Test; + +import static org.junit.Assert.*; + + +public class TestWeekdayFunc { + @Test + public void testEvaluate() throws Exception { + assertEquals(2.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(2.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(1.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(1.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(2.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(0.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(3.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(1.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(11.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(7.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(12.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(6.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(13.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(5.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(14.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(4.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(15.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(3.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(16.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(2.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(17.0)}, 0, 0)).getNumberValue(), 0.001); + + assertEquals(3.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(3.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(1.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(2.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(2.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(1.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(3.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(2.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(11.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(1.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(12.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(7.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(13.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(6.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(14.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(5.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(15.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(4.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(16.0)}, 0, 0)).getNumberValue(), 0.001); + assertEquals(3.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(17.0)}, 0, 0)).getNumberValue(), 0.001); + } + + @Test + public void testEvaluateInvalid() throws Exception { + assertEquals(ErrorEval.VALUE_INVALID, WeekdayFunc.instance.evaluate(new ValueEval[]{}, 0, 0)); + assertEquals(ErrorEval.VALUE_INVALID, WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(1.0), new NumberEval(1.0)}, 0, 0)); + + assertEquals(ErrorEval.NUM_ERROR, WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(-1.0)}, 0, 0)); + assertEquals(ErrorEval.VALUE_INVALID, WeekdayFunc.instance.evaluate(new ValueEval[]{new StringEval("")}, 0, 0)); + assertEquals(ErrorEval.VALUE_INVALID, WeekdayFunc.instance.evaluate(new ValueEval[]{new StringEval("1"), new StringEval("")}, 0, 0)); + assertEquals(ErrorEval.NUM_ERROR, WeekdayFunc.instance.evaluate(new ValueEval[]{new StringEval("2"), BlankEval.instance}, 0, 0)); + assertEquals(ErrorEval.NUM_ERROR, WeekdayFunc.instance.evaluate(new ValueEval[]{new StringEval("3"), MissingArgEval.instance}, 0, 0)); + assertEquals(ErrorEval.NUM_ERROR, WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(18.0)}, 0, 0)); + } +} \ No newline at end of file diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestBugzillaIssues.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestBugzillaIssues.java index a832635c1..bdd09bfd6 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/BaseTestBugzillaIssues.java +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestBugzillaIssues.java @@ -33,9 +33,12 @@ import java.awt.font.FontRenderContext; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.awt.geom.Rectangle2D; +import java.io.FileInputStream; import java.io.IOException; import java.text.AttributedString; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.junit.Assert.*; @@ -1606,4 +1609,78 @@ public abstract class BaseTestBugzillaIssues { assertNull("Sheet0 after write", wb2.getPrintArea(0)); // CURRENTLY FAILS with "Sheet0!$A$1:$C$6" assertEquals("Sheet1 after write", "Sheet1!$A$1:$A$1", wb2.getPrintArea(1)); } -} + + + @Test + public void test55384() throws Exception { + Workbook wb = _testDataProvider.createWorkbook(); + try { + Sheet sh = wb.createSheet(); + for (int rownum = 0; rownum < 10; rownum++) { + org.apache.poi.ss.usermodel.Row row = sh.createRow(rownum); + for (int cellnum = 0; cellnum < 3; cellnum++) { + Cell cell = row.createCell(cellnum); + cell.setCellValue(rownum + cellnum); + } + } + Row row = sh.createRow(10); + // setting no precalculated value works just fine. + Cell cell1 = row.createCell(0); + cell1.setCellFormula("SUM(A1:A10)"); + + // but setting a precalculated STRING value fails totally in SXSSF + Cell cell2 = row.createCell(1); + cell2.setCellFormula("SUM(B1:B10)"); + cell2.setCellValue("55"); + + // setting a precalculated int value works as expected + Cell cell3 = row.createCell(2); + cell3.setCellFormula("SUM(C1:C10)"); + cell3.setCellValue(65); + + assertEquals(CellType.FORMULA, cell1.getCellTypeEnum()); + assertEquals(CellType.FORMULA, cell2.getCellTypeEnum()); + assertEquals(CellType.FORMULA, cell3.getCellTypeEnum()); + + assertEquals("SUM(A1:A10)", cell1.getCellFormula()); + assertEquals("SUM(B1:B10)", cell2.getCellFormula()); + assertEquals("SUM(C1:C10)", cell3.getCellFormula()); + + /*String name = wb.getClass().getCanonicalName(); + String ext = (wb instanceof HSSFWorkbook) ? ".xls" : ".xlsx"; + OutputStream output = new FileOutputStream("/tmp" + name + ext); + try { + wb.write(output); + } finally { + output.close(); + }*/ + + Workbook wbBack = _testDataProvider.writeOutAndReadBack(wb); + checkFormulaPreevaluatedString(wbBack); + wbBack.close(); + } finally { + wb.close(); + } + } + + private void checkFormulaPreevaluatedString(Workbook readFile) { + Sheet sheet = readFile.getSheetAt(0); + Row row = sheet.getRow(sheet.getLastRowNum()); + assertEquals(10, row.getRowNum()); + + for (Cell cell : row) { + String cellValue = null; + switch (cell.getCellTypeEnum()) { + case STRING: + cellValue = cell.getRichStringCellValue().getString(); + break; + case FORMULA: + cellValue = cell.getCellFormula(); + break; + } + assertNotNull(cellValue); + cellValue = cellValue.isEmpty() ? null : cellValue; + assertNotNull(cellValue); + } + } +} \ No newline at end of file diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestNamedRange.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestNamedRange.java index 75fb77e82..92e362df5 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/BaseTestNamedRange.java +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestNamedRange.java @@ -201,11 +201,7 @@ public abstract class BaseTestNamedRange { assertEquals("The sheet already contains this name: aaa", e.getMessage()); } - int cnt = 0; - for (int i = 0; i < wb.getNumberOfNames(); i++) { - if("aaa".equals(wb.getNameAt(i).getNameName())) cnt++; - } - assertEquals(3, cnt); + assertEquals(3, wb.getNames("aaa").size()); wb.close(); } @@ -250,11 +246,11 @@ public abstract class BaseTestNamedRange { // Write the workbook to a file // Read the Excel file and verify its content Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb1); - Name nm1 = wb2.getNameAt(wb2.getNameIndex("RangeTest1")); + Name nm1 = wb2.getName("RangeTest1"); assertTrue("Name is "+nm1.getNameName(),"RangeTest1".equals(nm1.getNameName())); assertTrue("Reference is "+nm1.getRefersToFormula(),(wb2.getSheetName(0)+"!$A$1:$L$41").equals(nm1.getRefersToFormula())); - Name nm2 = wb2.getNameAt(wb2.getNameIndex("RangeTest2")); + Name nm2 = wb2.getName("RangeTest2"); assertTrue("Name is "+nm2.getNameName(),"RangeTest2".equals(nm2.getNameName())); assertTrue("Reference is "+nm2.getRefersToFormula(),(wb2.getSheetName(1)+"!$A$1:$O$21").equals(nm2.getRefersToFormula())); @@ -466,11 +462,11 @@ public abstract class BaseTestNamedRange { wb1.getNameAt(0); Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb1); - Name nm =wb2.getNameAt(wb2.getNameIndex("RangeTest")); + Name nm =wb2.getName("RangeTest"); assertTrue("Name is "+nm.getNameName(),"RangeTest".equals(nm.getNameName())); assertTrue("Reference is "+nm.getRefersToFormula(),(wb2.getSheetName(0)+"!$D$4:$E$8").equals(nm.getRefersToFormula())); - nm = wb2.getNameAt(wb2.getNameIndex("AnotherTest")); + nm = wb2.getName("AnotherTest"); assertTrue("Name is "+nm.getNameName(),"AnotherTest".equals(nm.getNameName())); assertTrue("Reference is "+nm.getRefersToFormula(),newNamedRange2.getRefersToFormula().equals(nm.getRefersToFormula())); @@ -499,8 +495,7 @@ public abstract class BaseTestNamedRange { namedCell.setRefersToFormula(reference); // retrieve the newly created named range - int namedCellIdx = wb.getNameIndex(cellName); - Name aNamedCell = wb.getNameAt(namedCellIdx); + Name aNamedCell = wb.getName(cellName); assertNotNull(aNamedCell); // retrieve the cell at the named range and test its contents @@ -540,8 +535,7 @@ public abstract class BaseTestNamedRange { namedCell.setRefersToFormula(reference); // retrieve the newly created named range - int namedCellIdx = wb.getNameIndex(cname); - Name aNamedCell = wb.getNameAt(namedCellIdx); + Name aNamedCell = wb.getName(cname); assertNotNull(aNamedCell); // retrieve the cell at the named range and test its contents diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetShiftRows.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetShiftRows.java index 3f8febb11..efae0d80c 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetShiftRows.java +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetShiftRows.java @@ -261,17 +261,17 @@ public abstract class BaseTestSheetShiftRows { name4.setSheetIndex(1); sheet1.shiftRows(0, 1, 2); //shift down the top row on Sheet1. - name1 = wb.getNameAt(0); + name1 = wb.getName("name1"); assertEquals("Sheet1!$A$3+Sheet1!$B$3", name1.getRefersToFormula()); - name2 = wb.getNameAt(1); + name2 = wb.getName("name2"); assertEquals("Sheet1!$A$3", name2.getRefersToFormula()); //name3 and name4 refer to Sheet2 and should not be affected - name3 = wb.getNameAt(2); + name3 = wb.getName("name3"); assertEquals("Sheet2!$A$1", name3.getRefersToFormula()); - name4 = wb.getNameAt(3); + name4 = wb.getName("name4"); assertEquals("A1", name4.getRefersToFormula()); wb.close(); diff --git a/src/testcases/org/apache/poi/ss/util/BaseTestCellUtil.java b/src/testcases/org/apache/poi/ss/util/BaseTestCellUtil.java index ed0c1da7e..a63ce5a60 100644 --- a/src/testcases/org/apache/poi/ss/util/BaseTestCellUtil.java +++ b/src/testcases/org/apache/poi/ss/util/BaseTestCellUtil.java @@ -78,14 +78,16 @@ public class BaseTestCellUtil { @Test(expected=RuntimeException.class) public void setCellStylePropertyWithInvalidValue() throws IOException { Workbook wb = _testDataProvider.createWorkbook(); - Sheet s = wb.createSheet(); - Row r = s.createRow(0); - Cell c = r.createCell(0); + try { + Sheet s = wb.createSheet(); + Row r = s.createRow(0); + Cell c = r.createCell(0); - // An invalid BorderStyle constant - CellUtil.setCellStyleProperty(c, CellUtil.BORDER_BOTTOM, 42); - - wb.close(); + // An invalid BorderStyle constant + CellUtil.setCellStyleProperty(c, CellUtil.BORDER_BOTTOM, 42); + } finally { + wb.close(); + } } @Test() @@ -352,10 +354,8 @@ public class BaseTestCellUtil { CellUtil.setFont(A1, font2); fail("setFont not allowed if font belongs to a different workbook"); } catch (final IllegalArgumentException e) { - if (e.getMessage().startsWith("Font does not belong to this workbook")) { - // expected - } - else { + // one specific message is expected + if (!e.getMessage().startsWith("Font does not belong to this workbook")) { throw e; } } finally { @@ -371,7 +371,7 @@ public class BaseTestCellUtil { */ // bug 55555 @Test - public void setFillForegroundColorBeforeFillBackgroundColor() { + public void setFillForegroundColorBeforeFillBackgroundColor() throws IOException { Workbook wb1 = _testDataProvider.createWorkbook(); Cell A1 = wb1.createSheet().createRow(0).createCell(0); Map properties = new HashMap(); @@ -386,13 +386,14 @@ public class BaseTestCellUtil { assertEquals("fill pattern", CellStyle.BRICKS, style.getFillPattern()); assertEquals("fill foreground color", IndexedColors.BLUE, IndexedColors.fromInt(style.getFillForegroundColor())); assertEquals("fill background color", IndexedColors.RED, IndexedColors.fromInt(style.getFillBackgroundColor())); + wb1.close(); } /** * bug 55555 * @since POI 3.15 beta 3 */ @Test - public void setFillForegroundColorBeforeFillBackgroundColorEnum() { + public void setFillForegroundColorBeforeFillBackgroundColorEnum() throws IOException { Workbook wb1 = _testDataProvider.createWorkbook(); Cell A1 = wb1.createSheet().createRow(0).createCell(0); Map properties = new HashMap(); @@ -407,5 +408,7 @@ public class BaseTestCellUtil { assertEquals("fill pattern", FillPatternType.BRICKS, style.getFillPatternEnum()); assertEquals("fill foreground color", IndexedColors.BLUE, IndexedColors.fromInt(style.getFillForegroundColor())); assertEquals("fill background color", IndexedColors.RED, IndexedColors.fromInt(style.getFillBackgroundColor())); + + wb1.close(); } } diff --git a/src/testcases/org/apache/poi/util/DummyPOILogger.java b/src/testcases/org/apache/poi/util/DummyPOILogger.java index 976c7d918..c8566fe66 100644 --- a/src/testcases/org/apache/poi/util/DummyPOILogger.java +++ b/src/testcases/org/apache/poi/util/DummyPOILogger.java @@ -30,16 +30,20 @@ public class DummyPOILogger extends POILogger { logged = new ArrayList(); } + @Override public boolean check(int level) { return true; } + @Override public void initialize(String cat) {} + @Override public void log(int level, Object obj1) { logged.add(level + " - " + obj1); } + @Override public void log(int level, Object obj1, Throwable exception) { logged.add(level + " - " + obj1 + " - " + exception); } diff --git a/src/testcases/org/apache/poi/util/TestPOILogger.java b/src/testcases/org/apache/poi/util/TestPOILogger.java index a53de44eb..3914b7643 100644 --- a/src/testcases/org/apache/poi/util/TestPOILogger.java +++ b/src/testcases/org/apache/poi/util/TestPOILogger.java @@ -26,10 +26,6 @@ import org.junit.Test; /** * Tests the log class. - * - * @author Glen Stampoultzis (glens at apache.org) - * @author Marc Johnson (mjohnson at apache dot org) - * @author Nicola Ken Barozzi (nicolaken at apache.org) */ public final class TestPOILogger extends POILogger { private String lastLog = ""; @@ -61,20 +57,26 @@ public final class TestPOILogger extends POILogger { POILogFactory._loggerClassName = oldLCN; } } + + // ---------- POI Logger methods implemented for testing ---------- + @Override public void initialize(String cat) { } + @Override public void log(int level, Object obj1) { lastLog = (obj1 == null) ? "" : obj1.toString(); lastEx = null; } + @Override public void log(int level, Object obj1, Throwable exception) { lastLog = (obj1 == null) ? "" : obj1.toString(); lastEx = exception; } + @Override public boolean check(int level) { return true; } diff --git a/test-data/spreadsheet/59736.xlsx b/test-data/spreadsheet/59736.xlsx new file mode 100644 index 000000000..1f29d54c5 Binary files /dev/null and b/test-data/spreadsheet/59736.xlsx differ