mirror of https://github.com/apache/poi.git
improved usermodel to prevent data corruption when setting array formulas, mimic Excel behaviour and block changing cells included in multi-cell arrays
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@894469 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
5c08e67cd0
commit
58fd770335
|
@ -230,7 +230,9 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
|
||||||
if (_sharedFormulaRecord != null) {
|
if (_sharedFormulaRecord != null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return _formulaRecord.getFormula().getExpReference() != null;
|
CellReference expRef = _formulaRecord.getFormula().getExpReference();
|
||||||
|
ArrayRecord arec = expRef == null ? null : _sharedValueManager.getArrayRecord(expRef.getRow(), expRef.getCol());
|
||||||
|
return arec != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CellRangeAddress getArrayFormulaRange() {
|
public CellRangeAddress getArrayFormulaRange() {
|
||||||
|
@ -260,6 +262,8 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
|
||||||
*/
|
*/
|
||||||
public CellRangeAddress removeArrayFormula(int rowIndex, int columnIndex) {
|
public CellRangeAddress removeArrayFormula(int rowIndex, int columnIndex) {
|
||||||
CellRangeAddress8Bit a = _sharedValueManager.removeArrayFormula(rowIndex, columnIndex);
|
CellRangeAddress8Bit a = _sharedValueManager.removeArrayFormula(rowIndex, columnIndex);
|
||||||
return new CellRangeAddress(a.getFirstRow(), a.getLastRow(), a.getFirstColumn(), a.getLastColumn());
|
// at this point FormulaRecordAggregate#isPartOfArrayFormula() should return false
|
||||||
|
_formulaRecord.setParsedExpression(null);
|
||||||
|
return new CellRangeAddress(a.getFirstRow(), a.getLastRow(), a.getFirstColumn(), a.getLastColumn());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,6 +284,9 @@ public class HSSFCell implements Cell {
|
||||||
*/
|
*/
|
||||||
public void setCellType(int cellType) {
|
public void setCellType(int cellType) {
|
||||||
notifyFormulaChanging();
|
notifyFormulaChanging();
|
||||||
|
if(isPartOfArrayFormulaGroup()){
|
||||||
|
notifyArrayFormulaChanging();
|
||||||
|
}
|
||||||
int row=_record.getRow();
|
int row=_record.getRow();
|
||||||
short col=_record.getColumn();
|
short col=_record.getColumn();
|
||||||
short styleIndex=_record.getXFIndex();
|
short styleIndex=_record.getXFIndex();
|
||||||
|
@ -578,6 +581,10 @@ public class HSSFCell implements Cell {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCellFormula(String formula) {
|
public void setCellFormula(String formula) {
|
||||||
|
if(isPartOfArrayFormulaGroup()){
|
||||||
|
notifyArrayFormulaChanging();
|
||||||
|
}
|
||||||
|
|
||||||
int row=_record.getRow();
|
int row=_record.getRow();
|
||||||
short col=_record.getColumn();
|
short col=_record.getColumn();
|
||||||
short styleIndex=_record.getXFIndex();
|
short styleIndex=_record.getXFIndex();
|
||||||
|
@ -1191,4 +1198,40 @@ public class HSSFCell implements Cell {
|
||||||
}
|
}
|
||||||
return ((FormulaRecordAggregate)_record).isPartOfArrayFormula();
|
return ((FormulaRecordAggregate)_record).isPartOfArrayFormula();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The purpose of this method is to validate the cell state prior to modification
|
||||||
|
*
|
||||||
|
* @see #notifyArrayFormulaChanging()
|
||||||
|
*/
|
||||||
|
void notifyArrayFormulaChanging(String msg){
|
||||||
|
CellRangeAddress cra = getArrayFormulaRange();
|
||||||
|
if(cra.getNumberOfCells() > 1) {
|
||||||
|
throw new IllegalStateException(msg);
|
||||||
|
}
|
||||||
|
//un-register the single-cell array formula from the parent XSSFSheet
|
||||||
|
getRow().getSheet().removeArrayFormula(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when this cell is modified.
|
||||||
|
* <p>
|
||||||
|
* The purpose of this method is to validate the cell state prior to modification.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @see #setCellType(int)
|
||||||
|
* @see #setCellFormula(String)
|
||||||
|
* @see HSSFRow#removeCell(org.apache.poi.ss.usermodel.Cell)
|
||||||
|
* @see org.apache.poi.hssf.usermodel.HSSFSheet#removeRow(org.apache.poi.ss.usermodel.Row)
|
||||||
|
* @see org.apache.poi.hssf.usermodel.HSSFSheet#shiftRows(int, int, int)
|
||||||
|
* @see org.apache.poi.hssf.usermodel.HSSFSheet#addMergedRegion(org.apache.poi.ss.util.CellRangeAddress)
|
||||||
|
* @throws IllegalStateException if modification is not allowed
|
||||||
|
*/
|
||||||
|
void notifyArrayFormulaChanging(){
|
||||||
|
CellReference ref = new CellReference(this);
|
||||||
|
String msg = "Cell "+ref.formatAsString()+" is part of a multi-cell array formula. " +
|
||||||
|
"You cannot change part of an array.";
|
||||||
|
notifyArrayFormulaChanging(msg);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,6 +163,10 @@ public final class HSSFRow implements Row {
|
||||||
if(column >= cells.length || cell != cells[column]) {
|
if(column >= cells.length || cell != cells[column]) {
|
||||||
throw new RuntimeException("Specified cell is not from this row");
|
throw new RuntimeException("Specified cell is not from this row");
|
||||||
}
|
}
|
||||||
|
if(cell.isPartOfArrayFormulaGroup()){
|
||||||
|
cell.notifyArrayFormulaChanging();
|
||||||
|
}
|
||||||
|
|
||||||
cells[column]=null;
|
cells[column]=null;
|
||||||
|
|
||||||
if(alsoRemoveRecords) {
|
if(alsoRemoveRecords) {
|
||||||
|
|
|
@ -238,6 +238,13 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
|
||||||
if (row.getSheet() != this) {
|
if (row.getSheet() != this) {
|
||||||
throw new IllegalArgumentException("Specified row does not belong to this sheet");
|
throw new IllegalArgumentException("Specified row does not belong to this sheet");
|
||||||
}
|
}
|
||||||
|
for(Cell cell : row) {
|
||||||
|
HSSFCell xcell = (HSSFCell)cell;
|
||||||
|
if(xcell.isPartOfArrayFormulaGroup()){
|
||||||
|
String msg = "Row[rownum="+row.getRowNum()+"] contains cell(s) included in a multi-cell array formula. You cannot change part of an array.";
|
||||||
|
xcell.notifyArrayFormulaChanging(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_rows.size() > 0) {
|
if (_rows.size() > 0) {
|
||||||
Integer key = Integer.valueOf(row.getRowNum());
|
Integer key = Integer.valueOf(row.getRowNum());
|
||||||
|
@ -571,12 +578,45 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
|
||||||
public int addMergedRegion(CellRangeAddress region)
|
public int addMergedRegion(CellRangeAddress region)
|
||||||
{
|
{
|
||||||
region.validate(SpreadsheetVersion.EXCEL97);
|
region.validate(SpreadsheetVersion.EXCEL97);
|
||||||
|
|
||||||
|
// throw IllegalStateException if the argument CellRangeAddress intersects with
|
||||||
|
// a multi-cell array formula defined in this sheet
|
||||||
|
validateArrayFormulas(region);
|
||||||
|
|
||||||
return _sheet.addMergedRegion( region.getFirstRow(),
|
return _sheet.addMergedRegion( region.getFirstRow(),
|
||||||
region.getFirstColumn(),
|
region.getFirstColumn(),
|
||||||
region.getLastRow(),
|
region.getLastRow(),
|
||||||
region.getLastColumn());
|
region.getLastColumn());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateArrayFormulas(CellRangeAddress region){
|
||||||
|
int firstRow = region.getFirstRow();
|
||||||
|
int firstColumn = region.getFirstColumn();
|
||||||
|
int lastRow = region.getLastRow();
|
||||||
|
int lastColumn = region.getLastColumn();
|
||||||
|
for (int rowIn = firstRow; rowIn <= lastRow; rowIn++) {
|
||||||
|
for (int colIn = firstColumn; colIn <= lastColumn; colIn++) {
|
||||||
|
HSSFRow row = getRow(rowIn);
|
||||||
|
if (row == null) continue;
|
||||||
|
|
||||||
|
HSSFCell cell = row.getCell(colIn);
|
||||||
|
if(cell == null) continue;
|
||||||
|
|
||||||
|
if(cell.isPartOfArrayFormulaGroup()){
|
||||||
|
CellRangeAddress arrayRange = cell.getArrayFormulaRange();
|
||||||
|
if (arrayRange.getNumberOfCells() > 1 &&
|
||||||
|
( arrayRange.isInRange(region.getFirstRow(), region.getFirstColumn()) ||
|
||||||
|
arrayRange.isInRange(region.getFirstRow(), region.getFirstColumn())) ){
|
||||||
|
String msg = "The range " + region.formatAsString() + " intersects with a multi-cell array formula. " +
|
||||||
|
"You cannot merge cells of an array.";
|
||||||
|
throw new IllegalStateException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether a record must be inserted or not at generation to indicate that
|
* Whether a record must be inserted or not at generation to indicate that
|
||||||
* formula must be recalculated when workbook is opened.
|
* formula must be recalculated when workbook is opened.
|
||||||
|
@ -1214,6 +1254,11 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
|
||||||
|
|
||||||
for ( int rowNum = s; rowNum >= startRow && rowNum <= endRow && rowNum >= 0 && rowNum < 65536; rowNum += inc ) {
|
for ( int rowNum = s; rowNum >= startRow && rowNum <= endRow && rowNum >= 0 && rowNum < 65536; rowNum += inc ) {
|
||||||
HSSFRow row = getRow( rowNum );
|
HSSFRow row = getRow( rowNum );
|
||||||
|
// notify all cells in this row that we are going to shift them,
|
||||||
|
// it can throw IllegalStateException if the operation is not allowed, for example,
|
||||||
|
// if the row contains cells included in a multi-cell array formula
|
||||||
|
if(row != null) notifyRowShifting(row);
|
||||||
|
|
||||||
HSSFRow row2Replace = getRow( rowNum + n );
|
HSSFRow row2Replace = getRow( rowNum + n );
|
||||||
if ( row2Replace == null )
|
if ( row2Replace == null )
|
||||||
row2Replace = createRow( rowNum + n );
|
row2Replace = createRow( rowNum + n );
|
||||||
|
@ -1301,6 +1346,17 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
|
||||||
_sheet.getRecords().addAll(window2Loc, records);
|
_sheet.getRecords().addAll(window2Loc, records);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void notifyRowShifting(HSSFRow row){
|
||||||
|
String msg = "Row[rownum="+row.getRowNum()+"] contains cell(s) included in a multi-cell array formula. " +
|
||||||
|
"You cannot change part of an array.";
|
||||||
|
for(Cell cell : row){
|
||||||
|
HSSFCell hcell = (HSSFCell)cell;
|
||||||
|
if(hcell.isPartOfArrayFormulaGroup()){
|
||||||
|
hcell.notifyArrayFormulaChanging(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a split (freezepane). Any existing freezepane or split pane is overwritten.
|
* Creates a split (freezepane). Any existing freezepane or split pane is overwritten.
|
||||||
* @param colSplit Horizonatal position of split.
|
* @param colSplit Horizonatal position of split.
|
||||||
|
|
|
@ -385,8 +385,13 @@ public final class XSSFCell implements Cell {
|
||||||
* @param formula the formula to set, e.g. <code>"SUM(C4:E4)"</code>.
|
* @param formula the formula to set, e.g. <code>"SUM(C4:E4)"</code>.
|
||||||
* If the argument is <code>null</code> then the current formula is removed.
|
* If the argument is <code>null</code> then the current formula is removed.
|
||||||
* @throws org.apache.poi.ss.formula.FormulaParseException if the formula has incorrect syntax or is otherwise invalid
|
* @throws org.apache.poi.ss.formula.FormulaParseException if the formula has incorrect syntax or is otherwise invalid
|
||||||
|
* @throws IllegalStateException if the operation is not allowed, for example,
|
||||||
|
* when the cell is a part of a multi-cell array formula
|
||||||
*/
|
*/
|
||||||
public void setCellFormula(String formula) {
|
public void setCellFormula(String formula) {
|
||||||
|
if(isPartOfArrayFormulaGroup()){
|
||||||
|
notifyArrayFormulaChanging();
|
||||||
|
}
|
||||||
setFormula(formula, FormulaType.CELL);
|
setFormula(formula, FormulaType.CELL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -694,6 +699,10 @@ public final class XSSFCell implements Cell {
|
||||||
* @see #CELL_TYPE_ERROR
|
* @see #CELL_TYPE_ERROR
|
||||||
*/
|
*/
|
||||||
public void setCellType(int cellType) {
|
public void setCellType(int cellType) {
|
||||||
|
if(isPartOfArrayFormulaGroup()){
|
||||||
|
notifyArrayFormulaChanging();
|
||||||
|
}
|
||||||
|
|
||||||
int prevType = getCellType();
|
int prevType = getCellType();
|
||||||
switch (cellType) {
|
switch (cellType) {
|
||||||
case CELL_TYPE_BLANK:
|
case CELL_TYPE_BLANK:
|
||||||
|
@ -982,4 +991,41 @@ public final class XSSFCell implements Cell {
|
||||||
public boolean isPartOfArrayFormulaGroup() {
|
public boolean isPartOfArrayFormulaGroup() {
|
||||||
return getSheet().isCellInArrayFormulaContext(this);
|
return getSheet().isCellInArrayFormulaContext(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The purpose of this method is to validate the cell state prior to modification
|
||||||
|
*
|
||||||
|
* @see #notifyArrayFormulaChanging()
|
||||||
|
*/
|
||||||
|
void notifyArrayFormulaChanging(String msg){
|
||||||
|
if(isPartOfArrayFormulaGroup()){
|
||||||
|
CellRangeAddress cra = getArrayFormulaRange();
|
||||||
|
if(cra.getNumberOfCells() > 1) {
|
||||||
|
throw new IllegalStateException(msg);
|
||||||
|
}
|
||||||
|
//un-register the single-cell array formula from the parent XSSFSheet
|
||||||
|
getRow().getSheet().removeArrayFormula(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when this cell is modified.
|
||||||
|
* <p>
|
||||||
|
* The purpose of this method is to validate the cell state prior to modification.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @see #setCellType(int)
|
||||||
|
* @see #setCellFormula(String)
|
||||||
|
* @see XSSFRow#removeCell(org.apache.poi.ss.usermodel.Cell)
|
||||||
|
* @see org.apache.poi.xssf.usermodel.XSSFSheet#removeRow(org.apache.poi.ss.usermodel.Row)
|
||||||
|
* @see org.apache.poi.xssf.usermodel.XSSFSheet#shiftRows(int, int, int)
|
||||||
|
* @see org.apache.poi.xssf.usermodel.XSSFSheet#addMergedRegion(org.apache.poi.ss.util.CellRangeAddress)
|
||||||
|
* @throws IllegalStateException if modification is not allowed
|
||||||
|
*/
|
||||||
|
void notifyArrayFormulaChanging(){
|
||||||
|
CellReference ref = new CellReference(this);
|
||||||
|
String msg = "Cell "+ref.formatAsString()+" is part of a multi-cell array formula. " +
|
||||||
|
"You cannot change part of an array.";
|
||||||
|
notifyArrayFormulaChanging(msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -361,7 +361,15 @@ public class XSSFRow implements Row, Comparable<XSSFRow> {
|
||||||
* @param cell the cell to remove
|
* @param cell the cell to remove
|
||||||
*/
|
*/
|
||||||
public void removeCell(Cell cell) {
|
public void removeCell(Cell cell) {
|
||||||
_cells.remove(cell.getColumnIndex());
|
if (cell.getRow() != this) {
|
||||||
|
throw new IllegalArgumentException("Specified cell does not belong to this row");
|
||||||
|
}
|
||||||
|
|
||||||
|
XSSFCell xcell = (XSSFCell)cell;
|
||||||
|
if(xcell.isPartOfArrayFormulaGroup()){
|
||||||
|
xcell.notifyArrayFormulaChanging();
|
||||||
|
}
|
||||||
|
_cells.remove(cell.getColumnIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -409,8 +417,13 @@ public class XSSFRow implements Row, Comparable<XSSFRow> {
|
||||||
int rownum = getRowNum() + n;
|
int rownum = getRowNum() + n;
|
||||||
CalculationChain calcChain = _sheet.getWorkbook().getCalculationChain();
|
CalculationChain calcChain = _sheet.getWorkbook().getCalculationChain();
|
||||||
int sheetId = (int)_sheet.sheet.getSheetId();
|
int sheetId = (int)_sheet.sheet.getSheetId();
|
||||||
|
String msg = "Row[rownum="+getRowNum()+"] contains cell(s) included in a multi-cell array formula. " +
|
||||||
|
"You cannot change part of an array.";
|
||||||
for(Cell c : this){
|
for(Cell c : this){
|
||||||
XSSFCell cell = (XSSFCell)c;
|
XSSFCell cell = (XSSFCell)c;
|
||||||
|
if(cell.isPartOfArrayFormulaGroup()){
|
||||||
|
cell.notifyArrayFormulaChanging(msg);
|
||||||
|
}
|
||||||
|
|
||||||
//remove the reference in the calculation chain
|
//remove the reference in the calculation chain
|
||||||
if(calcChain != null) calcChain.removeItem(sheetId, cell.getReference());
|
if(calcChain != null) calcChain.removeItem(sheetId, cell.getReference());
|
||||||
|
|
|
@ -244,18 +244,50 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
||||||
/**
|
/**
|
||||||
* Adds a merged region of cells (hence those cells form one).
|
* Adds a merged region of cells (hence those cells form one).
|
||||||
*
|
*
|
||||||
* @param cra (rowfrom/colfrom-rowto/colto) to merge
|
* @param region (rowfrom/colfrom-rowto/colto) to merge
|
||||||
* @return index of this region
|
* @return index of this region
|
||||||
*/
|
*/
|
||||||
public int addMergedRegion(CellRangeAddress cra) {
|
public int addMergedRegion(CellRangeAddress region) {
|
||||||
cra.validate(SpreadsheetVersion.EXCEL2007);
|
region.validate(SpreadsheetVersion.EXCEL2007);
|
||||||
|
|
||||||
|
// throw IllegalStateException if the argument CellRangeAddress intersects with
|
||||||
|
// a multi-cell array formula defined in this sheet
|
||||||
|
validateArrayFormulas(region);
|
||||||
|
|
||||||
CTMergeCells ctMergeCells = worksheet.isSetMergeCells() ? worksheet.getMergeCells() : worksheet.addNewMergeCells();
|
CTMergeCells ctMergeCells = worksheet.isSetMergeCells() ? worksheet.getMergeCells() : worksheet.addNewMergeCells();
|
||||||
CTMergeCell ctMergeCell = ctMergeCells.addNewMergeCell();
|
CTMergeCell ctMergeCell = ctMergeCells.addNewMergeCell();
|
||||||
ctMergeCell.setRef(cra.formatAsString());
|
ctMergeCell.setRef(region.formatAsString());
|
||||||
return ctMergeCells.sizeOfMergeCellArray();
|
return ctMergeCells.sizeOfMergeCellArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateArrayFormulas(CellRangeAddress region){
|
||||||
|
int firstRow = region.getFirstRow();
|
||||||
|
int firstColumn = region.getFirstColumn();
|
||||||
|
int lastRow = region.getLastRow();
|
||||||
|
int lastColumn = region.getLastColumn();
|
||||||
|
for (int rowIn = firstRow; rowIn <= lastRow; rowIn++) {
|
||||||
|
for (int colIn = firstColumn; colIn <= lastColumn; colIn++) {
|
||||||
|
XSSFRow row = getRow(rowIn);
|
||||||
|
if (row == null) continue;
|
||||||
|
|
||||||
|
XSSFCell cell = row.getCell(colIn);
|
||||||
|
if(cell == null) continue;
|
||||||
|
|
||||||
|
if(cell.isPartOfArrayFormulaGroup()){
|
||||||
|
CellRangeAddress arrayRange = cell.getArrayFormulaRange();
|
||||||
|
if (arrayRange.getNumberOfCells() > 1 &&
|
||||||
|
( arrayRange.isInRange(region.getFirstRow(), region.getFirstColumn()) ||
|
||||||
|
arrayRange.isInRange(region.getFirstRow(), region.getFirstColumn())) ){
|
||||||
|
String msg = "The range " + region.formatAsString() + " intersects with a multi-cell array formula. " +
|
||||||
|
"You cannot merge cells of an array.";
|
||||||
|
throw new IllegalStateException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts the column width to fit the contents.
|
* Adjusts the column width to fit the contents.
|
||||||
*
|
*
|
||||||
|
@ -1279,7 +1311,13 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
||||||
if (row.getSheet() != this) {
|
if (row.getSheet() != this) {
|
||||||
throw new IllegalArgumentException("Specified row does not belong to this sheet");
|
throw new IllegalArgumentException("Specified row does not belong to this sheet");
|
||||||
}
|
}
|
||||||
|
for(Cell cell : row) {
|
||||||
|
XSSFCell xcell = (XSSFCell)cell;
|
||||||
|
String msg = "Row[rownum="+row.getRowNum()+"] contains cell(s) included in a multi-cell array formula. You cannot change part of an array.";
|
||||||
|
if(xcell.isPartOfArrayFormulaGroup()){
|
||||||
|
xcell.notifyArrayFormulaChanging(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
rows.remove(row.getRowNum());
|
rows.remove(row.getRowNum());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,7 @@ package org.apache.poi.xssf.usermodel;
|
||||||
|
|
||||||
import junit.framework.AssertionFailedError;
|
import junit.framework.AssertionFailedError;
|
||||||
|
|
||||||
import org.apache.poi.ss.usermodel.BaseTestSheetUpdateArrayFormulas;
|
import org.apache.poi.ss.usermodel.*;
|
||||||
import org.apache.poi.ss.usermodel.CellRange;
|
|
||||||
import org.apache.poi.ss.util.CellRangeAddress;
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
import org.apache.poi.xssf.XSSFITestDataProvider;
|
import org.apache.poi.xssf.XSSFITestDataProvider;
|
||||||
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCell;
|
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCell;
|
||||||
|
@ -106,4 +105,5 @@ public final class TestXSSFSheetUpdateArrayFormulas extends BaseTestSheetUpdateA
|
||||||
assertEquals(STCellFormulaType.ARRAY, f.getT());
|
assertEquals(STCellFormulaType.ARRAY, f.getT());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,5 +100,8 @@ public final class TestFormulaRecordAggregate extends TestCase {
|
||||||
Ptg[] ptg = agg.getFormulaTokens();
|
Ptg[] ptg = agg.getFormulaTokens();
|
||||||
String fmlaSer = FormulaRenderer.toFormulaString(null, ptg);
|
String fmlaSer = FormulaRenderer.toFormulaString(null, ptg);
|
||||||
assertEquals(formula, fmlaSer);
|
assertEquals(formula, fmlaSer);
|
||||||
}
|
|
||||||
|
agg.removeArrayFormula(rownum, colnum);
|
||||||
|
assertFalse(agg.isPartOfArrayFormula());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,7 @@ import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
|
||||||
import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate;
|
import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate;
|
||||||
import org.apache.poi.hssf.record.aggregates.SharedValueManager;
|
import org.apache.poi.hssf.record.aggregates.SharedValueManager;
|
||||||
import org.apache.poi.hssf.record.aggregates.TestSharedValueManager;
|
import org.apache.poi.hssf.record.aggregates.TestSharedValueManager;
|
||||||
import org.apache.poi.ss.usermodel.BaseTestSheetUpdateArrayFormulas;
|
import org.apache.poi.ss.usermodel.*;
|
||||||
import org.apache.poi.ss.usermodel.CellRange;
|
|
||||||
import org.apache.poi.ss.util.CellRangeAddress;
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -233,4 +233,264 @@ public abstract class BaseTestSheetUpdateArrayFormulas extends TestCase {
|
||||||
assertTrue( cell1.isPartOfArrayFormulaGroup());
|
assertTrue( cell1.isPartOfArrayFormulaGroup());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that we can set pre-calculated formula result for array formulas
|
||||||
|
*/
|
||||||
|
public void testModifyArrayCells_setFormulaResult(){
|
||||||
|
Workbook workbook = _testDataProvider.createWorkbook();
|
||||||
|
Sheet sheet = workbook.createSheet();
|
||||||
|
|
||||||
|
//single-cell array formula
|
||||||
|
CellRange<? extends Cell> srange =
|
||||||
|
sheet.setArrayFormula("SUM(A4:A6,B4:B6)", CellRangeAddress.valueOf("B5"));
|
||||||
|
Cell scell = srange.getTopLeftCell();
|
||||||
|
assertEquals(Cell.CELL_TYPE_FORMULA, scell.getCellType());
|
||||||
|
assertEquals(0.0, scell.getNumericCellValue());
|
||||||
|
scell.setCellValue(1.1);
|
||||||
|
assertEquals(1.1, scell.getNumericCellValue());
|
||||||
|
|
||||||
|
//multi-cell array formula
|
||||||
|
CellRange<? extends Cell> mrange =
|
||||||
|
sheet.setArrayFormula("A1:A3*B1:B3", CellRangeAddress.valueOf("C1:C3"));
|
||||||
|
for(Cell mcell : mrange){
|
||||||
|
assertEquals(Cell.CELL_TYPE_FORMULA, mcell.getCellType());
|
||||||
|
assertEquals(0.0, mcell.getNumericCellValue());
|
||||||
|
double fmlaResult = 1.2;
|
||||||
|
mcell.setCellValue(fmlaResult);
|
||||||
|
assertEquals(fmlaResult, mcell.getNumericCellValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testModifyArrayCells_setCellType(){
|
||||||
|
Workbook workbook = _testDataProvider.createWorkbook();
|
||||||
|
Sheet sheet = workbook.createSheet();
|
||||||
|
|
||||||
|
// single-cell array formulas behave just like normal cells -
|
||||||
|
// changing cell type removes the array formula and associated cached result
|
||||||
|
CellRange<? extends Cell> srange =
|
||||||
|
sheet.setArrayFormula("SUM(A4:A6,B4:B6)", CellRangeAddress.valueOf("B5"));
|
||||||
|
Cell scell = srange.getTopLeftCell();
|
||||||
|
assertEquals(Cell.CELL_TYPE_FORMULA, scell.getCellType());
|
||||||
|
assertEquals(0.0, scell.getNumericCellValue());
|
||||||
|
scell.setCellType(Cell.CELL_TYPE_STRING);
|
||||||
|
assertEquals(Cell.CELL_TYPE_STRING, scell.getCellType());
|
||||||
|
scell.setCellValue("string cell");
|
||||||
|
assertEquals("string cell", scell.getStringCellValue());
|
||||||
|
|
||||||
|
//once you create a multi-cell array formula, you cannot change the type of its cells
|
||||||
|
CellRange<? extends Cell> mrange =
|
||||||
|
sheet.setArrayFormula("A1:A3*B1:B3", CellRangeAddress.valueOf("C1:C3"));
|
||||||
|
for(Cell mcell : mrange){
|
||||||
|
try {
|
||||||
|
assertEquals(Cell.CELL_TYPE_FORMULA, mcell.getCellType());
|
||||||
|
mcell.setCellType(Cell.CELL_TYPE_NUMERIC);
|
||||||
|
fail("expected exception");
|
||||||
|
} catch (IllegalStateException e){
|
||||||
|
CellReference ref = new CellReference(mcell);
|
||||||
|
String msg = "Cell "+ref.formatAsString()+" is part of a multi-cell array formula. You cannot change part of an array.";
|
||||||
|
assertEquals(msg, e.getMessage());
|
||||||
|
}
|
||||||
|
// a failed invocation of Cell.setCellType leaves the cell
|
||||||
|
// in the state that it was in prior to the invocation
|
||||||
|
assertEquals(Cell.CELL_TYPE_FORMULA, mcell.getCellType());
|
||||||
|
assertTrue(mcell.isPartOfArrayFormulaGroup());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testModifyArrayCells_setCellFormula(){
|
||||||
|
Workbook workbook = _testDataProvider.createWorkbook();
|
||||||
|
Sheet sheet = workbook.createSheet();
|
||||||
|
|
||||||
|
CellRange<? extends Cell> srange =
|
||||||
|
sheet.setArrayFormula("SUM(A4:A6,B4:B6)", CellRangeAddress.valueOf("B5"));
|
||||||
|
Cell scell = srange.getTopLeftCell();
|
||||||
|
assertEquals("SUM(A4:A6,B4:B6)", scell.getCellFormula());
|
||||||
|
assertEquals(Cell.CELL_TYPE_FORMULA, scell.getCellType());
|
||||||
|
assertTrue(scell.isPartOfArrayFormulaGroup());
|
||||||
|
scell.setCellFormula("SUM(A4,A6)");
|
||||||
|
//we are now a normal formula cell
|
||||||
|
assertEquals("SUM(A4,A6)", scell.getCellFormula());
|
||||||
|
assertFalse(scell.isPartOfArrayFormulaGroup());
|
||||||
|
assertEquals(Cell.CELL_TYPE_FORMULA, scell.getCellType());
|
||||||
|
//check that setting formula result works
|
||||||
|
assertEquals(0.0, scell.getNumericCellValue());
|
||||||
|
scell.setCellValue(33.0);
|
||||||
|
assertEquals(33.0, scell.getNumericCellValue());
|
||||||
|
|
||||||
|
//multi-cell array formula
|
||||||
|
CellRange<? extends Cell> mrange =
|
||||||
|
sheet.setArrayFormula("A1:A3*B1:B3", CellRangeAddress.valueOf("C1:C3"));
|
||||||
|
for(Cell mcell : mrange){
|
||||||
|
//we cannot set individual formulas for cells included in an array formula
|
||||||
|
try {
|
||||||
|
assertEquals("A1:A3*B1:B3", mcell.getCellFormula());
|
||||||
|
mcell.setCellFormula("A1+A2");
|
||||||
|
fail("expected exception");
|
||||||
|
} catch (IllegalStateException e){
|
||||||
|
CellReference ref = new CellReference(mcell);
|
||||||
|
String msg = "Cell "+ref.formatAsString()+" is part of a multi-cell array formula. You cannot change part of an array.";
|
||||||
|
assertEquals(msg, e.getMessage());
|
||||||
|
}
|
||||||
|
// a failed invocation of Cell.setCellFormula leaves the cell
|
||||||
|
// in the state that it was in prior to the invocation
|
||||||
|
assertEquals("A1:A3*B1:B3", mcell.getCellFormula());
|
||||||
|
assertTrue(mcell.isPartOfArrayFormulaGroup());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testModifyArrayCells_removeCell(){
|
||||||
|
Workbook workbook = _testDataProvider.createWorkbook();
|
||||||
|
Sheet sheet = workbook.createSheet();
|
||||||
|
|
||||||
|
//single-cell array formulas behave just like normal cells
|
||||||
|
CellRangeAddress cra = CellRangeAddress.valueOf("B5");
|
||||||
|
CellRange<? extends Cell> srange =
|
||||||
|
sheet.setArrayFormula("SUM(A4:A6,B4:B6)", cra);
|
||||||
|
Cell scell = srange.getTopLeftCell();
|
||||||
|
|
||||||
|
Row srow = sheet.getRow(cra.getFirstRow());
|
||||||
|
assertSame(srow, scell.getRow());
|
||||||
|
srow.removeCell(scell);
|
||||||
|
assertNull(srow.getCell(cra.getFirstColumn()));
|
||||||
|
|
||||||
|
//re-create the removed cell
|
||||||
|
scell = srow.createCell(cra.getFirstColumn());
|
||||||
|
assertEquals(Cell.CELL_TYPE_BLANK, scell.getCellType());
|
||||||
|
assertFalse(scell.isPartOfArrayFormulaGroup());
|
||||||
|
|
||||||
|
//we cannot remove cells included in a multi-cell array formula
|
||||||
|
CellRange<? extends Cell> mrange =
|
||||||
|
sheet.setArrayFormula("A1:A3*B1:B3", CellRangeAddress.valueOf("C1:C3"));
|
||||||
|
for(Cell mcell : mrange){
|
||||||
|
int columnIndex = mcell.getColumnIndex();
|
||||||
|
Row mrow = mcell.getRow();
|
||||||
|
try {
|
||||||
|
mrow.removeCell(mcell);
|
||||||
|
fail("expected exception");
|
||||||
|
} catch (IllegalStateException e){
|
||||||
|
CellReference ref = new CellReference(mcell);
|
||||||
|
String msg = "Cell "+ref.formatAsString()+" is part of a multi-cell array formula. You cannot change part of an array.";
|
||||||
|
assertEquals(msg, e.getMessage());
|
||||||
|
}
|
||||||
|
// a failed invocation of Row.removeCell leaves the row
|
||||||
|
// in the state that it was in prior to the invocation
|
||||||
|
assertSame(mcell, mrow.getCell(columnIndex));
|
||||||
|
assertTrue(mcell.isPartOfArrayFormulaGroup());
|
||||||
|
assertEquals(Cell.CELL_TYPE_FORMULA, mcell.getCellType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testModifyArrayCells_removeRow(){
|
||||||
|
Workbook workbook = _testDataProvider.createWorkbook();
|
||||||
|
Sheet sheet = workbook.createSheet();
|
||||||
|
|
||||||
|
//single-cell array formulas behave just like normal cells
|
||||||
|
CellRangeAddress cra = CellRangeAddress.valueOf("B5");
|
||||||
|
CellRange<? extends Cell> srange =
|
||||||
|
sheet.setArrayFormula("SUM(A4:A6,B4:B6)", cra);
|
||||||
|
Cell scell = srange.getTopLeftCell();
|
||||||
|
assertEquals(Cell.CELL_TYPE_FORMULA, scell.getCellType());
|
||||||
|
|
||||||
|
Row srow = scell.getRow();
|
||||||
|
assertSame(srow, sheet.getRow(cra.getFirstRow()));
|
||||||
|
sheet.removeRow(srow);
|
||||||
|
assertNull(sheet.getRow(cra.getFirstRow()));
|
||||||
|
|
||||||
|
//re-create the removed row and cell
|
||||||
|
scell = sheet.createRow(cra.getFirstRow()).createCell(cra.getFirstColumn());
|
||||||
|
assertEquals(Cell.CELL_TYPE_BLANK, scell.getCellType());
|
||||||
|
assertFalse(scell.isPartOfArrayFormulaGroup());
|
||||||
|
|
||||||
|
//we cannot remove rows with cells included in a multi-cell array formula
|
||||||
|
CellRange<? extends Cell> mrange =
|
||||||
|
sheet.setArrayFormula("A1:A3*B1:B3", CellRangeAddress.valueOf("C1:C3"));
|
||||||
|
for(Cell mcell : mrange){
|
||||||
|
int columnIndex = mcell.getColumnIndex();
|
||||||
|
Row mrow = mcell.getRow();
|
||||||
|
try {
|
||||||
|
sheet.removeRow(mrow);
|
||||||
|
fail("expected exception");
|
||||||
|
} catch (IllegalStateException e){
|
||||||
|
String msg = "Row[rownum="+mrow.getRowNum()+"] contains cell(s) included in a multi-cell array formula. You cannot change part of an array.";
|
||||||
|
assertEquals(msg, e.getMessage());
|
||||||
|
}
|
||||||
|
// a failed invocation of Row.removeCell leaves the row
|
||||||
|
// in the state that it was in prior to the invocation
|
||||||
|
assertSame(mrow, sheet.getRow(mrow.getRowNum()));
|
||||||
|
assertSame(mcell, mrow.getCell(columnIndex));
|
||||||
|
assertTrue(mcell.isPartOfArrayFormulaGroup());
|
||||||
|
assertEquals(Cell.CELL_TYPE_FORMULA, mcell.getCellType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testModifyArrayCells_mergeCells(){
|
||||||
|
Workbook workbook = _testDataProvider.createWorkbook();
|
||||||
|
Sheet sheet = workbook.createSheet();
|
||||||
|
assertEquals(0, sheet.getNumMergedRegions());
|
||||||
|
|
||||||
|
//single-cell array formulas behave just like normal cells
|
||||||
|
CellRange<? extends Cell> srange =
|
||||||
|
sheet.setArrayFormula("SUM(A4:A6,B4:B6)", CellRangeAddress.valueOf("B5"));
|
||||||
|
Cell scell = srange.getTopLeftCell();
|
||||||
|
sheet.addMergedRegion(CellRangeAddress.valueOf("B5:C6"));
|
||||||
|
//we are still an array formula
|
||||||
|
assertEquals(Cell.CELL_TYPE_FORMULA, scell.getCellType());
|
||||||
|
assertTrue(scell.isPartOfArrayFormulaGroup());
|
||||||
|
assertEquals(1, sheet.getNumMergedRegions());
|
||||||
|
|
||||||
|
//we cannot merge cells included in an array formula
|
||||||
|
CellRange<? extends Cell> mrange =
|
||||||
|
sheet.setArrayFormula("A1:A3*B1:B3", CellRangeAddress.valueOf("C1:C3"));
|
||||||
|
CellRangeAddress cra = CellRangeAddress.valueOf("C1:C3");
|
||||||
|
try {
|
||||||
|
sheet.addMergedRegion(cra);
|
||||||
|
fail("expected exception");
|
||||||
|
} catch (IllegalStateException e){
|
||||||
|
String msg = "The range "+cra.formatAsString()+" intersects with a multi-cell array formula. You cannot merge cells of an array.";
|
||||||
|
assertEquals(msg, e.getMessage());
|
||||||
|
}
|
||||||
|
//the number of merged regions remains the same
|
||||||
|
assertEquals(1, sheet.getNumMergedRegions());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testModifyArrayCells_shiftRows(){
|
||||||
|
Workbook workbook = _testDataProvider.createWorkbook();
|
||||||
|
Sheet sheet = workbook.createSheet();
|
||||||
|
|
||||||
|
//single-cell array formulas behave just like normal cells - we can change the cell type
|
||||||
|
CellRange<? extends Cell> srange =
|
||||||
|
sheet.setArrayFormula("SUM(A4:A6,B4:B6)", CellRangeAddress.valueOf("B5"));
|
||||||
|
Cell scell = srange.getTopLeftCell();
|
||||||
|
assertEquals("SUM(A4:A6,B4:B6)", scell.getCellFormula());
|
||||||
|
sheet.shiftRows(0, 0, 1);
|
||||||
|
sheet.shiftRows(0, 1, 1);
|
||||||
|
|
||||||
|
//we cannot set individual formulas for cells included in an array formula
|
||||||
|
CellRange<? extends Cell> mrange =
|
||||||
|
sheet.setArrayFormula("A1:A3*B1:B3", CellRangeAddress.valueOf("C1:C3"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
sheet.shiftRows(0, 0, 1);
|
||||||
|
fail("expected exception");
|
||||||
|
} catch (IllegalStateException e){
|
||||||
|
String msg = "Row[rownum=0] contains cell(s) included in a multi-cell array formula. You cannot change part of an array.";
|
||||||
|
assertEquals(msg, e.getMessage());
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
TODO: enable shifting the whole array
|
||||||
|
|
||||||
|
sheet.shiftRows(0, 2, 1);
|
||||||
|
//the array C1:C3 is now C2:C4
|
||||||
|
CellRangeAddress cra = CellRangeAddress.valueOf("C2:C4");
|
||||||
|
for(Cell mcell : mrange){
|
||||||
|
//TODO define equals and hashcode for CellRangeAddress
|
||||||
|
assertEquals(cra.formatAsString(), mcell.getArrayFormulaRange().formatAsString());
|
||||||
|
assertEquals("A2:A4*B2:B4", mcell.getCellFormula());
|
||||||
|
assertTrue(mcell.isPartOfArrayFormulaGroup());
|
||||||
|
assertEquals(Cell.CELL_TYPE_FORMULA, mcell.getCellType());
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue