From: Nick Burch Date: Sat, 29 Mar 2008 22:03:18 +0000 (+0000) Subject: Shuffle the FormulaParser stuff about, and get it all working with the new interfaces... X-Git-Tag: REL_3_5_BETA2~159 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=b4590a5c1ef657ed2338441349633500ec9b3a60;p=poi.git Shuffle the FormulaParser stuff about, and get it all working with the new interfaces, and not just with the hard coded HSSF classes git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@642624 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java b/src/java/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java index b1d81e6524..f40c83202e 100755 --- a/src/java/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java @@ -18,8 +18,8 @@ package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; /** * * Common entry point for all external functions (where @@ -29,7 +29,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; */ final class ExternalFunction implements FreeRefFunction { - public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) { + public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, Workbook workbook, Sheet sheet) { int nIncomingArgs = args.length; if(nIncomingArgs < 1) { @@ -56,7 +56,7 @@ final class ExternalFunction implements FreeRefFunction { return targetFunc.evaluate(outGoingArgs, srcCellRow, srcCellCol, workbook, sheet); } - private FreeRefFunction findTargetFunction(HSSFWorkbook workbook, NameEval functionNameEval) throws EvaluationException { + private FreeRefFunction findTargetFunction(Workbook workbook, NameEval functionNameEval) throws EvaluationException { int numberOfNames = workbook.getNumberOfNames(); diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java b/src/java/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java index 56d285543e..2163d8f5ed 100755 --- a/src/java/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java @@ -19,8 +19,8 @@ package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.ValueEval; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; /** @@ -53,5 +53,5 @@ public interface FreeRefFunction { * a specified Excel error (Exceptions are never thrown to represent Excel errors). * */ - ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet); + ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, Workbook workbook, Sheet sheet); } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Indirect.java b/src/java/org/apache/poi/hssf/record/formula/functions/Indirect.java index 935e7cdbbd..8d7d4463e5 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Indirect.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Indirect.java @@ -20,8 +20,8 @@ package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.ValueEval; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; /** * Implementation for Excel function INDIRECT

@@ -41,7 +41,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; */ public final class Indirect implements FreeRefFunction { - public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) { + public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, Workbook workbook, Sheet sheet) { // TODO - implement INDIRECT() return ErrorEval.FUNCTION_NOT_IMPLEMENTED; } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Offset.java b/src/java/org/apache/poi/hssf/record/formula/functions/Offset.java index 9497a5f21a..8a10e62253 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Offset.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Offset.java @@ -31,8 +31,8 @@ import org.apache.poi.hssf.record.formula.eval.RefEval; import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; /** * Implementation for Excel function OFFSET()

* @@ -201,7 +201,7 @@ public final class Offset implements FreeRefFunction { } - public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) { + public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, Workbook workbook, Sheet sheet) { if(args.length < 3 || args.length > 5) { return ErrorEval.VALUE_INVALID; @@ -235,7 +235,7 @@ public final class Offset implements FreeRefFunction { private static AreaEval createOffset(BaseRef baseRef, LinearOffsetRange rowOffsetRange, LinearOffsetRange colOffsetRange, - HSSFWorkbook workbook, HSSFSheet sheet) throws EvalEx { + Workbook workbook, Sheet sheet) throws EvalEx { LinearOffsetRange rows = rowOffsetRange.normaliseAndTranslate(baseRef.getFirstRowIndex()); LinearOffsetRange cols = colOffsetRange.normaliseAndTranslate(baseRef.getFirstColumnIndex()); diff --git a/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java b/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java deleted file mode 100755 index 90f5807ff5..0000000000 --- a/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java +++ /dev/null @@ -1,150 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.usermodel; - -import java.util.ArrayList; -import java.util.List; - -/** - * Instances of this class keep track of multiple dependent cell evaluations due - * to recursive calls to HSSFFormulaEvaluator.internalEvaluate(). - * The main purpose of this class is to detect an attempt to evaluate a cell - * that is already being evaluated. In other words, it detects circular - * references in spreadsheet formulas. - * - * @author Josh Micich - */ -final class EvaluationCycleDetector { - - /** - * Stores the parameters that identify the evaluation of one cell.
- */ - private static final class CellEvaluationFrame { - - private final HSSFWorkbook _workbook; - private final HSSFSheet _sheet; - private final int _srcRowNum; - private final int _srcColNum; - - public CellEvaluationFrame(HSSFWorkbook workbook, HSSFSheet sheet, int srcRowNum, int srcColNum) { - if (workbook == null) { - throw new IllegalArgumentException("workbook must not be null"); - } - if (sheet == null) { - throw new IllegalArgumentException("sheet must not be null"); - } - _workbook = workbook; - _sheet = sheet; - _srcRowNum = srcRowNum; - _srcColNum = srcColNum; - } - - public boolean equals(Object obj) { - CellEvaluationFrame other = (CellEvaluationFrame) obj; - if (_workbook != other._workbook) { - return false; - } - if (_sheet != other._sheet) { - return false; - } - if (_srcRowNum != other._srcRowNum) { - return false; - } - if (_srcColNum != other._srcColNum) { - return false; - } - return true; - } - - /** - * @return human readable string for debug purposes - */ - public String formatAsString() { - return "R=" + _srcRowNum + " C=" + _srcColNum + " ShIx=" + _workbook.getSheetIndex(_sheet); - } - - public String toString() { - StringBuffer sb = new StringBuffer(64); - sb.append(getClass().getName()).append(" ["); - sb.append(formatAsString()); - sb.append("]"); - return sb.toString(); - } - } - - private final List _evaluationFrames; - - public EvaluationCycleDetector() { - _evaluationFrames = new ArrayList(); - } - - /** - * Notifies this evaluation tracker that evaluation of the specified cell is - * about to start.
- * - * In the case of a true return code, the caller should - * continue evaluation of the specified cell, and also be sure to call - * endEvaluate() when complete.
- * - * In the case of a false return code, the caller should - * return an evaluation result of - * ErrorEval.CIRCULAR_REF_ERROR, and not call endEvaluate(). - *
- * @return true if the specified cell has not been visited yet in the current - * evaluation. false if the specified cell is already being evaluated. - */ - public boolean startEvaluate(HSSFWorkbook workbook, HSSFSheet sheet, int srcRowNum, int srcColNum) { - CellEvaluationFrame cef = new CellEvaluationFrame(workbook, sheet, srcRowNum, srcColNum); - if (_evaluationFrames.contains(cef)) { - return false; - } - _evaluationFrames.add(cef); - return true; - } - - /** - * Notifies this evaluation tracker that the evaluation of the specified - * cell is complete.

- * - * Every successful call to startEvaluate must be followed by a - * call to endEvaluate (recommended in a finally block) to enable - * proper tracking of which cells are being evaluated at any point in time.

- * - * Assuming a well behaved client, parameters to this method would not be - * required. However, they have been included to assert correct behaviour, - * and form more meaningful error messages. - */ - public void endEvaluate(HSSFWorkbook workbook, HSSFSheet sheet, int srcRowNum, int srcColNum) { - int nFrames = _evaluationFrames.size(); - if (nFrames < 1) { - throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate"); - } - - nFrames--; - CellEvaluationFrame cefExpected = (CellEvaluationFrame) _evaluationFrames.get(nFrames); - CellEvaluationFrame cefActual = new CellEvaluationFrame(workbook, sheet, srcRowNum, srcColNum); - if (!cefActual.equals(cefExpected)) { - throw new RuntimeException("Wrong cell specified. " - + "Corresponding startEvaluate() call was for cell {" - + cefExpected.formatAsString() + "} this endEvaluate() call is for cell {" - + cefActual.formatAsString() + "}"); - } - // else - no problems so pop current frame - _evaluationFrames.remove(nFrames); - } -} diff --git a/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java b/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java deleted file mode 100755 index a06cd201e2..0000000000 --- a/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java +++ /dev/null @@ -1,46 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.usermodel; - -/** - * This class makes an EvaluationCycleDetector instance available to - * each thread via a ThreadLocal in order to avoid adding a parameter - * to a few protected methods within HSSFFormulaEvaluator. - * - * @author Josh Micich - */ -final class EvaluationCycleDetectorManager { - - ThreadLocal tl = null; - private static ThreadLocal _tlEvaluationTracker = new ThreadLocal() { - protected synchronized Object initialValue() { - return new EvaluationCycleDetector(); - } - }; - - /** - * @return - */ - public static EvaluationCycleDetector getTracker() { - return (EvaluationCycleDetector) _tlEvaluationTracker.get(); - } - - private EvaluationCycleDetectorManager() { - // no instances of this class - } -} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java index bb16fdfadd..3da5f96882 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java @@ -17,96 +17,25 @@ package org.apache.poi.hssf.usermodel; -import java.lang.reflect.Constructor; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Stack; - import org.apache.poi.hssf.model.FormulaParser; -import org.apache.poi.hssf.model.Workbook; -import org.apache.poi.hssf.record.formula.Area3DPtg; -import org.apache.poi.hssf.record.formula.AreaPtg; -import org.apache.poi.hssf.record.formula.AttrPtg; -import org.apache.poi.hssf.record.formula.BoolPtg; -import org.apache.poi.hssf.record.formula.ControlPtg; -import org.apache.poi.hssf.record.formula.IntPtg; -import org.apache.poi.hssf.record.formula.MemErrPtg; -import org.apache.poi.hssf.record.formula.MissingArgPtg; -import org.apache.poi.hssf.record.formula.NamePtg; -import org.apache.poi.hssf.record.formula.NameXPtg; -import org.apache.poi.hssf.record.formula.NumberPtg; import org.apache.poi.hssf.record.formula.OperationPtg; -import org.apache.poi.hssf.record.formula.ParenthesisPtg; import org.apache.poi.hssf.record.formula.Ptg; -import org.apache.poi.hssf.record.formula.Ref3DPtg; -import org.apache.poi.hssf.record.formula.ReferencePtg; -import org.apache.poi.hssf.record.formula.StringPtg; -import org.apache.poi.hssf.record.formula.UnionPtg; -import org.apache.poi.hssf.record.formula.UnknownPtg; -import org.apache.poi.hssf.record.formula.eval.Area2DEval; -import org.apache.poi.hssf.record.formula.eval.Area3DEval; -import org.apache.poi.hssf.record.formula.eval.AreaEval; -import org.apache.poi.hssf.record.formula.eval.BlankEval; -import org.apache.poi.hssf.record.formula.eval.BoolEval; -import org.apache.poi.hssf.record.formula.eval.ErrorEval; -import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.FunctionEval; -import org.apache.poi.hssf.record.formula.eval.NameEval; -import org.apache.poi.hssf.record.formula.eval.NumberEval; -import org.apache.poi.hssf.record.formula.eval.OperationEval; -import org.apache.poi.hssf.record.formula.eval.Ref2DEval; -import org.apache.poi.hssf.record.formula.eval.Ref3DEval; -import org.apache.poi.hssf.record.formula.eval.RefEval; -import org.apache.poi.hssf.record.formula.eval.StringEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class HSSFFormulaEvaluator { - - // params to lookup the right constructor using reflection - private static final Class[] VALUE_CONTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; - - private static final Class[] AREA3D_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class, ValueEval[].class }; - - private static final Class[] REFERENCE_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class, ValueEval.class }; - - private static final Class[] REF3D_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class, ValueEval.class }; - - // Maps for mapping *Eval to *Ptg - private static final Map VALUE_EVALS_MAP = new HashMap(); - - /* - * Following is the mapping between the Ptg tokens returned - * by the FormulaParser and the *Eval classes that are used - * by the FormulaEvaluator - */ - static { - VALUE_EVALS_MAP.put(BoolPtg.class, BoolEval.class); - VALUE_EVALS_MAP.put(IntPtg.class, NumberEval.class); - VALUE_EVALS_MAP.put(NumberPtg.class, NumberEval.class); - VALUE_EVALS_MAP.put(StringPtg.class, StringEval.class); - - } - - - protected HSSFRow row; - protected HSSFSheet sheet; - protected HSSFWorkbook workbook; - +public class HSSFFormulaEvaluator extends FormulaEvaluator { public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) { - this.sheet = sheet; - this.workbook = workbook; - } - - public void setCurrentRow(HSSFRow row) { - this.row = row; + super(sheet, workbook); } - /** * Returns an underlying FormulaParser, for the specified * Formula String and HSSFWorkbook. @@ -118,639 +47,6 @@ public class HSSFFormulaEvaluator { return new FormulaParser(formula, workbook.getWorkbook()); } - /** - * If cell contains a formula, the formula is evaluated and returned, - * else the CellValue simply copies the appropriate cell value from - * the cell and also its cell type. This method should be preferred over - * evaluateInCell() when the call should not modify the contents of the - * original cell. - * @param cell - */ - public CellValue evaluate(HSSFCell cell) { - CellValue retval = null; - if (cell != null) { - switch (cell.getCellType()) { - case HSSFCell.CELL_TYPE_BLANK: - retval = new CellValue(HSSFCell.CELL_TYPE_BLANK); - break; - case HSSFCell.CELL_TYPE_BOOLEAN: - retval = new CellValue(HSSFCell.CELL_TYPE_BOOLEAN); - retval.setBooleanValue(cell.getBooleanCellValue()); - break; - case HSSFCell.CELL_TYPE_ERROR: - retval = new CellValue(HSSFCell.CELL_TYPE_ERROR); - retval.setErrorValue(cell.getErrorCellValue()); - break; - case HSSFCell.CELL_TYPE_FORMULA: - retval = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook)); - break; - case HSSFCell.CELL_TYPE_NUMERIC: - retval = new CellValue(HSSFCell.CELL_TYPE_NUMERIC); - retval.setNumberValue(cell.getNumericCellValue()); - break; - case HSSFCell.CELL_TYPE_STRING: - retval = new CellValue(HSSFCell.CELL_TYPE_STRING); - retval.setRichTextStringValue(cell.getRichStringCellValue()); - break; - } - } - return retval; - } - - - /** - * If cell contains formula, it evaluates the formula, - * and saves the result of the formula. The cell - * remains as a formula cell. - * Else if cell does not contain formula, this method leaves - * the cell unchanged. - * Note that the type of the formula result is returned, - * so you know what kind of value is also stored with - * the formula. - *

-     * int evaluatedCellType = evaluator.evaluateFormulaCell(cell);
-     * 
- * Be aware that your cell will hold both the formula, - * and the result. If you want the cell replaced with - * the result of the formula, use {@link #evaluateInCell(HSSFCell)} - * @param cell The cell to evaluate - * @return The type of the formula result (the cell's type remains as HSSFCell.CELL_TYPE_FORMULA however) - */ - public int evaluateFormulaCell(HSSFCell cell) { - if (cell != null) { - switch (cell.getCellType()) { - case HSSFCell.CELL_TYPE_FORMULA: - CellValue cv = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook)); - switch (cv.getCellType()) { - case HSSFCell.CELL_TYPE_BOOLEAN: - cell.setCellValue(cv.getBooleanValue()); - break; - case HSSFCell.CELL_TYPE_ERROR: - cell.setCellValue(cv.getErrorValue()); - break; - case HSSFCell.CELL_TYPE_NUMERIC: - cell.setCellValue(cv.getNumberValue()); - break; - case HSSFCell.CELL_TYPE_STRING: - cell.setCellValue(cv.getRichTextStringValue()); - break; - case HSSFCell.CELL_TYPE_BLANK: - break; - case HSSFCell.CELL_TYPE_FORMULA: // this will never happen, we have already evaluated the formula - break; - } - return cv.getCellType(); - } - } - return -1; - } - - /** - * If cell contains formula, it evaluates the formula, and - * puts the formula result back into the cell, in place - * of the old formula. - * Else if cell does not contain formula, this method leaves - * the cell unchanged. - * Note that the same instance of HSSFCell is returned to - * allow chained calls like: - *
-     * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();
-     * 
- * Be aware that your cell value will be changed to hold the - * result of the formula. If you simply want the formula - * value computed for you, use {@link #evaluateFormulaCell(HSSFCell)} - * @param cell - */ - public HSSFCell evaluateInCell(HSSFCell cell) { - if (cell != null) { - switch (cell.getCellType()) { - case HSSFCell.CELL_TYPE_FORMULA: - CellValue cv = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook)); - switch (cv.getCellType()) { - case HSSFCell.CELL_TYPE_BOOLEAN: - cell.setCellType(HSSFCell.CELL_TYPE_BOOLEAN); - cell.setCellValue(cv.getBooleanValue()); - break; - case HSSFCell.CELL_TYPE_ERROR: - cell.setCellType(HSSFCell.CELL_TYPE_ERROR); - cell.setCellValue(cv.getErrorValue()); - break; - case HSSFCell.CELL_TYPE_NUMERIC: - cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC); - cell.setCellValue(cv.getNumberValue()); - break; - case HSSFCell.CELL_TYPE_STRING: - cell.setCellType(HSSFCell.CELL_TYPE_STRING); - cell.setCellValue(cv.getRichTextStringValue()); - break; - case HSSFCell.CELL_TYPE_BLANK: - break; - case HSSFCell.CELL_TYPE_FORMULA: // this will never happen, we have already evaluated the formula - break; - } - } - } - return cell; - } - - /** - * Loops over all cells in all sheets of the supplied - * workbook. - * For cells that contain formulas, their formulas are - * evaluated, and the results are saved. These cells - * remain as formula cells. - * For cells that do not contain formulas, no changes - * are made. - * This is a helpful wrapper around looping over all - * cells, and calling evaluateFormulaCell on each one. - */ - public static void evaluateAllFormulaCells(HSSFWorkbook wb) { - for(int i=0; i= 0; j--) { - Eval p = (Eval) stack.pop(); - ops[j] = p; - } - Eval opresult = invokeOperation(operation, ops, srcRowNum, srcColNum, workbook, sheet); - stack.push(opresult); - } - else if (ptg instanceof ReferencePtg) { - ReferencePtg refPtg = (ReferencePtg) ptg; - int colIx = refPtg.getColumn(); - int rowIx = refPtg.getRow(); - HSSFRow row = sheet.getRow(rowIx); - HSSFCell cell = (row != null) ? row.getCell(colIx) : null; - stack.push(createRef2DEval(refPtg, cell, row, sheet, workbook)); - } - else if (ptg instanceof Ref3DPtg) { - Ref3DPtg refPtg = (Ref3DPtg) ptg; - int colIx = refPtg.getColumn(); - int rowIx = refPtg.getRow(); - Workbook wb = workbook.getWorkbook(); - HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(refPtg.getExternSheetIndex())); - HSSFRow row = xsheet.getRow(rowIx); - HSSFCell cell = (row != null) ? row.getCell(colIx) : null; - stack.push(createRef3DEval(refPtg, cell, row, xsheet, workbook)); - } - else if (ptg instanceof AreaPtg) { - AreaPtg ap = (AreaPtg) ptg; - AreaEval ae = evaluateAreaPtg(sheet, workbook, ap); - stack.push(ae); - } - else if (ptg instanceof Area3DPtg) { - Area3DPtg a3dp = (Area3DPtg) ptg; - AreaEval ae = evaluateArea3dPtg(workbook, a3dp); - stack.push(ae); - } - else { - Eval ptgEval = getEvalForPtg(ptg); - stack.push(ptgEval); - } - } - - ValueEval value = ((ValueEval) stack.pop()); - if (!stack.isEmpty()) { - throw new IllegalStateException("evaluation stack not empty"); - } - value = dereferenceValue(value, srcRowNum, srcColNum); - if (value instanceof BlankEval) { - // Note Excel behaviour here. A blank final final value is converted to zero. - return NumberEval.ZERO; - // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to - // blank, the actual value is empty string. This can be verified with ISBLANK(). - } - return value; - } - - /** - * Dereferences a single value from any AreaEval or RefEval evaluation result. - * If the supplied evaluationResult is just a plain value, it is returned as-is. - * @return a NumberEval, StringEval, BoolEval, - * BlankEval or ErrorEval. Never null. - */ - private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) { - if (evaluationResult instanceof RefEval) { - RefEval rv = (RefEval) evaluationResult; - return rv.getInnerValueEval(); - } - if (evaluationResult instanceof AreaEval) { - AreaEval ae = (AreaEval) evaluationResult; - if (ae.isRow()) { - if(ae.isColumn()) { - return ae.getValues()[0]; - } - return ae.getValueAt(ae.getFirstRow(), srcColNum); - } - if (ae.isColumn()) { - return ae.getValueAt(srcRowNum, ae.getFirstColumn()); - } - return ErrorEval.VALUE_INVALID; - } - return evaluationResult; - } - - private static Eval invokeOperation(OperationEval operation, Eval[] ops, int srcRowNum, short srcColNum, - HSSFWorkbook workbook, HSSFSheet sheet) { - - if(operation instanceof FunctionEval) { - FunctionEval fe = (FunctionEval) operation; - if(fe.isFreeRefFunction()) { - return fe.getFreeRefFunction().evaluate(ops, srcRowNum, srcColNum, workbook, sheet); - } - } - return operation.evaluate(ops, srcRowNum, srcColNum); - } - - public static AreaEval evaluateAreaPtg(HSSFSheet sheet, HSSFWorkbook workbook, AreaPtg ap) { - int row0 = ap.getFirstRow(); - int col0 = ap.getFirstColumn(); - int row1 = ap.getLastRow(); - int col1 = ap.getLastColumn(); - - // If the last row is -1, then the - // reference is for the rest of the column - // (eg C:C) - // TODO: Handle whole column ranges properly - if(row1 == -1 && row0 >= 0) { - row1 = (short)sheet.getLastRowNum(); - } - ValueEval[] values = evalArea(workbook, sheet, row0, col0, row1, col1); - return new Area2DEval(ap, values); - } - - public static AreaEval evaluateArea3dPtg(HSSFWorkbook workbook, Area3DPtg a3dp) { - int row0 = a3dp.getFirstRow(); - int col0 = a3dp.getFirstColumn(); - int row1 = a3dp.getLastRow(); - int col1 = a3dp.getLastColumn(); - Workbook wb = workbook.getWorkbook(); - HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(a3dp.getExternSheetIndex())); - - // If the last row is -1, then the - // reference is for the rest of the column - // (eg C:C) - // TODO: Handle whole column ranges properly - if(row1 == -1 && row0 >= 0) { - row1 = (short)xsheet.getLastRowNum(); - } - - ValueEval[] values = evalArea(workbook, xsheet, row0, col0, row1, col1); - return new Area3DEval(a3dp, values); - } - - private static ValueEval[] evalArea(HSSFWorkbook workbook, HSSFSheet sheet, - int row0, int col0, int row1, int col1) { - ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)]; - for (int x = row0; sheet != null && x < row1 + 1; x++) { - HSSFRow row = sheet.getRow(x); - for (int y = col0; y < col1 + 1; y++) { - ValueEval cellEval; - if(row == null) { - cellEval = BlankEval.INSTANCE; - } else { - cellEval = getEvalForCell(row.getCell(y), row, sheet, workbook); - } - values[(x - row0) * (col1 - col0 + 1) + (y - col0)] = cellEval; - } - } - return values; - } - - /** - * returns an appropriate Eval impl instance for the Ptg. The Ptg must be - * one of: Area3DPtg, AreaPtg, ReferencePtg, Ref3DPtg, IntPtg, NumberPtg, - * StringPtg, BoolPtg
special Note: OperationPtg subtypes cannot be - * passed here! - * - * @param ptg - */ - protected static Eval getEvalForPtg(Ptg ptg) { - Eval retval = null; - - Class clazz = (Class) VALUE_EVALS_MAP.get(ptg.getClass()); - try { - if (ptg instanceof Area3DPtg) { - Constructor constructor = clazz.getConstructor(AREA3D_CONSTRUCTOR_CLASS_ARRAY); - retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); - } - else if (ptg instanceof AreaPtg) { - Constructor constructor = clazz.getConstructor(AREA3D_CONSTRUCTOR_CLASS_ARRAY); - retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); - } - else if (ptg instanceof ReferencePtg) { - Constructor constructor = clazz.getConstructor(REFERENCE_CONSTRUCTOR_CLASS_ARRAY); - retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); - } - else if (ptg instanceof Ref3DPtg) { - Constructor constructor = clazz.getConstructor(REF3D_CONSTRUCTOR_CLASS_ARRAY); - retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); - } - else { - if (ptg instanceof IntPtg || ptg instanceof NumberPtg || ptg instanceof StringPtg - || ptg instanceof BoolPtg) { - Constructor constructor = clazz.getConstructor(VALUE_CONTRUCTOR_CLASS_ARRAY); - retval = (ValueEval) constructor.newInstance(new Ptg[] { ptg }); - } - } - } - catch (Exception e) { - throw new RuntimeException("Fatal Error: ", e); - } - return retval; - - } - - /** - * Given a cell, find its type and from that create an appropriate ValueEval - * impl instance and return that. Since the cell could be an external - * reference, we need the sheet that this belongs to. - * Non existent cells are treated as empty. - * @param cell - * @param sheet - * @param workbook - */ - protected static ValueEval getEvalForCell(HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) { - - if (cell == null) { - return BlankEval.INSTANCE; - } - switch (cell.getCellType()) { - case HSSFCell.CELL_TYPE_NUMERIC: - return new NumberEval(cell.getNumericCellValue()); - case HSSFCell.CELL_TYPE_STRING: - return new StringEval(cell.getRichStringCellValue().getString()); - case HSSFCell.CELL_TYPE_FORMULA: - return internalEvaluate(cell, row, sheet, workbook); - case HSSFCell.CELL_TYPE_BOOLEAN: - return BoolEval.valueOf(cell.getBooleanCellValue()); - case HSSFCell.CELL_TYPE_BLANK: - return BlankEval.INSTANCE; - case HSSFCell.CELL_TYPE_ERROR: - return ErrorEval.valueOf(cell.getErrorCellValue()); - } - throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); - } - - /** - * Creates a Ref2DEval for ReferencePtg. - * Non existent cells are treated as RefEvals containing BlankEval. - */ - private static Ref2DEval createRef2DEval(ReferencePtg ptg, HSSFCell cell, - HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) { - if (cell == null) { - return new Ref2DEval(ptg, BlankEval.INSTANCE); - } - - switch (cell.getCellType()) { - case HSSFCell.CELL_TYPE_NUMERIC: - return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue())); - case HSSFCell.CELL_TYPE_STRING: - return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString())); - case HSSFCell.CELL_TYPE_FORMULA: - return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook)); - case HSSFCell.CELL_TYPE_BOOLEAN: - return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue())); - case HSSFCell.CELL_TYPE_BLANK: - return new Ref2DEval(ptg, BlankEval.INSTANCE); - case HSSFCell.CELL_TYPE_ERROR: - return new Ref2DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue())); - } - throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); - } - - /** - * create a Ref3DEval for Ref3DPtg. - */ - private static Ref3DEval createRef3DEval(Ref3DPtg ptg, HSSFCell cell, - HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) { - if (cell == null) { - return new Ref3DEval(ptg, BlankEval.INSTANCE); - } - switch (cell.getCellType()) { - case HSSFCell.CELL_TYPE_NUMERIC: - return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue())); - case HSSFCell.CELL_TYPE_STRING: - return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString())); - case HSSFCell.CELL_TYPE_FORMULA: - return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook)); - case HSSFCell.CELL_TYPE_BOOLEAN: - return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue())); - case HSSFCell.CELL_TYPE_BLANK: - return new Ref3DEval(ptg, BlankEval.INSTANCE); - case HSSFCell.CELL_TYPE_ERROR: - return new Ref3DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue())); - } - throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); - } - - /** - * Mimics the 'data view' of a cell. This allows formula evaluator - * to return a CellValue instead of precasting the value to String - * or Number or boolean type. - * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - */ - public static final class CellValue { - private int cellType; - private HSSFRichTextString richTextStringValue; - private double numberValue; - private boolean booleanValue; - private byte errorValue; - - /** - * CellType should be one of the types defined in HSSFCell - * @param cellType - */ - public CellValue(int cellType) { - super(); - this.cellType = cellType; - } - /** - * @return Returns the booleanValue. - */ - public boolean getBooleanValue() { - return booleanValue; - } - /** - * @param booleanValue The booleanValue to set. - */ - public void setBooleanValue(boolean booleanValue) { - this.booleanValue = booleanValue; - } - /** - * @return Returns the numberValue. - */ - public double getNumberValue() { - return numberValue; - } - /** - * @param numberValue The numberValue to set. - */ - public void setNumberValue(double numberValue) { - this.numberValue = numberValue; - } - /** - * @return Returns the stringValue. This method is deprecated, use - * getRichTextStringValue instead - * @deprecated - */ - public String getStringValue() { - return richTextStringValue.getString(); - } - /** - * @param stringValue The stringValue to set. This method is deprecated, use - * getRichTextStringValue instead. - * @deprecated - */ - public void setStringValue(String stringValue) { - this.richTextStringValue = new HSSFRichTextString(stringValue); - } - /** - * @return Returns the cellType. - */ - public int getCellType() { - return cellType; - } - /** - * @return Returns the errorValue. - */ - public byte getErrorValue() { - return errorValue; - } - /** - * @param errorValue The errorValue to set. - */ - public void setErrorValue(byte errorValue) { - this.errorValue = errorValue; - } - /** - * @return Returns the richTextStringValue. - */ - public HSSFRichTextString getRichTextStringValue() { - return richTextStringValue; - } - /** - * @param richTextStringValue The richTextStringValue to set. - */ - public void setRichTextStringValue(HSSFRichTextString richTextStringValue) { - this.richTextStringValue = richTextStringValue; - } - } /** * debug method @@ -760,7 +56,8 @@ public class HSSFFormulaEvaluator { * @param workbook */ void inspectPtgs(String formula) { - FormulaParser fp = new FormulaParser(formula, workbook.getWorkbook()); + HSSFWorkbook hssfWb = (HSSFWorkbook)workbook; + FormulaParser fp = new FormulaParser(formula, hssfWb.getWorkbook()); fp.parse(); Ptg[] ptgs = fp.getRPNPtg(); System.out.println(""); @@ -775,4 +72,13 @@ public class HSSFFormulaEvaluator { System.out.println(""); } + /** + * Compatibility class. + * Seems to do more harm than good though + */ +// public static class CellValue extends FormulaEvaluator.CellValue { +// public CellValue(int cellType, CreationHelper creationHelper) { +// super(cellType, creationHelper); +// } +// } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index e70b771367..51499a99e4 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -610,7 +610,11 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm return sheets.size(); } - /** + public int getSheetIndexFromExternSheetIndex(int externSheetNumber) { + return workbook.getSheetIndexFromExternSheetIndex(externSheetNumber); + } + + /** * Get the HSSFSheet object at the given index. * @param index of the sheet number (0-based physical & logical) * @return HSSFSheet at the provided index diff --git a/src/java/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java b/src/java/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java deleted file mode 100755 index 1292009699..0000000000 --- a/src/java/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java +++ /dev/null @@ -1,165 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.usermodel; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.Map; - -import org.apache.poi.hssf.record.formula.AddPtg; -import org.apache.poi.hssf.record.formula.ConcatPtg; -import org.apache.poi.hssf.record.formula.DividePtg; -import org.apache.poi.hssf.record.formula.EqualPtg; -import org.apache.poi.hssf.record.formula.ExpPtg; -import org.apache.poi.hssf.record.formula.FuncPtg; -import org.apache.poi.hssf.record.formula.FuncVarPtg; -import org.apache.poi.hssf.record.formula.GreaterEqualPtg; -import org.apache.poi.hssf.record.formula.GreaterThanPtg; -import org.apache.poi.hssf.record.formula.LessEqualPtg; -import org.apache.poi.hssf.record.formula.LessThanPtg; -import org.apache.poi.hssf.record.formula.MultiplyPtg; -import org.apache.poi.hssf.record.formula.NotEqualPtg; -import org.apache.poi.hssf.record.formula.OperationPtg; -import org.apache.poi.hssf.record.formula.PercentPtg; -import org.apache.poi.hssf.record.formula.PowerPtg; -import org.apache.poi.hssf.record.formula.Ptg; -import org.apache.poi.hssf.record.formula.SubtractPtg; -import org.apache.poi.hssf.record.formula.UnaryMinusPtg; -import org.apache.poi.hssf.record.formula.UnaryPlusPtg; -import org.apache.poi.hssf.record.formula.eval.AddEval; -import org.apache.poi.hssf.record.formula.eval.ConcatEval; -import org.apache.poi.hssf.record.formula.eval.DivideEval; -import org.apache.poi.hssf.record.formula.eval.EqualEval; -import org.apache.poi.hssf.record.formula.eval.FuncVarEval; -import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval; -import org.apache.poi.hssf.record.formula.eval.GreaterThanEval; -import org.apache.poi.hssf.record.formula.eval.LessEqualEval; -import org.apache.poi.hssf.record.formula.eval.LessThanEval; -import org.apache.poi.hssf.record.formula.eval.MultiplyEval; -import org.apache.poi.hssf.record.formula.eval.NotEqualEval; -import org.apache.poi.hssf.record.formula.eval.OperationEval; -import org.apache.poi.hssf.record.formula.eval.PercentEval; -import org.apache.poi.hssf.record.formula.eval.PowerEval; -import org.apache.poi.hssf.record.formula.eval.SubtractEval; -import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; -import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; - -/** - * This class creates OperationEval instances to help evaluate OperationPtg - * formula tokens. - * - * @author Josh Micich - */ -final class OperationEvaluatorFactory { - private static final Class[] OPERATION_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; - - private static final Map _constructorsByPtgClass = initialiseConstructorsMap(); - - private OperationEvaluatorFactory() { - // no instances of this class - } - - private static Map initialiseConstructorsMap() { - Map m = new HashMap(32); - add(m, AddPtg.class, AddEval.class); - add(m, ConcatPtg.class, ConcatEval.class); - add(m, DividePtg.class, DivideEval.class); - add(m, EqualPtg.class, EqualEval.class); - add(m, FuncPtg.class, FuncVarEval.class); - add(m, FuncVarPtg.class, FuncVarEval.class); - add(m, GreaterEqualPtg.class, GreaterEqualEval.class); - add(m, GreaterThanPtg.class, GreaterThanEval.class); - add(m, LessEqualPtg.class, LessEqualEval.class); - add(m, LessThanPtg.class, LessThanEval.class); - add(m, MultiplyPtg.class, MultiplyEval.class); - add(m, NotEqualPtg.class, NotEqualEval.class); - add(m, PercentPtg.class, PercentEval.class); - add(m, PowerPtg.class, PowerEval.class); - add(m, SubtractPtg.class, SubtractEval.class); - add(m, UnaryMinusPtg.class, UnaryMinusEval.class); - add(m, UnaryPlusPtg.class, UnaryPlusEval.class); - return m; - } - - private static void add(Map m, Class ptgClass, Class evalClass) { - - // perform some validation now, to keep later exception handlers simple - if(!Ptg.class.isAssignableFrom(ptgClass)) { - throw new IllegalArgumentException("Expected Ptg subclass"); - } - if(!OperationEval.class.isAssignableFrom(evalClass)) { - throw new IllegalArgumentException("Expected OperationEval subclass"); - } - if (!Modifier.isPublic(evalClass.getModifiers())) { - throw new RuntimeException("Eval class must be public"); - } - if (Modifier.isAbstract(evalClass.getModifiers())) { - throw new RuntimeException("Eval class must not be abstract"); - } - - Constructor constructor; - try { - constructor = evalClass.getDeclaredConstructor(OPERATION_CONSTRUCTOR_CLASS_ARRAY); - } catch (NoSuchMethodException e) { - throw new RuntimeException("Missing constructor"); - } - if (!Modifier.isPublic(constructor.getModifiers())) { - throw new RuntimeException("Eval constructor must be public"); - } - m.put(ptgClass, constructor); - } - - /** - * returns the OperationEval concrete impl instance corresponding - * to the supplied operationPtg - */ - public static OperationEval create(OperationPtg ptg) { - if(ptg == null) { - throw new IllegalArgumentException("ptg must not be null"); - } - - Class ptgClass = ptg.getClass(); - - Constructor constructor = (Constructor) _constructorsByPtgClass.get(ptgClass); - if(constructor == null) { - if(ptgClass == ExpPtg.class) { - // ExpPtg is used for array formulas and shared formulas. - // it is currently unsupported, and may not even get implemented here - throw new RuntimeException("ExpPtg currently not supported"); - } - throw new RuntimeException("Unexpected operation ptg class (" + ptgClass.getName() + ")"); - } - - Object result; - Object[] initargs = { ptg }; - try { - result = constructor.newInstance(initargs); - } catch (IllegalArgumentException e) { - throw new RuntimeException(e); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - return (OperationEval) result; - } -} diff --git a/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetector.java b/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetector.java new file mode 100755 index 0000000000..9cf3aa982f --- /dev/null +++ b/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetector.java @@ -0,0 +1,153 @@ +/* ==================================================================== + 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.usermodel; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +/** + * Instances of this class keep track of multiple dependent cell evaluations due + * to recursive calls to HSSFFormulaEvaluator.internalEvaluate(). + * The main purpose of this class is to detect an attempt to evaluate a cell + * that is already being evaluated. In other words, it detects circular + * references in spreadsheet formulas. + * + * @author Josh Micich + */ +final class EvaluationCycleDetector { + + /** + * Stores the parameters that identify the evaluation of one cell.
+ */ + private static final class CellEvaluationFrame { + + private final Workbook _workbook; + private final Sheet _sheet; + private final int _srcRowNum; + private final int _srcColNum; + + public CellEvaluationFrame(Workbook workbook, Sheet sheet, int srcRowNum, int srcColNum) { + if (workbook == null) { + throw new IllegalArgumentException("workbook must not be null"); + } + if (sheet == null) { + throw new IllegalArgumentException("sheet must not be null"); + } + _workbook = workbook; + _sheet = sheet; + _srcRowNum = srcRowNum; + _srcColNum = srcColNum; + } + + public boolean equals(Object obj) { + CellEvaluationFrame other = (CellEvaluationFrame) obj; + if (_workbook != other._workbook) { + return false; + } + if (_sheet != other._sheet) { + return false; + } + if (_srcRowNum != other._srcRowNum) { + return false; + } + if (_srcColNum != other._srcColNum) { + return false; + } + return true; + } + + /** + * @return human readable string for debug purposes + */ + public String formatAsString() { + return "R=" + _srcRowNum + " C=" + _srcColNum + " ShIx=" + _workbook.getSheetIndex(_sheet); + } + + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(formatAsString()); + sb.append("]"); + return sb.toString(); + } + } + + private final List _evaluationFrames; + + public EvaluationCycleDetector() { + _evaluationFrames = new ArrayList(); + } + + /** + * Notifies this evaluation tracker that evaluation of the specified cell is + * about to start.
+ * + * In the case of a true return code, the caller should + * continue evaluation of the specified cell, and also be sure to call + * endEvaluate() when complete.
+ * + * In the case of a false return code, the caller should + * return an evaluation result of + * ErrorEval.CIRCULAR_REF_ERROR, and not call endEvaluate(). + *
+ * @return true if the specified cell has not been visited yet in the current + * evaluation. false if the specified cell is already being evaluated. + */ + public boolean startEvaluate(Workbook workbook, Sheet sheet, int srcRowNum, int srcColNum) { + CellEvaluationFrame cef = new CellEvaluationFrame(workbook, sheet, srcRowNum, srcColNum); + if (_evaluationFrames.contains(cef)) { + return false; + } + _evaluationFrames.add(cef); + return true; + } + + /** + * Notifies this evaluation tracker that the evaluation of the specified + * cell is complete.

+ * + * Every successful call to startEvaluate must be followed by a + * call to endEvaluate (recommended in a finally block) to enable + * proper tracking of which cells are being evaluated at any point in time.

+ * + * Assuming a well behaved client, parameters to this method would not be + * required. However, they have been included to assert correct behaviour, + * and form more meaningful error messages. + */ + public void endEvaluate(Workbook workbook, Sheet sheet, int srcRowNum, int srcColNum) { + int nFrames = _evaluationFrames.size(); + if (nFrames < 1) { + throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate"); + } + + nFrames--; + CellEvaluationFrame cefExpected = (CellEvaluationFrame) _evaluationFrames.get(nFrames); + CellEvaluationFrame cefActual = new CellEvaluationFrame(workbook, sheet, srcRowNum, srcColNum); + if (!cefActual.equals(cefExpected)) { + throw new RuntimeException("Wrong cell specified. " + + "Corresponding startEvaluate() call was for cell {" + + cefExpected.formatAsString() + "} this endEvaluate() call is for cell {" + + cefActual.formatAsString() + "}"); + } + // else - no problems so pop current frame + _evaluationFrames.remove(nFrames); + } +} diff --git a/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetectorManager.java b/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetectorManager.java new file mode 100755 index 0000000000..d83d187002 --- /dev/null +++ b/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetectorManager.java @@ -0,0 +1,46 @@ +/* ==================================================================== + 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.usermodel; + +/** + * This class makes an EvaluationCycleDetector instance available to + * each thread via a ThreadLocal in order to avoid adding a parameter + * to a few protected methods within HSSFFormulaEvaluator. + * + * @author Josh Micich + */ +final class EvaluationCycleDetectorManager { + + ThreadLocal tl = null; + private static ThreadLocal _tlEvaluationTracker = new ThreadLocal() { + protected synchronized Object initialValue() { + return new EvaluationCycleDetector(); + } + }; + + /** + * @return + */ + public static EvaluationCycleDetector getTracker() { + return (EvaluationCycleDetector) _tlEvaluationTracker.get(); + } + + private EvaluationCycleDetectorManager() { + // no instances of this class + } +} diff --git a/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java b/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java new file mode 100644 index 0000000000..80287f5ec4 --- /dev/null +++ b/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java @@ -0,0 +1,759 @@ +/* +* 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.usermodel; + +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Stack; + +import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.record.formula.Area3DPtg; +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.AttrPtg; +import org.apache.poi.hssf.record.formula.BoolPtg; +import org.apache.poi.hssf.record.formula.ControlPtg; +import org.apache.poi.hssf.record.formula.IntPtg; +import org.apache.poi.hssf.record.formula.MemErrPtg; +import org.apache.poi.hssf.record.formula.MissingArgPtg; +import org.apache.poi.hssf.record.formula.NamePtg; +import org.apache.poi.hssf.record.formula.NameXPtg; +import org.apache.poi.hssf.record.formula.NumberPtg; +import org.apache.poi.hssf.record.formula.OperationPtg; +import org.apache.poi.hssf.record.formula.ParenthesisPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.Ref3DPtg; +import org.apache.poi.hssf.record.formula.ReferencePtg; +import org.apache.poi.hssf.record.formula.StringPtg; +import org.apache.poi.hssf.record.formula.UnionPtg; +import org.apache.poi.hssf.record.formula.UnknownPtg; +import org.apache.poi.hssf.record.formula.eval.Area2DEval; +import org.apache.poi.hssf.record.formula.eval.Area3DEval; +import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.BlankEval; +import org.apache.poi.hssf.record.formula.eval.BoolEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.FunctionEval; +import org.apache.poi.hssf.record.formula.eval.NameEval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.OperationEval; +import org.apache.poi.hssf.record.formula.eval.Ref2DEval; +import org.apache.poi.hssf.record.formula.eval.Ref3DEval; +import org.apache.poi.hssf.record.formula.eval.RefEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; + +/** + * @author Amol S. Deshmukh < amolweb at ya hoo dot com > + * + */ +public class FormulaEvaluator { + + // params to lookup the right constructor using reflection + private static final Class[] VALUE_CONTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; + + private static final Class[] AREA3D_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class, ValueEval[].class }; + + private static final Class[] REFERENCE_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class, ValueEval.class }; + + private static final Class[] REF3D_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class, ValueEval.class }; + + // Maps for mapping *Eval to *Ptg + private static final Map VALUE_EVALS_MAP = new HashMap(); + + /* + * Following is the mapping between the Ptg tokens returned + * by the FormulaParser and the *Eval classes that are used + * by the FormulaEvaluator + */ + static { + VALUE_EVALS_MAP.put(BoolPtg.class, BoolEval.class); + VALUE_EVALS_MAP.put(IntPtg.class, NumberEval.class); + VALUE_EVALS_MAP.put(NumberPtg.class, NumberEval.class); + VALUE_EVALS_MAP.put(StringPtg.class, StringEval.class); + + } + + + protected Row row; + protected Sheet sheet; + protected Workbook workbook; + + public FormulaEvaluator(Sheet sheet, Workbook workbook) { + this.sheet = sheet; + this.workbook = workbook; + } + + public void setCurrentRow(Row row) { + this.row = row; + } + + /** + * If cell contains a formula, the formula is evaluated and returned, + * else the CellValue simply copies the appropriate cell value from + * the cell and also its cell type. This method should be preferred over + * evaluateInCell() when the call should not modify the contents of the + * original cell. + * @param cell + */ + public CellValue evaluate(Cell cell) { + CellValue retval = null; + if (cell != null) { + switch (cell.getCellType()) { + case Cell.CELL_TYPE_BLANK: + retval = new CellValue(Cell.CELL_TYPE_BLANK, workbook.getCreationHelper()); + break; + case Cell.CELL_TYPE_BOOLEAN: + retval = new CellValue(Cell.CELL_TYPE_BOOLEAN, workbook.getCreationHelper()); + retval.setBooleanValue(cell.getBooleanCellValue()); + break; + case Cell.CELL_TYPE_ERROR: + retval = new CellValue(Cell.CELL_TYPE_ERROR, workbook.getCreationHelper()); + retval.setErrorValue(cell.getErrorCellValue()); + break; + case Cell.CELL_TYPE_FORMULA: + retval = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook), workbook.getCreationHelper()); + break; + case Cell.CELL_TYPE_NUMERIC: + retval = new CellValue(Cell.CELL_TYPE_NUMERIC, workbook.getCreationHelper()); + retval.setNumberValue(cell.getNumericCellValue()); + break; + case Cell.CELL_TYPE_STRING: + retval = new CellValue(Cell.CELL_TYPE_STRING, workbook.getCreationHelper()); + retval.setRichTextStringValue(cell.getRichStringCellValue()); + break; + } + } + return retval; + } + + + /** + * If cell contains formula, it evaluates the formula, + * and saves the result of the formula. The cell + * remains as a formula cell. + * Else if cell does not contain formula, this method leaves + * the cell unchanged. + * Note that the type of the formula result is returned, + * so you know what kind of value is also stored with + * the formula. + *

+     * int evaluatedCellType = evaluator.evaluateFormulaCell(cell);
+     * 
+ * Be aware that your cell will hold both the formula, + * and the result. If you want the cell replaced with + * the result of the formula, use {@link #evaluateInCell(HSSFCell)} + * @param cell The cell to evaluate + * @return The type of the formula result (the cell's type remains as HSSFCell.CELL_TYPE_FORMULA however) + */ + public int evaluateFormulaCell(Cell cell) { + if (cell != null) { + switch (cell.getCellType()) { + case Cell.CELL_TYPE_FORMULA: + CellValue cv = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook), workbook.getCreationHelper()); + switch (cv.getCellType()) { + case Cell.CELL_TYPE_BOOLEAN: + cell.setCellValue(cv.getBooleanValue()); + break; + case Cell.CELL_TYPE_ERROR: + cell.setCellValue(cv.getErrorValue()); + break; + case Cell.CELL_TYPE_NUMERIC: + cell.setCellValue(cv.getNumberValue()); + break; + case Cell.CELL_TYPE_STRING: + cell.setCellValue(cv.getRichTextStringValue()); + break; + case Cell.CELL_TYPE_BLANK: + break; + case Cell.CELL_TYPE_FORMULA: // this will never happen, we have already evaluated the formula + break; + } + return cv.getCellType(); + } + } + return -1; + } + + /** + * If cell contains formula, it evaluates the formula, and + * puts the formula result back into the cell, in place + * of the old formula. + * Else if cell does not contain formula, this method leaves + * the cell unchanged. + * Note that the same instance of HSSFCell is returned to + * allow chained calls like: + *
+     * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();
+     * 
+ * Be aware that your cell value will be changed to hold the + * result of the formula. If you simply want the formula + * value computed for you, use {@link #evaluateFormulaCell(HSSFCell)} + * @param cell + */ + public Cell evaluateInCell(Cell cell) { + if (cell != null) { + switch (cell.getCellType()) { + case Cell.CELL_TYPE_FORMULA: + CellValue cv = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook), workbook.getCreationHelper()); + switch (cv.getCellType()) { + case Cell.CELL_TYPE_BOOLEAN: + cell.setCellType(Cell.CELL_TYPE_BOOLEAN); + cell.setCellValue(cv.getBooleanValue()); + break; + case Cell.CELL_TYPE_ERROR: + cell.setCellType(Cell.CELL_TYPE_ERROR); + cell.setCellValue(cv.getErrorValue()); + break; + case Cell.CELL_TYPE_NUMERIC: + cell.setCellType(Cell.CELL_TYPE_NUMERIC); + cell.setCellValue(cv.getNumberValue()); + break; + case Cell.CELL_TYPE_STRING: + cell.setCellType(Cell.CELL_TYPE_STRING); + cell.setCellValue(cv.getRichTextStringValue()); + break; + case Cell.CELL_TYPE_BLANK: + break; + case Cell.CELL_TYPE_FORMULA: // this will never happen, we have already evaluated the formula + break; + } + } + } + return cell; + } + + /** + * Loops over all cells in all sheets of the supplied + * workbook. + * For cells that contain formulas, their formulas are + * evaluated, and the results are saved. These cells + * remain as formula cells. + * For cells that do not contain formulas, no changes + * are made. + * This is a helpful wrapper around looping over all + * cells, and calling evaluateFormulaCell on each one. + */ + public static void evaluateAllFormulaCells(Workbook wb) { + for(int i=0; i= 0; j--) { + Eval p = (Eval) stack.pop(); + ops[j] = p; + } + Eval opresult = invokeOperation(operation, ops, srcRowNum, srcColNum, workbook, sheet); + stack.push(opresult); + } + else if (ptg instanceof ReferencePtg) { + ReferencePtg refPtg = (ReferencePtg) ptg; + int colIx = refPtg.getColumn(); + int rowIx = refPtg.getRow(); + Row row = sheet.getRow(rowIx); + Cell cell = (row != null) ? row.getCell(colIx) : null; + stack.push(createRef2DEval(refPtg, cell, row, sheet, workbook)); + } + else if (ptg instanceof Ref3DPtg) { + Ref3DPtg refPtg = (Ref3DPtg) ptg; + int colIx = refPtg.getColumn(); + int rowIx = refPtg.getRow(); + Sheet xsheet = workbook.getSheetAt( + workbook.getSheetIndexFromExternSheetIndex(refPtg.getExternSheetIndex()) + ); + Row row = xsheet.getRow(rowIx); + Cell cell = (row != null) ? row.getCell(colIx) : null; + stack.push(createRef3DEval(refPtg, cell, row, xsheet, workbook)); + } + else if (ptg instanceof AreaPtg) { + AreaPtg ap = (AreaPtg) ptg; + AreaEval ae = evaluateAreaPtg(sheet, workbook, ap); + stack.push(ae); + } + else if (ptg instanceof Area3DPtg) { + Area3DPtg a3dp = (Area3DPtg) ptg; + AreaEval ae = evaluateArea3dPtg(workbook, a3dp); + stack.push(ae); + } + else { + Eval ptgEval = getEvalForPtg(ptg); + stack.push(ptgEval); + } + } + + ValueEval value = ((ValueEval) stack.pop()); + if (!stack.isEmpty()) { + throw new IllegalStateException("evaluation stack not empty"); + } + value = dereferenceValue(value, srcRowNum, srcColNum); + if (value instanceof BlankEval) { + // Note Excel behaviour here. A blank final final value is converted to zero. + return NumberEval.ZERO; + // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to + // blank, the actual value is empty string. This can be verified with ISBLANK(). + } + return value; + } + + /** + * Dereferences a single value from any AreaEval or RefEval evaluation result. + * If the supplied evaluationResult is just a plain value, it is returned as-is. + * @return a NumberEval, StringEval, BoolEval, + * BlankEval or ErrorEval. Never null. + */ + private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) { + if (evaluationResult instanceof RefEval) { + RefEval rv = (RefEval) evaluationResult; + return rv.getInnerValueEval(); + } + if (evaluationResult instanceof AreaEval) { + AreaEval ae = (AreaEval) evaluationResult; + if (ae.isRow()) { + if(ae.isColumn()) { + return ae.getValues()[0]; + } + return ae.getValueAt(ae.getFirstRow(), srcColNum); + } + if (ae.isColumn()) { + return ae.getValueAt(srcRowNum, ae.getFirstColumn()); + } + return ErrorEval.VALUE_INVALID; + } + return evaluationResult; + } + + private static Eval invokeOperation(OperationEval operation, Eval[] ops, int srcRowNum, short srcColNum, + Workbook workbook, Sheet sheet) { + + if(operation instanceof FunctionEval) { + FunctionEval fe = (FunctionEval) operation; + if(fe.isFreeRefFunction()) { + return fe.getFreeRefFunction().evaluate(ops, srcRowNum, srcColNum, workbook, sheet); + } + } + return operation.evaluate(ops, srcRowNum, srcColNum); + } + + public static AreaEval evaluateAreaPtg(Sheet sheet, Workbook workbook, AreaPtg ap) { + int row0 = ap.getFirstRow(); + int col0 = ap.getFirstColumn(); + int row1 = ap.getLastRow(); + int col1 = ap.getLastColumn(); + + // If the last row is -1, then the + // reference is for the rest of the column + // (eg C:C) + // TODO: Handle whole column ranges properly + if(row1 == -1 && row0 >= 0) { + row1 = (short)sheet.getLastRowNum(); + } + ValueEval[] values = evalArea(workbook, sheet, row0, col0, row1, col1); + return new Area2DEval(ap, values); + } + + public static AreaEval evaluateArea3dPtg(Workbook workbook, Area3DPtg a3dp) { + int row0 = a3dp.getFirstRow(); + int col0 = a3dp.getFirstColumn(); + int row1 = a3dp.getLastRow(); + int col1 = a3dp.getLastColumn(); + Sheet xsheet = workbook.getSheetAt( + workbook.getSheetIndexFromExternSheetIndex(a3dp.getExternSheetIndex()) + ); + + // If the last row is -1, then the + // reference is for the rest of the column + // (eg C:C) + // TODO: Handle whole column ranges properly + if(row1 == -1 && row0 >= 0) { + row1 = (short)xsheet.getLastRowNum(); + } + + ValueEval[] values = evalArea(workbook, xsheet, row0, col0, row1, col1); + return new Area3DEval(a3dp, values); + } + + private static ValueEval[] evalArea(Workbook workbook, Sheet sheet, + int row0, int col0, int row1, int col1) { + ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)]; + for (int x = row0; sheet != null && x < row1 + 1; x++) { + Row row = sheet.getRow(x); + for (int y = col0; y < col1 + 1; y++) { + ValueEval cellEval; + if(row == null) { + cellEval = BlankEval.INSTANCE; + } else { + cellEval = getEvalForCell(row.getCell(y), row, sheet, workbook); + } + values[(x - row0) * (col1 - col0 + 1) + (y - col0)] = cellEval; + } + } + return values; + } + + /** + * returns an appropriate Eval impl instance for the Ptg. The Ptg must be + * one of: Area3DPtg, AreaPtg, ReferencePtg, Ref3DPtg, IntPtg, NumberPtg, + * StringPtg, BoolPtg
special Note: OperationPtg subtypes cannot be + * passed here! + * + * @param ptg + */ + protected static Eval getEvalForPtg(Ptg ptg) { + Eval retval = null; + + Class clazz = (Class) VALUE_EVALS_MAP.get(ptg.getClass()); + try { + if (ptg instanceof Area3DPtg) { + Constructor constructor = clazz.getConstructor(AREA3D_CONSTRUCTOR_CLASS_ARRAY); + retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); + } + else if (ptg instanceof AreaPtg) { + Constructor constructor = clazz.getConstructor(AREA3D_CONSTRUCTOR_CLASS_ARRAY); + retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); + } + else if (ptg instanceof ReferencePtg) { + Constructor constructor = clazz.getConstructor(REFERENCE_CONSTRUCTOR_CLASS_ARRAY); + retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); + } + else if (ptg instanceof Ref3DPtg) { + Constructor constructor = clazz.getConstructor(REF3D_CONSTRUCTOR_CLASS_ARRAY); + retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); + } + else { + if (ptg instanceof IntPtg || ptg instanceof NumberPtg || ptg instanceof StringPtg + || ptg instanceof BoolPtg) { + Constructor constructor = clazz.getConstructor(VALUE_CONTRUCTOR_CLASS_ARRAY); + retval = (ValueEval) constructor.newInstance(new Ptg[] { ptg }); + } + } + } + catch (Exception e) { + throw new RuntimeException("Fatal Error: ", e); + } + return retval; + + } + + /** + * Given a cell, find its type and from that create an appropriate ValueEval + * impl instance and return that. Since the cell could be an external + * reference, we need the sheet that this belongs to. + * Non existent cells are treated as empty. + * @param cell + * @param sheet + * @param workbook + */ + protected static ValueEval getEvalForCell(Cell cell, Row row, Sheet sheet, Workbook workbook) { + + if (cell == null) { + return BlankEval.INSTANCE; + } + switch (cell.getCellType()) { + case Cell.CELL_TYPE_NUMERIC: + return new NumberEval(cell.getNumericCellValue()); + case Cell.CELL_TYPE_STRING: + return new StringEval(cell.getRichStringCellValue().getString()); + case Cell.CELL_TYPE_FORMULA: + return internalEvaluate(cell, row, sheet, workbook); + case Cell.CELL_TYPE_BOOLEAN: + return BoolEval.valueOf(cell.getBooleanCellValue()); + case Cell.CELL_TYPE_BLANK: + return BlankEval.INSTANCE; + case Cell.CELL_TYPE_ERROR: + return ErrorEval.valueOf(cell.getErrorCellValue()); + } + throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); + } + + /** + * Creates a Ref2DEval for ReferencePtg. + * Non existent cells are treated as RefEvals containing BlankEval. + */ + private static Ref2DEval createRef2DEval(ReferencePtg ptg, Cell cell, + Row row, Sheet sheet, Workbook workbook) { + if (cell == null) { + return new Ref2DEval(ptg, BlankEval.INSTANCE); + } + + switch (cell.getCellType()) { + case Cell.CELL_TYPE_NUMERIC: + return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue())); + case Cell.CELL_TYPE_STRING: + return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString())); + case Cell.CELL_TYPE_FORMULA: + return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook)); + case Cell.CELL_TYPE_BOOLEAN: + return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue())); + case Cell.CELL_TYPE_BLANK: + return new Ref2DEval(ptg, BlankEval.INSTANCE); + case Cell.CELL_TYPE_ERROR: + return new Ref2DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue())); + } + throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); + } + + /** + * create a Ref3DEval for Ref3DPtg. + */ + private static Ref3DEval createRef3DEval(Ref3DPtg ptg, Cell cell, + Row row, Sheet sheet, Workbook workbook) { + if (cell == null) { + return new Ref3DEval(ptg, BlankEval.INSTANCE); + } + switch (cell.getCellType()) { + case Cell.CELL_TYPE_NUMERIC: + return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue())); + case Cell.CELL_TYPE_STRING: + return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString())); + case Cell.CELL_TYPE_FORMULA: + return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook)); + case Cell.CELL_TYPE_BOOLEAN: + return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue())); + case Cell.CELL_TYPE_BLANK: + return new Ref3DEval(ptg, BlankEval.INSTANCE); + case Cell.CELL_TYPE_ERROR: + return new Ref3DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue())); + } + throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); + } + + /** + * Mimics the 'data view' of a cell. This allows formula evaluator + * to return a CellValue instead of precasting the value to String + * or Number or boolean type. + * @author Amol S. Deshmukh < amolweb at ya hoo dot com > + */ + public static class CellValue { + private CreationHelper creationHelper; + private int cellType; + private RichTextString richTextStringValue; + private double numberValue; + private boolean booleanValue; + private byte errorValue; + + /** + * CellType should be one of the types defined in HSSFCell + * @param cellType + */ + public CellValue(int cellType, CreationHelper creationHelper) { + super(); + this.creationHelper = creationHelper; + this.cellType = cellType; + } + /** + * @return Returns the booleanValue. + */ + public boolean getBooleanValue() { + return booleanValue; + } + /** + * @param booleanValue The booleanValue to set. + */ + public void setBooleanValue(boolean booleanValue) { + this.booleanValue = booleanValue; + } + /** + * @return Returns the numberValue. + */ + public double getNumberValue() { + return numberValue; + } + /** + * @param numberValue The numberValue to set. + */ + public void setNumberValue(double numberValue) { + this.numberValue = numberValue; + } + /** + * @return Returns the stringValue. This method is deprecated, use + * getRichTextStringValue instead + * @deprecated + */ + public String getStringValue() { + return richTextStringValue.getString(); + } + /** + * @param stringValue The stringValue to set. This method is deprecated, use + * getRichTextStringValue instead. + * @deprecated + */ + public void setStringValue(String stringValue) { + this.richTextStringValue = + creationHelper.createRichTextString(stringValue); + } + /** + * @return Returns the cellType. + */ + public int getCellType() { + return cellType; + } + /** + * @return Returns the errorValue. + */ + public byte getErrorValue() { + return errorValue; + } + /** + * @param errorValue The errorValue to set. + */ + public void setErrorValue(byte errorValue) { + this.errorValue = errorValue; + } + /** + * @return Returns the richTextStringValue. + */ + public RichTextString getRichTextStringValue() { + return richTextStringValue; + } + /** + * @param richTextStringValue The richTextStringValue to set. + */ + public void setRichTextStringValue(RichTextString richTextStringValue) { + this.richTextStringValue = richTextStringValue; + } + } +} diff --git a/src/java/org/apache/poi/ss/usermodel/OperationEvaluatorFactory.java b/src/java/org/apache/poi/ss/usermodel/OperationEvaluatorFactory.java new file mode 100755 index 0000000000..ed136e0379 --- /dev/null +++ b/src/java/org/apache/poi/ss/usermodel/OperationEvaluatorFactory.java @@ -0,0 +1,165 @@ +/* ==================================================================== + 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.usermodel; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +import org.apache.poi.hssf.record.formula.AddPtg; +import org.apache.poi.hssf.record.formula.ConcatPtg; +import org.apache.poi.hssf.record.formula.DividePtg; +import org.apache.poi.hssf.record.formula.EqualPtg; +import org.apache.poi.hssf.record.formula.ExpPtg; +import org.apache.poi.hssf.record.formula.FuncPtg; +import org.apache.poi.hssf.record.formula.FuncVarPtg; +import org.apache.poi.hssf.record.formula.GreaterEqualPtg; +import org.apache.poi.hssf.record.formula.GreaterThanPtg; +import org.apache.poi.hssf.record.formula.LessEqualPtg; +import org.apache.poi.hssf.record.formula.LessThanPtg; +import org.apache.poi.hssf.record.formula.MultiplyPtg; +import org.apache.poi.hssf.record.formula.NotEqualPtg; +import org.apache.poi.hssf.record.formula.OperationPtg; +import org.apache.poi.hssf.record.formula.PercentPtg; +import org.apache.poi.hssf.record.formula.PowerPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.SubtractPtg; +import org.apache.poi.hssf.record.formula.UnaryMinusPtg; +import org.apache.poi.hssf.record.formula.UnaryPlusPtg; +import org.apache.poi.hssf.record.formula.eval.AddEval; +import org.apache.poi.hssf.record.formula.eval.ConcatEval; +import org.apache.poi.hssf.record.formula.eval.DivideEval; +import org.apache.poi.hssf.record.formula.eval.EqualEval; +import org.apache.poi.hssf.record.formula.eval.FuncVarEval; +import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval; +import org.apache.poi.hssf.record.formula.eval.GreaterThanEval; +import org.apache.poi.hssf.record.formula.eval.LessEqualEval; +import org.apache.poi.hssf.record.formula.eval.LessThanEval; +import org.apache.poi.hssf.record.formula.eval.MultiplyEval; +import org.apache.poi.hssf.record.formula.eval.NotEqualEval; +import org.apache.poi.hssf.record.formula.eval.OperationEval; +import org.apache.poi.hssf.record.formula.eval.PercentEval; +import org.apache.poi.hssf.record.formula.eval.PowerEval; +import org.apache.poi.hssf.record.formula.eval.SubtractEval; +import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; +import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; + +/** + * This class creates OperationEval instances to help evaluate OperationPtg + * formula tokens. + * + * @author Josh Micich + */ +final class OperationEvaluatorFactory { + private static final Class[] OPERATION_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; + + private static final Map _constructorsByPtgClass = initialiseConstructorsMap(); + + private OperationEvaluatorFactory() { + // no instances of this class + } + + private static Map initialiseConstructorsMap() { + Map m = new HashMap(32); + add(m, AddPtg.class, AddEval.class); + add(m, ConcatPtg.class, ConcatEval.class); + add(m, DividePtg.class, DivideEval.class); + add(m, EqualPtg.class, EqualEval.class); + add(m, FuncPtg.class, FuncVarEval.class); + add(m, FuncVarPtg.class, FuncVarEval.class); + add(m, GreaterEqualPtg.class, GreaterEqualEval.class); + add(m, GreaterThanPtg.class, GreaterThanEval.class); + add(m, LessEqualPtg.class, LessEqualEval.class); + add(m, LessThanPtg.class, LessThanEval.class); + add(m, MultiplyPtg.class, MultiplyEval.class); + add(m, NotEqualPtg.class, NotEqualEval.class); + add(m, PercentPtg.class, PercentEval.class); + add(m, PowerPtg.class, PowerEval.class); + add(m, SubtractPtg.class, SubtractEval.class); + add(m, UnaryMinusPtg.class, UnaryMinusEval.class); + add(m, UnaryPlusPtg.class, UnaryPlusEval.class); + return m; + } + + private static void add(Map m, Class ptgClass, Class evalClass) { + + // perform some validation now, to keep later exception handlers simple + if(!Ptg.class.isAssignableFrom(ptgClass)) { + throw new IllegalArgumentException("Expected Ptg subclass"); + } + if(!OperationEval.class.isAssignableFrom(evalClass)) { + throw new IllegalArgumentException("Expected OperationEval subclass"); + } + if (!Modifier.isPublic(evalClass.getModifiers())) { + throw new RuntimeException("Eval class must be public"); + } + if (Modifier.isAbstract(evalClass.getModifiers())) { + throw new RuntimeException("Eval class must not be abstract"); + } + + Constructor constructor; + try { + constructor = evalClass.getDeclaredConstructor(OPERATION_CONSTRUCTOR_CLASS_ARRAY); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Missing constructor"); + } + if (!Modifier.isPublic(constructor.getModifiers())) { + throw new RuntimeException("Eval constructor must be public"); + } + m.put(ptgClass, constructor); + } + + /** + * returns the OperationEval concrete impl instance corresponding + * to the supplied operationPtg + */ + public static OperationEval create(OperationPtg ptg) { + if(ptg == null) { + throw new IllegalArgumentException("ptg must not be null"); + } + + Class ptgClass = ptg.getClass(); + + Constructor constructor = (Constructor) _constructorsByPtgClass.get(ptgClass); + if(constructor == null) { + if(ptgClass == ExpPtg.class) { + // ExpPtg is used for array formulas and shared formulas. + // it is currently unsupported, and may not even get implemented here + throw new RuntimeException("ExpPtg currently not supported"); + } + throw new RuntimeException("Unexpected operation ptg class (" + ptgClass.getName() + ")"); + } + + Object result; + Object[] initargs = { ptg }; + try { + result = constructor.newInstance(initargs); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + return (OperationEval) result; + } +} diff --git a/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/Workbook.java b/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/Workbook.java index e66cf0e1d7..5605752d0a 100644 --- a/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/Workbook.java +++ b/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/Workbook.java @@ -174,6 +174,14 @@ public interface Workbook { */ int getNumberOfSheets(); + + /** + * Finds the sheet index for a particular external sheet number. + * @param externSheetNumber The external sheet number to convert + * @return The index to the sheet found. + */ + int getSheetIndexFromExternSheetIndex(int externSheetNumber); + /** * Get the HSSFSheet object at the given index. diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index d2f0d36080..dd6e5eb2c9 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -472,8 +472,17 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { } return -1; } + + /** + * Doesn't do anything - returns the same index + * TODO - figure out if this is a ole2 specific thing, or + * if we need to do something proper here too! + */ + public int getSheetIndexFromExternSheetIndex(int externSheetNumber) { + return externSheetNumber; + } - public Sheet getSheet(String name) { + public Sheet getSheet(String name) { CTSheet[] sheets = this.workbook.getSheets().getSheetArray(); for (int i = 0 ; i < sheets.length ; ++i) { if (name.equals(sheets[i].getName())) { diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParserEval.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParserEval.java index 7419734535..b200515ff9 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParserEval.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParserEval.java @@ -29,7 +29,7 @@ import org.apache.poi.hssf.usermodel.HSSFName; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; /** * Test the low level formula parser functionality, diff --git a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java index 72db658f77..97ddfdf220 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java @@ -25,7 +25,7 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; /** * Tests HSSFFormulaEvaluator for its handling of cell formula circular references. * diff --git a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java index 27e3338652..647b6330f2 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java @@ -25,7 +25,7 @@ import org.apache.poi.hssf.usermodel.HSSFName; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; /** * * @author Josh Micich diff --git a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulaBugs.java b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulaBugs.java index 617f5d0d4e..a833b77c0d 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulaBugs.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulaBugs.java @@ -32,7 +32,7 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; /** * Miscellaneous tests for bugzilla entries.

The test name contains the diff --git a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java index 2d5408c76a..4b079bcce6 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java @@ -30,6 +30,8 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.FormulaEvaluator; /** * Tests formulas and operators as loaded from a test data spreadsheet.

@@ -104,7 +106,7 @@ public final class TestFormulasFromSpreadsheet extends TestCase { } - private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) { + private static void confirmExpectedResult(String msg, Cell expected, FormulaEvaluator.CellValue actual) { if (expected == null) { throw new AssertionFailedError(msg + " - Bad setup data expected value is null"); } @@ -249,7 +251,7 @@ public final class TestFormulasFromSpreadsheet extends TestCase { continue; } - HSSFFormulaEvaluator.CellValue actualValue = evaluator.evaluate(c); + FormulaEvaluator.CellValue actualValue = evaluator.evaluate(c); HSSFCell expectedValueCell = getExpectedValueCell(expectedValuesRow, colnum); try { diff --git a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java index be8cef13fa..c1daa094cc 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java @@ -27,7 +27,7 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; /** * Test for percent operator evaluator. diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java index 7ce2bd245b..90389eeaa8 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java @@ -24,7 +24,7 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; /** * Tests for Excel function ISBLANK() * diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java index 071ca0f7d8..958654b060 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java @@ -32,7 +32,7 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; import org.apache.poi.hssf.util.CellReference; /** @@ -90,7 +90,7 @@ public final class TestLookupFunctionsFromSpreadsheet extends TestCase { - private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) { + private static void confirmExpectedResult(String msg, HSSFCell expected, CellValue actual) { if (expected == null) { throw new AssertionFailedError(msg + " - Bad setup data expected value is null"); } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java index c849fd4369..f137ef6e9a 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java @@ -25,7 +25,7 @@ import junit.framework.TestCase; import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; -import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; import org.apache.poi.hssf.util.CellReference; public final class TestBug42464 extends TestCase {