Changes to formula evaluation allowing for reduced memory usage
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@713811 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
7a98ca6d17
commit
9873ea6391
@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
<!-- Don't forget to update status.xml too! -->
|
<!-- Don't forget to update status.xml too! -->
|
||||||
<release version="3.5-beta4" date="2008-??-??">
|
<release version="3.5-beta4" date="2008-??-??">
|
||||||
|
<action dev="POI-DEVELOPERS" type="add">Changes to formula evaluation allowing for reduced memory usage</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">45290 - Support odd files where the POIFS header block comes after the data blocks, and is on the data blocks list</header>
|
<action dev="POI-DEVELOPERS" type="fix">45290 - Support odd files where the POIFS header block comes after the data blocks, and is on the data blocks list</header>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">46184 - More odd escaped date formats</action>
|
<action dev="POI-DEVELOPERS" type="fix">46184 - More odd escaped date formats</action>
|
||||||
<action dev="POI-DEVELOPERS" type="add">Include the sheet number in the output of XLS2CSVmra</action>
|
<action dev="POI-DEVELOPERS" type="add">Include the sheet number in the output of XLS2CSVmra</action>
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
<!-- Don't forget to update changes.xml too! -->
|
<!-- Don't forget to update changes.xml too! -->
|
||||||
<changes>
|
<changes>
|
||||||
<release version="3.5-beta4" date="2008-??-??">
|
<release version="3.5-beta4" date="2008-??-??">
|
||||||
|
<action dev="POI-DEVELOPERS" type="add">Changes to formula evaluation allowing for reduced memory usage</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">45290 - Support odd files where the POIFS header block comes after the data blocks, and is on the data blocks list</header>
|
<action dev="POI-DEVELOPERS" type="fix">45290 - Support odd files where the POIFS header block comes after the data blocks, and is on the data blocks list</header>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">46184 - More odd escaped date formats</action>
|
<action dev="POI-DEVELOPERS" type="fix">46184 - More odd escaped date formats</action>
|
||||||
<action dev="POI-DEVELOPERS" type="add">Include the sheet number in the output of XLS2CSVmra</action>
|
<action dev="POI-DEVELOPERS" type="add">Include the sheet number in the output of XLS2CSVmra</action>
|
||||||
|
@ -25,6 +25,7 @@ import org.apache.poi.hssf.record.formula.eval.NumberEval;
|
|||||||
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||||
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment;
|
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.WorkbookEvaluator;
|
||||||
import org.apache.poi.ss.usermodel.Cell;
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
import org.apache.poi.ss.usermodel.CellValue;
|
import org.apache.poi.ss.usermodel.CellValue;
|
||||||
@ -42,266 +43,273 @@ import org.apache.poi.ss.usermodel.FormulaEvaluator;
|
|||||||
*/
|
*/
|
||||||
public class HSSFFormulaEvaluator implements FormulaEvaluator {
|
public class HSSFFormulaEvaluator implements FormulaEvaluator {
|
||||||
|
|
||||||
private WorkbookEvaluator _bookEvaluator;
|
private WorkbookEvaluator _bookEvaluator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated (Sep 2008) HSSFSheet parameter is ignored
|
* @deprecated (Sep 2008) HSSFSheet parameter is ignored
|
||||||
*/
|
*/
|
||||||
public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) {
|
public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) {
|
||||||
this(workbook);
|
this(workbook);
|
||||||
if (false) {
|
if (false) {
|
||||||
sheet.toString(); // suppress unused parameter compiler warning
|
sheet.toString(); // suppress unused parameter compiler warning
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public HSSFFormulaEvaluator(HSSFWorkbook workbook) {
|
public HSSFFormulaEvaluator(HSSFWorkbook workbook) {
|
||||||
_bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook));
|
this(workbook, null);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @param stabilityClassifier used to optimise caching performance. Pass <code>null</code>
|
||||||
|
* for the (conservative) assumption that any cell may have its definition changed after
|
||||||
|
* evaluation begins.
|
||||||
|
*/
|
||||||
|
public HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) {
|
||||||
|
_bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook), stabilityClassifier);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coordinates several formula evaluators together so that formulas that involve external
|
* Coordinates several formula evaluators together so that formulas that involve external
|
||||||
* references can be evaluated.
|
* references can be evaluated.
|
||||||
* @param workbookNames the simple file names used to identify the workbooks in formulas
|
* @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")
|
* 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.
|
* @param evaluators all evaluators for the full set of workbooks required by the formulas.
|
||||||
*/
|
*/
|
||||||
public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) {
|
public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) {
|
||||||
WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length];
|
WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length];
|
||||||
for (int i = 0; i < wbEvals.length; i++) {
|
for (int i = 0; i < wbEvals.length; i++) {
|
||||||
wbEvals[i] = evaluators[i]._bookEvaluator;
|
wbEvals[i] = evaluators[i]._bookEvaluator;
|
||||||
}
|
}
|
||||||
CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals);
|
CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does nothing
|
* Does nothing
|
||||||
* @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell
|
* @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell
|
||||||
*/
|
*/
|
||||||
public void setCurrentRow(HSSFRow row) {
|
public void setCurrentRow(HSSFRow row) {
|
||||||
// do nothing
|
// do nothing
|
||||||
if (false) {
|
if (false) {
|
||||||
row.getClass(); // suppress unused parameter compiler warning
|
row.getClass(); // suppress unused parameter compiler warning
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should be called whenever there are major changes (e.g. moving sheets) to input cells
|
* 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
|
* 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.
|
* 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
|
* Failure to call this method after changing cell values will cause incorrect behaviour
|
||||||
* of the evaluate~ methods of this class
|
* of the evaluate~ methods of this class
|
||||||
*/
|
*/
|
||||||
public void clearAllCachedResultValues() {
|
public void clearAllCachedResultValues() {
|
||||||
_bookEvaluator.clearAllCachedResultValues();
|
_bookEvaluator.clearAllCachedResultValues();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Should be called to tell the cell value cache that the specified (value or formula) cell
|
* Should be called to tell the cell value cache that the specified (value or formula) cell
|
||||||
* has changed.
|
* has changed.
|
||||||
* Failure to call this method after changing cell values will cause incorrect behaviour
|
* Failure to call this method after changing cell values will cause incorrect behaviour
|
||||||
* of the evaluate~ methods of this class
|
* of the evaluate~ methods of this class
|
||||||
*/
|
*/
|
||||||
public void notifyUpdateCell(HSSFCell cell) {
|
public void notifyUpdateCell(HSSFCell cell) {
|
||||||
_bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell(cell));
|
_bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell(cell));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Should be called to tell the cell value cache that the specified cell has just been
|
* Should be called to tell the cell value cache that the specified cell has just been
|
||||||
* deleted.
|
* deleted.
|
||||||
* Failure to call this method after changing cell values will cause incorrect behaviour
|
* Failure to call this method after changing cell values will cause incorrect behaviour
|
||||||
* of the evaluate~ methods of this class
|
* of the evaluate~ methods of this class
|
||||||
*/
|
*/
|
||||||
public void notifyDeleteCell(HSSFCell cell) {
|
public void notifyDeleteCell(HSSFCell cell) {
|
||||||
_bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell));
|
_bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell));
|
||||||
}
|
}
|
||||||
public void notifyDeleteCell(Cell cell) {
|
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
|
* Should be called to tell the cell value cache that the specified (value or formula) cell
|
||||||
* has changed.
|
* has changed.
|
||||||
* Failure to call this method after changing cell values will cause incorrect behaviour
|
* Failure to call this method after changing cell values will cause incorrect behaviour
|
||||||
* of the evaluate~ methods of this class
|
* of the evaluate~ methods of this class
|
||||||
*/
|
*/
|
||||||
public void notifySetFormula(Cell cell) {
|
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,
|
* If cell contains a formula, the formula is evaluated and returned,
|
||||||
* else the CellValue simply copies the appropriate cell value from
|
* else the CellValue simply copies the appropriate cell value from
|
||||||
* the cell and also its cell type. This method should be preferred over
|
* the cell and also its cell type. This method should be preferred over
|
||||||
* evaluateInCell() when the call should not modify the contents of the
|
* evaluateInCell() when the call should not modify the contents of the
|
||||||
* original cell.
|
* original cell.
|
||||||
*
|
*
|
||||||
* @param cell may be <code>null</code> signifying that the cell is not present (or blank)
|
* @param cell may be <code>null</code> signifying that the cell is not present (or blank)
|
||||||
* @return <code>null</code> if the supplied cell is <code>null</code> or blank
|
* @return <code>null</code> if the supplied cell is <code>null</code> or blank
|
||||||
*/
|
*/
|
||||||
public CellValue evaluate(Cell cell) {
|
public CellValue evaluate(Cell cell) {
|
||||||
if (cell == null) {
|
if (cell == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (cell.getCellType()) {
|
switch (cell.getCellType()) {
|
||||||
case HSSFCell.CELL_TYPE_BOOLEAN:
|
case HSSFCell.CELL_TYPE_BOOLEAN:
|
||||||
return CellValue.valueOf(cell.getBooleanCellValue());
|
return CellValue.valueOf(cell.getBooleanCellValue());
|
||||||
case HSSFCell.CELL_TYPE_ERROR:
|
case HSSFCell.CELL_TYPE_ERROR:
|
||||||
return CellValue.getError(cell.getErrorCellValue());
|
return CellValue.getError(cell.getErrorCellValue());
|
||||||
case HSSFCell.CELL_TYPE_FORMULA:
|
case HSSFCell.CELL_TYPE_FORMULA:
|
||||||
return evaluateFormulaCellValue(cell);
|
return evaluateFormulaCellValue(cell);
|
||||||
case HSSFCell.CELL_TYPE_NUMERIC:
|
case HSSFCell.CELL_TYPE_NUMERIC:
|
||||||
return new CellValue(cell.getNumericCellValue());
|
return new CellValue(cell.getNumericCellValue());
|
||||||
case HSSFCell.CELL_TYPE_STRING:
|
case HSSFCell.CELL_TYPE_STRING:
|
||||||
return new CellValue(cell.getRichStringCellValue().getString());
|
return new CellValue(cell.getRichStringCellValue().getString());
|
||||||
case HSSFCell.CELL_TYPE_BLANK:
|
case HSSFCell.CELL_TYPE_BLANK:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")");
|
throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If cell contains formula, it evaluates the formula, and saves the result of the formula. The
|
* 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
|
* cell remains as a formula cell. If the cell does not contain formula, this method returns -1
|
||||||
* and leaves the cell unchanged.
|
* and leaves the cell unchanged.
|
||||||
*
|
*
|
||||||
* Note that the type of the <em>formula result</em> is returned, so you know what kind of
|
* Note that the type of the <em>formula result</em> is returned, so you know what kind of
|
||||||
* cached formula result is also stored with the formula.
|
* cached formula result is also stored with the formula.
|
||||||
* <pre>
|
* <pre>
|
||||||
* int evaluatedCellType = evaluator.evaluateFormulaCell(cell);
|
* int evaluatedCellType = evaluator.evaluateFormulaCell(cell);
|
||||||
* </pre>
|
* </pre>
|
||||||
* Be aware that your cell will hold both the formula, and the result. If you want the 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)}
|
* replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)}
|
||||||
* @param cell The cell to evaluate
|
* @param cell The cell to evaluate
|
||||||
* @return -1 for non-formula cells, or the type of the <em>formula result</em>
|
* @return -1 for non-formula cells, or the type of the <em>formula result</em>
|
||||||
*/
|
*/
|
||||||
public int evaluateFormulaCell(Cell cell) {
|
public int evaluateFormulaCell(Cell cell) {
|
||||||
if (cell == null || cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
|
if (cell == null || cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
CellValue cv = evaluateFormulaCellValue(cell);
|
CellValue cv = evaluateFormulaCellValue(cell);
|
||||||
// cell remains a formula cell, but the cached value is changed
|
// cell remains a formula cell, but the cached value is changed
|
||||||
setCellValue(cell, cv);
|
setCellValue(cell, cv);
|
||||||
return cv.getCellType();
|
return cv.getCellType();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If cell contains formula, it evaluates the formula, and
|
* If cell contains formula, it evaluates the formula, and
|
||||||
* puts the formula result back into the cell, in place
|
* puts the formula result back into the cell, in place
|
||||||
* of the old formula.
|
* of the old formula.
|
||||||
* Else if cell does not contain formula, this method leaves
|
* Else if cell does not contain formula, this method leaves
|
||||||
* the cell unchanged.
|
* the cell unchanged.
|
||||||
* Note that the same instance of HSSFCell is returned to
|
* Note that the same instance of HSSFCell is returned to
|
||||||
* allow chained calls like:
|
* allow chained calls like:
|
||||||
* <pre>
|
* <pre>
|
||||||
* int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();
|
* int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();
|
||||||
* </pre>
|
* </pre>
|
||||||
* Be aware that your cell value will be changed to hold the
|
* Be aware that your cell value will be changed to hold the
|
||||||
* result of the formula. If you simply want the formula
|
* result of the formula. If you simply want the formula
|
||||||
* value computed for you, use {@link #evaluateFormulaCell(org.apache.poi.ss.usermodel.Cell)}}
|
* value computed for you, use {@link #evaluateFormulaCell(Cell)}}
|
||||||
* @param cell
|
*/
|
||||||
*/
|
public HSSFCell evaluateInCell(Cell cell) {
|
||||||
public HSSFCell evaluateInCell(Cell cell) {
|
if (cell == null) {
|
||||||
if (cell == null) {
|
return null;
|
||||||
return null;
|
}
|
||||||
}
|
HSSFCell result = (HSSFCell) cell;
|
||||||
HSSFCell result = (HSSFCell) cell;
|
if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {
|
||||||
if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {
|
CellValue cv = evaluateFormulaCellValue(cell);
|
||||||
CellValue cv = evaluateFormulaCellValue(cell);
|
setCellType(cell, cv); // cell will no longer be a formula cell
|
||||||
setCellType(cell, cv); // cell will no longer be a formula cell
|
setCellValue(cell, cv);
|
||||||
setCellValue(cell, cv);
|
}
|
||||||
}
|
return result;
|
||||||
return result;
|
}
|
||||||
}
|
private static void setCellType(Cell cell, CellValue cv) {
|
||||||
private static void setCellType(Cell cell, CellValue cv) {
|
int cellType = cv.getCellType();
|
||||||
int cellType = cv.getCellType();
|
switch (cellType) {
|
||||||
switch (cellType) {
|
case HSSFCell.CELL_TYPE_BOOLEAN:
|
||||||
case HSSFCell.CELL_TYPE_BOOLEAN:
|
case HSSFCell.CELL_TYPE_ERROR:
|
||||||
case HSSFCell.CELL_TYPE_ERROR:
|
case HSSFCell.CELL_TYPE_NUMERIC:
|
||||||
case HSSFCell.CELL_TYPE_NUMERIC:
|
case HSSFCell.CELL_TYPE_STRING:
|
||||||
case HSSFCell.CELL_TYPE_STRING:
|
cell.setCellType(cellType);
|
||||||
cell.setCellType(cellType);
|
return;
|
||||||
return;
|
case HSSFCell.CELL_TYPE_BLANK:
|
||||||
case HSSFCell.CELL_TYPE_BLANK:
|
// never happens - blanks eventually get translated to zero
|
||||||
// never happens - blanks eventually get translated to zero
|
case HSSFCell.CELL_TYPE_FORMULA:
|
||||||
case HSSFCell.CELL_TYPE_FORMULA:
|
// this will never happen, we have already evaluated the formula
|
||||||
// this will never happen, we have already evaluated the formula
|
}
|
||||||
}
|
throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");
|
||||||
throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void setCellValue(Cell cell, CellValue cv) {
|
private static void setCellValue(Cell cell, CellValue cv) {
|
||||||
int cellType = cv.getCellType();
|
int cellType = cv.getCellType();
|
||||||
switch (cellType) {
|
switch (cellType) {
|
||||||
case HSSFCell.CELL_TYPE_BOOLEAN:
|
case HSSFCell.CELL_TYPE_BOOLEAN:
|
||||||
cell.setCellValue(cv.getBooleanValue());
|
cell.setCellValue(cv.getBooleanValue());
|
||||||
break;
|
break;
|
||||||
case HSSFCell.CELL_TYPE_ERROR:
|
case HSSFCell.CELL_TYPE_ERROR:
|
||||||
cell.setCellErrorValue(cv.getErrorValue());
|
cell.setCellErrorValue(cv.getErrorValue());
|
||||||
break;
|
break;
|
||||||
case HSSFCell.CELL_TYPE_NUMERIC:
|
case HSSFCell.CELL_TYPE_NUMERIC:
|
||||||
cell.setCellValue(cv.getNumberValue());
|
cell.setCellValue(cv.getNumberValue());
|
||||||
break;
|
break;
|
||||||
case HSSFCell.CELL_TYPE_STRING:
|
case HSSFCell.CELL_TYPE_STRING:
|
||||||
cell.setCellValue(new HSSFRichTextString(cv.getStringValue()));
|
cell.setCellValue(new HSSFRichTextString(cv.getStringValue()));
|
||||||
break;
|
break;
|
||||||
case HSSFCell.CELL_TYPE_BLANK:
|
case HSSFCell.CELL_TYPE_BLANK:
|
||||||
// never happens - blanks eventually get translated to zero
|
// never happens - blanks eventually get translated to zero
|
||||||
case HSSFCell.CELL_TYPE_FORMULA:
|
case HSSFCell.CELL_TYPE_FORMULA:
|
||||||
// this will never happen, we have already evaluated the formula
|
// this will never happen, we have already evaluated the formula
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");
|
throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loops over all cells in all sheets of the supplied
|
* Loops over all cells in all sheets of the supplied
|
||||||
* workbook.
|
* workbook.
|
||||||
* For cells that contain formulas, their formulas are
|
* For cells that contain formulas, their formulas are
|
||||||
* evaluated, and the results are saved. These cells
|
* evaluated, and the results are saved. These cells
|
||||||
* remain as formula cells.
|
* remain as formula cells.
|
||||||
* For cells that do not contain formulas, no changes
|
* For cells that do not contain formulas, no changes
|
||||||
* are made.
|
* are made.
|
||||||
* This is a helpful wrapper around looping over all
|
* This is a helpful wrapper around looping over all
|
||||||
* cells, and calling evaluateFormulaCell on each one.
|
* cells, and calling evaluateFormulaCell on each one.
|
||||||
*/
|
*/
|
||||||
public static void evaluateAllFormulaCells(HSSFWorkbook wb) {
|
public static void evaluateAllFormulaCells(HSSFWorkbook wb) {
|
||||||
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb);
|
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb);
|
||||||
for(int i=0; i<wb.getNumberOfSheets(); i++) {
|
for(int i=0; i<wb.getNumberOfSheets(); i++) {
|
||||||
HSSFSheet sheet = wb.getSheetAt(i);
|
HSSFSheet sheet = wb.getSheetAt(i);
|
||||||
|
|
||||||
for (Iterator rit = sheet.rowIterator(); rit.hasNext();) {
|
for (Iterator rit = sheet.rowIterator(); rit.hasNext();) {
|
||||||
HSSFRow r = (HSSFRow)rit.next();
|
HSSFRow r = (HSSFRow)rit.next();
|
||||||
|
|
||||||
for (Iterator cit = r.cellIterator(); cit.hasNext();) {
|
for (Iterator cit = r.cellIterator(); cit.hasNext();) {
|
||||||
HSSFCell c = (HSSFCell)cit.next();
|
HSSFCell c = (HSSFCell)cit.next();
|
||||||
if (c.getCellType() == HSSFCell.CELL_TYPE_FORMULA)
|
if (c.getCellType() == HSSFCell.CELL_TYPE_FORMULA)
|
||||||
evaluator.evaluateFormulaCell(c);
|
evaluator.evaluateFormulaCell(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a CellValue wrapper around the supplied ValueEval instance.
|
* Returns a CellValue wrapper around the supplied ValueEval instance.
|
||||||
* @param eval
|
* @param eval
|
||||||
*/
|
*/
|
||||||
private CellValue evaluateFormulaCellValue(Cell cell) {
|
private CellValue evaluateFormulaCellValue(Cell cell) {
|
||||||
ValueEval eval = _bookEvaluator.evaluate(new HSSFEvaluationCell((HSSFCell)cell));
|
ValueEval eval = _bookEvaluator.evaluate(new HSSFEvaluationCell((HSSFCell)cell));
|
||||||
if (eval instanceof NumberEval) {
|
if (eval instanceof NumberEval) {
|
||||||
NumberEval ne = (NumberEval) eval;
|
NumberEval ne = (NumberEval) eval;
|
||||||
return new CellValue(ne.getNumberValue());
|
return new CellValue(ne.getNumberValue());
|
||||||
}
|
}
|
||||||
if (eval instanceof BoolEval) {
|
if (eval instanceof BoolEval) {
|
||||||
BoolEval be = (BoolEval) eval;
|
BoolEval be = (BoolEval) eval;
|
||||||
return CellValue.valueOf(be.getBooleanValue());
|
return CellValue.valueOf(be.getBooleanValue());
|
||||||
}
|
}
|
||||||
if (eval instanceof StringEval) {
|
if (eval instanceof StringEval) {
|
||||||
StringEval ne = (StringEval) eval;
|
StringEval ne = (StringEval) eval;
|
||||||
return new CellValue(ne.getStringValue());
|
return new CellValue(ne.getStringValue());
|
||||||
}
|
}
|
||||||
if (eval instanceof ErrorEval) {
|
if (eval instanceof ErrorEval) {
|
||||||
return CellValue.getError(((ErrorEval)eval).getErrorCode());
|
return CellValue.getError(((ErrorEval)eval).getErrorCode());
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")");
|
throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,15 @@ final class FormulaCellCacheEntry extends CellCacheEntry {
|
|||||||
public FormulaCellCacheEntry() {
|
public FormulaCellCacheEntry() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInputSensitive() {
|
||||||
|
if (_sensitiveInputCells != null) {
|
||||||
|
if (_sensitiveInputCells.length > 0 ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _usedBlankCellGroup == null ? false : !_usedBlankCellGroup.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public void setSensitiveInputCells(CellCacheEntry[] sensitiveInputCells) {
|
public void setSensitiveInputCells(CellCacheEntry[] sensitiveInputCells) {
|
||||||
// need to tell all cells that were previously used, but no longer are,
|
// need to tell all cells that were previously used, but no longer are,
|
||||||
|
@ -186,4 +186,8 @@ final class FormulaUsedBlankCellSet {
|
|||||||
}
|
}
|
||||||
return bcsg.containsCell(rowIndex, columnIndex);
|
return bcsg.containsCell(rowIndex, columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return _sheetGroupsByBookSheet.isEmpty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
84
src/java/org/apache/poi/ss/formula/IStabilityClassifier.java
Normal file
84
src/java/org/apache/poi/ss/formula/IStabilityClassifier.java
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
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 org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to help optimise cell evaluation result caching by allowing applications to specify which
|
||||||
|
* parts of a workbook are <em>final</em>.<br/>
|
||||||
|
* The term <b>final</b> is introduced here to denote immutability or 'having constant definition'.
|
||||||
|
* This classification refers to potential actions (on the evaluated workbook) by the evaluating
|
||||||
|
* application. It does not refer to operations performed by the evaluator ({@link
|
||||||
|
* WorkbookEvaluator}).<br/>
|
||||||
|
* <br/>
|
||||||
|
* <b>General guidelines</b>:
|
||||||
|
* <ul>
|
||||||
|
* <li>a plain value cell can be marked as 'final' if it will not be changed after the first call
|
||||||
|
* to {@link WorkbookEvaluator#evaluate(EvaluationCell)}.
|
||||||
|
* </li>
|
||||||
|
* <li>a formula cell can be marked as 'final' if its formula will not be changed after the first
|
||||||
|
* call to {@link WorkbookEvaluator#evaluate(EvaluationCell)}. This remains true even if changes
|
||||||
|
* in dependent values may cause the evaluated value to change.</li>
|
||||||
|
* <li>plain value cells should be marked as 'not final' if their plain value value may change.
|
||||||
|
* </li>
|
||||||
|
* <li>formula cells should be marked as 'not final' if their formula definition may change.</li>
|
||||||
|
* <li>cells which may switch between plain value and formula should also be marked as 'not final'.
|
||||||
|
* </li>
|
||||||
|
* </ul>
|
||||||
|
* <b>Notes</b>:
|
||||||
|
* <ul>
|
||||||
|
* <li>If none of the spreadsheet cells is expected to have its definition changed after evaluation
|
||||||
|
* begins, every cell can be marked as 'final'. This is the most efficient / least resource
|
||||||
|
* intensive option.</li>
|
||||||
|
* <li>To retain freedom to change any cell definition at any time, an application may classify all
|
||||||
|
* cells as 'not final'. This freedom comes at the expense of greater memory consumption.</li>
|
||||||
|
* <li>For the purpose of these classifications, setting the cached formula result of a cell (for
|
||||||
|
* example in {@link HSSFFormulaEvaluator#evaluateFormulaCell(org.apache.poi.ss.usermodel.Cell)})
|
||||||
|
* does not constitute changing the definition of the cell.</li>
|
||||||
|
* <li>Updating cells which have been classified as 'final' will cause the evaluator to behave
|
||||||
|
* unpredictably (typically ignoring the update).</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
public interface IStabilityClassifier {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience implementation for situations where all cell definitions remain fixed after
|
||||||
|
* evaluation begins.
|
||||||
|
*/
|
||||||
|
IStabilityClassifier TOTALLY_IMMUTABLE = new IStabilityClassifier() {
|
||||||
|
public boolean isCellFinal(int sheetIndex, int rowIndex, int columnIndex) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a cell's value(/formula) is fixed - in other words - not expected to be modified
|
||||||
|
* between calls to the evaluator. (Note - this is an independent concept from whether a
|
||||||
|
* formula cell's evaluated value may change during successive calls to the evaluator).
|
||||||
|
*
|
||||||
|
* @param sheetIndex zero based index into workbook sheet list
|
||||||
|
* @param rowIndex zero based row index of cell
|
||||||
|
* @param columnIndex zero based column index of cell
|
||||||
|
* @return <code>false</code> if the evaluating application may need to modify the specified
|
||||||
|
* cell between calls to the evaluator.
|
||||||
|
*/
|
||||||
|
boolean isCellFinal(int sheetIndex, int rowIndex, int columnIndex);
|
||||||
|
}
|
@ -82,19 +82,22 @@ public final class WorkbookEvaluator {
|
|||||||
private int _workbookIx;
|
private int _workbookIx;
|
||||||
|
|
||||||
private final IEvaluationListener _evaluationListener;
|
private final IEvaluationListener _evaluationListener;
|
||||||
private final Map _sheetIndexesBySheet;
|
private final Map<EvaluationSheet, Integer> _sheetIndexesBySheet;
|
||||||
private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
|
private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
|
||||||
|
private final IStabilityClassifier _stabilityClassifier;
|
||||||
|
|
||||||
public WorkbookEvaluator(EvaluationWorkbook workbook) {
|
public WorkbookEvaluator(EvaluationWorkbook workbook, IStabilityClassifier stabilityClassifier) {
|
||||||
this (workbook, null);
|
this (workbook, null, stabilityClassifier);
|
||||||
}
|
}
|
||||||
/* package */ WorkbookEvaluator(EvaluationWorkbook workbook, IEvaluationListener evaluationListener) {
|
/* package */ WorkbookEvaluator(EvaluationWorkbook workbook, IEvaluationListener evaluationListener,
|
||||||
|
IStabilityClassifier stabilityClassifier) {
|
||||||
_workbook = workbook;
|
_workbook = workbook;
|
||||||
_evaluationListener = evaluationListener;
|
_evaluationListener = evaluationListener;
|
||||||
_cache = new EvaluationCache(evaluationListener);
|
_cache = new EvaluationCache(evaluationListener);
|
||||||
_sheetIndexesBySheet = new IdentityHashMap();
|
_sheetIndexesBySheet = new IdentityHashMap<EvaluationSheet, Integer>();
|
||||||
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
|
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
|
||||||
_workbookIx = 0;
|
_workbookIx = 0;
|
||||||
|
_stabilityClassifier = stabilityClassifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,7 +144,7 @@ public final class WorkbookEvaluator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should be called to tell the cell value cache that the specified (value or formula) cell
|
* Should be called to tell the cell value cache that the specified (value or formula) cell
|
||||||
* has changed.
|
* has changed.
|
||||||
*/
|
*/
|
||||||
public void notifyUpdateCell(EvaluationCell cell) {
|
public void notifyUpdateCell(EvaluationCell cell) {
|
||||||
@ -150,7 +153,7 @@ public final class WorkbookEvaluator {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Should be called to tell the cell value cache that the specified cell has just been
|
* Should be called to tell the cell value cache that the specified cell has just been
|
||||||
* deleted.
|
* deleted.
|
||||||
*/
|
*/
|
||||||
public void notifyDeleteCell(EvaluationCell cell) {
|
public void notifyDeleteCell(EvaluationCell cell) {
|
||||||
int sheetIndex = getSheetIndex(cell.getSheet());
|
int sheetIndex = getSheetIndex(cell.getSheet());
|
||||||
@ -158,7 +161,7 @@ public final class WorkbookEvaluator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int getSheetIndex(EvaluationSheet sheet) {
|
private int getSheetIndex(EvaluationSheet sheet) {
|
||||||
Integer result = (Integer) _sheetIndexesBySheet.get(sheet);
|
Integer result = _sheetIndexesBySheet.get(sheet);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
int sheetIndex = _workbook.getSheetIndex(sheet);
|
int sheetIndex = _workbook.getSheetIndex(sheet);
|
||||||
if (sheetIndex < 0) {
|
if (sheetIndex < 0) {
|
||||||
@ -182,14 +185,21 @@ public final class WorkbookEvaluator {
|
|||||||
private ValueEval evaluateAny(EvaluationCell srcCell, int sheetIndex,
|
private ValueEval evaluateAny(EvaluationCell srcCell, int sheetIndex,
|
||||||
int rowIndex, int columnIndex, EvaluationTracker tracker) {
|
int rowIndex, int columnIndex, EvaluationTracker tracker) {
|
||||||
|
|
||||||
|
// avoid tracking dependencies for cells that have constant definition
|
||||||
|
boolean shouldCellDependencyBeRecorded = _stabilityClassifier == null ? true
|
||||||
|
: !_stabilityClassifier.isCellFinal(sheetIndex, rowIndex, columnIndex);
|
||||||
if (srcCell == null || srcCell.getCellType() != Cell.CELL_TYPE_FORMULA) {
|
if (srcCell == null || srcCell.getCellType() != Cell.CELL_TYPE_FORMULA) {
|
||||||
ValueEval result = getValueFromNonFormulaCell(srcCell);
|
ValueEval result = getValueFromNonFormulaCell(srcCell);
|
||||||
tracker.acceptPlainValueDependency(_workbookIx, sheetIndex, rowIndex, columnIndex, result);
|
if (shouldCellDependencyBeRecorded) {
|
||||||
|
tracker.acceptPlainValueDependency(_workbookIx, sheetIndex, rowIndex, columnIndex, result);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
FormulaCellCacheEntry cce = _cache.getOrCreateFormulaCellEntry(srcCell);
|
FormulaCellCacheEntry cce = _cache.getOrCreateFormulaCellEntry(srcCell);
|
||||||
tracker.acceptFormulaDependency(cce);
|
if (shouldCellDependencyBeRecorded || cce.isInputSensitive()) {
|
||||||
|
tracker.acceptFormulaDependency(cce);
|
||||||
|
}
|
||||||
IEvaluationListener evalListener = _evaluationListener;
|
IEvaluationListener evalListener = _evaluationListener;
|
||||||
if (cce.getValue() == null) {
|
if (cce.getValue() == null) {
|
||||||
if (!tracker.startEvaluate(cce)) {
|
if (!tracker.startEvaluate(cce)) {
|
||||||
@ -252,7 +262,7 @@ public final class WorkbookEvaluator {
|
|||||||
// visibility raised for testing
|
// visibility raised for testing
|
||||||
/* package */ ValueEval evaluateFormula(int sheetIndex, int srcRowNum, int srcColNum, Ptg[] ptgs, EvaluationTracker tracker) {
|
/* package */ ValueEval evaluateFormula(int sheetIndex, int srcRowNum, int srcColNum, Ptg[] ptgs, EvaluationTracker tracker) {
|
||||||
|
|
||||||
Stack stack = new Stack();
|
Stack<Eval> stack = new Stack<Eval>();
|
||||||
for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
|
for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
|
||||||
|
|
||||||
// since we don't know how to handle these yet :(
|
// since we don't know how to handle these yet :(
|
||||||
@ -289,7 +299,7 @@ public final class WorkbookEvaluator {
|
|||||||
|
|
||||||
// storing the ops in reverse order since they are popping
|
// storing the ops in reverse order since they are popping
|
||||||
for (int j = numops - 1; j >= 0; j--) {
|
for (int j = numops - 1; j >= 0; j--) {
|
||||||
Eval p = (Eval) stack.pop();
|
Eval p = stack.pop();
|
||||||
ops[j] = p;
|
ops[j] = p;
|
||||||
}
|
}
|
||||||
// logDebug("invoke " + operation + " (nAgs=" + numops + ")");
|
// logDebug("invoke " + operation + " (nAgs=" + numops + ")");
|
||||||
@ -307,7 +317,7 @@ public final class WorkbookEvaluator {
|
|||||||
stack.push(opResult);
|
stack.push(opResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueEval value = ((ValueEval) stack.pop());
|
ValueEval value = (ValueEval) stack.pop();
|
||||||
if (!stack.isEmpty()) {
|
if (!stack.isEmpty()) {
|
||||||
throw new IllegalStateException("evaluation stack not empty");
|
throw new IllegalStateException("evaluation stack not empty");
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import org.apache.poi.hssf.record.formula.eval.ErrorEval;
|
|||||||
import org.apache.poi.hssf.record.formula.eval.NumberEval;
|
import org.apache.poi.hssf.record.formula.eval.NumberEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||||
|
import org.apache.poi.ss.formula.IStabilityClassifier;
|
||||||
import org.apache.poi.ss.formula.WorkbookEvaluator;
|
import org.apache.poi.ss.formula.WorkbookEvaluator;
|
||||||
import org.apache.poi.ss.usermodel.Cell;
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
import org.apache.poi.ss.usermodel.CellValue;
|
import org.apache.poi.ss.usermodel.CellValue;
|
||||||
@ -46,7 +47,15 @@ public class XSSFFormulaEvaluator implements FormulaEvaluator {
|
|||||||
private WorkbookEvaluator _bookEvaluator;
|
private WorkbookEvaluator _bookEvaluator;
|
||||||
|
|
||||||
public XSSFFormulaEvaluator(XSSFWorkbook workbook) {
|
public XSSFFormulaEvaluator(XSSFWorkbook workbook) {
|
||||||
_bookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(workbook));
|
this(workbook, null);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param stabilityClassifier used to optimise caching performance. Pass <code>null</code>
|
||||||
|
* for the (conservative) assumption that any cell may have its definition changed after
|
||||||
|
* evaluation begins.
|
||||||
|
*/
|
||||||
|
public XSSFFormulaEvaluator(XSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) {
|
||||||
|
_bookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(workbook), stabilityClassifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,6 +42,10 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
|||||||
*/
|
*/
|
||||||
public class TestWorkbookEvaluator extends TestCase {
|
public class TestWorkbookEvaluator extends TestCase {
|
||||||
|
|
||||||
|
private static WorkbookEvaluator createEvaluator() {
|
||||||
|
return new WorkbookEvaluator(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing
|
* Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing
|
||||||
* the whole formula which converts tAttrSum to tFuncVar("SUM") )
|
* the whole formula which converts tAttrSum to tFuncVar("SUM") )
|
||||||
@ -53,7 +57,7 @@ public class TestWorkbookEvaluator extends TestCase {
|
|||||||
AttrPtg.SUM,
|
AttrPtg.SUM,
|
||||||
};
|
};
|
||||||
|
|
||||||
ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
|
ValueEval result = createEvaluator().evaluateFormula(0, 0, 0, ptgs, null);
|
||||||
assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
|
assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +78,7 @@ public class TestWorkbookEvaluator extends TestCase {
|
|||||||
ptg,
|
ptg,
|
||||||
};
|
};
|
||||||
|
|
||||||
ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
|
ValueEval result = createEvaluator().evaluateFormula(0, 0, 0, ptgs, null);
|
||||||
assertEquals(ErrorEval.REF_INVALID, result);
|
assertEquals(ErrorEval.REF_INVALID, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +93,7 @@ public class TestWorkbookEvaluator extends TestCase {
|
|||||||
AttrPtg.SUM,
|
AttrPtg.SUM,
|
||||||
};
|
};
|
||||||
|
|
||||||
ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
|
ValueEval result = createEvaluator().evaluateFormula(0, 0, 0, ptgs, null);
|
||||||
assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
|
assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,6 @@ public final class WorkbookEvaluatorTestHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static WorkbookEvaluator createEvaluator(HSSFWorkbook wb, EvaluationListener listener) {
|
public static WorkbookEvaluator createEvaluator(HSSFWorkbook wb, EvaluationListener listener) {
|
||||||
return new WorkbookEvaluator(HSSFEvaluationWorkbook.create(wb), listener);
|
return new WorkbookEvaluator(HSSFEvaluationWorkbook.create(wb), listener, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user