From: Josh Micich Date: Fri, 19 Sep 2008 07:32:34 +0000 (+0000) Subject: Merged revisions 696860,696898 via svnmerge from X-Git-Tag: REL_3_5_BETA3~3 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=02b99aeeddaa8208324e4703feb7fec1f9f6e75a;p=poi.git Merged revisions 696860,696898 via svnmerge from https://svn.apache.org/repos/asf/poi/trunk Quite a lot of manual fixing required. Also moved CellValue to top level class. ........ r696860 | josh | 2008-09-18 17:02:21 -0700 (Thu, 18 Sep 2008) | 1 line code clean-up (removed compiler warnings/unused methods) ........ r696898 | josh | 2008-09-18 19:19:58 -0700 (Thu, 18 Sep 2008) | 1 line Partitioning common formula logic. Introduced FormulaParsingWorkbook and EvaluationWorkbook interfaces to make merge with ooxml branch easier ........ git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@696961 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/java/org/apache/poi/hssf/dev/FormulaViewer.java b/src/java/org/apache/poi/hssf/dev/FormulaViewer.java index 167eaba532..d42cda71a3 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 3c767dc25a..c7a1471a7d 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 2ea35c773e..430dcd4758 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/FormulaParser.java b/src/java/org/apache/poi/hssf/model/FormulaParser.java deleted file mode 100644 index e849dbf6eb..0000000000 --- a/src/java/org/apache/poi/hssf/model/FormulaParser.java +++ /dev/null @@ -1,1028 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.model; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.poi.hssf.record.UnicodeString; -import org.apache.poi.hssf.record.constant.ErrorConstant; -import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; -import org.apache.poi.hssf.record.formula.AddPtg; -import org.apache.poi.hssf.record.formula.Area3DPtg; -import org.apache.poi.hssf.record.formula.AreaPtg; -import org.apache.poi.hssf.record.formula.ArrayPtg; -import org.apache.poi.hssf.record.formula.BoolPtg; -import org.apache.poi.hssf.record.formula.ConcatPtg; -import org.apache.poi.hssf.record.formula.DividePtg; -import org.apache.poi.hssf.record.formula.EqualPtg; -import org.apache.poi.hssf.record.formula.ErrPtg; -import org.apache.poi.hssf.record.formula.FuncPtg; -import org.apache.poi.hssf.record.formula.FuncVarPtg; -import org.apache.poi.hssf.record.formula.GreaterEqualPtg; -import org.apache.poi.hssf.record.formula.GreaterThanPtg; -import org.apache.poi.hssf.record.formula.IntPtg; -import org.apache.poi.hssf.record.formula.LessEqualPtg; -import org.apache.poi.hssf.record.formula.LessThanPtg; -import org.apache.poi.hssf.record.formula.MissingArgPtg; -import org.apache.poi.hssf.record.formula.MultiplyPtg; -import org.apache.poi.hssf.record.formula.NamePtg; -import org.apache.poi.hssf.record.formula.NameXPtg; -import org.apache.poi.hssf.record.formula.NotEqualPtg; -import org.apache.poi.hssf.record.formula.NumberPtg; -import org.apache.poi.hssf.record.formula.ParenthesisPtg; -import org.apache.poi.hssf.record.formula.PercentPtg; -import org.apache.poi.hssf.record.formula.PowerPtg; -import org.apache.poi.hssf.record.formula.Ptg; -import org.apache.poi.hssf.record.formula.Ref3DPtg; -import org.apache.poi.hssf.record.formula.RefPtg; -import org.apache.poi.hssf.record.formula.StringPtg; -import org.apache.poi.hssf.record.formula.SubtractPtg; -import org.apache.poi.hssf.record.formula.UnaryMinusPtg; -import org.apache.poi.hssf.record.formula.UnaryPlusPtg; -import org.apache.poi.hssf.record.formula.function.FunctionMetadata; -import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; -import org.apache.poi.ss.usermodel.Name; -import org.apache.poi.ss.usermodel.Workbook; -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.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. - * Inspired by - * Lets Build a Compiler, by Jack Crenshaw - * BNF for the formula expression is : - * ::= [ ]* - * ::= [ ]* - * ::= | () | | - * ::= ([expression [, expression]*]) - * - * @author Avik Sengupta - * @author Andrew C. oliver (acoliver at apache dot org) - * @author Eric Ladner (eladner at goldinc dot com) - * @author Cameron Riley (criley at ekmail.com) - * @author Peter M. Murray (pete at quantrix dot com) - * @author Pavel Krupets (pkrupets at palmtreebusiness dot com) - */ -public final class FormulaParser { - - /** - * Specific exception thrown when a supplied formula does not parse properly.
- * Primarily used by test cases when testing for specific parsing exceptions.

