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;
private static String composeFormula(FormulaRecord record)
{
- return FormulaParser.toFormulaString((HSSFWorkbook)null, record.getParsedExpression());
+ return HSSFFormulaParser.toFormulaString((HSSFWorkbook)null, record.getParsedExpression());
}
/**
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;
* 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
* 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}
/**
* 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) {
/**
* 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) {
/**
* 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) {
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;
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;
/**
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
+++ /dev/null
-/* ====================================================================
- 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> ::= <term> [<addop> <term>]*
- * <term> ::= <factor> [ <mulop> <factor> ]*
- * <factor> ::= <number> | (<expression>) | <cellRef> | <function>
- * <function> ::= <functionName> ([expression [, expression]*])
- *
- * @author Avik Sengupta <avik at apache dot org>
- * @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.<br/>
- * Primarily used by test cases when testing for specific parsing exceptions.</p>
- *
- */
- 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 <b>not</b> 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.<br/>
- * 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 <code>null</code> 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 (dotCount<name.length() && name.charAt(dotPos+dotCount) == '.') {
- dotCount++;
- if (dotCount>3) {
- // 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 <code>true</code> 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.
- * <p>
- * For IF Formulas, additional PTGs are added to the tokens
- * @param name a {@link NamePtg} or {@link NameXPtg} or <code>null</code>
- * @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);
- }
-}
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.hssf.model;\r
+\r
+import java.util.List;\r
+\r
+import org.apache.poi.hssf.record.formula.Ptg;\r
+import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;\r
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;\r
+import org.apache.poi.ss.formula.FormulaParser;\r
+import org.apache.poi.ss.formula.FormulaParsingWorkbook;\r
+import org.apache.poi.ss.formula.FormulaRenderer;\r
+import org.apache.poi.ss.formula.FormulaRenderingWorkbook;\r
+\r
+/**\r
+ * HSSF wrapper for the {@link FormulaParser}\r
+ * \r
+ * @author Josh Micich\r
+ */\r
+public final class HSSFFormulaParser {\r
+\r
+ private static FormulaParsingWorkbook createParsingWorkbook(HSSFWorkbook book) {\r
+ return HSSFEvaluationWorkbook.create(book);\r
+ }\r
+\r
+ private HSSFFormulaParser() {\r
+ // no instances of this class\r
+ }\r
+\r
+ public static Ptg[] parse(String formula, HSSFWorkbook workbook) {\r
+ return FormulaParser.parse(formula, createParsingWorkbook(workbook));\r
+ }\r
+\r
+ public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType) {\r
+ return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType);\r
+ }\r
+\r
+ public static String toFormulaString(HSSFWorkbook book, List lptgs) {\r
+ return toFormulaString(HSSFEvaluationWorkbook.create(book), lptgs);\r
+ }\r
+ /**\r
+ * Convenience method which takes in a list then passes it to the\r
+ * other toFormulaString signature.\r
+ * @param book workbook for 3D and named references\r
+ * @param lptgs list of Ptg, can be null or empty\r
+ * @return a human readable String\r
+ */\r
+ public static String toFormulaString(FormulaRenderingWorkbook book, List lptgs) {\r
+ Ptg[] ptgs = new Ptg[lptgs.size()];\r
+ lptgs.toArray(ptgs);\r
+ return FormulaRenderer.toFormulaString(book, ptgs);\r
+ }\r
+ \r
+ public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) {\r
+ return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs);\r
+ }\r
+}\r
+++ /dev/null
-/* ====================================================================
- 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:
- * <ul>
- * <li>reference</li>
- * <li>value</li>
- * <li>array</li>
- * </ul>
- * <p/>
- *
- * 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 <em>may</em> 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.<p/>
- *
- * 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).<p/>
- *
- * 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 <tt>Ptg.setClass()</tt> 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 <code>true</code> 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);
- }
- }
-}
+++ /dev/null
-/* ====================================================================
- 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 <tt>Ptg</tt>
- * token. Each <tt>ParseNode</tt> may have child <tt>ParseNode</tt>s in the case when the wrapped
- * <tt>Ptg</tt> 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 <tt>Ptg</tt> 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<toIx; i++) {
- result += _ptgs[i].getSize();
- }
- return result;
- }
-
- public int createPlaceholder() {
- return _offset++;
- }
-
- public void add(Ptg token) {
- if (token == null) {
- throw new IllegalArgumentException("token must not be null");
- }
- _ptgs[_offset] = token;
- _offset++;
- }
-
- public void setPlaceholder(int index, Ptg token) {
- if (_ptgs[index] != null) {
- throw new IllegalStateException("Invalid placeholder index (" + index + ")");
- }
- _ptgs[index] = token;
- }
-
- public Ptg[] getResult() {
- return _ptgs;
- }
- }
-}
package org.apache.poi.hssf.record;
-import org.apache.poi.hssf.model.FormulaParser;
+import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.cf.BorderFormatting;
import org.apache.poi.hssf.record.cf.FontFormatting;
import org.apache.poi.hssf.record.cf.PatternFormatting;
if(formula == null) {
return null;
}
- return FormulaParser.parse(formula, workbook);
+ return HSSFFormulaParser.parse(formula, workbook);
}
}
import java.util.ArrayList;
import java.util.List;
-import org.apache.poi.hssf.model.FormulaParser;
+import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.formula.Area3DPtg;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.Ref3DPtg;
* @return area reference
*/
public String getAreaReference(HSSFWorkbook book){
- return FormulaParser.toFormulaString(book, field_13_name_definition);
+ return HSSFFormulaParser.toFormulaString(book, field_13_name_definition);
}
/** sets the reference , the area only (range)
public abstract int getNumberOfOperands();
public byte getDefaultOperandClass() {
- return Ptg.CLASS_VALUE;
- }
+ return Ptg.CLASS_VALUE;
+ }
public final int getType() {
- // TODO remove "int getType();" from Eval hierarchy
- throw new RuntimeException("remove this method");
+ // TODO remove "int getType();" from Eval hierarchy
+ throw new RuntimeException("remove this method");
}
}
}
public void writeBytes(byte [] array, int offset) {
- LittleEndian.putByte(array, 0 + offset, sid + getPtgClass());
- LittleEndian.putUShort(array, 1 + offset, getExternSheetIndex());
- writeCoordinates(array, offset + 3);
+ LittleEndian.putByte(array, 0 + offset, sid + getPtgClass());
+ LittleEndian.putUShort(array, 1 + offset, getExternSheetIndex());
+ writeCoordinates(array, offset + 3);
}
public int getSize() {
* formulas. The sheet name will get properly delimited if required.
*/
public String toFormulaString(FormulaRenderingWorkbook book) {
- return ExternSheetNameResolver.prependSheetName(book, field_1_index_extern_sheet, formatReferenceAsString());
+ return ExternSheetNameResolver.prependSheetName(book, field_1_index_extern_sheet, formatReferenceAsString());
+ }
+ public String toFormulaString() {
+ throw new RuntimeException("3D references need a workbook to determine formula text");
}
- public String toFormulaString() {
- throw new RuntimeException("3D references need a workbook to determine formula text");
- }
}
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
-import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.formula.EvaluationWorkbook;
public final class AnalysisToolPak {
private static final FreeRefFunction NotImplemented = new FreeRefFunction() {
- public ValueEval evaluate(Eval[] args, Workbook workbook, int srcCellSheet,
+ public ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook, int srcCellSheet,
int srcCellRow, int srcCellCol) {
return ErrorEval.FUNCTION_NOT_IMPLEMENTED;
}
package org.apache.poi.hssf.record.formula.atp;
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
-import org.apache.poi.ss.usermodel.Sheet;
-import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.formula.EvaluationWorkbook;
/**
* Implementation of Excel 'Analysis ToolPak' function ISEVEN() ISODD()<br/>
*
_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;
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;
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()<br/>
*
// 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;
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
*/
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;
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 + ")");
}
private FreeRefFunction findInternalUserDefinedFunction(NameEval functionNameEval) throws EvaluationException {
+
String functionName = functionNameEval.getFunctionName();
if(false) {
System.out.println("received call to internal user defined function (" + functionName + ")");
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();
}
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;
/**
* 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);
}
-/*
-* 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<p/>
*/
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;
}
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;
/**
*
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.
}
if (value != null) {
throw new IllegalStateException("Both formula and value cannot be present");
}
- return FormulaParser.parse(formula, workbook);
+ return HSSFFormulaParser.parse(formula, workbook);
}
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;
}
/**
- * set a string value for the cell. Please note that if you are using
- * full 16 bit unicode you should call <code>setEncoding()</code> 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);
}
/**
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());
}
/**
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;
if(parsedExpression ==null) {
return null;
}
- return FormulaParser.toFormulaString(workbook, parsedExpression);
+ return HSSFFormulaParser.toFormulaString(workbook, parsedExpression);
}
}
package org.apache.poi.hssf.usermodel;\r
\r
+import org.apache.poi.hssf.model.HSSFFormulaParser;\r
import org.apache.poi.hssf.model.Workbook;\r
+import org.apache.poi.hssf.record.NameRecord;\r
import org.apache.poi.hssf.record.formula.NamePtg;\r
import org.apache.poi.hssf.record.formula.NameXPtg;\r
+import org.apache.poi.hssf.record.formula.Ptg;\r
+import org.apache.poi.ss.formula.EvaluationName;\r
+import org.apache.poi.ss.formula.EvaluationWorkbook;\r
+import org.apache.poi.ss.formula.FormulaParsingWorkbook;\r
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;\r
+import org.apache.poi.ss.usermodel.Cell;\r
+import org.apache.poi.ss.usermodel.Sheet;\r
\r
/**\r
* Internal POI use only\r
* \r
* @author Josh Micich\r
*/\r
-public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook {\r
+public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook {\r
\r
+ private final HSSFWorkbook _uBook;\r
private final Workbook _iBook;\r
\r
public static HSSFEvaluationWorkbook create(HSSFWorkbook book) {\r
}\r
\r
private HSSFEvaluationWorkbook(HSSFWorkbook book) {\r
+ _uBook = book;\r
_iBook = book.getWorkbook();\r
}\r
\r
+ public int getExternalSheetIndex(String sheetName) {\r
+ int sheetIndex = _uBook.getSheetIndex(sheetName);\r
+ return _iBook.checkExternSheet(sheetIndex);\r
+ }\r
+\r
+ public EvaluationName getName(int index) {\r
+ return new Name(_iBook.getNameRecord(index), index);\r
+ }\r
+\r
+ public EvaluationName getName(String name) {\r
+ for(int i=0; i < _iBook.getNumNames(); i++) {\r
+ NameRecord nr = _iBook.getNameRecord(i);\r
+ if (name.equalsIgnoreCase(nr.getNameText())) {\r
+ return new Name(nr, i);\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ public int getSheetIndex(Sheet sheet) {\r
+ return _uBook.getSheetIndex(sheet);\r
+ }\r
+\r
+ public String getSheetName(int sheetIndex) {\r
+ return _uBook.getSheetName(sheetIndex);\r
+ }\r
+\r
+ public int getNameIndex(String name) {\r
+ return _uBook.getNameIndex(name);\r
+ }\r
+\r
+ public NameXPtg getNameXPtg(String name) {\r
+ return _iBook.getNameXPtg(name);\r
+ }\r
+\r
+ public Sheet getSheet(int sheetIndex) {\r
+ return _uBook.getSheetAt(sheetIndex);\r
+ }\r
+\r
+ public Sheet getSheetByExternSheetIndex(int externSheetIndex) {\r
+ int sheetIndex = _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);\r
+ return _uBook.getSheetAt(sheetIndex);\r
+ }\r
+\r
+ public HSSFWorkbook getWorkbook() {\r
+ return _uBook;\r
+ }\r
+\r
public String resolveNameXText(NameXPtg n) {\r
return _iBook.resolveNameXText(n.getSheetRefIndex(), n.getNameIndex());\r
}\r
public String getNameText(NamePtg namePtg) {\r
return _iBook.getNameRecord(namePtg.getIndex()).getNameText();\r
}\r
+ public EvaluationName getName(NamePtg namePtg) {\r
+ int ix = namePtg.getIndex();\r
+ return new Name(_iBook.getNameRecord(ix), ix);\r
+ }\r
+ public Ptg[] getFormulaTokens(Cell cell) {\r
+ return HSSFFormulaParser.parse(cell.getCellFormula(), _uBook);\r
+ }\r
+\r
+ private static final class Name implements EvaluationName {\r
+\r
+ private final NameRecord _nameRecord;\r
+ private final int _index;\r
+\r
+ public Name(NameRecord nameRecord, int index) {\r
+ _nameRecord = nameRecord;\r
+ _index = index;\r
+ }\r
+\r
+ public Ptg[] getNameDefinition() {\r
+ return _nameRecord.getNameDefinition();\r
+ }\r
+\r
+ public String getNameText() {\r
+ return _nameRecord.getNameText();\r
+ }\r
+\r
+ public boolean hasFormula() {\r
+ return _nameRecord.hasFormula();\r
+ }\r
+\r
+ public boolean isFunctionName() {\r
+ return _nameRecord.isFunctionName();\r
+ }\r
+\r
+ public boolean isRange() {\r
+ return _nameRecord.hasFormula(); // TODO - is this right?\r
+ }\r
+ public NamePtg createPtg() {\r
+ return new NamePtg(_index);\r
+ }\r
+ }\r
}\r
-/*
-* 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("<ptg-group>");
- for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
- System.out.println("<ptg>");
- System.out.println(ptgs[i]);
- if (ptgs[i] instanceof OperationPtg) {
- System.out.println("numoperands: " + ((OperationPtg) ptgs[i]).getNumberOfOperands());
- }
- System.out.println("</ptg>");
- }
- System.out.println("</ptg-group>");
- }
-
- /**
- * 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);
-// }
-// }
-}
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.hssf.usermodel;\r
+\r
+import java.util.Iterator;\r
+\r
+import org.apache.poi.hssf.record.formula.eval.BoolEval;\r
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;\r
+import org.apache.poi.hssf.record.formula.eval.NumberEval;\r
+import org.apache.poi.hssf.record.formula.eval.StringEval;\r
+import org.apache.poi.hssf.record.formula.eval.ValueEval;\r
+import org.apache.poi.ss.formula.WorkbookEvaluator;\r
+import org.apache.poi.ss.usermodel.Cell;\r
+import org.apache.poi.ss.usermodel.CellValue;\r
+import org.apache.poi.ss.usermodel.Sheet;\r
+\r
+/**\r
+ * Evaluates formula cells.<p/>\r
+ *\r
+ * For performance reasons, this class keeps a cache of all previously calculated intermediate\r
+ * cell values. Be sure to call {@link #clearCache()} if any workbook cells are changed between\r
+ * calls to evaluate~ methods on this class.\r
+ *\r
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >\r
+ * @author Josh Micich\r
+ */\r
+public class HSSFFormulaEvaluator /* almost implements FormulaEvaluator */ {\r
+\r
+ private WorkbookEvaluator _bookEvaluator;\r
+\r
+ /**\r
+ * @deprecated (Sep 2008) HSSFSheet parameter is ignored\r
+ */\r
+ public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) {\r
+ this(workbook);\r
+ if (false) {\r
+ sheet.toString(); // suppress unused parameter compiler warning\r
+ }\r
+ }\r
+ public HSSFFormulaEvaluator(HSSFWorkbook workbook) {\r
+ _bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook));\r
+ }\r
+\r
+ /**\r
+ * TODO for debug/test use\r
+ */\r
+ /* package */ int getEvaluationCount() {\r
+ return _bookEvaluator.getEvaluationCount();\r
+ }\r
+\r
+ /**\r
+ * Does nothing\r
+ * @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell\r
+ */\r
+ public void setCurrentRow(HSSFRow row) {\r
+ // do nothing\r
+ if (false) {\r
+ row.getClass(); // suppress unused parameter compiler warning\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Should be called whenever there are major changes (e.g. moving sheets) to input cells\r
+ * in the evaluated workbook.\r
+ * Failure to call this method after changing cell values will cause incorrect behaviour\r
+ * of the evaluate~ methods of this class\r
+ */\r
+ public void clearAllCachedResultValues() {\r
+ _bookEvaluator.clearAllCachedResultValues();\r
+ }\r
+ /**\r
+ * Should be called whenever there are changes to individual input cells in the evaluated workbook.\r
+ * Failure to call this method after changing cell values will cause incorrect behaviour\r
+ * of the evaluate~ methods of this class\r
+ */\r
+ public void clearCachedResultValue(Sheet sheet, int rowIndex, int columnIndex) {\r
+ _bookEvaluator.clearCachedResultValue(sheet, rowIndex, columnIndex);\r
+ }\r
+\r
+ /**\r
+ * If cell contains a formula, the formula is evaluated and returned,\r
+ * else the CellValue simply copies the appropriate cell value from\r
+ * the cell and also its cell type. This method should be preferred over\r
+ * evaluateInCell() when the call should not modify the contents of the\r
+ * original cell.\r
+ * @param cell\r
+ */\r
+ public CellValue evaluate(Cell cell) {\r
+ if (cell == null) {\r
+ return null;\r
+ }\r
+\r
+ switch (cell.getCellType()) {\r
+ case HSSFCell.CELL_TYPE_BOOLEAN:\r
+ return CellValue.valueOf(cell.getBooleanCellValue());\r
+ case HSSFCell.CELL_TYPE_ERROR:\r
+ return CellValue.getError(cell.getErrorCellValue());\r
+ case HSSFCell.CELL_TYPE_FORMULA:\r
+ return evaluateFormulaCellValue(cell);\r
+ case HSSFCell.CELL_TYPE_NUMERIC:\r
+ return new CellValue(cell.getNumericCellValue());\r
+ case HSSFCell.CELL_TYPE_STRING:\r
+ return new CellValue(cell.getRichStringCellValue().getString());\r
+ }\r
+ throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")");\r
+ }\r
+\r
+\r
+ /**\r
+ * If cell contains formula, it evaluates the formula,\r
+ * and saves the result of the formula. The cell\r
+ * remains as a formula cell.\r
+ * Else if cell does not contain formula, this method leaves\r
+ * the cell unchanged.\r
+ * Note that the type of the formula result is returned,\r
+ * so you know what kind of value is also stored with\r
+ * the formula.\r
+ * <pre>\r
+ * int evaluatedCellType = evaluator.evaluateFormulaCell(cell);\r
+ * </pre>\r
+ * Be aware that your cell will hold both the formula,\r
+ * and the result. If you want the cell replaced with\r
+ * the result of the formula, use {@link #evaluateInCell(HSSFCell)}\r
+ * @param cell The cell to evaluate\r
+ * @return The type of the formula result (the cell's type remains as HSSFCell.CELL_TYPE_FORMULA however)\r
+ */\r
+ public int evaluateFormulaCell(Cell cell) {\r
+ if (cell == null || cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {\r
+ return -1;\r
+ }\r
+ CellValue cv = evaluateFormulaCellValue(cell);\r
+ // cell remains a formula cell, but the cached value is changed\r
+ setCellValue(cell, cv);\r
+ return cv.getCellType();\r
+ }\r
+\r
+ /**\r
+ * If cell contains formula, it evaluates the formula, and\r
+ * puts the formula result back into the cell, in place\r
+ * of the old formula.\r
+ * Else if cell does not contain formula, this method leaves\r
+ * the cell unchanged.\r
+ * Note that the same instance of HSSFCell is returned to\r
+ * allow chained calls like:\r
+ * <pre>\r
+ * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();\r
+ * </pre>\r
+ * Be aware that your cell value will be changed to hold the\r
+ * result of the formula. If you simply want the formula\r
+ * value computed for you, use {@link #evaluateFormulaCell(HSSFCell)}\r
+ * @param cell\r
+ */\r
+ public HSSFCell evaluateInCell(Cell cell) {\r
+ if (cell == null) {\r
+ return null;\r
+ }\r
+ HSSFCell result = (HSSFCell) cell;\r
+ if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {\r
+ CellValue cv = evaluateFormulaCellValue(cell);\r
+ setCellType(cell, cv); // cell will no longer be a formula cell\r
+ setCellValue(cell, cv);\r
+ }\r
+ return result;\r
+ }\r
+ private static void setCellType(Cell cell, CellValue cv) {\r
+ int cellType = cv.getCellType();\r
+ switch (cellType) {\r
+ case HSSFCell.CELL_TYPE_BOOLEAN:\r
+ case HSSFCell.CELL_TYPE_ERROR:\r
+ case HSSFCell.CELL_TYPE_NUMERIC:\r
+ case HSSFCell.CELL_TYPE_STRING:\r
+ cell.setCellType(cellType);\r
+ return;\r
+ case HSSFCell.CELL_TYPE_BLANK:\r
+ // never happens - blanks eventually get translated to zero\r
+ case HSSFCell.CELL_TYPE_FORMULA:\r
+ // this will never happen, we have already evaluated the formula\r
+ }\r
+ throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");\r
+ }\r
+\r
+ private static void setCellValue(Cell cell, CellValue cv) {\r
+ int cellType = cv.getCellType();\r
+ switch (cellType) {\r
+ case HSSFCell.CELL_TYPE_BOOLEAN:\r
+ cell.setCellValue(cv.getBooleanValue());\r
+ break;\r
+ case HSSFCell.CELL_TYPE_ERROR:\r
+ cell.setCellErrorValue(cv.getErrorValue());\r
+ break;\r
+ case HSSFCell.CELL_TYPE_NUMERIC:\r
+ cell.setCellValue(cv.getNumberValue());\r
+ break;\r
+ case HSSFCell.CELL_TYPE_STRING:\r
+ cell.setCellValue(new HSSFRichTextString(cv.getStringValue()));\r
+ break;\r
+ case HSSFCell.CELL_TYPE_BLANK:\r
+ // never happens - blanks eventually get translated to zero\r
+ case HSSFCell.CELL_TYPE_FORMULA:\r
+ // this will never happen, we have already evaluated the formula\r
+ default:\r
+ throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Loops over all cells in all sheets of the supplied\r
+ * workbook.\r
+ * For cells that contain formulas, their formulas are\r
+ * evaluated, and the results are saved. These cells\r
+ * remain as formula cells.\r
+ * For cells that do not contain formulas, no changes\r
+ * are made.\r
+ * This is a helpful wrapper around looping over all\r
+ * cells, and calling evaluateFormulaCell on each one.\r
+ */\r
+ public static void evaluateAllFormulaCells(HSSFWorkbook wb) {\r
+ HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb);\r
+ for(int i=0; i<wb.getNumberOfSheets(); i++) {\r
+ HSSFSheet sheet = wb.getSheetAt(i);\r
+\r
+ for (Iterator rit = sheet.rowIterator(); rit.hasNext();) {\r
+ HSSFRow r = (HSSFRow)rit.next();\r
+\r
+ for (Iterator cit = r.cellIterator(); cit.hasNext();) {\r
+ HSSFCell c = (HSSFCell)cit.next();\r
+ if (c.getCellType() == HSSFCell.CELL_TYPE_FORMULA)\r
+ evaluator.evaluateFormulaCell(c);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns a CellValue wrapper around the supplied ValueEval instance.\r
+ * @param eval\r
+ */\r
+ private CellValue evaluateFormulaCellValue(Cell cell) {\r
+ ValueEval eval = _bookEvaluator.evaluate(cell);\r
+ if (eval instanceof NumberEval) {\r
+ NumberEval ne = (NumberEval) eval;\r
+ return new CellValue(ne.getNumberValue());\r
+ }\r
+ if (eval instanceof BoolEval) {\r
+ BoolEval be = (BoolEval) eval;\r
+ return CellValue.valueOf(be.getBooleanValue());\r
+ }\r
+ if (eval instanceof StringEval) {\r
+ StringEval ne = (StringEval) eval;\r
+ return new CellValue(ne.getStringValue());\r
+ }\r
+ if (eval instanceof ErrorEval) {\r
+ return CellValue.getError(((ErrorEval)eval).getErrorCode());\r
+ }\r
+ throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")");\r
+ }\r
+}\r
*
* @author Libin Roman (Vista Portal LDT. Developer)
*/
-public class HSSFName implements Name {
+public final class HSSFName implements Name {
private HSSFWorkbook _book;
private NameRecord _definedNameRec;
*/
private void setSheetName(String sheetName){
int sheetNumber = _book.getSheetIndex(sheetName);
- short externSheetNumber = (short)
- _book.getExternalSheetIndex(sheetNumber);
+ short externSheetNumber = _book.getWorkbook().checkExternSheet(sheetNumber);
_definedNameRec.setExternSheetNumber(externSheetNumber);
}
return -1;
}
- /* package */ int findSheetIndex(Sheet sheet) {
- for(int i=0; i<_sheets.size(); i++) {
- HSSFSheet hSheet = (HSSFSheet) _sheets.get(i);
- if(hSheet.getSheet() == sheet) {
- return i;
- }
- }
- throw new IllegalArgumentException("Specified sheet not found in this workbook");
- }
-
/**
+<<<<<<< .working
* Returns the external sheet index of the sheet
* with the given internal index, creating one
* if needed.
/**
+=======
+>>>>>>> .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.
*
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];
+++ /dev/null
-/* ====================================================================
- 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);
- }
-}
+++ /dev/null
-/* ====================================================================\r
- Licensed to the Apache Software Foundation (ASF) under one or more\r
- contributor license agreements. See the NOTICE file distributed with\r
- this work for additional information regarding copyright ownership.\r
- The ASF licenses this file to You under the Apache License, Version 2.0\r
- (the "License"); you may not use this file except in compliance with\r
- the License. You may obtain a copy of the License at\r
-\r
- http://www.apache.org/licenses/LICENSE-2.0\r
-\r
- Unless required by applicable law or agreed to in writing, software\r
- distributed under the License is distributed on an "AS IS" BASIS,\r
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- See the License for the specific language governing permissions and\r
- limitations under the License.\r
-==================================================================== */\r
-\r
-package org.apache.poi.hssf.usermodel;\r
-import org.apache.poi.hssf.record.formula.Ref3DPtg;\r
-import org.apache.poi.hssf.record.formula.RefPtg;\r
-\r
-/**\r
-*\r
-* @author Josh Micich \r
-*/\r
-final class LazyRefEval extends org.apache.poi.ss.usermodel.LazyRefEval {\r
- public LazyRefEval(RefPtg ptg, HSSFSheet sheet, HSSFFormulaEvaluator evaluator) {\r
- super(ptg, sheet, evaluator);\r
- }\r
- public LazyRefEval(Ref3DPtg ptg, HSSFSheet sheet, HSSFFormulaEvaluator evaluator) {\r
- super(ptg, sheet, evaluator);\r
- }\r
-}\r
+++ /dev/null
-/* ====================================================================
- 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 <tt>OperationEval</tt> instances to help evaluate <tt>OperationPtg</tt>
- * 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;
- }
-}
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.ss.formula;\r
+\r
+/**\r
+ * Stores the parameters that identify the evaluation of one cell.<br/>\r
+ */\r
+final class CellEvaluationFrame {\r
+\r
+ private final int _sheetIndex;\r
+ private final int _srcRowNum;\r
+ private final int _srcColNum;\r
+ private final int _hashCode;\r
+\r
+ public CellEvaluationFrame(int sheetIndex, int srcRowNum, int srcColNum) {\r
+ if (sheetIndex < 0) {\r
+ throw new IllegalArgumentException("sheetIndex must not be negative");\r
+ }\r
+ _sheetIndex = sheetIndex;\r
+ _srcRowNum = srcRowNum;\r
+ _srcColNum = srcColNum;\r
+ _hashCode = sheetIndex + 17 * (srcRowNum + 17 * srcColNum);\r
+ }\r
+\r
+ public boolean equals(Object obj) {\r
+ CellEvaluationFrame other = (CellEvaluationFrame) obj;\r
+ if (_sheetIndex != other._sheetIndex) {\r
+ return false;\r
+ }\r
+ if (_srcRowNum != other._srcRowNum) {\r
+ return false;\r
+ }\r
+ if (_srcColNum != other._srcColNum) {\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+ public int hashCode() {\r
+ return _hashCode;\r
+ }\r
+\r
+ /**\r
+ * @return human readable string for debug purposes\r
+ */\r
+ public String formatAsString() {\r
+ return "R=" + _srcRowNum + " C=" + _srcColNum + " ShIx=" + _sheetIndex;\r
+ }\r
+\r
+ public String toString() {\r
+ StringBuffer sb = new StringBuffer(64);\r
+ sb.append(getClass().getName()).append(" [");\r
+ sb.append(formatAsString());\r
+ sb.append("]");\r
+ return sb.toString();\r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+package org.apache.poi.ss.formula;\r
+\r
+import org.apache.poi.hssf.record.formula.eval.BlankEval;\r
+import org.apache.poi.hssf.record.formula.eval.BoolEval;\r
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;\r
+import org.apache.poi.hssf.record.formula.eval.NumberEval;\r
+import org.apache.poi.hssf.record.formula.eval.StringEval;\r
+import org.apache.poi.hssf.record.formula.eval.ValueEval;\r
+import org.apache.poi.ss.usermodel.Cell;\r
+import org.apache.poi.ss.usermodel.Sheet;\r
+\r
+final class CellEvaluator {\r
+\r
+ private final WorkbookEvaluator _bookEvaluator;\r
+ private final EvaluationTracker _tracker;\r
+\r
+ public CellEvaluator(WorkbookEvaluator bookEvaluator, EvaluationTracker tracker) {\r
+ _bookEvaluator = bookEvaluator;\r
+ _tracker = tracker;\r
+ }\r
+\r
+ /**\r
+ * Given a cell, find its type and from that create an appropriate ValueEval\r
+ * impl instance and return that. Since the cell could be an external\r
+ * reference, we need the sheet that this belongs to.\r
+ * Non existent cells are treated as empty.\r
+ */\r
+ public ValueEval getEvalForCell(Cell cell) {\r
+\r
+ if (cell == null) {\r
+ return BlankEval.INSTANCE;\r
+ }\r
+ switch (cell.getCellType()) {\r
+ case Cell.CELL_TYPE_NUMERIC:\r
+ return new NumberEval(cell.getNumericCellValue());\r
+ case Cell.CELL_TYPE_STRING:\r
+ return new StringEval(cell.getRichStringCellValue().getString());\r
+ case Cell.CELL_TYPE_FORMULA:\r
+ return _bookEvaluator.internalEvaluate(cell, _tracker);\r
+ case Cell.CELL_TYPE_BOOLEAN:\r
+ return BoolEval.valueOf(cell.getBooleanCellValue());\r
+ case Cell.CELL_TYPE_BLANK:\r
+ return BlankEval.INSTANCE;\r
+ case Cell.CELL_TYPE_ERROR:\r
+ return ErrorEval.valueOf(cell.getErrorCellValue());\r
+ }\r
+ throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");\r
+ }\r
+\r
+ public String getSheetName(Sheet sheet) {\r
+ return _bookEvaluator.getSheetName(sheet);\r
+ }\r
+\r
+}\r
--- /dev/null
+/* ====================================================================
+ 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;
+ }
+}
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.ss.formula;\r
+\r
+import org.apache.poi.hssf.record.formula.NamePtg;\r
+import org.apache.poi.hssf.record.formula.Ptg;\r
+/**\r
+ * Abstracts a name record for formula evaluation.<br/>\r
+ * \r
+ * For POI internal use only\r
+ * \r
+ * @author Josh Micich\r
+ */\r
+public interface EvaluationName {\r
+\r
+ String getNameText();\r
+\r
+ boolean isFunctionName();\r
+\r
+ boolean hasFormula();\r
+\r
+ Ptg[] getNameDefinition();\r
+\r
+ boolean isRange();\r
+ NamePtg createPtg();\r
+}\r
--- /dev/null
+/* ====================================================================
+ 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.<br/>
+ *
+ * In the case of a <code>true</code> return code, the caller should
+ * continue evaluation of the specified cell, and also be sure to call
+ * <tt>endEvaluate()</tt> when complete.<br/>
+ *
+ * In the case of a <code>false</code> return code, the caller should
+ * return an evaluation result of
+ * <tt>ErrorEval.CIRCULAR_REF_ERROR<tt>, and not call <tt>endEvaluate()</tt>.
+ * <br/>
+ * @return <code>true</code> if the specified cell has not been visited yet in the current
+ * evaluation. <code>false</code> 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. <p/>
+ *
+ * Every successful call to <tt>startEvaluate</tt> must be followed by a
+ * call to <tt>endEvaluate</tt> (recommended in a finally block) to enable
+ * proper tracking of which cells are being evaluated at any point in time.<p/>
+ *
+ * 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);
+ }
+}
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.ss.formula;\r
+\r
+import org.apache.poi.hssf.record.formula.NamePtg;\r
+import org.apache.poi.hssf.record.formula.NameXPtg;\r
+import org.apache.poi.hssf.record.formula.Ptg;\r
+import org.apache.poi.ss.usermodel.Cell;\r
+import org.apache.poi.ss.usermodel.Sheet;\r
+/**\r
+ * Abstracts a workbook for the purpose of formula evaluation.<br/>\r
+ * \r
+ * For POI internal use only\r
+ * \r
+ * @author Josh Micich\r
+ */\r
+public interface EvaluationWorkbook {\r
+ String getSheetName(int sheetIndex);\r
+ int getSheetIndex(Sheet sheet);\r
+\r
+ Sheet getSheet(int sheetIndex);\r
+\r
+ Sheet getSheetByExternSheetIndex(int externSheetIndex);\r
+ EvaluationName getName(NamePtg namePtg);\r
+ String resolveNameXText(NameXPtg ptg);\r
+ Ptg[] getFormulaTokens(Cell cell);\r
+}\r
--- /dev/null
+/* ====================================================================
+ 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> ::= <term> [<addop> <term>]*
+ * <term> ::= <factor> [ <mulop> <factor> ]*
+ * <factor> ::= <number> | (<expression>) | <cellRef> | <function>
+ * <function> ::= <functionName> ([expression [, expression]*])
+ *
+ * @author Avik Sengupta <avik at apache dot org>
+ * @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.<br/>
+ * Primarily used by test cases when testing for specific parsing exceptions.</p>
+ *
+ */
+ 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 <b>not</b> 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.<br/>
+ * 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 <code>null</code> 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 (dotCount<name.length() && name.charAt(dotPos+dotCount) == '.') {
+ dotCount++;
+ if (dotCount>3) {
+ // 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 <code>true</code> 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.
+ * <p>
+ * For IF Formulas, additional PTGs are added to the tokens
+ * @param name a {@link NamePtg} or {@link NameXPtg} or <code>null</code>
+ * @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);
+ }
+}
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.ss.formula;\r
+\r
+import org.apache.poi.hssf.record.formula.NameXPtg;\r
+\r
+/**\r
+ * Abstracts a workbook for the purpose of formula parsing.<br/>\r
+ * \r
+ * For POI internal use only\r
+ * \r
+ * @author Josh Micich\r
+ */\r
+public interface FormulaParsingWorkbook {\r
+ /**\r
+ * named range name matching is case insensitive\r
+ */\r
+ EvaluationName getName(String name);\r
+\r
+ int getExternalSheetIndex(String sheetName);\r
+ NameXPtg getNameXPtg(String name);\r
+}\r
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.ss.formula;\r
+\r
+/**\r
+ * Enumeration of various formula types.<br/>\r
+ * \r
+ * For POI internal use only\r
+ * \r
+ * @author Josh Micich\r
+ */\r
+public final class FormulaType {\r
+ private FormulaType() {\r
+ // no instances of this class\r
+ }\r
+ public static final int CELL = 0;\r
+ public static final int SHARED = 1;\r
+ public static final int ARRAY =2;\r
+ public static final int CONDFORMAT = 3;\r
+ public static final int NAMEDRANGE = 4;\r
+ // this constant is currently very specific. The exact differences from general data\r
+ // validation formulas or conditional format formulas is not known yet\r
+ public static final int DATAVALIDATION_LIST = 5;\r
+\r
+}\r
--- /dev/null
+/* ====================================================================
+ 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();
+ }
+}
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.ss.formula;\r
+\r
+import org.apache.poi.hssf.record.formula.AreaI;\r
+import org.apache.poi.hssf.record.formula.Ref3DPtg;\r
+import org.apache.poi.hssf.record.formula.RefPtg;\r
+import org.apache.poi.hssf.record.formula.AreaI.OffsetArea;\r
+import org.apache.poi.hssf.record.formula.eval.AreaEval;\r
+import org.apache.poi.hssf.record.formula.eval.BlankEval;\r
+import org.apache.poi.hssf.record.formula.eval.RefEvalBase;\r
+import org.apache.poi.hssf.record.formula.eval.ValueEval;\r
+import org.apache.poi.ss.usermodel.Cell;\r
+import org.apache.poi.ss.usermodel.Row;\r
+import org.apache.poi.ss.usermodel.Sheet;\r
+import org.apache.poi.hssf.util.CellReference;\r
+\r
+/**\r
+*\r
+* @author Josh Micich \r
+*/\r
+final class LazyRefEval extends RefEvalBase {\r
+\r
+ private final Sheet _sheet;\r
+ private final CellEvaluator _evaluator;\r
+\r
+\r
+ public LazyRefEval(RefPtg ptg, Sheet sheet, CellEvaluator evaluator) {\r
+ super(ptg.getRow(), ptg.getColumn());\r
+ _sheet = sheet;\r
+ _evaluator = evaluator;\r
+ }\r
+ public LazyRefEval(Ref3DPtg ptg, Sheet sheet, CellEvaluator evaluator) {\r
+ super(ptg.getRow(), ptg.getColumn());\r
+ _sheet = sheet;\r
+ _evaluator = evaluator;\r
+ }\r
+\r
+ public ValueEval getInnerValueEval() {\r
+ int rowIx = getRow();\r
+ int colIx = getColumn();\r
+ \r
+ Row row = _sheet.getRow(rowIx);\r
+ if (row == null) {\r
+ return BlankEval.INSTANCE;\r
+ }\r
+ Cell cell = row.getCell(colIx);\r
+ if (cell == null) {\r
+ return BlankEval.INSTANCE;\r
+ }\r
+ return _evaluator.getEvalForCell(cell);\r
+ }\r
+ \r
+ public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) {\r
+ \r
+ AreaI area = new OffsetArea(getRow(), getColumn(),\r
+ relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);\r
+\r
+ return new LazyAreaEval(area, _sheet, _evaluator);\r
+ }\r
+ \r
+ public String toString() {\r
+ CellReference cr = new CellReference(getRow(), getColumn());\r
+ StringBuffer sb = new StringBuffer();\r
+ sb.append(getClass().getName()).append("[");\r
+ String sheetName = _evaluator.getSheetName(_sheet);\r
+ sb.append(sheetName);\r
+ sb.append('!');\r
+ sb.append(cr.formatAsString());\r
+ sb.append("]");\r
+ return sb.toString();\r
+ }\r
+}\r
--- /dev/null
+/* ====================================================================
+ 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:
+ * <ul>
+ * <li>reference</li>
+ * <li>value</li>
+ * <li>array</li>
+ * </ul>
+ * <p/>
+ *
+ * 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 <em>may</em> 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.<p/>
+ *
+ * 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).<p/>
+ *
+ * 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 <tt>Ptg.setClass()</tt> 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 <code>true</code> 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);
+ }
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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 <tt>OperationEval</tt> instances to help evaluate <tt>OperationPtg</tt>
+ * 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;
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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 <tt>Ptg</tt>
+ * token. Each <tt>ParseNode</tt> may have child <tt>ParseNode</tt>s in the case when the wrapped
+ * <tt>Ptg</tt> 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 <tt>Ptg</tt> 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<toIx; i++) {
+ result += _ptgs[i].getSize();
+ }
+ return result;
+ }
+
+ public int createPlaceholder() {
+ return _offset++;
+ }
+
+ public void add(Ptg token) {
+ if (token == null) {
+ throw new IllegalArgumentException("token must not be null");
+ }
+ _ptgs[_offset] = token;
+ _offset++;
+ }
+
+ public void setPlaceholder(int index, Ptg token) {
+ if (_ptgs[index] != null) {
+ throw new IllegalStateException("Invalid placeholder index (" + index + ")");
+ }
+ _ptgs[index] = token;
+ }
+
+ public Ptg[] getResult() {
+ return _ptgs;
+ }
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula;
+
+import java.util.Stack;
+
+import org.apache.poi.hssf.record.formula.Area3DPtg;
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.BoolPtg;
+import org.apache.poi.hssf.record.formula.ControlPtg;
+import org.apache.poi.hssf.record.formula.ErrPtg;
+import org.apache.poi.hssf.record.formula.IntPtg;
+import org.apache.poi.hssf.record.formula.MemErrPtg;
+import org.apache.poi.hssf.record.formula.MissingArgPtg;
+import org.apache.poi.hssf.record.formula.NamePtg;
+import org.apache.poi.hssf.record.formula.NameXPtg;
+import org.apache.poi.hssf.record.formula.NumberPtg;
+import org.apache.poi.hssf.record.formula.OperationPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.Ref3DPtg;
+import org.apache.poi.hssf.record.formula.RefPtg;
+import org.apache.poi.hssf.record.formula.StringPtg;
+import org.apache.poi.hssf.record.formula.UnionPtg;
+import org.apache.poi.hssf.record.formula.UnknownPtg;
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.BoolEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.FunctionEval;
+import org.apache.poi.hssf.record.formula.eval.NameEval;
+import org.apache.poi.hssf.record.formula.eval.NameXEval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.OperationEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.util.CellReference;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Sheet;
+
+/**
+ * Evaluates formula cells.<p/>
+ *
+ * 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.<br/>
+ *
+ * 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 <code>null</code>, 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 <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>,
+ * <tt>BlankEval</tt> or <tt>ErrorEval</tt>. Never <code>null</code>.
+ */
+ 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 <br/>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);
+ }
+}
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.ss.usermodel;\r
+\r
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;\r
+import org.apache.poi.ss.usermodel.Cell;\r
+\r
+/**\r
+ * Mimics the 'data view' of a cell. This allows formula evaluator\r
+ * to return a CellValue instead of precasting the value to String\r
+ * or Number or boolean type.\r
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >\r
+ */\r
+public final class CellValue {\r
+ public static final CellValue TRUE = new CellValue(Cell.CELL_TYPE_BOOLEAN, 0.0, true, null, 0);\r
+ public static final CellValue FALSE= new CellValue(Cell.CELL_TYPE_BOOLEAN, 0.0, false, null, 0);\r
+\r
+ private final int _cellType;\r
+ private final double _numberValue;\r
+ private final boolean _booleanValue;\r
+ private final String _textValue;\r
+ private final int _errorCode;\r
+\r
+ private CellValue(int cellType, double numberValue, boolean booleanValue,\r
+ String textValue, int errorCode) {\r
+ _cellType = cellType;\r
+ _numberValue = numberValue;\r
+ _booleanValue = booleanValue;\r
+ _textValue = textValue;\r
+ _errorCode = errorCode;\r
+ }\r
+\r
+\r
+ public CellValue(double numberValue) {\r
+ this(Cell.CELL_TYPE_NUMERIC, numberValue, false, null, 0);\r
+ }\r
+ public static CellValue valueOf(boolean booleanValue) {\r
+ return booleanValue ? TRUE : FALSE;\r
+ }\r
+ public CellValue(String stringValue) {\r
+ this(Cell.CELL_TYPE_STRING, 0.0, false, stringValue, 0);\r
+ }\r
+ public static CellValue getError(int errorCode) {\r
+ return new CellValue(Cell.CELL_TYPE_ERROR, 0.0, false, null, errorCode);\r
+ }\r
+\r
+\r
+ /**\r
+ * @return Returns the booleanValue.\r
+ */\r
+ public boolean getBooleanValue() {\r
+ return _booleanValue;\r
+ }\r
+ /**\r
+ * @return Returns the numberValue.\r
+ */\r
+ public double getNumberValue() {\r
+ return _numberValue;\r
+ }\r
+ /**\r
+ * @return Returns the stringValue.\r
+ */\r
+ public String getStringValue() {\r
+ return _textValue;\r
+ }\r
+ /**\r
+ * @return Returns the cellType.\r
+ */\r
+ public int getCellType() {\r
+ return _cellType;\r
+ }\r
+ /**\r
+ * @return Returns the errorValue.\r
+ */\r
+ public byte getErrorValue() {\r
+ return (byte) _errorCode;\r
+ }\r
+ public String toString() {\r
+ StringBuffer sb = new StringBuffer(64);\r
+ sb.append(getClass().getName()).append(" [");\r
+ sb.append(formatAsString());\r
+ sb.append("]");\r
+ return sb.toString();\r
+ }\r
+\r
+ public String formatAsString() {\r
+ switch (_cellType) {\r
+ case Cell.CELL_TYPE_NUMERIC:\r
+ return String.valueOf(_numberValue);\r
+ case Cell.CELL_TYPE_STRING:\r
+ return '"' + _textValue + '"';\r
+ case Cell.CELL_TYPE_BOOLEAN:\r
+ return _booleanValue ? "TRUE" : "FALSE";\r
+ case Cell.CELL_TYPE_ERROR:\r
+ return ErrorEval.getText(_errorCode);\r
+ }\r
+ return "<error unexpected cell type " + _cellType + ">";\r
+ }\r
+}\r
+++ /dev/null
-/* ====================================================================
- 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();
- }
-}
+++ /dev/null
-/* ====================================================================
- 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 <tt>FormulaEvaluator.internalEvaluate()</tt>.
- * 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.<br/>
- */
- 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.<br/>
- *
- * In the case of a <code>true</code> return code, the caller should
- * continue evaluation of the specified cell, and also be sure to call
- * <tt>endEvaluate()</tt> when complete.<br/>
- *
- * In the case of a <code>false</code> return code, the caller should
- * return an evaluation result of
- * <tt>ErrorEval.CIRCULAR_REF_ERROR<tt>, and not call <tt>endEvaluate()</tt>.
- * <br/>
- * @return <code>true</code> if the specified cell has not been visited yet in the current
- * evaluation. <code>false</code> 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. <p/>
- *
- * Every successful call to <tt>startEvaluate</tt> must be followed by a
- * call to <tt>endEvaluate</tt> (recommended in a finally block) to enable
- * proper tracking of which cells are being evaluated at any point in time.<p/>
- *
- * 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);
- }
-}
+++ /dev/null
-/* ====================================================================
- 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 <tt>EvaluationCycleDetector</tt> instance available to
- * each thread via a <tt>ThreadLocal</tt> in order to avoid adding a parameter
- * to a few protected methods within <tt>HSSFFormulaEvaluator</tt>.
- *
- * @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
- }
-}
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.<p/>
* @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
* 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);
/**
* @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
* 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; i<wb.getNumberOfSheets(); i++) {
- Sheet sheet = wb.getSheetAt(i);
-
- for (Iterator rit = sheet.rowIterator(); rit.hasNext();) {
- Row r = (Row)rit.next();
-
- for (Iterator cit = r.cellIterator(); cit.hasNext();) {
- Cell c = (Cell)cit.next();
- if (c.getCellType() == Cell.CELL_TYPE_FORMULA)
- evaluator.evaluateFormulaCell(c);
- }
- }
- }
- }
-
- /**
- * Returns a CellValue wrapper around the supplied ValueEval instance.
- * @param eval
- */
- private CellValue evaluateFormulaCellValue(Cell cell) {
- ValueEval eval = internalEvaluate(cell);
- if (eval instanceof NumberEval) {
- NumberEval ne = (NumberEval) eval;
- return new CellValue(ne.getNumberValue(), _workbook.getCreationHelper());
- }
- 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(), _workbook.getCreationHelper());
- }
- if (eval instanceof ErrorEval) {
- return CellValue.getError(((ErrorEval)eval).getErrorCode());
- }
- throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")");
- }
-
- /**
- * 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 <code>null</code>, 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 <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>,
- * <tt>BlankEval</tt> or <tt>ErrorEval</tt>. Never <code>null</code>.
- */
- 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 <br/>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 "<error unexpected cell type " + _cellType + ">";
- }
- }
+ Cell evaluateInCell(Cell cell);
}
+++ /dev/null
-/* ====================================================================
- 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();
- }
-}
+++ /dev/null
-/* ====================================================================
- 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();
- }
-}
+++ /dev/null
-/* ====================================================================
- 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 <tt>OperationEval</tt> instances to help evaluate <tt>OperationPtg</tt>
- * 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;
- }
-}
*/
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.
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)
*/
void removeSheetAt(int index);
-
- String findSheetNameFromExternSheet(int externSheetIndex);
/**
* determine whether the Excel GUI will backup the workbook when saving.
*/
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
* <p>
--- /dev/null
+package org.apache.poi.xssf.usermodel;\r
+\r
+import org.apache.poi.hssf.record.formula.NamePtg;\r
+import org.apache.poi.hssf.record.formula.NameXPtg;\r
+import org.apache.poi.hssf.record.formula.Ptg;\r
+import org.apache.poi.ss.formula.EvaluationName;\r
+import org.apache.poi.ss.formula.EvaluationWorkbook;\r
+import org.apache.poi.ss.formula.FormulaParser;\r
+import org.apache.poi.ss.formula.FormulaParsingWorkbook;\r
+import org.apache.poi.ss.formula.FormulaRenderingWorkbook;\r
+import org.apache.poi.ss.usermodel.Cell;\r
+import org.apache.poi.ss.usermodel.Sheet;\r
+import org.apache.poi.ss.usermodel.Workbook;\r
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName;\r
+\r
+/**\r
+ * Internal POI use only\r
+ * \r
+ * @author Josh Micich\r
+ */\r
+public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook {\r
+\r
+ private final XSSFWorkbook _uBook;\r
+ \r
+ public static XSSFEvaluationWorkbook create(XSSFWorkbook book) {\r
+ if (book == null) {\r
+ return null;\r
+ }\r
+ return new XSSFEvaluationWorkbook(book);\r
+ }\r
+\r
+ private XSSFEvaluationWorkbook(XSSFWorkbook book) {\r
+ _uBook = book;\r
+ }\r
+\r
+ private int convertFromExternalSheetIndex(int externSheetIndex) {\r
+ return externSheetIndex;\r
+ }\r
+ /**\r
+ * @returns the external sheet index of the sheet with the given internal\r
+ * index, creating one if needed. Used by some of the more obscure\r
+ * formula and named range things. Fairly easy on XSSF (we\r
+ * think...) since the internal and external indicies are the same\r
+ */\r
+ private int convertToExternalSheetIndex(int sheetIndex) {\r
+ return sheetIndex;\r
+ }\r
+\r
+ public int getExternalSheetIndex(String sheetName) {\r
+ int sheetIndex = _uBook.getSheetIndex(sheetName);\r
+ return convertToExternalSheetIndex(sheetIndex);\r
+ }\r
+\r
+ public EvaluationName getName(int index) {\r
+ return new Name(_uBook.getNameAt(index), index, this);\r
+ }\r
+\r
+ public EvaluationName getName(String name) {\r
+ for(int i=0; i < _uBook.getNumberOfNames(); i++) {\r
+ String nameText = _uBook.getNameName(i);\r
+ if (name.equalsIgnoreCase(nameText)) {\r
+ return new Name(_uBook.getNameAt(i), i, this);\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ public int getSheetIndex(Sheet sheet) {\r
+ return _uBook.getSheetIndex(sheet);\r
+ }\r
+\r
+ public String getSheetName(int sheetIndex) {\r
+ return _uBook.getSheetName(sheetIndex);\r
+ }\r
+\r
+ public int getNameIndex(String name) {\r
+ return _uBook.getNameIndex(name);\r
+ }\r
+\r
+ public NameXPtg getNameXPtg(String name) {\r
+ // may require to return null to make tests pass\r
+ throw new RuntimeException("Not implemented yet");\r
+ }\r
+\r
+ public Sheet getSheet(int sheetIndex) {\r
+ return _uBook.getSheetAt(sheetIndex);\r
+ }\r
+\r
+ /**\r
+ * Doesn't do anything - returns the same index\r
+ * TODO - figure out if this is a ole2 specific thing, or\r
+ * if we need to do something proper here too!\r
+ */\r
+ public Sheet getSheetByExternSheetIndex(int externSheetIndex) {\r
+ int sheetIndex = convertFromExternalSheetIndex(externSheetIndex);\r
+ return _uBook.getSheetAt(sheetIndex);\r
+ }\r
+\r
+ public Workbook getWorkbook() {\r
+ return _uBook;\r
+ }\r
+\r
+ /**\r
+ * TODO - figure out what the hell this methods does in\r
+ * HSSF...\r
+ */\r
+ public String resolveNameXText(NameXPtg n) {\r
+ throw new RuntimeException("method not implemented yet");\r
+ }\r
+\r
+ public String getSheetNameByExternSheet(int externSheetIndex) {\r
+ int sheetIndex = convertFromExternalSheetIndex(externSheetIndex);\r
+ return _uBook.getSheetName(sheetIndex);\r
+ }\r
+\r
+ public String getNameText(NamePtg namePtg) {\r
+ return _uBook.getNameAt(namePtg.getIndex()).getNameName();\r
+ }\r
+ public EvaluationName getName(NamePtg namePtg) {\r
+ int ix = namePtg.getIndex();\r
+ return new Name(_uBook.getNameAt(ix), ix, this);\r
+ }\r
+ public Ptg[] getFormulaTokens(Cell cell) {\r
+ XSSFEvaluationWorkbook frBook = XSSFEvaluationWorkbook.create(_uBook);\r
+ return FormulaParser.parse(cell.getCellFormula(), frBook);\r
+ }\r
+\r
+ private static final class Name implements EvaluationName {\r
+\r
+ private final XSSFName _nameRecord;\r
+ private final int _index;\r
+ private final FormulaParsingWorkbook _fpBook;\r
+\r
+ public Name(XSSFName name, int index, FormulaParsingWorkbook fpBook) {\r
+ _nameRecord = name;\r
+ _index = index;\r
+ _fpBook = fpBook;\r
+ }\r
+\r
+ public Ptg[] getNameDefinition() {\r
+ \r
+ return FormulaParser.parse(_nameRecord.getReference(), _fpBook);\r
+ }\r
+\r
+ public String getNameText() {\r
+ return _nameRecord.getNameName();\r
+ }\r
+\r
+ public boolean hasFormula() {\r
+ // TODO - no idea if this is right\r
+ CTDefinedName ctn = _nameRecord.getCTName();\r
+ String strVal = ctn.getStringValue();\r
+ return !ctn.getFunction() && strVal != null && strVal.length() > 0;\r
+ }\r
+\r
+ public boolean isFunctionName() {\r
+ return _nameRecord.isFunctionName();\r
+ }\r
+\r
+ public boolean isRange() {\r
+ return hasFormula(); // TODO - is this right?\r
+ }\r
+ public NamePtg createPtg() {\r
+ return new NamePtg(_index);\r
+ }\r
+ }\r
+}\r
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.xssf.usermodel;\r
+\r
+import java.util.Iterator;\r
+\r
+import org.apache.poi.hssf.record.formula.eval.BoolEval;\r
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;\r
+import org.apache.poi.hssf.record.formula.eval.NumberEval;\r
+import org.apache.poi.hssf.record.formula.eval.StringEval;\r
+import org.apache.poi.hssf.record.formula.eval.ValueEval;\r
+import org.apache.poi.hssf.usermodel.HSSFCell;\r
+import org.apache.poi.ss.formula.WorkbookEvaluator;\r
+import org.apache.poi.ss.usermodel.Cell;\r
+import org.apache.poi.ss.usermodel.CellValue;\r
+import org.apache.poi.ss.usermodel.FormulaEvaluator;\r
+import org.apache.poi.ss.usermodel.Row;\r
+import org.apache.poi.ss.usermodel.Sheet;\r
+\r
+/**\r
+ * Evaluates formula cells.<p/>\r
+ *\r
+ * For performance reasons, this class keeps a cache of all previously calculated intermediate\r
+ * cell values. Be sure to call {@link #clearCache()} if any workbook cells are changed between\r
+ * calls to evaluate~ methods on this class.\r
+ *\r
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >\r
+ * @author Josh Micich\r
+ */\r
+public class XSSFFormulaEvaluator implements FormulaEvaluator {\r
+\r
+ private WorkbookEvaluator _bookEvaluator;\r
+\r
+ public XSSFFormulaEvaluator(XSSFWorkbook workbook) {\r
+ _bookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(workbook));\r
+ }\r
+\r
+ /**\r
+ * TODO for debug/test use\r
+ */\r
+ /* package */ int getEvaluationCount() {\r
+ return _bookEvaluator.getEvaluationCount();\r
+ }\r
+\r
+\r
+ /**\r
+ * Should be called whenever there are major changes (e.g. moving sheets) to input cells\r
+ * in the evaluated workbook.\r
+ * Failure to call this method after changing cell values will cause incorrect behaviour\r
+ * of the evaluate~ methods of this class\r
+ */\r
+ public void clearAllCachedResultValues() {\r
+ _bookEvaluator.clearAllCachedResultValues();\r
+ }\r
+ /**\r
+ * Should be called whenever there are changes to individual input cells in the evaluated workbook.\r
+ * Failure to call this method after changing cell values will cause incorrect behaviour\r
+ * of the evaluate~ methods of this class\r
+ */\r
+ public void clearCachedResultValue(Sheet sheet, int rowIndex, int columnIndex) {\r
+ _bookEvaluator.clearCachedResultValue(sheet, rowIndex, columnIndex);\r
+ }\r
+\r
+ /**\r
+ * If cell contains a formula, the formula is evaluated and returned,\r
+ * else the CellValue simply copies the appropriate cell value from\r
+ * the cell and also its cell type. This method should be preferred over\r
+ * evaluateInCell() when the call should not modify the contents of the\r
+ * original cell.\r
+ * @param cell\r
+ */\r
+ public CellValue evaluate(Cell cell) {\r
+ if (cell == null) {\r
+ return null;\r
+ }\r
+\r
+ switch (cell.getCellType()) {\r
+ case XSSFCell.CELL_TYPE_BOOLEAN:\r
+ return CellValue.valueOf(cell.getBooleanCellValue());\r
+ case XSSFCell.CELL_TYPE_ERROR:\r
+ return CellValue.getError(cell.getErrorCellValue());\r
+ case XSSFCell.CELL_TYPE_FORMULA:\r
+ return evaluateFormulaCellValue(cell);\r
+ case XSSFCell.CELL_TYPE_NUMERIC:\r
+ return new CellValue(cell.getNumericCellValue());\r
+ case XSSFCell.CELL_TYPE_STRING:\r
+ return new CellValue(cell.getRichStringCellValue().getString());\r
+ }\r
+ throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")");\r
+ }\r
+\r
+\r
+ /**\r
+ * If cell contains formula, it evaluates the formula,\r
+ * and saves the result of the formula. The cell\r
+ * remains as a formula cell.\r
+ * Else if cell does not contain formula, this method leaves\r
+ * the cell unchanged.\r
+ * Note that the type of the formula result is returned,\r
+ * so you know what kind of value is also stored with\r
+ * the formula.\r
+ * <pre>\r
+ * int evaluatedCellType = evaluator.evaluateFormulaCell(cell);\r
+ * </pre>\r
+ * Be aware that your cell will hold both the formula,\r
+ * and the result. If you want the cell replaced with\r
+ * the result of the formula, use {@link #evaluateInCell(HSSFCell)}\r
+ * @param cell The cell to evaluate\r
+ * @return The type of the formula result (the cell's type remains as HSSFCell.CELL_TYPE_FORMULA however)\r
+ */\r
+ public int evaluateFormulaCell(Cell cell) {\r
+ if (cell == null || cell.getCellType() != XSSFCell.CELL_TYPE_FORMULA) {\r
+ return -1;\r
+ }\r
+ CellValue cv = evaluateFormulaCellValue(cell);\r
+ // cell remains a formula cell, but the cached value is changed\r
+ setCellValue(cell, cv);\r
+ return cv.getCellType();\r
+ }\r
+\r
+ /**\r
+ * If cell contains formula, it evaluates the formula, and\r
+ * puts the formula result back into the cell, in place\r
+ * of the old formula.\r
+ * Else if cell does not contain formula, this method leaves\r
+ * the cell unchanged.\r
+ * Note that the same instance of HSSFCell is returned to\r
+ * allow chained calls like:\r
+ * <pre>\r
+ * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();\r
+ * </pre>\r
+ * Be aware that your cell value will be changed to hold the\r
+ * result of the formula. If you simply want the formula\r
+ * value computed for you, use {@link #evaluateFormulaCell(HSSFCell)}\r
+ * @param cell\r
+ */\r
+ public XSSFCell evaluateInCell(Cell cell) {\r
+ if (cell == null) {\r
+ return null;\r
+ }\r
+ XSSFCell result = (XSSFCell) cell;\r
+ if (cell.getCellType() == XSSFCell.CELL_TYPE_FORMULA) {\r
+ CellValue cv = evaluateFormulaCellValue(cell);\r
+ setCellType(cell, cv); // cell will no longer be a formula cell\r
+ setCellValue(cell, cv);\r
+ }\r
+ return result;\r
+ }\r
+ private static void setCellType(Cell cell, CellValue cv) {\r
+ int cellType = cv.getCellType();\r
+ switch (cellType) {\r
+ case XSSFCell.CELL_TYPE_BOOLEAN:\r
+ case XSSFCell.CELL_TYPE_ERROR:\r
+ case XSSFCell.CELL_TYPE_NUMERIC:\r
+ case XSSFCell.CELL_TYPE_STRING:\r
+ cell.setCellType(cellType);\r
+ return;\r
+ case XSSFCell.CELL_TYPE_BLANK:\r
+ // never happens - blanks eventually get translated to zero\r
+ case XSSFCell.CELL_TYPE_FORMULA:\r
+ // this will never happen, we have already evaluated the formula\r
+ }\r
+ throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");\r
+ }\r
+\r
+ private static void setCellValue(Cell cell, CellValue cv) {\r
+ int cellType = cv.getCellType();\r
+ switch (cellType) {\r
+ case XSSFCell.CELL_TYPE_BOOLEAN:\r
+ cell.setCellValue(cv.getBooleanValue());\r
+ break;\r
+ case XSSFCell.CELL_TYPE_ERROR:\r
+ cell.setCellErrorValue(cv.getErrorValue());\r
+ break;\r
+ case XSSFCell.CELL_TYPE_NUMERIC:\r
+ cell.setCellValue(cv.getNumberValue());\r
+ break;\r
+ case XSSFCell.CELL_TYPE_STRING:\r
+ cell.setCellValue(new XSSFRichTextString(cv.getStringValue()));\r
+ break;\r
+ case XSSFCell.CELL_TYPE_BLANK:\r
+ // never happens - blanks eventually get translated to zero\r
+ case XSSFCell.CELL_TYPE_FORMULA:\r
+ // this will never happen, we have already evaluated the formula\r
+ default:\r
+ throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Loops over all cells in all sheets of the supplied\r
+ * workbook.\r
+ * For cells that contain formulas, their formulas are\r
+ * evaluated, and the results are saved. These cells\r
+ * remain as formula cells.\r
+ * For cells that do not contain formulas, no changes\r
+ * are made.\r
+ * This is a helpful wrapper around looping over all\r
+ * cells, and calling evaluateFormulaCell on each one.\r
+ */\r
+ public static void evaluateAllFormulaCells(XSSFWorkbook wb) {\r
+ XSSFFormulaEvaluator evaluator = new XSSFFormulaEvaluator(wb);\r
+ for(int i=0; i<wb.getNumberOfSheets(); i++) {\r
+ Sheet sheet = wb.getSheetAt(i);\r
+\r
+ for (Iterator<Row> rit = sheet.rowIterator(); rit.hasNext();) {\r
+ Row r = rit.next();\r
+\r
+ for (Iterator cit = r.cellIterator(); cit.hasNext();) {\r
+ XSSFCell c = (XSSFCell) cit.next();\r
+ if (c.getCellType() == XSSFCell.CELL_TYPE_FORMULA)\r
+ evaluator.evaluateFormulaCell(c);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns a CellValue wrapper around the supplied ValueEval instance.\r
+ * @param eval\r
+ */\r
+ private CellValue evaluateFormulaCellValue(Cell cell) {\r
+ ValueEval eval = _bookEvaluator.evaluate(cell);\r
+ if (eval instanceof NumberEval) {\r
+ NumberEval ne = (NumberEval) eval;\r
+ return new CellValue(ne.getNumberValue());\r
+ }\r
+ if (eval instanceof BoolEval) {\r
+ BoolEval be = (BoolEval) eval;\r
+ return CellValue.valueOf(be.getBooleanValue());\r
+ }\r
+ if (eval instanceof StringEval) {\r
+ StringEval ne = (StringEval) eval;\r
+ return new CellValue(ne.getStringValue());\r
+ }\r
+ if (eval instanceof ErrorEval) {\r
+ return CellValue.getError(((ErrorEval)eval).getErrorCode());\r
+ }\r
+ throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")");\r
+ }\r
+}\r
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
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();
}
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) {
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();
}
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;
/**
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
}
- 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");
}
*/
private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) {
- FormulaEvaluator evaluator = new FormulaEvaluator(workbook);
+ FormulaEvaluator evaluator = new XSSFFormulaEvaluator(workbook);
int rowIndex = startRowIndex;
while (true) {
continue;
}
- FormulaEvaluator.CellValue actualValue;
+ CellValue actualValue;
try {
actualValue = evaluator.evaluate(c);
} catch (RuntimeException e) {
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);
}
public void testSimpleArithmatic() {
- Workbook wb = new XSSFWorkbook();
+ XSSFWorkbook wb = new XSSFWorkbook();
Sheet s = wb.createSheet();
Row r = s.createRow(0);
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);
}
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);
// Evaluate and test
- FormulaEvaluator fe = new FormulaEvaluator(s, wb);
+ FormulaEvaluator fe = new XSSFFormulaEvaluator(wb);
fe.evaluateFormulaCell(c1);
fe.evaluateFormulaCell(c2);
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;
// Check we can get the formula without breaking
for(int i=0; i<fRecs.length; i++) {
- FormulaParser.toFormulaString(stubHSSF, fRecs[i].getParsedExpression());
+ HSSFFormulaParser.toFormulaString(stubHSSF, fRecs[i].getParsedExpression());
}
// Peer into just one formula, and check that
fr = fRecs[0];
assertEquals(1, fr.getRow());
assertEquals(0, fr.getColumn());
- assertEquals("Sheet1!A1", FormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression()));
+ assertEquals("Sheet1!A1", HSSFFormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression()));
// Sheet 1 A5 is to another sheet
fr = fRecs[3];
assertEquals(4, fr.getRow());
assertEquals(0, fr.getColumn());
- assertEquals("'S2'!A1", FormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression()));
+ assertEquals("'S2'!A1", HSSFFormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression()));
// Sheet 1 A7 is to another sheet, range
fr = fRecs[5];
assertEquals(6, fr.getRow());
assertEquals(0, fr.getColumn());
- assertEquals("SUM(Sh3!A1:A4)", FormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression()));
+ assertEquals("SUM(Sh3!A1:A4)", HSSFFormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression()));
// Now, load via Usermodel and re-check
import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples;
-import org.apache.poi.hssf.model.FormulaParser.FormulaParseException;
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.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.formula.FormulaParserTestHelper;
/**
* Test the low level formula parser functionality. High level tests are to
* @return parsed token array already confirmed not <code>null</code>
*/
/* 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");
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
Ptg[] ptgs = {
new FuncPtg(10),
};
- assertEquals("NA()", FormulaParser.toFormulaString(book, ptgs));
+ assertEquals("NA()", HSSFFormulaParser.toFormulaString(book, ptgs));
}
public void testPercent() {
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() {
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);
}
}
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");
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);
}
DividePtg.instance,
};
try {
- FormulaParser.toFormulaString(null, ptgs);
+ toFormulaString(ptgs);
fail("Expected exception was not thrown");
} catch (IllegalStateException e) {
// expected during successful test
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);
}
}
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.");
}
}
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");
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
}
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());
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;
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,
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());
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);
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 <tt>OperandClassTransformer</tt>.
*/
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);
*/
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);
*/
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");
}
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];
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) {
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;
/**
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<ptgs.length; i++)
{
Ptg ptg = ptgs[i];
aptg.setLastColumn((short)(aptg.getLastColumn()+letUsShiftColumn1By1Column));
}
}
- String newFormula = FormulaParser.toFormulaString(wb, ptgs);
+ String newFormula = HSSFFormulaParser.toFormulaString(wb, ptgs);
return newFormula;
}
}
import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples;
-import org.apache.poi.hssf.model.FormulaParser;
+import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
-import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue;
+import org.apache.poi.ss.usermodel.CellValue;
/**
* Tests for functions from external workbooks (e.g. YEARFRAC).
*
public void testParse() {
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("externalFunctionExample.xls");
- Ptg[] ptgs = FormulaParser.parse("YEARFRAC(B1,C1)", wb);
+ Ptg[] ptgs = HSSFFormulaParser.parse("YEARFRAC(B1,C1)", wb);
assertEquals(4, ptgs.length);
assertEquals(NameXPtg.class, ptgs[0].getClass());
package org.apache.poi.hssf.record.formula;
-import org.apache.poi.hssf.model.FormulaParser;
+import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import junit.framework.AssertionFailedError;
*/
public void testOperandClass() {
HSSFWorkbook book = new HSSFWorkbook();
- Ptg[] ptgs = FormulaParser.parse("sum(A1:A2)", book);
+ Ptg[] ptgs = HSSFFormulaParser.parse("sum(A1:A2)", book);
assertEquals(2, ptgs.length);
assertEquals(AreaPtg.class, ptgs[0].getClass());
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 HSSFFormulaEvaluator for its handling of cell formula circular references.
*
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;
/**
*
* @author Josh Micich
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;
/**
* Miscellaneous tests for bugzilla entries.<p/> The test name contains the
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.<p/>
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
}
- 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");
}
* Typically pass <code>null</code> 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) {
* @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
continue;
}
- FormulaEvaluator.CellValue actualValue = evaluator.evaluate(c);
+ CellValue actualValue = evaluator.evaluate(c);
Cell expectedValueCell = getExpectedValueCell(expectedValuesRow, colnum);
try {
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.
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;
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);
// 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);
}
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()
private void confirm(String formulaText, double expectedResult) {
cell11.setCellFormula(formulaText);
- evaluator.clearCache();
+ evaluator.clearAllCachedResultValues();
double actualValue = evaluator.evaluate(cell11).getNumberValue();
assertEquals(expectedResult, actualValue, 0);
}
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.<p/>
- 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");
}
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()
*
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.<p/>
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;
/**
*
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
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.ss.formula;\r
+\r
+import junit.framework.Assert;\r
+import junit.framework.AssertionFailedError;\r
+\r
+import org.apache.poi.ss.formula.FormulaParser;\r
+import org.apache.poi.ss.formula.FormulaParser.FormulaParseException;\r
+/**\r
+ * Avoids making {@link FormulaParser#FormulaParseException} public\r
+ * \r
+ * @author Josh Micich\r
+ */\r
+public class FormulaParserTestHelper {\r
+ public static void confirmParseException(RuntimeException e, String expectedMessage) {\r
+ checkType(e);\r
+ Assert.assertEquals(expectedMessage, e.getMessage());\r
+ }\r
+ public static void confirmParseException(RuntimeException e) {\r
+ checkType(e);\r
+ Assert.assertNotNull(e.getMessage());\r
+ }\r
+ private static void checkType(RuntimeException e) throws AssertionFailedError {\r
+ if (!(e instanceof FormulaParseException)) {\r
+ String failMsg = "Expected FormulaParseException, but got (" \r
+ + e.getClass().getName() + "):";\r
+ System.err.println(failMsg);\r
+ e.printStackTrace();\r
+ throw new AssertionFailedError(failMsg);\r
+ }\r
+ }\r
+}\r