import org.apache.poi.ss.usermodel.CellBase;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.usermodel.Comment;
import org.apache.poi.ss.usermodel.FormulaError;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Hyperlink;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.util.CellAddress;
frec.setRow(row);
frec.setColumn(col);
}
- if (setValue)
- {
+ if (getCellType() == CellType.BLANK) {
+ frec.getFormulaRecord().setValue(0);
+ } else if (setValue) {
frec.getFormulaRecord().setValue(getNumericCellValue());
}
frec.setXFIndex(styleIndex);
short col=_record.getColumn();
short styleIndex=_record.getXFIndex();
+ final CellValue savedValue = readValue();
int sheetIndex = _book.getSheetIndex(_sheet);
Ptg[] ptgs = HSSFFormulaParser.parse(formula, _book, FormulaType.CELL, sheetIndex);
setCellType(CellType.FORMULA, false, row, col, styleIndex);
FormulaRecord frec = agg.getFormulaRecord();
frec.setOptions((short) 2);
- frec.setValue(0);
-
//only set to default if there is no extended format index already set
if (agg.getXFIndex() == (short)0) {
agg.setXFIndex((short) 0x0f);
}
agg.setParsedExpression(ptgs);
+
+ restoreValue(savedValue);
+ }
+
+ private CellValue readValue() {
+ final CellType valueType = getCellType() == CellType.FORMULA ? getCachedFormulaResultType() : getCellType();
+ switch (valueType) {
+ case NUMERIC:
+ return new CellValue(getNumericCellValue());
+ case STRING:
+ return new CellValue(getStringCellValue());
+ case BOOLEAN:
+ return CellValue.valueOf(getBooleanCellValue());
+ case ERROR:
+ return CellValue.getError(getErrorCellValue());
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ private void restoreValue(CellValue value) {
+ switch (value.getCellType()) {
+ case NUMERIC:
+ setCellValue(value.getNumberValue());
+ break;
+ case STRING:
+ setCellValue(value.getStringValue());
+ break;
+ case BOOLEAN:
+ setCellValue(value.getBooleanValue());
+ break;
+ case ERROR:
+ setCellErrorValue(FormulaError.forInt(value.getErrorValue()));
+ break;
+ default:
+ throw new IllegalStateException();
+ }
}
@Override
/**
* Sets formula for this cell.
+ * <p>If {@code formula} is not null, sets or updates the formula. If {@code formula} is null, removes the formula.
+ * Or use {@link #removeFormula()} to remove the formula.</p>
*
- * <p>
- * Note, this method only sets the formula string and does not calculate the formula value.
- * To set the precalculated value use {@link #setCellValue}
- * </p>
+ * <p>Note, this method only sets the formula string and does not calculate the formula value.
+ * To set the precalculated value use {@link #setCellValue}.</p>
*
- * <p>
- * If the cell was blank, sets value to 0. Otherwise, preserves the value as precalculated.
- * </p>
+ * <p>If the cell was blank, sets value to 0. Otherwise, preserves the value as precalculated.</p>
*
* @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.
*
+ * @see Cell#removeFormula
* @throws IllegalStateException if this cell is a part of an array formula group containing other cells
* @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
*/
*/
@Override
public final void setCellFormula(String formula) throws FormulaParseException, IllegalStateException {
+ // todo validate formula here, before changing the cell?
+ tryToDeleteArrayFormulaIfSet();
+
if (formula == null) {
removeFormula();
return;
}
- CellType previousValueType = getCellType() == CellType.FORMULA ? getCachedFormulaResultType() : getCellType();
-
- tryToDeleteArrayFormulaIfSet();
-
- setCellFormulaImpl(formula);
-
- if (previousValueType == CellType.BLANK) {
+ // formula cells always have a value. If the cell is blank (either initially or after removing an
+ // array formula), set value to 0
+ if (getValueType() == CellType.BLANK) {
setCellValue(0);
}
+
+ setCellFormulaImpl(formula);
}
/**
- * Implementation-specific setting the formula.
+ * Implementation-specific setting the formula. Formula is not null.
* Shall not change the value.
* @param formula
*/
protected abstract void setCellFormulaImpl(String formula);
+ /**
+ * Get value type of this cell. Can return BLANK, NUMERIC, STRING, BOOLEAN or ERROR.
+ * For current implementations where type is strongly coupled with formula, is equivalent to
+ * <code>getCellType() == CellType.FORMULA ? getCachedFormulaResultType() : getCellType()</code>
+ *
+ * <p>This is meant as a temporary helper method until the time when value type is decoupled from the formula.</p>
+ * @return value type
+ */
+ protected final CellType getValueType() {
+ CellType type = getCellType();
+ if (type != CellType.FORMULA) {
+ return type;
+ }
+ return getCachedFormulaResultType();
+ }
+
/**
* {@inheritDoc}
*/
public SXSSFCell(SXSSFRow row, CellType cellType)
{
_row=row;
+ _value = new BlankValue();
setType(cellType);
}
* @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
*/
@Override
- public void setCellFormulaImpl(String formula) throws FormulaParseException
- {
- if(formula == null) {
- setType(CellType.BLANK);
- return;
+ public void setCellFormulaImpl(String formula) throws FormulaParseException {
+ assert formula != null;
+ if (getCellType() == CellType.FORMULA) {
+ ((FormulaValue)_value).setValue(formula);
+ } else {
+ switch (getCellType()) {
+ case NUMERIC:
+ _value = new NumericFormulaValue(formula, getNumericCellValue());
+ break;
+ case STRING:
+ if (_value instanceof PlainStringValue) {
+ _value = new StringFormulaValue(formula, getStringCellValue());
+ } else {
+ assert(_value instanceof RichTextValue);
+ _value = new RichTextStringFormulaValue(formula, ((RichTextValue) _value).getValue());
+ }
+ break;
+ case BOOLEAN:
+ _value = new BooleanFormulaValue(formula, getBooleanCellValue());
+ break;
+ case ERROR:
+ _value = new ErrorFormulaValue(formula, getErrorCellValue());
+ break;
+ case _NONE:
+ // fall-through
+ case FORMULA:
+ // fall-through
+ case BLANK:
+ throw new AssertionError();
+ }
}
-
- ensureFormulaType(computeTypeFromFormula(formula));
- ((FormulaValue)_value).setValue(formula);
}
@Override
public void setCellErrorValue(byte value) {
// for formulas, we want to keep the type and only have an ERROR as formula value
if(_value.getType()==CellType.FORMULA) {
- // ensure that the type is "ERROR"
- setFormulaType(CellType.ERROR);
-
- // populate the value
- ((ErrorFormulaValue) _value).setPreEvaluatedValue(value);
+ _value = new ErrorFormulaValue(getCellFormula(), value);
} else {
- ensureTypeOrFormulaType(CellType.ERROR);
- ((ErrorValue) _value).setValue(value);
+ _value = new ErrorValue(value);
}
}
// don't change cell type for formulas
if(_value.getType() == CellType.FORMULA) {
String formula = ((FormulaValue)_value).getValue();
- _value = new RichTextStringFormulaValue();
- ((RichTextStringFormulaValue) _value).setValue(formula);
+ _value = new RichTextStringFormulaValue(formula, new XSSFRichTextString(""));
} else if(_value.getType()!=CellType.STRING ||
!((StringValue)_value).isRichText()) {
_value = new RichTextValue();
if(_value.getType()!=type)
setType(type);
}
- /*package*/ void ensureFormulaType(CellType type)
- {
- if(_value.getType()!=CellType.FORMULA
- ||((FormulaValue)_value).getFormulaType()!=type)
- setFormulaType(type);
- }
+
/*
* Sets the cell type to type if it is different
*/
{
if(((FormulaValue)_value).getFormulaType()==type)
return;
- setFormulaType(type); // once a formula, always a formula
+ switch (type) {
+ case BOOLEAN:
+ _value = new BooleanFormulaValue(getCellFormula(), false);
+ break;
+ case NUMERIC:
+ _value = new NumericFormulaValue(getCellFormula(), 0);
+ break;
+ case STRING:
+ _value = new StringFormulaValue(getCellFormula(), "");
+ break;
+ case ERROR:
+ _value = new ErrorFormulaValue(getCellFormula(), FormulaError._NO_ERROR.getCode());
+ break;
+ default:
+ throw new AssertionError();
+ }
return;
}
setType(type);
}
case FORMULA:
{
- _value = new NumericFormulaValue();
+ if (getCellType() == CellType.BLANK) {
+ _value = new NumericFormulaValue("", 0);
+ }
break;
}
case BLANK:
}
}
}
- /*package*/ void setFormulaType(CellType type)
- {
- Value prevValue = _value;
- switch(type)
- {
- case NUMERIC:
- {
- _value = new NumericFormulaValue();
- break;
- }
- case STRING:
- {
- _value = new StringFormulaValue();
- break;
- }
- case BOOLEAN:
- {
- _value = new BooleanFormulaValue();
- break;
- }
- case ERROR:
- {
- _value = new ErrorFormulaValue();
- break;
- }
- default:
- {
- 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)
- {
- return CellType.NUMERIC;
- }
//COPIED FROM https://svn.apache.org/repos/asf/poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java since the functions are declared private there
/**
* Used to help format error messages
{
CellType getType();
}
- static class NumericValue implements Value
- {
+ static class NumericValue implements Value {
double _value;
+
+ public NumericValue() {
+ _value = 0;
+ }
+
+ public NumericValue(double _value) {
+ this._value = _value;
+ }
+
public CellType getType()
{
return CellType.NUMERIC;
static abstract class FormulaValue implements Value
{
String _value;
+
+ public FormulaValue(String _value) {
+ this._value = _value;
+ }
+
public CellType getType()
{
return CellType.FORMULA;
}
abstract CellType getFormulaType();
}
- static class NumericFormulaValue extends FormulaValue
- {
+ static class NumericFormulaValue extends FormulaValue {
double _preEvaluatedValue;
+
+ public NumericFormulaValue(String formula, double _preEvaluatedValue) {
+ super(formula);
+ this._preEvaluatedValue = _preEvaluatedValue;
+ }
+
@Override
CellType getFormulaType()
{
return _preEvaluatedValue;
}
}
- static class StringFormulaValue extends FormulaValue
- {
+ static class StringFormulaValue extends FormulaValue {
String _preEvaluatedValue;
+
+ public StringFormulaValue(String formula, String value) {
+ super(formula);
+ _preEvaluatedValue = value;
+ }
+
@Override
CellType getFormulaType()
{
return _preEvaluatedValue;
}
}
- static class RichTextStringFormulaValue extends FormulaValue
- {
+
+ static class RichTextStringFormulaValue extends FormulaValue {
RichTextString _preEvaluatedValue;
+
+ public RichTextStringFormulaValue(String formula, RichTextString value) {
+ super(formula);
+ _preEvaluatedValue = value;
+ }
+
@Override
CellType getFormulaType()
{
return _preEvaluatedValue;
}
}
- static class BooleanFormulaValue extends FormulaValue
- {
+
+ static class BooleanFormulaValue extends FormulaValue {
boolean _preEvaluatedValue;
+
+ public BooleanFormulaValue(String formula, boolean value) {
+ super(formula);
+ _preEvaluatedValue = value;
+ }
+
@Override
CellType getFormulaType()
{
return _preEvaluatedValue;
}
}
- static class ErrorFormulaValue extends FormulaValue
- {
+
+ static class ErrorFormulaValue extends FormulaValue {
byte _preEvaluatedValue;
+
+ public ErrorFormulaValue(String formula, byte value) {
+ super(formula);
+ _preEvaluatedValue = value;
+ }
+
@Override
CellType getFormulaType()
{
return _preEvaluatedValue;
}
}
- static class BlankValue implements Value
- {
+
+ static class BlankValue implements Value {
public CellType getType()
{
return CellType.BLANK;
}
}
- static class BooleanValue implements Value
- {
+
+ static class BooleanValue implements Value {
boolean _value;
+
+ public BooleanValue() {
+ _value = false;
+ }
+
+ public BooleanValue(boolean _value) {
+ this._value = _value;
+ }
+
public CellType getType()
{
return CellType.BOOLEAN;
static class ErrorValue implements Value
{
byte _value;
+
+ public ErrorValue() {
+ _value = FormulaError._NO_ERROR.getCode();
+ }
+
+ public ErrorValue(byte _value) {
+ this._value = _value;
+ }
+
public CellType getType()
{
return CellType.ERROR;
*/
@Override
protected void setCellFormulaImpl(String formula) {
+ assert formula != null;
setFormula(formula, FormulaType.CELL);
}
f.setStringValue(formula);
_cell.setF(f);
}
- if(_cell.isSetV()) {
- _cell.unsetV();
- }
}
@Override
@Override
@Ignore
- @Test
public void removeFormula_turnsCellToBlank_whenFormulaWasASingleCellArrayFormula() {
}
+
+ @Override
+ @Ignore
+ public void setCellFormula_onASingleCellArrayFormulaCell_preservesTheValue() {
+ }
+
+ @Test
+ @Ignore
+ public void setCellFormula_isExceptionSafe_onBlankCell() {
+ }
}
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
+import java.util.function.Consumer;
import org.apache.poi.common.usermodel.HyperlinkType;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.ITestDataProvider;
import org.apache.poi.ss.SpreadsheetVersion;
+import org.apache.poi.ss.formula.FormulaParseException;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.util.LocaleUtil;
import org.junit.Test;
assertEquals(CellType.BLANK, cell.getCellType());
}
+ @Test
+ public void setCellFormula_onABlankCell_setsValueToZero() {
+ Cell cell = getInstance();
+ cell.setCellFormula("\"foo\"");
+ assertEquals(CellType.FORMULA, cell.getCellType());
+ assertEquals(CellType.NUMERIC, cell.getCachedFormulaResultType());
+ assertEquals(0, cell.getNumericCellValue(), 0);
+ }
+
+
+ @Test
+ public void setCellFormula_onANonBlankCell_preservesTheValue() {
+ Cell cell = getInstance();
+ cell.setCellValue(true);
+ cell.setCellFormula("\"foo\"");
+ assertEquals(CellType.FORMULA, cell.getCellType());
+ assertEquals(CellType.BOOLEAN, cell.getCachedFormulaResultType());
+ assertTrue(cell.getBooleanCellValue());
+ }
+
+ @Test
+ public void setCellFormula_onAFormulaCell_changeFormula_preservesTheValue() {
+ Cell cell = getInstance();
+ cell.setCellFormula("\"foo\"");
+ cell.setCellValue(true);
+ assertEquals(CellType.FORMULA, cell.getCellType());
+ assertEquals(CellType.BOOLEAN, cell.getCachedFormulaResultType());
+ assertTrue(cell.getBooleanCellValue());
+
+ cell.setCellFormula("\"bar\"");
+ assertEquals(CellType.FORMULA, cell.getCellType());
+ assertEquals(CellType.BOOLEAN, cell.getCachedFormulaResultType());
+ assertTrue(cell.getBooleanCellValue());
+ }
+
+ @Test
+ public void setCellFormula_onASingleCellArrayFormulaCell_preservesTheValue() {
+ Cell cell = getInstance();
+ cell.getSheet().setArrayFormula("\"foo\"", CellRangeAddress.valueOf("A1"));
+ cell.setCellValue(true);
+
+ assertTrue(cell.isPartOfArrayFormulaGroup());
+ assertEquals(CellType.FORMULA, cell.getCellType());
+ assertEquals(CellType.BOOLEAN, cell.getCachedFormulaResultType());
+ assertTrue(cell.getBooleanCellValue());
+
+ cell.getSheet().setArrayFormula("\"bar\"", CellRangeAddress.valueOf("A1"));
+ assertEquals(CellType.FORMULA, cell.getCellType());
+ assertEquals(CellType.BOOLEAN, cell.getCachedFormulaResultType());
+ assertTrue(cell.getBooleanCellValue());
+ }
+
private Cell getInstance() {
return _testDataProvider.createWorkbook().createSheet().createRow(0).createCell(0);
}