- * - */ - static final class FormulaParseException extends RuntimeException { - // This class was given package scope until it would become clear that it is useful to - // general client code. - public FormulaParseException(String msg) { - super(msg); - } - } - - 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; - private int pointer; - - private ParseNode _rootNode; - - private static char TAB = '\t'; - - /** - * Lookahead Character. - * gets value '\0' when the input string is exhausted - */ - private char look; - - private Workbook book; - - - /** - * Create the formula parser, with the string that is to be - * parsed against the supplied workbook. - * A later call the parse() method to return ptg list in - * rpn order, then call the getRPNPtg() to retrive the - * parse results. - * This class is recommended only for single threaded use. - * - * If you only have a usermodel.Workbook, and not a - * model.Workbook, then use the convenience method on - * usermodel.HSSFFormulaEvaluator - */ - public FormulaParser(String formula, Workbook book){ - formulaString = formula; - pointer=0; - this.book = book; - formulaLength = formulaString.length(); - } - - public static Ptg[] parse(String formula, Workbook book) { - return parse(formula, book, FORMULA_TYPE_CELL); - } - - public static Ptg[] parse(String formula, Workbook workbook, int formulaType) { - FormulaParser fp = HSSFFormulaEvaluator.getUnderlyingParser(workbook, formula); - fp.parse(); - return fp.getRPNPtg(formulaType); - } - - /** Read New Character From Input Stream */ - private void GetChar() { - // Check to see if we've walked off the end of the string. - if (pointer > formulaLength) { - throw new RuntimeException("too far"); - } - if (pointer < formulaLength) { - look=formulaString.charAt(pointer); - } else { - // Just return if so and reset 'look' to something to keep - // SkipWhitespace from spinning - look = (char)0; - } - pointer++; - //System.out.println("Got char: "+ look); - } - - /** Report What Was Expected */ - private RuntimeException expected(String s) { - String msg; - - if (look == '=' && formulaString.substring(0, pointer-1).trim().length() < 1) { - msg = "The specified formula '" + formulaString - + "' starts with an equals sign which is not allowed."; - } else { - msg = "Parse error near char " + (pointer-1) + " '" + look + "'" - + " in specified formula '" + formulaString + "'. Expected " - + s; - } - return new FormulaParseException(msg); - } - - /** Recognize an Alpha Character */ - private boolean IsAlpha(char c) { - return Character.isLetter(c) || c == '$' || c=='_'; - } - - /** Recognize a Decimal Digit */ - private boolean IsDigit(char c) { - return Character.isDigit(c); - } - - /** Recognize an Alphanumeric */ - private boolean IsAlNum(char c) { - return (IsAlpha(c) || IsDigit(c)); - } - - /** Recognize White Space */ - private boolean IsWhite( char c) { - return (c ==' ' || c== TAB); - } - - /** Skip Over Leading White Space */ - private void SkipWhite() { - while (IsWhite(look)) { - GetChar(); - } - } - - /** - * Consumes the next input character if it is equal to the one specified otherwise throws an - * unchecked exception. This method does not consume whitespace (before or after the - * matched character). - */ - private void Match(char x) { - if (look != x) { - throw expected("'" + x + "'"); - } - GetChar(); - } - - /** - * Parses a sheet name, named range name, or simple cell reference.
- * Note - identifiers in Excel can contain dots, so this method may return a String - * which may need to be converted to an area reference. For example, this method - * may return a value like "A1..B2", in which case the caller must convert it to - * an area reference like "A1:B2" - */ - private String parseIdentifier() { - StringBuffer Token = new StringBuffer(); - if (!IsAlpha(look) && look != '\'') { - throw expected("Name"); - } - if(look == '\'') - { - Match('\''); - boolean done = look == '\''; - while(!done) - { - Token.append(look); - GetChar(); - if(look == '\'') - { - Match('\''); - done = look != '\''; - } - } - } - else - { - // allow for any sequence of dots and identifier chars - // special case of two consecutive dots is best treated in the calling code - while (IsAlNum(look) || look == '.') { - Token.append(look); - GetChar(); - } - } - return Token.toString(); - } - - /** Get a Number */ - private String GetNum() { - StringBuffer value = new StringBuffer(); - - while (IsDigit(this.look)){ - value.append(this.look); - GetChar(); - } - return value.length() == 0 ? null : value.toString(); - } - - private ParseNode parseFunctionReferenceOrName() { - String name = parseIdentifier(); - if (look == '('){ - //This is a function - return function(name); - } - return new ParseNode(parseNameOrReference(name)); - } - - private Ptg parseNameOrReference(String name) { - - AreaReference areaRef = parseArea(name); - if (areaRef != null) { - // will happen if dots are used instead of colon - return new AreaPtg(areaRef.formatAsString()); - } - - if (look == ':' || look == '.') { // this is a AreaReference - GetChar(); - - while (look == '.') { // formulas can have . or .. or ... instead of : - GetChar(); - } - - String first = name; - String second = parseIdentifier(); - return new AreaPtg(first+":"+second); - } - - if (look == '!') { - Match('!'); - String sheetName = name; - String first = parseIdentifier(); - short externIdx = (short)book.getExternalSheetIndex(book.getSheetIndex(sheetName)); - areaRef = parseArea(name); - if (areaRef != null) { - // will happen if dots are used instead of colon - return new Area3DPtg(areaRef.formatAsString(), externIdx); - } - if (look == ':') { - Match(':'); - String second=parseIdentifier(); - if (look == '!') { - //The sheet name was included in both of the areas. Only really - //need it once - Match('!'); - String third=parseIdentifier(); - - if (!sheetName.equals(second)) - throw new RuntimeException("Unhandled double sheet reference."); - - return new Area3DPtg(first+":"+third,externIdx); - } - return new Area3DPtg(first+":"+second,externIdx); - } - return new Ref3DPtg(first,externIdx); - } - if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) { - return new BoolPtg(name.toUpperCase()); - } - - // This can be either a cell ref or a named range - // Try to spot which it is - int nameType = CellReference.classifyCellReference(name); - if (nameType == NameType.CELL) { - return new RefPtg(name); - } - if (nameType != NameType.NAMED_RANGE) { - 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 '" - + name + "' does not exist in the current workbook."); - } - - /** - * @param name an 'identifier' like string (i.e. contains alphanums, and dots) - * @return null if name cannot be split at a dot - */ - private AreaReference parseArea(String name) { - int dotPos = name.indexOf('.'); - if (dotPos < 0) { - return null; - } - int dotCount = 1; - while (dotCount3) { - // four or more consecutive dots does not convert to ':' - return null; - } - } - // This expression is only valid as an area ref, if the LHS and RHS of the dot(s) are both - // cell refs. Otherwise, this expression must be a named range name - String partA = name.substring(0, dotPos); - if (!isValidCellReference(partA)) { - return null; - } - String partB = name.substring(dotPos+dotCount); - if (!isValidCellReference(partB)) { - return null; - } - CellReference topLeft = new CellReference(partA); - CellReference bottomRight = new CellReference(partB); - return new AreaReference(topLeft, bottomRight); - } - - /** - * @return true if the specified name is a valid cell reference - */ - private static boolean isValidCellReference(String str) { - return CellReference.classifyCellReference(str) == NameType.CELL; - } - - - /** - * Note - Excel function names are 'case aware but not case sensitive'. This method may end - * up creating a defined name record in the workbook if the specified name is not an internal - * Excel function, and has not been encountered before. - * - * @param name case preserved function name (as it was entered/appeared in the formula). - */ - private ParseNode function(String name) { - Ptg nameToken = null; - if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) { - // user defined function - // in the token tree, the name is more or less the first argument - int nameIndex = book.getNameIndex(name); - if (nameIndex >= 0) { - Name hName = book.getNameAt(nameIndex); - 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"); - } - - // calls to user-defined functions within the workbook - // get a Name token which points to a defined name record - nameToken = new NamePtg(nameIndex); - } else { - if(book instanceof HSSFWorkbook) { - nameToken = ((HSSFWorkbook)book).getNameXPtg(name); - } - if (nameToken == null) { - throw new FormulaParseException("Name '" + name - + "' is completely unknown in the current workbook"); - } - } - } - - Match('('); - ParseNode[] args = Arguments(); - Match(')'); - - return getFunction(name, nameToken, args); - } - - /** - * Generates the variable function ptg for the formula. - *

- * For IF Formulas, additional PTGs are added to the tokens - * @param name a {@link NamePtg} or {@link NameXPtg} or null - * @param numArgs - * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function - */ - private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) { - - FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase()); - int numArgs = args.length; - if(fm == null) { - if (namePtg == null) { - throw new IllegalStateException("NamePtg must be supplied for external functions"); - } - // must be external function - ParseNode[] allArgs = new ParseNode[numArgs+1]; - allArgs[0] = new ParseNode(namePtg); - System.arraycopy(args, 0, allArgs, 1, numArgs); - return new ParseNode(new FuncVarPtg(name, (byte)(numArgs+1)), allArgs); - } - - if (namePtg != null) { - throw new IllegalStateException("NamePtg no applicable to internal functions"); - } - boolean isVarArgs = !fm.hasFixedArgsLength(); - int funcIx = fm.getIndex(); - validateNumArgs(args.length, fm); - - AbstractFunctionPtg retval; - if(isVarArgs) { - retval = new FuncVarPtg(name, (byte)numArgs); - } else { - retval = new FuncPtg(funcIx); - } - return new ParseNode(retval, args); - } - - private void validateNumArgs(int numArgs, FunctionMetadata fm) { - if(numArgs < fm.getMinParams()) { - String msg = "Too few arguments to function '" + fm.getName() + "'. "; - if(fm.hasFixedArgsLength()) { - msg += "Expected " + fm.getMinParams(); - } else { - msg += "At least " + fm.getMinParams() + " were expected"; - } - msg += " but got " + numArgs + "."; - throw new FormulaParseException(msg); - } - if(numArgs > fm.getMaxParams()) { - String msg = "Too many arguments to function '" + fm.getName() + "'. "; - if(fm.hasFixedArgsLength()) { - msg += "Expected " + fm.getMaxParams(); - } else { - msg += "At most " + fm.getMaxParams() + " were expected"; - } - msg += " but got " + numArgs + "."; - throw new FormulaParseException(msg); - } - } - - private static boolean isArgumentDelimiter(char ch) { - return ch == ',' || ch == ')'; - } - - /** get arguments to a function */ - private ParseNode[] Arguments() { - //average 2 args per function - List temp = new ArrayList(2); - SkipWhite(); - if(look == ')') { - return ParseNode.EMPTY_ARRAY; - } - - boolean missedPrevArg = true; - int numArgs = 0; - while (true) { - SkipWhite(); - if (isArgumentDelimiter(look)) { - if (missedPrevArg) { - temp.add(new ParseNode(MissingArgPtg.instance)); - numArgs++; - } - if (look == ')') { - break; - } - Match(','); - missedPrevArg = true; - continue; - } - temp.add(comparisonExpression()); - numArgs++; - missedPrevArg = false; - SkipWhite(); - if (!isArgumentDelimiter(look)) { - throw expected("',' or ')'"); - } - } - ParseNode[] result = new ParseNode[temp.size()]; - temp.toArray(result); - return result; - } - - /** Parse and Translate a Math Factor */ - private ParseNode powerFactor() { - ParseNode result = percentFactor(); - while(true) { - SkipWhite(); - if(look != '^') { - return result; - } - Match('^'); - ParseNode other = percentFactor(); - result = new ParseNode(PowerPtg.instance, result, other); - } - } - - private ParseNode percentFactor() { - ParseNode result = parseSimpleFactor(); - while(true) { - SkipWhite(); - if(look != '%') { - return result; - } - Match('%'); - result = new ParseNode(PercentPtg.instance, result); - } - } - - - /** - * factors (without ^ or % ) - */ - private ParseNode parseSimpleFactor() { - SkipWhite(); - switch(look) { - case '#': - return new ParseNode(ErrPtg.valueOf(parseErrorLiteral())); - case '-': - Match('-'); - return new ParseNode(UnaryMinusPtg.instance, powerFactor()); - case '+': - Match('+'); - return new ParseNode(UnaryPlusPtg.instance, powerFactor()); - case '(': - Match('('); - ParseNode inside = comparisonExpression(); - Match(')'); - return new ParseNode(ParenthesisPtg.instance, inside); - case '"': - return new ParseNode(new StringPtg(parseStringLiteral())); - case '{': - Match('{'); - ParseNode arrayNode = parseArray(); - Match('}'); - return arrayNode; - } - if (IsAlpha(look) || look == '\''){ - return parseFunctionReferenceOrName(); - } - // else - assume number - return new ParseNode(parseNumber()); - } - - - private ParseNode parseArray() { - List rowsData = new ArrayList(); - while(true) { - Object[] singleRowData = parseArrayRow(); - rowsData.add(singleRowData); - if (look == '}') { - break; - } - if (look != ';') { - throw expected("'}' or ';'"); - } - Match(';'); - } - int nRows = rowsData.size(); - Object[][] values2d = new Object[nRows][]; - rowsData.toArray(values2d); - int nColumns = values2d[0].length; - checkRowLengths(values2d, nColumns); - - return new ParseNode(new ArrayPtg(values2d)); - } - private void checkRowLengths(Object[][] values2d, int nColumns) { - for (int i = 0; i < values2d.length; i++) { - int rowLen = values2d[i].length; - if (rowLen != nColumns) { - throw new FormulaParseException("Array row " + i + " has length " + rowLen - + " but row 0 has length " + nColumns); - } - } - } - - private Object[] parseArrayRow() { - List temp = new ArrayList(); - while (true) { - temp.add(parseArrayItem()); - SkipWhite(); - switch(look) { - case '}': - case ';': - break; - case ',': - Match(','); - continue; - default: - throw expected("'}' or ','"); - - } - break; - } - - Object[] result = new Object[temp.size()]; - temp.toArray(result); - return result; - } - - private Object parseArrayItem() { - SkipWhite(); - switch(look) { - case '"': return new UnicodeString(parseStringLiteral()); - case '#': return ErrorConstant.valueOf(parseErrorLiteral()); - case 'F': case 'f': - case 'T': case 't': - return parseBooleanLiteral(); - } - // else assume number - return convertArrayNumber(parseNumber()); - } - - private Boolean parseBooleanLiteral() { - String iden = parseIdentifier(); - if ("TRUE".equalsIgnoreCase(iden)) { - return Boolean.TRUE; - } - if ("FALSE".equalsIgnoreCase(iden)) { - return Boolean.FALSE; - } - throw expected("'TRUE' or 'FALSE'"); - } - - private static Double convertArrayNumber(Ptg ptg) { - if (ptg instanceof IntPtg) { - return new Double(((IntPtg)ptg).getValue()); - } - if (ptg instanceof NumberPtg) { - return new Double(((NumberPtg)ptg).getValue()); - } - throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")"); - } - - private Ptg parseNumber() { - String number2 = null; - String exponent = null; - String number1 = GetNum(); - - if (look == '.') { - GetChar(); - number2 = GetNum(); - } - - if (look == 'E') { - GetChar(); - - String sign = ""; - if (look == '+') { - GetChar(); - } else if (look == '-') { - GetChar(); - sign = "-"; - } - - String number = GetNum(); - if (number == null) { - throw expected("Integer"); - } - exponent = sign + number; - } - - if (number1 == null && number2 == null) { - throw expected("Integer"); - } - - return getNumberPtgFromString(number1, number2, exponent); - } - - - private int parseErrorLiteral() { - Match('#'); - String part1 = parseIdentifier().toUpperCase(); - - switch(part1.charAt(0)) { - case 'V': - if(part1.equals("VALUE")) { - Match('!'); - return HSSFErrorConstants.ERROR_VALUE; - } - throw expected("#VALUE!"); - case 'R': - if(part1.equals("REF")) { - Match('!'); - return HSSFErrorConstants.ERROR_REF; - } - throw expected("#REF!"); - case 'D': - if(part1.equals("DIV")) { - Match('/'); - Match('0'); - Match('!'); - return HSSFErrorConstants.ERROR_DIV_0; - } - throw expected("#DIV/0!"); - case 'N': - if(part1.equals("NAME")) { - Match('?'); // only one that ends in '?' - return HSSFErrorConstants.ERROR_NAME; - } - if(part1.equals("NUM")) { - Match('!'); - return HSSFErrorConstants.ERROR_NUM; - } - if(part1.equals("NULL")) { - Match('!'); - return HSSFErrorConstants.ERROR_NULL; - } - if(part1.equals("N")) { - Match('/'); - if(look != 'A' && look != 'a') { - throw expected("#N/A"); - } - Match(look); - // Note - no '!' or '?' suffix - return HSSFErrorConstants.ERROR_NA; - } - throw expected("#NAME?, #NUM!, #NULL! or #N/A"); - - } - throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A"); - } - - - /** - * Get a PTG for an integer from its string representation. - * return Int or Number Ptg based on size of input - */ - private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) { - StringBuffer number = new StringBuffer(); - - if (number2 == null) { - number.append(number1); - - if (exponent != null) { - number.append('E'); - number.append(exponent); - } - - String numberStr = number.toString(); - int intVal; - try { - intVal = Integer.parseInt(numberStr); - } catch (NumberFormatException e) { - return new NumberPtg(numberStr); - } - if (IntPtg.isInRange(intVal)) { - return new IntPtg(intVal); - } - return new NumberPtg(numberStr); - } - - if (number1 != null) { - number.append(number1); - } - - number.append('.'); - number.append(number2); - - if (exponent != null) { - number.append('E'); - number.append(exponent); - } - - return new NumberPtg(number.toString()); - } - - - private String parseStringLiteral() { - Match('"'); - - StringBuffer token = new StringBuffer(); - while (true) { - if (look == '"') { - GetChar(); - if (look != '"') { - break; - } - } - token.append(look); - GetChar(); - } - return token.toString(); - } - - /** Parse and Translate a Math Term */ - private ParseNode Term() { - ParseNode result = powerFactor(); - while(true) { - SkipWhite(); - Ptg operator; - switch(look) { - case '*': - Match('*'); - operator = MultiplyPtg.instance; - break; - case '/': - Match('/'); - operator = DividePtg.instance; - break; - default: - return result; // finished with Term - } - ParseNode other = powerFactor(); - result = new ParseNode(operator, result, other); - } - } - - private ParseNode comparisonExpression() { - ParseNode result = concatExpression(); - while (true) { - SkipWhite(); - switch(look) { - case '=': - case '>': - case '<': - Ptg comparisonToken = getComparisonToken(); - ParseNode other = concatExpression(); - result = new ParseNode(comparisonToken, result, other); - continue; - } - return result; // finished with predicate expression - } - } - - private Ptg getComparisonToken() { - if(look == '=') { - Match(look); - return EqualPtg.instance; - } - boolean isGreater = look == '>'; - Match(look); - if(isGreater) { - if(look == '=') { - Match('='); - return GreaterEqualPtg.instance; - } - return GreaterThanPtg.instance; - } - switch(look) { - case '=': - Match('='); - return LessEqualPtg.instance; - case '>': - Match('>'); - return NotEqualPtg.instance; - } - return LessThanPtg.instance; - } - - - private ParseNode concatExpression() { - ParseNode result = additiveExpression(); - while (true) { - SkipWhite(); - if(look != '&') { - break; // finished with concat expression - } - Match('&'); - ParseNode other = additiveExpression(); - result = new ParseNode(ConcatPtg.instance, result, other); - } - return result; - } - - - /** Parse and Translate an Expression */ - private ParseNode additiveExpression() { - ParseNode result = Term(); - while (true) { - SkipWhite(); - Ptg operator; - switch(look) { - case '+': - Match('+'); - operator = AddPtg.instance; - break; - case '-': - Match('-'); - operator = SubtractPtg.instance; - break; - default: - return result; // finished with additive expression - } - ParseNode other = Term(); - result = new ParseNode(operator, result, other); - } - } - - //{--------------------------------------------------------------} - //{ Parse and Translate an Assignment Statement } - /** -procedure Assignment; -var Name: string[8]; -begin - Name := GetName; - Match('='); - Expression; - -end; - **/ - - - /** - * API call to execute the parsing of the formula - * @deprecated use {@link #parse(String, Workbook)} directly - */ - public void parse() { - pointer=0; - GetChar(); - _rootNode = comparisonExpression(); - - if(pointer <= formulaLength) { - String msg = "Unused input [" + formulaString.substring(pointer-1) - + "] after attempting to parse the formula [" + formulaString + "]"; - throw new FormulaParseException(msg); - } - } - - - /********************************* - * 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) { - 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; - } - - /** - * 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/hssf/model/HSSFFormulaParser.java b/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java new file mode 100644 index 0000000000..c492c5e86e --- /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/model/OperandClassTransformer.java b/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java deleted file mode 100644 index 8b7b56638b..0000000000 --- a/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java +++ /dev/null @@ -1,259 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.model; - -import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; -import org.apache.poi.hssf.record.formula.ControlPtg; -import org.apache.poi.hssf.record.formula.ValueOperatorPtg; -import org.apache.poi.hssf.record.formula.Ptg; - -/** - * This class performs 'operand class' transformation. Non-base tokens are classified into three - * operand classes: - *

    - *
  • reference
  • - *
  • value
  • - *
  • array
  • - *
- *

- * - * The final operand class chosen for each token depends on the formula type and the token's place - * in the formula. If POI gets the operand class wrong, Excel may interpret the formula - * incorrectly. This condition is typically manifested as a formula cell that displays as '#VALUE!', - * but resolves correctly when the user presses F2, enter.

- * - * The logic implemented here was partially inspired by the description in - * "OpenOffice.org's Documentation of the Microsoft Excel File Format". The model presented there - * seems to be inconsistent with observed Excel behaviour (These differences have not been fully - * investigated). The implementation in this class has been heavily modified in order to satisfy - * concrete examples of how Excel performs the same logic (see TestRVA).

- * - * Hopefully, as additional important test cases are identified and added to the test suite, - * patterns might become more obvious in this code and allow for simplification. - * - * @author Josh Micich - */ -final class OperandClassTransformer { - - private final int _formulaType; - - public OperandClassTransformer(int formulaType) { - _formulaType = formulaType; - } - - /** - * Traverses the supplied formula parse tree, calling Ptg.setClass() for each non-base - * token to set its operand class. - */ - public void transformFormula(ParseNode rootNode) { - byte rootNodeOperandClass; - switch (_formulaType) { - case FormulaParser.FORMULA_TYPE_CELL: - rootNodeOperandClass = Ptg.CLASS_VALUE; - break; - case FormulaParser.FORMULA_TYPE_DATAVALIDATION_LIST: - rootNodeOperandClass = Ptg.CLASS_REF; - break; - default: - throw new RuntimeException("Incomplete code - formula type (" - + _formulaType + ") not supported yet"); - - } - transformNode(rootNode, rootNodeOperandClass, false); - } - - /** - * @param callerForceArrayFlag true if one of the current node's parents is a - * function Ptg which has been changed from default 'V' to 'A' type (due to requirements on - * the function return value). - */ - private void transformNode(ParseNode node, byte desiredOperandClass, - boolean callerForceArrayFlag) { - Ptg token = node.getToken(); - ParseNode[] children = node.getChildren(); - boolean isSimpleValueFunc = isSimpleValueFunction(token); - - if (isSimpleValueFunc) { - boolean localForceArray = desiredOperandClass == Ptg.CLASS_ARRAY; - for (int i = 0; i < children.length; i++) { - transformNode(children[i], desiredOperandClass, localForceArray); - } - setSimpleValueFuncClass((AbstractFunctionPtg) token, desiredOperandClass, callerForceArrayFlag); - return; - } - - if (token instanceof ValueOperatorPtg || token instanceof ControlPtg) { - // Value Operator Ptgs and Control are base tokens, so token will be unchanged - // but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag - - // As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1" - // All direct operands of value operators that are initially 'R' type will - // be converted to 'V' type. - byte localDesiredOperandClass = desiredOperandClass == Ptg.CLASS_REF ? Ptg.CLASS_VALUE : desiredOperandClass; - for (int i = 0; i < children.length; i++) { - transformNode(children[i], localDesiredOperandClass, callerForceArrayFlag); - } - return; - } - if (token instanceof AbstractFunctionPtg) { - transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass, callerForceArrayFlag); - return; - } - if (children.length > 0) { - throw new IllegalStateException("Node should not have any children"); - } - - if (token.isBaseToken()) { - // nothing to do - return; - } - token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag)); - } - - private static boolean isSimpleValueFunction(Ptg token) { - if (token instanceof AbstractFunctionPtg) { - AbstractFunctionPtg aptg = (AbstractFunctionPtg) token; - if (aptg.getDefaultOperandClass() != Ptg.CLASS_VALUE) { - return false; - } - int numberOfOperands = aptg.getNumberOfOperands(); - for (int i=numberOfOperands-1; i>=0; i--) { - if (aptg.getParameterClass(i) != Ptg.CLASS_VALUE) { - return false; - } - } - return true; - } - return false; - } - - private byte transformClass(byte currentOperandClass, byte desiredOperandClass, - boolean callerForceArrayFlag) { - switch (desiredOperandClass) { - case Ptg.CLASS_VALUE: - if (!callerForceArrayFlag) { - return Ptg.CLASS_VALUE; - } - // else fall through - case Ptg.CLASS_ARRAY: - return Ptg.CLASS_ARRAY; - case Ptg.CLASS_REF: - if (!callerForceArrayFlag) { - return currentOperandClass; - } - return Ptg.CLASS_REF; - } - throw new IllegalStateException("Unexpected operand class (" + desiredOperandClass + ")"); - } - - private void transformFunctionNode(AbstractFunctionPtg afp, ParseNode[] children, - byte desiredOperandClass, boolean callerForceArrayFlag) { - - boolean localForceArrayFlag; - byte defaultReturnOperandClass = afp.getDefaultOperandClass(); - - if (callerForceArrayFlag) { - switch (defaultReturnOperandClass) { - case Ptg.CLASS_REF: - if (desiredOperandClass == Ptg.CLASS_REF) { - afp.setClass(Ptg.CLASS_REF); - } else { - afp.setClass(Ptg.CLASS_ARRAY); - } - localForceArrayFlag = false; - break; - case Ptg.CLASS_ARRAY: - afp.setClass(Ptg.CLASS_ARRAY); - localForceArrayFlag = false; - break; - case Ptg.CLASS_VALUE: - afp.setClass(Ptg.CLASS_ARRAY); - localForceArrayFlag = true; - break; - default: - throw new IllegalStateException("Unexpected operand class (" - + defaultReturnOperandClass + ")"); - } - } else { - if (defaultReturnOperandClass == desiredOperandClass) { - localForceArrayFlag = false; - // an alternative would have been to for non-base Ptgs to set their operand class - // from their default, but this would require the call in many subclasses because - // the default OC is not known until the end of the constructor - afp.setClass(defaultReturnOperandClass); - } else { - switch (desiredOperandClass) { - case Ptg.CLASS_VALUE: - // always OK to set functions to return 'value' - afp.setClass(Ptg.CLASS_VALUE); - localForceArrayFlag = false; - break; - case Ptg.CLASS_ARRAY: - switch (defaultReturnOperandClass) { - case Ptg.CLASS_REF: - afp.setClass(Ptg.CLASS_REF); -// afp.setClass(Ptg.CLASS_ARRAY); - break; - case Ptg.CLASS_VALUE: - afp.setClass(Ptg.CLASS_ARRAY); - break; - default: - throw new IllegalStateException("Unexpected operand class (" - + defaultReturnOperandClass + ")"); - } - localForceArrayFlag = (defaultReturnOperandClass == Ptg.CLASS_VALUE); - break; - case Ptg.CLASS_REF: - switch (defaultReturnOperandClass) { - case Ptg.CLASS_ARRAY: - afp.setClass(Ptg.CLASS_ARRAY); - break; - case Ptg.CLASS_VALUE: - afp.setClass(Ptg.CLASS_VALUE); - break; - default: - throw new IllegalStateException("Unexpected operand class (" - + defaultReturnOperandClass + ")"); - } - localForceArrayFlag = false; - break; - default: - throw new IllegalStateException("Unexpected operand class (" - + desiredOperandClass + ")"); - } - - } - } - - for (int i = 0; i < children.length; i++) { - ParseNode child = children[i]; - byte paramOperandClass = afp.getParameterClass(i); - transformNode(child, paramOperandClass, localForceArrayFlag); - } - } - - private void setSimpleValueFuncClass(AbstractFunctionPtg afp, - byte desiredOperandClass, boolean callerForceArrayFlag) { - - if (callerForceArrayFlag || desiredOperandClass == Ptg.CLASS_ARRAY) { - afp.setClass(Ptg.CLASS_ARRAY); - } else { - afp.setClass(Ptg.CLASS_VALUE); - } - } -} diff --git a/src/java/org/apache/poi/hssf/model/ParseNode.java b/src/java/org/apache/poi/hssf/model/ParseNode.java deleted file mode 100644 index acd8cb12be..0000000000 --- a/src/java/org/apache/poi/hssf/model/ParseNode.java +++ /dev/null @@ -1,201 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.model; - -import org.apache.poi.hssf.record.formula.AttrPtg; -import org.apache.poi.hssf.record.formula.FuncVarPtg; -import org.apache.poi.hssf.record.formula.Ptg; -import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; -/** - * Represents a syntactic element from a formula by encapsulating the corresponding Ptg - * token. Each ParseNode may have child ParseNodes in the case when the wrapped - * Ptg is non-atomic. - * - * @author Josh Micich - */ -final class ParseNode { - - public static final ParseNode[] EMPTY_ARRAY = { }; - private final Ptg _token; - private final ParseNode[] _children; - private boolean _isIf; - private final int _tokenCount; - - public ParseNode(Ptg token, ParseNode[] children) { - _token = token; - _children = children; - _isIf = isIf(token); - int tokenCount = 1; - for (int i = 0; i < children.length; i++) { - tokenCount += children[i].getTokenCount(); - } - if (_isIf) { - // there will be 2 or 3 extra tAttr tokens according to whether the false param is present - tokenCount += children.length; - } - _tokenCount = tokenCount; - } - public ParseNode(Ptg token) { - this(token, EMPTY_ARRAY); - } - public ParseNode(Ptg token, ParseNode child0) { - this(token, new ParseNode[] { child0, }); - } - public ParseNode(Ptg token, ParseNode child0, ParseNode child1) { - this(token, new ParseNode[] { child0, child1, }); - } - private int getTokenCount() { - return _tokenCount; - } - - /** - * Collects the array of Ptg tokens for the specified tree. - */ - public static Ptg[] toTokenArray(ParseNode rootNode) { - TokenCollector temp = new TokenCollector(rootNode.getTokenCount()); - rootNode.collectPtgs(temp); - return temp.getResult(); - } - private void collectPtgs(TokenCollector temp) { - if (isIf(getToken())) { - collectIfPtgs(temp); - return; - } - for (int i=0; i< getChildren().length; i++) { - getChildren()[i].collectPtgs(temp); - } - temp.add(getToken()); - } - /** - * The IF() function gets marked up with two or three tAttr tokens. - * Similar logic will be required for CHOOSE() when it is supported - * - * See excelfileformat.pdf sec 3.10.5 "tAttr (19H) - */ - private void collectIfPtgs(TokenCollector temp) { - - // condition goes first - getChildren()[0].collectPtgs(temp); - - // placeholder for tAttrIf - int ifAttrIndex = temp.createPlaceholder(); - - // true parameter - getChildren()[1].collectPtgs(temp); - - // placeholder for first skip attr - int skipAfterTrueParamIndex = temp.createPlaceholder(); - int trueParamSize = temp.sumTokenSizes(ifAttrIndex+1, skipAfterTrueParamIndex); - - AttrPtg attrIf = new AttrPtg(); - attrIf.setOptimizedIf(true); - AttrPtg attrSkipAfterTrue = new AttrPtg(); - attrSkipAfterTrue.setGoto(true); - - if (getChildren().length > 2) { - // false param present - - // false parameter - getChildren()[2].collectPtgs(temp); - - int skipAfterFalseParamIndex = temp.createPlaceholder(); - - AttrPtg attrSkipAfterFalse = new AttrPtg(); - attrSkipAfterFalse.setGoto(true); - - int falseParamSize = temp.sumTokenSizes(skipAfterTrueParamIndex+1, skipAfterFalseParamIndex); - - attrIf.setData((short)(trueParamSize + 4)); // distance to start of false parameter. +4 for skip after true - attrSkipAfterTrue.setData((short)(falseParamSize + 4 + 4 - 1)); // 1 less than distance to end of if FuncVar(size=4). +4 for attr skip before - attrSkipAfterFalse.setData((short)(4 - 1)); // 1 less than distance to end of if FuncVar(size=4). - - temp.setPlaceholder(ifAttrIndex, attrIf); - temp.setPlaceholder(skipAfterTrueParamIndex, attrSkipAfterTrue); - temp.setPlaceholder(skipAfterFalseParamIndex, attrSkipAfterFalse); - } else { - // false parameter not present - attrIf.setData((short)(trueParamSize + 4)); // distance to start of FuncVar. +4 for skip after true - attrSkipAfterTrue.setData((short)(4 - 1)); // 1 less than distance to end of if FuncVar(size=4). - - temp.setPlaceholder(ifAttrIndex, attrIf); - temp.setPlaceholder(skipAfterTrueParamIndex, attrSkipAfterTrue); - } - - temp.add(getToken()); - } - - private static boolean isIf(Ptg token) { - if (token instanceof FuncVarPtg) { - FuncVarPtg func = (FuncVarPtg) token; - if (FunctionMetadataRegistry.FUNCTION_NAME_IF.equals(func.getName())) { - return true; - } - } - return false; - } - - public Ptg getToken() { - return _token; - } - - public ParseNode[] getChildren() { - return _children; - } - - private static final class TokenCollector { - - private final Ptg[] _ptgs; - private int _offset; - - public TokenCollector(int tokenCount) { - _ptgs = new Ptg[tokenCount]; - _offset = 0; - } - - public int sumTokenSizes(int fromIx, int toIx) { - int result = 0; - for (int i=fromIx; i * @@ -42,7 +40,7 @@ final class ParityFunction implements FreeRefFunction { _desiredParity = desiredParity; } - public ValueEval evaluate(Eval[] args, Workbook 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 59c6aea89d..7c87fe2521 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.ss.usermodel.DateUtil; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; +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, Workbook 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 3782c6c4de..8e8a9799e4 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.ss.usermodel.Workbook; +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.ss.usermodel.Workbook; */ final class ExternalFunction implements FreeRefFunction { - public ValueEval evaluate(Eval[] args, Workbook 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(Workbook 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 + ")"); @@ -75,6 +75,7 @@ final class ExternalFunction implements FreeRefFunction { } private FreeRefFunction findInternalUserDefinedFunction(NameEval functionNameEval) throws EvaluationException { + String functionName = functionNameEval.getFunctionName(); if(false) { System.out.println("received call to internal 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 12b6be3805..b67f4eb3d3 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 3a3478ca52..cdec045b40 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.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; +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, Workbook 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 be63d70275..567f29b2b1 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.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.formula.EvaluationWorkbook; /** * Implementation for Excel function INDIRECT

@@ -41,7 +40,7 @@ import org.apache.poi.ss.usermodel.Workbook; */ public final class Indirect implements FreeRefFunction { - public ValueEval evaluate(Eval[] args, Workbook 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 a1027ccfaa..5bc478f424 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 4fbedb8745..a6a1500321 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; @@ -513,20 +513,16 @@ public class HSSFCell implements Cell { } /** - * set a string value for the cell. Please note that if you are using - * full 16 bit unicode you should call setEncoding() first. + * set a string value for the cell. * - * @param value value to set the cell to. For formulas we'll set the formula - * string, for String cells we'll set its value. For other types we will + * @param value value to set the cell to. For formulas we'll set the formula + * cached string result, for String cells we'll set its value. For other types we will * change the cell to a string cell and set its value. * If value is null then we will change the cell to a Blank cell. - * @deprecated Use setCellValue(HSSFRichTextString) instead. */ - - public void setCellValue(String value) - { - HSSFRichTextString str = new HSSFRichTextString(value); - setCellValue(str); + public void setCellValue(String value) { + HSSFRichTextString str = value == null ? null : new HSSFRichTextString(value); + setCellValue(str); } /** @@ -597,12 +593,12 @@ public class HSSFCell implements Cell { 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 fc34b9da9c..29430b094f 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 f9399ee8c5..39dbdcd069 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java @@ -1,17 +1,26 @@ 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; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Sheet; /** * Internal POI use only * * @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 +31,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(Sheet 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 Sheet getSheet(int sheetIndex) { + return _uBook.getSheetAt(sheetIndex); + } + + public Sheet 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 +93,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(Cell 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 64ec2fd211..9003c1b3bc 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java @@ -1,75 +1,272 @@ -/* -* 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 org.apache.poi.hssf.model.FormulaParser; -import org.apache.poi.hssf.record.formula.OperationPtg; -import org.apache.poi.hssf.record.formula.Ptg; -import org.apache.poi.ss.usermodel.FormulaEvaluator; -import org.apache.poi.ss.usermodel.Workbook; - -/** - * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * - */ -public class HSSFFormulaEvaluator extends FormulaEvaluator { - public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) { - super(sheet, workbook); - } - public HSSFFormulaEvaluator(HSSFWorkbook workbook) { - super(workbook); - } - - /** - * 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(Workbook workbook, String formula) { - return new FormulaParser(formula, workbook); - } - - /** - * 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(""); - } - - /** - * Compatibility class. - * Seems to do more harm than good though - */ -// public static class CellValue extends FormulaEvaluator.CellValue { -// public CellValue(int cellType, CreationHelper creationHelper) { -// super(cellType, creationHelper); -// } -// } -} +/* ==================================================================== + 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; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellValue; +import org.apache.poi.ss.usermodel.Sheet; + +/** + * 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 /* almost implements FormulaEvaluator */ { + + 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(Sheet 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(Cell 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(Cell 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(Cell cell) { + if (cell == null) { + return null; + } + HSSFCell result = (HSSFCell) cell; + 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 result; + } + private static void setCellType(Cell 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(Cell 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(new HSSFRichTextString(cv.getStringValue())); + 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>>>>>> .merge-right.r696898 * create an HSSFSheet for this HSSFWorkbook, adds it to the sheets and returns * the high level representation. Use this to create new sheets. * @@ -751,7 +744,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm 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/hssf/usermodel/LazyAreaEval.java b/src/java/org/apache/poi/hssf/usermodel/LazyAreaEval.java deleted file mode 100644 index 0a195c8754..0000000000 --- a/src/java/org/apache/poi/hssf/usermodel/LazyAreaEval.java +++ /dev/null @@ -1,29 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.usermodel; -import org.apache.poi.hssf.record.formula.AreaI; - -/** - * - * @author Josh Micich - */ -final class LazyAreaEval extends org.apache.poi.ss.usermodel.LazyAreaEval { - public LazyAreaEval(AreaI ptg, HSSFSheet sheet, HSSFFormulaEvaluator evaluator) { - super(ptg, sheet, evaluator); - } -} diff --git a/src/java/org/apache/poi/hssf/usermodel/LazyRefEval.java b/src/java/org/apache/poi/hssf/usermodel/LazyRefEval.java deleted file mode 100644 index 3b591bbcc0..0000000000 --- a/src/java/org/apache/poi/hssf/usermodel/LazyRefEval.java +++ /dev/null @@ -1,33 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.usermodel; -import org.apache.poi.hssf.record.formula.Ref3DPtg; -import org.apache.poi.hssf.record.formula.RefPtg; - -/** -* -* @author Josh Micich -*/ -final class LazyRefEval extends org.apache.poi.ss.usermodel.LazyRefEval { - public LazyRefEval(RefPtg ptg, HSSFSheet sheet, HSSFFormulaEvaluator evaluator) { - super(ptg, sheet, evaluator); - } - public LazyRefEval(Ref3DPtg ptg, HSSFSheet sheet, HSSFFormulaEvaluator evaluator) { - super(ptg, sheet, evaluator); - } -} diff --git a/src/java/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java b/src/java/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java deleted file mode 100755 index 21dc067592..0000000000 --- a/src/java/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java +++ /dev/null @@ -1,184 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.usermodel; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.Map; - -import org.apache.poi.hssf.record.formula.AddPtg; -import org.apache.poi.hssf.record.formula.ConcatPtg; -import org.apache.poi.hssf.record.formula.DividePtg; -import org.apache.poi.hssf.record.formula.EqualPtg; -import org.apache.poi.hssf.record.formula.ExpPtg; -import org.apache.poi.hssf.record.formula.FuncPtg; -import org.apache.poi.hssf.record.formula.FuncVarPtg; -import org.apache.poi.hssf.record.formula.GreaterEqualPtg; -import org.apache.poi.hssf.record.formula.GreaterThanPtg; -import org.apache.poi.hssf.record.formula.LessEqualPtg; -import org.apache.poi.hssf.record.formula.LessThanPtg; -import org.apache.poi.hssf.record.formula.MultiplyPtg; -import org.apache.poi.hssf.record.formula.NotEqualPtg; -import org.apache.poi.hssf.record.formula.OperationPtg; -import org.apache.poi.hssf.record.formula.PercentPtg; -import org.apache.poi.hssf.record.formula.PowerPtg; -import org.apache.poi.hssf.record.formula.Ptg; -import org.apache.poi.hssf.record.formula.SubtractPtg; -import org.apache.poi.hssf.record.formula.UnaryMinusPtg; -import org.apache.poi.hssf.record.formula.UnaryPlusPtg; -import org.apache.poi.hssf.record.formula.eval.AddEval; -import org.apache.poi.hssf.record.formula.eval.ConcatEval; -import org.apache.poi.hssf.record.formula.eval.DivideEval; -import org.apache.poi.hssf.record.formula.eval.EqualEval; -import org.apache.poi.hssf.record.formula.eval.FuncVarEval; -import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval; -import org.apache.poi.hssf.record.formula.eval.GreaterThanEval; -import org.apache.poi.hssf.record.formula.eval.LessEqualEval; -import org.apache.poi.hssf.record.formula.eval.LessThanEval; -import org.apache.poi.hssf.record.formula.eval.MultiplyEval; -import org.apache.poi.hssf.record.formula.eval.NotEqualEval; -import org.apache.poi.hssf.record.formula.eval.OperationEval; -import org.apache.poi.hssf.record.formula.eval.PercentEval; -import org.apache.poi.hssf.record.formula.eval.PowerEval; -import org.apache.poi.hssf.record.formula.eval.SubtractEval; -import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; -import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; - -/** - * This class creates OperationEval instances to help evaluate OperationPtg - * formula tokens. - * - * @author Josh Micich - */ -final class OperationEvaluatorFactory { - private static final Class[] OPERATION_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; - // TODO - use singleton instances directly instead of reflection - private static final Map _constructorsByPtgClass = initialiseConstructorsMap(); - private static final Map _instancesByPtgClass = initialiseInstancesMap(); - - private OperationEvaluatorFactory() { - // no instances of this class - } - - private static Map initialiseConstructorsMap() { - Map m = new HashMap(32); - add(m, ConcatPtg.class, ConcatEval.class); - add(m, FuncPtg.class, FuncVarEval.class); - add(m, FuncVarPtg.class, FuncVarEval.class); - return m; - } - private static Map initialiseInstancesMap() { - Map m = new HashMap(32); - add(m, EqualPtg.class, EqualEval.instance); - add(m, GreaterEqualPtg.class, GreaterEqualEval.instance); - add(m, GreaterThanPtg.class, GreaterThanEval.instance); - add(m, LessEqualPtg.class, LessEqualEval.instance); - add(m, LessThanPtg.class, LessThanEval.instance); - add(m, NotEqualPtg.class, NotEqualEval.instance); - - add(m, AddPtg.class, AddEval.instance); - add(m, DividePtg.class, DivideEval.instance); - add(m, MultiplyPtg.class, MultiplyEval.instance); - add(m, PercentPtg.class, PercentEval.instance); - add(m, PowerPtg.class, PowerEval.instance); - add(m, SubtractPtg.class, SubtractEval.instance); - add(m, UnaryMinusPtg.class, UnaryMinusEval.instance); - add(m, UnaryPlusPtg.class, UnaryPlusEval.instance); - return m; - } - - private static void add(Map m, Class ptgClass, OperationEval evalInstance) { - if(!Ptg.class.isAssignableFrom(ptgClass)) { - throw new IllegalArgumentException("Expected Ptg subclass"); - } - m.put(ptgClass, evalInstance); - } - - private static void add(Map m, Class ptgClass, Class evalClass) { - // perform some validation now, to keep later exception handlers simple - if(!Ptg.class.isAssignableFrom(ptgClass)) { - throw new IllegalArgumentException("Expected Ptg subclass"); - } - - if(!OperationEval.class.isAssignableFrom(evalClass)) { - throw new IllegalArgumentException("Expected OperationEval subclass"); - } - if (!Modifier.isPublic(evalClass.getModifiers())) { - throw new RuntimeException("Eval class must be public"); - } - if (Modifier.isAbstract(evalClass.getModifiers())) { - throw new RuntimeException("Eval class must not be abstract"); - } - - Constructor constructor; - try { - constructor = evalClass.getDeclaredConstructor(OPERATION_CONSTRUCTOR_CLASS_ARRAY); - } catch (NoSuchMethodException e) { - throw new RuntimeException("Missing constructor"); - } - if (!Modifier.isPublic(constructor.getModifiers())) { - throw new RuntimeException("Eval constructor must be public"); - } - m.put(ptgClass, constructor); - } - - /** - * returns the OperationEval concrete impl instance corresponding - * to the supplied operationPtg - */ - public static OperationEval create(OperationPtg ptg) { - if(ptg == null) { - throw new IllegalArgumentException("ptg must not be null"); - } - Object result; - - Class ptgClass = ptg.getClass(); - - result = _instancesByPtgClass.get(ptgClass); - if (result != null) { - return (OperationEval) result; - } - - - Constructor constructor = (Constructor) _constructorsByPtgClass.get(ptgClass); - if(constructor == null) { - if(ptgClass == ExpPtg.class) { - // ExpPtg is used for array formulas and shared formulas. - // it is currently unsupported, and may not even get implemented here - throw new RuntimeException("ExpPtg currently not supported"); - } - throw new RuntimeException("Unexpected operation ptg class (" + ptgClass.getName() + ")"); - } - - Object[] initargs = { ptg }; - try { - result = constructor.newInstance(initargs); - } catch (IllegalArgumentException e) { - throw new RuntimeException(e); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - return (OperationEval) result; - } -} diff --git a/src/java/org/apache/poi/ss/formula/CellEvaluationFrame.java b/src/java/org/apache/poi/ss/formula/CellEvaluationFrame.java new file mode 100644 index 0000000000..b6f542a082 --- /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 0000000000..73f8d4d900 --- /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.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Sheet; + +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(Cell cell) { + + if (cell == null) { + return BlankEval.INSTANCE; + } + switch (cell.getCellType()) { + case Cell.CELL_TYPE_NUMERIC: + return new NumberEval(cell.getNumericCellValue()); + case Cell.CELL_TYPE_STRING: + return new StringEval(cell.getRichStringCellValue().getString()); + case Cell.CELL_TYPE_FORMULA: + return _bookEvaluator.internalEvaluate(cell, _tracker); + case Cell.CELL_TYPE_BOOLEAN: + return BoolEval.valueOf(cell.getBooleanCellValue()); + case Cell.CELL_TYPE_BLANK: + return BlankEval.INSTANCE; + case Cell.CELL_TYPE_ERROR: + return ErrorEval.valueOf(cell.getErrorCellValue()); + } + throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); + } + + public String getSheetName(Sheet sheet) { + return _bookEvaluator.getSheetName(sheet); + } + +} diff --git a/src/java/org/apache/poi/ss/formula/EvaluationCache.java b/src/java/org/apache/poi/ss/formula/EvaluationCache.java new file mode 100644 index 0000000000..d78f4a7286 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/EvaluationCache.java @@ -0,0 +1,99 @@ +/* ==================================================================== + 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.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 + * calculated values of already visited cells, to avoid unnecessary re-calculation when the + * same cells are referenced multiple times + * + * + * @author Josh Micich + */ +final class EvaluationCache { + + 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) { + 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) { + 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"); + } + _valuesByKey.put(key, value); + } + + /** + * Should be called whenever there are changes to input cells in the evaluated workbook. + */ + 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 0000000000..ae8624c7b8 --- /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/ss/formula/EvaluationTracker.java b/src/java/org/apache/poi/ss/formula/EvaluationTracker.java new file mode 100755 index 0000000000..20ab3299af --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/EvaluationTracker.java @@ -0,0 +1,106 @@ +/* ==================================================================== + 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.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 {@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 EvaluationTracker { + + private final List _evaluationFrames; + private final EvaluationCache _cache; + + public EvaluationTracker(EvaluationCache cache) { + _cache = cache; + _evaluationFrames = new ArrayList(); + } + + /** + * Notifies this evaluation tracker that evaluation of the specified cell is + * about to start.
+ * + * In the case of a true return code, the caller should + * continue evaluation of the specified cell, and also be sure to call + * endEvaluate() when complete.
+ * + * In the case of a false return code, the caller should + * return an evaluation result of + * ErrorEval.CIRCULAR_REF_ERROR, and not call endEvaluate(). + *
+ * @return true if the specified cell has not been visited yet in the current + * evaluation. false if the specified cell is already being evaluated. + */ + public ValueEval startEvaluate(int sheetIndex, int srcRowNum, int srcColNum) { + CellEvaluationFrame cef = new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum); + if (_evaluationFrames.contains(cef)) { + return ErrorEval.CIRCULAR_REF_ERROR; + } + ValueEval result = _cache.getValue(cef); + if (result == null) { + _evaluationFrames.add(cef); + } + return result; + } + + /** + * Notifies this evaluation tracker that the evaluation of the specified + * cell is complete.

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

+ * + * Assuming a well behaved client, parameters to this method would not be + * required. However, they have been included to assert correct behaviour, + * and form more meaningful error messages. + * @param result + */ + 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"); + } + + nFrames--; + CellEvaluationFrame cefExpected = (CellEvaluationFrame) _evaluationFrames.get(nFrames); + CellEvaluationFrame cefActual = new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum); + if (!cefActual.equals(cefExpected)) { + throw new RuntimeException("Wrong cell specified. " + + "Corresponding startEvaluate() call was for cell {" + + cefExpected.formatAsString() + "} this endEvaluate() call is for cell {" + + cefActual.formatAsString() + "}"); + } + // else - no problems so pop current frame + _evaluationFrames.remove(nFrames); + + _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 0000000000..adf69d0d51 --- /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.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Sheet; +/** + * 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(Sheet sheet); + + Sheet getSheet(int sheetIndex); + + Sheet getSheetByExternSheetIndex(int externSheetIndex); + EvaluationName getName(NamePtg namePtg); + String resolveNameXText(NameXPtg ptg); + Ptg[] getFormulaTokens(Cell cell); +} diff --git a/src/java/org/apache/poi/ss/formula/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java new file mode 100644 index 0000000000..df2f8c282f --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java @@ -0,0 +1,976 @@ +/* ==================================================================== + 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.ArrayList; +import java.util.List; + +import org.apache.poi.hssf.record.UnicodeString; +import org.apache.poi.hssf.record.constant.ErrorConstant; +import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; +import org.apache.poi.hssf.record.formula.AddPtg; +import org.apache.poi.hssf.record.formula.Area3DPtg; +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.ArrayPtg; +import org.apache.poi.hssf.record.formula.BoolPtg; +import org.apache.poi.hssf.record.formula.ConcatPtg; +import org.apache.poi.hssf.record.formula.DividePtg; +import org.apache.poi.hssf.record.formula.EqualPtg; +import org.apache.poi.hssf.record.formula.ErrPtg; +import org.apache.poi.hssf.record.formula.FuncPtg; +import org.apache.poi.hssf.record.formula.FuncVarPtg; +import org.apache.poi.hssf.record.formula.GreaterEqualPtg; +import org.apache.poi.hssf.record.formula.GreaterThanPtg; +import org.apache.poi.hssf.record.formula.IntPtg; +import org.apache.poi.hssf.record.formula.LessEqualPtg; +import org.apache.poi.hssf.record.formula.LessThanPtg; +import org.apache.poi.hssf.record.formula.MissingArgPtg; +import org.apache.poi.hssf.record.formula.MultiplyPtg; +import org.apache.poi.hssf.record.formula.NamePtg; +import org.apache.poi.hssf.record.formula.NameXPtg; +import org.apache.poi.hssf.record.formula.NotEqualPtg; +import org.apache.poi.hssf.record.formula.NumberPtg; +import org.apache.poi.hssf.record.formula.ParenthesisPtg; +import org.apache.poi.hssf.record.formula.PercentPtg; +import org.apache.poi.hssf.record.formula.PowerPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.Ref3DPtg; +import org.apache.poi.hssf.record.formula.RefPtg; +import org.apache.poi.hssf.record.formula.StringPtg; +import org.apache.poi.hssf.record.formula.SubtractPtg; +import org.apache.poi.hssf.record.formula.UnaryMinusPtg; +import org.apache.poi.hssf.record.formula.UnaryPlusPtg; +import org.apache.poi.hssf.record.formula.function.FunctionMetadata; +import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; +import org.apache.poi.hssf.usermodel.HSSFErrorConstants; +import org.apache.poi.hssf.util.AreaReference; +import org.apache.poi.hssf.util.CellReference; +import org.apache.poi.hssf.util.CellReference.NameType; + +/** + * This class parses a formula string into a List of tokens in RPN order. + * Inspired by + * Lets Build a Compiler, by Jack Crenshaw + * BNF for the formula expression is : + * ::= [ ]* + * ::= [ ]* + * ::= | () | | + * ::= ([expression [, expression]*]) + * + * @author Avik Sengupta + * @author Andrew C. oliver (acoliver at apache dot org) + * @author Eric Ladner (eladner at goldinc dot com) + * @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 { + + /** + * Specific exception thrown when a supplied formula does not parse properly.
+ * Primarily used by test cases when testing for specific parsing exceptions.

+ * + */ + static final class FormulaParseException extends RuntimeException { + // This class was given package scope until it would become clear that it is useful to + // general client code. + public FormulaParseException(String msg) { + super(msg); + } + } + + + private final String formulaString; + private final int formulaLength; + private int pointer; + + private ParseNode _rootNode; + + private static char TAB = '\t'; + + /** + * Lookahead Character. + * gets value '\0' when the input string is exhausted + */ + private char look; + + private FormulaParsingWorkbook book; + + + + /** + * Create the formula parser, with the string that is to be + * parsed against the supplied workbook. + * A later call the parse() method to return ptg list in + * rpn order, then call the getRPNPtg() to retrive the + * parse results. + * This class is recommended only for single threaded use. + * + * If you only have a usermodel.HSSFWorkbook, and not a + * model.Workbook, then use the convenience method on + * usermodel.HSSFFormulaEvaluator + */ + private FormulaParser(String formula, FormulaParsingWorkbook book){ + formulaString = formula; + pointer=0; + this.book = book; + formulaLength = formulaString.length(); + } + + public static Ptg[] parse(String formula, FormulaParsingWorkbook book) { + return parse(formula, book, FormulaType.CELL); + } + + public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType) { + FormulaParser fp = new FormulaParser(formula, workbook); + fp.parse(); + return fp.getRPNPtg(formulaType); + } + + /** Read New Character From Input Stream */ + private void GetChar() { + // Check to see if we've walked off the end of the string. + if (pointer > formulaLength) { + throw new RuntimeException("too far"); + } + if (pointer < formulaLength) { + look=formulaString.charAt(pointer); + } else { + // Just return if so and reset 'look' to something to keep + // SkipWhitespace from spinning + look = (char)0; + } + pointer++; + //System.out.println("Got char: "+ look); + } + + /** Report What Was Expected */ + private RuntimeException expected(String s) { + String msg; + + if (look == '=' && formulaString.substring(0, pointer-1).trim().length() < 1) { + msg = "The specified formula '" + formulaString + + "' starts with an equals sign which is not allowed."; + } else { + msg = "Parse error near char " + (pointer-1) + " '" + look + "'" + + " in specified formula '" + formulaString + "'. Expected " + + s; + } + return new FormulaParseException(msg); + } + + /** Recognize an Alpha Character */ + private boolean IsAlpha(char c) { + return Character.isLetter(c) || c == '$' || c=='_'; + } + + /** Recognize a Decimal Digit */ + private boolean IsDigit(char c) { + return Character.isDigit(c); + } + + /** Recognize an Alphanumeric */ + private boolean IsAlNum(char c) { + return (IsAlpha(c) || IsDigit(c)); + } + + /** Recognize White Space */ + private boolean IsWhite( char c) { + return (c ==' ' || c== TAB); + } + + /** Skip Over Leading White Space */ + private void SkipWhite() { + while (IsWhite(look)) { + GetChar(); + } + } + + /** + * Consumes the next input character if it is equal to the one specified otherwise throws an + * unchecked exception. This method does not consume whitespace (before or after the + * matched character). + */ + private void Match(char x) { + if (look != x) { + throw expected("'" + x + "'"); + } + GetChar(); + } + + /** + * Parses a sheet name, named range name, or simple cell reference.
+ * Note - identifiers in Excel can contain dots, so this method may return a String + * which may need to be converted to an area reference. For example, this method + * may return a value like "A1..B2", in which case the caller must convert it to + * an area reference like "A1:B2" + */ + private String parseIdentifier() { + StringBuffer Token = new StringBuffer(); + if (!IsAlpha(look) && look != '\'') { + throw expected("Name"); + } + if(look == '\'') + { + Match('\''); + boolean done = look == '\''; + while(!done) + { + Token.append(look); + GetChar(); + if(look == '\'') + { + Match('\''); + done = look != '\''; + } + } + } + else + { + // allow for any sequence of dots and identifier chars + // special case of two consecutive dots is best treated in the calling code + while (IsAlNum(look) || look == '.') { + Token.append(look); + GetChar(); + } + } + return Token.toString(); + } + + /** Get a Number */ + private String GetNum() { + StringBuffer value = new StringBuffer(); + + while (IsDigit(this.look)){ + value.append(this.look); + GetChar(); + } + return value.length() == 0 ? null : value.toString(); + } + + private ParseNode parseFunctionReferenceOrName() { + String name = parseIdentifier(); + if (look == '('){ + //This is a function + return function(name); + } + return new ParseNode(parseNameOrReference(name)); + } + + private Ptg parseNameOrReference(String name) { + + AreaReference areaRef = parseArea(name); + if (areaRef != null) { + // will happen if dots are used instead of colon + return new AreaPtg(areaRef.formatAsString()); + } + + if (look == ':' || look == '.') { // this is a AreaReference + GetChar(); + + while (look == '.') { // formulas can have . or .. or ... instead of : + GetChar(); + } + + String first = name; + String second = parseIdentifier(); + return new AreaPtg(first+":"+second); + } + + if (look == '!') { + Match('!'); + String sheetName = name; + String first = parseIdentifier(); + int externIdx = book.getExternalSheetIndex(sheetName); + areaRef = parseArea(name); + if (areaRef != null) { + // will happen if dots are used instead of colon + return new Area3DPtg(areaRef.formatAsString(), externIdx); + } + if (look == ':') { + Match(':'); + String second=parseIdentifier(); + if (look == '!') { + //The sheet name was included in both of the areas. Only really + //need it once + Match('!'); + String third=parseIdentifier(); + + if (!sheetName.equals(second)) + throw new RuntimeException("Unhandled double sheet reference."); + + return new Area3DPtg(first+":"+third,externIdx); + } + return new Area3DPtg(first+":"+second,externIdx); + } + return new Ref3DPtg(first, externIdx); + } + if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) { + return new BoolPtg(name.toUpperCase()); + } + + // This can be either a cell ref or a named range + // Try to spot which it is + int nameType = CellReference.classifyCellReference(name); + if (nameType == NameType.CELL) { + return new RefPtg(name); + } + if (nameType != NameType.NAMED_RANGE) { + new FormulaParseException("Name '" + name + + "' does not look like a cell reference or 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"); + } + + /** + * @param name an 'identifier' like string (i.e. contains alphanums, and dots) + * @return null if name cannot be split at a dot + */ + private AreaReference parseArea(String name) { + int dotPos = name.indexOf('.'); + if (dotPos < 0) { + return null; + } + int dotCount = 1; + while (dotCount3) { + // four or more consecutive dots does not convert to ':' + return null; + } + } + // This expression is only valid as an area ref, if the LHS and RHS of the dot(s) are both + // cell refs. Otherwise, this expression must be a named range name + String partA = name.substring(0, dotPos); + if (!isValidCellReference(partA)) { + return null; + } + String partB = name.substring(dotPos+dotCount); + if (!isValidCellReference(partB)) { + return null; + } + CellReference topLeft = new CellReference(partA); + CellReference bottomRight = new CellReference(partB); + return new AreaReference(topLeft, bottomRight); + } + + /** + * @return true if the specified name is a valid cell reference + */ + private static boolean isValidCellReference(String str) { + return CellReference.classifyCellReference(str) == NameType.CELL; + } + + + /** + * Note - Excel function names are 'case aware but not case sensitive'. This method may end + * up creating a defined name record in the workbook if the specified name is not an internal + * Excel function, and has not been encountered before. + * + * @param name case preserved function name (as it was entered/appeared in the formula). + */ + private ParseNode function(String name) { + Ptg nameToken = null; + if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) { + // user defined function + // in the token tree, the name is more or less the first argument + + EvaluationName hName = book.getName(name); + if (hName == null) { + + 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"); + } + + // calls to user-defined functions within the workbook + // get a Name token which points to a defined name record + nameToken = hName.createPtg(); + } + } + + Match('('); + ParseNode[] args = Arguments(); + Match(')'); + + return getFunction(name, nameToken, args); + } + + /** + * Generates the variable function ptg for the formula. + *

+ * For IF Formulas, additional PTGs are added to the tokens + * @param name a {@link NamePtg} or {@link NameXPtg} or null + * @param numArgs + * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function + */ + private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) { + + FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase()); + int numArgs = args.length; + if(fm == null) { + if (namePtg == null) { + throw new IllegalStateException("NamePtg must be supplied for external functions"); + } + // must be external function + ParseNode[] allArgs = new ParseNode[numArgs+1]; + allArgs[0] = new ParseNode(namePtg); + System.arraycopy(args, 0, allArgs, 1, numArgs); + return new ParseNode(new FuncVarPtg(name, (byte)(numArgs+1)), allArgs); + } + + if (namePtg != null) { + throw new IllegalStateException("NamePtg no applicable to internal functions"); + } + boolean isVarArgs = !fm.hasFixedArgsLength(); + int funcIx = fm.getIndex(); + validateNumArgs(args.length, fm); + + AbstractFunctionPtg retval; + if(isVarArgs) { + retval = new FuncVarPtg(name, (byte)numArgs); + } else { + retval = new FuncPtg(funcIx); + } + return new ParseNode(retval, args); + } + + private void validateNumArgs(int numArgs, FunctionMetadata fm) { + if(numArgs < fm.getMinParams()) { + String msg = "Too few arguments to function '" + fm.getName() + "'. "; + if(fm.hasFixedArgsLength()) { + msg += "Expected " + fm.getMinParams(); + } else { + msg += "At least " + fm.getMinParams() + " were expected"; + } + msg += " but got " + numArgs + "."; + throw new FormulaParseException(msg); + } + if(numArgs > fm.getMaxParams()) { + String msg = "Too many arguments to function '" + fm.getName() + "'. "; + if(fm.hasFixedArgsLength()) { + msg += "Expected " + fm.getMaxParams(); + } else { + msg += "At most " + fm.getMaxParams() + " were expected"; + } + msg += " but got " + numArgs + "."; + throw new FormulaParseException(msg); + } + } + + private static boolean isArgumentDelimiter(char ch) { + return ch == ',' || ch == ')'; + } + + /** get arguments to a function */ + private ParseNode[] Arguments() { + //average 2 args per function + List temp = new ArrayList(2); + SkipWhite(); + if(look == ')') { + return ParseNode.EMPTY_ARRAY; + } + + boolean missedPrevArg = true; + int numArgs = 0; + while (true) { + SkipWhite(); + if (isArgumentDelimiter(look)) { + if (missedPrevArg) { + temp.add(new ParseNode(MissingArgPtg.instance)); + numArgs++; + } + if (look == ')') { + break; + } + Match(','); + missedPrevArg = true; + continue; + } + temp.add(comparisonExpression()); + numArgs++; + missedPrevArg = false; + SkipWhite(); + if (!isArgumentDelimiter(look)) { + throw expected("',' or ')'"); + } + } + ParseNode[] result = new ParseNode[temp.size()]; + temp.toArray(result); + return result; + } + + /** Parse and Translate a Math Factor */ + private ParseNode powerFactor() { + ParseNode result = percentFactor(); + while(true) { + SkipWhite(); + if(look != '^') { + return result; + } + Match('^'); + ParseNode other = percentFactor(); + result = new ParseNode(PowerPtg.instance, result, other); + } + } + + private ParseNode percentFactor() { + ParseNode result = parseSimpleFactor(); + while(true) { + SkipWhite(); + if(look != '%') { + return result; + } + Match('%'); + result = new ParseNode(PercentPtg.instance, result); + } + } + + + /** + * factors (without ^ or % ) + */ + private ParseNode parseSimpleFactor() { + SkipWhite(); + switch(look) { + case '#': + return new ParseNode(ErrPtg.valueOf(parseErrorLiteral())); + case '-': + Match('-'); + return new ParseNode(UnaryMinusPtg.instance, powerFactor()); + case '+': + Match('+'); + return new ParseNode(UnaryPlusPtg.instance, powerFactor()); + case '(': + Match('('); + ParseNode inside = comparisonExpression(); + Match(')'); + return new ParseNode(ParenthesisPtg.instance, inside); + case '"': + return new ParseNode(new StringPtg(parseStringLiteral())); + case '{': + Match('{'); + ParseNode arrayNode = parseArray(); + Match('}'); + return arrayNode; + } + if (IsAlpha(look) || look == '\''){ + return parseFunctionReferenceOrName(); + } + // else - assume number + return new ParseNode(parseNumber()); + } + + + private ParseNode parseArray() { + List rowsData = new ArrayList(); + while(true) { + Object[] singleRowData = parseArrayRow(); + rowsData.add(singleRowData); + if (look == '}') { + break; + } + if (look != ';') { + throw expected("'}' or ';'"); + } + Match(';'); + } + int nRows = rowsData.size(); + Object[][] values2d = new Object[nRows][]; + rowsData.toArray(values2d); + int nColumns = values2d[0].length; + checkRowLengths(values2d, nColumns); + + return new ParseNode(new ArrayPtg(values2d)); + } + private void checkRowLengths(Object[][] values2d, int nColumns) { + for (int i = 0; i < values2d.length; i++) { + int rowLen = values2d[i].length; + if (rowLen != nColumns) { + throw new FormulaParseException("Array row " + i + " has length " + rowLen + + " but row 0 has length " + nColumns); + } + } + } + + private Object[] parseArrayRow() { + List temp = new ArrayList(); + while (true) { + temp.add(parseArrayItem()); + SkipWhite(); + switch(look) { + case '}': + case ';': + break; + case ',': + Match(','); + continue; + default: + throw expected("'}' or ','"); + + } + break; + } + + Object[] result = new Object[temp.size()]; + temp.toArray(result); + return result; + } + + private Object parseArrayItem() { + SkipWhite(); + switch(look) { + case '"': return new UnicodeString(parseStringLiteral()); + case '#': return ErrorConstant.valueOf(parseErrorLiteral()); + case 'F': case 'f': + case 'T': case 't': + return parseBooleanLiteral(); + } + // else assume number + return convertArrayNumber(parseNumber()); + } + + private Boolean parseBooleanLiteral() { + String iden = parseIdentifier(); + if ("TRUE".equalsIgnoreCase(iden)) { + return Boolean.TRUE; + } + if ("FALSE".equalsIgnoreCase(iden)) { + return Boolean.FALSE; + } + throw expected("'TRUE' or 'FALSE'"); + } + + private static Double convertArrayNumber(Ptg ptg) { + if (ptg instanceof IntPtg) { + return new Double(((IntPtg)ptg).getValue()); + } + if (ptg instanceof NumberPtg) { + return new Double(((NumberPtg)ptg).getValue()); + } + throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")"); + } + + private Ptg parseNumber() { + String number2 = null; + String exponent = null; + String number1 = GetNum(); + + if (look == '.') { + GetChar(); + number2 = GetNum(); + } + + if (look == 'E') { + GetChar(); + + String sign = ""; + if (look == '+') { + GetChar(); + } else if (look == '-') { + GetChar(); + sign = "-"; + } + + String number = GetNum(); + if (number == null) { + throw expected("Integer"); + } + exponent = sign + number; + } + + if (number1 == null && number2 == null) { + throw expected("Integer"); + } + + return getNumberPtgFromString(number1, number2, exponent); + } + + + private int parseErrorLiteral() { + Match('#'); + String part1 = parseIdentifier().toUpperCase(); + + switch(part1.charAt(0)) { + case 'V': + if(part1.equals("VALUE")) { + Match('!'); + return HSSFErrorConstants.ERROR_VALUE; + } + throw expected("#VALUE!"); + case 'R': + if(part1.equals("REF")) { + Match('!'); + return HSSFErrorConstants.ERROR_REF; + } + throw expected("#REF!"); + case 'D': + if(part1.equals("DIV")) { + Match('/'); + Match('0'); + Match('!'); + return HSSFErrorConstants.ERROR_DIV_0; + } + throw expected("#DIV/0!"); + case 'N': + if(part1.equals("NAME")) { + Match('?'); // only one that ends in '?' + return HSSFErrorConstants.ERROR_NAME; + } + if(part1.equals("NUM")) { + Match('!'); + return HSSFErrorConstants.ERROR_NUM; + } + if(part1.equals("NULL")) { + Match('!'); + return HSSFErrorConstants.ERROR_NULL; + } + if(part1.equals("N")) { + Match('/'); + if(look != 'A' && look != 'a') { + throw expected("#N/A"); + } + Match(look); + // Note - no '!' or '?' suffix + return HSSFErrorConstants.ERROR_NA; + } + throw expected("#NAME?, #NUM!, #NULL! or #N/A"); + + } + throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A"); + } + + + /** + * Get a PTG for an integer from its string representation. + * return Int or Number Ptg based on size of input + */ + private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) { + StringBuffer number = new StringBuffer(); + + if (number2 == null) { + number.append(number1); + + if (exponent != null) { + number.append('E'); + number.append(exponent); + } + + String numberStr = number.toString(); + int intVal; + try { + intVal = Integer.parseInt(numberStr); + } catch (NumberFormatException e) { + return new NumberPtg(numberStr); + } + if (IntPtg.isInRange(intVal)) { + return new IntPtg(intVal); + } + return new NumberPtg(numberStr); + } + + if (number1 != null) { + number.append(number1); + } + + number.append('.'); + number.append(number2); + + if (exponent != null) { + number.append('E'); + number.append(exponent); + } + + return new NumberPtg(number.toString()); + } + + + private String parseStringLiteral() { + Match('"'); + + StringBuffer token = new StringBuffer(); + while (true) { + if (look == '"') { + GetChar(); + if (look != '"') { + break; + } + } + token.append(look); + GetChar(); + } + return token.toString(); + } + + /** Parse and Translate a Math Term */ + private ParseNode Term() { + ParseNode result = powerFactor(); + while(true) { + SkipWhite(); + Ptg operator; + switch(look) { + case '*': + Match('*'); + operator = MultiplyPtg.instance; + break; + case '/': + Match('/'); + operator = DividePtg.instance; + break; + default: + return result; // finished with Term + } + ParseNode other = powerFactor(); + result = new ParseNode(operator, result, other); + } + } + + private ParseNode comparisonExpression() { + ParseNode result = concatExpression(); + while (true) { + SkipWhite(); + switch(look) { + case '=': + case '>': + case '<': + Ptg comparisonToken = getComparisonToken(); + ParseNode other = concatExpression(); + result = new ParseNode(comparisonToken, result, other); + continue; + } + return result; // finished with predicate expression + } + } + + private Ptg getComparisonToken() { + if(look == '=') { + Match(look); + return EqualPtg.instance; + } + boolean isGreater = look == '>'; + Match(look); + if(isGreater) { + if(look == '=') { + Match('='); + return GreaterEqualPtg.instance; + } + return GreaterThanPtg.instance; + } + switch(look) { + case '=': + Match('='); + return LessEqualPtg.instance; + case '>': + Match('>'); + return NotEqualPtg.instance; + } + return LessThanPtg.instance; + } + + + private ParseNode concatExpression() { + ParseNode result = additiveExpression(); + while (true) { + SkipWhite(); + if(look != '&') { + break; // finished with concat expression + } + Match('&'); + ParseNode other = additiveExpression(); + result = new ParseNode(ConcatPtg.instance, result, other); + } + return result; + } + + + /** Parse and Translate an Expression */ + private ParseNode additiveExpression() { + ParseNode result = Term(); + while (true) { + SkipWhite(); + Ptg operator; + switch(look) { + case '+': + Match('+'); + operator = AddPtg.instance; + break; + case '-': + Match('-'); + operator = SubtractPtg.instance; + break; + default: + return result; // finished with additive expression + } + ParseNode other = Term(); + result = new ParseNode(operator, result, other); + } + } + + //{--------------------------------------------------------------} + //{ Parse and Translate an Assignment Statement } + /** +procedure Assignment; +var Name: string[8]; +begin + Name := GetName; + Match('='); + Expression; + +end; + **/ + + + /** + * API call to execute the parsing of the formula + * + */ + private void parse() { + pointer=0; + GetChar(); + _rootNode = comparisonExpression(); + + if(pointer <= formulaLength) { + String msg = "Unused input [" + formulaString.substring(pointer-1) + + "] after attempting to parse the formula [" + formulaString + "]"; + throw new FormulaParseException(msg); + } + } + + 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); + } +} 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 0000000000..69431c2c22 --- /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/FormulaType.java b/src/java/org/apache/poi/ss/formula/FormulaType.java new file mode 100644 index 0000000000..3b47030d47 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/FormulaType.java @@ -0,0 +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.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/ss/formula/LazyAreaEval.java b/src/java/org/apache/poi/ss/formula/LazyAreaEval.java new file mode 100644 index 0000000000..5816f0052b --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/LazyAreaEval.java @@ -0,0 +1,82 @@ +/* ==================================================================== + 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.AreaI; +import org.apache.poi.hssf.record.formula.AreaI.OffsetArea; +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.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.hssf.util.CellReference; + +/** + * + * @author Josh Micich + */ +final class LazyAreaEval extends AreaEvalBase { + + private final Sheet _sheet; + private final CellEvaluator _evaluator; + + public LazyAreaEval(AreaI ptg, Sheet sheet, CellEvaluator evaluator) { + super(ptg); + _sheet = sheet; + _evaluator = evaluator; + } + + public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) { + + int rowIx = (relativeRowIndex + getFirstRow() ) & 0xFFFF; + int colIx = (relativeColumnIndex + getFirstColumn() ) & 0x00FF; + + Row row = _sheet.getRow(rowIx); + if (row == null) { + return BlankEval.INSTANCE; + } + Cell cell = row.getCell(colIx); + if (cell == null) { + return BlankEval.INSTANCE; + } + return _evaluator.getEvalForCell(cell); + } + + public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) { + AreaI area = new OffsetArea(getFirstRow(), getFirstColumn(), + relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx); + + return new LazyAreaEval(area, _sheet, _evaluator); + } + public String toString() { + CellReference crA = new CellReference(getFirstRow(), getFirstColumn()); + CellReference crB = new CellReference(getLastRow(), getLastColumn()); + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()).append("["); + String sheetName = _evaluator.getSheetName(_sheet); + sb.append(sheetName); + sb.append('!'); + sb.append(crA.formatAsString()); + sb.append(':'); + sb.append(crB.formatAsString()); + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/java/org/apache/poi/ss/formula/LazyRefEval.java b/src/java/org/apache/poi/ss/formula/LazyRefEval.java new file mode 100644 index 0000000000..2d80585fcb --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/LazyRefEval.java @@ -0,0 +1,88 @@ +/* ==================================================================== + 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.AreaI; +import org.apache.poi.hssf.record.formula.Ref3DPtg; +import org.apache.poi.hssf.record.formula.RefPtg; +import org.apache.poi.hssf.record.formula.AreaI.OffsetArea; +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.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.hssf.util.CellReference; + +/** +* +* @author Josh Micich +*/ +final class LazyRefEval extends RefEvalBase { + + private final Sheet _sheet; + private final CellEvaluator _evaluator; + + + public LazyRefEval(RefPtg ptg, Sheet sheet, CellEvaluator evaluator) { + super(ptg.getRow(), ptg.getColumn()); + _sheet = sheet; + _evaluator = evaluator; + } + public LazyRefEval(Ref3DPtg ptg, Sheet sheet, CellEvaluator evaluator) { + super(ptg.getRow(), ptg.getColumn()); + _sheet = sheet; + _evaluator = evaluator; + } + + public ValueEval getInnerValueEval() { + int rowIx = getRow(); + int colIx = getColumn(); + + Row row = _sheet.getRow(rowIx); + if (row == null) { + return BlankEval.INSTANCE; + } + Cell cell = row.getCell(colIx); + if (cell == null) { + return BlankEval.INSTANCE; + } + return _evaluator.getEvalForCell(cell); + } + + public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) { + + AreaI area = new OffsetArea(getRow(), getColumn(), + relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx); + + return new LazyAreaEval(area, _sheet, _evaluator); + } + + public String toString() { + CellReference cr = new CellReference(getRow(), getColumn()); + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()).append("["); + String sheetName = _evaluator.getSheetName(_sheet); + sb.append(sheetName); + sb.append('!'); + sb.append(cr.formatAsString()); + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java b/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java new file mode 100644 index 0000000000..79087e4d6d --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java @@ -0,0 +1,259 @@ +/* ==================================================================== + 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.AbstractFunctionPtg; +import org.apache.poi.hssf.record.formula.ControlPtg; +import org.apache.poi.hssf.record.formula.ValueOperatorPtg; +import org.apache.poi.hssf.record.formula.Ptg; + +/** + * This class performs 'operand class' transformation. Non-base tokens are classified into three + * operand classes: + *

    + *
  • reference
  • + *
  • value
  • + *
  • array
  • + *
+ *

+ * + * The final operand class chosen for each token depends on the formula type and the token's place + * in the formula. If POI gets the operand class wrong, Excel may interpret the formula + * incorrectly. This condition is typically manifested as a formula cell that displays as '#VALUE!', + * but resolves correctly when the user presses F2, enter.

+ * + * The logic implemented here was partially inspired by the description in + * "OpenOffice.org's Documentation of the Microsoft Excel File Format". The model presented there + * seems to be inconsistent with observed Excel behaviour (These differences have not been fully + * investigated). The implementation in this class has been heavily modified in order to satisfy + * concrete examples of how Excel performs the same logic (see TestRVA).

+ * + * Hopefully, as additional important test cases are identified and added to the test suite, + * patterns might become more obvious in this code and allow for simplification. + * + * @author Josh Micich + */ +final class OperandClassTransformer { + + private final int _formulaType; + + public OperandClassTransformer(int formulaType) { + _formulaType = formulaType; + } + + /** + * Traverses the supplied formula parse tree, calling Ptg.setClass() for each non-base + * token to set its operand class. + */ + public void transformFormula(ParseNode rootNode) { + byte rootNodeOperandClass; + switch (_formulaType) { + case FormulaType.CELL: + rootNodeOperandClass = Ptg.CLASS_VALUE; + break; + case FormulaType.DATAVALIDATION_LIST: + rootNodeOperandClass = Ptg.CLASS_REF; + break; + default: + throw new RuntimeException("Incomplete code - formula type (" + + _formulaType + ") not supported yet"); + + } + transformNode(rootNode, rootNodeOperandClass, false); + } + + /** + * @param callerForceArrayFlag true if one of the current node's parents is a + * function Ptg which has been changed from default 'V' to 'A' type (due to requirements on + * the function return value). + */ + private void transformNode(ParseNode node, byte desiredOperandClass, + boolean callerForceArrayFlag) { + Ptg token = node.getToken(); + ParseNode[] children = node.getChildren(); + boolean isSimpleValueFunc = isSimpleValueFunction(token); + + if (isSimpleValueFunc) { + boolean localForceArray = desiredOperandClass == Ptg.CLASS_ARRAY; + for (int i = 0; i < children.length; i++) { + transformNode(children[i], desiredOperandClass, localForceArray); + } + setSimpleValueFuncClass((AbstractFunctionPtg) token, desiredOperandClass, callerForceArrayFlag); + return; + } + + if (token instanceof ValueOperatorPtg || token instanceof ControlPtg) { + // Value Operator Ptgs and Control are base tokens, so token will be unchanged + // but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag + + // As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1" + // All direct operands of value operators that are initially 'R' type will + // be converted to 'V' type. + byte localDesiredOperandClass = desiredOperandClass == Ptg.CLASS_REF ? Ptg.CLASS_VALUE : desiredOperandClass; + for (int i = 0; i < children.length; i++) { + transformNode(children[i], localDesiredOperandClass, callerForceArrayFlag); + } + return; + } + if (token instanceof AbstractFunctionPtg) { + transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass, callerForceArrayFlag); + return; + } + if (children.length > 0) { + throw new IllegalStateException("Node should not have any children"); + } + + if (token.isBaseToken()) { + // nothing to do + return; + } + token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag)); + } + + private static boolean isSimpleValueFunction(Ptg token) { + if (token instanceof AbstractFunctionPtg) { + AbstractFunctionPtg aptg = (AbstractFunctionPtg) token; + if (aptg.getDefaultOperandClass() != Ptg.CLASS_VALUE) { + return false; + } + int numberOfOperands = aptg.getNumberOfOperands(); + for (int i=numberOfOperands-1; i>=0; i--) { + if (aptg.getParameterClass(i) != Ptg.CLASS_VALUE) { + return false; + } + } + return true; + } + return false; + } + + private byte transformClass(byte currentOperandClass, byte desiredOperandClass, + boolean callerForceArrayFlag) { + switch (desiredOperandClass) { + case Ptg.CLASS_VALUE: + if (!callerForceArrayFlag) { + return Ptg.CLASS_VALUE; + } + // else fall through + case Ptg.CLASS_ARRAY: + return Ptg.CLASS_ARRAY; + case Ptg.CLASS_REF: + if (!callerForceArrayFlag) { + return currentOperandClass; + } + return Ptg.CLASS_REF; + } + throw new IllegalStateException("Unexpected operand class (" + desiredOperandClass + ")"); + } + + private void transformFunctionNode(AbstractFunctionPtg afp, ParseNode[] children, + byte desiredOperandClass, boolean callerForceArrayFlag) { + + boolean localForceArrayFlag; + byte defaultReturnOperandClass = afp.getDefaultOperandClass(); + + if (callerForceArrayFlag) { + switch (defaultReturnOperandClass) { + case Ptg.CLASS_REF: + if (desiredOperandClass == Ptg.CLASS_REF) { + afp.setClass(Ptg.CLASS_REF); + } else { + afp.setClass(Ptg.CLASS_ARRAY); + } + localForceArrayFlag = false; + break; + case Ptg.CLASS_ARRAY: + afp.setClass(Ptg.CLASS_ARRAY); + localForceArrayFlag = false; + break; + case Ptg.CLASS_VALUE: + afp.setClass(Ptg.CLASS_ARRAY); + localForceArrayFlag = true; + break; + default: + throw new IllegalStateException("Unexpected operand class (" + + defaultReturnOperandClass + ")"); + } + } else { + if (defaultReturnOperandClass == desiredOperandClass) { + localForceArrayFlag = false; + // an alternative would have been to for non-base Ptgs to set their operand class + // from their default, but this would require the call in many subclasses because + // the default OC is not known until the end of the constructor + afp.setClass(defaultReturnOperandClass); + } else { + switch (desiredOperandClass) { + case Ptg.CLASS_VALUE: + // always OK to set functions to return 'value' + afp.setClass(Ptg.CLASS_VALUE); + localForceArrayFlag = false; + break; + case Ptg.CLASS_ARRAY: + switch (defaultReturnOperandClass) { + case Ptg.CLASS_REF: + afp.setClass(Ptg.CLASS_REF); +// afp.setClass(Ptg.CLASS_ARRAY); + break; + case Ptg.CLASS_VALUE: + afp.setClass(Ptg.CLASS_ARRAY); + break; + default: + throw new IllegalStateException("Unexpected operand class (" + + defaultReturnOperandClass + ")"); + } + localForceArrayFlag = (defaultReturnOperandClass == Ptg.CLASS_VALUE); + break; + case Ptg.CLASS_REF: + switch (defaultReturnOperandClass) { + case Ptg.CLASS_ARRAY: + afp.setClass(Ptg.CLASS_ARRAY); + break; + case Ptg.CLASS_VALUE: + afp.setClass(Ptg.CLASS_VALUE); + break; + default: + throw new IllegalStateException("Unexpected operand class (" + + defaultReturnOperandClass + ")"); + } + localForceArrayFlag = false; + break; + default: + throw new IllegalStateException("Unexpected operand class (" + + desiredOperandClass + ")"); + } + + } + } + + for (int i = 0; i < children.length; i++) { + ParseNode child = children[i]; + byte paramOperandClass = afp.getParameterClass(i); + transformNode(child, paramOperandClass, localForceArrayFlag); + } + } + + private void setSimpleValueFuncClass(AbstractFunctionPtg afp, + byte desiredOperandClass, boolean callerForceArrayFlag) { + + if (callerForceArrayFlag || desiredOperandClass == Ptg.CLASS_ARRAY) { + afp.setClass(Ptg.CLASS_ARRAY); + } else { + afp.setClass(Ptg.CLASS_VALUE); + } + } +} diff --git a/src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java b/src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java new file mode 100755 index 0000000000..eaa57c114f --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java @@ -0,0 +1,184 @@ +/* ==================================================================== + 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.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +import org.apache.poi.hssf.record.formula.AddPtg; +import org.apache.poi.hssf.record.formula.ConcatPtg; +import org.apache.poi.hssf.record.formula.DividePtg; +import org.apache.poi.hssf.record.formula.EqualPtg; +import org.apache.poi.hssf.record.formula.ExpPtg; +import org.apache.poi.hssf.record.formula.FuncPtg; +import org.apache.poi.hssf.record.formula.FuncVarPtg; +import org.apache.poi.hssf.record.formula.GreaterEqualPtg; +import org.apache.poi.hssf.record.formula.GreaterThanPtg; +import org.apache.poi.hssf.record.formula.LessEqualPtg; +import org.apache.poi.hssf.record.formula.LessThanPtg; +import org.apache.poi.hssf.record.formula.MultiplyPtg; +import org.apache.poi.hssf.record.formula.NotEqualPtg; +import org.apache.poi.hssf.record.formula.OperationPtg; +import org.apache.poi.hssf.record.formula.PercentPtg; +import org.apache.poi.hssf.record.formula.PowerPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.SubtractPtg; +import org.apache.poi.hssf.record.formula.UnaryMinusPtg; +import org.apache.poi.hssf.record.formula.UnaryPlusPtg; +import org.apache.poi.hssf.record.formula.eval.AddEval; +import org.apache.poi.hssf.record.formula.eval.ConcatEval; +import org.apache.poi.hssf.record.formula.eval.DivideEval; +import org.apache.poi.hssf.record.formula.eval.EqualEval; +import org.apache.poi.hssf.record.formula.eval.FuncVarEval; +import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval; +import org.apache.poi.hssf.record.formula.eval.GreaterThanEval; +import org.apache.poi.hssf.record.formula.eval.LessEqualEval; +import org.apache.poi.hssf.record.formula.eval.LessThanEval; +import org.apache.poi.hssf.record.formula.eval.MultiplyEval; +import org.apache.poi.hssf.record.formula.eval.NotEqualEval; +import org.apache.poi.hssf.record.formula.eval.OperationEval; +import org.apache.poi.hssf.record.formula.eval.PercentEval; +import org.apache.poi.hssf.record.formula.eval.PowerEval; +import org.apache.poi.hssf.record.formula.eval.SubtractEval; +import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; +import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; + +/** + * This class creates OperationEval instances to help evaluate OperationPtg + * formula tokens. + * + * @author Josh Micich + */ +final class OperationEvaluatorFactory { + private static final Class[] OPERATION_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; + // TODO - use singleton instances directly instead of reflection + private static final Map _constructorsByPtgClass = initialiseConstructorsMap(); + private static final Map _instancesByPtgClass = initialiseInstancesMap(); + + private OperationEvaluatorFactory() { + // no instances of this class + } + + private static Map initialiseConstructorsMap() { + Map m = new HashMap(32); + add(m, ConcatPtg.class, ConcatEval.class); + add(m, FuncPtg.class, FuncVarEval.class); + add(m, FuncVarPtg.class, FuncVarEval.class); + return m; + } + private static Map initialiseInstancesMap() { + Map m = new HashMap(32); + add(m, EqualPtg.class, EqualEval.instance); + add(m, GreaterEqualPtg.class, GreaterEqualEval.instance); + add(m, GreaterThanPtg.class, GreaterThanEval.instance); + add(m, LessEqualPtg.class, LessEqualEval.instance); + add(m, LessThanPtg.class, LessThanEval.instance); + add(m, NotEqualPtg.class, NotEqualEval.instance); + + add(m, AddPtg.class, AddEval.instance); + add(m, DividePtg.class, DivideEval.instance); + add(m, MultiplyPtg.class, MultiplyEval.instance); + add(m, PercentPtg.class, PercentEval.instance); + add(m, PowerPtg.class, PowerEval.instance); + add(m, SubtractPtg.class, SubtractEval.instance); + add(m, UnaryMinusPtg.class, UnaryMinusEval.instance); + add(m, UnaryPlusPtg.class, UnaryPlusEval.instance); + return m; + } + + private static void add(Map m, Class ptgClass, OperationEval evalInstance) { + if(!Ptg.class.isAssignableFrom(ptgClass)) { + throw new IllegalArgumentException("Expected Ptg subclass"); + } + m.put(ptgClass, evalInstance); + } + + private static void add(Map m, Class ptgClass, Class evalClass) { + // perform some validation now, to keep later exception handlers simple + if(!Ptg.class.isAssignableFrom(ptgClass)) { + throw new IllegalArgumentException("Expected Ptg subclass"); + } + + if(!OperationEval.class.isAssignableFrom(evalClass)) { + throw new IllegalArgumentException("Expected OperationEval subclass"); + } + if (!Modifier.isPublic(evalClass.getModifiers())) { + throw new RuntimeException("Eval class must be public"); + } + if (Modifier.isAbstract(evalClass.getModifiers())) { + throw new RuntimeException("Eval class must not be abstract"); + } + + Constructor constructor; + try { + constructor = evalClass.getDeclaredConstructor(OPERATION_CONSTRUCTOR_CLASS_ARRAY); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Missing constructor"); + } + if (!Modifier.isPublic(constructor.getModifiers())) { + throw new RuntimeException("Eval constructor must be public"); + } + m.put(ptgClass, constructor); + } + + /** + * returns the OperationEval concrete impl instance corresponding + * to the supplied operationPtg + */ + public static OperationEval create(OperationPtg ptg) { + if(ptg == null) { + throw new IllegalArgumentException("ptg must not be null"); + } + Object result; + + Class ptgClass = ptg.getClass(); + + result = _instancesByPtgClass.get(ptgClass); + if (result != null) { + return (OperationEval) result; + } + + + Constructor constructor = (Constructor) _constructorsByPtgClass.get(ptgClass); + if(constructor == null) { + if(ptgClass == ExpPtg.class) { + // ExpPtg is used for array formulas and shared formulas. + // it is currently unsupported, and may not even get implemented here + throw new RuntimeException("ExpPtg currently not supported"); + } + throw new RuntimeException("Unexpected operation ptg class (" + ptgClass.getName() + ")"); + } + + Object[] initargs = { ptg }; + try { + result = constructor.newInstance(initargs); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + return (OperationEval) result; + } +} diff --git a/src/java/org/apache/poi/ss/formula/ParseNode.java b/src/java/org/apache/poi/ss/formula/ParseNode.java new file mode 100644 index 0000000000..75685dee64 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/ParseNode.java @@ -0,0 +1,201 @@ +/* ==================================================================== + 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.AttrPtg; +import org.apache.poi.hssf.record.formula.FuncVarPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; +/** + * Represents a syntactic element from a formula by encapsulating the corresponding Ptg + * token. Each ParseNode may have child ParseNodes in the case when the wrapped + * Ptg is non-atomic. + * + * @author Josh Micich + */ +final class ParseNode { + + public static final ParseNode[] EMPTY_ARRAY = { }; + private final Ptg _token; + private final ParseNode[] _children; + private boolean _isIf; + private final int _tokenCount; + + public ParseNode(Ptg token, ParseNode[] children) { + _token = token; + _children = children; + _isIf = isIf(token); + int tokenCount = 1; + for (int i = 0; i < children.length; i++) { + tokenCount += children[i].getTokenCount(); + } + if (_isIf) { + // there will be 2 or 3 extra tAttr tokens according to whether the false param is present + tokenCount += children.length; + } + _tokenCount = tokenCount; + } + public ParseNode(Ptg token) { + this(token, EMPTY_ARRAY); + } + public ParseNode(Ptg token, ParseNode child0) { + this(token, new ParseNode[] { child0, }); + } + public ParseNode(Ptg token, ParseNode child0, ParseNode child1) { + this(token, new ParseNode[] { child0, child1, }); + } + private int getTokenCount() { + return _tokenCount; + } + + /** + * Collects the array of Ptg tokens for the specified tree. + */ + public static Ptg[] toTokenArray(ParseNode rootNode) { + TokenCollector temp = new TokenCollector(rootNode.getTokenCount()); + rootNode.collectPtgs(temp); + return temp.getResult(); + } + private void collectPtgs(TokenCollector temp) { + if (isIf(getToken())) { + collectIfPtgs(temp); + return; + } + for (int i=0; i< getChildren().length; i++) { + getChildren()[i].collectPtgs(temp); + } + temp.add(getToken()); + } + /** + * The IF() function gets marked up with two or three tAttr tokens. + * Similar logic will be required for CHOOSE() when it is supported + * + * See excelfileformat.pdf sec 3.10.5 "tAttr (19H) + */ + private void collectIfPtgs(TokenCollector temp) { + + // condition goes first + getChildren()[0].collectPtgs(temp); + + // placeholder for tAttrIf + int ifAttrIndex = temp.createPlaceholder(); + + // true parameter + getChildren()[1].collectPtgs(temp); + + // placeholder for first skip attr + int skipAfterTrueParamIndex = temp.createPlaceholder(); + int trueParamSize = temp.sumTokenSizes(ifAttrIndex+1, skipAfterTrueParamIndex); + + AttrPtg attrIf = new AttrPtg(); + attrIf.setOptimizedIf(true); + AttrPtg attrSkipAfterTrue = new AttrPtg(); + attrSkipAfterTrue.setGoto(true); + + if (getChildren().length > 2) { + // false param present + + // false parameter + getChildren()[2].collectPtgs(temp); + + int skipAfterFalseParamIndex = temp.createPlaceholder(); + + AttrPtg attrSkipAfterFalse = new AttrPtg(); + attrSkipAfterFalse.setGoto(true); + + int falseParamSize = temp.sumTokenSizes(skipAfterTrueParamIndex+1, skipAfterFalseParamIndex); + + attrIf.setData((short)(trueParamSize + 4)); // distance to start of false parameter. +4 for skip after true + attrSkipAfterTrue.setData((short)(falseParamSize + 4 + 4 - 1)); // 1 less than distance to end of if FuncVar(size=4). +4 for attr skip before + attrSkipAfterFalse.setData((short)(4 - 1)); // 1 less than distance to end of if FuncVar(size=4). + + temp.setPlaceholder(ifAttrIndex, attrIf); + temp.setPlaceholder(skipAfterTrueParamIndex, attrSkipAfterTrue); + temp.setPlaceholder(skipAfterFalseParamIndex, attrSkipAfterFalse); + } else { + // false parameter not present + attrIf.setData((short)(trueParamSize + 4)); // distance to start of FuncVar. +4 for skip after true + attrSkipAfterTrue.setData((short)(4 - 1)); // 1 less than distance to end of if FuncVar(size=4). + + temp.setPlaceholder(ifAttrIndex, attrIf); + temp.setPlaceholder(skipAfterTrueParamIndex, attrSkipAfterTrue); + } + + temp.add(getToken()); + } + + private static boolean isIf(Ptg token) { + if (token instanceof FuncVarPtg) { + FuncVarPtg func = (FuncVarPtg) token; + if (FunctionMetadataRegistry.FUNCTION_NAME_IF.equals(func.getName())) { + return true; + } + } + return false; + } + + public Ptg getToken() { + return _token; + } + + public ParseNode[] getChildren() { + return _children; + } + + private static final class TokenCollector { + + private final Ptg[] _ptgs; + private int _offset; + + public TokenCollector(int tokenCount) { + _ptgs = new Ptg[tokenCount]; + _offset = 0; + } + + public int sumTokenSizes(int fromIx, int toIx) { + int result = 0; + for (int i=fromIx; i + * + * 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(Sheet 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(Sheet sheet, int rowIndex, int columnIndex) { + int sheetIndex = getSheetIndex(sheet); + _cache.clearValue(sheetIndex, rowIndex, columnIndex); + + } + private int getSheetIndex(Sheet sheet) { + // TODO cache sheet indexes too + return _workbook.getSheetIndex(sheet); + } + + public ValueEval evaluate(Cell 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(Cell 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 Sheet 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); + Sheet 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; + Sheet xsheet = getOtherSheet(refPtg.getExternSheetIndex()); + return new LazyRefEval(refPtg, xsheet, ce); + } + if (ptg instanceof Area3DPtg) { + Area3DPtg a3dp = (Area3DPtg) ptg; + Sheet 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/java/org/apache/poi/ss/usermodel/CellValue.java b/src/java/org/apache/poi/ss/usermodel/CellValue.java new file mode 100644 index 0000000000..705f557101 --- /dev/null +++ b/src/java/org/apache/poi/ss/usermodel/CellValue.java @@ -0,0 +1,114 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.ss.usermodel; + +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.ss.usermodel.Cell; + +/** + * 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 final class CellValue { + public static final CellValue TRUE = new CellValue(Cell.CELL_TYPE_BOOLEAN, 0.0, true, null, 0); + public static final CellValue FALSE= new CellValue(Cell.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; + } + + + public CellValue(double numberValue) { + this(Cell.CELL_TYPE_NUMERIC, numberValue, false, null, 0); + } + public static CellValue valueOf(boolean booleanValue) { + return booleanValue ? TRUE : FALSE; + } + public CellValue(String stringValue) { + this(Cell.CELL_TYPE_STRING, 0.0, false, stringValue, 0); + } + public static CellValue getError(int errorCode) { + return new CellValue(Cell.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; + } + 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 Cell.CELL_TYPE_NUMERIC: + return String.valueOf(_numberValue); + case Cell.CELL_TYPE_STRING: + return '"' + _textValue + '"'; + case Cell.CELL_TYPE_BOOLEAN: + return _booleanValue ? "TRUE" : "FALSE"; + case Cell.CELL_TYPE_ERROR: + return ErrorEval.getText(_errorCode); + } + return ""; + } +} diff --git a/src/java/org/apache/poi/ss/usermodel/EvaluationCache.java b/src/java/org/apache/poi/ss/usermodel/EvaluationCache.java deleted file mode 100644 index 75eda02fbb..0000000000 --- a/src/java/org/apache/poi/ss/usermodel/EvaluationCache.java +++ /dev/null @@ -1,95 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.ss.usermodel; - -import java.util.HashMap; -import java.util.Map; - -import org.apache.poi.hssf.record.formula.eval.ValueEval; - -/** - * Performance optimisation for {@link HSSFFormulaEvaluator}. This class stores previously - * calculated values of already visited cells, to avoid unnecessary re-calculation when the - * same cells are referenced multiple times - * - * - * @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 final Map _valuesByKey; - - /* package */EvaluationCache() { - _valuesByKey = new HashMap(); - } - - public ValueEval getValue(int sheetIndex, int srcRowNum, int srcColNum) { - Key key = new Key(sheetIndex, srcRowNum, srcColNum); - return (ValueEval) _valuesByKey.get(key); - } - - public void setValue(int sheetIndex, int srcRowNum, int srcColNum, ValueEval value) { - Key key = new Key(sheetIndex, srcRowNum, srcColNum); - if (_valuesByKey.containsKey(key)) { - throw new RuntimeException("Already have cached value for this cell"); - } - _valuesByKey.put(key, value); - } - - /** - * Should be called whenever there are changes to input cells in the evaluated workbook. - */ - public void clear() { - _valuesByKey.clear(); - } -} diff --git a/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetector.java b/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetector.java deleted file mode 100755 index 30c6d31b1e..0000000000 --- a/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetector.java +++ /dev/null @@ -1,150 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.ss.usermodel; - -import java.util.ArrayList; -import java.util.List; - -/** - * Instances of this class keep track of multiple dependent cell evaluations due - * to recursive calls to FormulaEvaluator.internalEvaluate(). - * The main purpose of this class is to detect an attempt to evaluate a cell - * that is already being evaluated. In other words, it detects circular - * references in spreadsheet formulas. - * - * @author Josh Micich - */ -final class EvaluationCycleDetector { - - /** - * Stores the parameters that identify the evaluation of one cell.
- */ - private static final class CellEvaluationFrame { - - private final Workbook _workbook; - private final int _sheetIndex; - private final int _srcRowNum; - private final int _srcColNum; - - public CellEvaluationFrame(Workbook 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(); - } - } - - private final List _evaluationFrames; - - public EvaluationCycleDetector() { - _evaluationFrames = new ArrayList(); - } - - /** - * Notifies this evaluation tracker that evaluation of the specified cell is - * about to start.
- * - * In the case of a true return code, the caller should - * continue evaluation of the specified cell, and also be sure to call - * endEvaluate() when complete.
- * - * In the case of a false return code, the caller should - * return an evaluation result of - * ErrorEval.CIRCULAR_REF_ERROR, and not call endEvaluate(). - *
- * @return true if the specified cell has not been visited yet in the current - * evaluation. false if the specified cell is already being evaluated. - */ - public boolean startEvaluate(Workbook workbook, int sheetIndex, int srcRowNum, int srcColNum) { - CellEvaluationFrame cef = new CellEvaluationFrame(workbook, sheetIndex, srcRowNum, srcColNum); - if (_evaluationFrames.contains(cef)) { - return false; - } - _evaluationFrames.add(cef); - return true; - } - - /** - * Notifies this evaluation tracker that the evaluation of the specified - * cell is complete.

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

- * - * Assuming a well behaved client, parameters to this method would not be - * required. However, they have been included to assert correct behaviour, - * and form more meaningful error messages. - */ - public void endEvaluate(Workbook workbook, int sheetIndex, int srcRowNum, int srcColNum) { - int nFrames = _evaluationFrames.size(); - if (nFrames < 1) { - throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate"); - } - - nFrames--; - CellEvaluationFrame cefExpected = (CellEvaluationFrame) _evaluationFrames.get(nFrames); - CellEvaluationFrame cefActual = new CellEvaluationFrame(workbook, sheetIndex, srcRowNum, srcColNum); - if (!cefActual.equals(cefExpected)) { - throw new RuntimeException("Wrong cell specified. " - + "Corresponding startEvaluate() call was for cell {" - + cefExpected.formatAsString() + "} this endEvaluate() call is for cell {" - + cefActual.formatAsString() + "}"); - } - // else - no problems so pop current frame - _evaluationFrames.remove(nFrames); - } -} diff --git a/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetectorManager.java b/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetectorManager.java deleted file mode 100755 index d83d187002..0000000000 --- a/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetectorManager.java +++ /dev/null @@ -1,46 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.ss.usermodel; - -/** - * This class makes an EvaluationCycleDetector instance available to - * each thread via a ThreadLocal in order to avoid adding a parameter - * to a few protected methods within HSSFFormulaEvaluator. - * - * @author Josh Micich - */ -final class EvaluationCycleDetectorManager { - - ThreadLocal tl = null; - private static ThreadLocal _tlEvaluationTracker = new ThreadLocal() { - protected synchronized Object initialValue() { - return new EvaluationCycleDetector(); - } - }; - - /** - * @return - */ - public static EvaluationCycleDetector getTracker() { - return (EvaluationCycleDetector) _tlEvaluationTracker.get(); - } - - private EvaluationCycleDetectorManager() { - // no instances of this class - } -} diff --git a/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java b/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java index 0f1b7bc47c..1234c9564f 100644 --- a/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java +++ b/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java @@ -17,43 +17,6 @@ package org.apache.poi.ss.usermodel; -import java.util.Iterator; -import java.util.Stack; - -import org.apache.poi.hssf.model.FormulaParser; -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.

@@ -65,85 +28,15 @@ import org.apache.poi.hssf.util.CellReference; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * @author Josh Micich */ -public class FormulaEvaluator { - - /** - * used to track the number of evaluations - */ - private static final class Counter { - public int value; - public int depth; - public Counter() { - value = 0; - } - } - - protected final Workbook _workbook; - private final EvaluationCache _cache; - - private Counter _evaluationCounter; - - /** - * @deprecated (Sep 2008) Sheet parameter is ignored - */ - public FormulaEvaluator(Sheet sheet, Workbook workbook) { - this(workbook); - if (false) { - sheet.toString(); // suppress unused parameter compiler warning - } - } - public FormulaEvaluator(Workbook workbook) { - this(workbook, new EvaluationCache(), new Counter()); - } - - private FormulaEvaluator(Workbook workbook, EvaluationCache cache, Counter evaluationCounter) { - _workbook = workbook; - _cache = cache; - _evaluationCounter = evaluationCounter; - } - - /** - * for debug use. Used in toString methods - */ - public String getSheetName(Sheet sheet) { - return _workbook.getSheetName(_workbook.getSheetIndex(sheet)); - } - /** - * 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); - } - } - - /** - * Does nothing - * @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell - */ - public void setCurrentRow(Row row) { - // do nothing - if (false) { - row.getClass(); // suppress unused parameter compiler warning - } - } +public interface FormulaEvaluator { /** * 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(); - } - + void clearAllCachedResultValues(); + void clearCachedResultValue(Sheet sheet, int rowIndex, int columnIndex); /** * If cell contains a formula, the formula is evaluated and returned, * else the CellValue simply copies the appropriate cell value from @@ -152,25 +45,7 @@ public class FormulaEvaluator { * original cell. * @param cell */ - public CellValue evaluate(Cell cell) { - if (cell == null) { - return null; - } - - switch (cell.getCellType()) { - case Cell.CELL_TYPE_BOOLEAN: - return CellValue.valueOf(cell.getBooleanCellValue()); - case Cell.CELL_TYPE_ERROR: - return CellValue.getError(cell.getErrorCellValue()); - case Cell.CELL_TYPE_FORMULA: - return evaluateFormulaCellValue(cell); - case Cell.CELL_TYPE_NUMERIC: - return new CellValue(cell.getNumericCellValue(), _workbook.getCreationHelper()); - case Cell.CELL_TYPE_STRING: - return new CellValue(cell.getRichStringCellValue().getString(), _workbook.getCreationHelper()); - } - throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")"); - } + CellValue evaluate(Cell cell); /** @@ -191,15 +66,7 @@ public class FormulaEvaluator { * @param cell The cell to evaluate * @return The type of the formula result (the cell's type remains as Cell.CELL_TYPE_FORMULA however) */ - public int evaluateFormulaCell(Cell cell) { - if (cell == null || cell.getCellType() != Cell.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(); - } + int evaluateFormulaCell(Cell cell); /** * If cell contains formula, it evaluates the formula, and @@ -217,462 +84,5 @@ public class FormulaEvaluator { * value computed for you, use {@link #evaluateFormulaCell(Cell)} * @param cell */ - public Cell evaluateInCell(Cell cell) { - if (cell == null) { - return null; - } - if (cell.getCellType() == Cell.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(Cell cell, CellValue cv) { - int cellType = cv.getCellType(); - switch (cellType) { - case Cell.CELL_TYPE_BOOLEAN: - case Cell.CELL_TYPE_ERROR: - case Cell.CELL_TYPE_NUMERIC: - case Cell.CELL_TYPE_STRING: - cell.setCellType(cellType); - return; - case Cell.CELL_TYPE_BLANK: - // never happens - blanks eventually get translated to zero - case Cell.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(Cell cell, CellValue cv) { - int cellType = cv.getCellType(); - switch (cellType) { - case Cell.CELL_TYPE_BOOLEAN: - cell.setCellValue(cv.getBooleanValue()); - break; - case Cell.CELL_TYPE_ERROR: - cell.setCellErrorValue(cv.getErrorValue()); - break; - case Cell.CELL_TYPE_NUMERIC: - cell.setCellValue(cv.getNumberValue()); - break; - case Cell.CELL_TYPE_STRING: - cell.setCellValue(cv.getRichTextStringValue()); - break; - case Cell.CELL_TYPE_BLANK: - // never happens - blanks eventually get translated to zero - case Cell.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(Workbook wb) { - FormulaEvaluator evaluator = new FormulaEvaluator(wb); - for(int i=0; inull, never {@link BlankEval} - */ - private ValueEval internalEvaluate(Cell 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, - Workbook 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 Sheet getOtherSheet(int externSheetIndex) { - return _workbook.getSheetAt(_workbook.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) + ")"); - } - if(_workbook instanceof org.apache.poi.hssf.usermodel.HSSFWorkbook) { - NameRecord nameRecord = ((org.apache.poi.hssf.usermodel.HSSFWorkbook)_workbook).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() + "'"); - } - throw new RuntimeException("Don't now how to evalate name for XSSFWorkbook"); - } - 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()); - } - Sheet 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; - Sheet xsheet = getOtherSheet(refPtg.getExternSheetIndex()); - return new LazyRefEval(refPtg, xsheet, this); - } - if (ptg instanceof Area3DPtg) { - Area3DPtg a3dp = (Area3DPtg) ptg; - Sheet 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. - */ - public ValueEval getEvalForCell(Cell cell) { - - if (cell == null) { - return BlankEval.INSTANCE; - } - switch (cell.getCellType()) { - case Cell.CELL_TYPE_NUMERIC: - return new NumberEval(cell.getNumericCellValue()); - case Cell.CELL_TYPE_STRING: - return new StringEval(cell.getRichStringCellValue().getString()); - case Cell.CELL_TYPE_FORMULA: - return internalEvaluate(cell); - case Cell.CELL_TYPE_BOOLEAN: - return BoolEval.valueOf(cell.getBooleanCellValue()); - case Cell.CELL_TYPE_BLANK: - return BlankEval.INSTANCE; - case Cell.CELL_TYPE_ERROR: - return ErrorEval.valueOf(cell.getErrorCellValue()); - } - throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); - } - - /** - * 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(Cell.CELL_TYPE_BOOLEAN, 0.0, true, null, 0, null); - public static final CellValue FALSE= new CellValue(Cell.CELL_TYPE_BOOLEAN, 0.0, false, null, 0, null); - - private final int _cellType; - private final double _numberValue; - private final boolean _booleanValue; - private final String _textValue; - private final int _errorCode; - private CreationHelper _creationHelper; - - private CellValue(int cellType, double numberValue, boolean booleanValue, - String textValue, int errorCode, CreationHelper creationHelper) { - _cellType = cellType; - _numberValue = numberValue; - _booleanValue = booleanValue; - _textValue = textValue; - _errorCode = errorCode; - _creationHelper = creationHelper; - } - - - /* package*/ CellValue(double numberValue, CreationHelper creationHelper) { - this(Cell.CELL_TYPE_NUMERIC, numberValue, false, null, 0, creationHelper); - } - /* package*/ static CellValue valueOf(boolean booleanValue) { - return booleanValue ? TRUE : FALSE; - } - /* package*/ CellValue(String stringValue, CreationHelper creationHelper) { - this(Cell.CELL_TYPE_STRING, 0.0, false, stringValue, 0, creationHelper); - } - /* package*/ static CellValue getError(int errorCode) { - return new CellValue(Cell.CELL_TYPE_ERROR, 0.0, false, null, errorCode, null); - } - - - /** - * @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 RichTextString getRichTextStringValue() { - return _creationHelper.createRichTextString(_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 Cell.CELL_TYPE_NUMERIC: - return String.valueOf(_numberValue); - case Cell.CELL_TYPE_STRING: - return '"' + _textValue + '"'; - case Cell.CELL_TYPE_BOOLEAN: - return _booleanValue ? "TRUE" : "FALSE"; - case Cell.CELL_TYPE_ERROR: - return ErrorEval.getText(_errorCode); - } - return ""; - } - } + Cell evaluateInCell(Cell cell); } diff --git a/src/java/org/apache/poi/ss/usermodel/LazyAreaEval.java b/src/java/org/apache/poi/ss/usermodel/LazyAreaEval.java deleted file mode 100644 index 8d9b192291..0000000000 --- a/src/java/org/apache/poi/ss/usermodel/LazyAreaEval.java +++ /dev/null @@ -1,79 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.ss.usermodel; - -import org.apache.poi.hssf.record.formula.AreaI; -import org.apache.poi.hssf.record.formula.AreaI.OffsetArea; -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.util.CellReference; - -/** - * - * @author Josh Micich - */ -public class LazyAreaEval extends AreaEvalBase { - - private final Sheet _sheet; - private FormulaEvaluator _evaluator; - - public LazyAreaEval(AreaI ptg, Sheet sheet, FormulaEvaluator evaluator) { - super(ptg); - _sheet = sheet; - _evaluator = evaluator; - } - - public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) { - - int rowIx = (relativeRowIndex + getFirstRow() ) & 0xFFFF; - int colIx = (relativeColumnIndex + getFirstColumn() ) & 0x00FF; - - Row row = _sheet.getRow(rowIx); - if (row == null) { - return BlankEval.INSTANCE; - } - Cell cell = row.getCell(colIx); - if (cell == null) { - return BlankEval.INSTANCE; - } - return _evaluator.getEvalForCell(cell); - } - - public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) { - AreaI area = new OffsetArea(getFirstRow(), getFirstColumn(), - relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx); - - return new LazyAreaEval(area, _sheet, _evaluator); - } - public String toString() { - CellReference crA = new CellReference(getFirstRow(), getFirstColumn()); - CellReference crB = new CellReference(getLastRow(), getLastColumn()); - StringBuffer sb = new StringBuffer(); - sb.append(getClass().getName()).append("["); - String sheetName = _evaluator.getSheetName(_sheet); - sb.append(sheetName); - sb.append('!'); - sb.append(crA.formatAsString()); - sb.append(':'); - sb.append(crB.formatAsString()); - sb.append("]"); - return sb.toString(); - } -} diff --git a/src/java/org/apache/poi/ss/usermodel/LazyRefEval.java b/src/java/org/apache/poi/ss/usermodel/LazyRefEval.java deleted file mode 100644 index 7fb7681aeb..0000000000 --- a/src/java/org/apache/poi/ss/usermodel/LazyRefEval.java +++ /dev/null @@ -1,85 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.ss.usermodel; - -import org.apache.poi.hssf.record.formula.AreaI; -import org.apache.poi.hssf.record.formula.Ref3DPtg; -import org.apache.poi.hssf.record.formula.RefPtg; -import org.apache.poi.hssf.record.formula.AreaI.OffsetArea; -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.util.CellReference; - -/** -* -* @author Josh Micich -*/ -public class LazyRefEval extends RefEvalBase { - - private final Sheet _sheet; - private final FormulaEvaluator _evaluator; - - - public LazyRefEval(RefPtg ptg, Sheet sheet, FormulaEvaluator evaluator) { - super(ptg.getRow(), ptg.getColumn()); - _sheet = sheet; - _evaluator = evaluator; - } - public LazyRefEval(Ref3DPtg ptg, Sheet sheet, FormulaEvaluator evaluator) { - super(ptg.getRow(), ptg.getColumn()); - _sheet = sheet; - _evaluator = evaluator; - } - - public ValueEval getInnerValueEval() { - int rowIx = getRow(); - int colIx = getColumn(); - - Row row = _sheet.getRow(rowIx); - if (row == null) { - return BlankEval.INSTANCE; - } - Cell cell = row.getCell(colIx); - if (cell == null) { - return BlankEval.INSTANCE; - } - return _evaluator.getEvalForCell(cell); - } - - public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) { - - AreaI area = new OffsetArea(getRow(), getColumn(), - relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx); - - return new LazyAreaEval(area, _sheet, _evaluator); - } - - public String toString() { - CellReference cr = new CellReference(getRow(), getColumn()); - StringBuffer sb = new StringBuffer(); - sb.append(getClass().getName()).append("["); - String sheetName = _evaluator.getSheetName(_sheet); - sb.append(sheetName); - sb.append('!'); - sb.append(cr.formatAsString()); - sb.append("]"); - return sb.toString(); - } -} diff --git a/src/java/org/apache/poi/ss/usermodel/OperationEvaluatorFactory.java b/src/java/org/apache/poi/ss/usermodel/OperationEvaluatorFactory.java deleted file mode 100755 index ca8a92312a..0000000000 --- a/src/java/org/apache/poi/ss/usermodel/OperationEvaluatorFactory.java +++ /dev/null @@ -1,184 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.ss.usermodel; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.Map; - -import org.apache.poi.hssf.record.formula.AddPtg; -import org.apache.poi.hssf.record.formula.ConcatPtg; -import org.apache.poi.hssf.record.formula.DividePtg; -import org.apache.poi.hssf.record.formula.EqualPtg; -import org.apache.poi.hssf.record.formula.ExpPtg; -import org.apache.poi.hssf.record.formula.FuncPtg; -import org.apache.poi.hssf.record.formula.FuncVarPtg; -import org.apache.poi.hssf.record.formula.GreaterEqualPtg; -import org.apache.poi.hssf.record.formula.GreaterThanPtg; -import org.apache.poi.hssf.record.formula.LessEqualPtg; -import org.apache.poi.hssf.record.formula.LessThanPtg; -import org.apache.poi.hssf.record.formula.MultiplyPtg; -import org.apache.poi.hssf.record.formula.NotEqualPtg; -import org.apache.poi.hssf.record.formula.OperationPtg; -import org.apache.poi.hssf.record.formula.PercentPtg; -import org.apache.poi.hssf.record.formula.PowerPtg; -import org.apache.poi.hssf.record.formula.Ptg; -import org.apache.poi.hssf.record.formula.SubtractPtg; -import org.apache.poi.hssf.record.formula.UnaryMinusPtg; -import org.apache.poi.hssf.record.formula.UnaryPlusPtg; -import org.apache.poi.hssf.record.formula.eval.AddEval; -import org.apache.poi.hssf.record.formula.eval.ConcatEval; -import org.apache.poi.hssf.record.formula.eval.DivideEval; -import org.apache.poi.hssf.record.formula.eval.EqualEval; -import org.apache.poi.hssf.record.formula.eval.FuncVarEval; -import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval; -import org.apache.poi.hssf.record.formula.eval.GreaterThanEval; -import org.apache.poi.hssf.record.formula.eval.LessEqualEval; -import org.apache.poi.hssf.record.formula.eval.LessThanEval; -import org.apache.poi.hssf.record.formula.eval.MultiplyEval; -import org.apache.poi.hssf.record.formula.eval.NotEqualEval; -import org.apache.poi.hssf.record.formula.eval.OperationEval; -import org.apache.poi.hssf.record.formula.eval.PercentEval; -import org.apache.poi.hssf.record.formula.eval.PowerEval; -import org.apache.poi.hssf.record.formula.eval.SubtractEval; -import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; -import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; - -/** - * This class creates OperationEval instances to help evaluate OperationPtg - * formula tokens. - * - * @author Josh Micich - */ -final class OperationEvaluatorFactory { - private static final Class[] OPERATION_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; - // TODO - use singleton instances directly instead of reflection - private static final Map _constructorsByPtgClass = initialiseConstructorsMap(); - private static final Map _instancesByPtgClass = initialiseInstancesMap(); - - private OperationEvaluatorFactory() { - // no instances of this class - } - - private static Map initialiseConstructorsMap() { - Map m = new HashMap(32); - add(m, ConcatPtg.class, ConcatEval.class); - add(m, FuncPtg.class, FuncVarEval.class); - add(m, FuncVarPtg.class, FuncVarEval.class); - return m; - } - private static Map initialiseInstancesMap() { - Map m = new HashMap(32); - add(m, EqualPtg.class, EqualEval.instance); - add(m, GreaterEqualPtg.class, GreaterEqualEval.instance); - add(m, GreaterThanPtg.class, GreaterThanEval.instance); - add(m, LessEqualPtg.class, LessEqualEval.instance); - add(m, LessThanPtg.class, LessThanEval.instance); - add(m, NotEqualPtg.class, NotEqualEval.instance); - - add(m, AddPtg.class, AddEval.instance); - add(m, DividePtg.class, DivideEval.instance); - add(m, MultiplyPtg.class, MultiplyEval.instance); - add(m, PercentPtg.class, PercentEval.instance); - add(m, PowerPtg.class, PowerEval.instance); - add(m, SubtractPtg.class, SubtractEval.instance); - add(m, UnaryMinusPtg.class, UnaryMinusEval.instance); - add(m, UnaryPlusPtg.class, UnaryPlusEval.instance); - return m; - } - - private static void add(Map m, Class ptgClass, OperationEval evalInstance) { - if(!Ptg.class.isAssignableFrom(ptgClass)) { - throw new IllegalArgumentException("Expected Ptg subclass"); - } - m.put(ptgClass, evalInstance); - } - - private static void add(Map m, Class ptgClass, Class evalClass) { - // perform some validation now, to keep later exception handlers simple - if(!Ptg.class.isAssignableFrom(ptgClass)) { - throw new IllegalArgumentException("Expected Ptg subclass"); - } - - if(!OperationEval.class.isAssignableFrom(evalClass)) { - throw new IllegalArgumentException("Expected OperationEval subclass"); - } - if (!Modifier.isPublic(evalClass.getModifiers())) { - throw new RuntimeException("Eval class must be public"); - } - if (Modifier.isAbstract(evalClass.getModifiers())) { - throw new RuntimeException("Eval class must not be abstract"); - } - - Constructor constructor; - try { - constructor = evalClass.getDeclaredConstructor(OPERATION_CONSTRUCTOR_CLASS_ARRAY); - } catch (NoSuchMethodException e) { - throw new RuntimeException("Missing constructor"); - } - if (!Modifier.isPublic(constructor.getModifiers())) { - throw new RuntimeException("Eval constructor must be public"); - } - m.put(ptgClass, constructor); - } - - /** - * returns the OperationEval concrete impl instance corresponding - * to the supplied operationPtg - */ - public static OperationEval create(OperationPtg ptg) { - if(ptg == null) { - throw new IllegalArgumentException("ptg must not be null"); - } - Object result; - - Class ptgClass = ptg.getClass(); - - result = _instancesByPtgClass.get(ptgClass); - if (result != null) { - return (OperationEval) result; - } - - - Constructor constructor = (Constructor) _constructorsByPtgClass.get(ptgClass); - if(constructor == null) { - if(ptgClass == ExpPtg.class) { - // ExpPtg is used for array formulas and shared formulas. - // it is currently unsupported, and may not even get implemented here - throw new RuntimeException("ExpPtg currently not supported"); - } - throw new RuntimeException("Unexpected operation ptg class (" + ptgClass.getName() + ")"); - } - - Object[] initargs = { ptg }; - try { - result = constructor.newInstance(initargs); - } catch (IllegalArgumentException e) { - throw new RuntimeException(e); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - return (OperationEval) result; - } -} diff --git a/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/Workbook.java b/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/Workbook.java index bcf1b4ee96..40c5006874 100644 --- a/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/Workbook.java +++ b/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/Workbook.java @@ -121,15 +121,6 @@ public interface Workbook { */ int getSheetIndex(Sheet sheet); - /** - * 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. - */ - int getExternalSheetIndex(int internalSheetIndex); - /** * create an HSSFSheet for this HSSFWorkbook, adds it to the sheets and returns * the high level representation. Use this to create new sheets. @@ -164,14 +155,6 @@ public interface Workbook { int getNumberOfSheets(); - /** - * Finds the sheet index for a particular external sheet number. - * @param externSheetNumber The external sheet number to convert - * @return The index to the sheet found. - */ - int getSheetIndexFromExternSheetIndex(int externSheetNumber); - - /** * Get the HSSFSheet object at the given index. * @param index of the sheet number (0-based physical & logical) @@ -194,8 +177,6 @@ public interface Workbook { */ void removeSheetAt(int index); - - String findSheetNameFromExternSheet(int externSheetIndex); /** * determine whether the Excel GUI will backup the workbook when saving. @@ -333,14 +314,6 @@ public interface Workbook { */ String getNameName(int index); - /** - * TODO - make this less cryptic / move elsewhere - * @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 - */ - String resolveNameXText(int refIndex, int definedNameIndex); - /** * Sets the printarea for the sheet provided *

diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java new file mode 100644 index 0000000000..2d442e8f4a --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java @@ -0,0 +1,167 @@ +package org.apache.poi.xssf.usermodel; + +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.FormulaParser; +import org.apache.poi.ss.formula.FormulaParsingWorkbook; +import org.apache.poi.ss.formula.FormulaRenderingWorkbook; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName; + +/** + * Internal POI use only + * + * @author Josh Micich + */ +public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook { + + private final XSSFWorkbook _uBook; + + public static XSSFEvaluationWorkbook create(XSSFWorkbook book) { + if (book == null) { + return null; + } + return new XSSFEvaluationWorkbook(book); + } + + private XSSFEvaluationWorkbook(XSSFWorkbook book) { + _uBook = book; + } + + private int convertFromExternalSheetIndex(int externSheetIndex) { + return externSheetIndex; + } + /** + * @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. Fairly easy on XSSF (we + * think...) since the internal and external indicies are the same + */ + private int convertToExternalSheetIndex(int sheetIndex) { + return sheetIndex; + } + + public int getExternalSheetIndex(String sheetName) { + int sheetIndex = _uBook.getSheetIndex(sheetName); + return convertToExternalSheetIndex(sheetIndex); + } + + public EvaluationName getName(int index) { + return new Name(_uBook.getNameAt(index), index, this); + } + + public EvaluationName getName(String name) { + for(int i=0; i < _uBook.getNumberOfNames(); i++) { + String nameText = _uBook.getNameName(i); + if (name.equalsIgnoreCase(nameText)) { + return new Name(_uBook.getNameAt(i), i, this); + } + } + return null; + } + + public int getSheetIndex(Sheet 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) { + // may require to return null to make tests pass + throw new RuntimeException("Not implemented yet"); + } + + public Sheet getSheet(int sheetIndex) { + return _uBook.getSheetAt(sheetIndex); + } + + /** + * Doesn't do anything - returns the same index + * TODO - figure out if this is a ole2 specific thing, or + * if we need to do something proper here too! + */ + public Sheet getSheetByExternSheetIndex(int externSheetIndex) { + int sheetIndex = convertFromExternalSheetIndex(externSheetIndex); + return _uBook.getSheetAt(sheetIndex); + } + + public Workbook getWorkbook() { + return _uBook; + } + + /** + * TODO - figure out what the hell this methods does in + * HSSF... + */ + public String resolveNameXText(NameXPtg n) { + throw new RuntimeException("method not implemented yet"); + } + + public String getSheetNameByExternSheet(int externSheetIndex) { + int sheetIndex = convertFromExternalSheetIndex(externSheetIndex); + return _uBook.getSheetName(sheetIndex); + } + + public String getNameText(NamePtg namePtg) { + return _uBook.getNameAt(namePtg.getIndex()).getNameName(); + } + public EvaluationName getName(NamePtg namePtg) { + int ix = namePtg.getIndex(); + return new Name(_uBook.getNameAt(ix), ix, this); + } + public Ptg[] getFormulaTokens(Cell cell) { + XSSFEvaluationWorkbook frBook = XSSFEvaluationWorkbook.create(_uBook); + return FormulaParser.parse(cell.getCellFormula(), frBook); + } + + private static final class Name implements EvaluationName { + + private final XSSFName _nameRecord; + private final int _index; + private final FormulaParsingWorkbook _fpBook; + + public Name(XSSFName name, int index, FormulaParsingWorkbook fpBook) { + _nameRecord = name; + _index = index; + _fpBook = fpBook; + } + + public Ptg[] getNameDefinition() { + + return FormulaParser.parse(_nameRecord.getReference(), _fpBook); + } + + public String getNameText() { + return _nameRecord.getNameName(); + } + + public boolean hasFormula() { + // TODO - no idea if this is right + CTDefinedName ctn = _nameRecord.getCTName(); + String strVal = ctn.getStringValue(); + return !ctn.getFunction() && strVal != null && strVal.length() > 0; + } + + public boolean isFunctionName() { + return _nameRecord.isFunctionName(); + } + + public boolean isRange() { + return hasFormula(); // TODO - is this right? + } + public NamePtg createPtg() { + return new NamePtg(_index); + } + } +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java new file mode 100644 index 0000000000..3dcedf8f5e --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java @@ -0,0 +1,256 @@ +/* ==================================================================== + 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.xssf.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.hssf.usermodel.HSSFCell; +import org.apache.poi.ss.formula.WorkbookEvaluator; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellValue; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; + +/** + * 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 XSSFFormulaEvaluator implements FormulaEvaluator { + + private WorkbookEvaluator _bookEvaluator; + + public XSSFFormulaEvaluator(XSSFWorkbook workbook) { + _bookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(workbook)); + } + + /** + * TODO for debug/test use + */ + /* package */ int getEvaluationCount() { + return _bookEvaluator.getEvaluationCount(); + } + + + /** + * 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(Sheet 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(Cell cell) { + if (cell == null) { + return null; + } + + switch (cell.getCellType()) { + case XSSFCell.CELL_TYPE_BOOLEAN: + return CellValue.valueOf(cell.getBooleanCellValue()); + case XSSFCell.CELL_TYPE_ERROR: + return CellValue.getError(cell.getErrorCellValue()); + case XSSFCell.CELL_TYPE_FORMULA: + return evaluateFormulaCellValue(cell); + case XSSFCell.CELL_TYPE_NUMERIC: + return new CellValue(cell.getNumericCellValue()); + case XSSFCell.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(Cell cell) { + if (cell == null || cell.getCellType() != XSSFCell.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 XSSFCell evaluateInCell(Cell cell) { + if (cell == null) { + return null; + } + XSSFCell result = (XSSFCell) cell; + if (cell.getCellType() == XSSFCell.CELL_TYPE_FORMULA) { + CellValue cv = evaluateFormulaCellValue(cell); + setCellType(cell, cv); // cell will no longer be a formula cell + setCellValue(cell, cv); + } + return result; + } + private static void setCellType(Cell cell, CellValue cv) { + int cellType = cv.getCellType(); + switch (cellType) { + case XSSFCell.CELL_TYPE_BOOLEAN: + case XSSFCell.CELL_TYPE_ERROR: + case XSSFCell.CELL_TYPE_NUMERIC: + case XSSFCell.CELL_TYPE_STRING: + cell.setCellType(cellType); + return; + case XSSFCell.CELL_TYPE_BLANK: + // never happens - blanks eventually get translated to zero + case XSSFCell.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(Cell cell, CellValue cv) { + int cellType = cv.getCellType(); + switch (cellType) { + case XSSFCell.CELL_TYPE_BOOLEAN: + cell.setCellValue(cv.getBooleanValue()); + break; + case XSSFCell.CELL_TYPE_ERROR: + cell.setCellErrorValue(cv.getErrorValue()); + break; + case XSSFCell.CELL_TYPE_NUMERIC: + cell.setCellValue(cv.getNumberValue()); + break; + case XSSFCell.CELL_TYPE_STRING: + cell.setCellValue(new XSSFRichTextString(cv.getStringValue())); + break; + case XSSFCell.CELL_TYPE_BLANK: + // never happens - blanks eventually get translated to zero + case XSSFCell.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(XSSFWorkbook wb) { + XSSFFormulaEvaluator evaluator = new XSSFFormulaEvaluator(wb); + for(int i=0; i rit = sheet.rowIterator(); rit.hasNext();) { + Row r = rit.next(); + + for (Iterator cit = r.cellIterator(); cit.hasNext();) { + XSSFCell c = (XSSFCell) cit.next(); + if (c.getCellType() == XSSFCell.CELL_TYPE_FORMULA) + evaluator.evaluateFormulaCell(c); + } + } + } + } + + /** + * Returns a CellValue wrapper around the supplied ValueEval instance. + * @param eval + */ + private CellValue evaluateFormulaCellValue(Cell cell) { + ValueEval eval = _bookEvaluator.evaluate(cell); + if (eval instanceof NumberEval) { + NumberEval ne = (NumberEval) eval; + return new CellValue(ne.getNumberValue()); + } + if (eval instanceof BoolEval) { + BoolEval be = (BoolEval) eval; + return CellValue.valueOf(be.getBooleanValue()); + } + if (eval instanceof StringEval) { + StringEval ne = (StringEval) eval; + return new CellValue(ne.getStringValue()); + } + if (eval instanceof ErrorEval) { + return CellValue.getError(((ErrorEval)eval).getErrorCode()); + } + throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")"); + } +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java index 5d6b7aec0c..0e9e573b45 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java @@ -37,7 +37,7 @@ public class XSSFName implements Name { public boolean isFunctionName() { // TODO Figure out how HSSF does this, and do the same! - return false; + return ctName.getFunction(); // maybe this works - verify } /** * Returns the underlying named range object diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index 5bc8c14e5d..a7b30ff191 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -408,15 +408,6 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { return -1; } - /** - * TODO - figure out what the hell this methods does in - * HSSF... - */ - public String resolveNameXText(int refIndex, int definedNameIndex) { - // TODO Replace with something proper - return null; - } - public short getNumCellStyles() { return (short) ((StylesTable)stylesSource).getNumCellStyles(); } @@ -450,23 +441,6 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { return -1; } - /** - * Doesn't do anything - returns the same index - * TODO - figure out if this is a ole2 specific thing, or - * if we need to do something proper here too! - */ - public int getSheetIndexFromExternSheetIndex(int externSheetNumber) { - return externSheetNumber; - } - /** - * Doesn't do anything special - returns the same as getSheetName() - * TODO - figure out if this is a ole2 specific thing, or - * if we need to do something proper here too! - */ - public String findSheetNameFromExternSheet(int externSheetIndex) { - return getSheetName(externSheetIndex); - } - public Sheet getSheet(String name) { CTSheet[] sheets = this.workbook.getSheets().getSheetArray(); for (int i = 0 ; i < sheets.length ; ++i) { @@ -495,19 +469,6 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { return this.sheets.indexOf(sheet); } - /** - * 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. - * Fairly easy on XSSF (we think...) since the - * internal and external indicies are the same - */ - public int getExternalSheetIndex(int internalSheetIndex) { - return internalSheetIndex; - } - public String getSheetName(int sheet) { return this.workbook.getSheets().getSheetArray(sheet).getName(); } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestFormulaEvaluatorOnXSSF.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestFormulaEvaluatorOnXSSF.java index 90c75c5a07..9464c05b8f 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestFormulaEvaluatorOnXSSF.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestFormulaEvaluatorOnXSSF.java @@ -28,10 +28,10 @@ import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.record.formula.eval.TestFormulasFromSpreadsheet; import org.apache.poi.hssf.record.formula.functions.TestMathX; import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellValue; import org.apache.poi.ss.usermodel.FormulaEvaluator; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; import org.openxml4j.opc.Package; /** @@ -90,7 +90,7 @@ public final class TestFormulaEvaluatorOnXSSF extends TestCase { public static final int NUMBER_OF_ROWS_PER_FUNCTION = 4; } - private Workbook workbook; + private XSSFWorkbook workbook; private Sheet sheet; // Note - multiple failures are aggregated before ending. // If one or more functions fail, a single AssertionFailedError is thrown at the end @@ -107,7 +107,7 @@ public final class TestFormulaEvaluatorOnXSSF extends TestCase { } - private static void confirmExpectedResult(String msg, Cell expected, FormulaEvaluator.CellValue actual) { + private static void confirmExpectedResult(String msg, Cell expected, CellValue actual) { if (expected == null) { throw new AssertionFailedError(msg + " - Bad setup data expected value is null"); } @@ -199,7 +199,7 @@ public final class TestFormulaEvaluatorOnXSSF extends TestCase { */ private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) { - FormulaEvaluator evaluator = new FormulaEvaluator(workbook); + FormulaEvaluator evaluator = new XSSFFormulaEvaluator(workbook); int rowIndex = startRowIndex; while (true) { @@ -256,7 +256,7 @@ public final class TestFormulaEvaluatorOnXSSF extends TestCase { continue; } - FormulaEvaluator.CellValue actualValue; + CellValue actualValue; try { actualValue = evaluator.evaluate(c); } catch (RuntimeException e) { diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java index 70a54b5ef9..160f5609bc 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java @@ -17,15 +17,14 @@ package org.apache.poi.xssf.usermodel; +import junit.framework.TestCase; + import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.FormulaEvaluator; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; - -import junit.framework.TestCase; -public class TestXSSFFormulaEvaluation extends TestCase { +public final class TestXSSFFormulaEvaluation extends TestCase { public TestXSSFFormulaEvaluation(String name) { super(name); @@ -37,7 +36,7 @@ public class TestXSSFFormulaEvaluation extends TestCase { } public void testSimpleArithmatic() { - Workbook wb = new XSSFWorkbook(); + XSSFWorkbook wb = new XSSFWorkbook(); Sheet s = wb.createSheet(); Row r = s.createRow(0); @@ -49,7 +48,7 @@ public class TestXSSFFormulaEvaluation extends TestCase { c2.setCellFormula("10/2"); assertTrue( Double.isNaN(c2.getNumericCellValue()) ); - FormulaEvaluator fe = new FormulaEvaluator(s, wb); + FormulaEvaluator fe = new XSSFFormulaEvaluator(wb); fe.evaluateFormulaCell(c1); fe.evaluateFormulaCell(c2); @@ -59,7 +58,7 @@ public class TestXSSFFormulaEvaluation extends TestCase { } public void testSumCount() { - Workbook wb = new XSSFWorkbook(); + XSSFWorkbook wb = new XSSFWorkbook(); Sheet s = wb.createSheet(); Row r = s.createRow(0); r.createCell(0).setCellValue(2.5); @@ -87,7 +86,7 @@ public class TestXSSFFormulaEvaluation extends TestCase { // Evaluate and test - FormulaEvaluator fe = new FormulaEvaluator(s, wb); + FormulaEvaluator fe = new XSSFFormulaEvaluator(wb); fe.evaluateFormulaCell(c1); fe.evaluateFormulaCell(c2); diff --git a/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java b/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java index a17414af74..2c56ea57a1 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 bd40e3d05a..f0839cfd2e 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; @@ -29,7 +29,8 @@ import org.apache.poi.hssf.usermodel.HSSFName; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; +import org.apache.poi.ss.formula.FormulaParserTestHelper; +import org.apache.poi.ss.usermodel.CellValue; /** * 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 47cfb574c1..530c2b09c2 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 1ec7c92ae9..eb69a5cd0b 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 f5b80f6dac..84f6897748 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 The test name contains the diff --git a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java index 74b436cdb7..aee26093e2 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java @@ -25,12 +25,12 @@ import junit.framework.TestCase; import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.record.formula.functions.TestMathX; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.CellValue; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook;; /** * Tests formulas and operators as loaded from a test data spreadsheet.

@@ -88,7 +88,7 @@ public final class TestFormulasFromSpreadsheet extends TestCase { public static final int NUMBER_OF_ROWS_PER_FUNCTION = 4; } - private Workbook workbook; + private HSSFWorkbook workbook; private Sheet sheet; // Note - multiple failures are aggregated before ending. // If one or more functions fail, a single AssertionFailedError is thrown at the end @@ -105,7 +105,7 @@ public final class TestFormulasFromSpreadsheet extends TestCase { } - private static void confirmExpectedResult(String msg, Cell expected, FormulaEvaluator.CellValue actual) { + private static void confirmExpectedResult(String msg, Cell expected, CellValue actual) { if (expected == null) { throw new AssertionFailedError(msg + " - Bad setup data expected value is null"); } @@ -178,7 +178,7 @@ public final class TestFormulasFromSpreadsheet extends TestCase { * Typically pass null to test all functions */ private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) { - FormulaEvaluator evaluator = new FormulaEvaluator(workbook); + HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(workbook); int rowIndex = startRowIndex; while (true) { @@ -219,7 +219,7 @@ public final class TestFormulasFromSpreadsheet extends TestCase { * @return a constant from the local Result class denoting whether there were any evaluation * cases, and whether they all succeeded. */ - private int processFunctionRow(FormulaEvaluator evaluator, String targetFunctionName, + private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName, Row formulasRow, Row expectedValuesRow) { int result = Result.NO_EVALUATIONS_FOUND; // so far @@ -232,7 +232,7 @@ public final class TestFormulasFromSpreadsheet extends TestCase { continue; } - FormulaEvaluator.CellValue actualValue = evaluator.evaluate(c); + CellValue actualValue = evaluator.evaluate(c); Cell expectedValueCell = getExpectedValueCell(expectedValuesRow, colnum); try { diff --git a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java index bac2e68862..750f71ba01 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java @@ -27,7 +27,7 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; +import org.apache.poi.ss.usermodel.CellValue; /** * Test for percent operator evaluator. diff --git a/src/testcases/org/apache/poi/hssf/record/formula/function/TestParseMissingBuiltInFuncs.java b/src/testcases/org/apache/poi/hssf/record/formula/function/TestParseMissingBuiltInFuncs.java index 7030c5a508..95ad2ef535 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/function/TestParseMissingBuiltInFuncs.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/function/TestParseMissingBuiltInFuncs.java @@ -20,7 +20,7 @@ package org.apache.poi.hssf.record.formula.function; import junit.framework.AssertionFailedError; import junit.framework.TestCase; -import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; import org.apache.poi.hssf.record.formula.FuncPtg; import org.apache.poi.hssf.record.formula.FuncVarPtg; @@ -36,7 +36,7 @@ public final class TestParseMissingBuiltInFuncs extends TestCase { private static Ptg[] parse(String formula) { HSSFWorkbook book = new HSSFWorkbook(); - return FormulaParser.parse(formula, book); + return HSSFFormulaParser.parse(formula, book); } private static void confirmFunc(String formula, int expPtgArraySize, boolean isVarArgFunc, int funcIx) { Ptg[] ptgs = parse(formula); @@ -58,7 +58,7 @@ public final class TestParseMissingBuiltInFuncs extends TestCase { // check that parsed Ptg array converts back to formula text OK HSSFWorkbook book = new HSSFWorkbook(); - String reRenderedFormula = FormulaParser.toFormulaString(book, ptgs); + String reRenderedFormula = HSSFFormulaParser.toFormulaString(book, ptgs); assertEquals(formula, reRenderedFormula); } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java index 8e5a8f32a4..98be3db43e 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java @@ -34,7 +34,7 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; +import org.apache.poi.ss.usermodel.CellValue; /** * Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK() diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestDate.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestDate.java index 0c08ef5a54..b93eb7a6c7 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestDate.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestDate.java @@ -76,7 +76,7 @@ public final class TestDate extends TestCase { private void confirm(String formulaText, double expectedResult) { cell11.setCellFormula(formulaText); - evaluator.clearCache(); + evaluator.clearAllCachedResultValues(); double actualValue = evaluator.evaluate(cell11).getNumberValue(); assertEquals(expectedResult, actualValue, 0); } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndexFunctionFromSpreadsheet.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndexFunctionFromSpreadsheet.java index f28a6462f8..2b0f776f73 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndexFunctionFromSpreadsheet.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndexFunctionFromSpreadsheet.java @@ -31,7 +31,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.util.CellReference; -import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; +import org.apache.poi.ss.usermodel.CellValue; /** * Tests INDEX() as loaded from a test data spreadsheet.

@@ -66,7 +66,7 @@ public final class TestIndexFunctionFromSpreadsheet extends TestCase { - private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) { + private static void confirmExpectedResult(String msg, HSSFCell expected, CellValue actual) { if (expected == null) { throw new AssertionFailedError(msg + " - Bad setup data expected value is null"); } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java index 50f92dfecf..5168cd9224 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java @@ -24,7 +24,7 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; +import org.apache.poi.ss.usermodel.CellValue; /** * Tests for Excel function ISBLANK() * diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java index 49d67a8d4e..5bacdbfca9 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java @@ -30,8 +30,8 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; import org.apache.poi.hssf.util.CellReference; +import org.apache.poi.ss.usermodel.CellValue; /** * Tests lookup functions (VLOOKUP, HLOOKUP, LOOKUP, MATCH) as loaded from a test data spreadsheet.

diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java index a43764e852..73ee3f8486 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java @@ -25,8 +25,8 @@ import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; import org.apache.poi.hssf.record.formula.Ptg; -import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; import org.apache.poi.hssf.util.CellReference; +import org.apache.poi.ss.usermodel.CellValue; /** * diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java index d1c511feb2..484d9af94e 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java @@ -17,10 +17,10 @@ package org.apache.poi.hssf.usermodel; -import org.apache.poi.hssf.HSSFTestDataSamples; -import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue; - import junit.framework.TestCase; + +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.ss.usermodel.CellValue; /** * * @author Josh Micich diff --git a/src/testcases/org/apache/poi/ss/formula/FormulaParserTestHelper.java b/src/testcases/org/apache/poi/ss/formula/FormulaParserTestHelper.java new file mode 100644 index 0000000000..144002279d --- /dev/null +++ b/src/testcases/org/apache/poi/ss/formula/FormulaParserTestHelper.java @@ -0,0 +1,48 @@ +/* ==================================================================== + 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 junit.framework.Assert; +import junit.framework.AssertionFailedError; + +import org.apache.poi.ss.formula.FormulaParser; +import org.apache.poi.ss.formula.FormulaParser.FormulaParseException; +/** + * Avoids making {@link FormulaParser#FormulaParseException} public + * + * @author Josh Micich + */ +public class FormulaParserTestHelper { + public static void confirmParseException(RuntimeException e, String expectedMessage) { + checkType(e); + Assert.assertEquals(expectedMessage, e.getMessage()); + } + public static void confirmParseException(RuntimeException e) { + checkType(e); + Assert.assertNotNull(e.getMessage()); + } + private static void checkType(RuntimeException e) throws AssertionFailedError { + if (!(e instanceof FormulaParseException)) { + String failMsg = "Expected FormulaParseException, but got (" + + e.getClass().getName() + "):"; + System.err.println(failMsg); + e.printStackTrace(); + throw new AssertionFailedError(failMsg); + } + } +}