diff --git a/src/java/org/apache/poi/hssf/dev/FormulaViewer.java b/src/java/org/apache/poi/hssf/dev/FormulaViewer.java index 167eaba53..d42cda71a 100644 --- a/src/java/org/apache/poi/hssf/dev/FormulaViewer.java +++ b/src/java/org/apache/poi/hssf/dev/FormulaViewer.java @@ -20,7 +20,7 @@ package org.apache.poi.hssf.dev; import java.io.FileInputStream; import java.util.List; -import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RecordFactory; @@ -181,7 +181,7 @@ public class FormulaViewer private static String composeFormula(FormulaRecord record) { - return FormulaParser.toFormulaString((HSSFWorkbook)null, record.getParsedExpression()); + return HSSFFormulaParser.toFormulaString((HSSFWorkbook)null, record.getParsedExpression()); } /** diff --git a/src/java/org/apache/poi/hssf/eventusermodel/EventWorkbookBuilder.java b/src/java/org/apache/poi/hssf/eventusermodel/EventWorkbookBuilder.java index 3c767dc25..c7a1471a7 100644 --- a/src/java/org/apache/poi/hssf/eventusermodel/EventWorkbookBuilder.java +++ b/src/java/org/apache/poi/hssf/eventusermodel/EventWorkbookBuilder.java @@ -19,7 +19,7 @@ package org.apache.poi.hssf.eventusermodel; import java.util.ArrayList; import java.util.List; -import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.record.BoundSheetRecord; import org.apache.poi.hssf.record.EOFRecord; @@ -33,7 +33,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; * When working with the EventUserModel, if you want to * process formulas, you need an instance of * {@link Workbook} to pass to a {@link HSSFWorkbook}, - * to finally give to {@link FormulaParser}, + * to finally give to {@link HSSFFormulaParser}, * and this will build you stub ones. * Since you're working with the EventUserModel, you * wouldn't want to get a full {@link Workbook} and @@ -41,7 +41,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; * Instead, you should collect a few key records as they * go past, then call this once you have them to build a * stub {@link Workbook}, and from that a stub - * {@link HSSFWorkbook}, to use with the {@link FormulaParser}. + * {@link HSSFWorkbook}, to use with the {@link HSSFFormulaParser}. * * The records you should collect are: * * {@link ExternSheetRecord} @@ -56,7 +56,7 @@ public class EventWorkbookBuilder { /** * Wraps up your stub {@link Workbook} as a stub * {@link HSSFWorkbook}, ready for passing to - * {@link FormulaParser} + * {@link HSSFFormulaParser} * @param workbook A stub {@link Workbook} */ public static HSSFWorkbook createStubHSSFWorkbook(Workbook workbook) { @@ -65,11 +65,11 @@ public class EventWorkbookBuilder { /** * Creates a stub Workbook from the supplied records, - * suitable for use with the {@link FormulaParser} + * suitable for use with the {@link HSSFFormulaParser} * @param externs The ExternSheetRecords in your file * @param bounds The BoundSheetRecords in your file * @param sst The SSTRecord in your file. - * @return A stub Workbook suitable for use with {@link FormulaParser} + * @return A stub Workbook suitable for use with {@link HSSFFormulaParser} */ public static Workbook createStubWorkbook(ExternSheetRecord[] externs, BoundSheetRecord[] bounds, SSTRecord sst) { @@ -103,10 +103,10 @@ public class EventWorkbookBuilder { /** * Creates a stub workbook from the supplied records, - * suitable for use with the {@link FormulaParser} + * suitable for use with the {@link HSSFFormulaParser} * @param externs The ExternSheetRecords in your file * @param bounds The BoundSheetRecords in your file - * @return A stub Workbook suitable for use with {@link FormulaParser} + * @return A stub Workbook suitable for use with {@link HSSFFormulaParser} */ public static Workbook createStubWorkbook(ExternSheetRecord[] externs, BoundSheetRecord[] bounds) { diff --git a/src/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java b/src/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java index 2ea35c773..430dcd475 100644 --- a/src/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java +++ b/src/java/org/apache/poi/hssf/extractor/EventBasedExcelExtractor.java @@ -32,7 +32,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; import org.apache.poi.hssf.eventusermodel.HSSFListener; import org.apache.poi.hssf.eventusermodel.HSSFRequest; -import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.record.BOFRecord; import org.apache.poi.hssf.record.BoundSheetRecord; import org.apache.poi.hssf.record.CellValueRecordInterface; @@ -45,6 +45,7 @@ import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.SSTRecord; import org.apache.poi.hssf.record.StringRecord; import org.apache.poi.hssf.usermodel.HSSFDateUtil; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.poifs.filesystem.POIFSFileSystem; /** @@ -177,7 +178,7 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor { thisRow = frec.getRow(); if(formulasNotResults) { - thisText = FormulaParser.toFormulaString(null, frec.getParsedExpression()); + thisText = HSSFFormulaParser.toFormulaString((HSSFWorkbook)null, frec.getParsedExpression()); } else { if(frec.hasCachedResultString()) { // Formula result is a string diff --git a/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java b/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java new file mode 100644 index 000000000..c492c5e86 --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java @@ -0,0 +1,72 @@ +/* ==================================================================== + 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.model; + +import java.util.List; + +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.FormulaParser; +import org.apache.poi.ss.formula.FormulaParsingWorkbook; +import org.apache.poi.ss.formula.FormulaRenderer; +import org.apache.poi.ss.formula.FormulaRenderingWorkbook; + +/** + * HSSF wrapper for the {@link FormulaParser} + * + * @author Josh Micich + */ +public final class HSSFFormulaParser { + + private static FormulaParsingWorkbook createParsingWorkbook(HSSFWorkbook book) { + return HSSFEvaluationWorkbook.create(book); + } + + private HSSFFormulaParser() { + // no instances of this class + } + + public static Ptg[] parse(String formula, HSSFWorkbook workbook) { + return FormulaParser.parse(formula, createParsingWorkbook(workbook)); + } + + public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType) { + return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType); + } + + public static String toFormulaString(HSSFWorkbook book, List lptgs) { + return toFormulaString(HSSFEvaluationWorkbook.create(book), lptgs); + } + /** + * Convenience method which takes in a list then passes it to the + * other toFormulaString signature. + * @param book workbook for 3D and named references + * @param lptgs list of Ptg, can be null or empty + * @return a human readable String + */ + public static String toFormulaString(FormulaRenderingWorkbook book, List lptgs) { + Ptg[] ptgs = new Ptg[lptgs.size()]; + lptgs.toArray(ptgs); + return FormulaRenderer.toFormulaString(book, ptgs); + } + + public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) { + return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs); + } +} diff --git a/src/java/org/apache/poi/hssf/record/CFRuleRecord.java b/src/java/org/apache/poi/hssf/record/CFRuleRecord.java index 06256c5cc..cca4e6c65 100644 --- a/src/java/org/apache/poi/hssf/record/CFRuleRecord.java +++ b/src/java/org/apache/poi/hssf/record/CFRuleRecord.java @@ -17,7 +17,7 @@ package org.apache.poi.hssf.record; -import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.record.cf.BorderFormatting; import org.apache.poi.hssf.record.cf.FontFormatting; import org.apache.poi.hssf.record.cf.PatternFormatting; @@ -595,6 +595,6 @@ public final class CFRuleRecord extends Record { if(formula == null) { return null; } - return FormulaParser.parse(formula, workbook); + return HSSFFormulaParser.parse(formula, workbook); } } diff --git a/src/java/org/apache/poi/hssf/record/NameRecord.java b/src/java/org/apache/poi/hssf/record/NameRecord.java index d36124e7b..dc301bca9 100644 --- a/src/java/org/apache/poi/hssf/record/NameRecord.java +++ b/src/java/org/apache/poi/hssf/record/NameRecord.java @@ -20,7 +20,7 @@ package org.apache.poi.hssf.record; import java.util.ArrayList; import java.util.List; -import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.record.formula.Area3DPtg; import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ref3DPtg; @@ -495,7 +495,7 @@ public final class NameRecord extends Record { * @return area reference */ public String getAreaReference(HSSFWorkbook book){ - return FormulaParser.toFormulaString(book, field_13_name_definition); + return HSSFFormulaParser.toFormulaString(book, field_13_name_definition); } /** sets the reference , the area only (range) diff --git a/src/java/org/apache/poi/hssf/record/formula/atp/AnalysisToolPak.java b/src/java/org/apache/poi/hssf/record/formula/atp/AnalysisToolPak.java index f528fb67b..ca9dd7d3e 100644 --- a/src/java/org/apache/poi/hssf/record/formula/atp/AnalysisToolPak.java +++ b/src/java/org/apache/poi/hssf/record/formula/atp/AnalysisToolPak.java @@ -24,13 +24,13 @@ 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.record.formula.functions.FreeRefFunction; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.EvaluationWorkbook; public final class AnalysisToolPak { private static final FreeRefFunction NotImplemented = new FreeRefFunction() { - public ValueEval evaluate(Eval[] args, HSSFWorkbook workbook, int srcCellSheet, + public ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol) { return ErrorEval.FUNCTION_NOT_IMPLEMENTED; } diff --git a/src/java/org/apache/poi/hssf/record/formula/atp/ParityFunction.java b/src/java/org/apache/poi/hssf/record/formula/atp/ParityFunction.java index a6dd10a84..b907d89ea 100644 --- a/src/java/org/apache/poi/hssf/record/formula/atp/ParityFunction.java +++ b/src/java/org/apache/poi/hssf/record/formula/atp/ParityFunction.java @@ -17,7 +17,6 @@ package org.apache.poi.hssf.record.formula.atp; -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; @@ -25,8 +24,7 @@ import org.apache.poi.hssf.record.formula.eval.EvaluationException; import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.ValueEval; 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.formula.EvaluationWorkbook; /** * Implementation of Excel 'Analysis ToolPak' function ISEVEN() ISODD()
* @@ -42,7 +40,7 @@ final class ParityFunction implements FreeRefFunction { _desiredParity = desiredParity; } - public ValueEval evaluate(Eval[] args, HSSFWorkbook workbook, int srcCellSheet, int srcCellRow, + public ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol) { if (args.length != 1) { return ErrorEval.VALUE_INVALID; diff --git a/src/java/org/apache/poi/hssf/record/formula/atp/YearFrac.java b/src/java/org/apache/poi/hssf/record/formula/atp/YearFrac.java index f48eba8d5..05c14f368 100644 --- a/src/java/org/apache/poi/hssf/record/formula/atp/YearFrac.java +++ b/src/java/org/apache/poi/hssf/record/formula/atp/YearFrac.java @@ -21,7 +21,6 @@ import java.util.Calendar; import java.util.GregorianCalendar; import java.util.regex.Pattern; -import org.apache.poi.hssf.record.formula.eval.BlankEval; 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.EvaluationException; @@ -31,8 +30,7 @@ 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.functions.FreeRefFunction; import org.apache.poi.hssf.usermodel.HSSFDateUtil; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.EvaluationWorkbook; /** * Implementation of Excel 'Analysis ToolPak' function YEARFRAC()
* @@ -61,7 +59,7 @@ final class YearFrac implements FreeRefFunction { // enforce singleton } - public ValueEval evaluate(Eval[] args, HSSFWorkbook workbook, int srcCellSheet, int srcCellRow, + public ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol) { double result; 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 96cc64310..8e8a9799e 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 @@ -19,7 +19,7 @@ package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.atp.AnalysisToolPak; import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.EvaluationWorkbook; /** * * Common entry point for all user-defined (non-built-in) functions (where @@ -30,7 +30,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; */ final class ExternalFunction implements FreeRefFunction { - public ValueEval evaluate(Eval[] args, HSSFWorkbook workbook, + public ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow,int srcCellCol) { int nIncomingArgs = args.length; @@ -58,9 +58,9 @@ final class ExternalFunction implements FreeRefFunction { return targetFunc.evaluate(outGoingArgs, workbook, srcCellSheet, srcCellRow, srcCellCol); } - private FreeRefFunction findExternalUserDefinedFunction(HSSFWorkbook workbook, + private FreeRefFunction findExternalUserDefinedFunction(EvaluationWorkbook workbook, NameXEval n) throws EvaluationException { - String functionName = workbook.resolveNameXText(n.getSheetRefIndex(), n.getNameNumber()); + String functionName = workbook.resolveNameXText(n.getPtg()); if(false) { System.out.println("received call to external user defined function (" + functionName + ")"); diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/NameXEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/NameXEval.java index 12b6be380..b67f4eb3d 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/NameXEval.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/NameXEval.java @@ -17,32 +17,27 @@ package org.apache.poi.hssf.record.formula.eval; +import org.apache.poi.hssf.record.formula.NameXPtg; + /** * @author Josh Micich */ public final class NameXEval implements Eval { - /** index to REF entry in externsheet record */ - private final int _sheetRefIndex; - /** index to defined name or externname table(1 based) */ - private final int _nameNumber; + private final NameXPtg _ptg; - public NameXEval(int sheetRefIndex, int nameNumber) { - _sheetRefIndex = sheetRefIndex; - _nameNumber = nameNumber; + public NameXEval(NameXPtg ptg) { + _ptg = ptg; } - public int getSheetRefIndex() { - return _sheetRefIndex; - } - public int getNameNumber() { - return _nameNumber; + public NameXPtg getPtg() { + return _ptg; } public String toString() { StringBuffer sb = new StringBuffer(64); sb.append(getClass().getName()).append(" ["); - sb.append(_sheetRefIndex).append(", ").append(_nameNumber); + sb.append(_ptg.getSheetRefIndex()).append(", ").append(_ptg.getNameIndex()); sb.append("]"); return sb.toString(); } 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 977c1b363..cdec045b4 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,7 @@ 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.formula.EvaluationWorkbook; /** @@ -53,5 +52,5 @@ public interface FreeRefFunction { * a specified Excel error (Exceptions are never thrown to represent Excel errors). * */ - ValueEval evaluate(Eval[] args, HSSFWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol); + ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol); } 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 f4036c107..567f29b2b 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 @@ -1,27 +1,26 @@ -/* -* 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. -*/ +/* ==================================================================== + 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.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.formula.EvaluationWorkbook; /** * Implementation for Excel function INDIRECT

@@ -41,7 +40,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; */ public final class Indirect implements FreeRefFunction { - public ValueEval evaluate(Eval[] args, HSSFWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol) { + public ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol) { // TODO - implement INDIRECT() return ErrorEval.FUNCTION_NOT_IMPLEMENTED; } diff --git a/src/java/org/apache/poi/hssf/usermodel/DVConstraint.java b/src/java/org/apache/poi/hssf/usermodel/DVConstraint.java index a1027ccfa..5bc478f42 100644 --- a/src/java/org/apache/poi/hssf/usermodel/DVConstraint.java +++ b/src/java/org/apache/poi/hssf/usermodel/DVConstraint.java @@ -20,10 +20,12 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; -import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.record.formula.NumberPtg; import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.StringPtg; +import org.apache.poi.ss.formula.FormulaParser; +import org.apache.poi.ss.formula.FormulaType; /** * @@ -339,7 +341,7 @@ public class DVConstraint { if (_explicitListValues == null) { // formula is parsed with slightly different RVA rules: (root node type must be 'reference') - return FormulaParser.parse(_formula1, workbook, FormulaParser.FORMULA_TYPE_DATAVALIDATION_LIST); + return HSSFFormulaParser.parse(_formula1, workbook, FormulaType.DATAVALIDATION_LIST); // To do: Excel places restrictions on the available operations within a list formula. // Some things like union and intersection are not allowed. } @@ -369,7 +371,7 @@ public class DVConstraint { if (value != null) { throw new IllegalStateException("Both formula and value cannot be present"); } - return FormulaParser.parse(formula, workbook); + return HSSFFormulaParser.parse(formula, workbook); } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 9f12f4305..3d2f99f41 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -25,7 +25,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; -import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.record.BlankRecord; @@ -124,7 +124,7 @@ public final class HSSFCell { short xfindex = sheet.getSheet().getXFIndexForColAt(col); setCellType(CELL_TYPE_BLANK, false, row, col,xfindex); } - /* package */ HSSFSheet getSheet() { + public HSSFSheet getSheet() { return sheet; } @@ -589,12 +589,12 @@ public final class HSSFCell { if (rec.getXFIndex() == (short)0) { rec.setXFIndex((short) 0x0f); } - Ptg[] ptgs = FormulaParser.parse(formula, book); + Ptg[] ptgs = HSSFFormulaParser.parse(formula, book); frec.setParsedExpression(ptgs); } public String getCellFormula() { - return FormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression()); + return HSSFFormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression()); } /** diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java b/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java index fc34b9da9..29430b094 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java @@ -17,7 +17,7 @@ package org.apache.poi.hssf.usermodel; -import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.record.CFRuleRecord; import org.apache.poi.hssf.record.CFRuleRecord.ComparisonOperator; import org.apache.poi.hssf.record.cf.BorderFormatting; @@ -205,6 +205,6 @@ public final class HSSFConditionalFormattingRule if(parsedExpression ==null) { return null; } - return FormulaParser.toFormulaString(workbook, parsedExpression); + return HSSFFormulaParser.toFormulaString(workbook, parsedExpression); } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java index f9399ee8c..16ef3b2a9 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java @@ -1,8 +1,14 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.model.Workbook; +import org.apache.poi.hssf.record.NameRecord; import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NameXPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.ss.formula.EvaluationName; +import org.apache.poi.ss.formula.EvaluationWorkbook; +import org.apache.poi.ss.formula.FormulaParsingWorkbook; import org.apache.poi.ss.formula.FormulaRenderingWorkbook; /** @@ -10,8 +16,9 @@ import org.apache.poi.ss.formula.FormulaRenderingWorkbook; * * @author Josh Micich */ -public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook { +public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook { + private final HSSFWorkbook _uBook; private final Workbook _iBook; public static HSSFEvaluationWorkbook create(HSSFWorkbook book) { @@ -22,9 +29,58 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook { } private HSSFEvaluationWorkbook(HSSFWorkbook book) { + _uBook = book; _iBook = book.getWorkbook(); } + public int getExternalSheetIndex(String sheetName) { + int sheetIndex = _uBook.getSheetIndex(sheetName); + return _iBook.checkExternSheet(sheetIndex); + } + + public EvaluationName getName(int index) { + return new Name(_iBook.getNameRecord(index), index); + } + + public EvaluationName getName(String name) { + for(int i=0; i < _iBook.getNumNames(); i++) { + NameRecord nr = _iBook.getNameRecord(i); + if (name.equalsIgnoreCase(nr.getNameText())) { + return new Name(nr, i); + } + } + return null; + } + + public int getSheetIndex(HSSFSheet sheet) { + return _uBook.getSheetIndex(sheet); + } + + public String getSheetName(int sheetIndex) { + return _uBook.getSheetName(sheetIndex); + } + + public int getNameIndex(String name) { + return _uBook.getNameIndex(name); + } + + public NameXPtg getNameXPtg(String name) { + return _iBook.getNameXPtg(name); + } + + public HSSFSheet getSheet(int sheetIndex) { + return _uBook.getSheetAt(sheetIndex); + } + + public HSSFSheet getSheetByExternSheetIndex(int externSheetIndex) { + int sheetIndex = _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex); + return _uBook.getSheetAt(sheetIndex); + } + + public HSSFWorkbook getWorkbook() { + return _uBook; + } + public String resolveNameXText(NameXPtg n) { return _iBook.resolveNameXText(n.getSheetRefIndex(), n.getNameIndex()); } @@ -35,4 +91,45 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook { public String getNameText(NamePtg namePtg) { return _iBook.getNameRecord(namePtg.getIndex()).getNameText(); } + public EvaluationName getName(NamePtg namePtg) { + int ix = namePtg.getIndex(); + return new Name(_iBook.getNameRecord(ix), ix); + } + public Ptg[] getFormulaTokens(HSSFCell cell) { + return HSSFFormulaParser.parse(cell.getCellFormula(), _uBook); + } + + private static final class Name implements EvaluationName { + + private final NameRecord _nameRecord; + private final int _index; + + public Name(NameRecord nameRecord, int index) { + _nameRecord = nameRecord; + _index = index; + } + + public Ptg[] getNameDefinition() { + return _nameRecord.getNameDefinition(); + } + + public String getNameText() { + return _nameRecord.getNameText(); + } + + public boolean hasFormula() { + return _nameRecord.hasFormula(); + } + + public boolean isFunctionName() { + return _nameRecord.isFunctionName(); + } + + public boolean isRange() { + return _nameRecord.hasFormula(); // TODO - is this right? + } + public NamePtg createPtg() { + return new NamePtg(_index); + } + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java index 0e6e49338..1e6aa163c 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java @@ -1,703 +1,368 @@ -/* ==================================================================== - 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.Iterator; -import java.util.Stack; - -import org.apache.poi.hssf.model.FormulaParser; -import org.apache.poi.hssf.model.Workbook; -import org.apache.poi.hssf.record.NameRecord; -import org.apache.poi.hssf.record.formula.Area3DPtg; -import org.apache.poi.hssf.record.formula.AreaPtg; -import org.apache.poi.hssf.record.formula.BoolPtg; -import org.apache.poi.hssf.record.formula.ControlPtg; -import org.apache.poi.hssf.record.formula.ErrPtg; -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.Ptg; -import org.apache.poi.hssf.record.formula.Ref3DPtg; -import org.apache.poi.hssf.record.formula.RefPtg; -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.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.NameXEval; -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.RefEval; -import org.apache.poi.hssf.record.formula.eval.StringEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; -import org.apache.poi.hssf.util.CellReference; - -/** - * Evaluates formula cells.

- * - * For performance reasons, this class keeps a cache of all previously calculated intermediate - * cell values. Be sure to call {@link #clearCache()} if any workbook cells are changed between - * calls to evaluate~ methods on this class. - * - * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * @author Josh Micich - */ -public class HSSFFormulaEvaluator { - - /** - * used to track the number of evaluations - */ - private static final class Counter { - public int value; - public int depth; - public Counter() { - value = 0; - } - } - - private final HSSFWorkbook _workbook; - private final EvaluationCache _cache; - - private Counter _evaluationCounter; - - /** - * @deprecated (Sep 2008) HSSFSheet parameter is ignored - */ - public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) { - this(workbook); - if (false) { - sheet.toString(); // suppress unused parameter compiler warning - } - } - public HSSFFormulaEvaluator(HSSFWorkbook workbook) { - this(workbook, new EvaluationCache(), new Counter()); - } - - private HSSFFormulaEvaluator(HSSFWorkbook workbook, EvaluationCache cache, Counter evaluationCounter) { - _workbook = workbook; - _cache = cache; - _evaluationCounter = evaluationCounter; - } - - /** - * for debug use. Used in toString methods - */ - /* package */ String getSheetName(HSSFSheet sheet) { - return _workbook.getSheetName(_workbook.getSheetIndex(sheet)); - } - /** - * for debug/test use - */ - /* package */ int getEvaluationCount() { - return _evaluationCounter.value; - } - - private static boolean isDebugLogEnabled() { - return false; - } - private static void logDebug(String s) { - if (isDebugLogEnabled()) { - System.out.println(s); - } - } - - /** - * Does nothing - * @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell - */ - public void setCurrentRow(HSSFRow row) { - // do nothing - if (false) { - row.getClass(); // suppress unused parameter compiler warning - } - } - - /** - * Should be called whenever there are changes to input cells in the evaluated workbook. - * Failure to call this method after changing cell values will cause incorrect behaviour - * of the evaluate~ methods of this class - */ - public void clearCache() { - _cache.clear(); - } - - /** - * Returns an underlying FormulaParser, for the specified - * Formula String and HSSFWorkbook. - * This will allow you to generate the Ptgs yourself, if - * your needs are more complex than just having the - * formula evaluated. - */ - public static FormulaParser getUnderlyingParser(HSSFWorkbook workbook, String formula) { - return new FormulaParser(formula, workbook); - } - - /** - * 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) { - if (cell == null) { - return null; - } - - switch (cell.getCellType()) { - case HSSFCell.CELL_TYPE_BOOLEAN: - return CellValue.valueOf(cell.getBooleanCellValue()); - case HSSFCell.CELL_TYPE_ERROR: - return CellValue.getError(cell.getErrorCellValue()); - case HSSFCell.CELL_TYPE_FORMULA: - return evaluateFormulaCellValue(cell); - case HSSFCell.CELL_TYPE_NUMERIC: - return new CellValue(cell.getNumericCellValue()); - case HSSFCell.CELL_TYPE_STRING: - return new CellValue(cell.getRichStringCellValue().getString()); - } - throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")"); - } - - - /** - * 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 || cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { - return -1; - } - CellValue cv = evaluateFormulaCellValue(cell); - // cell remains a formula cell, but the cached value is changed - setCellValue(cell, cv); - return cv.getCellType(); - } - - /** - * 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) { - return null; - } - if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) { - CellValue cv = evaluateFormulaCellValue(cell); - setCellType(cell, cv); // cell will no longer be a formula cell - setCellValue(cell, cv); - } - return cell; - } - private static void setCellType(HSSFCell cell, CellValue cv) { - int cellType = cv.getCellType(); - switch (cellType) { - case HSSFCell.CELL_TYPE_BOOLEAN: - case HSSFCell.CELL_TYPE_ERROR: - case HSSFCell.CELL_TYPE_NUMERIC: - case HSSFCell.CELL_TYPE_STRING: - cell.setCellType(cellType); - return; - case HSSFCell.CELL_TYPE_BLANK: - // never happens - blanks eventually get translated to zero - case HSSFCell.CELL_TYPE_FORMULA: - // this will never happen, we have already evaluated the formula - } - throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); - } - - private static void setCellValue(HSSFCell cell, CellValue cv) { - int cellType = cv.getCellType(); - switch (cellType) { - case HSSFCell.CELL_TYPE_BOOLEAN: - cell.setCellValue(cv.getBooleanValue()); - break; - case HSSFCell.CELL_TYPE_ERROR: - cell.setCellErrorValue(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: - // never happens - blanks eventually get translated to zero - case HSSFCell.CELL_TYPE_FORMULA: - // this will never happen, we have already evaluated the formula - default: - throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); - } - } - - /** - * Loops over all cells in all sheets of the supplied - * workbook. - * For cells that contain formulas, their formulas are - * evaluated, and the results are saved. These cells - * remain as formula cells. - * For cells that do not contain formulas, no changes - * are made. - * This is a helpful wrapper around looping over all - * cells, and calling evaluateFormulaCell on each one. - */ - public static void evaluateAllFormulaCells(HSSFWorkbook wb) { - HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb); - for(int i=0; inull, never {@link BlankEval} - */ - private ValueEval internalEvaluate(HSSFCell srcCell) { - int srcRowNum = srcCell.getRowIndex(); - int srcColNum = srcCell.getCellNum(); - - ValueEval result; - - int sheetIndex = _workbook.getSheetIndex(srcCell.getSheet()); - result = _cache.getValue(sheetIndex, srcRowNum, srcColNum); - if (result != null) { - return result; - } - _evaluationCounter.value++; - _evaluationCounter.depth++; - - EvaluationCycleDetector tracker = EvaluationCycleDetectorManager.getTracker(); - - if(!tracker.startEvaluate(_workbook, sheetIndex, srcRowNum, srcColNum)) { - return ErrorEval.CIRCULAR_REF_ERROR; - } - try { - result = evaluateCell(sheetIndex, srcRowNum, (short)srcColNum, srcCell.getCellFormula()); - } finally { - tracker.endEvaluate(_workbook, sheetIndex, srcRowNum, srcColNum); - _cache.setValue(sheetIndex, srcRowNum, srcColNum, result); - _evaluationCounter.depth--; - } - if (isDebugLogEnabled()) { - String sheetName = _workbook.getSheetName(sheetIndex); - CellReference cr = new CellReference(srcRowNum, srcColNum); - logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + result.toString()); - } - return result; - } - private ValueEval evaluateCell(int sheetIndex, int srcRowNum, short srcColNum, String cellFormulaText) { - - Ptg[] ptgs = FormulaParser.parse(cellFormulaText, _workbook); - - Stack stack = new Stack(); - for (int i = 0, iSize = ptgs.length; i < iSize; i++) { - - // since we don't know how to handle these yet :( - Ptg ptg = ptgs[i]; - if (ptg instanceof ControlPtg) { - // skip Parentheses, Attr, etc - continue; - } - if (ptg instanceof MemErrPtg) { continue; } - if (ptg instanceof MissingArgPtg) { - // TODO - might need to push BlankEval or MissingArgEval - continue; - } - Eval opResult; - if (ptg instanceof OperationPtg) { - OperationPtg optg = (OperationPtg) ptg; - - if (optg instanceof UnionPtg) { continue; } - - OperationEval operation = OperationEvaluatorFactory.create(optg); - - int numops = operation.getNumberOfOperands(); - Eval[] ops = new Eval[numops]; - - // storing the ops in reverse order since they are popping - for (int j = numops - 1; j >= 0; j--) { - Eval p = (Eval) stack.pop(); - ops[j] = p; - } -// logDebug("invoke " + operation + " (nAgs=" + numops + ")"); - opResult = invokeOperation(operation, ops, _workbook, sheetIndex, srcRowNum, srcColNum); - } else { - opResult = getEvalForPtg(ptg, sheetIndex); - } - if (opResult == null) { - throw new RuntimeException("Evaluation result must not be null"); - } -// logDebug("push " + opResult); - stack.push(opResult); - } - - ValueEval value = ((ValueEval) stack.pop()); - if (!stack.isEmpty()) { - throw new IllegalStateException("evaluation stack not empty"); - } - value = dereferenceValue(value, srcRowNum, srcColNum); - if (value == BlankEval.INSTANCE) { - // 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.getRelativeValue(0, 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, - HSSFWorkbook workbook, int sheetIndex, int srcRowNum, int srcColNum) { - - if(operation instanceof FunctionEval) { - FunctionEval fe = (FunctionEval) operation; - if(fe.isFreeRefFunction()) { - return fe.getFreeRefFunction().evaluate(ops, workbook, sheetIndex, srcRowNum, srcColNum); - } - } - return operation.evaluate(ops, srcRowNum, (short)srcColNum); - } - - private HSSFSheet getOtherSheet(int externSheetIndex) { - Workbook wb = _workbook.getWorkbook(); - return _workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(externSheetIndex)); - } - - /** - * 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! - */ - private Eval getEvalForPtg(Ptg ptg, int sheetIndex) { - if (ptg instanceof NamePtg) { - // named ranges, macro functions - NamePtg namePtg = (NamePtg) ptg; - int numberOfNames = _workbook.getNumberOfNames(); - int nameIndex = namePtg.getIndex(); - if(nameIndex < 0 || nameIndex >= numberOfNames) { - throw new RuntimeException("Bad name index (" + nameIndex - + "). Allowed range is (0.." + (numberOfNames-1) + ")"); - } - NameRecord nameRecord = _workbook.getWorkbook().getNameRecord(nameIndex); - if (nameRecord.isFunctionName()) { - return new NameEval(nameRecord.getNameText()); - } - if (nameRecord.hasFormula()) { - return evaluateNameFormula(nameRecord.getNameDefinition(), sheetIndex); - } - - throw new RuntimeException("Don't now how to evalate name '" + nameRecord.getNameText() + "'"); - } - if (ptg instanceof NameXPtg) { - NameXPtg nameXPtg = (NameXPtg) ptg; - return new NameXEval(nameXPtg.getSheetRefIndex(), nameXPtg.getNameIndex()); - } - - if (ptg instanceof IntPtg) { - return new NumberEval(((IntPtg)ptg).getValue()); - } - if (ptg instanceof NumberPtg) { - return new NumberEval(((NumberPtg)ptg).getValue()); - } - if (ptg instanceof StringPtg) { - return new StringEval(((StringPtg) ptg).getValue()); - } - if (ptg instanceof BoolPtg) { - return BoolEval.valueOf(((BoolPtg) ptg).getValue()); - } - if (ptg instanceof ErrPtg) { - return ErrorEval.valueOf(((ErrPtg) ptg).getErrorCode()); - } - HSSFSheet sheet = _workbook.getSheetAt(sheetIndex); - if (ptg instanceof RefPtg) { - return new LazyRefEval(((RefPtg) ptg), sheet, this); - } - if (ptg instanceof AreaPtg) { - return new LazyAreaEval(((AreaPtg) ptg), sheet, this); - } - if (ptg instanceof Ref3DPtg) { - Ref3DPtg refPtg = (Ref3DPtg) ptg; - HSSFSheet xsheet = getOtherSheet(refPtg.getExternSheetIndex()); - return new LazyRefEval(refPtg, xsheet, this); - } - if (ptg instanceof Area3DPtg) { - Area3DPtg a3dp = (Area3DPtg) ptg; - HSSFSheet xsheet = getOtherSheet(a3dp.getExternSheetIndex()); - return new LazyAreaEval(a3dp, xsheet, this); - } - - if (ptg instanceof UnknownPtg) { - // POI uses UnknownPtg when the encoded Ptg array seems to be corrupted. - // This seems to occur in very rare cases (e.g. unused name formulas in bug 44774, attachment 21790) - // In any case, formulas are re-parsed before execution, so UnknownPtg should not get here - throw new RuntimeException("UnknownPtg not allowed"); - } - - throw new RuntimeException("Unexpected ptg class (" + ptg.getClass().getName() + ")"); - } - private Eval evaluateNameFormula(Ptg[] ptgs, int sheetIndex) { - if (ptgs.length > 1) { - throw new RuntimeException("Complex name formulas not supported yet"); - } - return getEvalForPtg(ptgs[0], sheetIndex); - } - - /** - * 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. - */ - /* package */ ValueEval getEvalForCell(HSSFCell cell) { - - 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); - 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() + ")"); - } - - /** - * 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 { - public static final CellValue TRUE = new CellValue(HSSFCell.CELL_TYPE_BOOLEAN, 0.0, true, null, 0); - public static final CellValue FALSE= new CellValue(HSSFCell.CELL_TYPE_BOOLEAN, 0.0, false, null, 0); - - private final int _cellType; - private final double _numberValue; - private final boolean _booleanValue; - private final String _textValue; - private final int _errorCode; - - private CellValue(int cellType, double numberValue, boolean booleanValue, - String textValue, int errorCode) { - _cellType = cellType; - _numberValue = numberValue; - _booleanValue = booleanValue; - _textValue = textValue; - _errorCode = errorCode; - } - - - /* package*/ CellValue(double numberValue) { - this(HSSFCell.CELL_TYPE_NUMERIC, numberValue, false, null, 0); - } - /* package*/ static CellValue valueOf(boolean booleanValue) { - return booleanValue ? TRUE : FALSE; - } - /* package*/ CellValue(String stringValue) { - this(HSSFCell.CELL_TYPE_STRING, 0.0, false, stringValue, 0); - } - /* package*/ static CellValue getError(int errorCode) { - return new CellValue(HSSFCell.CELL_TYPE_ERROR, 0.0, false, null, errorCode); - } - - - /** - * @return Returns the booleanValue. - */ - public boolean getBooleanValue() { - return _booleanValue; - } - /** - * @return Returns the numberValue. - */ - public double getNumberValue() { - return _numberValue; - } - /** - * @return Returns the stringValue. - */ - public String getStringValue() { - return _textValue; - } - /** - * @return Returns the cellType. - */ - public int getCellType() { - return _cellType; - } - /** - * @return Returns the errorValue. - */ - public byte getErrorValue() { - return (byte) _errorCode; - } - /** - * @return Returns the richTextStringValue. - * @deprecated (Sep 2008) Text formatting is lost during formula evaluation. Use {@link #getStringValue()} - */ - public HSSFRichTextString getRichTextStringValue() { - return new HSSFRichTextString(_textValue); - } - public String toString() { - StringBuffer sb = new StringBuffer(64); - sb.append(getClass().getName()).append(" ["); - sb.append(formatAsString()); - sb.append("]"); - return sb.toString(); - } - - public String formatAsString() { - switch (_cellType) { - case HSSFCell.CELL_TYPE_NUMERIC: - return String.valueOf(_numberValue); - case HSSFCell.CELL_TYPE_STRING: - return '"' + _textValue + '"'; - case HSSFCell.CELL_TYPE_BOOLEAN: - return _booleanValue ? "TRUE" : "FALSE"; - case HSSFCell.CELL_TYPE_ERROR: - return ErrorEval.getText(_errorCode); - } - return ""; - } - } - - /** - * debug method - */ - void inspectPtgs(String formula) { - Ptg[] ptgs = FormulaParser.parse(formula, _workbook); - System.out.println(""); - for (int i = 0, iSize = ptgs.length; i < iSize; i++) { - System.out.println(""); - System.out.println(ptgs[i]); - if (ptgs[i] instanceof OperationPtg) { - System.out.println("numoperands: " + ((OperationPtg) ptgs[i]).getNumberOfOperands()); - } - System.out.println(""); - } - System.out.println(""); - } -} +/* ==================================================================== + 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.Iterator; + +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.NumberEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.ss.formula.WorkbookEvaluator; + +/** + * Evaluates formula cells.

+ * + * For performance reasons, this class keeps a cache of all previously calculated intermediate + * cell values. Be sure to call {@link #clearCache()} if any workbook cells are changed between + * calls to evaluate~ methods on this class. + * + * @author Amol S. Deshmukh < amolweb at ya hoo dot com > + * @author Josh Micich + */ +public class HSSFFormulaEvaluator { + + private WorkbookEvaluator _bookEvaluator; + + /** + * @deprecated (Sep 2008) HSSFSheet parameter is ignored + */ + public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) { + this(workbook); + if (false) { + sheet.toString(); // suppress unused parameter compiler warning + } + } + public HSSFFormulaEvaluator(HSSFWorkbook workbook) { + _bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook)); + } + + /** + * TODO for debug/test use + */ + /* package */ int getEvaluationCount() { + return _bookEvaluator.getEvaluationCount(); + } + + /** + * Does nothing + * @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell + */ + public void setCurrentRow(HSSFRow row) { + // do nothing + if (false) { + row.getClass(); // suppress unused parameter compiler warning + } + } + + /** + * Should be called whenever there are major changes (e.g. moving sheets) to input cells + * in the evaluated workbook. + * Failure to call this method after changing cell values will cause incorrect behaviour + * of the evaluate~ methods of this class + */ + public void clearAllCachedResultValues() { + _bookEvaluator.clearAllCachedResultValues(); + } + /** + * Should be called whenever there are changes to individual input cells in the evaluated workbook. + * Failure to call this method after changing cell values will cause incorrect behaviour + * of the evaluate~ methods of this class + */ + public void clearCachedResultValue(HSSFSheet sheet, int rowIndex, int columnIndex) { + _bookEvaluator.clearCachedResultValue(sheet, rowIndex, columnIndex); + } + + /** + * 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) { + if (cell == null) { + return null; + } + + switch (cell.getCellType()) { + case HSSFCell.CELL_TYPE_BOOLEAN: + return CellValue.valueOf(cell.getBooleanCellValue()); + case HSSFCell.CELL_TYPE_ERROR: + return CellValue.getError(cell.getErrorCellValue()); + case HSSFCell.CELL_TYPE_FORMULA: + return evaluateFormulaCellValue(cell); + case HSSFCell.CELL_TYPE_NUMERIC: + return new CellValue(cell.getNumericCellValue()); + case HSSFCell.CELL_TYPE_STRING: + return new CellValue(cell.getRichStringCellValue().getString()); + } + throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")"); + } + + + /** + * 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 || cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { + return -1; + } + CellValue cv = evaluateFormulaCellValue(cell); + // cell remains a formula cell, but the cached value is changed + setCellValue(cell, cv); + return cv.getCellType(); + } + + /** + * 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) { + return null; + } + if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) { + CellValue cv = evaluateFormulaCellValue(cell); + setCellType(cell, cv); // cell will no longer be a formula cell + setCellValue(cell, cv); + } + return cell; + } + private static void setCellType(HSSFCell cell, CellValue cv) { + int cellType = cv.getCellType(); + switch (cellType) { + case HSSFCell.CELL_TYPE_BOOLEAN: + case HSSFCell.CELL_TYPE_ERROR: + case HSSFCell.CELL_TYPE_NUMERIC: + case HSSFCell.CELL_TYPE_STRING: + cell.setCellType(cellType); + return; + case HSSFCell.CELL_TYPE_BLANK: + // never happens - blanks eventually get translated to zero + case HSSFCell.CELL_TYPE_FORMULA: + // this will never happen, we have already evaluated the formula + } + throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); + } + + private static void setCellValue(HSSFCell cell, CellValue cv) { + int cellType = cv.getCellType(); + switch (cellType) { + case HSSFCell.CELL_TYPE_BOOLEAN: + cell.setCellValue(cv.getBooleanValue()); + break; + case HSSFCell.CELL_TYPE_ERROR: + cell.setCellErrorValue(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: + // never happens - blanks eventually get translated to zero + case HSSFCell.CELL_TYPE_FORMULA: + // this will never happen, we have already evaluated the formula + default: + throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); + } + } + + /** + * Loops over all cells in all sheets of the supplied + * workbook. + * For cells that contain formulas, their formulas are + * evaluated, and the results are saved. These cells + * remain as formula cells. + * For cells that do not contain formulas, no changes + * are made. + * This is a helpful wrapper around looping over all + * cells, and calling evaluateFormulaCell on each one. + */ + public static void evaluateAllFormulaCells(HSSFWorkbook wb) { + HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb); + for(int i=0; i"; + } + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFName.java b/src/java/org/apache/poi/hssf/usermodel/HSSFName.java index 8c264e4a5..c24fa0508 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFName.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFName.java @@ -95,7 +95,7 @@ public final class HSSFName { */ private void setSheetName(String sheetName){ int sheetNumber = _book.getSheetIndex(sheetName); - short externSheetNumber = _book.getExternalSheetIndex(sheetNumber); + short externSheetNumber = _book.getWorkbook().checkExternSheet(sheetNumber); _definedNameRec.setExternSheetNumber(externSheetNumber); } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index a12aff5e1..25e24426a 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -661,52 +661,6 @@ public class HSSFWorkbook extends POIDocument return -1; } - /* package */ int findSheetIndex(Sheet sheet) { - for(int i=0; i<_sheets.size(); i++) { - HSSFSheet hSheet = (HSSFSheet) _sheets.get(i); - if(hSheet.getSheet() == sheet) { - return i; - } - } - throw new IllegalArgumentException("Specified sheet not found in this workbook"); - } - - /** - * Returns the external sheet index of the sheet - * with the given internal index, creating one - * if needed. - * Used by some of the more obscure formula and - * named range things. - * @deprecated for POI internal use only (formula parsing). This method is likely to - * be removed in future versions of POI. - */ - public short getExternalSheetIndex(int internalSheetIndex) { - return workbook.checkExternSheet(internalSheetIndex); - } - /** - * @deprecated for POI internal use only (formula rendering). This method is likely to - * be removed in future versions of POI. - */ - public String findSheetNameFromExternSheet(int externSheetIndex){ - // TODO - don't expose internal ugliness like externSheet indexes to the user model API - return workbook.findSheetNameFromExternSheet(externSheetIndex); - } - /** - * @deprecated for POI internal use only (formula rendering). This method is likely to - * be removed in future versions of POI. - * - * @param refIndex Index to REF entry in EXTERNSHEET record in the Link Table - * @param definedNameIndex zero-based to DEFINEDNAME or EXTERNALNAME record - * @return the string representation of the defined or external name - */ - public String resolveNameXText(int refIndex, int definedNameIndex) { - // TODO - make this less cryptic / move elsewhere - return workbook.resolveNameXText(refIndex, definedNameIndex); - } - - - - /** * create an HSSFSheet for this HSSFWorkbook, adds it to the sheets and returns * the high level representation. Use this to create new sheets. @@ -750,7 +704,7 @@ public class HSSFWorkbook extends POIDocument if (filterDbNameIndex >=0) { NameRecord origNameRecord = workbook.getNameRecord(filterDbNameIndex); // copy original formula but adjust 3D refs to the new external sheet index - int newExtSheetIx = getExternalSheetIndex(newSheetIndex); + int newExtSheetIx = workbook.checkExternSheet(newSheetIndex); Ptg[] ptgs = origNameRecord.getNameDefinition(); for (int i=0; i< ptgs.length; i++) { Ptg ptg = ptgs[i]; diff --git a/src/java/org/apache/poi/ss/formula/CellEvaluationFrame.java b/src/java/org/apache/poi/ss/formula/CellEvaluationFrame.java new file mode 100644 index 000000000..b6f542a08 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/CellEvaluationFrame.java @@ -0,0 +1,71 @@ +/* ==================================================================== + 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; + +/** + * Stores the parameters that identify the evaluation of one cell.
+ */ +final class CellEvaluationFrame { + + private final int _sheetIndex; + private final int _srcRowNum; + private final int _srcColNum; + private final int _hashCode; + + public CellEvaluationFrame(int sheetIndex, int srcRowNum, int srcColNum) { + if (sheetIndex < 0) { + throw new IllegalArgumentException("sheetIndex must not be negative"); + } + _sheetIndex = sheetIndex; + _srcRowNum = srcRowNum; + _srcColNum = srcColNum; + _hashCode = sheetIndex + 17 * (srcRowNum + 17 * srcColNum); + } + + public boolean equals(Object obj) { + CellEvaluationFrame other = (CellEvaluationFrame) obj; + if (_sheetIndex != other._sheetIndex) { + return false; + } + if (_srcRowNum != other._srcRowNum) { + return false; + } + if (_srcColNum != other._srcColNum) { + return false; + } + return true; + } + public int hashCode() { + return _hashCode; + } + + /** + * @return human readable string for debug purposes + */ + public String formatAsString() { + return "R=" + _srcRowNum + " C=" + _srcColNum + " ShIx=" + _sheetIndex; + } + + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(formatAsString()); + sb.append("]"); + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/java/org/apache/poi/ss/formula/CellEvaluator.java b/src/java/org/apache/poi/ss/formula/CellEvaluator.java new file mode 100644 index 000000000..22846c2d4 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/CellEvaluator.java @@ -0,0 +1,54 @@ +package org.apache.poi.ss.formula; + +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.NumberEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFSheet; + +final class CellEvaluator { + + private final WorkbookEvaluator _bookEvaluator; + private final EvaluationTracker _tracker; + + public CellEvaluator(WorkbookEvaluator bookEvaluator, EvaluationTracker tracker) { + _bookEvaluator = bookEvaluator; + _tracker = tracker; + } + + /** + * 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. + */ + public ValueEval getEvalForCell(HSSFCell cell) { + + 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 _bookEvaluator.internalEvaluate(cell, _tracker); + 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() + ")"); + } + + public String getSheetName(HSSFSheet sheet) { + return _bookEvaluator.getSheetName(sheet); + } + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/EvaluationCache.java b/src/java/org/apache/poi/ss/formula/EvaluationCache.java similarity index 58% rename from src/java/org/apache/poi/hssf/usermodel/EvaluationCache.java rename to src/java/org/apache/poi/ss/formula/EvaluationCache.java index 97c4d6eed..d78f4a728 100644 --- a/src/java/org/apache/poi/hssf/usermodel/EvaluationCache.java +++ b/src/java/org/apache/poi/ss/formula/EvaluationCache.java @@ -15,12 +15,14 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hssf.usermodel; +package org.apache.poi.ss.formula; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; /** * Performance optimisation for {@link HSSFFormulaEvaluator}. This class stores previously @@ -31,55 +33,29 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; * @author Josh Micich */ final class EvaluationCache { - private static final class Key { - - private final int _sheetIndex; - private final int _srcRowNum; - private final int _srcColNum; - private final int _hashCode; - - public Key(int sheetIndex, int srcRowNum, int srcColNum) { - _sheetIndex = sheetIndex; - _srcRowNum = srcRowNum; - _srcColNum = srcColNum; - _hashCode = sheetIndex + srcRowNum + srcColNum; - } - - public int hashCode() { - return _hashCode; - } - - public boolean equals(Object obj) { - Key other = (Key) obj; - if (_hashCode != other._hashCode) { - return false; - } - if (_sheetIndex != other._sheetIndex) { - return false; - } - if (_srcRowNum != other._srcRowNum) { - return false; - } - if (_srcColNum != other._srcColNum) { - return false; - } - return true; - } - } + private static final CellEvaluationFrame[] EMPTY_CEF_ARRAY = { }; private final Map _valuesByKey; + private final Map _consumingCellsByDest; /* package */EvaluationCache() { _valuesByKey = new HashMap(); + _consumingCellsByDest = new HashMap(); } public ValueEval getValue(int sheetIndex, int srcRowNum, int srcColNum) { - Key key = new Key(sheetIndex, srcRowNum, srcColNum); + return getValue(new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum)); + } + + /* package */ ValueEval getValue(CellEvaluationFrame key) { return (ValueEval) _valuesByKey.get(key); } public void setValue(int sheetIndex, int srcRowNum, int srcColNum, ValueEval value) { - Key key = new Key(sheetIndex, srcRowNum, srcColNum); + setValue(new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum), value); + } + + /* package */ void setValue(CellEvaluationFrame key, ValueEval value) { if (_valuesByKey.containsKey(key)) { throw new RuntimeException("Already have cached value for this cell"); } @@ -92,4 +68,32 @@ final class EvaluationCache { public void clear() { _valuesByKey.clear(); } + + public void clearValue(int sheetIndex, int rowIndex, int columnIndex) { + clearValuesRecursive(new CellEvaluationFrame(sheetIndex, rowIndex, columnIndex)); + + } + + private void clearValuesRecursive(CellEvaluationFrame cef) { + CellEvaluationFrame[] consumingCells = getConsumingCells(cef); + for (int i = 0; i < consumingCells.length; i++) { + clearValuesRecursive(consumingCells[i]); + } + _valuesByKey.remove(cef); + _consumingCellsByDest.remove(cef); + } + + private CellEvaluationFrame[] getConsumingCells(CellEvaluationFrame cef) { + List temp = (List) _consumingCellsByDest.get(cef); + if (temp == null) { + return EMPTY_CEF_ARRAY; + } + int nItems = temp.size(); + if (temp.size() < 1) { + return EMPTY_CEF_ARRAY; + } + CellEvaluationFrame[] result = new CellEvaluationFrame[nItems]; + temp.toArray(result); + return result; + } } diff --git a/src/java/org/apache/poi/ss/formula/EvaluationName.java b/src/java/org/apache/poi/ss/formula/EvaluationName.java new file mode 100644 index 000000000..ae8624c7b --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/EvaluationName.java @@ -0,0 +1,41 @@ +/* ==================================================================== + 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.record.formula.NamePtg; +import org.apache.poi.hssf.record.formula.Ptg; +/** + * Abstracts a name record for formula evaluation.
+ * + * For POI internal use only + * + * @author Josh Micich + */ +public interface EvaluationName { + + String getNameText(); + + boolean isFunctionName(); + + boolean hasFormula(); + + Ptg[] getNameDefinition(); + + boolean isRange(); + NamePtg createPtg(); +} diff --git a/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java b/src/java/org/apache/poi/ss/formula/EvaluationTracker.java similarity index 61% rename from src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java rename to src/java/org/apache/poi/ss/formula/EvaluationTracker.java index 0c168af6b..20ab3299a 100755 --- a/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetector.java +++ b/src/java/org/apache/poi/ss/formula/EvaluationTracker.java @@ -15,81 +15,31 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hssf.usermodel; +package org.apache.poi.ss.formula; import java.util.ArrayList; import java.util.List; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.usermodel.HSSFCell; + /** * Instances of this class keep track of multiple dependent cell evaluations due - * to recursive calls to HSSFFormulaEvaluator.internalEvaluate(). + * to recursive calls to {@link WorkbookEvaluator#evaluate(HSSFCell)} * 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 int _sheetIndex; - private final int _srcRowNum; - private final int _srcColNum; - - public CellEvaluationFrame(HSSFWorkbook workbook, int sheetIndex, int srcRowNum, int srcColNum) { - if (workbook == null) { - throw new IllegalArgumentException("workbook must not be null"); - } - if (sheetIndex < 0) { - throw new IllegalArgumentException("sheetIndex must not be negative"); - } - _workbook = workbook; - _sheetIndex = sheetIndex; - _srcRowNum = srcRowNum; - _srcColNum = srcColNum; - } - - public boolean equals(Object obj) { - CellEvaluationFrame other = (CellEvaluationFrame) obj; - if (_workbook != other._workbook) { - return false; - } - if (_sheetIndex != other._sheetIndex) { - 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=" + _sheetIndex; - } - - public String toString() { - StringBuffer sb = new StringBuffer(64); - sb.append(getClass().getName()).append(" ["); - sb.append(formatAsString()); - sb.append("]"); - return sb.toString(); - } - } +final class EvaluationTracker { private final List _evaluationFrames; + private final EvaluationCache _cache; - public EvaluationCycleDetector() { + public EvaluationTracker(EvaluationCache cache) { + _cache = cache; _evaluationFrames = new ArrayList(); } @@ -108,13 +58,16 @@ final class EvaluationCycleDetector { * @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, int sheetIndex, int srcRowNum, int srcColNum) { - CellEvaluationFrame cef = new CellEvaluationFrame(workbook, sheetIndex, srcRowNum, srcColNum); + public ValueEval startEvaluate(int sheetIndex, int srcRowNum, int srcColNum) { + CellEvaluationFrame cef = new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum); if (_evaluationFrames.contains(cef)) { - return false; + return ErrorEval.CIRCULAR_REF_ERROR; } - _evaluationFrames.add(cef); - return true; + ValueEval result = _cache.getValue(cef); + if (result == null) { + _evaluationFrames.add(cef); + } + return result; } /** @@ -128,8 +81,9 @@ final class EvaluationCycleDetector { * 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. + * @param result */ - public void endEvaluate(HSSFWorkbook workbook, int sheetIndex, int srcRowNum, int srcColNum) { + public void endEvaluate(int sheetIndex, int srcRowNum, int srcColNum, ValueEval result) { int nFrames = _evaluationFrames.size(); if (nFrames < 1) { throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate"); @@ -137,7 +91,7 @@ final class EvaluationCycleDetector { nFrames--; CellEvaluationFrame cefExpected = (CellEvaluationFrame) _evaluationFrames.get(nFrames); - CellEvaluationFrame cefActual = new CellEvaluationFrame(workbook, sheetIndex, srcRowNum, srcColNum); + CellEvaluationFrame cefActual = new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum); if (!cefActual.equals(cefExpected)) { throw new RuntimeException("Wrong cell specified. " + "Corresponding startEvaluate() call was for cell {" @@ -146,5 +100,7 @@ final class EvaluationCycleDetector { } // else - no problems so pop current frame _evaluationFrames.remove(nFrames); + + _cache.setValue(cefActual, result); } } diff --git a/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java new file mode 100644 index 000000000..29083f7a5 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java @@ -0,0 +1,42 @@ +/* ==================================================================== + 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.record.formula.NamePtg; +import org.apache.poi.hssf.record.formula.NameXPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFSheet; +/** + * Abstracts a workbook for the purpose of formula evaluation.
+ * + * For POI internal use only + * + * @author Josh Micich + */ +public interface EvaluationWorkbook { + String getSheetName(int sheetIndex); + int getSheetIndex(HSSFSheet sheet); + + HSSFSheet getSheet(int sheetIndex); + + HSSFSheet getSheetByExternSheetIndex(int externSheetIndex); + EvaluationName getName(NamePtg namePtg); + String resolveNameXText(NameXPtg ptg); + Ptg[] getFormulaTokens(HSSFCell cell); +} diff --git a/src/java/org/apache/poi/hssf/model/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java similarity index 89% rename from src/java/org/apache/poi/hssf/model/FormulaParser.java rename to src/java/org/apache/poi/ss/formula/FormulaParser.java index a24c7be60..df2f8c282 100644 --- a/src/java/org/apache/poi/hssf/model/FormulaParser.java +++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java @@ -15,7 +15,7 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hssf.model; +package org.apache.poi.ss.formula; import java.util.ArrayList; import java.util.List; @@ -58,14 +58,9 @@ import org.apache.poi.hssf.record.formula.UnaryPlusPtg; import org.apache.poi.hssf.record.formula.function.FunctionMetadata; import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; import org.apache.poi.hssf.usermodel.HSSFErrorConstants; -import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; -import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; -import org.apache.poi.hssf.usermodel.HSSFName; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.util.AreaReference; import org.apache.poi.hssf.util.CellReference; import org.apache.poi.hssf.util.CellReference.NameType; -import org.apache.poi.ss.formula.FormulaRenderer; /** * This class parses a formula string into a List of tokens in RPN order. @@ -83,6 +78,7 @@ import org.apache.poi.ss.formula.FormulaRenderer; * @author Cameron Riley (criley at ekmail.com) * @author Peter M. Murray (pete at quantrix dot com) * @author Pavel Krupets (pkrupets at palmtreebusiness dot com) + * @author Josh Micich */ public final class FormulaParser { @@ -99,14 +95,6 @@ public final class FormulaParser { } } - public static final int FORMULA_TYPE_CELL = 0; - public static final int FORMULA_TYPE_SHARED = 1; - public static final int FORMULA_TYPE_ARRAY =2; - public static final int FORMULA_TYPE_CONDFORMAT = 3; - public static final int FORMULA_TYPE_NAMEDRANGE = 4; - // this constant is currently very specific. The exact differences from general data - // validation formulas or conditional format formulas is not known yet - public static final int FORMULA_TYPE_DATAVALIDATION_LIST = 5; private final String formulaString; private final int formulaLength; @@ -122,7 +110,8 @@ public final class FormulaParser { */ private char look; - private HSSFWorkbook book; + private FormulaParsingWorkbook book; + /** @@ -137,19 +126,19 @@ public final class FormulaParser { * model.Workbook, then use the convenience method on * usermodel.HSSFFormulaEvaluator */ - public FormulaParser(String formula, HSSFWorkbook book){ + private FormulaParser(String formula, FormulaParsingWorkbook book){ formulaString = formula; pointer=0; this.book = book; formulaLength = formulaString.length(); } - public static Ptg[] parse(String formula, HSSFWorkbook book) { - return parse(formula, book, FORMULA_TYPE_CELL); + public static Ptg[] parse(String formula, FormulaParsingWorkbook book) { + return parse(formula, book, FormulaType.CELL); } - public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType) { - FormulaParser fp = HSSFFormulaEvaluator.getUnderlyingParser(workbook, formula); + public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType) { + FormulaParser fp = new FormulaParser(formula, workbook); fp.parse(); return fp.getRPNPtg(formulaType); } @@ -308,7 +297,7 @@ public final class FormulaParser { Match('!'); String sheetName = name; String first = parseIdentifier(); - short externIdx = book.getExternalSheetIndex(book.getSheetIndex(sheetName)); + int externIdx = book.getExternalSheetIndex(sheetName); areaRef = parseArea(name); if (areaRef != null) { // will happen if dots are used instead of colon @@ -330,7 +319,7 @@ public final class FormulaParser { } return new Area3DPtg(first+":"+second,externIdx); } - return new Ref3DPtg(first,externIdx); + return new Ref3DPtg(first, externIdx); } if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) { return new BoolPtg(name.toUpperCase()); @@ -346,15 +335,16 @@ public final class FormulaParser { new FormulaParseException("Name '" + name + "' does not look like a cell reference or named range"); } - - for(int i = 0; i < book.getNumberOfNames(); i++) { - // named range name matching is case insensitive - if(book.getNameAt(i).getNameName().equalsIgnoreCase(name)) { - return new NamePtg(i); - } - } - throw new FormulaParseException("Specified named range '" + EvaluationName evalName = book.getName(name); + if (evalName == null) { + throw new FormulaParseException("Specified named range '" + name + "' does not exist in the current workbook."); + } + if (evalName.isRange()) { + return evalName.createPtg(); + } + throw new FormulaParseException("Specified name '" + + name + "' is not a range as expected"); } /** @@ -410,10 +400,15 @@ public final class FormulaParser { // user defined function // in the token tree, the name is more or less the first argument + EvaluationName hName = book.getName(name); + if (hName == null) { - int nameIndex = book.getNameIndex(name); - if (nameIndex >= 0) { - HSSFName hName = book.getNameAt(nameIndex); + nameToken = book.getNameXPtg(name); + if (nameToken == null) { + throw new FormulaParseException("Name '" + name + + "' is completely unknown in the current workbook"); + } + } else { if (!hName.isFunctionName()) { throw new FormulaParseException("Attempt to use name '" + name + "' as a function, but defined name in workbook does not refer to a function"); @@ -421,14 +416,7 @@ public final class FormulaParser { // calls to user-defined functions within the workbook // get a Name token which points to a defined name record - nameToken = new NamePtg(nameIndex); - } else { - - nameToken = book.getNameXPtg(name); - if (nameToken == null) { - throw new FormulaParseException("Name '" + name - + "' is completely unknown in the current workbook"); - } + nameToken = hName.createPtg(); } } @@ -965,9 +953,9 @@ end; /** * API call to execute the parsing of the formula - * @deprecated use {@link #parse(String, HSSFWorkbook)} directly + * */ - public void parse() { + private void parse() { pointer=0; GetChar(); _rootNode = comparisonExpression(); @@ -979,60 +967,10 @@ end; } } - - /********************************* - * PARSER IMPLEMENTATION ENDS HERE - * EXCEL SPECIFIC METHODS BELOW - *******************************/ - - /** API call to retrive the array of Ptgs created as - * a result of the parsing - */ - public Ptg[] getRPNPtg() { - return getRPNPtg(FORMULA_TYPE_CELL); - } - - public Ptg[] getRPNPtg(int formulaType) { + private Ptg[] getRPNPtg(int formulaType) { OperandClassTransformer oct = new OperandClassTransformer(formulaType); // RVA is for 'operand class': 'reference', 'value', 'array' oct.transformFormula(_rootNode); return ParseNode.toTokenArray(_rootNode); } - - /** - * Convenience method which takes in a list then passes it to the - * other toFormulaString signature. - * @param book workbook for 3D and named references - * @param lptgs list of Ptg, can be null or empty - * @return a human readable String - */ - public static String toFormulaString(HSSFWorkbook book, List lptgs) { - String retval = null; - if (lptgs == null || lptgs.size() == 0) return "#NAME"; - Ptg[] ptgs = new Ptg[lptgs.size()]; - ptgs = (Ptg[])lptgs.toArray(ptgs); - retval = toFormulaString(book, ptgs); - return retval; - } - /** - * Convenience method which takes in a list then passes it to the - * other toFormulaString signature. Works on the current - * workbook for 3D and named references - * @param lptgs list of Ptg, can be null or empty - * @return a human readable String - */ - public String toFormulaString(List lptgs) { - return toFormulaString(book, lptgs); - } - - /** - * Static method to convert an array of Ptgs in RPN order - * to a human readable string format in infix mode. - * @param book workbook for named and 3D references - * @param ptgs array of Ptg, can be null or empty - * @return a human readable String - */ - public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) { - return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs); - } } diff --git a/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java new file mode 100644 index 000000000..69431c2c2 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java @@ -0,0 +1,37 @@ +/* ==================================================================== + 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.record.formula.NameXPtg; + +/** + * Abstracts a workbook for the purpose of formula parsing.
+ * + * For POI internal use only + * + * @author Josh Micich + */ +public interface FormulaParsingWorkbook { + /** + * named range name matching is case insensitive + */ + EvaluationName getName(String name); + + int getExternalSheetIndex(String sheetName); + NameXPtg getNameXPtg(String name); +} diff --git a/src/java/org/apache/poi/ss/formula/FormulaRenderer.java b/src/java/org/apache/poi/ss/formula/FormulaRenderer.java index 21386cd0d..980c345d3 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaRenderer.java +++ b/src/java/org/apache/poi/ss/formula/FormulaRenderer.java @@ -1,3 +1,20 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + package org.apache.poi.ss.formula; import java.util.List; @@ -11,6 +28,13 @@ import org.apache.poi.hssf.record.formula.OperationPtg; import org.apache.poi.hssf.record.formula.ParenthesisPtg; import org.apache.poi.hssf.record.formula.Ptg; +/** + * Common logic for rendering formulas.
+ * + * For POI internal use only + * + * @author Josh Micich + */ public class FormulaRenderer { /** * Convenience method which takes in a list then passes it to the diff --git a/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java b/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java index 3a92aa8cb..ac95f4da0 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java +++ b/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java @@ -1,8 +1,32 @@ +/* ==================================================================== + 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.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NameXPtg; +/** + * Abstracts a workbook for the purpose of converting formula to text.
+ * + * For POI internal use only + * + * @author Josh Micich + */ public interface FormulaRenderingWorkbook { String getSheetNameByExternSheet(int externSheetIndex); diff --git a/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java b/src/java/org/apache/poi/ss/formula/FormulaType.java old mode 100755 new mode 100644 similarity index 59% rename from src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java rename to src/java/org/apache/poi/ss/formula/FormulaType.java index a06cd201e..3b47030d4 --- a/src/java/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java +++ b/src/java/org/apache/poi/ss/formula/FormulaType.java @@ -1,46 +1,40 @@ -/* ==================================================================== - 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 - } -} +/* ==================================================================== + 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; + +/** + * Enumeration of various formula types.
+ * + * For POI internal use only + * + * @author Josh Micich + */ +public final class FormulaType { + private FormulaType() { + // no instances of this class + } + public static final int CELL = 0; + public static final int SHARED = 1; + public static final int ARRAY =2; + public static final int CONDFORMAT = 3; + public static final int NAMEDRANGE = 4; + // this constant is currently very specific. The exact differences from general data + // validation formulas or conditional format formulas is not known yet + public static final int DATAVALIDATION_LIST = 5; + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/LazyAreaEval.java b/src/java/org/apache/poi/ss/formula/LazyAreaEval.java similarity index 90% rename from src/java/org/apache/poi/hssf/usermodel/LazyAreaEval.java rename to src/java/org/apache/poi/ss/formula/LazyAreaEval.java index 0e1d1fd00..07c368ed0 100644 --- a/src/java/org/apache/poi/hssf/usermodel/LazyAreaEval.java +++ b/src/java/org/apache/poi/ss/formula/LazyAreaEval.java @@ -15,7 +15,7 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hssf.usermodel; +package org.apache.poi.ss.formula; import org.apache.poi.hssf.record.formula.AreaI; import org.apache.poi.hssf.record.formula.AreaI.OffsetArea; @@ -23,6 +23,9 @@ import org.apache.poi.hssf.record.formula.eval.AreaEval; import org.apache.poi.hssf.record.formula.eval.AreaEvalBase; import org.apache.poi.hssf.record.formula.eval.BlankEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.util.CellReference; /** @@ -32,9 +35,9 @@ import org.apache.poi.hssf.util.CellReference; final class LazyAreaEval extends AreaEvalBase { private final HSSFSheet _sheet; - private HSSFFormulaEvaluator _evaluator; + private final CellEvaluator _evaluator; - public LazyAreaEval(AreaI ptg, HSSFSheet sheet, HSSFFormulaEvaluator evaluator) { + public LazyAreaEval(AreaI ptg, HSSFSheet sheet, CellEvaluator evaluator) { super(ptg); _sheet = sheet; _evaluator = evaluator; diff --git a/src/java/org/apache/poi/hssf/usermodel/LazyRefEval.java b/src/java/org/apache/poi/ss/formula/LazyRefEval.java similarity index 85% rename from src/java/org/apache/poi/hssf/usermodel/LazyRefEval.java rename to src/java/org/apache/poi/ss/formula/LazyRefEval.java index d26ac59ff..fa1f3a383 100644 --- a/src/java/org/apache/poi/hssf/usermodel/LazyRefEval.java +++ b/src/java/org/apache/poi/ss/formula/LazyRefEval.java @@ -15,7 +15,7 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hssf.usermodel; +package org.apache.poi.ss.formula; import org.apache.poi.hssf.record.formula.AreaI; import org.apache.poi.hssf.record.formula.Ref3DPtg; @@ -25,6 +25,9 @@ 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.RefEvalBase; import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.util.CellReference; /** @@ -34,15 +37,15 @@ import org.apache.poi.hssf.util.CellReference; final class LazyRefEval extends RefEvalBase { private final HSSFSheet _sheet; - private final HSSFFormulaEvaluator _evaluator; + private final CellEvaluator _evaluator; - public LazyRefEval(RefPtg ptg, HSSFSheet sheet, HSSFFormulaEvaluator evaluator) { + public LazyRefEval(RefPtg ptg, HSSFSheet sheet, CellEvaluator evaluator) { super(ptg.getRow(), ptg.getColumn()); _sheet = sheet; _evaluator = evaluator; } - public LazyRefEval(Ref3DPtg ptg, HSSFSheet sheet, HSSFFormulaEvaluator evaluator) { + public LazyRefEval(Ref3DPtg ptg, HSSFSheet sheet, CellEvaluator evaluator) { super(ptg.getRow(), ptg.getColumn()); _sheet = sheet; _evaluator = evaluator; diff --git a/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java b/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java similarity index 98% rename from src/java/org/apache/poi/hssf/model/OperandClassTransformer.java rename to src/java/org/apache/poi/ss/formula/OperandClassTransformer.java index 8b7b56638..79087e4d6 100644 --- a/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java +++ b/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java @@ -15,7 +15,7 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hssf.model; +package org.apache.poi.ss.formula; import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; import org.apache.poi.hssf.record.formula.ControlPtg; @@ -63,10 +63,10 @@ final class OperandClassTransformer { public void transformFormula(ParseNode rootNode) { byte rootNodeOperandClass; switch (_formulaType) { - case FormulaParser.FORMULA_TYPE_CELL: + case FormulaType.CELL: rootNodeOperandClass = Ptg.CLASS_VALUE; break; - case FormulaParser.FORMULA_TYPE_DATAVALIDATION_LIST: + case FormulaType.DATAVALIDATION_LIST: rootNodeOperandClass = Ptg.CLASS_REF; break; default: diff --git a/src/java/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java b/src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java similarity index 99% rename from src/java/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java rename to src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java index 21dc06759..eaa57c114 100755 --- a/src/java/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java +++ b/src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java @@ -15,7 +15,7 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hssf.usermodel; +package org.apache.poi.ss.formula; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; diff --git a/src/java/org/apache/poi/hssf/model/ParseNode.java b/src/java/org/apache/poi/ss/formula/ParseNode.java similarity index 99% rename from src/java/org/apache/poi/hssf/model/ParseNode.java rename to src/java/org/apache/poi/ss/formula/ParseNode.java index acd8cb12b..75685dee6 100644 --- a/src/java/org/apache/poi/hssf/model/ParseNode.java +++ b/src/java/org/apache/poi/ss/formula/ParseNode.java @@ -15,7 +15,7 @@ limitations under the License. ==================================================================== */ -package org.apache.poi.hssf.model; +package org.apache.poi.ss.formula; import org.apache.poi.hssf.record.formula.AttrPtg; import org.apache.poi.hssf.record.formula.FuncVarPtg; diff --git a/src/java/org/apache/poi/ss/formula/WorkbookDependentFormula.java b/src/java/org/apache/poi/ss/formula/WorkbookDependentFormula.java index 420527dd8..b7ccfaf0e 100644 --- a/src/java/org/apache/poi/ss/formula/WorkbookDependentFormula.java +++ b/src/java/org/apache/poi/ss/formula/WorkbookDependentFormula.java @@ -1,5 +1,30 @@ +/* ==================================================================== + 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; +/** + * Should be implemented by any {@link Ptg} subclass that needs a workbook to render its formula. + *
+ * + * For POI internal use only + * + * @author Josh Micich + */ public interface WorkbookDependentFormula { String toFormulaString(FormulaRenderingWorkbook book); } diff --git a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java new file mode 100644 index 000000000..199d35b66 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java @@ -0,0 +1,348 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.ss.formula; + +import java.util.Stack; + +import org.apache.poi.hssf.record.formula.Area3DPtg; +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.BoolPtg; +import org.apache.poi.hssf.record.formula.ControlPtg; +import org.apache.poi.hssf.record.formula.ErrPtg; +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.Ptg; +import org.apache.poi.hssf.record.formula.Ref3DPtg; +import org.apache.poi.hssf.record.formula.RefPtg; +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.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.NameXEval; +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.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.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.util.CellReference; + +/** + * Evaluates formula cells.

+ * + * For performance reasons, this class keeps a cache of all previously calculated intermediate + * cell values. Be sure to call {@link #clearCache()} if any workbook cells are changed between + * calls to evaluate~ methods on this class.
+ * + * For POI internal use only + * + * @author Josh Micich + */ +public class WorkbookEvaluator { + + /** + * used to track the number of evaluations + */ + private static final class Counter { + public int value; + public int depth; + public Counter() { + value = 0; + } + } + + private final EvaluationWorkbook _workbook; + private final EvaluationCache _cache; + + private Counter _evaluationCounter; + + public WorkbookEvaluator(EvaluationWorkbook workbook) { + _workbook = workbook; + _cache = new EvaluationCache(); + _evaluationCounter = new Counter(); + } + + /** + * for debug use. Used in toString methods + */ + /* package */ String getSheetName(HSSFSheet sheet) { + return getSheetName(getSheetIndex(sheet)); + } + private String getSheetName(int sheetIndex) { + return _workbook.getSheetName(sheetIndex); + } + /** + * for debug/test use + */ + public int getEvaluationCount() { + return _evaluationCounter.value; + } + + private static boolean isDebugLogEnabled() { + return false; + } + private static void logDebug(String s) { + if (isDebugLogEnabled()) { + System.out.println(s); + } + } + + /** + * Should be called whenever there are changes to input cells in the evaluated workbook. + * Failure to call this method after changing cell values will cause incorrect behaviour + * of the evaluate~ methods of this class + */ + public void clearAllCachedResultValues() { + _cache.clear(); + } + + public void clearCachedResultValue(HSSFSheet sheet, int rowIndex, int columnIndex) { + int sheetIndex = getSheetIndex(sheet); + _cache.clearValue(sheetIndex, rowIndex, columnIndex); + + } + private int getSheetIndex(HSSFSheet sheet) { + // TODO cache sheet indexes too + return _workbook.getSheetIndex(sheet); + } + + public ValueEval evaluate(HSSFCell srcCell) { + return internalEvaluate(srcCell, new EvaluationTracker(_cache)); + } + + /** + * Dev. Note: Internal evaluate must be passed only a formula cell + * else a runtime exception will be thrown somewhere inside the method. + * (Hence this is a private method.) + * @return never null, never {@link BlankEval} + */ + /* package */ ValueEval internalEvaluate(HSSFCell srcCell, EvaluationTracker tracker) { + int srcRowNum = srcCell.getRowIndex(); + int srcColNum = srcCell.getCellNum(); + + ValueEval result; + + int sheetIndex = getSheetIndex(srcCell.getSheet()); + result = tracker.startEvaluate(sheetIndex, srcRowNum, srcColNum); + if (result != null) { + return result; + } + _evaluationCounter.value++; + _evaluationCounter.depth++; + + try { + Ptg[] ptgs = _workbook.getFormulaTokens(srcCell); + result = evaluateCell(sheetIndex, srcRowNum, (short)srcColNum, ptgs, tracker); + } finally { + tracker.endEvaluate(sheetIndex, srcRowNum, srcColNum, result); + _evaluationCounter.depth--; + } + if (isDebugLogEnabled()) { + String sheetName = getSheetName(sheetIndex); + CellReference cr = new CellReference(srcRowNum, srcColNum); + logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + result.toString()); + } + return result; + } + private ValueEval evaluateCell(int sheetIndex, int srcRowNum, short srcColNum, Ptg[] ptgs, EvaluationTracker tracker) { + + Stack stack = new Stack(); + for (int i = 0, iSize = ptgs.length; i < iSize; i++) { + + // since we don't know how to handle these yet :( + Ptg ptg = ptgs[i]; + if (ptg instanceof ControlPtg) { + // skip Parentheses, Attr, etc + continue; + } + if (ptg instanceof MemErrPtg) { continue; } + if (ptg instanceof MissingArgPtg) { + // TODO - might need to push BlankEval or MissingArgEval + continue; + } + Eval opResult; + if (ptg instanceof OperationPtg) { + OperationPtg optg = (OperationPtg) ptg; + + if (optg instanceof UnionPtg) { continue; } + + OperationEval operation = OperationEvaluatorFactory.create(optg); + + int numops = operation.getNumberOfOperands(); + Eval[] ops = new Eval[numops]; + + // storing the ops in reverse order since they are popping + for (int j = numops - 1; j >= 0; j--) { + Eval p = (Eval) stack.pop(); + ops[j] = p; + } +// logDebug("invoke " + operation + " (nAgs=" + numops + ")"); + opResult = invokeOperation(operation, ops, _workbook, sheetIndex, srcRowNum, srcColNum); + } else { + opResult = getEvalForPtg(ptg, sheetIndex, tracker); + } + if (opResult == null) { + throw new RuntimeException("Evaluation result must not be null"); + } +// logDebug("push " + opResult); + stack.push(opResult); + } + + ValueEval value = ((ValueEval) stack.pop()); + if (!stack.isEmpty()) { + throw new IllegalStateException("evaluation stack not empty"); + } + value = dereferenceValue(value, srcRowNum, srcColNum); + if (value == BlankEval.INSTANCE) { + // 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.getRelativeValue(0, 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, + EvaluationWorkbook workbook, int sheetIndex, int srcRowNum, int srcColNum) { + + if(operation instanceof FunctionEval) { + FunctionEval fe = (FunctionEval) operation; + if(fe.isFreeRefFunction()) { + return fe.getFreeRefFunction().evaluate(ops, workbook, sheetIndex, srcRowNum, srcColNum); + } + } + return operation.evaluate(ops, srcRowNum, (short)srcColNum); + } + + private HSSFSheet getOtherSheet(int externSheetIndex) { + return _workbook.getSheetByExternSheetIndex(externSheetIndex); + } + + /** + * 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! + */ + private Eval getEvalForPtg(Ptg ptg, int sheetIndex, EvaluationTracker tracker) { + if (ptg instanceof NamePtg) { + // named ranges, macro functions + NamePtg namePtg = (NamePtg) ptg; + EvaluationName nameRecord = _workbook.getName(namePtg); + if (nameRecord.isFunctionName()) { + return new NameEval(nameRecord.getNameText()); + } + if (nameRecord.hasFormula()) { + return evaluateNameFormula(nameRecord.getNameDefinition(), sheetIndex, tracker); + } + + throw new RuntimeException("Don't now how to evalate name '" + nameRecord.getNameText() + "'"); + } + if (ptg instanceof NameXPtg) { + return new NameXEval(((NameXPtg) ptg)); + } + + if (ptg instanceof IntPtg) { + return new NumberEval(((IntPtg)ptg).getValue()); + } + if (ptg instanceof NumberPtg) { + return new NumberEval(((NumberPtg)ptg).getValue()); + } + if (ptg instanceof StringPtg) { + return new StringEval(((StringPtg) ptg).getValue()); + } + if (ptg instanceof BoolPtg) { + return BoolEval.valueOf(((BoolPtg) ptg).getValue()); + } + if (ptg instanceof ErrPtg) { + return ErrorEval.valueOf(((ErrPtg) ptg).getErrorCode()); + } + CellEvaluator ce = new CellEvaluator(this, tracker); + HSSFSheet sheet = _workbook.getSheet(sheetIndex); + if (ptg instanceof RefPtg) { + return new LazyRefEval(((RefPtg) ptg), sheet, ce); + } + if (ptg instanceof AreaPtg) { + return new LazyAreaEval(((AreaPtg) ptg), sheet, ce); + } + if (ptg instanceof Ref3DPtg) { + Ref3DPtg refPtg = (Ref3DPtg) ptg; + HSSFSheet xsheet = getOtherSheet(refPtg.getExternSheetIndex()); + return new LazyRefEval(refPtg, xsheet, ce); + } + if (ptg instanceof Area3DPtg) { + Area3DPtg a3dp = (Area3DPtg) ptg; + HSSFSheet xsheet = getOtherSheet(a3dp.getExternSheetIndex()); + return new LazyAreaEval(a3dp, xsheet, ce); + } + + if (ptg instanceof UnknownPtg) { + // POI uses UnknownPtg when the encoded Ptg array seems to be corrupted. + // This seems to occur in very rare cases (e.g. unused name formulas in bug 44774, attachment 21790) + // In any case, formulas are re-parsed before execution, so UnknownPtg should not get here + throw new RuntimeException("UnknownPtg not allowed"); + } + + throw new RuntimeException("Unexpected ptg class (" + ptg.getClass().getName() + ")"); + } + private Eval evaluateNameFormula(Ptg[] ptgs, int sheetIndex, EvaluationTracker tracker) { + if (ptgs.length > 1) { + throw new RuntimeException("Complex name formulas not supported yet"); + } + return getEvalForPtg(ptgs[0], sheetIndex, tracker); + } +} diff --git a/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java b/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java index a17414af7..2c56ea57a 100644 --- a/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java +++ b/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java @@ -26,7 +26,7 @@ import junit.framework.TestCase; import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder.SheetRecordCollectingListener; -import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.Record; @@ -101,7 +101,7 @@ public final class TestEventWorkbookBuilder extends TestCase { // Check we can get the formula without breaking for(int i=0; inull */ /* package */ static Ptg[] parseFormula(String formula) { - Ptg[] result = FormulaParser.parse(formula, null); + Ptg[] result = HSSFFormulaParser.parse(formula, (HSSFWorkbook)null); assertNotNull("Ptg array should not be null", result); return result; } + private static String toFormulaString(Ptg[] ptgs) { + return HSSFFormulaParser.toFormulaString((HSSFWorkbook)null, ptgs); + } public void testSimpleFormula() { Ptg[] ptgs = parseFormula("2+2"); @@ -133,7 +136,7 @@ public final class TestFormulaParser extends TestCase { HSSFWorkbook w = HSSFTestDataSamples.openSampleWorkbook("testNames.xls"); HSSFEvaluationWorkbook book = HSSFEvaluationWorkbook.create(w); - Ptg[] ptg = FormulaParser.parse("myFunc()", w); + Ptg[] ptg = HSSFFormulaParser.parse("myFunc()", w); // myFunc() actually takes 1 parameter. Don't know if POI will ever be able to detect this problem // the name gets encoded as the first arg @@ -405,7 +408,7 @@ public final class TestFormulaParser extends TestCase { Ptg[] ptgs = { new FuncPtg(10), }; - assertEquals("NA()", FormulaParser.toFormulaString(book, ptgs)); + assertEquals("NA()", HSSFFormulaParser.toFormulaString(book, ptgs)); } public void testPercent() { @@ -639,11 +642,11 @@ public final class TestFormulaParser extends TestCase { String formulaString; Ptg[] ptgs; ptgs = parseFormula("sum(5, 2, if(3>2, sum(A1:A2), 6))"); - formulaString = FormulaParser.toFormulaString(null, ptgs); + formulaString = toFormulaString(ptgs); assertEquals("SUM(5,2,IF(3>2,SUM(A1:A2),6))", formulaString); ptgs = parseFormula("if(1<2,sum(5, 2, if(3>2, sum(A1:A2), 6)),4)"); - formulaString = FormulaParser.toFormulaString(null, ptgs); + formulaString = toFormulaString(ptgs); assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString); } public void testParserErrors() { @@ -665,12 +668,9 @@ public final class TestFormulaParser extends TestCase { try { parseFormula(formula); throw new AssertionFailedError("expected parse exception"); - } catch (FormulaParseException e) { - // expected during successful test - assertNotNull(e.getMessage()); } catch (RuntimeException e) { - e.printStackTrace(); - fail("Wrong exception:" + e.getMessage()); + // expected during successful test + FormulaParserTestHelper.confirmParseException(e); } } @@ -697,7 +697,7 @@ public final class TestFormulaParser extends TestCase { Ptg[] ptgs = { spacePtg, new IntPtg(4), }; String formulaString; try { - formulaString = FormulaParser.toFormulaString(null, ptgs); + formulaString = toFormulaString(ptgs); } catch (IllegalStateException e) { if(e.getMessage().equalsIgnoreCase("too much stuff left on the stack")) { throw new AssertionFailedError("Identified bug 44609"); @@ -709,7 +709,7 @@ public final class TestFormulaParser extends TestCase { assertEquals("4", formulaString); ptgs = new Ptg[] { new IntPtg(3), spacePtg, new IntPtg(4), spacePtg, AddPtg.instance, }; - formulaString = FormulaParser.toFormulaString(null, ptgs); + formulaString = toFormulaString(ptgs); assertEquals("3+4", formulaString); } @@ -725,7 +725,7 @@ public final class TestFormulaParser extends TestCase { DividePtg.instance, }; try { - FormulaParser.toFormulaString(null, ptgs); + toFormulaString(ptgs); fail("Expected exception was not thrown"); } catch (IllegalStateException e) { // expected during successful test @@ -764,10 +764,10 @@ public final class TestFormulaParser extends TestCase { private static void confirmArgCountMsg(String formula, String expectedMessage) { HSSFWorkbook book = new HSSFWorkbook(); try { - FormulaParser.parse(formula, book); + HSSFFormulaParser.parse(formula, book); throw new AssertionFailedError("Didn't get parse exception as expected"); - } catch (FormulaParseException e) { - assertEquals(expectedMessage, e.getMessage()); + } catch (RuntimeException e) { + FormulaParserTestHelper.confirmParseException(e, expectedMessage); } } @@ -776,15 +776,17 @@ public final class TestFormulaParser extends TestCase { try { parseFormula("round(3.14;2)"); throw new AssertionFailedError("Didn't get parse exception as expected"); - } catch (FormulaParseException e) { - assertEquals("Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'", e.getMessage()); + } catch (RuntimeException e) { + FormulaParserTestHelper.confirmParseException(e, + "Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'"); } try { parseFormula(" =2+2"); throw new AssertionFailedError("Didn't get parse exception as expected"); - } catch (FormulaParseException e) { - assertEquals("The specified formula ' =2+2' starts with an equals sign which is not allowed.", e.getMessage()); + } catch (RuntimeException e) { + FormulaParserTestHelper.confirmParseException(e, + "The specified formula ' =2+2' starts with an equals sign which is not allowed."); } } @@ -819,7 +821,7 @@ public final class TestFormulaParser extends TestCase { Ptg[] ptgs; try { - ptgs = FormulaParser.parse("count(pfy1)", wb); + ptgs = HSSFFormulaParser.parse("count(pfy1)", wb); } catch (IllegalArgumentException e) { if (e.getMessage().equals("Specified colIx (1012) is out of range")) { throw new AssertionFailedError("Identified bug 45354"); @@ -835,10 +837,9 @@ public final class TestFormulaParser extends TestCase { try { cell.setCellFormula("count(pf1)"); throw new AssertionFailedError("Expected formula parse execption"); - } catch (FormulaParseException e) { - if (!e.getMessage().equals("Specified named range 'pf1' does not exist in the current workbook.")) { - throw e; - } + } catch (RuntimeException e) { + FormulaParserTestHelper.confirmParseException(e, + "Specified named range 'pf1' does not exist in the current workbook."); } cell.setCellFormula("count(fp1)"); // plain cell ref, col is in range } @@ -850,14 +851,14 @@ public final class TestFormulaParser extends TestCase { HSSFWorkbook book = new HSSFWorkbook(); book.createSheet("Sheet1"); - ptgs = FormulaParser.parse("Sheet1!A10:A40000", book); + ptgs = HSSFFormulaParser.parse("Sheet1!A10:A40000", book); aptg = (AreaI) ptgs[0]; if (aptg.getLastRow() == -25537) { throw new AssertionFailedError("Identified bug 45358"); } assertEquals(39999, aptg.getLastRow()); - ptgs = FormulaParser.parse("Sheet1!A10:A65536", book); + ptgs = HSSFFormulaParser.parse("Sheet1!A10:A65536", book); aptg = (AreaI) ptgs[0]; assertEquals(65535, aptg.getLastRow()); diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParserEval.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParserEval.java index 2bede8e5f..fbbdbd54d 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParserEval.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParserEval.java @@ -17,9 +17,9 @@ package org.apache.poi.hssf.model; +import junit.framework.AssertionFailedError; import junit.framework.TestCase; -import org.apache.poi.hssf.model.FormulaParser.FormulaParseException; import org.apache.poi.hssf.record.formula.FuncVarPtg; import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.Ptg; @@ -30,6 +30,7 @@ import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +import org.apache.poi.ss.formula.FormulaParserTestHelper; /** * Test the low level formula parser functionality, @@ -51,21 +52,21 @@ public final class TestFormulaParserEval extends TestCase { name.setNameName("testName"); name.setReference("A1:A2"); - ptgs = FormulaParser.parse("SUM(testName)", workbook); + ptgs = HSSFFormulaParser.parse("SUM(testName)", workbook); assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2); assertEquals(NamePtg.class, ptgs[0].getClass()); assertEquals(FuncVarPtg.class, ptgs[1].getClass()); // Now make it a single cell name.setReference("C3"); - ptgs = FormulaParser.parse("SUM(testName)", workbook); + ptgs = HSSFFormulaParser.parse("SUM(testName)", workbook); assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2); assertEquals(NamePtg.class, ptgs[0].getClass()); assertEquals(FuncVarPtg.class, ptgs[1].getClass()); // And make it non-contiguous name.setReference("A1:A2,C3"); - ptgs = FormulaParser.parse("SUM(testName)", workbook); + ptgs = HSSFFormulaParser.parse("SUM(testName)", workbook); assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2); assertEquals(NamePtg.class, ptgs[0].getClass()); assertEquals(FuncVarPtg.class, ptgs[1].getClass()); @@ -89,11 +90,12 @@ public final class TestFormulaParserEval extends TestCase { CellValue result; try { result = fe.evaluate(cell); - } catch (FormulaParseException e) { - if(e.getMessage().equals("Found reference to named range \"A\", but that named range wasn't defined!")) { - fail("Identifed bug 44539"); + } catch (RuntimeException e) { + FormulaParserTestHelper.confirmParseException(e); + if (!e.getMessage().equals("Found reference to named range \"A\", but that named range wasn't defined!")) { + throw new AssertionFailedError("Identifed bug 44539"); } - throw new RuntimeException(e); + throw e; } assertEquals(HSSFCell.CELL_TYPE_NUMERIC, result.getCellType()); assertEquals(42.0, result.getNumberValue(), 0.0); diff --git a/src/testcases/org/apache/poi/hssf/model/TestOperandClassTransformer.java b/src/testcases/org/apache/poi/hssf/model/TestOperandClassTransformer.java index 47cfb574c..530c2b09c 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestOperandClassTransformer.java +++ b/src/testcases/org/apache/poi/hssf/model/TestOperandClassTransformer.java @@ -23,6 +23,7 @@ import junit.framework.TestCase; import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; import org.apache.poi.hssf.record.formula.FuncVarPtg; import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; /** * Tests specific formula examples in OperandClassTransformer. @@ -31,9 +32,15 @@ import org.apache.poi.hssf.record.formula.Ptg; */ public final class TestOperandClassTransformer extends TestCase { + private static Ptg[] parseFormula(String formula) { + Ptg[] result = HSSFFormulaParser.parse(formula, (HSSFWorkbook)null); + assertNotNull("Ptg array should not be null", result); + return result; + } + public void testMdeterm() { String formula = "MDETERM(ABS(A1))"; - Ptg[] ptgs = FormulaParser.parse(formula, null); + Ptg[] ptgs = parseFormula(formula); confirmTokenClass(ptgs, 0, Ptg.CLASS_ARRAY); confirmFuncClass(ptgs, 1, "ABS", Ptg.CLASS_ARRAY); @@ -51,7 +58,7 @@ public final class TestOperandClassTransformer extends TestCase { */ public void DISABLED_testIndexPi1() { String formula = "INDEX(PI(),1)"; - Ptg[] ptgs = FormulaParser.parse(formula, null); + Ptg[] ptgs = parseFormula(formula); confirmFuncClass(ptgs, 1, "PI", Ptg.CLASS_ARRAY); // fails as of POI 3.1 confirmFuncClass(ptgs, 2, "INDEX", Ptg.CLASS_VALUE); @@ -63,7 +70,7 @@ public final class TestOperandClassTransformer extends TestCase { */ public void testDirectOperandOfValueOperator() { String formula = "COUNT(A1*1)"; - Ptg[] ptgs = FormulaParser.parse(formula, null); + Ptg[] ptgs = parseFormula(formula); if (ptgs[0].getPtgClass() == Ptg.CLASS_REF) { throw new AssertionFailedError("Identified bug 45348"); } @@ -78,13 +85,13 @@ public final class TestOperandClassTransformer extends TestCase { public void testRtoV() { String formula = "lookup(A1, A3:A52, B3:B52)"; - Ptg[] ptgs = FormulaParser.parse(formula, null); + Ptg[] ptgs = parseFormula(formula); confirmTokenClass(ptgs, 0, Ptg.CLASS_VALUE); } public void testComplexIRR_bug45041() { String formula = "(1+IRR(SUMIF(A:A,ROW(INDIRECT(MIN(A:A)&\":\"&MAX(A:A))),B:B),0))^365-1"; - Ptg[] ptgs = FormulaParser.parse(formula, null); + Ptg[] ptgs = parseFormula(formula); FuncVarPtg rowFunc = (FuncVarPtg) ptgs[10]; FuncVarPtg sumifFunc = (FuncVarPtg) ptgs[12]; diff --git a/src/testcases/org/apache/poi/hssf/model/TestRVA.java b/src/testcases/org/apache/poi/hssf/model/TestRVA.java index 1ec7c92ae..eb69a5cd0 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestRVA.java +++ b/src/testcases/org/apache/poi/hssf/model/TestRVA.java @@ -82,7 +82,7 @@ public final class TestRVA extends TestCase { private void confirmCell(HSSFCell formulaCell, String formula, HSSFWorkbook wb) { Ptg[] excelPtgs = FormulaExtractor.getPtgs(formulaCell); - Ptg[] poiPtgs = FormulaParser.parse(formula, wb); + Ptg[] poiPtgs = HSSFFormulaParser.parse(formula, wb); int nExcelTokens = excelPtgs.length; int nPoiTokens = poiPtgs.length; if (nExcelTokens != nPoiTokens) { diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java b/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java index f5b80f6da..84f689774 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java @@ -20,7 +20,7 @@ package org.apache.poi.hssf.record.formula; import junit.framework.TestCase; -import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.usermodel.HSSFWorkbook; /** @@ -87,7 +87,7 @@ public final class TestAreaPtg extends TestCase { private static String shiftAllColumnsBy1(String formula) { int letUsShiftColumn1By1Column=1; HSSFWorkbook wb = null; - Ptg[] ptgs = FormulaParser.parse(formula, wb); + Ptg[] ptgs = HSSFFormulaParser.parse(formula, wb); for(int i=0; i