From 60782ef942736018eb0a17ab629bafe2e3a425bf Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Tue, 4 Mar 2008 16:53:32 +0000 Subject: [PATCH] Big patch from Josh from bug #44504 - lots of formula parser improvements git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@633547 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hssf/model/FormulaParser.java | 1253 +++++++++-------- .../org/apache/poi/hssf/model/LinkTable.java | 302 ++++ .../apache/poi/hssf/model/RecordStream.java | 65 + .../org/apache/poi/hssf/model/Workbook.java | 237 +--- .../poi/hssf/record/CRNCountRecord.java | 94 ++ .../org/apache/poi/hssf/record/CRNRecord.java | 99 ++ .../poi/hssf/record/ExternalNameRecord.java | 179 +++ .../apache/poi/hssf/record/RecordFactory.java | 6 +- .../apache/poi/hssf/record/SupBookRecord.java | 207 ++- .../record/constant/ConstantValueParser.java | 111 ++ .../record/formula/AbstractFunctionPtg.java | 80 +- .../poi/hssf/record/formula/IntPtg.java | 95 +- .../poi/hssf/record/formula/NamePtg.java | 36 +- .../poi/hssf/record/formula/NameXPtg.java | 18 +- .../apache/poi/hssf/usermodel/HSSFCell.java | 2 +- .../apache/poi/hssf/usermodel/HSSFSheet.java | 4 +- .../org/apache/poi/util/LittleEndian.java | 10 + .../poi/hssf/record/formula/eval/AddEval.java | 50 +- .../hssf/record/formula/eval/ConcatEval.java | 52 +- .../hssf/record/formula/eval/DivideEval.java | 50 +- .../hssf/record/formula/eval/ErrorEval.java | 6 - .../record/formula/eval/ExternalFunction.java | 81 ++ .../record/formula/eval/FunctionEval.java | 6 +- .../record/formula/eval/MultiplyEval.java | 76 +- .../hssf/record/formula/eval/NameEval.java | 48 + .../record/formula/eval/OperandResolver.java | 46 +- .../hssf/record/formula/eval/PowerEval.java | 77 +- .../hssf/record/formula/eval/Ref2DEval.java | 30 +- .../hssf/record/formula/eval/Ref3DEval.java | 29 +- .../poi/hssf/record/formula/eval/RefEval.java | 20 +- .../record/formula/eval/SubtractEval.java | 52 +- .../record/formula/eval/UnaryMinusEval.java | 45 +- .../record/formula/eval/UnaryPlusEval.java | 126 +- .../eval/ValueEvalToNumericXlator.java | 148 +- .../hssf/record/formula/functions/Avedev.java | 5 +- .../record/formula/functions/Average.java | 5 +- .../formula/functions/FinanceFunction.java | 13 +- .../poi/hssf/record/formula/functions/If.java | 24 +- .../record/formula/functions/Isblank.java | 80 +- .../hssf/record/formula/functions/Len.java | 127 +- .../hssf/record/formula/functions/Maxa.java | 8 +- .../hssf/record/formula/functions/Mid.java | 16 +- .../hssf/record/formula/functions/Mina.java | 6 +- .../MultiOperandNumericFunction.java | 29 +- .../formula/functions/NumericFunction.java | 2 - .../hssf/record/formula/functions/Stdev.java | 5 +- .../hssf/record/formula/functions/Sumsq.java | 8 +- .../record/formula/functions/Sumx2my2.java | 51 +- .../record/formula/functions/Sumx2py2.java | 51 +- .../record/formula/functions/Sumxmy2.java | 51 +- .../hssf/record/formula/functions/Trim.java | 66 +- .../formula/functions/XYNumericFunction.java | 231 ++- .../hssf/usermodel/HSSFFormulaEvaluator.java | 128 +- .../TestMissingRecordAwareHSSFListener.java | 96 +- .../formula/eval/AllFormulaEvalTests.java | 38 + .../formula/eval/TestExternalFunction.java | 61 + .../AllIndividualFunctionEvaluationTests.java | 5 + .../record/formula/functions/EvalFactory.java | 2 +- .../functions/NumericFunctionInvoker.java | 3 - .../record/formula/functions/TestAverage.java | 103 ++ .../formula/functions/TestCountFuncs.java | 2 +- .../record/formula/functions/TestIsBlank.java | 62 + .../record/formula/functions/TestLen.java | 73 + .../record/formula/functions/TestMid.java | 3 +- .../formula/functions/TestSumproduct.java | 2 +- .../record/formula/functions/TestTFunc.java | 2 +- .../record/formula/functions/TestTrim.java | 78 + .../functions/TestXYNumericFunction.java | 139 ++ .../org/apache/poi/hssf/HSSFTests.java | 187 +-- .../poi/hssf/data/FormulaEvalTestData.xls | Bin 141312 -> 136704 bytes .../poi/hssf/data/externalFunctionExample.xls | Bin 0 -> 16384 bytes .../apache/poi/hssf/data/logoKarmokar4.png | Bin 0 -> 15792 bytes .../poi/hssf/model/TestFormulaParser.java | 274 +++- .../poi/hssf/record/AllRecordTests.java | 104 ++ .../poi/hssf/record/TestSupBookRecord.java | 87 +- .../record/TestcaseRecordInputStream.java | 28 +- .../record/formula/AbstractPtgTestCase.java | 20 +- .../hssf/record/formula/AllFormulaTests.java | 4 +- .../formula/TestExternalFunctionFormulas.java | 56 + .../poi/hssf/usermodel/AllUserModelTests.java | 71 + .../poi/hssf/usermodel/TestHSSFPicture.java | 60 +- 81 files changed, 3815 insertions(+), 2391 deletions(-) create mode 100755 src/java/org/apache/poi/hssf/model/LinkTable.java create mode 100755 src/java/org/apache/poi/hssf/model/RecordStream.java create mode 100755 src/java/org/apache/poi/hssf/record/CRNCountRecord.java create mode 100755 src/java/org/apache/poi/hssf/record/CRNRecord.java create mode 100755 src/java/org/apache/poi/hssf/record/ExternalNameRecord.java create mode 100755 src/java/org/apache/poi/hssf/record/constant/ConstantValueParser.java create mode 100755 src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java create mode 100755 src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java create mode 100755 src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls create mode 100755 src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png create mode 100755 src/testcases/org/apache/poi/hssf/record/AllRecordTests.java create mode 100755 src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java create mode 100755 src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java diff --git a/src/java/org/apache/poi/hssf/model/FormulaParser.java b/src/java/org/apache/poi/hssf/model/FormulaParser.java index dc75a5c170..7b89b90d81 100644 --- a/src/java/org/apache/poi/hssf/model/FormulaParser.java +++ b/src/java/org/apache/poi/hssf/model/FormulaParser.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,15 +14,13 @@ 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.Iterator; -import java.util.LinkedList; import java.util.List; +import java.util.Stack; import java.util.regex.Pattern; //import PTG's .. since we need everything, import * @@ -33,7 +30,7 @@ import org.apache.poi.hssf.record.formula.*; /** * This class parses a formula string into a List of tokens in RPN order. - * Inspired by + * Inspired by * Lets Build a Compiler, by Jack Crenshaw * BNF for the formula expression is : * ::= [ ]* @@ -48,138 +45,124 @@ import org.apache.poi.hssf.record.formula.*; * @author Peter M. Murray (pete at quantrix dot com) * @author Pavel Krupets (pkrupets at palmtreebusiness dot com) */ -public class FormulaParser { +public final class FormulaParser { + /** + * Specific exception thrown when a supplied formula does not parse properly.
+ * Primarily used by test cases when testing for specific parsing exceptions.

+ * + */ + static final class FormulaParseException extends RuntimeException { + // This class was given package scope until it would become clear that it is useful to + // general client code. + public FormulaParseException(String msg) { + super(msg); + } + } + public static int FORMULA_TYPE_CELL = 0; public static int FORMULA_TYPE_SHARED = 1; public static int FORMULA_TYPE_ARRAY =2; public static int FORMULA_TYPE_CONDFOMRAT = 3; public static int FORMULA_TYPE_NAMEDRANGE = 4; - - private String formulaString; - private int pointer=0; - private int formulaLength; - - private List tokens = new java.util.Stack(); - - /** - * Using an unsynchronized linkedlist to implement a stack since we're not multi-threaded. - */ - private List functionTokens = new LinkedList(); + + private final String formulaString; + private final int formulaLength; + private int pointer; + + private final List tokens = new Stack(); /** * Used for spotting if we have a cell reference, * or a named range */ private final static Pattern CELL_REFERENCE_PATTERN = Pattern.compile("(?:('?)[^:\\\\/\\?\\*\\[\\]]+\\1!)?\\$?[A-Za-z]+\\$?[\\d]+"); - + private static char TAB = '\t'; - private static char CR = '\n'; - - private char look; // Lookahead Character - - private Workbook book; - - - /** + + /** + * 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.HSSFWorkbook, and not a * model.Workbook, then use the convenience method on - * usermodel.HSSFFormulaEvaluator + * usermodel.HSSFFormulaEvaluator */ public FormulaParser(String formula, Workbook book){ formulaString = formula; pointer=0; this.book = book; - formulaLength = formulaString.length(); + formulaLength = formulaString.length(); + } + + public static Ptg[] parse(String formula, Workbook book) { + FormulaParser fp = new FormulaParser(formula, book); + fp.parse(); + return fp.getRPNPtg(); } - /** Read New Character From Input Stream */ private void GetChar() { // Check to see if we've walked off the end of the string. - // Just return if so and reset Look to smoething to keep - // SkipWhitespace from spinning - if (pointer == formulaLength) { + 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; - return; - } - look=formulaString.charAt(pointer++); + } + pointer++; //System.out.println("Got char: "+ look); } - - - /** Report an Error */ - private void Error(String s) { - System.out.println("Error: "+s); - } - - - - /** Report Error and Halt */ - private void Abort(String s) { - Error(s); - //System.exit(1); //throw exception?? - throw new RuntimeException("Cannot Parse, sorry : " + s + " @ " + pointer + " [Formula String was: '" + formulaString + "']"); - } - - /** Report What Was Expected */ - private void Expected(String s) { - Abort(s + " Expected"); + private RuntimeException expected(String s) { + return new FormulaParseException(s + " Expected"); } - - - + + + /** Recognize an Alpha Character */ private boolean IsAlpha(char c) { return Character.isLetter(c) || c == '$' || c=='_'; } - - - + + + /** Recognize a Decimal Digit */ private boolean IsDigit(char c) { //System.out.println("Checking digit for"+c); return Character.isDigit(c); } - - + + /** Recognize an Alphanumeric */ private boolean IsAlNum(char c) { return (IsAlpha(c) || IsDigit(c)); } - - - /** Recognize an Addop */ - private boolean IsAddop( char c) { - return (c =='+' || c =='-'); - } - /** Recognize White Space */ private boolean IsWhite( char c) { return (c ==' ' || c== TAB); } - - /** - * Determines special characters;primarily in use for definition of string literals - * @param c - * @return boolean - */ - private boolean IsSpecialChar(char c) { - return (c == '>' || c== '<' || c== '=' || c=='&' || c=='[' || c==']'); - } - /** Skip Over Leading White Space */ private void SkipWhite() { @@ -187,108 +170,84 @@ public class FormulaParser { GetChar(); } } - - - /** Match a Specific Input Character */ + /** + * Consumes the next input character if it is equal to the one specified otherwise throws an + * unchecked exception. This method does not consume whitespace (before or after the + * matched character). + */ private void Match(char x) { if (look != x) { - Expected("" + x + ""); - }else { - GetChar(); - SkipWhite(); + throw expected("'" + x + "'"); } + GetChar(); } - + /** Get an Identifier */ private String GetName() { StringBuffer Token = new StringBuffer(); if (!IsAlpha(look) && look != '\'') { - Expected("Name"); + throw expected("Name"); } if(look == '\'') { - Match('\''); - boolean done = look == '\''; - while(!done) - { - Token.append(Character.toUpperCase(look)); - GetChar(); - if(look == '\'') - { - Match('\''); - done = look != '\''; - } - } + Match('\''); + boolean done = look == '\''; + while(!done) + { + Token.append(look); + GetChar(); + if(look == '\'') + { + Match('\''); + done = look != '\''; + } + } } else { - while (IsAlNum(look)) { - Token.append(Character.toUpperCase(look)); - GetChar(); - } - } - SkipWhite(); - return Token.toString(); - } - - /**Get an Identifier AS IS, without stripping white spaces or - converting to uppercase; used for literals */ - private String GetNameAsIs() { - StringBuffer Token = new StringBuffer(); - - while (IsAlNum(look) || IsWhite(look) || IsSpecialChar(look)) { - Token = Token.append(look); - GetChar(); + while (IsAlNum(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(); } - - SkipWhite(); - return value.length() == 0 ? null : value.toString(); } - - - /** Output a String with Tab */ - private void Emit(String s){ - System.out.print(TAB+s); - } - /** Output a String with Tab and CRLF */ - private void EmitLn(String s) { - Emit(s); - System.out.println();; - } - /** Parse and Translate a String Identifier */ - private void Ident() { + private Ptg parseIdent() { String name; name = GetName(); if (look == '('){ //This is a function - function(name); - } else if (look == ':' || look == '.') { // this is a AreaReference + return function(name); + } + + if (look == ':' || look == '.') { // this is a AreaReference GetChar(); - + while (look == '.') { // formulas can have . or .. or ... instead of : GetChar(); } - + String first = name; String second = GetName(); - tokens.add(new AreaPtg(first+":"+second)); - } else if (look == '!') { + return new AreaPtg(first+":"+second); + } + + if (look == '!') { Match('!'); String sheetName = name; String first = GetName(); @@ -297,79 +256,78 @@ public class FormulaParser { Match(':'); String second=GetName(); if (look == '!') { - //The sheet name was included in both of the areas. Only really - //need it once - Match('!'); - String third=GetName(); - - if (!sheetName.equals(second)) - throw new RuntimeException("Unhandled double sheet reference."); - - tokens.add(new Area3DPtg(first+":"+third,externIdx)); - } else { - tokens.add(new Area3DPtg(first+":"+second,externIdx)); + //The sheet name was included in both of the areas. Only really + //need it once + Match('!'); + String third=GetName(); + + if (!sheetName.equals(second)) + throw new RuntimeException("Unhandled double sheet reference."); + + return new Area3DPtg(first+":"+third,externIdx); } - } else { - tokens.add(new Ref3DPtg(first,externIdx)); + return new Area3DPtg(first+":"+second,externIdx); } - } else { - // This can be either a cell ref or a named range - // Try to spot which it is - boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches(); - boolean boolLit = (name.equals("TRUE") || name.equals("FALSE")); - - if (boolLit) { - tokens.add(new BoolPtg(name)); - } else if (cellRef) { - tokens.add(new ReferencePtg(name)); - } else { - boolean nameRecordExists = false; - for(int i = 0; i < book.getNumNames(); i++) { - // Our formula will by now contain an upper-cased - // version of any named range names - if(book.getNameRecord(i).getNameText().equalsIgnoreCase(name)) { - nameRecordExists = true; - } - } - if(!nameRecordExists) - Abort("Found reference to named range \"" + name + "\", but that named range wasn't defined!"); - tokens.add(new NamePtg(name, book)); + 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 + boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches(); + + if (cellRef) { + return new ReferencePtg(name); + } + + for(int i = 0; i < book.getNumNames(); i++) { + // named range name matching is case insensitive + if(book.getNameRecord(i).getNameText().equalsIgnoreCase(name)) { + return new NamePtg(name, book); } } + throw new FormulaParseException("Found reference to named range \"" + + name + "\", but that named range wasn't defined!"); } - + /** * Adds a pointer to the last token to the latest function argument list. * @param obj */ - private void addArgumentPointer() { - if (this.functionTokens.size() > 0) { - //no bounds check because this method should not be called unless a token array is setup by function() - List arguments = (List)this.functionTokens.get(0); - arguments.add(tokens.get(tokens.size()-1)); - } + private void addArgumentPointer(List argumentPointers) { + argumentPointers.add(tokens.get(tokens.size()-1)); } - - private void function(String name) { - //average 2 args per function - this.functionTokens.add(0, new ArrayList(2)); - + + /** + * 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 Ptg function(String name) { + int numArgs =0 ; + // Note regarding parameter - + if(!AbstractFunctionPtg.isInternalFunctionName(name)) { + // external functions get a Name token which points to a defined name record + NamePtg nameToken = new NamePtg(name, this.book); + + // in the token tree, the name is more or less the first argument + numArgs++; + tokens.add(nameToken); + } + //average 2 args per function + List argumentPointers = new ArrayList(2); + Match('('); - int numArgs = Arguments(); + numArgs += Arguments(argumentPointers); Match(')'); - - AbstractFunctionPtg functionPtg = getFunction(name,(byte)numArgs); - - tokens.add(functionPtg); - - if (functionPtg.getName().equals("externalflag")) { - tokens.add(new NamePtg(name, this.book)); - } - //remove what we just put in - this.functionTokens.remove(0); + return getFunction(name, numArgs, argumentPointers); } - + /** * Adds the size of all the ptgs after the provided index (inclusive). *

@@ -378,17 +336,17 @@ public class FormulaParser { * @return int */ private int getPtgSize(int index) { - int count = 0; - - Iterator ptgIterator = tokens.listIterator(index); - while (ptgIterator.hasNext()) { - Ptg ptg = (Ptg)ptgIterator.next(); - count+=ptg.getSize(); - } - - return count; + int count = 0; + + Iterator ptgIterator = tokens.listIterator(index); + while (ptgIterator.hasNext()) { + Ptg ptg = (Ptg)ptgIterator.next(); + count+=ptg.getSize(); + } + + return count; } - + private int getPtgSize(int start, int end) { int count = 0; int index = start; @@ -398,390 +356,430 @@ public class FormulaParser { count+=ptg.getSize(); index++; } - + return count; } /** * Generates the variable function ptg for the formula. *

- * For IF Formulas, additional PTGs are added to the tokens + * For IF Formulas, additional PTGs are added to the tokens * @param name * @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 AbstractFunctionPtg getFunction(String name, byte numArgs) { - AbstractFunctionPtg retval = null; - - if (name.equals("IF")) { - retval = new FuncVarPtg(AbstractFunctionPtg.ATTR_NAME, numArgs); - - //simulated pop, no bounds checking because this list better be populated by function() - List argumentPointers = (List)this.functionTokens.get(0); - - - AttrPtg ifPtg = new AttrPtg(); - ifPtg.setData((short)7); //mirroring excel output - ifPtg.setOptimizedIf(true); - - if (argumentPointers.size() != 2 && argumentPointers.size() != 3) { - throw new IllegalArgumentException("["+argumentPointers.size()+"] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]"); - } - - //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are - //tracked in the argument pointers - //The beginning first argument pointer is the last ptg of the condition - int ifIndex = tokens.indexOf(argumentPointers.get(0))+1; - tokens.add(ifIndex, ifPtg); - - //we now need a goto ptgAttr to skip to the end of the formula after a true condition - //the true condition is should be inserted after the last ptg in the first argument - - int gotoIndex = tokens.indexOf(argumentPointers.get(1))+1; - - AttrPtg goto1Ptg = new AttrPtg(); - goto1Ptg.setGoto(true); - - - tokens.add(gotoIndex, goto1Ptg); - - - if (numArgs > 2) { //only add false jump if there is a false condition - - //second goto to skip past the function ptg - AttrPtg goto2Ptg = new AttrPtg(); - goto2Ptg.setGoto(true); - goto2Ptg.setData((short)(retval.getSize()-1)); - //Page 472 of the Microsoft Excel Developer's kit states that: - //The b(or w) field specifies the number byes (or words to skip, minus 1 - - tokens.add(goto2Ptg); //this goes after all the arguments are defined - } - - //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit) - //count the number of bytes after the ifPtg to the False Subexpression - //doesn't specify -1 in the documentation - ifPtg.setData((short)(getPtgSize(ifIndex+1, gotoIndex))); - - //count all the additional (goto) ptgs but dont count itself - int ptgCount = this.getPtgSize(gotoIndex)-goto1Ptg.getSize()+retval.getSize(); - if (ptgCount > (int)Short.MAX_VALUE) { - throw new RuntimeException("Ptg Size exceeds short when being specified for a goto ptg in an if"); - } - - goto1Ptg.setData((short)(ptgCount-1)); - - } else { - - retval = new FuncVarPtg(name,numArgs); + private AbstractFunctionPtg getFunction(String name, int numArgs, List argumentPointers) { + + AbstractFunctionPtg retval = new FuncVarPtg(name, (byte)numArgs); + if (!name.equals(AbstractFunctionPtg.FUNCTION_NAME_IF)) { + // early return for everything else besides IF() + return retval; } - + + + AttrPtg ifPtg = new AttrPtg(); + ifPtg.setData((short)7); //mirroring excel output + ifPtg.setOptimizedIf(true); + + if (argumentPointers.size() != 2 && argumentPointers.size() != 3) { + throw new IllegalArgumentException("["+argumentPointers.size()+"] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]"); + } + + //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are + //tracked in the argument pointers + //The beginning first argument pointer is the last ptg of the condition + int ifIndex = tokens.indexOf(argumentPointers.get(0))+1; + tokens.add(ifIndex, ifPtg); + + //we now need a goto ptgAttr to skip to the end of the formula after a true condition + //the true condition is should be inserted after the last ptg in the first argument + + int gotoIndex = tokens.indexOf(argumentPointers.get(1))+1; + + AttrPtg goto1Ptg = new AttrPtg(); + goto1Ptg.setGoto(true); + + + tokens.add(gotoIndex, goto1Ptg); + + + if (numArgs > 2) { //only add false jump if there is a false condition + + //second goto to skip past the function ptg + AttrPtg goto2Ptg = new AttrPtg(); + goto2Ptg.setGoto(true); + goto2Ptg.setData((short)(retval.getSize()-1)); + //Page 472 of the Microsoft Excel Developer's kit states that: + //The b(or w) field specifies the number byes (or words to skip, minus 1 + + tokens.add(goto2Ptg); //this goes after all the arguments are defined + } + + //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit) + //count the number of bytes after the ifPtg to the False Subexpression + //doesn't specify -1 in the documentation + ifPtg.setData((short)(getPtgSize(ifIndex+1, gotoIndex))); + + //count all the additional (goto) ptgs but dont count itself + int ptgCount = this.getPtgSize(gotoIndex)-goto1Ptg.getSize()+retval.getSize(); + if (ptgCount > Short.MAX_VALUE) { + throw new RuntimeException("Ptg Size exceeds short when being specified for a goto ptg in an if"); + } + + goto1Ptg.setData((short)(ptgCount-1)); + return retval; } + + private static boolean isArgumentDelimiter(char ch) { + return ch == ',' || ch == ')'; + } /** get arguments to a function */ - private int Arguments() { - int numArgs = 0; - if (look != ')') { - numArgs++; - Expression(); - addArgumentPointer(); + private int Arguments(List argumentPointers) { + SkipWhite(); + if(look == ')') { + return 0; } - while (look == ',' || look == ';') { //TODO handle EmptyArgs - if(look == ',') { - Match(','); - } - else { - Match(';'); + + boolean missedPrevArg = true; + + int numArgs = 0; + while(true) { + SkipWhite(); + if(isArgumentDelimiter(look)) { + if(missedPrevArg) { + tokens.add(new MissingArgPtg()); + addArgumentPointer(argumentPointers); + numArgs++; + } + if(look == ')') { + break; + } + Match(','); + missedPrevArg = true; + continue; } - Expression(); - addArgumentPointer(); + comparisonExpression(); + addArgumentPointer(argumentPointers); numArgs++; + missedPrevArg = false; } return numArgs; } /** Parse and Translate a Math Factor */ - private void Factor() { - if (look == '-') - { - Match('-'); - Factor(); - tokens.add(new UnaryMinusPtg()); - } - else if (look == '+') { - Match('+'); - Factor(); - tokens.add(new UnaryPlusPtg()); + private void powerFactor() { + percentFactor(); + while(true) { + SkipWhite(); + if(look != '^') { + return; + } + Match('^'); + percentFactor(); + tokens.add(new PowerPtg()); } - else if (look == '(' ) { - Match('('); - Expression(); - Match(')'); - tokens.add(new ParenthesisPtg()); - } else if (IsAlpha(look) || look == '\''){ - Ident(); - } else if(look == '"') { - StringLiteral(); - } else if (look == ')' || look == ',') { - tokens.add(new MissingArgPtg()); - } else { - String number2 = null; - String exponent = null; - String number1 = GetNum(); - - if (look == '.') { - GetChar(); - number2 = GetNum(); + } + + private void percentFactor() { + tokens.add(parseSimpleFactor()); + while(true) { + SkipWhite(); + if(look != '%') { + return; } - - if (look == 'E') { + Match('%'); + tokens.add(new PercentPtg()); + } + } + + + /** + * factors (without ^ or % ) + */ + private Ptg parseSimpleFactor() { + SkipWhite(); + switch(look) { + case '#': + return parseErrorLiteral(); + case '-': + Match('-'); + powerFactor(); + return new UnaryMinusPtg(); + case '+': + Match('+'); + powerFactor(); + return new UnaryPlusPtg(); + case '(': + Match('('); + comparisonExpression(); + Match(')'); + return new ParenthesisPtg(); + case '"': + return parseStringLiteral(); + case ',': + case ')': + return new MissingArgPtg(); // TODO - not quite the right place to recognise a missing arg + } + if (IsAlpha(look) || look == '\''){ + return parseIdent(); + } + // else - assume number + return parseNumber(); + } + + + 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(); - - String sign = ""; - if (look == '+') { - GetChar(); - } else if (look == '-') { - GetChar(); - sign = "-"; - } - - String number = GetNum(); - if (number == null) { - Expected("Integer"); - } - exponent = sign + number; + } else if (look == '-') { + GetChar(); + sign = "-"; } - - if (number1 == null && number2 == null) { - Expected("Integer"); + + String number = GetNum(); + if (number == null) { + throw expected("Integer"); } - - tokens.add(getNumberPtgFromString(number1, number2, exponent)); + exponent = sign + number; } + + if (number1 == null && number2 == null) { + throw expected("Integer"); + } + + return getNumberPtgFromString(number1, number2, exponent); } - - /** - * Get a PTG for an integer from its string representation. - * return Int or Number Ptg based on size of input - */ - private Ptg getNumberPtgFromString(String number1, String number2, String exponent) { + + + private ErrPtg parseErrorLiteral() { + Match('#'); + String part1 = GetName().toUpperCase(); + + switch(part1.charAt(0)) { + case 'V': + if(part1.equals("VALUE")) { + Match('!'); + return ErrPtg.VALUE_INVALID; + } + throw expected("#VALUE!"); + case 'R': + if(part1.equals("REF")) { + Match('!'); + return ErrPtg.REF_INVALID; + } + throw expected("#REF!"); + case 'D': + if(part1.equals("DIV")) { + Match('/'); + Match('0'); + Match('!'); + return ErrPtg.DIV_ZERO; + } + throw expected("#DIV/0!"); + case 'N': + if(part1.equals("NAME")) { + Match('?'); // only one that ends in '?' + return ErrPtg.NAME_INVALID; + } + if(part1.equals("NUM")) { + Match('!'); + return ErrPtg.NUM_ERROR; + } + if(part1.equals("NULL")) { + Match('!'); + return ErrPtg.NULL_INTERSECTION; + } + if(part1.equals("N")) { + Match('/'); + if(look != 'A' && look != 'a') { + throw expected("#N/A"); + } + Match(look); + // Note - no '!' or '?' suffix + return ErrPtg.N_A; + } + 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); - } - + + if (number2 == null) { + number.append(number1); + + if (exponent != null) { + number.append('E'); + number.append(exponent); + } + String numberStr = number.toString(); - + int intVal; try { - return new IntPtg(numberStr); + intVal = Integer.parseInt(numberStr); } catch (NumberFormatException e) { return new NumberPtg(numberStr); } - } else { - if (number1 != null) { - number.append(number1); - } - - number.append('.'); - number.append(number2); - - if (exponent != null) { - number.append('E'); - number.append(exponent); + if (IntPtg.isInRange(intVal)) { + return new IntPtg(intVal); } - - return new NumberPtg(number.toString()); - } - } - - - private void StringLiteral() - { - // Can't use match here 'cuz it consumes whitespace - // which we need to preserve inside the string. - // - pete - // Match('"'); - if (look != '"') - Expected("\""); - else - { - GetChar(); - StringBuffer Token = new StringBuffer(); - for (;;) - { - if (look == '"') - { - GetChar(); - SkipWhite(); //potential white space here since it doesnt matter up to the operator - if (look == '"') - Token.append("\""); - else - break; - } - else if (look == 0) - { - break; - } - else - { - Token.append(look); - GetChar(); - } - } - tokens.add(new StringPtg(Token.toString())); - } - } - - /** Recognize and Translate a Multiply */ - private void Multiply(){ - Match('*'); - Factor(); - tokens.add(new MultiplyPtg()); - - } - - - /** Recognize and Translate a Divide */ - private void Divide() { - Match('/'); - Factor(); - tokens.add(new DividePtg()); + 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()); } - - - /** Parse and Translate a Math Term */ - private void Term(){ - Factor(); - while (look == '*' || look == '/' || look == '^' || look == '&') { + + + private StringPtg parseStringLiteral() + { + Match('"'); - ///TODO do we need to do anything here?? - if (look == '*') Multiply(); - else if (look == '/') Divide(); - else if (look == '^') Power(); - else if (look == '&') Concat(); + StringBuffer token = new StringBuffer(); + while (true) { + if (look == '"') { + GetChar(); + if (look != '"') { + break; + } + } + token.append(look); + GetChar(); } + return new StringPtg(token.toString()); } - - /** Recognize and Translate an Add */ - private void Add() { - Match('+'); - Term(); - tokens.add(new AddPtg()); + + /** Parse and Translate a Math Term */ + private void Term() { + powerFactor(); + while(true) { + SkipWhite(); + switch(look) { + case '*': + Match('*'); + powerFactor(); + tokens.add(new MultiplyPtg()); + continue; + case '/': + Match('/'); + powerFactor(); + tokens.add(new DividePtg()); + continue; + } + return; // finished with Term + } } - /** Recognize and Translate a Concatination */ - private void Concat() { - Match('&'); - Term(); - tokens.add(new ConcatPtg()); + private void comparisonExpression() { + concatExpression(); + while (true) { + SkipWhite(); + switch(look) { + case '=': + case '>': + case '<': + Ptg comparisonToken = getComparisonToken(); + concatExpression(); + tokens.add(comparisonToken); + continue; + } + return; // finished with predicate expression + } } - - /** Recognize and Translate a test for Equality */ - private void Equal() { - Match('='); - Expression(); - tokens.add(new EqualPtg()); + + private Ptg getComparisonToken() { + if(look == '=') { + Match(look); + return new EqualPtg(); + } + boolean isGreater = look == '>'; + Match(look); + if(isGreater) { + if(look == '=') { + Match('='); + return new GreaterEqualPtg(); + } + return new GreaterThanPtg(); + } + switch(look) { + case '=': + Match('='); + return new LessEqualPtg(); + case '>': + Match('>'); + return new NotEqualPtg(); + } + return new LessThanPtg(); } - /** Recognize and Translate a Subtract */ - private void Subtract() { - Match('-'); - Term(); - tokens.add(new SubtractPtg()); - } - private void Power() { - Match('^'); - Term(); - tokens.add(new PowerPtg()); + private void concatExpression() { + additiveExpression(); + while (true) { + SkipWhite(); + if(look != '&') { + break; // finished with concat expression + } + Match('&'); + additiveExpression(); + tokens.add(new ConcatPtg()); + } } - + /** Parse and Translate an Expression */ - private void Expression() { + private void additiveExpression() { Term(); - while (IsAddop(look)) { - if (look == '+' ) Add(); - else if (look == '-') Subtract(); + while (true) { + SkipWhite(); + switch(look) { + case '+': + Match('+'); + Term(); + tokens.add(new AddPtg()); + continue; + case '-': + Match('-'); + Term(); + tokens.add(new SubtractPtg()); + continue; + } + return; // finished with additive expression } - - /* - * This isn't quite right since it would allow multiple comparison operators. - */ - - if(look == '=' || look == '>' || look == '<') { - if (look == '=') Equal(); - else if (look == '>') GreaterThan(); - else if (look == '<') LessThan(); - return; - } - - - } - - /** Recognize and Translate a Greater Than */ - private void GreaterThan() { - Match('>'); - if(look == '=') - GreaterEqual(); - else { - Expression(); - tokens.add(new GreaterThanPtg()); - } } - - /** Recognize and Translate a Less Than */ - private void LessThan() { - Match('<'); - if(look == '=') - LessEqual(); - else if(look == '>') - NotEqual(); - else { - Expression(); - tokens.add(new LessThanPtg()); - } - - } - - /** - * Recognize and translate Greater than or Equal - * - */ - private void GreaterEqual() { - Match('='); - Expression(); - tokens.add(new GreaterEqualPtg()); - } - - /** - * Recognize and translate Less than or Equal - * - */ - - private void LessEqual() { - Match('='); - Expression(); - tokens.add(new LessEqualPtg()); - } - - /** - * Recognize and not Equal - * - */ - - private void NotEqual() { - Match('>'); - Expression(); - tokens.add(new NotEqualPtg()); - } - + //{--------------------------------------------------------------} //{ Parse and Translate an Assignment Statement } /** @@ -794,48 +792,46 @@ begin end; **/ - - - /** Initialize */ - - private void init() { - GetChar(); - SkipWhite(); - } - + + /** API call to execute the parsing of the formula * */ public void parse() { - synchronized (tokens) { - init(); - Expression(); + pointer=0; + GetChar(); + 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 + + /** 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) { Node node = createTree(); setRootLevelRVA(node, formulaType); setParameterRVA(node,formulaType); return (Ptg[]) tokens.toArray(new Ptg[0]); } - + private void setRootLevelRVA(Node n, int formulaType) { //Pg 16, excelfileformat.pdf @ openoffice.org - Ptg p = (Ptg) n.getValue(); + Ptg p = n.getValue(); if (formulaType == FormulaParser.FORMULA_TYPE_NAMEDRANGE) { if (p.getDefaultOperandClass() == Ptg.CLASS_REF) { setClass(n,Ptg.CLASS_REF); @@ -845,9 +841,9 @@ end; } else { setClass(n,Ptg.CLASS_VALUE); } - + } - + private void setParameterRVA(Node n, int formulaType) { Ptg p = n.getValue(); int numOperands = n.getNumChildren(); @@ -863,11 +859,11 @@ end; for (int i =0;i 0; j--) { - //TODO: catch stack underflow and throw parse exception. - operands[j - 1] = (String) stack.pop(); - } - - stack.push(o.toFormulaString(operands)); - if (!(o instanceof AbstractFunctionPtg)) continue; - final AbstractFunctionPtg f = (AbstractFunctionPtg) o; - final String fname = f.getName(); - if (fname == null) continue; + final OperationPtg o = (OperationPtg) ptg; + int nOperands = o.getNumberOfOperands(); + final String[] operands = new String[nOperands]; - if ((ifptg != null) && (fname.equals("specialflag"))) { - // this special case will be way different. - stack.push(ifptg.toFormulaString(new String[]{(String) stack.pop()})); - continue; - } - if (fname.equals("externalflag")) { - final String top = (String) stack.pop(); - final int paren = top.indexOf('('); - final int comma = top.indexOf(','); - if (comma == -1) { - final int rparen = top.indexOf(')'); - stack.push(top.substring(paren + 1, rparen) + "()"); - } - else { - stack.push(top.substring(paren + 1, comma) + '(' + - top.substring(comma + 1)); + for (int j = nOperands-1; j >= 0; j--) { + if(stack.isEmpty()) { + //TODO: write junit to prove this works + String msg = "Too few arguments suppled to operation token (" + + o.getClass().getName() + "). Expected (" + nOperands + + " but got " + (nOperands - j + 1); + throw new FormulaParseException(msg); + } + operands[j] = (String) stack.pop(); } + stack.push(o.toFormulaString(operands)); } - } - // TODO: catch stack underflow and throw parse exception. - return (String) stack.pop(); + if(stack.isEmpty()) { + // inspection of the code above reveals that every stack.pop() is followed by a + // stack.push(). So this is either an internal error or impossible. + throw new IllegalStateException("Stack underflow"); + } + String result = (String) stack.pop(); + if(!stack.isEmpty()) { + // Might be caused by some tokens like AttrPtg and Mem*Ptg, which really shouldn't + // put anything on the stack + throw new IllegalStateException("too much stuff left on the stack"); + } + return result; } /** * Static method to convert an array of Ptgs in RPN order @@ -1013,7 +1014,7 @@ end; * @return a human readable String */ public String toFormulaString(Ptg[] ptgs) { - return toFormulaString(book, ptgs); + return toFormulaString(book, ptgs); } @@ -1021,19 +1022,19 @@ end; *used to run the class(RVA) change algo */ private Node createTree() { - java.util.Stack stack = new java.util.Stack(); + Stack stack = new Stack(); int numPtgs = tokens.size(); OperationPtg o; int numOperands; Node[] operands; for (int i=0;i + * + * The main data of all types of references is stored in the Link Table inside the Workbook Globals + * Substream (4.2.5). The Link Table itself is optional and occurs only, if there are any + * references in the document. + *

+ * + * In BIFF8 the Link Table consists of + *

    + *
  • one or more EXTERNALBOOK Blocks

    + * each consisting of + *

      + *
    • exactly one EXTERNALBOOK (0x01AE) record
    • + *
    • zero or more EXTERNALNAME (0x0023) records
    • + *
    • zero or more CRN Blocks

      + * each consisting of + *

        + *
      • exactly one XCT (0x0059)record
      • + *
      • zero or more CRN (0x005A) records (documentation says one or more)
      • + *
      + *
    • + *
    + *
  • + *
  • exactly one EXTERNSHEET (0x0017) record
  • + *
  • zero or more DEFINEDNAME (0x0018) records
  • + *
+ * + * + * @author Josh Micich + */ +final class LinkTable { + + private static final class CRNBlock { + + private final CRNCountRecord _countRecord; + private final CRNRecord[] _crns; + + public CRNBlock(RecordStream rs) { + _countRecord = (CRNCountRecord) rs.getNext(); + int nCRNs = _countRecord.getNumberOfCRNs(); + CRNRecord[] crns = new CRNRecord[nCRNs]; + for (int i = 0; i < crns.length; i++) { + crns[i] = (CRNRecord) rs.getNext(); + } + _crns = crns; + } + public CRNRecord[] getCrns() { + return (CRNRecord[]) _crns.clone(); + } + } + + private static final class ExternalBookBlock { + private final SupBookRecord _externalBookRecord; + private final ExternalNameRecord[] _externalNameRecords; + private final CRNBlock[] _crnBlocks; + + public ExternalBookBlock(RecordStream rs) { + _externalBookRecord = (SupBookRecord) rs.getNext(); + List temp = new ArrayList(); + while(rs.peekNextClass() == ExternalNameRecord.class) { + temp.add(rs.getNext()); + } + _externalNameRecords = new ExternalNameRecord[temp.size()]; + temp.toArray(_externalNameRecords); + + temp.clear(); + + while(rs.peekNextClass() == CRNCountRecord.class) { + temp.add(new CRNBlock(rs)); + } + _crnBlocks = new CRNBlock[temp.size()]; + temp.toArray(_crnBlocks); + } + + public ExternalBookBlock(short numberOfSheets) { + _externalBookRecord = SupBookRecord.createInternalReferences(numberOfSheets); + _externalNameRecords = new ExternalNameRecord[0]; + _crnBlocks = new CRNBlock[0]; + } + + public SupBookRecord getExternalBookRecord() { + return _externalBookRecord; + } + + public String getNameText(int definedNameIndex) { + return _externalNameRecords[definedNameIndex].getText(); + } + } + + private final ExternalBookBlock[] _externalBookBlocks; + private final ExternSheetRecord _externSheetRecord; + private final List _definedNames; + private final int _recordCount; + private final WorkbookRecordList _workbookRecordList; // TODO - would be nice to remove this + + public LinkTable(List inputList, int startIndex, WorkbookRecordList workbookRecordList) { + + _workbookRecordList = workbookRecordList; + RecordStream rs = new RecordStream(inputList, startIndex); + + List temp = new ArrayList(); + while(rs.peekNextClass() == SupBookRecord.class) { + temp.add(new ExternalBookBlock(rs)); + } + if(temp.size() < 1) { + throw new RuntimeException("Need at least one EXTERNALBOOK blocks"); + } + _externalBookBlocks = new ExternalBookBlock[temp.size()]; + temp.toArray(_externalBookBlocks); + temp.clear(); + + // If link table is present, there is always 1 of ExternSheetRecord + Record next = rs.getNext(); + _externSheetRecord = (ExternSheetRecord)next; + _definedNames = new ArrayList(); + // collect zero or more DEFINEDNAMEs id=0x18 + while(rs.peekNextClass() == NameRecord.class) { + NameRecord nr = (NameRecord)rs.getNext(); + _definedNames.add(nr); + } + + _recordCount = rs.getCountRead(); + _workbookRecordList.getRecords().addAll(inputList.subList(startIndex, startIndex + _recordCount)); + } + + public LinkTable(short numberOfSheets, WorkbookRecordList workbookRecordList) { + _workbookRecordList = workbookRecordList; + _definedNames = new ArrayList(); + _externalBookBlocks = new ExternalBookBlock[] { + new ExternalBookBlock(numberOfSheets), + }; + _externSheetRecord = new ExternSheetRecord(); + _recordCount = 2; + + // tell _workbookRecordList about the 2 new records + + SupBookRecord supbook = _externalBookBlocks[0].getExternalBookRecord(); + + int idx = findFirstRecordLocBySid(CountryRecord.sid); + if(idx < 0) { + throw new RuntimeException("CountryRecord not found"); + } + _workbookRecordList.add(idx+1, _externSheetRecord); + _workbookRecordList.add(idx+1, supbook); + } + + /** + * TODO - would not be required if calling code used RecordStream or similar + */ + public int getRecordCount() { + return _recordCount; + } + + + public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex) { + + Iterator iterator = _definedNames.iterator(); + while (iterator.hasNext()) { + NameRecord record = ( NameRecord ) iterator.next(); + + //print areas are one based + if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) { + return record; + } + } + + return null; + } + + public void removeBuiltinRecord(byte name, int sheetIndex) { + //the name array is smaller so searching through it should be faster than + //using the findFirstXXXX methods + NameRecord record = getSpecificBuiltinRecord(name, sheetIndex); + if (record != null) { + _definedNames.remove(record); + } + // TODO - do we need "Workbook.records.remove(...);" similar to that in Workbook.removeName(int namenum) {}? + } + + public int getNumNames() { + return _definedNames.size(); + } + + public NameRecord getNameRecord(int index) { + return (NameRecord) _definedNames.get(index); + } + + public void addName(NameRecord name) { + _definedNames.add(name); + + // TODO - this is messy + // Not the most efficient way but the other way was causing too many bugs + int idx = findFirstRecordLocBySid(ExternSheetRecord.sid); + if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid); + if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid); + int countNames = _definedNames.size(); + _workbookRecordList.add(idx+countNames, name); + + } + + public void removeName(int namenum) { + _definedNames.remove(namenum); + } + + public short getIndexToSheet(short num) { + return _externSheetRecord.getREFRecordAt(num).getIndexToFirstSupBook(); + } + + public int getSheetIndexFromExternSheetIndex(int externSheetNumber) { + if (externSheetNumber >= _externSheetRecord.getNumOfREFStructures()) { + return -1; + } + return _externSheetRecord.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook(); + } + + public short addSheetIndexToExternSheet(short sheetNumber) { + + ExternSheetSubRecord record = new ExternSheetSubRecord(); + record.setIndexToFirstSupBook(sheetNumber); + record.setIndexToLastSupBook(sheetNumber); + _externSheetRecord.addREFRecord(record); + _externSheetRecord.setNumOfREFStructures((short)(_externSheetRecord.getNumOfREFStructures() + 1)); + return (short)(_externSheetRecord.getNumOfREFStructures() - 1); + } + + public short checkExternSheet(int sheetNumber) { + + //Trying to find reference to this sheet + int nESRs = _externSheetRecord.getNumOfREFStructures(); + for(short i=0; i< nESRs; i++) { + ExternSheetSubRecord esr = _externSheetRecord.getREFRecordAt(i); + + if (esr.getIndexToFirstSupBook() == sheetNumber + && esr.getIndexToLastSupBook() == sheetNumber){ + return i; + } + } + + //We Haven't found reference to this sheet + return addSheetIndexToExternSheet((short) sheetNumber); + } + + + /** + * copied from Workbook + */ + private int findFirstRecordLocBySid(short sid) { + int index = 0; + for (Iterator iterator = _workbookRecordList.iterator(); iterator.hasNext(); ) { + Record record = ( Record ) iterator.next(); + + if (record.getSid() == sid) { + return index; + } + index ++; + } + return -1; + } + + public int getNumberOfREFStructures() { + return _externSheetRecord.getNumOfREFStructures(); + } + + public String resolveNameXText(int refIndex, int definedNameIndex) { + short extBookIndex = _externSheetRecord.getREFRecordAt(refIndex).getIndexToSupBook(); + return _externalBookBlocks[extBookIndex].getNameText(definedNameIndex); + } +} diff --git a/src/java/org/apache/poi/hssf/model/RecordStream.java b/src/java/org/apache/poi/hssf/model/RecordStream.java new file mode 100755 index 0000000000..03177c7c22 --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/RecordStream.java @@ -0,0 +1,65 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.model; + +import java.util.List; + +import org.apache.poi.hssf.record.Record; +/** + * Simplifies iteration over a sequence of Record objects. + * + * @author Josh Micich + */ +final class RecordStream { + + private final List _list; + private int _nextIndex; + private int _countRead; + + public RecordStream(List inputList, int startIndex) { + _list = inputList; + _nextIndex = startIndex; + _countRead = 0; + } + + public boolean hasNext() { + return _nextIndex < _list.size(); + } + + public Record getNext() { + if(_nextIndex >= _list.size()) { + throw new RuntimeException("Attempt to read past end of record stream"); + } + _countRead ++; + return (Record) _list.get(_nextIndex++); + } + + /** + * @return the {@link Class} of the next Record. null if this stream is exhausted. + */ + public Class peekNextClass() { + if(_nextIndex >= _list.size()) { + return null; + } + return _list.get(_nextIndex).getClass(); + } + + public int getCountRead() { + return _countRead; + } +} diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index 2ba50857ca..9bf1c8f475 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -79,10 +79,8 @@ public class Workbook implements Model */ protected SSTRecord sst = null; - /** - * Holds the Extern Sheet with references to bound sheets - */ - protected ExternSheetRecord externSheet= null; + + private LinkTable linkTable; // optionally occurs if there are references in the document. (4.10.3) /** * holds the "boundsheet" records (aka bundlesheet) so that they can have their @@ -92,8 +90,6 @@ public class Workbook implements Model protected ArrayList formats = new ArrayList(); - protected ArrayList names = new ArrayList(); - protected ArrayList hyperlinks = new ArrayList(); protected int numxfs = 0; // hold the number of extended format records @@ -134,6 +130,7 @@ public class Workbook implements Model new Integer(recs.size())); Workbook retval = new Workbook(); ArrayList records = new ArrayList(recs.size() / 3); + retval.records.setRecords(records); int k; for (k = 0; k < recs.size(); k++) { @@ -192,21 +189,16 @@ public class Workbook implements Model retval.records.setBackuppos( k ); break; case ExternSheetRecord.sid : - if (log.check( POILogger.DEBUG )) - log.log(DEBUG, "found extern sheet record at " + k); - retval.externSheet = ( ExternSheetRecord ) rec; - break; + throw new RuntimeException("Extern sheet is part of LinkTable"); case NameRecord.sid : - if (log.check( POILogger.DEBUG )) - log.log(DEBUG, "found name record at " + k); - retval.names.add(rec); - // retval.records.namepos = k; - break; + throw new RuntimeException("DEFINEDNAME is part of LinkTable"); case SupBookRecord.sid : if (log.check( POILogger.DEBUG )) log.log(DEBUG, "found SupBook record at " + k); + retval.linkTable = new LinkTable(recs, k, retval.records); // retval.records.supbookpos = k; - break; + k+=retval.linkTable.getRecordCount() - 1; + continue; case FormatRecord.sid : if (log.check( POILogger.DEBUG )) log.log(DEBUG, "found format record at " + k); @@ -262,8 +254,6 @@ public class Workbook implements Model break; } } - - retval.records.setRecords(records); if (retval.windowOne == null) { retval.windowOne = (WindowOneRecord) retval.createWindowOne(); @@ -283,6 +273,7 @@ public class Workbook implements Model log.log( DEBUG, "creating new workbook from scratch" ); Workbook retval = new Workbook(); ArrayList records = new ArrayList( 30 ); + retval.records.setRecords(records); ArrayList formats = new ArrayList( 8 ); records.add( retval.createBOF() ); @@ -339,8 +330,9 @@ public class Workbook implements Model records.add( retval.createStyle( k ) ); } records.add( retval.createUseSelFS() ); - for ( int k = 0; k < 1; k++ ) - { // now just do 1 + + int nBoundSheets = 1; // now just do 1 + for ( int k = 0; k < nBoundSheets; k++ ) { BoundSheetRecord bsr = (BoundSheetRecord) retval.createBoundSheet( k ); @@ -351,12 +343,14 @@ public class Workbook implements Model // retval.records.supbookpos = retval.records.bspos + 1; // retval.records.namepos = retval.records.supbookpos + 2; records.add( retval.createCountry() ); + for ( int k = 0; k < nBoundSheets; k++ ) { + retval.getOrCreateLinkTable().checkExternSheet(k); + } retval.sst = (SSTRecord) retval.createSST(); records.add( retval.sst ); records.add( retval.createExtendedSST() ); records.add( retval.createEOF() ); - retval.records.setRecords(records); if (log.check( POILogger.DEBUG )) log.log( DEBUG, "exit create new workbook from scratch" ); return retval; @@ -369,36 +363,20 @@ public class Workbook implements Model * @param sheetIndex Index to match * @return null if no builtin NameRecord matches */ - public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex) - { - Iterator iterator = names.iterator(); - while (iterator.hasNext()) { - NameRecord record = ( NameRecord ) iterator.next(); - - //print areas are one based - if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) { - return record; - } - } - - return null; - - } + public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex) + { + return getOrCreateLinkTable().getSpecificBuiltinRecord(name, sheetIndex); + } /** * Removes the specified Builtin NameRecord that matches the name and index * @param name byte representation of the builtin to match * @param sheetIndex zero-based sheet reference */ - public void removeBuiltinRecord(byte name, int sheetIndex) { - //the name array is smaller so searching through it should be faster than - //using the findFirstXXXX methods - NameRecord record = getSpecificBuiltinRecord(name, sheetIndex); - if (record != null) { - names.remove(record); - } - - } + public void removeBuiltinRecord(byte name, int sheetIndex) { + linkTable.removeBuiltinRecord(name, sheetIndex); + // TODO - do we need "this.records.remove(...);" similar to that in this.removeName(int namenum) {}? + } public int getNumRecords() { return records.size(); @@ -614,6 +592,7 @@ public class Workbook implements Model records.add(records.getBspos()+1, bsr); records.setBspos( records.getBspos() + 1 ); boundsheets.add(bsr); + getOrCreateLinkTable().checkExternSheet(sheetnum); fixTabIdRecord(); } } @@ -1824,14 +1803,26 @@ public class Workbook implements Model protected Record createEOF() { return new EOFRecord(); } + + /** + * lazy initialization + * Note - creating the link table causes creation of 1 EXTERNALBOOK and 1 EXTERNALSHEET record + */ + private LinkTable getOrCreateLinkTable() { + if(linkTable == null) { + linkTable = new LinkTable((short) getNumSheets(), records); + } + return linkTable; + } public SheetReferences getSheetReferences() { SheetReferences refs = new SheetReferences(); - if (externSheet != null) { - for (int k = 0; k < externSheet.getNumOfREFStructures(); k++) { + if (linkTable != null) { + int numRefStructures = linkTable.getNumberOfREFStructures(); + for (short k = 0; k < numRefStructures; k++) { - String sheetName = findSheetNameFromExternSheet((short)k); + String sheetName = findSheetNameFromExternSheet(k); refs.addSheetReference(sheetName, k); } @@ -1846,7 +1837,8 @@ public class Workbook implements Model public String findSheetNameFromExternSheet(short num){ String result=""; - short indexToSheet = externSheet.getREFRecordAt(num).getIndexToFirstSupBook(); + short indexToSheet = linkTable.getIndexToSheet(num); + if (indexToSheet>-1) { //error check, bail out gracefully! result = getSheetName(indexToSheet); } @@ -1861,10 +1853,7 @@ public class Workbook implements Model */ public int getSheetIndexFromExternSheetIndex(int externSheetNumber) { - if (externSheetNumber >= externSheet.getNumOfREFStructures()) - return -1; - else - return externSheet.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook(); + return linkTable.getSheetIndexFromExternSheetIndex(externSheetNumber); } /** returns the extern sheet number for specific sheet number , @@ -1873,58 +1862,17 @@ public class Workbook implements Model * @return index to extern sheet */ public short checkExternSheet(int sheetNumber){ - - int i = 0; - boolean flag = false; - short result = 0; - - if (externSheet == null) { - externSheet = createExternSheet(); - } - - //Trying to find reference to this sheet - while (i < externSheet.getNumOfREFStructures() && !flag){ - ExternSheetSubRecord record = externSheet.getREFRecordAt(i); - - if (record.getIndexToFirstSupBook() == sheetNumber && - record.getIndexToLastSupBook() == sheetNumber){ - flag = true; - result = (short) i; - } - - ++i; - } - - //We Havent found reference to this sheet - if (!flag) { - result = addSheetIndexToExternSheet((short) sheetNumber); - } - - return result; + return getOrCreateLinkTable().checkExternSheet(sheetNumber); } - private short addSheetIndexToExternSheet(short sheetNumber){ - short result; - - ExternSheetSubRecord record = new ExternSheetSubRecord(); - record.setIndexToFirstSupBook(sheetNumber); - record.setIndexToLastSupBook(sheetNumber); - externSheet.addREFRecord(record); - externSheet.setNumOfREFStructures((short)(externSheet.getNumOfREFStructures() + 1)); - result = (short)(externSheet.getNumOfREFStructures() - 1); - - return result; - } - - - /** gets the total number of names * @return number of names */ public int getNumNames(){ - int result = names.size(); - - return result; + if(linkTable == null) { + return 0; + } + return linkTable.getNumNames(); } /** gets the name record @@ -1932,28 +1880,14 @@ public class Workbook implements Model * @return name record */ public NameRecord getNameRecord(int index){ - NameRecord result = (NameRecord) names.get(index); - - return result; - + return linkTable.getNameRecord(index); } /** creates new name * @return new name record */ public NameRecord createName(){ - - NameRecord name = new NameRecord(); - - // Not the most efficient way but the other way was causing too many bugs - int idx = findFirstRecordLocBySid(ExternSheetRecord.sid); - if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid); - if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid); - - records.add(idx+names.size()+1, name); - names.add(name); - - return name; + return addName(new NameRecord()); } @@ -1962,67 +1896,41 @@ public class Workbook implements Model */ public NameRecord addName(NameRecord name) { - // Not the most efficient way but the other way was causing too many bugs - int idx = findFirstRecordLocBySid(ExternSheetRecord.sid); - if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid); - if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid); - records.add(idx+names.size()+1, name); - names.add(name); + + getOrCreateLinkTable().addName(name); return name; } - /**Generates a NameRecord to represent a built-in region - * @return a new NameRecord unless the index is invalid - */ - public NameRecord createBuiltInName(byte builtInName, int index) - { - if (index == -1 || index+1 > (int)Short.MAX_VALUE) - throw new IllegalArgumentException("Index is not valid ["+index+"]"); - - NameRecord name = new NameRecord(builtInName, (short)(index)); - - addName(name); - - return name; - } + /**Generates a NameRecord to represent a built-in region + * @return a new NameRecord unless the index is invalid + */ + public NameRecord createBuiltInName(byte builtInName, int index) + { + if (index == -1 || index+1 > Short.MAX_VALUE) + throw new IllegalArgumentException("Index is not valid ["+index+"]"); + + NameRecord name = new NameRecord(builtInName, (short)(index)); + + addName(name); + + return name; + } /** removes the name * @param namenum name index */ public void removeName(int namenum){ - if (names.size() > namenum) { + + if (linkTable.getNumNames() > namenum) { int idx = findFirstRecordLocBySid(NameRecord.sid); records.remove(idx + namenum); - names.remove(namenum); + linkTable.removeName(namenum); } } - /** creates a new extern sheet record - * @return the new extern sheet record - */ - protected ExternSheetRecord createExternSheet(){ - ExternSheetRecord externSheet = new ExternSheetRecord(); - - int idx = findFirstRecordLocBySid(CountryRecord.sid); - - records.add(idx+1, externSheet); -// records.add(records.supbookpos + 1 , rec); - - //We also adds the supBook for internal reference - SupBookRecord supbook = new SupBookRecord(); - - supbook.setNumberOfSheets((short)getNumSheets()); - //supbook.setFlag(); - - records.add(idx+1, supbook); -// records.add(records.supbookpos + 1 , supbook); - - return externSheet; - } - /** * Returns a format index that matches the passed in format. It does not tie into HSSFDataFormat. * @param format the format string @@ -2419,5 +2327,14 @@ public class Workbook implements Model writeProtect = null; } + /** + * @param refIndex Index to REF entry in EXTERNSHEET record in the Link Table + * @param definedNameIndex zero-based to DEFINEDNAME or EXTERNALNAME record + * @return the string representation of the defined or external name + */ + public String resolveNameXText(int refIndex, int definedNameIndex) { + return linkTable.resolveNameXText(refIndex, definedNameIndex); + } } + diff --git a/src/java/org/apache/poi/hssf/record/CRNCountRecord.java b/src/java/org/apache/poi/hssf/record/CRNCountRecord.java new file mode 100755 index 0000000000..4c9e4425cd --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/CRNCountRecord.java @@ -0,0 +1,94 @@ +/* ==================================================================== + 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; + +import org.apache.poi.util.LittleEndian; +/** + * XCT – CRN Count

+ * + * REFERENCE: 5.114

+ * + * @author Josh Micich + */ +public final class CRNCountRecord extends Record { + public final static short sid = 0x59; + + private static final short BASE_RECORD_SIZE = 4; + + + private int field_1_number_crn_records; + private int field_2_sheet_table_index; + + public CRNCountRecord() { + throw new RuntimeException("incomplete code"); + } + + public CRNCountRecord(RecordInputStream in) { + super(in); + } + + protected void validateSid(short id) { + if (id != sid) { + throw new RecordFormatException("NOT An XCT RECORD"); + } + } + + public int getNumberOfCRNs() { + return field_1_number_crn_records; + } + + + protected void fillFields(RecordInputStream in) { + field_1_number_crn_records = in.readShort(); + if(field_1_number_crn_records < 0) { + // TODO - seems like the sign bit of this field might be used for some other purpose + // see example file for test case "TestBugs.test19599()" + field_1_number_crn_records = (short)-field_1_number_crn_records; + } + field_2_sheet_table_index = in.readShort(); + } + + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()).append(" [XCT"); + sb.append(" nCRNs=").append(field_1_number_crn_records); + sb.append(" sheetIx=").append(field_2_sheet_table_index); + sb.append("]"); + return sb.toString(); + } + + public int serialize(int offset, byte [] data) { + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, BASE_RECORD_SIZE); + LittleEndian.putShort(data, 4 + offset, (short)field_1_number_crn_records); + LittleEndian.putShort(data, 6 + offset, (short)field_2_sheet_table_index); + return getRecordSize(); + } + + public int getRecordSize() { + return BASE_RECORD_SIZE + 4; + } + + /** + * return the non static version of the id for this record. + */ + public short getSid() { + return sid; + } +} diff --git a/src/java/org/apache/poi/hssf/record/CRNRecord.java b/src/java/org/apache/poi/hssf/record/CRNRecord.java new file mode 100755 index 0000000000..73b9e42dfa --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/CRNRecord.java @@ -0,0 +1,99 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record; + +import org.apache.poi.hssf.record.constant.ConstantValueParser; +import org.apache.poi.util.LittleEndian; + +/** + * Title: CRN

+ * Description: This record stores the contents of an external cell or cell range

+ * REFERENCE: 5.23

+ * + * @author josh micich + */ +public final class CRNRecord extends Record { + public final static short sid = 0x5A; + + private int field_1_last_column_index; + private int field_2_first_column_index; + private int field_3_row_index; + private Object[] field_4_constant_values; + + public CRNRecord() { + throw new RuntimeException("incomplete code"); + } + + public CRNRecord(RecordInputStream in) { + super(in); + } + + protected void validateSid(short id) { + if (id != sid) { + throw new RecordFormatException("NOT An XCT RECORD"); + } + } + + public int getNumberOfCRNs() { + return field_1_last_column_index; + } + + + protected void fillFields(RecordInputStream in) { + field_1_last_column_index = in.readByte() & 0x00FF; + field_2_first_column_index = in.readByte() & 0x00FF; + field_3_row_index = in.readShort(); + int nValues = field_1_last_column_index - field_2_first_column_index + 1; + field_4_constant_values = ConstantValueParser.parse(in, nValues); + } + + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()).append(" [CRN"); + sb.append(" rowIx=").append(field_3_row_index); + sb.append(" firstColIx=").append(field_2_first_column_index); + sb.append(" lastColIx=").append(field_1_last_column_index); + sb.append("]"); + return sb.toString(); + } + private int getDataSize() { + return 4 + ConstantValueParser.getEncodedSize(field_4_constant_values); + } + + public int serialize(int offset, byte [] data) { + int dataSize = getDataSize(); + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, (short) dataSize); + LittleEndian.putByte(data, 4 + offset, field_1_last_column_index); + LittleEndian.putByte(data, 5 + offset, field_2_first_column_index); + LittleEndian.putShort(data, 6 + offset, (short) field_3_row_index); + return getRecordSize(); + } + + public int getRecordSize() { + return getDataSize() + 4; + } + + /** + * return the non static version of the id for this record. + */ + public short getSid() { + return sid; + } +} diff --git a/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java b/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java new file mode 100755 index 0000000000..771603c859 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java @@ -0,0 +1,179 @@ +/* ==================================================================== + 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; + +import java.util.List; +import java.util.Stack; + +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.StringUtil; + +/** + * EXTERNALNAME

+ * + * @author josh micich + */ +public final class ExternalNameRecord extends Record { + + public final static short sid = 0x23; // as per BIFF8. (some old versions used 0x223) + + + private static final int OPT_BUILTIN_NAME = 0x0001; + private static final int OPT_AUTOMATIC_LINK = 0x0002; + private static final int OPT_PICTURE_LINK = 0x0004; + private static final int OPT_STD_DOCUMENT_NAME = 0x0008; + private static final int OPT_OLE_LINK = 0x0010; +// private static final int OPT_CLIP_FORMAT_MASK = 0x7FE0; + private static final int OPT_ICONIFIED_PICTURE_LINK = 0x8000; + + + private short field_1_option_flag; + private short field_2_index; + private short field_3_not_used; + private String field_4_name; + private Stack field_5_name_definition; + + + public ExternalNameRecord(RecordInputStream in) { + super(in); + } + + /** + * Convenience Function to determine if the name is a built-in name + */ + public boolean isBuiltInName() { + return (field_1_option_flag & OPT_BUILTIN_NAME) != 0; + } + /** + * For OLE and DDE, links can be either 'automatic' or 'manual' + */ + public boolean isAutomaticLink() { + return (field_1_option_flag & OPT_AUTOMATIC_LINK) != 0; + } + /** + * only for OLE and DDE + */ + public boolean isPicureLink() { + return (field_1_option_flag & OPT_PICTURE_LINK) != 0; + } + /** + * DDE links only. If true, this denotes the 'StdDocumentName' + */ + public boolean isStdDocumentNameIdentifier() { + return (field_1_option_flag & OPT_STD_DOCUMENT_NAME) != 0; + } + public boolean isOLELink() { + return (field_1_option_flag & OPT_OLE_LINK) != 0; + } + public boolean isIconifiedPictureLink() { + return (field_1_option_flag & OPT_ICONIFIED_PICTURE_LINK) != 0; + } + /** + * @return the standard String representation of this name + */ + public String getText() { + return field_4_name; + } + + + /** + * called by constructor, should throw runtime exception in the event of a + * record passed with a differing ID. + * + * @param id alleged id for this record + */ + protected void validateSid(short id) { + if (id != sid) { + throw new RecordFormatException("NOT A valid ExternalName RECORD"); + } + } + + private int getDataSize(){ + return 2 + 2 + field_4_name.length() + 2 + getNameDefinitionSize(); + } + + /** + * called by the class that is responsible for writing this sucker. + * Subclasses should implement this so that their data is passed back in a + * byte array. + * + * @param offset to begin writing at + * @param data byte array containing instance data + * @return number of bytes written + */ + public int serialize( int offset, byte[] data ) { + // TODO - junit tests + int dataSize = getDataSize(); + + LittleEndian.putShort( data, 0 + offset, sid ); + LittleEndian.putShort( data, 2 + offset, (short) dataSize ); + LittleEndian.putShort( data, 4 + offset, field_1_option_flag ); + LittleEndian.putShort( data, 6 + offset, field_2_index ); + LittleEndian.putShort( data, 8 + offset, field_3_not_used ); + short nameLen = (short) field_4_name.length(); + LittleEndian.putShort( data, 10 + offset, nameLen ); + StringUtil.putCompressedUnicode( field_4_name, data, 10 + offset ); + short defLen = (short) getNameDefinitionSize(); + LittleEndian.putShort( data, 12 + nameLen + offset, defLen ); + Ptg.serializePtgStack(field_5_name_definition, data, 12 + nameLen + offset ); + return dataSize + 4; + } + + private int getNameDefinitionSize() { + int result = 0; + List list = field_5_name_definition; + + for (int k = 0; k < list.size(); k++) + { + Ptg ptg = ( Ptg ) list.get(k); + + result += ptg.getSize(); + } + return result; + } + + + public int getRecordSize(){ + return 6 + 2 + field_4_name.length() + 2 + getNameDefinitionSize(); + } + + + protected void fillFields(RecordInputStream in) { + field_1_option_flag = in.readShort(); + field_2_index = in.readShort(); + field_3_not_used = in.readShort(); + short nameLength = in.readShort(); + field_4_name = in.readCompressedUnicode(nameLength); + short formulaLen = in.readShort(); + field_5_name_definition = Ptg.createParsedExpressionTokens(formulaLen, in); + } + + public short getSid() { + return sid; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()).append(" [EXTERNALNAME "); + sb.append(" ").append(field_4_name); + sb.append(" ix=").append(field_2_index); + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java index 0f164b4473..f247a8d4b9 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java @@ -77,7 +77,11 @@ public class RecordFactory NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class, FileSharingRecord.class, ChartTitleFormatRecord.class, DVRecord.class, DVALRecord.class, UncalcedRecord.class, - HyperlinkRecord.class + HyperlinkRecord.class, + ExternalNameRecord.class, // TODO - same changes in non-@deprecated version of this class + SupBookRecord.class, + CRNCountRecord.class, + CRNRecord.class, }; } private static Map recordsMap = recordsToMap(records); diff --git a/src/java/org/apache/poi/hssf/record/SupBookRecord.java b/src/java/org/apache/poi/hssf/record/SupBookRecord.java index 91a16f0793..6755aa6f8b 100644 --- a/src/java/org/apache/poi/hssf/record/SupBookRecord.java +++ b/src/java/org/apache/poi/hssf/record/SupBookRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,72 +14,159 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record; +import org.apache.poi.hssf.record.UnicodeString.UnicodeRecordStats; import org.apache.poi.util.LittleEndian; /** - * Title: Sup Book

- * Description: A Extrenal Workbook Deciption (Sup Book) + * Title: Sup Book (EXTERNALBOOK)

+ * Description: A External Workbook Description (Suplemental Book) * Its only a dummy record for making new ExternSheet Record

- * REFERENCE:

+ * REFERENCE: 5.38

* @author Libin Roman (Vista Portal LDT. Developer) * @author Andrew C. Oliver (acoliver@apache.org) * */ -public class SupBookRecord extends Record -{ +public final class SupBookRecord extends Record { + public final static short sid = 0x1AE; - private short field_1_number_of_sheets; - private short field_2_flag; + private static final short SMALL_RECORD_SIZE = 4; + private static final short TAG_INTERNAL_REFERENCES = 0x0401; + private static final short TAG_ADD_IN_FUNCTIONS = 0x3A01; - public SupBookRecord() - { - setFlag((short)0x401); + private short field_1_number_of_sheets; + private UnicodeString field_2_encoded_url; + private UnicodeString[] field_3_sheet_names; + private boolean _isAddInFunctions; + + + public static SupBookRecord createInternalReferences(short numberOfSheets) { + return new SupBookRecord(false, numberOfSheets); + } + public static SupBookRecord createAddInFunctions() { + return new SupBookRecord(true, (short)0); + } + public static SupBookRecord createExternalReferences(UnicodeString url, UnicodeString[] sheetNames) { + return new SupBookRecord(url, sheetNames); + } + private SupBookRecord(boolean isAddInFuncs, short numberOfSheets) { + // else not 'External References' + field_1_number_of_sheets = numberOfSheets; + field_2_encoded_url = null; + field_3_sheet_names = null; + _isAddInFunctions = isAddInFuncs; + } + public SupBookRecord(UnicodeString url, UnicodeString[] sheetNames) { + field_1_number_of_sheets = (short) sheetNames.length; + field_2_encoded_url = url; + field_3_sheet_names = sheetNames; + _isAddInFunctions = false; } /** * Constructs a Extern Sheet record and sets its fields appropriately. * - * @param in the RecordInputstream to read the record from + * @param id id must be 0x16 or an exception will be throw upon validation + * @param size the size of the data area of the record + * @param data data of the record (should not contain sid/len) */ - public SupBookRecord(RecordInputStream in) - { + public SupBookRecord(RecordInputStream in) { super(in); } - protected void validateSid(short id) - { - if (id != sid) - { - throw new RecordFormatException("NOT An Supbook RECORD"); + protected void validateSid(short id) { + if (id != sid) { + throw new RecordFormatException("NOT An ExternSheet RECORD"); } } + public boolean isExternalReferences() { + return field_3_sheet_names != null; + } + public boolean isInternalReferences() { + return field_3_sheet_names == null && !_isAddInFunctions; + } + public boolean isAddInFunctions() { + return field_3_sheet_names == null && _isAddInFunctions; + } /** - * @param in the RecordInputstream to read the record from + * called by the constructor, should set class level fields. Should throw + * runtime exception for bad/incomplete data. + * + * @param data raw data + * @param size size of data + * @param offset of the record's data (provided a big array of the file) */ - protected void fillFields(RecordInputStream in) - { - //For now We use it only for one case - //When we need to add an named range when no named ranges was - //before it + protected void fillFields(RecordInputStream in) { field_1_number_of_sheets = in.readShort(); - field_2_flag = in.readShort(); - } - + + if(in.getLength() > SMALL_RECORD_SIZE) { + // 5.38.1 External References + _isAddInFunctions = false; + + field_2_encoded_url = in.readUnicodeString(); + UnicodeString[] sheetNames = new UnicodeString[field_1_number_of_sheets]; + for (int i = 0; i < sheetNames.length; i++) { + sheetNames[i] = in.readUnicodeString(); + } + field_3_sheet_names = sheetNames; + return; + } + // else not 'External References' + field_2_encoded_url = null; + field_3_sheet_names = null; + + short nextShort = in.readShort(); + if(nextShort == TAG_INTERNAL_REFERENCES) { + // 5.38.2 'Internal References' + _isAddInFunctions = false; + } else if(nextShort == TAG_ADD_IN_FUNCTIONS) { + // 5.38.3 'Add-In Functions' + _isAddInFunctions = true; + if(field_1_number_of_sheets != 1) { + throw new RuntimeException("Expected 0x0001 for number of sheets field in 'Add-In Functions' but got (" + + field_1_number_of_sheets + ")"); + } + } else { + throw new RuntimeException("invalid EXTERNALBOOK code (" + + Integer.toHexString(nextShort) + ")"); + } + } - public String toString() - { - StringBuffer buffer = new StringBuffer(); - buffer.append("[SUPBOOK]\n"); - buffer.append("numberosheets = ").append(getNumberOfSheets()).append('\n'); - buffer.append("flag = ").append(getFlag()).append('\n'); - buffer.append("[/SUPBOOK]\n"); - return buffer.toString(); + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()).append(" [SUPBOOK "); + + if(isExternalReferences()) { + sb.append("External References"); + sb.append(" nSheets=").append(field_1_number_of_sheets); + sb.append(" url=").append(field_2_encoded_url); + } else if(_isAddInFunctions) { + sb.append("Add-In Functions"); + } else { + sb.append("Internal References "); + sb.append(" nSheets= ").append(field_1_number_of_sheets); + } + return sb.toString(); + } + private int getDataSize() { + if(!isExternalReferences()) { + return SMALL_RECORD_SIZE; + } + int sum = 2; // u16 number of sheets + UnicodeRecordStats urs = new UnicodeRecordStats(); + field_2_encoded_url.getRecordSize(urs); + sum += urs.recordSize; + + for(int i=0; i + * TODO - code in ArrayPtg should be merged with this code. It currently supports only 2 of the constant types + * + * @author Josh Micich + */ +public final class ConstantValueParser { + // note - value 3 seems to be unused + private static final int TYPE_EMPTY = 0; + private static final int TYPE_NUMBER = 1; + private static final int TYPE_STRING = 2; + private static final int TYPE_BOOLEAN = 4; + + private static final int TRUE_ENCODING = 1; + private static final int FALSE_ENCODING = 0; + + // TODO - is this the best way to represent 'EMPTY'? + private static final Object EMPTY_REPRESENTATION = null; + + private ConstantValueParser() { + // no instances of this class + } + + public static Object[] parse(RecordInputStream in, int nValues) { + Object[] result = new Object[nValues]; + for (int i = 0; i < result.length; i++) { + result[i] = readAConstantValue(in); + } + return result; + } + + private static Object readAConstantValue(RecordInputStream in) { + byte grbit = in.readByte(); + switch(grbit) { + case TYPE_EMPTY: + in.readLong(); // 8 byte 'not used' field + return EMPTY_REPRESENTATION; + case TYPE_NUMBER: + return new Double(in.readDouble()); + case TYPE_STRING: + return in.readUnicodeString(); + case TYPE_BOOLEAN: + return readBoolean(in); + } + return null; + } + + private static Object readBoolean(RecordInputStream in) { + byte val = in.readByte(); + in.readLong(); // 8 byte 'not used' field + switch(val) { + case FALSE_ENCODING: + return Boolean.FALSE; + case TRUE_ENCODING: + return Boolean.TRUE; + } + // Don't tolerate unusual boolean encoded values (unless it becomes evident that they occur) + throw new RuntimeException("unexpected boolean encoding (" + val + ")"); + } + + public static int getEncodedSize(Object[] values) { + // start with one byte 'type' code for each value + int result = values.length * 1; + for (int i = 0; i < values.length; i++) { + result += getEncodedSize(values[i]); + } + return 0; + } + + /** + * @return encoded size without the 'type' code byte + */ + private static int getEncodedSize(Object object) { + if(object == EMPTY_REPRESENTATION) { + return 8; + } + Class cls = object.getClass(); + if(cls == Boolean.class || cls == Double.class) { + return 8; + } + UnicodeString strVal = (UnicodeString)object; + UnicodeRecordStats urs = new UnicodeRecordStats(); + strVal.getRecordSize(urs); + return urs.recordSize; + } +} diff --git a/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java b/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java index 555c502949..c7c57280d3 100644 --- a/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java @@ -29,10 +29,13 @@ import org.apache.poi.hssf.model.Workbook; * @author Andrew C. Oliver (acoliver at apache dot org) */ public abstract class AbstractFunctionPtg extends OperationPtg { - //constant used allow a ptgAttr to be mapped properly for its functionPtg - public static final String ATTR_NAME = "specialflag"; - - public static final short INDEX_EXTERNAL = 255; + + /** + * The name of the IF function (i.e. "IF"). Extracted as a constant for clarity. + */ + public static final String FUNCTION_NAME_IF = "IF"; + /** All external functions have function index 255 */ + private static final short FUNCTION_INDEX_EXTERNAL = 255; private static BinaryTree map = produceHash(); protected static Object[][] functionData = produceFunctionData(); @@ -66,6 +69,13 @@ public abstract class AbstractFunctionPtg extends OperationPtg { public String getName() { return lookupName(field_2_fnc_index); } + /** + * external functions get some special processing + * @return true if this is an external function + */ + public boolean isExternalFunction() { + return field_2_fnc_index == FUNCTION_INDEX_EXTERNAL; + } public String toFormulaString(Workbook book) { return getName(); @@ -73,39 +83,57 @@ public abstract class AbstractFunctionPtg extends OperationPtg { public String toFormulaString(String[] operands) { StringBuffer buf = new StringBuffer(); - - if (field_2_fnc_index != 1) { - buf.append(getName()); - buf.append('('); - } - if (operands.length >0) { - for (int i=0;ifirstArgIx) { + buf.append(','); + } + buf.append(operands[i]); + } + buf.append(")"); + } public abstract void writeBytes(byte[] array, int offset); public abstract int getSize(); - - + /** + * Used to detect whether a function name found in a formula is one of the standard excel functions + *

+ * The name matching is case insensitive. + * @return true if the name specifies a standard worksheet function, + * false if the name should be assumed to be an external function. + */ + public static final boolean isInternalFunctionName(String name) { + return map.containsValue(name.toUpperCase()); + } protected String lookupName(short index) { return ((String)map.get(new Integer(index))); } - protected short lookupIndex(String name) { - Integer index = (Integer) map.getKeyForValue(name); + /** + * Resolves internal function names into function indexes. + *

+ * The name matching is case insensitive. + * @return the standard worksheet function index if found, otherwise FUNCTION_INDEX_EXTERNAL + */ + protected static short lookupIndex(String name) { + Integer index = (Integer) map.getKeyForValue(name.toUpperCase()); if (index != null) return index.shortValue(); - return INDEX_EXTERNAL; + return FUNCTION_INDEX_EXTERNAL; } /** @@ -115,7 +143,7 @@ public abstract class AbstractFunctionPtg extends OperationPtg { BinaryTree dmap = new BinaryTree(); dmap.put(new Integer(0),"COUNT"); - dmap.put(new Integer(1),"specialflag"); + dmap.put(new Integer(1),FUNCTION_NAME_IF); dmap.put(new Integer(2),"ISNA"); dmap.put(new Integer(3),"ISERROR"); dmap.put(new Integer(4),"SUM"); @@ -354,7 +382,7 @@ public abstract class AbstractFunctionPtg extends OperationPtg { dmap.put(new Integer(252),"FREQUENCY"); dmap.put(new Integer(253),"ADDTOOLBAR"); dmap.put(new Integer(254),"DELETETOOLBAR"); - dmap.put(new Integer(255),"externalflag"); + dmap.put(new Integer(FUNCTION_INDEX_EXTERNAL),"externalflag"); dmap.put(new Integer(256),"RESETTOOLBAR"); dmap.put(new Integer(257),"EVALUATE"); dmap.put(new Integer(258),"GETTOOLBAR"); diff --git a/src/java/org/apache/poi/hssf/record/formula/IntPtg.java b/src/java/org/apache/poi/hssf/record/formula/IntPtg.java index 257c089df8..f4106b6aa8 100644 --- a/src/java/org/apache/poi/hssf/record/formula/IntPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/IntPtg.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -16,12 +15,6 @@ limitations under the License. ==================================================================== */ - -/* - * IntPtg.java - * - * Created on October 29, 2001, 7:37 PM - */ package org.apache.poi.hssf.record.formula; import org.apache.poi.util.LittleEndian; @@ -29,64 +22,45 @@ import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.record.RecordInputStream; /** - * Integer (unsigned short intger) + * Integer (unsigned short integer) * Stores an unsigned short value (java int) in a formula * @author Andrew C. Oliver (acoliver at apache dot org) * @author Jason Height (jheight at chariot dot net dot au) */ +public final class IntPtg extends Ptg { + // 16 bit unsigned integer + private static final int MIN_VALUE = 0x0000; + private static final int MAX_VALUE = 0xFFFF; + + /** + * Excel represents integers 0..65535 with the tInt token. + * @return true if the specified value is within the range of values + * IntPtg can represent. + */ + public static boolean isInRange(int i) { + return i>=MIN_VALUE && i <=MAX_VALUE; + } -public class IntPtg - extends Ptg -{ public final static int SIZE = 3; public final static byte sid = 0x1e; private int field_1_value; - private IntPtg() { - //Required for clone methods + public IntPtg(RecordInputStream in) { + this(in.readUShort()); } - public IntPtg(RecordInputStream in) - { - setValue(in.readUShort()); - } - - - // IntPtg should be able to create itself, shouldnt have to call setValue - public IntPtg(String formulaToken) { - setValue(Integer.parseInt(formulaToken)); - } - /** - * Sets the wrapped value. - * Normally you should call with a positive int. - */ - public void setValue(int value) - { - if(value < 0 || value > (Short.MAX_VALUE + 1)*2 ) - throw new IllegalArgumentException("Unsigned short is out of range: " + value); + public IntPtg(int value) { + if(!isInRange(value)) { + throw new IllegalArgumentException("value is out of range: " + value); + } field_1_value = value; } - /** - * Returns the value as a short, which may have - * been wrapped into negative numbers - */ - public int getValue() - { + public int getValue() { return field_1_value; } - /** - * Returns the value as an unsigned positive int. - */ - public int getValueAsInt() - { - if(field_1_value < 0) { - return (Short.MAX_VALUE + 1)*2 + field_1_value; - } - return field_1_value; - } public void writeBytes(byte [] array, int offset) { @@ -94,20 +68,25 @@ public class IntPtg LittleEndian.putUShort(array, offset + 1, getValue()); } - public int getSize() - { + public int getSize() { return SIZE; } - public String toFormulaString(Workbook book) - { - return "" + getValue(); + public String toFormulaString(Workbook book) { + return String.valueOf(getValue()); + } + public byte getDefaultOperandClass() { + return Ptg.CLASS_VALUE; } - public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;} - public Object clone() { - IntPtg ptg = new IntPtg(); - ptg.field_1_value = field_1_value; - return ptg; - } + public Object clone() { + return new IntPtg(field_1_value); + } + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(field_1_value); + sb.append("]"); + return sb.toString(); + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/NamePtg.java b/src/java/org/apache/poi/hssf/record/formula/NamePtg.java index 01323c3a01..5405481a09 100644 --- a/src/java/org/apache/poi/hssf/record/formula/NamePtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/NamePtg.java @@ -33,6 +33,7 @@ public class NamePtg { public final static short sid = 0x23; private final static int SIZE = 5; + /** one-based index to defined name record */ private short field_1_label_index; private short field_2_zero; // reserved must be 0 boolean xtra=false; @@ -42,24 +43,32 @@ public class NamePtg //Required for clone methods } - /** Creates new NamePtg */ - - public NamePtg(String name, Workbook book) - { - final short n = (short) (book.getNumNames() + 1); + /** + * Creates new NamePtg and sets its name index to that of the corresponding defined name record + * in the workbook. The search for the name record is case insensitive. If it is not found, + * it gets created. + */ + public NamePtg(String name, Workbook book) { + field_1_label_index = (short)(1+getOrCreateNameRecord(book, name)); // convert to 1-based + } + /** + * @return zero based index of the found or newly created defined name record. + */ + private static final int getOrCreateNameRecord(Workbook book, String name) { + // perhaps this logic belongs in Workbook + int countNames = book.getNumNames(); NameRecord rec; - for (short i = 1; i < n; i++) { - rec = book.getNameRecord(i - 1); + for (int i = 0; i < countNames; i++) { + rec = book.getNameRecord(i); if (name.equalsIgnoreCase(rec.getNameText())) { - field_1_label_index = i; - return; + return i; } } rec = new NameRecord(); rec.setNameText(name); rec.setNameTextLength((byte) name.length()); book.addName(rec); - field_1_label_index = n; + return countNames; } /** Creates new NamePtg */ @@ -71,6 +80,13 @@ public class NamePtg field_2_zero = in.readShort(); //if (data[offset+6]==0) xtra=true; } + + /** + * @return zero based index to a defined name record in the LinkTable + */ + public int getIndex() { + return field_1_label_index-1; // convert to zero based + } public void writeBytes(byte [] array, int offset) { diff --git a/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java b/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java index b1f280a017..ccf5ab6fcd 100644 --- a/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java @@ -25,13 +25,11 @@ import org.apache.poi.hssf.record.RecordInputStream; * * @author aviks */ - -public class NameXPtg extends Ptg -{ +public final class NameXPtg extends Ptg { public final static short sid = 0x39; private final static int SIZE = 7; - private short field_1_ixals; // index to externsheet record - private short field_2_ilbl; //index to name or externname table(1 based) + private short field_1_ixals; // index to REF entry in externsheet record + private short field_2_ilbl; //index to defined name or externname table(1 based) private short field_3_reserved; // reserved must be 0 @@ -41,13 +39,6 @@ public class NameXPtg extends Ptg /** Creates new NamePtg */ - public NameXPtg(String name) - { - //TODO - } - - /** Creates new NamePtg */ - public NameXPtg(RecordInputStream in) { field_1_ixals = in.readShort(); @@ -72,7 +63,8 @@ public class NameXPtg extends Ptg public String toFormulaString(Workbook book) { - return "NO IDEA - NAME"; + // -1 to convert definedNameIndex from 1-based to zero-based + return book.resolveNameXText(field_1_ixals, field_2_ilbl-1); } public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 05cee4e8d2..17bae63ca7 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -639,7 +639,7 @@ public class HSSFCell //only set to default if there is no extended format index already set if (rec.getXFIndex() == (short)0) rec.setXFIndex(( short ) 0x0f); - FormulaParser fp = new FormulaParser(formula+";",book); + FormulaParser fp = new FormulaParser(formula, book); fp.parse(); Ptg[] ptg = fp.getRPNPtg(); int size = 0; diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index 9f7b17ad83..8c7590d4eb 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -400,7 +400,7 @@ public class HSSFSheet //formula fields ( size and data ) String str_formula = obj_validation.getFirstFormula(); - FormulaParser fp = new FormulaParser(str_formula+";",book); + FormulaParser fp = new FormulaParser(str_formula, book); fp.parse(); Stack ptg_arr = new Stack(); Ptg[] ptg = fp.getRPNPtg(); @@ -424,7 +424,7 @@ public class HSSFSheet if ( obj_validation.getSecondFormula() != null ) { str_formula = obj_validation.getSecondFormula(); - fp = new FormulaParser(str_formula+";",book); + fp = new FormulaParser(str_formula, book); fp.parse(); ptg_arr = new Stack(); ptg = fp.getRPNPtg(); diff --git a/src/java/org/apache/poi/util/LittleEndian.java b/src/java/org/apache/poi/util/LittleEndian.java index b883d71b18..2278649cb8 100644 --- a/src/java/org/apache/poi/util/LittleEndian.java +++ b/src/java/org/apache/poi/util/LittleEndian.java @@ -245,6 +245,16 @@ public class LittleEndian putNumber(data, offset, value, SHORT_SIZE); } + /** + * executes:

+ * + * data[offset] = (byte)value; + *

+ * Added for consistency with other put~() methods + */ + public static void putByte(byte[] data, int offset, int value) { + putNumber(data, offset, value, LittleEndianConsts.BYTE_SIZE); + } /** * put a array of shorts into a byte array diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AddEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AddEval.java index 5bc0d3e9a9..6562263d54 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AddEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AddEval.java @@ -43,8 +43,6 @@ public class AddEval extends NumericOperationEval { private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED @@ -59,33 +57,31 @@ public class AddEval extends NumericOperationEval { } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - Eval retval = null; + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } + double d = 0; - switch (operands.length) { - default: // will rarely happen. currently the parser itself fails. - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 2: - for (int i = 0, iSize = 2; retval==null && i < iSize; i++) { - ValueEval ve = singleOperandEvaluate(operands[i], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d += ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } // end for inside case - } // end switch - - if (retval == null) { - retval = Double.isNaN(d) ? (ValueEval) ErrorEval.VALUE_INVALID : new NumberEval(d); + for (int i = 0; i < 2; i++) { + ValueEval ve = singleOperandEvaluate(args[i], srcRow, srcCol); + if(ve instanceof ErrorEval) { + return ve; + } + if (ve instanceof NumericValueEval) { + d += ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { + return ErrorEval.VALUE_INVALID; + } } - - return retval; + if(Double.isNaN(d) || Double.isInfinite(d)) { + return ErrorEval.NUM_ERROR; + } + return new NumberEval(d); } public int getNumberOfOperands() { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java index 2d8c58ef3e..e54cd483f1 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.ConcatPtg; @@ -27,7 +24,7 @@ import org.apache.poi.hssf.record.formula.Ptg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class ConcatEval extends StringOperationEval { +public final class ConcatEval extends StringOperationEval { private ConcatPtg delegate; @@ -35,36 +32,27 @@ public class ConcatEval extends StringOperationEval { this.delegate = (ConcatPtg) ptg; } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - Eval retval = null; - StringBuffer sb = null; - - switch (operands.length) { - default: // paranoid check :) - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 2: - sb = new StringBuffer(); - for (int i = 0, iSize = 2; retval == null && i < iSize; i++) { - - ValueEval ve = singleOperandEvaluate(operands[i], srcRow, srcCol); - if (ve instanceof StringValueEval) { - StringValueEval sve = (StringValueEval) ve; - sb.append(sve.getStringValue()); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { // must be an error eval - retval = ve; - } + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 2; i++) { + + ValueEval ve = singleOperandEvaluate(args[i], srcRow, srcCol); + if (ve instanceof StringValueEval) { + StringValueEval sve = (StringValueEval) ve; + sb.append(sve.getStringValue()); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { // must be an error eval + return ve; } } - if (retval == null) { - retval = new StringEval(sb.toString()); - } - return retval; + return new StringEval(sb.toString()); } public int getNumberOfOperands() { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java index 6dd3db23de..021168ad79 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.DividePtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class DivideEval extends NumericOperationEval { +public final class DivideEval extends NumericOperationEval { private DividePtg delegate; private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED @@ -49,18 +44,28 @@ public class DivideEval extends NumericOperationEval { return NUM_XLATOR; } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } Eval retval = null; double d0 = 0; double d1 = 0; - switch (operands.length) { - default: // will rarely happen. currently the parser itself fails. - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 2: - ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); + ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d0 = ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { + retval = ErrorEval.VALUE_INVALID; + } + + if (retval == null) { // no error yet + ve = singleOperandEvaluate(args[1], srcRow, srcCol); if (ve instanceof NumericValueEval) { - d0 = ((NumericValueEval) ve).getNumberValue(); + d1 = ((NumericValueEval) ve).getNumberValue(); } else if (ve instanceof BlankEval) { // do nothing @@ -68,20 +73,7 @@ public class DivideEval extends NumericOperationEval { else { retval = ErrorEval.VALUE_INVALID; } - - if (retval == null) { // no error yet - ve = singleOperandEvaluate(operands[1], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d1 = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - } // end switch + } if (retval == null) { retval = (d1 == 0) diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java index 56e4db1b22..e8e197d201 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java @@ -48,12 +48,6 @@ public final class ErrorEval implements ValueEval { private static final int CIRCULAR_REF_ERROR_CODE = 0xFFFFFFC4; private static final int FUNCTION_NOT_IMPLEMENTED_CODE = 0xFFFFFFE2; - /** - * @deprecated do not use this error code. For conditions that should never occur, throw an - * unchecked exception. For all other situations use the error code that corresponds to the - * error Excel would have raised under the same circumstances. - */ - public static final ErrorEval UNKNOWN_ERROR = new ErrorEval(-20); public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(FUNCTION_NOT_IMPLEMENTED_CODE); // Note - Excel does not seem to represent this condition with an error code public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(CIRCULAR_REF_ERROR_CODE); diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java new file mode 100755 index 0000000000..b1d81e6524 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java @@ -0,0 +1,81 @@ +/* ==================================================================== + 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.eval; + +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +/** + * + * Common entry point for all external functions (where + * AbstractFunctionPtg.field_2_fnc_index == 255) + * + * @author Josh Micich + */ +final class ExternalFunction implements FreeRefFunction { + + public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) { + + int nIncomingArgs = args.length; + if(nIncomingArgs < 1) { + throw new RuntimeException("function name argument missing"); + } + + if (!(args[0] instanceof NameEval)) { + throw new RuntimeException("First argument should be a NameEval, but got (" + + args[0].getClass().getName() + ")"); + } + NameEval functionNameEval = (NameEval) args[0]; + + int nOutGoingArgs = nIncomingArgs -1; + Eval[] outGoingArgs = new Eval[nOutGoingArgs]; + System.arraycopy(args, 1, outGoingArgs, 0, nOutGoingArgs); + + FreeRefFunction targetFunc; + try { + targetFunc = findTargetFunction(workbook, functionNameEval); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + + return targetFunc.evaluate(outGoingArgs, srcCellRow, srcCellCol, workbook, sheet); + } + + private FreeRefFunction findTargetFunction(HSSFWorkbook workbook, NameEval functionNameEval) throws EvaluationException { + + int numberOfNames = workbook.getNumberOfNames(); + + int nameIndex = functionNameEval.getIndex(); + if(nameIndex < 0 || nameIndex >= numberOfNames) { + throw new RuntimeException("Bad name index (" + nameIndex + + "). Allowed range is (0.." + (numberOfNames-1) + ")"); + } + + String functionName = workbook.getNameName(nameIndex); + if(false) { + System.out.println("received call to external function index (" + functionName + ")"); + } + // TODO - detect if the NameRecord corresponds to a named range, function, or something undefined + // throw the right errors in these cases + + // TODO find the implementation for the external function e.g. "YEARFRAC" or "ISEVEN" + + throw new EvaluationException(ErrorEval.FUNCTION_NOT_IMPLEMENTED); + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java index d1c17b9f54..533c604a0c 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java @@ -38,7 +38,8 @@ public abstract class FunctionEval implements OperationEval { public static final int OFFSET = 78; /** 148 */ public static final int INDIRECT = 148; - + /** 255 */ + public static final int EXTERNAL_FUNC = 255; } // convenient access to namespace private static final FunctionID ID = null; @@ -51,6 +52,7 @@ public abstract class FunctionEval implements OperationEval { Map m = new HashMap(); addMapping(m, ID.OFFSET, new Offset()); addMapping(m, ID.INDIRECT, new Indirect()); + addMapping(m, ID.EXTERNAL_FUNC, new ExternalFunction()); freeRefFunctionsByIdMap = m; } private static void addMapping(Map m, int offset, FreeRefFunction frf) { @@ -316,7 +318,7 @@ public abstract class FunctionEval implements OperationEval { retval[252] = new Frequency(); // FREQUENCY retval[253] = new NotImplementedFunction(); // ADDTOOLBAR retval[254] = new NotImplementedFunction(); // DELETETOOLBAR - retval[255] = new NotImplementedFunction(); // EXTERNALFLAG + retval[ID.EXTERNAL_FUNC] = null; // ExternalFunction is a FreeREfFunction retval[256] = new NotImplementedFunction(); // RESETTOOLBAR retval[257] = new Evaluate(); // EVALUATE retval[258] = new NotImplementedFunction(); // GETTOOLBAR diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java index ecada85d56..22d87b7e4d 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.MultiplyPtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class MultiplyEval extends NumericOperationEval { +public final class MultiplyEval extends NumericOperationEval { private MultiplyPtg delegate; private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED @@ -49,46 +44,39 @@ public class MultiplyEval extends NumericOperationEval { return NUM_XLATOR; } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - Eval retval = null; + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } + double d0 = 0; double d1 = 0; - switch (operands.length) { - default: // will rarely happen. currently the parser itself fails. - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 2: - ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d0 = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - - if (retval == null) { // no error yet - ve = singleOperandEvaluate(operands[1], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d1 = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - } // end switch - - if (retval == null) { - retval = (Double.isNaN(d0) || Double.isNaN(d1)) - ? (ValueEval) ErrorEval.VALUE_INVALID - : new NumberEval(d0 * d1); + ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d0 = ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { + return ErrorEval.VALUE_INVALID; + } + + ve = singleOperandEvaluate(args[1], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d1 = ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { + return ErrorEval.VALUE_INVALID; } - return retval; + + if (Double.isNaN(d0) || Double.isNaN(d1)) { + return ErrorEval.NUM_ERROR; + } + return new NumberEval(d0 * d1); } public int getNumberOfOperands() { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java new file mode 100755 index 0000000000..682394b3c2 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java @@ -0,0 +1,48 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.eval; + +/** + * @author Josh Micich + */ +public final class NameEval implements Eval { + + private final int _index; + + /** + * @param index zero based index to a defined name record + */ + public NameEval(int index) { + _index = index; + } + + /** + * @return zero based index to a defined name record + */ + public int getIndex() { + return _index; + } + + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(_index); + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java index 75a2bc46c3..be1cda5f8e 100755 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java @@ -17,7 +17,6 @@ package org.apache.poi.hssf.record.formula.eval; - /** * Provides functionality for evaluating arguments to functions and operators. * @@ -43,12 +42,10 @@ public final class OperandResolver { */ public static ValueEval getSingleValue(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException { - if (arg instanceof RefEval) { - RefEval re = (RefEval) arg; - return re.getInnerValueEval(); - } Eval result; - if (arg instanceof AreaEval) { + if (arg instanceof RefEval) { + result = ((RefEval) arg).getInnerValueEval(); + } else if (arg instanceof AreaEval) { result = chooseSingleElementFromArea((AreaEval) arg, srcCellRow, srcCellCol); } else { result = arg; @@ -223,13 +220,26 @@ public final class OperandResolver { * Some examples:
* " 123 " -> 123.0
* ".123" -> 0.123
+ * These not supported yet:
* " $ 1,000.00 " -> 1000.0
* "$1.25E4" -> 12500.0
+ * "5**2" -> 500
+ * "250%" -> 2.5
* * @param text * @return null if the specified text cannot be parsed as a number */ - public static Double parseDouble(String text) { + public static Double parseDouble(String pText) { + String text = pText.trim(); + if(text.length() < 1) { + return null; + } + boolean isPositive = true; + if(text.charAt(0) == '-') { + isPositive = false; + text= text.substring(1).trim(); + } + if(!Character.isDigit(text.charAt(0))) { // avoid using NumberFormatException to tell when string is not a number return null; @@ -242,8 +252,26 @@ public final class OperandResolver { } catch (NumberFormatException e) { return null; } - return new Double(val); + return new Double(isPositive ? +val : -val); } - + /** + * @param ve must be a NumberEval, StringEval, BoolEval, or BlankEval + * @return the converted string value. never null + */ + public static String coerceValueToString(ValueEval ve) { + if (ve instanceof StringValueEval) { + StringValueEval sve = (StringValueEval) ve; + return sve.getStringValue(); + } + if (ve instanceof NumberEval) { + NumberEval neval = (NumberEval) ve; + return neval.getStringValue(); + } + + if (ve instanceof BlankEval) { + return ""; + } + throw new IllegalArgumentException("Unexpected eval class (" + ve.getClass().getName() + ")"); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java index 437c24e40d..651c5d2aa2 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.PowerPtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class PowerEval extends NumericOperationEval { +public final class PowerEval extends NumericOperationEval { private PowerPtg delegate; private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED @@ -49,48 +44,40 @@ public class PowerEval extends NumericOperationEval { return NUM_XLATOR; } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - Eval retval = null; + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } double d0 = 0; double d1 = 0; - switch (operands.length) { - default: // will rarely happen. currently the parser itself fails. - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 2: - ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d0 = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - - if (retval == null) { // no error yet - ve = singleOperandEvaluate(operands[1], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d1 = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - } // end switch - - if (retval == null) { - double p = Math.pow(d0, d1); - retval = (Double.isNaN(p)) - ? (ValueEval) ErrorEval.VALUE_INVALID - : new NumberEval(p); + ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d0 = ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { + return ErrorEval.VALUE_INVALID; } - return retval; + + ve = singleOperandEvaluate(args[1], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d1 = ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { + return ErrorEval.VALUE_INVALID; + } + + double p = Math.pow(d0, d1); + if (Double.isNaN(p)) { + return ErrorEval.VALUE_INVALID; + } + return new NumberEval(p); } public int getNumberOfOperands() { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java index 7b24cb062b..4dd055c6c0 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java @@ -14,47 +14,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 9, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; -import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.ReferencePtg; /** * @author adeshmukh * */ -public class Ref2DEval implements RefEval { +public final class Ref2DEval implements RefEval { - private ValueEval value; - - private ReferencePtg delegate; + private final ValueEval value; + private final ReferencePtg delegate; - private boolean evaluated; - - public Ref2DEval(Ptg ptg, ValueEval value, boolean evaluated) { - this.value = value; - this.delegate = (ReferencePtg) ptg; - this.evaluated = evaluated; + public Ref2DEval(ReferencePtg ptg, ValueEval ve) { + value = ve; + delegate = ptg; } - public ValueEval getInnerValueEval() { return value; } - public short getRow() { return delegate.getRow(); } - public short getColumn() { return delegate.getColumn(); } - - public boolean isEvaluated() { - return evaluated; - } - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java index dcf8f0f712..7ecca26788 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java @@ -14,13 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 9, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; -import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ref3DPtg; /** @@ -29,36 +25,23 @@ import org.apache.poi.hssf.record.formula.Ref3DPtg; */ public final class Ref3DEval implements RefEval { - private ValueEval value; + private final ValueEval value; + private final Ref3DPtg delegate; - private Ref3DPtg delegate; - - private boolean evaluated; - - public Ref3DEval(Ptg ptg, ValueEval value, boolean evaluated) { - this.value = value; - this.delegate = (Ref3DPtg) ptg; - this.evaluated = evaluated; + public Ref3DEval(Ref3DPtg ptg, ValueEval ve) { + value = ve; + delegate = ptg; } - public ValueEval getInnerValueEval() { return value; } - public short getRow() { return delegate.getRow(); } - public short getColumn() { return delegate.getColumn(); } - - public boolean isEvaluated() { - return evaluated; - } - public int getExternSheetIndex() { return delegate.getExternSheetIndex(); } - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java index bb72adc4a0..48a94b024e 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java @@ -14,11 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 9, 2005 - * - * - */ + package org.apache.poi.hssf.record.formula.eval; /** @@ -52,18 +48,4 @@ public interface RefEval extends ValueEval { * returns the row index. */ public short getRow(); - - /** - * returns true if this RefEval contains an - * evaluated value instead of a direct value. - * eg. say cell A1 has the value: ="test" - * Then the RefEval representing A1 will return - * isEvaluated() equal to false. On the other - * hand, say cell A1 has the value: =B1 and - * B1 has the value "test", then the RefEval - * representing A1 will return isEvaluated() - * equal to true. - */ - public boolean isEvaluated(); - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java index 4bd77029f7..85a3845299 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.SubtractPtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class SubtractEval extends NumericOperationEval { +public final class SubtractEval extends NumericOperationEval { private SubtractPtg delegate; private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED @@ -49,18 +44,28 @@ public class SubtractEval extends NumericOperationEval { return NUM_XLATOR; } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } Eval retval = null; double d0 = 0; double d1 = 0; - switch (operands.length) { - default: // will rarely happen. currently the parser itself fails. - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 2: - ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); + ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d0 = ((NumericValueEval) ve).getNumberValue(); + } + else if (ve instanceof BlankEval) { + // do nothing + } + else { + retval = ErrorEval.VALUE_INVALID; + } + + if (retval == null) { // no error yet + ve = singleOperandEvaluate(args[1], srcRow, srcCol); if (ve instanceof NumericValueEval) { - d0 = ((NumericValueEval) ve).getNumberValue(); + d1 = ((NumericValueEval) ve).getNumberValue(); } else if (ve instanceof BlankEval) { // do nothing @@ -68,21 +73,8 @@ public class SubtractEval extends NumericOperationEval { else { retval = ErrorEval.VALUE_INVALID; } - - if (retval == null) { // no error yet - ve = singleOperandEvaluate(operands[1], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d1 = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - } // end switch - + } + if (retval == null) { retval = (Double.isNaN(d0) || Double.isNaN(d1)) ? (ValueEval) ErrorEval.VALUE_INVALID diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java index b4975eefcf..ef6f533ea5 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,14 +24,12 @@ import org.apache.poi.hssf.record.formula.UnaryMinusPtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class UnaryMinusEval extends NumericOperationEval { +public final class UnaryMinusEval extends NumericOperationEval { private UnaryMinusPtg delegate; private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED @@ -49,32 +44,24 @@ public class UnaryMinusEval extends NumericOperationEval { return NUM_XLATOR; } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - ValueEval retval = null; + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if(args.length != 1) { + return ErrorEval.VALUE_INVALID; + } double d = 0; - switch (operands.length) { - default: - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 1: - ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); - if (ve instanceof NumericValueEval) { - d = ((NumericValueEval) ve).getNumberValue(); - } - else if (ve instanceof BlankEval) { - // do nothing - } - else if (ve instanceof ErrorEval) { - retval = ve; - } + ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + d = ((NumericValueEval) ve).getNumberValue(); } - - if (retval == null) { - retval = new NumberEval(-d); + else if (ve instanceof BlankEval) { + // do nothing } - - return retval; + else if (ve instanceof ErrorEval) { + return ve; + } + + return new NumberEval(-d); } public int getNumberOfOperands() { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java index 7816b228f7..edcc7bee79 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 8, 2005 - * - */ + package org.apache.poi.hssf.record.formula.eval; import org.apache.poi.hssf.record.formula.Ptg; @@ -27,114 +24,38 @@ import org.apache.poi.hssf.record.formula.UnaryPlusPtg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class UnaryPlusEval implements OperationEval /*extends NumericOperationEval*/ { +public final class UnaryPlusEval implements OperationEval { private UnaryPlusPtg delegate; - /* - * COMMENT FOR COMMENTED CODE IN THIS FILE - * - * In excel the programmer seems to not have cared to - * think about how strings were handled in other numeric - * operations when he/she was implementing this operation :P - * - * Here's what I mean: - * - * Q. If the formula -"hello" evaluates to #VALUE! in excel, what should - * the formula +"hello" evaluate to? - * - * A. +"hello" evaluates to "hello" (what the...?) - * - */ - - -// private static final ValueEvalToNumericXlator NUM_XLATOR = -// new ValueEvalToNumericXlator((short) -// ( ValueEvalToNumericXlator.BOOL_IS_PARSED -// | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED -// | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED -// | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED -// | ValueEvalToNumericXlator.STRING_IS_PARSED -// )); - - /** * called by reflection */ public UnaryPlusEval(Ptg ptg) { this.delegate = (UnaryPlusPtg) ptg; } - -// protected ValueEvalToNumericXlator getXlator() { -// return NUM_XLATOR; -// } - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - ValueEval retval = null; - - switch (operands.length) { - default: - retval = ErrorEval.UNKNOWN_ERROR; - break; - case 1: - -// ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol); -// if (ve instanceof NumericValueEval) { -// d = ((NumericValueEval) ve).getNumberValue(); -// } -// else if (ve instanceof BlankEval) { -// // do nothing -// } -// else if (ve instanceof ErrorEval) { -// retval = ve; -// } - if (operands[0] instanceof RefEval) { - RefEval re = (RefEval) operands[0]; - retval = re.getInnerValueEval(); - } - else if (operands[0] instanceof AreaEval) { - AreaEval ae = (AreaEval) operands[0]; - if (ae.contains(srcRow, srcCol)) { // circular ref! - retval = ErrorEval.CIRCULAR_REF_ERROR; - } - else if (ae.isRow()) { - if (ae.containsColumn(srcCol)) { - ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol); - if (ve instanceof RefEval) { - ve = ((RefEval) ve).getInnerValueEval(); - } - retval = ve; - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else if (ae.isColumn()) { - if (ae.containsRow(srcRow)) { - ValueEval ve = ae.getValueAt(srcRow, ae.getFirstColumn()); - if (ve instanceof RefEval) { - ve = ((RefEval) ve).getInnerValueEval(); - } - retval = ve; - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else { - retval = (ValueEval) operands[0]; - } - } - - if (retval instanceof BlankEval) { - retval = new NumberEval(0); - } - - return retval; + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + if(args.length != 1) { + return ErrorEval.VALUE_INVALID; + } + double d; + try { + ValueEval ve = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + if(ve instanceof BlankEval) { + return NumberEval.ZERO; + } + if(ve instanceof StringEval) { + // Note - asymmetric with UnaryMinus + // -"hello" evaluates to #VALUE! + // but +"hello" evaluates to "hello" + return ve; + } + d = OperandResolver.coerceValueToDouble(ve); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + return new NumberEval(+d); } public int getNumberOfOperands() { @@ -144,5 +65,4 @@ public class UnaryPlusEval implements OperationEval /*extends NumericOperationEv public int getType() { return delegate.getType(); } - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java index 5ffa2faeef..1abcf34d2b 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java @@ -24,7 +24,7 @@ package org.apache.poi.hssf.record.formula.eval; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class ValueEvalToNumericXlator { +public final class ValueEvalToNumericXlator { public static final int STRING_IS_PARSED = 0x0001; public static final int BOOL_IS_PARSED = 0x0002; @@ -34,26 +34,18 @@ public class ValueEvalToNumericXlator { public static final int REF_BOOL_IS_PARSED = 0x0010; public static final int REF_BLANK_IS_PARSED = 0x0020; - public static final int EVALUATED_REF_STRING_IS_PARSED = 0x0040; - public static final int EVALUATED_REF_BOOL_IS_PARSED = 0x0080; - public static final int EVALUATED_REF_BLANK_IS_PARSED = 0x0100; - - public static final int STRING_TO_BOOL_IS_PARSED = 0x0200; - public static final int REF_STRING_TO_BOOL_IS_PARSED = 0x0400; - public static final int STRING_IS_INVALID_VALUE = 0x0800; - public static final int REF_STRING_IS_INVALID_VALUE = 0x1000; - -// public static final int BOOL_IS_BLANK = 0x2000; -// public static final int REF_BOOL_IS_BLANK = 0x4000; -// public static final int STRING_IS_BLANK = 0x8000; -// public static final int REF_STRING_IS_BLANK = 0x10000; private final int flags; public ValueEvalToNumericXlator(int flags) { - this.flags = flags; + + if (false) { // uncomment to see who is using this class + System.err.println(new Throwable().getStackTrace()[1].getClassName() + "\t0x" + + Integer.toHexString(flags).toUpperCase()); + } + this.flags = flags; } /** @@ -71,7 +63,7 @@ public class ValueEvalToNumericXlator { // most common case - least worries :) else if (eval instanceof NumberEval) { - retval = (NumberEval) eval; + retval = eval; } // booleval @@ -125,50 +117,33 @@ public class ValueEvalToNumericXlator { * @param eval */ private ValueEval xlateRefEval(RefEval reval) { - ValueEval retval = null; - ValueEval eval = (ValueEval) reval.getInnerValueEval(); + ValueEval eval = reval.getInnerValueEval(); // most common case - least worries :) if (eval instanceof NumberEval) { - retval = (NumberEval) eval; + return eval; } - // booleval - else if (eval instanceof BoolEval) { - retval = ((flags & REF_BOOL_IS_PARSED) > 0) + if (eval instanceof BoolEval) { + return ((flags & REF_BOOL_IS_PARSED) > 0) ? (ValueEval) eval : BlankEval.INSTANCE; } - // stringeval - else if (eval instanceof StringEval) { - retval = xlateRefStringEval((StringEval) eval); - } - - // erroreval - else if (eval instanceof ErrorEval) { - retval = eval; - } - - // refeval - else if (eval instanceof RefEval) { - RefEval re = (RefEval) eval; - retval = xlateRefEval(re); + if (eval instanceof StringEval) { + return xlateRefStringEval((StringEval) eval); } - else if (eval instanceof BlankEval) { - retval = xlateBlankEval(reval.isEvaluated() ? EVALUATED_REF_BLANK_IS_PARSED : REF_BLANK_IS_PARSED); + if (eval instanceof ErrorEval) { + return eval; } - // probably AreaEval ? then not acceptable. - else { - throw new RuntimeException("Invalid ValueEval type passed for conversion: " + eval.getClass()); + if (eval instanceof BlankEval) { + return xlateBlankEval(REF_BLANK_IS_PARSED); } - - - - return retval; + throw new RuntimeException("Invalid ValueEval type passed for conversion: (" + + eval.getClass().getName() + ")"); } /** @@ -176,93 +151,38 @@ public class ValueEvalToNumericXlator { * @param eval */ private ValueEval xlateStringEval(StringEval eval) { - ValueEval retval = null; + if ((flags & STRING_IS_PARSED) > 0) { String s = eval.getStringValue(); - try { - double d = Double.parseDouble(s); - retval = new NumberEval(d); - } - catch (Exception e) { - if ((flags & STRING_TO_BOOL_IS_PARSED) > 0) { - try { - boolean b = Boolean.getBoolean(s); - retval = b ? BoolEval.TRUE : BoolEval.FALSE; - } - catch (Exception e2) { retval = ErrorEval.VALUE_INVALID; } - } - else { - retval = ErrorEval.VALUE_INVALID; - } + Double d = OperandResolver.parseDouble(s); + if(d == null) { + return ErrorEval.VALUE_INVALID; } + return new NumberEval(d.doubleValue()); } - else if ((flags & STRING_TO_BOOL_IS_PARSED) > 0) { - String s = eval.getStringValue(); - try { - boolean b = Boolean.getBoolean(s); - retval = b ? BoolEval.TRUE : BoolEval.FALSE; - } - catch (Exception e) { retval = ErrorEval.VALUE_INVALID; } - } - // strings are errors? - else if ((flags & STRING_IS_INVALID_VALUE) > 0) { - retval = ErrorEval.VALUE_INVALID; + if ((flags & STRING_IS_INVALID_VALUE) > 0) { + return ErrorEval.VALUE_INVALID; } // ignore strings - else { - retval = xlateBlankEval(BLANK_IS_PARSED); - } - return retval; + return xlateBlankEval(BLANK_IS_PARSED); } /** * uses the relevant flags to decode the StringEval * @param eval */ - private ValueEval xlateRefStringEval(StringEval eval) { - ValueEval retval = null; + private ValueEval xlateRefStringEval(StringEval sve) { if ((flags & REF_STRING_IS_PARSED) > 0) { - StringEval sve = (StringEval) eval; String s = sve.getStringValue(); - try { - double d = Double.parseDouble(s); - retval = new NumberEval(d); - } - catch (Exception e) { - if ((flags & REF_STRING_TO_BOOL_IS_PARSED) > 0) { - try { - boolean b = Boolean.getBoolean(s); - retval = b ? BoolEval.TRUE : BoolEval.FALSE; - } - catch (Exception e2) { retval = ErrorEval.VALUE_INVALID; } - } - else { - retval = ErrorEval.VALUE_INVALID; - } + Double d = OperandResolver.parseDouble(s); + if(d == null) { + return ErrorEval.VALUE_INVALID; } + return new NumberEval(d.doubleValue()); } - else if ((flags & REF_STRING_TO_BOOL_IS_PARSED) > 0) { - StringEval sve = (StringEval) eval; - String s = sve.getStringValue(); - try { - boolean b = Boolean.getBoolean(s); - retval = b ? BoolEval.TRUE : BoolEval.FALSE;; - } - catch (Exception e) { retval = ErrorEval.VALUE_INVALID; } - } - - // strings are errors? - else if ((flags & REF_STRING_IS_INVALID_VALUE) > 0) { - retval = ErrorEval.VALUE_INVALID; - } - // strings are blanks - else { - retval = BlankEval.INSTANCE; - } - return retval; + return BlankEval.INSTANCE; } - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java index 592402b80d..bf888b97df 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java @@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; public class Avedev extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = - new ValueEvalToNumericXlator((short) (0 - // ValueEvalToNumericXlator.BOOL_IS_PARSED + new ValueEvalToNumericXlator((short) ( + ValueEvalToNumericXlator.BOOL_IS_PARSED //| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED @@ -44,7 +44,6 @@ public class Avedev extends MultiOperandNumericFunction { //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE - | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED )); /** diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java index 3491109177..4043040713 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java @@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; public class Average extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = - new ValueEvalToNumericXlator((short) (0 - // ValueEvalToNumericXlator.BOOL_IS_PARSED + new ValueEvalToNumericXlator((short) ( + ValueEvalToNumericXlator.BOOL_IS_PARSED //| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED @@ -44,7 +44,6 @@ public class Average extends MultiOperandNumericFunction { //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE - | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED )); /** diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java index 8eb7e841d6..c054c6dac4 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on Jun 20, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.BoolEval; @@ -38,13 +35,10 @@ public abstract class FinanceFunction extends NumericFunction { new ValueEvalToNumericXlator((short) (0 | ValueEvalToNumericXlator.BOOL_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED | ValueEvalToNumericXlator.BLANK_IS_PARSED | ValueEvalToNumericXlator.REF_BLANK_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE @@ -56,11 +50,11 @@ public abstract class FinanceFunction extends NumericFunction { * if they desire to return a different ValueEvalToNumericXlator instance * than the default. */ - protected ValueEvalToNumericXlator getXlator() { + protected final ValueEvalToNumericXlator getXlator() { return DEFAULT_NUM_XLATOR; } - protected ValueEval singleOperandNumericAsBoolean(Eval eval, int srcRow, short srcCol) { + protected final ValueEval singleOperandNumericAsBoolean(Eval eval, int srcRow, short srcCol) { ValueEval retval = null; retval = singleOperandEvaluate(eval, srcRow, srcCol); if (retval instanceof NumericValueEval) { @@ -74,5 +68,4 @@ public abstract class FinanceFunction extends NumericFunction { } return retval; } - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java index 90dbd591b7..7aba5db72a 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java @@ -28,28 +28,22 @@ import org.apache.poi.hssf.record.formula.eval.Eval; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class If implements Function { +public final class If implements Function { + + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { - public Eval evaluate(Eval[] evals, int srcCellRow, short srcCellCol) { - Eval retval = null; Eval evalWhenFalse = BoolEval.FALSE; - switch (evals.length) { + switch (args.length) { case 3: - evalWhenFalse = evals[2]; + evalWhenFalse = args[2]; case 2: - BoolEval beval = (BoolEval) evals[0]; + BoolEval beval = (BoolEval) args[0]; // TODO - class cast exception if (beval.getBooleanValue()) { - retval = evals[1]; - } - else { - retval = evalWhenFalse; + return args[1]; } - break; + return evalWhenFalse; default: - retval = ErrorEval.UNKNOWN_ERROR; + return ErrorEval.VALUE_INVALID; } - return retval; } - - } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java index 6e8f84b342..c0e482e5a8 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java @@ -14,79 +14,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; -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.RefEval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.ValueEval; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class Isblank implements Function { +public final class Isblank implements Function { - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - ValueEval retval = null; - boolean b = false; - - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - break; - case 1: - if (operands[0] instanceof BlankEval) { - b = true; - } - else if (operands[0] instanceof AreaEval) { - AreaEval ae = (AreaEval) operands[0]; - if (ae.contains(srcCellRow, srcCellCol)) { // circular ref! - retval = ErrorEval.CIRCULAR_REF_ERROR; - } - else if (ae.isRow()) { - if (ae.containsColumn(srcCellCol)) { - ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCellCol); - b = (ve instanceof BlankEval); - } - else { - b = false; - } - } - else if (ae.isColumn()) { - if (ae.containsRow(srcCellRow)) { - ValueEval ve = ae.getValueAt(srcCellRow, ae.getFirstColumn()); - b = (ve instanceof BlankEval); - } - else { - b = false; - } - } - else { - b = false; - } - } - else if (operands[0] instanceof RefEval) { - RefEval re = (RefEval) operands[0]; - b = (!re.isEvaluated()) && re.getInnerValueEval() instanceof BlankEval; - } - else { - b = false; - } - } - - if (retval == null) { - retval = b - ? BoolEval.TRUE - : BoolEval.FALSE; - } - return retval; - } + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + if(args.length != 1) { + return ErrorEval.VALUE_INVALID; + } + Eval arg = args[0]; + + ValueEval singleCellValue; + try { + singleCellValue = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); + } catch (EvaluationException e) { + return BoolEval.FALSE; + } + return BoolEval.valueOf(singleCellValue instanceof BlankEval); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java index c0cb39b268..0bc49b4070 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java @@ -14,125 +14,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; -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.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.NumberEval; -import org.apache.poi.hssf.record.formula.eval.RefEval; -import org.apache.poi.hssf.record.formula.eval.StringValueEval; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.ValueEval; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class Len extends TextFunction { - +public final class Len extends TextFunction { - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - ValueEval retval = null; - String s = null; - - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - break; - case 1: - ValueEval ve = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol); - if (ve instanceof StringValueEval) { - StringValueEval sve = (StringValueEval) ve; - s = sve.getStringValue(); - } - else if (ve instanceof RefEval) { - RefEval re = (RefEval) ve; - ValueEval ive = re.getInnerValueEval(); - if (ive instanceof BlankEval) { - s = re.isEvaluated() ? "0" : null; - } - else if (ive instanceof StringValueEval) { - s = ((StringValueEval) ive).getStringValue(); - } - else if (ive instanceof BlankEval) {} - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else if (ve instanceof BlankEval) {} - else { - retval = ErrorEval.VALUE_INVALID; - break; - } - } - - if (retval == null) { - s = (s == null) ? EMPTY_STRING : s; - retval = new NumberEval(s.length()); - } - - return retval; - } - - - protected ValueEval singleOperandEvaluate(Eval eval, int srcRow, short srcCol) { - ValueEval retval; - if (eval instanceof AreaEval) { - AreaEval ae = (AreaEval) eval; - if (ae.contains(srcRow, srcCol)) { // circular ref! - retval = ErrorEval.CIRCULAR_REF_ERROR; - } - else if (ae.isRow()) { - if (ae.containsColumn(srcCol)) { - ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol); - retval = attemptXlateToText(ve); - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else if (ae.isColumn()) { - if (ae.containsRow(srcRow)) { - ValueEval ve = ae.getValueAt(srcRow, ae.getFirstColumn()); - retval = attemptXlateToText(ve); - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else { - retval = attemptXlateToText((ValueEval) eval); - } - return retval; - } + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + + if(args.length != 1) { + return ErrorEval.VALUE_INVALID; + } + + try { + ValueEval veval = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); - - /** - * converts from Different ValueEval types to StringEval. - * Note: AreaEvals are not handled, if arg is an AreaEval, - * the returned value is ErrorEval.VALUE_INVALID - * @param ve - */ - protected ValueEval attemptXlateToText(ValueEval ve) { - ValueEval retval; - if (ve instanceof StringValueEval || ve instanceof RefEval) { - retval = ve; - } - else if (ve instanceof BlankEval) { - retval = ve; - } - else { - retval = ErrorEval.VALUE_INVALID; - } - return retval; - } + String str = OperandResolver.coerceValueToString(veval); + + return new NumberEval(str.length()); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Maxa.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Maxa.java index 21e30de0c2..e25db7b746 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Maxa.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Maxa.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.ErrorEval; @@ -30,12 +27,11 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class Maxa extends MultiOperandNumericFunction { +public final class Maxa extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED //| ValueEvalToNumericXlator.REF_STRING_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java index 517e89feed..7f30aa4cec 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java @@ -23,7 +23,6 @@ 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.OperandResolver; import org.apache.poi.hssf.record.formula.eval.StringEval; -import org.apache.poi.hssf.record.formula.eval.StringValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; /** @@ -35,7 +34,7 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; * * @author Manda Wilson < wilson at c bio dot msk cc dot org > */ -public class Mid extends TextFunction { +public class Mid implements Function { /** * Returns a specific number of characters from a text string, starting at * the position you specify, based on the number of characters you specify. @@ -52,7 +51,8 @@ public class Mid extends TextFunction { int numChars; try { - text = evaluateTextArg(args[0], srcCellRow, srcCellCol); + ValueEval evText = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + text = OperandResolver.coerceValueToString(evText); int startCharNum = evaluateNumberArg(args[1], srcCellRow, srcCellCol); numChars = evaluateNumberArg(args[2], srcCellRow, srcCellCol); startIx = startCharNum - 1; // convert to zero-based @@ -79,7 +79,7 @@ public class Mid extends TextFunction { } - public static int evaluateNumberArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException { + private static int evaluateNumberArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException { ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); if (ev instanceof BlankEval) { // Note - for start_num arg, blank causes error(#VALUE!), @@ -89,12 +89,4 @@ public class Mid extends TextFunction { return OperandResolver.coerceValueToInt(ev); } - - private static String evaluateTextArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException { - ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); - if (ev instanceof StringValueEval) { - return ((StringValueEval) ev).getStringValue(); - } - throw EvaluationException.invalidValue(); - } } \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java index a998a870f6..21ba47b569 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java @@ -14,10 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.ErrorEval; @@ -35,7 +32,6 @@ public class Mina extends MultiOperandNumericFunction { new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED //| ValueEvalToNumericXlator.REF_STRING_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java index f7dc60fea7..0e7cce217e 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java @@ -76,32 +76,9 @@ public abstract class MultiOperandNumericFunction extends NumericFunction { } } - - private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = - new ValueEvalToNumericXlator((short) ( - ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.STRING_IS_PARSED - | ValueEvalToNumericXlator.REF_STRING_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED - //| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE - //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE - )); - private static final int DEFAULT_MAX_NUM_OPERANDS = 30; - /** - * this is the default impl for the factory method getXlator - * of the super class NumericFunction. Subclasses can override this method - * if they desire to return a different ValueEvalToNumericXlator instance - * than the default. - */ - protected ValueEvalToNumericXlator getXlator() { - return DEFAULT_NUM_XLATOR; - } + protected abstract ValueEvalToNumericXlator getXlator(); /** * Maximum number of operands accepted by this function. @@ -160,9 +137,7 @@ public abstract class MultiOperandNumericFunction extends NumericFunction { * HSSFFormulaEvaluator where we store an array * of RefEvals as the "values" array. */ - RefEval re = (values[j] instanceof RefEval) - ? new Ref2DEval(null, ((RefEval) values[j]).getInnerValueEval(), true) - : new Ref2DEval(null, values[j], false); + RefEval re = new Ref2DEval(null, values[j]); ValueEval ve = singleOperandEvaluate(re, srcRow, srcCol); if (ve instanceof NumericValueEval) { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/NumericFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/NumericFunction.java index 7b43317a3b..fd96f1495a 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/NumericFunction.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/NumericFunction.java @@ -39,10 +39,8 @@ public abstract class NumericFunction implements Function { new ValueEvalToNumericXlator((short) ( ValueEvalToNumericXlator.BOOL_IS_PARSED | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED //| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java index fef7e03464..7995e66c34 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java @@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; public class Stdev extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = - new ValueEvalToNumericXlator((short) (0 - // ValueEvalToNumericXlator.BOOL_IS_PARSED + new ValueEvalToNumericXlator((short) ( + ValueEvalToNumericXlator.BOOL_IS_PARSED //| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED | ValueEvalToNumericXlator.STRING_IS_PARSED @@ -44,7 +44,6 @@ public class Stdev extends MultiOperandNumericFunction { //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE - | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED )); /** diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java index f4e1959be6..b74b4161ac 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java @@ -33,18 +33,18 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; public class Sumsq extends MultiOperandNumericFunction { private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = new ValueEvalToNumericXlator((short) ( - // ValueEvalToNumericXlator.BOOL_IS_PARSED + ValueEvalToNumericXlator.BOOL_IS_PARSED //| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.STRING_IS_PARSED + | ValueEvalToNumericXlator.STRING_IS_PARSED //| ValueEvalToNumericXlator.REF_STRING_IS_PARSED //| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED //| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE - ValueEvalToNumericXlator.REF_BLANK_IS_PARSED - | ValueEvalToNumericXlator.BLANK_IS_PARSED + //| ValueEvalToNumericXlator.REF_BLANK_IS_PARSED + //| ValueEvalToNumericXlator.BLANK_IS_PARSED )); protected ValueEvalToNumericXlator getXlator() { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java index 8e31224078..30ad5ec230 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java @@ -14,50 +14,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ -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.NumberEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; +package org.apache.poi.hssf.record.formula.functions; /** + * Implementation of Excel function SUMX2MY2()

+ * + * Calculates the sum of differences of squares in two arrays of the same size.
+ * Syntax:
+ * SUMX2MY2(arrayX, arrayY)

+ * + * result = Σi: 0..n(xi2-yi2) + * * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * */ -public class Sumx2my2 extends XYNumericFunction { +public final class Sumx2my2 extends XYNumericFunction { - - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - ValueEval retval = null; - double[][] values = null; - - int checkLen = 0; // check to see that all array lengths are equal - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - break; - case 2: - values = getValues(operands, srcCellRow, srcCellCol); - if (values==null - || values[X] == null || values[Y] == null - || values[X].length == 0 || values[Y].length == 0 - || values[X].length != values[Y].length) { - retval = ErrorEval.VALUE_INVALID; - } - } - - if (retval == null) { - double d = MathX.sumx2my2(values[X], values[Y]); - retval = (Double.isNaN(d) || Double.isInfinite(d)) - ? (ValueEval) ErrorEval.NUM_ERROR - : new NumberEval(d); - } - - return retval; + protected double evaluate(double[] xArray, double[] yArray) { + return MathX.sumx2my2(xArray, yArray); } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java index deb7675a4e..dfd730d12c 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java @@ -14,50 +14,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ -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.NumberEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; +package org.apache.poi.hssf.record.formula.functions; /** + * Implementation of Excel function SUMX2PY2()

+ * + * Calculates the sum of squares in two arrays of the same size.
+ * Syntax:
+ * SUMX2PY2(arrayX, arrayY)

+ * + * result = Σi: 0..n(xi2+yi2) + * * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * */ -public class Sumx2py2 extends XYNumericFunction { +public final class Sumx2py2 extends XYNumericFunction { - - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - ValueEval retval = null; - double[][] values = null; - - int checkLen = 0; // check to see that all array lengths are equal - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - break; - case 2: - values = getValues(operands, srcCellRow, srcCellCol); - if (values==null - || values[X] == null || values[Y] == null - || values[X].length == 0 || values[Y].length == 0 - || values[X].length != values[Y].length) { - retval = ErrorEval.VALUE_INVALID; - } - } - - if (retval == null) { - double d = MathX.sumx2py2(values[X], values[Y]); - retval = (Double.isNaN(d) || Double.isInfinite(d)) - ? (ValueEval) ErrorEval.NUM_ERROR - : new NumberEval(d); - } - - return retval; + protected double evaluate(double[] xArray, double[] yArray) { + return MathX.sumx2py2(xArray, yArray); } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java index c62a0b7622..a1b2fec9b2 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java @@ -14,50 +14,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ -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.NumberEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; +package org.apache.poi.hssf.record.formula.functions; /** + * Implementation of Excel function SUMXMY2()

+ * + * Calculates the sum of squares of differences between two arrays of the same size.
+ * Syntax:
+ * SUMXMY2(arrayX, arrayY)

+ * + * result = Σi: 0..n(xi-yi)2 + * * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * */ -public class Sumxmy2 extends XYNumericFunction { +public final class Sumxmy2 extends XYNumericFunction { - - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - ValueEval retval = null; - double[][] values = null; - - int checkLen = 0; // check to see that all array lengths are equal - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - break; - case 2: - values = getValues(operands, srcCellRow, srcCellCol); - if (values==null - || values[X] == null || values[Y] == null - || values[X].length == 0 || values[Y].length == 0 - || values[X].length != values[Y].length) { - retval = ErrorEval.VALUE_INVALID; - } - } - - if (retval == null) { - double d = MathX.sumxmy2(values[X], values[Y]); - retval = (Double.isNaN(d) || Double.isInfinite(d)) - ? (ValueEval) ErrorEval.NUM_ERROR - : new NumberEval(d); - } - - return retval; + protected double evaluate(double[] xArray, double[] yArray) { + return MathX.sumxmy2(xArray, yArray); } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java index 5e9d91c7cc..87e29ee34d 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java @@ -16,12 +16,11 @@ */ package org.apache.poi.hssf.record.formula.functions; -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.NumberEval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.StringEval; -import org.apache.poi.hssf.record.formula.eval.StringValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; /** @@ -30,46 +29,25 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; * value is string. * @author Manda Wilson < wilson at c bio dot msk cc dot org > */ -public class Trim extends TextFunction { +public final class Trim extends TextFunction { - /** - * Removes leading and trailing spaces from value if evaluated - * operand value is string. - * Returns StringEval only if evaluated operand is of type string - * (and is not blank or null) or number. If evaluated operand is - * of type string and is blank or null, or if evaluated operand is - * of type blank, returns BlankEval. Otherwise returns ErrorEval. - * - * @see org.apache.poi.hssf.record.formula.eval.Eval - */ - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - Eval retval = ErrorEval.VALUE_INVALID; - String str = null; - - switch (operands.length) { - default: - break; - case 1: - ValueEval veval = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol); - if (veval instanceof StringValueEval) { - StringValueEval sve = (StringValueEval) veval; - str = sve.getStringValue(); - if (str == null || str.trim().equals("")) { - return BlankEval.INSTANCE; - } - } - else if (veval instanceof NumberEval) { - NumberEval neval = (NumberEval) veval; - str = neval.getStringValue(); - } - else if (veval instanceof BlankEval) { - return BlankEval.INSTANCE; - } - } - - if (str != null) { - retval = new StringEval(str.trim()); - } - return retval; - } + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + + if(args.length != 1) { + return ErrorEval.VALUE_INVALID; + } + + try { + ValueEval veval = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + + String str = OperandResolver.coerceValueToString(veval); + str = str.trim(); + if(str.length() < 1) { + return StringEval.EMPTY_INSTANCE; + } + return new StringEval(str); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java index 1e6955ad94..b989c33a26 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java @@ -14,154 +14,151 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 29, 2005 - * - */ + package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.AreaEval; 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.NumberEval; import org.apache.poi.hssf.record.formula.eval.RefEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; -import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public abstract class XYNumericFunction extends NumericFunction { +public abstract class XYNumericFunction implements Function { protected static final int X = 0; protected static final int Y = 1; + + protected static final class DoubleArrayPair { - private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR = - new ValueEvalToNumericXlator((short) ( - ValueEvalToNumericXlator.BOOL_IS_PARSED - | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.STRING_IS_PARSED - | ValueEvalToNumericXlator.REF_STRING_IS_PARSED - | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED - //| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED - //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE - //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE - )); + private final double[] _xArray; + private final double[] _yArray; - /** - * this is the default impl for the factory method getXlator - * of the super class NumericFunction. Subclasses can override this method - * if they desire to return a different ValueEvalToNumericXlator instance - * than the default. - */ - protected ValueEvalToNumericXlator getXlator() { - return DEFAULT_NUM_XLATOR; - } - - protected int getMaxNumOperands() { - return 30; + public DoubleArrayPair(double[] xArray, double[] yArray) { + _xArray = xArray; + _yArray = yArray; + } + public double[] getXArray() { + return _xArray; + } + public double[] getYArray() { + return _yArray; + } } + + public final Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + if(args.length != 2) { + return ErrorEval.VALUE_INVALID; + } + + double[][] values; + try { + values = getValues(args[0], args[1]); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + if (values==null + || values[X] == null || values[Y] == null + || values[X].length == 0 || values[Y].length == 0 + || values[X].length != values[Y].length) { + return ErrorEval.VALUE_INVALID; + } + + double d = evaluate(values[X], values[Y]); + if (Double.isNaN(d) || Double.isInfinite(d)) { + return ErrorEval.NUM_ERROR; + } + return new NumberEval(d); + } + protected abstract double evaluate(double[] xArray, double[] yArray); + /** * Returns a double array that contains values for the numeric cells * from among the list of operands. Blanks and Blank equivalent cells * are ignored. Error operands or cells containing operands of type * that are considered invalid and would result in #VALUE! error in * excel cause this function to return null. - * - * @param xops - * @param yops - * @param srcRow - * @param srcCol */ - protected double[][] getNumberArray(Eval[] xops, Eval[] yops, int srcRow, short srcCol) { - double[][] retval = new double[2][30]; + private static double[][] getNumberArray(Eval[] xops, Eval[] yops) throws EvaluationException { + + // check for errors first: size mismatch, value errors in x, value errors in y + + int nArrayItems = xops.length; + if(nArrayItems != yops.length) { + throw new EvaluationException(ErrorEval.NA); + } + for (int i = 0; i < xops.length; i++) { + Eval eval = xops[i]; + if (eval instanceof ErrorEval) { + throw new EvaluationException((ErrorEval) eval); + } + } + for (int i = 0; i < yops.length; i++) { + Eval eval = yops[i]; + if (eval instanceof ErrorEval) { + throw new EvaluationException((ErrorEval) eval); + } + } + + double[] xResult = new double[nArrayItems]; + double[] yResult = new double[nArrayItems]; + int count = 0; - if (xops.length > getMaxNumOperands() - || yops.length > getMaxNumOperands() - || xops.length != yops.length) { - retval = null; - } - else { - - for (int i=0, iSize=xops.length; i= arr.length) { - arr = new double[arr.length << 2]; - } - if (temp.length != arr.length) - System.arraycopy(temp, 0, arr, 0, temp.length); - return arr; + private static double[][] getValues(Eval argX, Eval argY) throws EvaluationException { + + if (argX instanceof ErrorEval) { + throw new EvaluationException((ErrorEval) argX); + } + if (argY instanceof ErrorEval) { + throw new EvaluationException((ErrorEval) argY); + } + + Eval[] xEvals; + Eval[] yEvals; + if (argX instanceof AreaEval) { + AreaEval ae = (AreaEval) argX; + xEvals = ae.getValues(); + } else { + xEvals = new Eval[] { argX, }; + } + + if (argY instanceof AreaEval) { + AreaEval ae = (AreaEval) argY; + yEvals = ae.getValues(); + } else { + yEvals = new Eval[] { argY, }; + } + + return getNumberArray(xEvals, yEvals); } - protected static double[] trimToSize(double[] arr, int len) { + private static double[] trimToSize(double[] arr, int len) { double[] tarr = arr; if (arr.length > len) { tarr = new double[len]; @@ -170,7 +167,7 @@ public abstract class XYNumericFunction extends NumericFunction { return tarr; } - protected static boolean isNumberEval(Eval eval) { + private static boolean isNumberEval(Eval eval) { boolean retval = false; if (eval instanceof NumberEval) { @@ -185,7 +182,7 @@ public abstract class XYNumericFunction extends NumericFunction { return retval; } - protected static double getDoubleValue(Eval eval) { + private static double getDoubleValue(Eval eval) { double retval = 0; if (eval instanceof NumberEval) { NumberEval ne = (NumberEval) eval; diff --git a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java index acbca05e62..3cbec2e6d2 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java +++ b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java @@ -80,6 +80,7 @@ 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.NameEval; import org.apache.poi.hssf.record.formula.eval.NotEqualEval; import org.apache.poi.hssf.record.formula.eval.NumberEval; import org.apache.poi.hssf.record.formula.eval.OperationEval; @@ -360,16 +361,16 @@ public class HSSFFormulaEvaluator { EvaluationCycleDetector tracker = EvaluationCycleDetectorManager.getTracker(); if(!tracker.startEvaluate(workbook, sheet, srcRowNum, srcColNum)) { - return ErrorEval.CIRCULAR_REF_ERROR; + return ErrorEval.CIRCULAR_REF_ERROR; } try { - return evaluateCell(workbook, sheet, srcRowNum, srcColNum, srcCell.getCellFormula()); + return evaluateCell(workbook, sheet, srcRowNum, srcColNum, srcCell.getCellFormula()); } finally { - tracker.endEvaluate(workbook, sheet, srcRowNum, srcColNum); + tracker.endEvaluate(workbook, sheet, srcRowNum, srcColNum); } } private static ValueEval evaluateCell(HSSFWorkbook workbook, HSSFSheet sheet, - int srcRowNum, short srcColNum, String cellFormulaText) { + int srcRowNum, short srcColNum, String cellFormulaText) { FormulaParser parser = new FormulaParser(cellFormulaText, workbook.getWorkbook()); parser.parse(); Ptg[] ptgs = parser.getRPNPtg(); @@ -380,15 +381,24 @@ public class HSSFFormulaEvaluator { for (int i = 0, iSize = ptgs.length; i < iSize; i++) { // since we don't know how to handle these yet :( - if (ptgs[i] instanceof ControlPtg) { continue; } - if (ptgs[i] instanceof MemErrPtg) { continue; } - if (ptgs[i] instanceof MissingArgPtg) { continue; } - if (ptgs[i] instanceof NamePtg) { continue; } - if (ptgs[i] instanceof NameXPtg) { continue; } - if (ptgs[i] instanceof UnknownPtg) { continue; } + Ptg ptg = ptgs[i]; + if (ptg instanceof ControlPtg) { continue; } + if (ptg instanceof MemErrPtg) { continue; } + if (ptg instanceof MissingArgPtg) { continue; } + if (ptg instanceof NamePtg) { + // named ranges, macro functions + NamePtg namePtg = (NamePtg) ptg; + stack.push(new NameEval(namePtg.getIndex())); + continue; + } + if (ptg instanceof NameXPtg) { + // TODO - external functions + continue; + } + if (ptg instanceof UnknownPtg) { continue; } - if (ptgs[i] instanceof OperationPtg) { - OperationPtg optg = (OperationPtg) ptgs[i]; + if (ptg instanceof OperationPtg) { + OperationPtg optg = (OperationPtg) ptg; // parens can be ignored since we have RPN tokens if (optg instanceof ParenthesisPtg) { continue; } @@ -408,47 +418,67 @@ public class HSSFFormulaEvaluator { Eval opresult = invokeOperation(operation, ops, srcRowNum, srcColNum, workbook, sheet); stack.push(opresult); } - else if (ptgs[i] instanceof ReferencePtg) { - ReferencePtg ptg = (ReferencePtg) ptgs[i]; - short colnum = ptg.getColumn(); - short rownum = ptg.getRow(); + else if (ptg instanceof ReferencePtg) { + ReferencePtg refPtg = (ReferencePtg) ptg; + short colnum = refPtg.getColumn(); + short rownum = refPtg.getRow(); HSSFRow row = sheet.getRow(rownum); HSSFCell cell = (row != null) ? row.getCell(colnum) : null; - stack.push(createRef2DEval(ptg, cell, row, sheet, workbook)); + stack.push(createRef2DEval(refPtg, cell, row, sheet, workbook)); } - else if (ptgs[i] instanceof Ref3DPtg) { - Ref3DPtg ptg = (Ref3DPtg) ptgs[i]; - short colnum = ptg.getColumn(); - short rownum = ptg.getRow(); + else if (ptg instanceof Ref3DPtg) { + Ref3DPtg refPtg = (Ref3DPtg) ptg; + short colnum = refPtg.getColumn(); + short rownum = refPtg.getRow(); Workbook wb = workbook.getWorkbook(); - HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(ptg.getExternSheetIndex())); + HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(refPtg.getExternSheetIndex())); HSSFRow row = xsheet.getRow(rownum); HSSFCell cell = (row != null) ? row.getCell(colnum) : null; - stack.push(createRef3DEval(ptg, cell, row, xsheet, workbook)); + stack.push(createRef3DEval(refPtg, cell, row, xsheet, workbook)); } - else if (ptgs[i] instanceof AreaPtg) { - AreaPtg ap = (AreaPtg) ptgs[i]; + else if (ptg instanceof AreaPtg) { + AreaPtg ap = (AreaPtg) ptg; AreaEval ae = evaluateAreaPtg(sheet, workbook, ap); stack.push(ae); } - else if (ptgs[i] instanceof Area3DPtg) { - Area3DPtg a3dp = (Area3DPtg) ptgs[i]; + else if (ptg instanceof Area3DPtg) { + Area3DPtg a3dp = (Area3DPtg) ptg; AreaEval ae = evaluateArea3dPtg(workbook, a3dp); stack.push(ae); } else { - Eval ptgEval = getEvalForPtg(ptgs[i]); + Eval ptgEval = getEvalForPtg(ptg); stack.push(ptgEval); } } ValueEval value = ((ValueEval) stack.pop()); - if (value instanceof RefEval) { - RefEval rv = (RefEval) value; + if (!stack.isEmpty()) { + throw new IllegalStateException("evaluation stack not empty"); + } + value = dereferenceValue(value, srcRowNum, srcColNum); + if (value instanceof BlankEval) { + // Note Excel behaviour here. A blank final final value is converted to zero. + return NumberEval.ZERO; + // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to + // blank, the actual value is empty string. This can be verified with ISBLANK(). + } + return value; + } + + /** + * Dereferences a single value from any AreaEval or RefEval evaluation result. + * If the supplied evaluationResult is just a plain value, it is returned as-is. + * @return a NumberEval, StringEval, BoolEval, + * BlankEval or ErrorEval. Never null. + */ + private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) { + if (evaluationResult instanceof RefEval) { + RefEval rv = (RefEval) evaluationResult; return rv.getInnerValueEval(); } - if (value instanceof AreaEval) { - AreaEval ae = (AreaEval) value; + if (evaluationResult instanceof AreaEval) { + AreaEval ae = (AreaEval) evaluationResult; if (ae.isRow()) { if(ae.isColumn()) { return ae.getValues()[0]; @@ -460,7 +490,7 @@ public class HSSFFormulaEvaluator { } return ErrorEval.VALUE_INVALID; } - return value; + return evaluationResult; } private static Eval invokeOperation(OperationEval operation, Eval[] ops, int srcRowNum, short srcColNum, @@ -486,7 +516,7 @@ public class HSSFFormulaEvaluator { // (eg C:C) // TODO: Handle whole column ranges properly if(row1 == -1 && row0 >= 0) { - row1 = (short)sheet.getLastRowNum(); + row1 = (short)sheet.getLastRowNum(); } ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)]; @@ -514,7 +544,7 @@ public class HSSFFormulaEvaluator { // (eg C:C) // TODO: Handle whole column ranges properly if(row1 == -1 && row0 >= 0) { - row1 = (short)xsheet.getLastRowNum(); + row1 = (short)xsheet.getLastRowNum(); } ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)]; @@ -631,22 +661,22 @@ public class HSSFFormulaEvaluator { private static Ref2DEval createRef2DEval(ReferencePtg ptg, HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) { if (cell == null) { - return new Ref2DEval(ptg, BlankEval.INSTANCE, false); + return new Ref2DEval(ptg, BlankEval.INSTANCE); } switch (cell.getCellType()) { case HSSFCell.CELL_TYPE_NUMERIC: - return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()), false); + return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue())); case HSSFCell.CELL_TYPE_STRING: - return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false); + return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString())); case HSSFCell.CELL_TYPE_FORMULA: - return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true); + return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook)); case HSSFCell.CELL_TYPE_BOOLEAN: - return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()), false); + return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue())); case HSSFCell.CELL_TYPE_BLANK: - return new Ref2DEval(ptg, BlankEval.INSTANCE, false); + return new Ref2DEval(ptg, BlankEval.INSTANCE); case HSSFCell.CELL_TYPE_ERROR: - return new Ref2DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()), false); + return new Ref2DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue())); } throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); } @@ -657,21 +687,21 @@ public class HSSFFormulaEvaluator { private static Ref3DEval createRef3DEval(Ref3DPtg ptg, HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) { if (cell == null) { - return new Ref3DEval(ptg, BlankEval.INSTANCE, false); + return new Ref3DEval(ptg, BlankEval.INSTANCE); } switch (cell.getCellType()) { case HSSFCell.CELL_TYPE_NUMERIC: - return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()), false); + return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue())); case HSSFCell.CELL_TYPE_STRING: - return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false); + return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString())); case HSSFCell.CELL_TYPE_FORMULA: - return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true); + return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook)); case HSSFCell.CELL_TYPE_BOOLEAN: - return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()), false); + return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue())); case HSSFCell.CELL_TYPE_BLANK: - return new Ref3DEval(ptg, BlankEval.INSTANCE, false); + return new Ref3DEval(ptg, BlankEval.INSTANCE); case HSSFCell.CELL_TYPE_ERROR: - return new Ref3DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()), false); + return new Ref3DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue())); } throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")"); } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java b/src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java index 9c2cb2b87f..a038a964ee 100644 --- a/src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java @@ -16,14 +16,14 @@ ==================================================================== */ package org.apache.poi.hssf.eventusermodel; -import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; -import org.apache.poi.hssf.eventusermodel.HSSFListener; - import java.io.File; import java.io.FileInputStream; +import java.io.IOException; import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; -import org.apache.poi.hssf.eventusermodel.HSSFRequest; import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord; import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord; import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingRowDummyRecord; @@ -31,31 +31,33 @@ import org.apache.poi.hssf.record.LabelSSTRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RowRecord; import org.apache.poi.poifs.filesystem.POIFSFileSystem; - -import junit.framework.TestCase; - -public class TestMissingRecordAwareHSSFListener extends TestCase { - private String dirname; - - public TestMissingRecordAwareHSSFListener() { - dirname = System.getProperty("HSSF.testdata.path"); - } +/** + * Tests for MissingRecordAwareHSSFListener + */ +public final class TestMissingRecordAwareHSSFListener extends TestCase { - public void testMissingRowRecords() throws Exception { + private Record[] r; + + public void setUp() { + String dirname = System.getProperty("HSSF.testdata.path"); File f = new File(dirname + "/MissingBits.xls"); - + HSSFRequest req = new HSSFRequest(); MockHSSFListener mockListen = new MockHSSFListener(); MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen); req.addListenerForAllRecords(listener); - POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f)); HSSFEventFactory factory = new HSSFEventFactory(); - factory.processWorkbookEvents(req, fs); - - // Check we got the dummy records - Record[] r = (Record[]) - mockListen.records.toArray(new Record[mockListen.records.size()]); + try { + POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f)); + factory.processWorkbookEvents(req, fs); + } catch (IOException e) { + throw new RuntimeException(e); + } + r = mockListen.getRecords(); + } + + public void testMissingRowRecords() throws Exception { // We have rows 0, 1, 2, 20 and 21 int row0 = -1; @@ -105,20 +107,6 @@ public class TestMissingRecordAwareHSSFListener extends TestCase { } public void testEndOfRowRecords() throws Exception { - File f = new File(dirname + "/MissingBits.xls"); - - HSSFRequest req = new HSSFRequest(); - MockHSSFListener mockListen = new MockHSSFListener(); - MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen); - req.addListenerForAllRecords(listener); - - POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f)); - HSSFEventFactory factory = new HSSFEventFactory(); - factory.processWorkbookEvents(req, fs); - - // Check we got the dummy records - Record[] r = (Record[]) - mockListen.records.toArray(new Record[mockListen.records.size()]); // Find the cell at 0,0 int cell00 = -1; @@ -240,20 +228,6 @@ public class TestMissingRecordAwareHSSFListener extends TestCase { public void testMissingCellRecords() throws Exception { - File f = new File(dirname + "/MissingBits.xls"); - - HSSFRequest req = new HSSFRequest(); - MockHSSFListener mockListen = new MockHSSFListener(); - MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen); - req.addListenerForAllRecords(listener); - - POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f)); - HSSFEventFactory factory = new HSSFEventFactory(); - factory.processWorkbookEvents(req, fs); - - // Check we got the dummy records - Record[] r = (Record[]) - mockListen.records.toArray(new Record[mockListen.records.size()]); // Find the cell at 0,0 int cell00 = -1; @@ -352,25 +326,35 @@ public class TestMissingRecordAwareHSSFListener extends TestCase { assertEquals(10, mc.getColumn()); } - private static class MockHSSFListener implements HSSFListener { - private MockHSSFListener() {} - private ArrayList records = new ArrayList(); + private static final class MockHSSFListener implements HSSFListener { + public MockHSSFListener() {} + private final List _records = new ArrayList(); public void processRecord(Record record) { - records.add(record); + _records.add(record); if(record instanceof MissingRowDummyRecord) { MissingRowDummyRecord mr = (MissingRowDummyRecord)record; - System.out.println("Got dummy row " + mr.getRowNumber()); + log("Got dummy row " + mr.getRowNumber()); } if(record instanceof MissingCellDummyRecord) { MissingCellDummyRecord mc = (MissingCellDummyRecord)record; - System.out.println("Got dummy cell " + mc.getRow() + " " + mc.getColumn()); + log("Got dummy cell " + mc.getRow() + " " + mc.getColumn()); } if(record instanceof LastCellOfRowDummyRecord) { LastCellOfRowDummyRecord lc = (LastCellOfRowDummyRecord)record; - System.out.println("Got end-of row, row was " + lc.getRow() + ", last column was " + lc.getLastColumnNumber()); + log("Got end-of row, row was " + lc.getRow() + ", last column was " + lc.getLastColumnNumber()); + } + } + private static void log(String msg) { + if(false) { // successful tests should be quiet + System.out.println(msg); } } + public Record[] getRecords() { + Record[] result = new Record[_records.size()]; + _records.toArray(result); + return result; + } } } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java new file mode 100755 index 0000000000..3260c371c6 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java @@ -0,0 +1,38 @@ +/* ==================================================================== + 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.eval; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Collects all tests the package org.apache.poi.hssf.record.formula.eval. + * + * @author Josh Micich + */ +public class AllFormulaEvalTests { + + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.eval"); + result.addTestSuite(TestCircularReferences.class); + result.addTestSuite(TestExternalFunction.class); + result.addTestSuite(TestFormulasFromSpreadsheet.class); + result.addTestSuite(TestUnaryPlusEval.class); + return result; + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java new file mode 100755 index 0000000000..27e3338652 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java @@ -0,0 +1,61 @@ +/* ==================================================================== + 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.eval; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFName; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +/** + * + * @author Josh Micich + */ +public final class TestExternalFunction extends TestCase { + + /** + * Checks that an external function can get invoked from the formula evaluator. + */ + public void testInvoke() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell((short)0); + + HSSFName hssfName = wb.createName(); + hssfName.setNameName("myFunc"); + + cell.setCellFormula("myFunc()"); + String actualFormula=cell.getCellFormula(); + assertEquals("myFunc()", actualFormula); + + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb); + fe.setCurrentRow(row); + CellValue evalResult = fe.evaluate(cell); + + // Check the return value from ExternalFunction.evaluate() + // TODO - make this test assert something more interesting as soon as ExternalFunction works a bit better + assertEquals(HSSFCell.CELL_TYPE_ERROR, evalResult.getCellType()); + assertEquals(ErrorEval.FUNCTION_NOT_IMPLEMENTED.getErrorCode(), evalResult.getErrorValue()); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java index 8d21956edb..d3e9c4c412 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java @@ -31,10 +31,13 @@ public final class AllIndividualFunctionEvaluationTests { // TODO - have this suite incorporated into a higher level one public static Test suite() { TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.functions"); + result.addTestSuite(TestAverage.class); result.addTestSuite(TestCountFuncs.class); result.addTestSuite(TestDate.class); result.addTestSuite(TestFinanceLib.class); result.addTestSuite(TestIndex.class); + result.addTestSuite(TestIsBlank.class); + result.addTestSuite(TestLen.class); result.addTestSuite(TestMid.class); result.addTestSuite(TestMathX.class); result.addTestSuite(TestMatch.class); @@ -43,6 +46,8 @@ public final class AllIndividualFunctionEvaluationTests { result.addTestSuite(TestSumproduct.class); result.addTestSuite(TestStatsLib.class); result.addTestSuite(TestTFunc.class); + result.addTestSuite(TestTrim.class); + result.addTestSuite(TestXYNumericFunction.class); return result; } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java index 958c486649..a6e262b868 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java @@ -58,6 +58,6 @@ final class EvalFactory { * Creates a single RefEval (with value zero) */ public static RefEval createRefEval(String refStr) { - return new Ref2DEval(new ReferencePtg(refStr), ZERO, true); + return new Ref2DEval(new ReferencePtg(refStr), ZERO); } } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java index 2093d35b1e..d477231349 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java @@ -111,9 +111,6 @@ public final class NumericFunctionInvoker { if(errorCodesAreEqual(ee, ErrorEval.FUNCTION_NOT_IMPLEMENTED)) { return "Function not implemented"; } - if(errorCodesAreEqual(ee, ErrorEval.UNKNOWN_ERROR)) { - return "Unknown error"; - } if(errorCodesAreEqual(ee, ErrorEval.VALUE_INVALID)) { return "Error code: #VALUE! (invalid value)"; } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java new file mode 100755 index 0000000000..4f0e5fff2c --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java @@ -0,0 +1,103 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +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.NumberEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +/** + * Tests for Excel function AVERAGE() + * + * @author Josh Micich + */ +public final class TestAverage extends TestCase { + + + private static Eval invokeAverage(Eval[] args) { + return new Average().evaluate(args, -1, (short)-1); + } + + private void confirmAverage(Eval[] args, double expected) { + Eval result = invokeAverage(args); + assertEquals(NumberEval.class, result.getClass()); + assertEquals(expected, ((NumberEval)result).getNumberValue(), 0); + } + + private void confirmAverage(Eval[] args, ErrorEval expectedError) { + Eval result = invokeAverage(args); + assertEquals(ErrorEval.class, result.getClass()); + assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode()); + } + + public void testBasic() { + + ValueEval[] values = { + new NumberEval(1), + new NumberEval(2), + new NumberEval(3), + new NumberEval(4), + }; + + confirmAverage(values, 2.5); + + values = new ValueEval[] { + new NumberEval(1), + new NumberEval(2), + BlankEval.INSTANCE, + new NumberEval(3), + BlankEval.INSTANCE, + new NumberEval(4), + BlankEval.INSTANCE, + }; + + confirmAverage(values, 2.5); + } + + /** + * Valid cases where values are not pure numbers + */ + public void testUnusualArgs() { + ValueEval[] values = { + new NumberEval(1), + new NumberEval(2), + BoolEval.TRUE, + BoolEval.FALSE, + }; + + confirmAverage(values, 1.0); + + } + + // currently disabled because MultiOperandNumericFunction.getNumberArray(Eval[], int, short) + // does not handle error values properly yet + public void XtestErrors() { + ValueEval[] values = { + new NumberEval(1), + ErrorEval.NAME_INVALID, + new NumberEval(3), + ErrorEval.DIV_ZERO, + }; + confirmAverage(values, ErrorEval.NAME_INVALID); + + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java index fbaace9210..ae93a2d41b 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java @@ -125,7 +125,7 @@ public final class TestCountFuncs extends TestCase { }; Area2DEval arg0 = new Area2DEval(new AreaPtg("C1:C6"), values); - Ref2DEval criteriaArg = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(25), true); + Ref2DEval criteriaArg = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(25)); Eval[] args= { arg0, criteriaArg, }; double actual = NumericFunctionInvoker.invoke(new Countif(), args); diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java new file mode 100755 index 0000000000..7ce2bd245b --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java @@ -0,0 +1,62 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; +/** + * Tests for Excel function ISBLANK() + * + * @author Josh Micich + */ +public final class TestIsBlank extends TestCase { + + + + public void test3DArea() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet1 = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); + wb.createSheet(); + wb.setSheetName(1, "Sheet2"); + HSSFRow row = sheet1.createRow(0); + HSSFCell cell = row.createCell((short)0); + + + cell.setCellFormula("isblank(Sheet2!A1:A1)"); + + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet1, wb); + fe.setCurrentRow(row); + CellValue result = fe.evaluate(cell); + assertEquals(HSSFCell.CELL_TYPE_BOOLEAN, result.getCellType()); + assertEquals(true, result.getBooleanValue()); + + cell.setCellFormula("isblank(D7:D7)"); + + result = fe.evaluate(cell); + assertEquals(HSSFCell.CELL_TYPE_BOOLEAN, result.getCellType()); + assertEquals(true, result.getBooleanValue()); + + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java new file mode 100755 index 0000000000..a96fb4e2b0 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java @@ -0,0 +1,73 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +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.NumberEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +/** + * Tests for Excel function LEN() + * + * @author Josh Micich + */ +public final class TestLen extends TestCase { + + + private static Eval invokeLen(Eval text) { + Eval[] args = new Eval[] { text, }; + return new Len().evaluate(args, -1, (short)-1); + } + + private void confirmLen(Eval text, int expected) { + Eval result = invokeLen(text); + assertEquals(NumberEval.class, result.getClass()); + assertEquals(expected, ((NumberEval)result).getNumberValue(), 0); + } + + private void confirmLen(Eval text, ErrorEval expectedError) { + Eval result = invokeLen(text); + assertEquals(ErrorEval.class, result.getClass()); + assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode()); + } + + public void testBasic() { + + confirmLen(new StringEval("galactic"), 8); + } + + /** + * Valid cases where text arg is not exactly a string + */ + public void testUnusualArgs() { + + // text (first) arg type is number, other args are strings with fractional digits + confirmLen(new NumberEval(123456), 6); + confirmLen(BoolEval.FALSE, 5); + confirmLen(BoolEval.TRUE, 4); + confirmLen(BlankEval.INSTANCE, 0); + } + + public void testErrors() { + confirmLen(ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java index ab9f609d1d..dc3d595aed 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java @@ -77,13 +77,14 @@ public final class TestMid extends TestCase { // startPos is 1x1 area ref, numChars is cell ref AreaEval aeStart = new Area2DEval(new AreaPtg("A1:A1"), new ValueEval[] { new NumberEval(2), } ); - RefEval reNumChars = new Ref2DEval(new ReferencePtg("B1"), new NumberEval(3),false); + RefEval reNumChars = new Ref2DEval(new ReferencePtg("B1"), new NumberEval(3)); confirmMid(new StringEval("galactic"), aeStart, reNumChars, "ala"); confirmMid(new StringEval("galactic"), new NumberEval(3.1), BlankEval.INSTANCE, ""); confirmMid(new StringEval("galactic"), new NumberEval(3), BoolEval.FALSE, ""); confirmMid(new StringEval("galactic"), new NumberEval(3), BoolEval.TRUE, "l"); + confirmMid(BlankEval.INSTANCE, new NumberEval(3), BoolEval.TRUE, ""); } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java index 9fbc9e42fe..73043911f4 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java @@ -50,7 +50,7 @@ public final class TestSumproduct extends TestCase { public void testScalarSimple() { - RefEval refEval = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(3), true); + RefEval refEval = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(3)); Eval[] args = { refEval, new NumberEval(2), diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java index 6ecc3619c4..4d63cad1c5 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java @@ -50,7 +50,7 @@ public final class TestTFunc extends TestCase { * where cell A1 has the specified innerValue */ private Eval invokeTWithReference(ValueEval innerValue) { - Eval arg = new Ref2DEval(new ReferencePtg((short)1, (short)1, false, false), innerValue, true); + Eval arg = new Ref2DEval(new ReferencePtg((short)1, (short)1, false, false), innerValue); return invokeT(arg); } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java new file mode 100755 index 0000000000..076ac1fc7e --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java @@ -0,0 +1,78 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +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.NumberEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +/** + * Tests for Excel function TRIM() + * + * @author Josh Micich + */ +public final class TestTrim extends TestCase { + + + private static Eval invokeTrim(Eval text) { + Eval[] args = new Eval[] { text, }; + return new Trim().evaluate(args, -1, (short)-1); + } + + private void confirmTrim(Eval text, String expected) { + Eval result = invokeTrim(text); + assertEquals(StringEval.class, result.getClass()); + assertEquals(expected, ((StringEval)result).getStringValue()); + } + + private void confirmTrim(Eval text, ErrorEval expectedError) { + Eval result = invokeTrim(text); + assertEquals(ErrorEval.class, result.getClass()); + assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode()); + } + + public void testBasic() { + + confirmTrim(new StringEval(" hi "), "hi"); + confirmTrim(new StringEval("hi "), "hi"); + confirmTrim(new StringEval(" hi"), "hi"); + confirmTrim(new StringEval(" hi there "), "hi there"); + confirmTrim(new StringEval(""), ""); + confirmTrim(new StringEval(" "), ""); + } + + /** + * Valid cases where text arg is not exactly a string + */ + public void testUnusualArgs() { + + // text (first) arg type is number, other args are strings with fractional digits + confirmTrim(new NumberEval(123456), "123456"); + confirmTrim(BoolEval.FALSE, "FALSE"); + confirmTrim(BoolEval.TRUE, "TRUE"); + confirmTrim(BlankEval.INSTANCE, ""); + } + + public void testErrors() { + confirmTrim(ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java new file mode 100755 index 0000000000..c9f043bd3b --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java @@ -0,0 +1,139 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.eval.Area2DEval; +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.NumberEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +/** + * Tests for Excel functions SUMX2MY2(), SUMX2PY2(), SUMXMY2() + * + * @author Josh Micich + */ +public final class TestXYNumericFunction extends TestCase { + private static final Function SUM_SQUARES = new Sumx2py2(); + private static final Function DIFF_SQUARES = new Sumx2my2(); + private static final Function SUM_SQUARES_OF_DIFFS = new Sumxmy2(); + + private static Eval invoke(Function function, Eval xArray, Eval yArray) { + Eval[] args = new Eval[] { xArray, yArray, }; + return function.evaluate(args, -1, (short)-1); + } + + private void confirm(Function function, Eval xArray, Eval yArray, double expected) { + Eval result = invoke(function, xArray, yArray); + assertEquals(NumberEval.class, result.getClass()); + assertEquals(expected, ((NumberEval)result).getNumberValue(), 0); + } + private void confirmError(Function function, Eval xArray, Eval yArray, ErrorEval expectedError) { + Eval result = invoke(function, xArray, yArray); + assertEquals(ErrorEval.class, result.getClass()); + assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode()); + } + + private void confirmError(Eval xArray, Eval yArray, ErrorEval expectedError) { + confirmError(SUM_SQUARES, xArray, yArray, expectedError); + confirmError(DIFF_SQUARES, xArray, yArray, expectedError); + confirmError(SUM_SQUARES_OF_DIFFS, xArray, yArray, expectedError); + } + + public void testBasic() { + ValueEval[] xValues = { + new NumberEval(1), + new NumberEval(2), + }; + ValueEval areaEvalX = createAreaEval(xValues); + confirm(SUM_SQUARES, areaEvalX, areaEvalX, 10.0); + confirm(DIFF_SQUARES, areaEvalX, areaEvalX, 0.0); + confirm(SUM_SQUARES_OF_DIFFS, areaEvalX, areaEvalX, 0.0); + + ValueEval[] yValues = { + new NumberEval(3), + new NumberEval(4), + }; + ValueEval areaEvalY = createAreaEval(yValues); + confirm(SUM_SQUARES, areaEvalX, areaEvalY, 30.0); + confirm(DIFF_SQUARES, areaEvalX, areaEvalY, -20.0); + confirm(SUM_SQUARES_OF_DIFFS, areaEvalX, areaEvalY, 8.0); + } + + /** + * number of items in array is not limited to 30 + */ + public void testLargeArrays() { + ValueEval[] xValues = createMockNumberArray(100, 3); + ValueEval[] yValues = createMockNumberArray(100, 2); + + confirm(SUM_SQUARES, createAreaEval(xValues), createAreaEval(yValues), 1300.0); + confirm(DIFF_SQUARES, createAreaEval(xValues), createAreaEval(yValues), 500.0); + confirm(SUM_SQUARES_OF_DIFFS, createAreaEval(xValues), createAreaEval(yValues), 100.0); + } + + + private ValueEval[] createMockNumberArray(int size, double value) { + ValueEval[] result = new ValueEval[size]; + for (int i = 0; i < result.length; i++) { + result[i] = new NumberEval(value); + } + return result; + } + + private static ValueEval createAreaEval(ValueEval[] values) { + String refStr = "A1:A" + values.length; + return new Area2DEval(new AreaPtg(refStr), values); + } + + public void testErrors() { + ValueEval[] xValues = { + ErrorEval.REF_INVALID, + new NumberEval(2), + }; + ValueEval areaEvalX = createAreaEval(xValues); + ValueEval[] yValues = { + new NumberEval(2), + ErrorEval.NULL_INTERSECTION, + }; + ValueEval areaEvalY = createAreaEval(yValues); + ValueEval[] zValues = { // wrong size + new NumberEval(2), + }; + ValueEval areaEvalZ = createAreaEval(zValues); + + // if either arg is an error, that error propagates + confirmError(ErrorEval.REF_INVALID, ErrorEval.NAME_INVALID, ErrorEval.REF_INVALID); + confirmError(areaEvalX, ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID); + confirmError(ErrorEval.NAME_INVALID, areaEvalX, ErrorEval.NAME_INVALID); + + // array sizes must match + confirmError(areaEvalX, areaEvalZ, ErrorEval.NA); + confirmError(areaEvalZ, areaEvalY, ErrorEval.NA); + + // any error in an array item propagates up + confirmError(areaEvalX, areaEvalX, ErrorEval.REF_INVALID); + + // search for errors array by array, not pair by pair + confirmError(areaEvalX, areaEvalY, ErrorEval.REF_INVALID); + confirmError(areaEvalY, areaEvalX, ErrorEval.NULL_INTERSECTION); + + } +} diff --git a/src/testcases/org/apache/poi/hssf/HSSFTests.java b/src/testcases/org/apache/poi/hssf/HSSFTests.java index 7a1d51903a..5b597a67c8 100644 --- a/src/testcases/org/apache/poi/hssf/HSSFTests.java +++ b/src/testcases/org/apache/poi/hssf/HSSFTests.java @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - + package org.apache.poi.hssf; import junit.framework.Test; @@ -25,80 +25,8 @@ import org.apache.poi.hssf.eventmodel.TestModelFactory; import org.apache.poi.hssf.model.TestDrawingManager; import org.apache.poi.hssf.model.TestFormulaParser; import org.apache.poi.hssf.model.TestSheet; -import org.apache.poi.hssf.record.TestAreaFormatRecord; -import org.apache.poi.hssf.record.TestAreaRecord; -import org.apache.poi.hssf.record.TestAxisLineFormatRecord; -import org.apache.poi.hssf.record.TestAxisOptionsRecord; -import org.apache.poi.hssf.record.TestAxisParentRecord; -import org.apache.poi.hssf.record.TestAxisRecord; -import org.apache.poi.hssf.record.TestAxisUsedRecord; -import org.apache.poi.hssf.record.TestBarRecord; -import org.apache.poi.hssf.record.TestBoundSheetRecord; -import org.apache.poi.hssf.record.TestCategorySeriesAxisRecord; -import org.apache.poi.hssf.record.TestChartRecord; -import org.apache.poi.hssf.record.TestDatRecord; -import org.apache.poi.hssf.record.TestDataFormatRecord; -import org.apache.poi.hssf.record.TestDefaultDataLabelTextPropertiesRecord; -import org.apache.poi.hssf.record.TestFontBasisRecord; -import org.apache.poi.hssf.record.TestFontIndexRecord; -import org.apache.poi.hssf.record.TestFormulaRecord; -import org.apache.poi.hssf.record.TestFrameRecord; -import org.apache.poi.hssf.record.TestLegendRecord; -import org.apache.poi.hssf.record.TestLineFormatRecord; -import org.apache.poi.hssf.record.TestLinkedDataRecord; -import org.apache.poi.hssf.record.TestNameRecord; -import org.apache.poi.hssf.record.TestNumberFormatIndexRecord; -import org.apache.poi.hssf.record.TestObjectLinkRecord; -import org.apache.poi.hssf.record.TestPaletteRecord; -import org.apache.poi.hssf.record.TestPlotAreaRecord; -import org.apache.poi.hssf.record.TestPlotGrowthRecord; -import org.apache.poi.hssf.record.TestRecordFactory; -import org.apache.poi.hssf.record.TestSCLRecord; -import org.apache.poi.hssf.record.TestSSTDeserializer; -import org.apache.poi.hssf.record.TestSSTRecord; -import org.apache.poi.hssf.record.TestSSTRecordSizeCalculator; -import org.apache.poi.hssf.record.TestSeriesChartGroupIndexRecord; -import org.apache.poi.hssf.record.TestSeriesIndexRecord; -import org.apache.poi.hssf.record.TestSeriesLabelsRecord; -import org.apache.poi.hssf.record.TestSeriesListRecord; -import org.apache.poi.hssf.record.TestSeriesRecord; -import org.apache.poi.hssf.record.TestSeriesTextRecord; -import org.apache.poi.hssf.record.TestSeriesToChartGroupRecord; -import org.apache.poi.hssf.record.TestSheetPropertiesRecord; -import org.apache.poi.hssf.record.TestStringRecord; -import org.apache.poi.hssf.record.TestSupBookRecord; -import org.apache.poi.hssf.record.TestTextRecord; -import org.apache.poi.hssf.record.TestTickRecord; -import org.apache.poi.hssf.record.TestUnicodeString; -import org.apache.poi.hssf.record.TestUnitsRecord; -import org.apache.poi.hssf.record.TestValueRangeRecord; -import org.apache.poi.hssf.record.aggregates.TestRowRecordsAggregate; -import org.apache.poi.hssf.record.aggregates.TestValueRecordsAggregate; -import org.apache.poi.hssf.record.formula.AllFormulaTests; -import org.apache.poi.hssf.usermodel.TestBugs; -import org.apache.poi.hssf.usermodel.TestCellStyle; -import org.apache.poi.hssf.usermodel.TestCloneSheet; -import org.apache.poi.hssf.usermodel.TestEscherGraphics; -import org.apache.poi.hssf.usermodel.TestEscherGraphics2d; -import org.apache.poi.hssf.usermodel.TestFontDetails; -import org.apache.poi.hssf.usermodel.TestFormulas; -import org.apache.poi.hssf.usermodel.TestHSSFCell; -import org.apache.poi.hssf.usermodel.TestHSSFClientAnchor; -import org.apache.poi.hssf.usermodel.TestHSSFComment; -import org.apache.poi.hssf.usermodel.TestHSSFDateUtil; -import org.apache.poi.hssf.usermodel.TestHSSFHeaderFooter; -import org.apache.poi.hssf.usermodel.TestHSSFPalette; -import org.apache.poi.hssf.usermodel.TestHSSFRichTextString; -import org.apache.poi.hssf.usermodel.TestHSSFRow; -import org.apache.poi.hssf.usermodel.TestHSSFSheet; -import org.apache.poi.hssf.usermodel.TestHSSFSheetOrder; -import org.apache.poi.hssf.usermodel.TestHSSFSheetSetOrder; -import org.apache.poi.hssf.usermodel.TestHSSFWorkbook; -import org.apache.poi.hssf.usermodel.TestNamedRange; -import org.apache.poi.hssf.usermodel.TestReadWriteChart; -import org.apache.poi.hssf.usermodel.TestSanityChecker; -import org.apache.poi.hssf.usermodel.TestSheetShiftRows; -import org.apache.poi.hssf.usermodel.TestWorkbook; +import org.apache.poi.hssf.record.AllRecordTests; +import org.apache.poi.hssf.usermodel.AllUserModelTests; import org.apache.poi.hssf.util.TestAreaReference; import org.apache.poi.hssf.util.TestCellReference; import org.apache.poi.hssf.util.TestRKUtil; @@ -112,113 +40,30 @@ import org.apache.poi.hssf.util.TestSheetReferences; * * @author Andrew C. Oliver acoliver@apache.org */ -public final class HSSFTests -{ +public final class HSSFTests { - public static void main(String[] args) - { + public static void main(String[] args) { junit.textui.TestRunner.run(suite()); } - public static Test suite() - { - TestSuite suite = - new TestSuite("Tests for org.apache.poi.hssf"); - //$JUnit-BEGIN$ - - suite.addTest(new TestSuite(TestBugs.class)); - suite.addTest(new TestSuite(TestCloneSheet.class)); - suite.addTest(new TestSuite(TestEscherGraphics.class)); - suite.addTest(new TestSuite(TestEscherGraphics2d.class)); - suite.addTest(new TestSuite(TestFontDetails.class)); - suite.addTest(new TestSuite(TestHSSFClientAnchor.class)); - suite.addTest(new TestSuite(TestHSSFHeaderFooter.class)); - suite.addTest(new TestSuite(TestHSSFRichTextString.class)); - suite.addTest(new TestSuite(TestHSSFSheetOrder.class)); - suite.addTest(new TestSuite(TestHSSFSheetSetOrder.class)); - suite.addTest(new TestSuite(TestHSSFWorkbook.class)); - suite.addTest(new TestSuite(TestSanityChecker.class)); - suite.addTest(new TestSuite(TestSheetShiftRows.class)); - - suite.addTest(new TestSuite(TestCellStyle.class)); - suite.addTest(new TestSuite(TestFormulas.class)); - suite.addTest(new TestSuite(TestHSSFCell.class)); - suite.addTest(new TestSuite(TestHSSFDateUtil.class)); - suite.addTest(new TestSuite(TestHSSFPalette.class)); - suite.addTest(new TestSuite(TestHSSFRow.class)); - suite.addTest(new TestSuite(TestHSSFSheet.class)); - suite.addTest(new TestSuite(TestNamedRange.class)); - suite.addTest(new TestSuite(TestReadWriteChart.class)); - suite.addTest(new TestSuite(TestWorkbook.class)); - + public static Test suite() { + TestSuite suite = new TestSuite("Tests for org.apache.poi.hssf"); + // $JUnit-BEGIN$ + suite.addTest(AllUserModelTests.suite()); + suite.addTest(AllRecordTests.suite()); suite.addTest(new TestSuite(TestFormulaParser.class)); - suite.addTest(new TestSuite(TestAreaFormatRecord.class)); - suite.addTest(new TestSuite(TestAreaRecord.class)); - suite.addTest(new TestSuite(TestAxisLineFormatRecord.class)); - suite.addTest(new TestSuite(TestAxisOptionsRecord.class)); - suite.addTest(new TestSuite(TestAxisParentRecord.class)); - suite.addTest(new TestSuite(TestAxisRecord.class)); - suite.addTest(new TestSuite(TestAxisUsedRecord.class)); - suite.addTest(new TestSuite(TestBarRecord.class)); - suite.addTest(new TestSuite(TestBoundSheetRecord.class)); - suite.addTest(new TestSuite(TestCategorySeriesAxisRecord.class)); - suite.addTest(new TestSuite(TestChartRecord.class)); - suite.addTest(new TestSuite(TestDatRecord.class)); - suite.addTest(new TestSuite(TestDataFormatRecord.class)); - suite.addTest( - new TestSuite(TestDefaultDataLabelTextPropertiesRecord.class)); - suite.addTest(new TestSuite(TestFontBasisRecord.class)); - suite.addTest(new TestSuite(TestFontIndexRecord.class)); - suite.addTest(new TestSuite(TestFormulaRecord.class)); - suite.addTest(new TestSuite(TestFrameRecord.class)); - suite.addTest(new TestSuite(TestLegendRecord.class)); - suite.addTest(new TestSuite(TestLineFormatRecord.class)); - suite.addTest(new TestSuite(TestLinkedDataRecord.class)); - suite.addTest(new TestSuite(TestNumberFormatIndexRecord.class)); - suite.addTest(new TestSuite(TestObjectLinkRecord.class)); - suite.addTest(new TestSuite(TestPaletteRecord.class)); - suite.addTest(new TestSuite(TestPlotAreaRecord.class)); - suite.addTest(new TestSuite(TestPlotGrowthRecord.class)); - suite.addTest(new TestSuite(TestRecordFactory.class)); - suite.addTest(new TestSuite(TestSCLRecord.class)); - suite.addTest(new TestSuite(TestSSTDeserializer.class)); - suite.addTest(new TestSuite(TestSSTRecord.class)); - suite.addTest(new TestSuite(TestSSTRecordSizeCalculator.class)); - suite.addTest(new TestSuite(TestSeriesChartGroupIndexRecord.class)); - suite.addTest(new TestSuite(TestSeriesIndexRecord.class)); - suite.addTest(new TestSuite(TestSeriesLabelsRecord.class)); - suite.addTest(new TestSuite(TestSeriesListRecord.class)); - suite.addTest(new TestSuite(TestSeriesRecord.class)); - suite.addTest(new TestSuite(TestSeriesTextRecord.class)); - suite.addTest(new TestSuite(TestSeriesToChartGroupRecord.class)); - suite.addTest(new TestSuite(TestSheetPropertiesRecord.class)); - suite.addTest(new TestSuite(TestStringRecord.class)); - suite.addTest(new TestSuite(TestSupBookRecord.class)); - suite.addTest(new TestSuite(TestTextRecord.class)); - suite.addTest(new TestSuite(TestTickRecord.class)); - suite.addTest(new TestSuite(TestUnicodeString.class)); - suite.addTest(new TestSuite(TestUnitsRecord.class)); - suite.addTest(new TestSuite(TestValueRangeRecord.class)); - suite.addTest(new TestSuite(TestRowRecordsAggregate.class)); suite.addTest(new TestSuite(TestAreaReference.class)); suite.addTest(new TestSuite(TestCellReference.class)); - suite.addTest(new TestSuite(TestRangeAddress.class)); + suite.addTest(new TestSuite(TestRangeAddress.class)); suite.addTest(new TestSuite(TestRKUtil.class)); suite.addTest(new TestSuite(TestSheetReferences.class)); - - - suite.addTest(AllFormulaTests.suite()); - suite.addTest(new TestSuite(TestValueRecordsAggregate.class)); - suite.addTest(new TestSuite(TestNameRecord.class)); - suite.addTest(new TestSuite(TestEventRecordFactory.class)); - suite.addTest(new TestSuite(TestModelFactory.class)); - suite.addTest(new TestSuite(TestDrawingManager.class)); - suite.addTest(new TestSuite(TestSheet.class)); - - suite.addTest(new TestSuite(TestHSSFComment.class)); - //$JUnit-END$ + suite.addTest(new TestSuite(TestEventRecordFactory.class)); + suite.addTest(new TestSuite(TestModelFactory.class)); + suite.addTest(new TestSuite(TestDrawingManager.class)); + suite.addTest(new TestSuite(TestSheet.class)); + // $JUnit-END$ return suite; } } diff --git a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls index 97766d6aaa13fb4633854f7b88bdb43c850a7427..6260d878bc89ada3ebe92c7fa615baa1c387cde3 100644 GIT binary patch delta 25973 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 delta 24388 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 aTWYt|N5Txxjnf`=KN=N|tndKa*#0j@oN&(o diff --git a/src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls b/src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls new file mode 100755 index 0000000000000000000000000000000000000000..07eafb414416e98875b4c0a9cd2a78b453a5413a GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png b/src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png new file mode 100755 index 0000000000000000000000000000000000000000..90a915a3a993732fb52cf87c32cf3ab2c64e62b7 GIT binary patch literal 15792 zcmYLQby(Bi_kS-LFktjXH%N{S1vUvmQ3j$S5+elUgE~r)+!%}y2_+R}5{e26CLl37 zB!(iOf`p_Xp_J5q^ZEYq+w*MiXLaBA+;h(Bo_O74!l`3CTw+`R0Pxt_SUR$=VeID< z1f2aUyd6BxzCiu(C-DGKl@_(@aEJX~#@oj6BmhJx0sxr`0DoEREBkk0SOEA*1OSs< z01&;J`y_u5089_tTH>9;3%vIDpvYpPGl&d$iTQJ+f`3BDfY_};PXqw=*KFimZ(YhOVC{!vH@9J8BKXWFVa{vDQ$<@ggHgTY|`naeK$9Ke%r1EbLCGH`=7A=={BcWvz60fVu_GKGkvSiaNV+pQ(N zjOON6MqhX9>TbU|)_M~oFAvG%E5eiW^ONx>DEB85@1ul#qaLbtsFoCEc7McgLY% zk^|KumkdIC#WJnJU&rlpI4aB)MZJ=i=ZzZaV3?^h{PFJl+lfSL+VqqV$0Jr&(Pi7-Poo2kU8F#0^<<0 zS@c2AYJ%;ZNtU@1t*~GRxg}Nali|0cTP7z2Wt*Ihl`5a1DCEydE|MpiHZl5#53R;x z?Q>jogxkS8_L`*;+t5Sd1^-O@X^z$O)d+VmWsuf z>pW1oe^&6s=8TgQ^j%d+YEX;X{}wH_U@{oPJwxB8YSUG>LgF`p%yKJOEhyk z78*@4-s?{{M=DmSB6D2IkvByy`);o<2#&Q-P;95eeXjZJO>8DfNecoDuvR47>;7xu!V5q{kqmkdbm?e5-tsD6|@ z+2f{7JEXQ8cXK_K0Uui2ibWsn?enGi6XM~Ald$3ENjjBS7W^hffT!#PEI^;nfNiJ{ zsV8*AuV23|8pZ4xwe*iO;7K-!mUppo$am79#kOOJu*$QV>W2Hf`?-^IiH%6Q*hyz+ zbU_F%GpS-gPD6vIT?XjxqJub{p|4)Pj1x?#`e;wfpW#A^^VWMC-PllT`DPzW=I?AJ zmNb3Hkd^~G{o!7ACmbF4{P|*@X`}%)C55ZEndbU$DlU6*?`* z@!StBXWpy}rB=?2*9h~&5K(1aY8jy0@@%USy5~6v2-cQqB9eKmpQ|05;HS73%K#g(2p8BH;ef6j+a(c@SNq- z5p=R5%iX}T;adzlOb)iYal@DzUxIA0f8t8}VTjilYMd~81n{X@#3$-eOO>!wto@6) zlh<8Wcld=UyTc)I@){;4Vgy& zCtEoQ!NI}AhQdPWaE1B#fnwx6gUepd^%45imynD0HM7017G@Z%gQ{s3exA+9FavQ-$1nH|i> zRvtuh_vj;6htyOA@MtH?;E)h%FO{c@GDr>b7!VM^2*CfA31b5t5ecTpfBN_ld?n%8vuD(fdy*0oj6hpkTT(MH zv)#nrQ*>2gwEdf-$cqi zYQCpKv1ZFkp$B6JZzWrH3bG(5CaQ10F54^9>^&N^ZMdWS zn74OYNxM53QZxATIK6e@=T9yJjuNTC>u^QAj7a<}c@K;7ZU#V>8#b`d_6rLO6NfZT zN(#~zhc&e;t8hEi;O<)Fut$>iXbfmwPffQavEQ6>OZF^nDD*thA_*perMq&f4&iD* z7_yl9^_A~14wbT9h^hddpEsqXx%nvR=tymtn#HM8ywsc=5o@BeUT zK~?6ubaC+CX!NPNi_pmR;T3bFMp8N9hJ5~wG8V^#!bu`dOqV|8gu?+6U33H#M8HE*dotii_n(mQO!Er5!k0&0jze(QtwGh0zPRkL$tQucFb=a`f!gsLs`e zMd?*LK9%5n%Gmu_LOP{A-J^cLY4zZf7XAG}QyV_{@2EUKjTL(YGu)OUIZC{StQ-j{U2?N zwK<4I$d)TH`&EXlp)$8?FnN^sS;rIDKRLrxJ1O+#mg~uZG|{V#x+54a-T0amT|@{A zp593Y!@ighsyOfRfMyUG?u25PlSiD%R^@X}1o`Je0=H-!L`n$6Ml+xA)d^5FzX%4? zHLy8X|KCM&;K86cT)u)zq`q5bB_T*dg{6y{=UROJI zOd{oqjDzz_naA|7Qw$E$6_BFR2m{i2nI3_eb|(ZORU2Y)rVt%*0~=8CI%}dcfd{w0 z#~CV(TI|%-o-a+-Jrc8@?m6`|7ui3)=eZ3H^o!bk(z&|KEpCywcA3P&_!ztiR%5$k zePMuaK=bNEyDwZnD3YW=J;K#OFXDCTi93rXJDR8vaKP(!gcFn^$7A3H3FwO3iKEx_ z{`?t0a^r%?=+muv5ai%*AI>o%`0`GcC|WT<$m4(vAZF`Q>mykGU5K9_H1wAbbJU`d z%pr+GW5_8pFm4g@7@&H9zee7vsf5DC55pCe2Z;EtXHX9ieM}=Y*E+N^I0`%iXXUf5Tm9nSe_)V~sbymvovdm??-HN3?_+rST>6 z6S244Z!;36fLF1=aWJkZ-6@|BI(6%~O%cL+WO zwOx|mcQs#dEsLBcR+tq$a>fb(POKO4kK9#2f5FQk5`jCq*G;7iuD*$d(&TP$AD&VM zo?W}zf(Xa6r{-Y05!yCc~NUPE;!gFI~{>^}js@U60LJ1#!)jwYR_2BI= zBJ1z%qT53^f1VHN3lgoc`%gpT!|kE4TUDZwGwYa(qdaen+7UC0cQDy^4g~#3G7wpL zKV3b7ne^k(gSuFiMoW`C-q`pk82$5=D*)6}1<>lu#zKp+&=b`hYF)!6H0rU-Q@y~( z#>U*FmX7ST@;6148)Kn|KX5KVR7v_~U@(ySTW>D)D{R8C`-hQ;?G4;U&}a8^o_RjK zLoT5&yi?0EXPI_|?Md99i(Sp#4LEzZ1!rx%f!gU^-3;qx5-?!>+ zay_Zl8{Aj?Z2mrSr3Wf)TaR-%mFX}L8QYHjqVaM!&JNQFlIMDIymaa9k%uw39LjCm zGjhfOA808foncAopjAc7g?9Ckay*Fpg zloyj5-)yQKyZ~N*J;6oBBB2EP(c;ki5YBpwQ}SAQOqvqoqVM6ZKut9Uq&7n zWnhng!pqPeFM?N84h(*{q6`X{E%6zd%AsVGgg6=K!- zbsXGuXi5`%IaHgi^5{4E%@JgdEbUdBbu>Ca?58BrWU1!->J(rkES^xb&;wrUdv&*w~F=pp_bW42jaZJd=G=yUNU1!m^d(6|MY@;@7IEVJk2$W$eGv8P z7xx)~W5##Jq&fonI~&lz>l-!`V%9)~&p=eg%0=Pe?`=Fh@_(x{)TRAeJV`_^gIan= zV3KO-8Om?B+)oM)(pSHE9IP6_CI)#O(NHfH@+@?W`+M_-MDzV;#z~^6t3PBT)cJR5 zaP`X%*QqLTI{8;`7q*Vuj=fEKY_rYf_TWZZ-lN$BgDn2iEZ@+I4XcFOWBZgn<-It8 ziIjt(4kl04XzoaQJ^#=Bzr)n1$FfAQ{E~oFvy^}%w(Fw7^*tiQ{&u3N{e9I)6m4*6 zJxQ-_l-kPzHcvOoVDAE_shyi zUeqcbG@d$axRf~qCQscVjz%?PB1sJ=_k=$2H=Fo$Rhi)Y*1rK0J}( z)Oi%*CBa(!-FC8WC5V$zE%^vQG!uZgi-Te767E_@D7o{Re>tg186pRW)PSpfTSGe> zPrE-H9(x{`6iE+$^zojHIumOdEN4bJ&ncJFmldOJ8PbUBjXbqceo6e;dl%JX`NPMH zaWGV{qI1Ew7UXB8zEsv(?FHwAo@iUOK2azx3szK&H4!ouf25g&bqO?GSJD+7nZDap zZdcy~X1M*Un?(;eC{%f<>-NBJqEkJh~%o0Q`)5KTYMk&h-|%uMCbYSgxw zs}+@vx{Gpk`&O!x`?~x?%>HL~nHEcmxh85LOSDwBKE-GC+-Z^!*Jdq90uLx104VxxzLAEoGn?hr<2p zOgl0=q1aZEoHaG3x^#5h%Muj}iK8o2e#y6c+4X*RhIw!lJ|y;HI_H8yRo=}DK z-}6&(>}})BM24@8#NkrpY>G_KiD5vW880rgO8lc&oJGDbJGXxRlIU{n>6#6y~P_1R<18~L_Lqi}AD@4v`U?f_esTuP|lKgldXQn%Q8)cyH zk>Vhi78V!zLW&O-B&zicz6mL(1-zi(A55PxRk@ij!&L(HiPy#wq+YK2a~F);kvz5@ z3}wA4@jh&FB+iV$=lEa6p}DnUUwt3B8GBe;6iLIl-yOE<*12;nS1(JYu~QLPxJH7=utTjZSOK}!Hm~z!U1iRH zUyhY)OI0&l!yPI1v9VS6KWQCzxdhyXCuFWWVVMZb@$8RBTJC9FG0@Mi3k8|}uJo(| zwHrx!4s4$NdG8Qru?d1T1p{FEb|e&tw_m#WsQ5kSy9J6u4sZUA%yrK9;9 zN9N>izmxDI*5XCOuWqMy>+PJkza7PY?q5_cEIeNluXc@_(Ir^`AX*4$UU!KWTZxWa zu8gqJ5=i(v4@E+qkVg2tRs8h+4W*$fho|#g73Jn=v$V3h|K9Y!vu1WG1f>s_e9KCq z>*3!ddLphIKYQWruZPxWR%^?v3L~?gwd$GAxy$Gt^SrGXS+AK_V$&p`z()4dZsob??gl|mhzPRk?X zZsl9ijw)Pd|CFfB_u47n-C#a@V~|-?q|hDx2{(3UL0zce92g%QIqGe!QRViox?nt8 ziq|5&;6*D!QO855CzF|UQ0~F^p<9NO%3r_LrL;*1FtkuNVybe1l2HdPvJ4VVJhYxxe=R<3HoMLyradA_<{=(s zn)UYA_@@2+A2B=;;}zO9Nr(r!YRHG&;G-{v2Jaf&6-fMud(L_KQMJmQP^~^*RTNa* zBE8~9?-NOl&lQheX02Ix^BYY`yLP{9swlCMXIwwD^{c03zI?uba95ybCK{O|fAkBa)%#nkfu%!k-BI|fFL)2S4*_%6u7hQe5YUKQK~q;PVR+%}teQj; z-&^#m71z|v-NOHNRbp4GI zFdZ(e$XF_PEx+8=`4Hn>HMEs9cQ@AalhDDZDgRT&zB3KHf-%BZo`wi(zr5j>XkF&l znK6L2%&;ziz0z(+uAS2xeR1%*(~VKC?KYypo>(Z`Zy=zollc_czd@?IOfjmZ;O>c} zhoy6G&KE-e9m~=2U@P!dS(#j z;FG5w4{UaBtKY+2;mwzIzbo_S^fA$Rz1jD7d0P{FDiC=9x7fCwUbybu2U`RJ$4(`r zZoXW9l=Y`^92;J|e7^Fin5q6E54`onOWaSKE3OHEj*t^o}ZO9Gffny-iRj1SXsz z0iIl|LiZuU>$d0x`Ej5b@Xi<0G5YwN`SQ@8>7AQ)VCtlf{h(||8h6zVpKsxg-LS}x z;Ao+5E5#2T5(Vtdx+&XFC_XMlAv=lz_(t zuMkyW(#H3SLJKCkC3KqrG<1L~!(H;!C5h6rK>5UCu|}3-VDw7;YWo;355ith_DsC* z@Kn)bZ|gG=pO(5lkyb=&k~n*LL#Jw8QF@TxAD3qWt5AzwmUpEOrevQ%yv;=O-d*2ES^f zBGrw|4t|b6W$L+30dUWW4-S8VL5-MZn5Aozae+$x&B4OUr5tf_9LdwcvyG0)=*B1d zrB?Wl^tLcxt7ku-E@{)6)ZF+-i(%sJh?dUZVyF848o{=oeRgpRg4TV?j?mhRc;~A6 z?#--3(bg3?ue^}C1jo&*0^keHL3Q@kP+~pT6##)BLC3ge35UNI{9^e9#~{!vLC0yX5^(9% zL+K^Eztc~hIWLbO!#Ql`j+3_ZUTuNzTHRO)&}l#8?lH}e8%%aWEfXFwZm~v=)i(xH#R-bNi zjoncr8dN%L#txOAXHlZhk`}=5?VpY(Pi9!yZpzED+ZThTQozV>C|}(V2t7xxa-|)( zJWGW`a3SgRrt8d9o;N_*%=@)TYg(!X25HXPi@*8mRi$pvYJO7&F#{(Yi3`*ACxP{b zxC72mX@+<}Zym9`OraSEeO{qIv@fZ-7{Klu6)j_S{><`U(WB7zacUnQ-iyb3SJ6A6 z_rC#|2Z{EE1Hho4ThK+=@%T6(iBI4de11+i4v28+$l1h?sS@P*24}z(9^lKi(1E{| z4;yMtM)|o+;&R~pT>pxCV>Yl`*RNd{JbQi5==guA*V$mbBSw6ADrIL#WH=|U`H|E! zylxVMxe34QIKJ?ixEEDr@)vaBsvm-~YRm7kL7>La)Ge#sFw(m-GqKJ1wXZo zi#C098t+vNlv(lqyb8JLkpz@!5Cc=+zuBGVKeLz24M0(s<%9<5`|3umT*s zzt3WQ_+a7hLmmx2D9oXK>sLD`fH=|(!T|$Hj(Fk^>#5+yG|B2-z)Inz3rZS}B44s6kRTf+1+F{#(^i3U^md;l6V>lxKx9y@?iqUAkUtc?_4z>mrBJ4q5vzo2sds4 z2<5T8Y$l@nz6{dKJo-4}MWNX~>&4TQ_fpZ@8L5B*-@mn!7l!uTr-r8xTD!YEt<@v$ zb4|4Xlmf04g!3}kN=bbR#~yFy)Vd8UnIGIa+YBl*otQ}&Go=?qZfSNkS#z=#`E`85 z*T0j&96sheR(uSJOiQEBz!BQa2uUUvcLq8MzUB$Mp5l$J1h+mWDN3COUh6=@mL8pg|BWwXc4xa)>NJjzTbskfjjf!P?!kQOq-Wv_Vyl>POa#p=^DMtj+X z>0{FLA=ZzMk_7&2cCTbW%Mni=NqY0DrNBdBWJ!T%;y;xpKt;#%6u3$oXUlnQiwa*| za84}xw}4km;(#jt|1NH@%zxv|zxyg*2Kv~Yv4_=<#PR%G_p2}}Y^ANIJE3F@yu!j{ z_uT@4nzpzXc-U#)p5*y9HCghTZJ0h)L%s67=-`gxh}|PocGsWs4_M6ZS0};3uCtru zCZ%wW&dih-i;&Z&R2-8JapFz~(qr@uuE)_}PmX*!srm0OY{LK)W4+`=;_(p7){3_w z$G8kkgc)1MYK&?;l1GC?g&C;eDN6NgHMdf0JY4?uibvQim;K%3bZ7Zu7AseSnl9P` zN-Ict<^;D=DSN|RGzpwLRez0Vw^|lK-5C#=`zYt6!ggo9)l4q%*7Bs3dkd|&*x^F1 zkr&^&C#m|6iy#DV#JNS0AsLg%9Ba@~40~ct{7+;67tNxgqfxEDSNB-spCB$-@tCzd`kyZTM&2&oQ`B#;^IX?o;JtY@+ z`3?>F^|xOZEz<+(e}x4dX?E(X$5ahRk2=QK8IUJ) zMDscAs|zyOLJXfGYLz-o6xBh!BI%s1O z8v`6UA<0%c)!dP7qaP(-ccr9`ro8b;saWZ10-g7W|IR1n#g>zHs-zhx!?(0sJ6>t) zd_u>_gubC~oTS%=p#ipPBG^kzznu2=ZM?j&le9O4-ozdHCS;a5%%=xs1L|%z-)n&W z{v+Z(gA?5O{xe?fB_0h;le9R);SzPSy{48b%qCJXQ=L#`cW}Ju$X74XV~xRLuVX{I z1XAwnIsX%+cS>|Hqsj0;a7%U0ou#h{=e4Lb$*W^UFU5=U-C)u)|L~7 zep^@UOl|Jb6Lr{&{!sXdmWSM|>6#}qLw4N`KFyC$O%7(hbPBD%d^vPeXoHP$JoimD zrmSpCxlBW(wE(wK8P(&?W7%qXkHg}Y0s7RNTnLDL6aVSKu*D>45)gLI21J+8P))hS z>yPqE`o}Qa%ltZG6BREecJ!w!ieVr~{jZxxk1l&C`I3~sD_YO&Nb5f-zO$w11>9;O zCYtGJpmqrnnyL|TJpm1v+>{P22l*!gI)LN`?^j{+%6tv}AD@jh>%@E@=6dhifOeT0 z_~&<-`%6cWjOO zP&w8M(n+<2g66NOF&F4`K~qm4)SdkP@T9mZoQ+@frtH&rwcp8KD7?+|hMyFO^DX>J z?L<%ZHsF7Wr|iXH==&r8EKN>WuiIj&;L!XMeqQzhmc-h@W8t8pb3X)AE;BoY=T}wN z>nrIe9jNGkQ%M?3;7-0e`RFnA)b&;@*w}F`xp+0XxB1&!HVBue{!E)7+FX%}|K~$y zj%yX)Ejs83nJRp|p`$HG`P3fk#q+eEQcOpOBPX0VxYz?StX1b#oW10QUI$A>oAZd^ zu9ff?i@x^d+DBTOns(lu!T&RZ375vXev6%)R)DD)%sk}foF*4b`hh}wL#n4U+SV32 z0J`yTeWXL1rkfWLaEn;|qkPR20{r7R44)H((m!1FJeDB{{`O868y=3j{5p;%nV%-- zs8uEoGwH@zKtjzqEs=U8Q0)nL=p$|6{&NbV>|JF4rU!Ew>vbv+G>NZ02Seg+>93oJ zyq>t1^e5>O4*+If<2VH1a6&lAo-?j}8l$JQPJUskZ!4Boy>?t_JY%W5_OrdZAHeR> z1CMVWs+ga7X8eoS_0da?TO;r27NY*DckN0EdGJ}H&?q~Fe3XOA5cytd9M z_~XY@4+l+-4&N6%v0NO#KW(@d6O6_;gU^!f@^Wc*Aj?7fD1#hT5#fc;I zf!&M%E)r7_O6TS#4kr@C$pSn}V!D2Qe%o&PMxe^q#7c<4>*wxPNffbD&YEu@>;Kz< z4YP1O@~(3VGW~Hfl7ct6%h~PGsJ)+jCby=^X&1~%6TtRIND<&aeyhf~*@N!c5At6E zvA6#a)lPq6npZUm^iGUvb%buFwsgheh|FQNn-3qo75QT^gN?Hu+{o zuW~n|FZl+CQJGU`f4_I0iE#ff`%WO#MBLw9WY#Z(h3j~3arJr$$3ICnPvbkD{NHM1-E_J z_+4~ggMMa}-VQdy`teTdz~n$$T6`@{kX=knbWH2&%r73=ipO_{c^fF8m@?ddg{-Zd zj-pnl@XkvA~|?*X@wlUZE?#PFrHK7vyOydMWal%kNcnAi_?Sv z*R)pM+ib00H9mfpjX!CTJGp#+5$P4Djw~AWK#zP;X>3DD?j8I|pKSVMk1UMg)KPgB zpu6<#UE<{5o99S3m=%unWx@GtwoJDQqYE9oo7%vI^N1L4`RSKXv3~(h%qRXVYh0w; zs+G#Tn0oL|Q0#YI|7Ts;>Y80F_T>qkf`-3^lkKO^(%yL(k*)rC{2oKJz_ko zBUs8UnI{-U1t$`kzIRcZdMr7dPc~3J7C2J~1XJ!>B1y6`jJvjk@0;5Uabh~Yc8=rq z_Y`JnwG+VJ;`R%;YEz)07vwd8pEu)vp7&Z#w1DuO!64)6)R7U^21uIkbEHQ@49B<; z^5+G!=--Hf|6tojm};Xu^vHRZZl%utMIziw2~ME1V8gdq2fDg@e>_W_ zcP)MLQ*I%0Ja*3$C7A0&IGs4Ur=p<1ApQ8LO}pv0sV!mhC*E_p&T3-u42~aB3y=Ku z_07b9D)VG7{hm_7s(w7~geNhR^S2?hUgg8ZG9(-GYQt1jUb5H9e^RfEA8#EWM=r?& zs0!o9X!mkAbzP0F-^E`zSBQ7}kkvHUnncM@;5kwYt$iV+X9E5CDJE)*f3$LE?T4X} z4o=>BX{mfd%>X|f!j;0MqXN3=z?7V2hXI7#tzV0N$agOay+*FhNVKf1tY8AyjnI7_ z-gt}&)a?Se_N-uNq|W@$lX1~AJmNCRnAfWOPH_~2E0pCMj|V2q*#rc3jGGn~)TF_e zVnc{8dXc0IT3zGR+1uZuGpq1fBon0QMCj6%sYI^L=kBQz6= z*@nhrswq>cMq)}Hjlg10`c;fsLu~KrX4_@jqUfe>UCf4%$^m6^<;7Lx&eHuYyRa0CRUM6V9;;kS0=oJhl=v~7i=%P*-R@b>0s3_SYLCVUpLeZ_3wGkkMazUAidRkY@j24tYyOYJ>x zqVG0eyB$MSD(fa1b3nC4ZS$3|K=QlNd2rEQR;(_mYTr9U#P?!`lNZFG8TF$t-DBT3 zBPTv^5^(e4t9A1KkfSkS19Qvb7PIzIn(e+sj7ByTN`}rE`GbjngKd<6`#(=!{Fj(N zIgS65yj`nA|5oI9`q10ip*On7XE}YuB+~4~31Wx(Z#IRse|2WpoVH@l(%u863L9CF z#Xc5ebYD7393v?2|KNTmB|CxNc@C^CE(lR(N?ajI_Oz#%9$PWqNTyt{yotB{z#Ank z>A#JF;@ZNFALrVpUAzs<5BV|mKhxK|2n#Tsbgmymooqxs}3qAyXRu5e^J(5}IGBNjpg%nSZ@+3~4@iYpX71%pv#I$;+t*wnbKKyee^$A`H;FfBk^M2+Oz(h& zu0;=|);P=0D0m5JbQCX$U=yY%xOY0!2q<7<0RLGSLG~?O9gDMrv+=k)Sk)ha)cYD& zVb3MD1JUP2^xVA7wLD(aTp81*2R~+U7VZH0%9BI^U;Hzfj^yZz2EV&vK?!>(S{cjJ zp!Yxwc>9)<-6H6;V~U*gdg~#;jw6#^vh&unK|DHST5FlmL%L@7-#>GhYfB?dq`hev zEjD$D%?M`EQ@&I)KAmes_l@nfGuqp`=?@>mmy6?`f35>9rI3}r9D+wgAQ`-x@jTrU zoSa$rIALrvj5!6{gaq{+DR=E+phT?S7UMvO0Jpduiv5ElAdzTujFB@7BCZ#Uu09`& zjoM!?nEbu*82Q{cYGU$e0GL4B0~=JHUAfV*`fJVuE~zL2m&M1>JXn7*4F^*`+Xsoa zCb!HD;QZPc;^H~}(NcWl(gT0sZUI)8irk|Dm9p(93A-w3wv2l=y`4m2p0VI*iPuXZ zzg#+RwJU&`j)Kg2RGIYl#s@#RdtWSaDvpIQ!nkYi?H)j#^;w?VtQD=wd%trhKi+R- zNPK9X2yE_P^jMDJe;>Aw(UEw+p4`*~=;^@ty?wmkU->Y%b~Y08CtK`g{~NNoJ9|(&&VrxxFaba$2lr`MWobb(3(@{C@U^cz^W7wQ#`q*3>v9{+;$dNO1#?@^9;p3(4Ufsqx z-XbCY0}AoAJ%XOlj-yHz#r2EU7dSAZ3QhU8w!yfElYoV5yTUHEV#v5XEcj=yn zgdoM9h{Va=#~lh*DhR~dz8~|Pua8mWzHUoXNMVz_O-bB^^cZa5;R6aK@rTwIc5yBh z8H*lNFxMTej>!z|H8xJt#uNFC;M-6ObO&(RzR?$2$y$nsXd zeb+}ma5E$6%|~Ca?VWwm7FFW2jgR+NEUlJb88%|xO8jMzW_Lv4Vdsg%E}Q0UKNO@J z_x!^5b5b?=%GNF1j~)(^n;zxX`DZ;jZ*ZB3yf`%p(u}(t;Qs_s#;4j`+DX^IUrTOb z@GLcoKgD9}b{L(cKa=NZg6A5@i)?&4OT6OiJ3;gYr}M<_?Bq_e>vKpxk3Hz{L)DKx z$l8=t+>Mv@0EgE2zV}GPlqgeh`lLo&h+1Hnxqp>d*&5Vk&0dZTMpKJU%!sprH9-rG za9WwlZ4Da5yCY4z+~o~d-Nk0ha|KP7GaL|ekWt*Rp`4)lvcI= zGlk6r%Ke18sl*2p3-@A?qAPpe>rDW^_Y5B)n=6n>)y6BQke+1rN%o$3+v+qNIH1E&yFYUF2w zKm~-Zh?)1Idupg&i3Bu^LBH;NsSc74$AY0o#$PZcirbWZYXi9AArZKHng_B6InF-2 zvu~vznG7mye1)ysZ|)}Tc6rc_X~kgcZt!6uef|8r*#O{+;O2&`)Ia3HNmoFGo9IP( z|D=kS-&i=^W91kaNN+$aTiiYbr3ZO1=RY=4IhtB%SmiiyMtbfYylY+}{%HIyylVz1 zDIXJFb3m2<%Nid?vJUn^*KZPAlG%yvs&qIyBN>)weV%>j#LshkHJ>v1_gf=w@8)FN zw{fV3V{)B;XjS)J$#+a-@JPX&9nVt3Vo$%tQ z2qXgOV=uui_wqSk$HQK7fd@}-!vb&a10u!d$&A zE(!exf{+=_e@K*>SQ%yN>f;t6$EtLwe{dZKjd7wCtpX;kN zZb{p?m>dkaTtpl`1=725%97owzyXurLy@)7oY5iJc>xzycC~Yhb)7`oHV>J#L)Ngt znZ>dBm$5zDdk_8l(xpqp8bA6yIvw!gwOQ)P*Hy?rg(w-J{NsxA^G?y zjK&Z%`OXbnv|*b8OZRhd6mBF$b$&2&e^>i7kH5EIHc&3j6)+|TVcM#_^6dqGk{P{u z@vwpopAGMzGk8FeVci-$ub}(eAjYf5gxfSuWKcsJt}*OG1g=Ja^Ep341R$Ox)_%-@ zs62F;6dc5yx{_ziF6En_WaR5+oKa*QOrH67Q+eS*+))n#{Oj#wKbtS=0BqVYW#PvU z2(JwR`k*anQ;SXF|4p|!UmJR;TQkJ52Rs`CCu&0Ofpo=7r1IpOrsxB5DBufZ3KF6e z^&X~KH3M#Xq?&4!WIJdx)T^Nm!DBF4hE@^acw#(=O`&>ofRlxiLciai7l;6Y`@x99hHA8MX;m;D@ zBQHO&o2;`l66nDE#QyBQ6R)lG!3c2&8kFlC<}Bwg@yy&f=5UVAHLldwXHSbuUL=`R ziqhgrh#tEEEM3!|KQ65jl}CG|Bg^~stOf+1Gn=g~)apy(qT4(aV*V%FqardqDf z`$`+IG(I>ys@;!g`w>q=dCD>&0ZS+daYMOmK;P-# z4QdLW%Jnr({HK6>8{^16LPpdWd-$&aNqmld2rzhM#NNuZTU_G7AGTJfET11G#{C~j$`!5v literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java index e7400bc351..7b42bf333f 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -18,27 +18,34 @@ package org.apache.poi.hssf.model; -import java.util.List; - +import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; import org.apache.poi.hssf.record.formula.AddPtg; +import org.apache.poi.hssf.record.formula.AreaPtg; import org.apache.poi.hssf.record.formula.AttrPtg; import org.apache.poi.hssf.record.formula.BoolPtg; +import org.apache.poi.hssf.record.formula.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.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.NotEqualPtg; import org.apache.poi.hssf.record.formula.NumberPtg; +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.ReferencePtg; 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.usermodel.HSSFCell; @@ -64,15 +71,25 @@ public class TestFormulaParser extends TestCase { public void tearDown() { } + /** + * @return parsed token array already confirmed not null + */ + private static Ptg[] parseFormula(String s) { + FormulaParser fp = new FormulaParser(s, null); + fp.parse(); + Ptg[] result = fp.getRPNPtg(); + assertNotNull("Ptg array should not be null", result); + return result; + } public void testSimpleFormula() { - FormulaParser fp = new FormulaParser("2+2;",null); + FormulaParser fp = new FormulaParser("2+2",null); fp.parse(); Ptg[] ptgs = fp.getRPNPtg(); assertTrue("three tokens expected, got "+ptgs.length,ptgs.length == 3); } public void testFormulaWithSpace1() { - FormulaParser fp = new FormulaParser(" 2 + 2 ;",null); + FormulaParser fp = new FormulaParser(" 2 + 2 ",null); fp.parse(); Ptg[] ptgs = fp.getRPNPtg(); assertTrue("three tokens expected, got "+ptgs.length,ptgs.length == 3); @@ -85,7 +102,7 @@ public class TestFormulaParser extends TestCase { public void testFormulaWithSpace2() { Ptg[] ptgs; FormulaParser fp; - fp = new FormulaParser("2+ sum( 3 , 4) ;",null); + fp = new FormulaParser("2+ sum( 3 , 4) ",null); fp.parse(); ptgs = fp.getRPNPtg(); assertTrue("five tokens expected, got "+ptgs.length,ptgs.length == 5); @@ -94,7 +111,7 @@ public class TestFormulaParser extends TestCase { public void testFormulaWithSpaceNRef() { Ptg[] ptgs; FormulaParser fp; - fp = new FormulaParser("sum( A2:A3 );",null); + fp = new FormulaParser("sum( A2:A3 )",null); fp.parse(); ptgs = fp.getRPNPtg(); assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2); @@ -103,7 +120,7 @@ public class TestFormulaParser extends TestCase { public void testFormulaWithString() { Ptg[] ptgs; FormulaParser fp; - fp = new FormulaParser("\"hello\" & \"world\" ;",null); + fp = new FormulaParser("\"hello\" & \"world\" ",null); fp.parse(); ptgs = fp.getRPNPtg(); assertTrue("three token expected, got " + ptgs.length, ptgs.length == 3); @@ -276,20 +293,21 @@ public class TestFormulaParser extends TestCase { } public void testMacroFunction() { - Workbook w = new Workbook(); + Workbook w = Workbook.createWorkbook(); FormulaParser fp = new FormulaParser("FOO()", w); fp.parse(); Ptg[] ptg = fp.getRPNPtg(); - AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[0]; - assertEquals("externalflag", tfunc.getName()); - - NamePtg tname = (NamePtg) ptg[1]; + // the name gets encoded as the first arg + NamePtg tname = (NamePtg) ptg[0]; assertEquals("FOO", tname.toFormulaString(w)); + + AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[1]; + assertEquals("externalflag", tfunc.getName()); } public void testEmbeddedSlash() { - FormulaParser fp = new FormulaParser("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\");",null); + FormulaParser fp = new FormulaParser("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\")",null); fp.parse(); Ptg[] ptg = fp.getRPNPtg(); assertTrue("first ptg is string",ptg[0] instanceof StringPtg); @@ -589,4 +607,234 @@ public class TestFormulaParser extends TestCase { }; assertEquals("NA()", FormulaParser.toFormulaString(book, ptgs)); } + + public void testPercent() { + Ptg[] ptgs; + ptgs = parseFormula("5%"); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), IntPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + + // spaces OK + ptgs = parseFormula(" 250 % "); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), IntPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + + + // double percent OK + ptgs = parseFormula("12345.678%%"); + assertEquals(3, ptgs.length); + assertEquals(ptgs[0].getClass(), NumberPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + assertEquals(ptgs[2].getClass(), PercentPtg.class); + + // percent of a bracketed expression + ptgs = parseFormula("(A1+35)%*B1%"); + assertEquals(8, ptgs.length); + assertEquals(ptgs[4].getClass(), PercentPtg.class); + assertEquals(ptgs[6].getClass(), PercentPtg.class); + + // percent of a text quantity + ptgs = parseFormula("\"8.75\"%"); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), StringPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + + // percent to the power of + ptgs = parseFormula("50%^3"); + assertEquals(4, ptgs.length); + assertEquals(ptgs[0].getClass(), IntPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + assertEquals(ptgs[2].getClass(), IntPtg.class); + assertEquals(ptgs[3].getClass(), PowerPtg.class); + + // + // things that parse OK but would *evaluate* to an error + + ptgs = parseFormula("\"abc\"%"); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), StringPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + + ptgs = parseFormula("#N/A%"); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), ErrPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + } + + /** + * Tests combinations of various operators in the absence of brackets + */ + public void testPrecedenceAndAssociativity() { + + Class[] expClss; + + // TRUE=TRUE=2=2 evaluates to FALSE + expClss = new Class[] { BoolPtg.class, BoolPtg.class, EqualPtg.class, + IntPtg.class, EqualPtg.class, IntPtg.class, EqualPtg.class, }; + confirmTokenClasses("TRUE=TRUE=2=2", expClss); + + + // 2^3^2 evaluates to 64 not 512 + expClss = new Class[] { IntPtg.class, IntPtg.class, PowerPtg.class, + IntPtg.class, PowerPtg.class, }; + confirmTokenClasses("2^3^2", expClss); + + // "abc" & 2 + 3 & "def" evaluates to "abc5def" + expClss = new Class[] { StringPtg.class, IntPtg.class, IntPtg.class, + AddPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class, }; + confirmTokenClasses("\"abc\"&2+3&\"def\"", expClss); + + + // (1 / 2) - (3 * 4) + expClss = new Class[] { IntPtg.class, IntPtg.class, DividePtg.class, + IntPtg.class, IntPtg.class, MultiplyPtg.class, SubtractPtg.class, }; + confirmTokenClasses("1/2-3*4", expClss); + + // 2 * (2^2) + expClss = new Class[] { IntPtg.class, IntPtg.class, IntPtg.class, PowerPtg.class, MultiplyPtg.class, }; + // NOT: (2 *2) ^ 2 -> int int multiply int power + confirmTokenClasses("2*2^2", expClss); + + // 2^200% -> 2 not 1.6E58 + expClss = new Class[] { IntPtg.class, IntPtg.class, PercentPtg.class, PowerPtg.class, }; + confirmTokenClasses("2^200%", expClss); + } + + private static void confirmTokenClasses(String formula, Class[] expectedClasses) { + Ptg[] ptgs = parseFormula(formula); + assertEquals(expectedClasses.length, ptgs.length); + for (int i = 0; i < expectedClasses.length; i++) { + if(expectedClasses[i] != ptgs[i].getClass()) { + fail("difference at token[" + i + "]: expected (" + + expectedClasses[i].getName() + ") but got (" + + ptgs[i].getClass().getName() + ")"); + } + } + } + + public void testPower() { + confirmTokenClasses("2^5", new Class[] { IntPtg.class, IntPtg.class, PowerPtg.class, }); + } + + private static Ptg parseSingleToken(String formula, Class ptgClass) { + Ptg[] ptgs = parseFormula(formula); + assertEquals(1, ptgs.length); + Ptg result = ptgs[0]; + assertEquals(ptgClass, result.getClass()); + return result; + } + + public void testParseNumber() { + IntPtg ip; + + // bug 33160 + ip = (IntPtg) parseSingleToken("40", IntPtg.class); + assertEquals(40, ip.getValue()); + ip = (IntPtg) parseSingleToken("40000", IntPtg.class); + assertEquals(40000, ip.getValue()); + + // check the upper edge of the IntPtg range: + ip = (IntPtg) parseSingleToken("65535", IntPtg.class); + assertEquals(65535, ip.getValue()); + NumberPtg np = (NumberPtg) parseSingleToken("65536", NumberPtg.class); + assertEquals(65536, np.getValue(), 0); + + np = (NumberPtg) parseSingleToken("65534.6", NumberPtg.class); + assertEquals(65534.6, np.getValue(), 0); + } + + public void testMissingArgs() { + + Class[] expClss; + + expClss = new Class[] { ReferencePtg.class, MissingArgPtg.class, ReferencePtg.class, + FuncVarPtg.class, }; + confirmTokenClasses("if(A1, ,C1)", expClss); + + expClss = new Class[] { MissingArgPtg.class, AreaPtg.class, MissingArgPtg.class, + FuncVarPtg.class, }; + confirmTokenClasses("counta( , A1:B2, )", expClss); + } + + public void testParseErrorLiterals() { + + confirmParseErrorLiteral(ErrPtg.NULL_INTERSECTION, "#NULL!"); + confirmParseErrorLiteral(ErrPtg.DIV_ZERO, "#DIV/0!"); + confirmParseErrorLiteral(ErrPtg.VALUE_INVALID, "#VALUE!"); + confirmParseErrorLiteral(ErrPtg.REF_INVALID, "#REF!"); + confirmParseErrorLiteral(ErrPtg.NAME_INVALID, "#NAME?"); + confirmParseErrorLiteral(ErrPtg.NUM_ERROR, "#NUM!"); + confirmParseErrorLiteral(ErrPtg.N_A, "#N/A"); + } + + private static void confirmParseErrorLiteral(ErrPtg expectedToken, String formula) { + assertEquals(expectedToken, parseSingleToken(formula, ErrPtg.class)); + } + + /** + * To aid readability the parameters have been encoded with single quotes instead of double + * quotes. This method converts single quotes to double quotes before performing the parse + * and result check. + */ + private static void confirmStringParse(String singleQuotedValue) { + // formula: internal quotes become double double, surround with double quotes + String formula = '"' + singleQuotedValue.replaceAll("'", "\"\"") + '"'; + String expectedValue = singleQuotedValue.replace('\'', '"'); + + StringPtg sp = (StringPtg) parseSingleToken(formula, StringPtg.class); + assertEquals(expectedValue, sp.getValue()); + } + + public void testPaseStringLiterals() { + confirmStringParse("goto considered harmful"); + + confirmStringParse("goto 'considered' harmful"); + + confirmStringParse(""); + confirmStringParse("'"); + confirmStringParse("''"); + confirmStringParse("' '"); + confirmStringParse(" ' "); + } + + public void testParseSumIfSum() { + String formulaString; + Ptg[] ptgs; + ptgs = parseFormula("sum(5, 2, if(3>2, sum(A1:A2), 6))"); + formulaString = FormulaParser.toFormulaString(null, 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); + assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString); + } + public void testParserErrors() { + parseExpectedException("1 2"); + parseExpectedException(" 12 . 345 "); + parseExpectedException("1 .23 "); + + parseExpectedException("sum(#NAME)"); + parseExpectedException("1 + #N / A * 2"); + parseExpectedException("#value?"); + parseExpectedException("#DIV/ 0+2"); + + + if (false) { // TODO - add functionality to detect func arg count mismatch + parseExpectedException("IF(TRUE)"); + parseExpectedException("countif(A1:B5, C1, D1)"); + } + } + + private static void parseExpectedException(String formula) { + try { + parseFormula(formula); + throw new AssertionFailedError("expected parse exception"); + } catch (RuntimeException e) { + // TODO - catch more specific exception + // expected during successful test + return; + } + } } diff --git a/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java b/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java new file mode 100755 index 0000000000..b1acfeafa1 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java @@ -0,0 +1,104 @@ +/* ==================================================================== + 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; + +import org.apache.poi.hssf.record.formula.AllFormulaTests; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Collects all tests for package org.apache.poi.hssf.record. + * + * @author Josh Micich + */ +public class AllRecordTests { + + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record"); + + result.addTest(AllFormulaTests.suite()); + + result.addTestSuite(TestAreaFormatRecord.class); + result.addTestSuite(TestAreaRecord.class); + result.addTestSuite(TestAxisLineFormatRecord.class); + result.addTestSuite(TestAxisOptionsRecord.class); + result.addTestSuite(TestAxisParentRecord.class); + result.addTestSuite(TestAxisRecord.class); + result.addTestSuite(TestAxisUsedRecord.class); + result.addTestSuite(TestBOFRecord.class); + result.addTestSuite(TestBarRecord.class); + result.addTestSuite(TestBoundSheetRecord.class); + result.addTestSuite(TestCategorySeriesAxisRecord.class); + result.addTestSuite(TestChartRecord.class); + result.addTestSuite(TestChartTitleFormatRecord.class); + result.addTestSuite(TestCommonObjectDataSubRecord.class); + result.addTestSuite(TestDatRecord.class); + result.addTestSuite(TestDataFormatRecord.class); + result.addTestSuite(TestDefaultDataLabelTextPropertiesRecord.class); + result.addTestSuite(TestDrawingGroupRecord.class); + result.addTestSuite(TestEmbeddedObjectRefSubRecord.class); + result.addTestSuite(TestEndSubRecord.class); + result.addTestSuite(TestEscherAggregate.class); + result.addTestSuite(TestFontBasisRecord.class); + result.addTestSuite(TestFontIndexRecord.class); + result.addTestSuite(TestFormulaRecord.class); + result.addTestSuite(TestFrameRecord.class); + result.addTestSuite(TestHyperlinkRecord.class); + result.addTestSuite(TestLegendRecord.class); + result.addTestSuite(TestLineFormatRecord.class); + result.addTestSuite(TestLinkedDataRecord.class); + result.addTestSuite(TestMergeCellsRecord.class); + result.addTestSuite(TestNameRecord.class); + result.addTestSuite(TestNoteRecord.class); + result.addTestSuite(TestNoteStructureSubRecord.class); + result.addTestSuite(TestNumberFormatIndexRecord.class); + result.addTestSuite(TestObjRecord.class); + result.addTestSuite(TestObjectLinkRecord.class); + result.addTestSuite(TestPaletteRecord.class); + result.addTestSuite(TestPaneRecord.class); + result.addTestSuite(TestPlotAreaRecord.class); + result.addTestSuite(TestPlotGrowthRecord.class); + result.addTestSuite(TestRecordFactory.class); + result.addTestSuite(TestSCLRecord.class); + result.addTestSuite(TestSSTDeserializer.class); + result.addTestSuite(TestSSTRecord.class); + result.addTestSuite(TestSSTRecordSizeCalculator.class); + result.addTestSuite(TestSeriesChartGroupIndexRecord.class); + result.addTestSuite(TestSeriesIndexRecord.class); + result.addTestSuite(TestSeriesLabelsRecord.class); + result.addTestSuite(TestSeriesListRecord.class); + result.addTestSuite(TestSeriesRecord.class); + result.addTestSuite(TestSeriesTextRecord.class); + result.addTestSuite(TestSeriesToChartGroupRecord.class); + result.addTestSuite(TestSheetPropertiesRecord.class); + result.addTestSuite(TestStringRecord.class); + result.addTestSuite(TestSubRecord.class); + result.addTestSuite(TestSupBookRecord.class); + result.addTestSuite(TestTextObjectBaseRecord.class); + result.addTestSuite(TestTextObjectRecord.class); + result.addTestSuite(TestTextRecord.class); + result.addTestSuite(TestTickRecord.class); + result.addTestSuite(TestUnicodeNameRecord.class); + result.addTestSuite(TestUnicodeString.class); + result.addTestSuite(TestUnitsRecord.class); + result.addTestSuite(TestValueRangeRecord.class); + return result; + } +} diff --git a/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java b/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java index fca5bae545..3727989efb 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java @@ -29,15 +29,25 @@ import junit.framework.TestCase; * * @author Andrew C. Oliver (acoliver at apache dot org) */ -public class TestSupBookRecord - extends TestCase -{ +public final class TestSupBookRecord extends TestCase { /** * This contains a fake data section of a SubBookRecord */ - byte[] data = new byte[] { - (byte)0x04,(byte)0x00,(byte)0x01,(byte)0x04 + byte[] dataIR = new byte[] { + (byte)0x04,(byte)0x00,(byte)0x01,(byte)0x04, }; + byte[] dataAIF = new byte[] { + (byte)0x01,(byte)0x00,(byte)0x01,(byte)0x3A, + }; + byte[] dataER = new byte[] { + (byte)0x02,(byte)0x00, + (byte)0x07,(byte)0x00, (byte)0x00, + (byte)'t', (byte)'e', (byte)'s', (byte)'t', (byte)'U', (byte)'R', (byte)'L', + (byte)0x06,(byte)0x00, (byte)0x00, + (byte)'S', (byte)'h', (byte)'e', (byte)'e', (byte)'t', (byte)'1', + (byte)0x06,(byte)0x00, (byte)0x00, + (byte)'S', (byte)'h', (byte)'e', (byte)'e', (byte)'t', (byte)'2', + }; public TestSupBookRecord(String name) { @@ -47,36 +57,67 @@ public class TestSupBookRecord /** * tests that we can load the record */ - public void testLoad() - throws Exception - { + public void testLoadIR() { - SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, (short)data.length, data)); - assertEquals( 0x401, record.getFlag()); //expected flag + SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, dataIR)); + assertTrue( record.isInternalReferences() ); //expected flag assertEquals( 0x4, record.getNumberOfSheets() ); //expected # of sheets assertEquals( 8, record.getRecordSize() ); //sid+size+data record.validateSid((short)0x01AE); } - + /** + * tests that we can load the record + */ + public void testLoadER() { + + SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, dataER)); + assertTrue( record.isExternalReferences() ); //expected flag + assertEquals( 0x2, record.getNumberOfSheets() ); //expected # of sheets + + assertEquals( 34, record.getRecordSize() ); //sid+size+data + + assertEquals("testURL", record.getURL().getString()); + UnicodeString[] sheetNames = record.getSheetNames(); + assertEquals(2, sheetNames.length); + assertEquals("Sheet1", sheetNames[0].getString()); + assertEquals("Sheet2", sheetNames[1].getString()); + + record.validateSid((short)0x01AE); + } + /** + * tests that we can load the record + */ + public void testLoadAIF() { + + SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, dataAIF)); + assertTrue( record.isAddInFunctions() ); //expected flag + assertEquals( 0x1, record.getNumberOfSheets() ); //expected # of sheets + assertEquals( 8, record.getRecordSize() ); //sid+size+data + record.validateSid((short)0x01AE); + } + /** * Tests that we can store the record * */ - public void testStore() - { - SupBookRecord record = new SupBookRecord(); - record.setFlag( (short) 0x401 ); - record.setNumberOfSheets( (short)0x4 ); - - + public void testStoreIR() { + SupBookRecord record = SupBookRecord.createInternalReferences((short)4); - byte [] recordBytes = record.serialize(); - assertEquals(recordBytes.length - 4, data.length); - for (int i = 0; i < data.length; i++) - assertEquals("At offset " + i, data[i], recordBytes[i+4]); + TestcaseRecordInputStream.confirmRecordEncoding(0x01AE, dataIR, record.serialize()); + } + + public void testStoreER() { + UnicodeString url = new UnicodeString("testURL"); + UnicodeString[] sheetNames = { + new UnicodeString("Sheet1"), + new UnicodeString("Sheet2"), + }; + SupBookRecord record = SupBookRecord.createExternalReferences(url, sheetNames); + + TestcaseRecordInputStream.confirmRecordEncoding(0x01AE, dataER, record.serialize()); } public static void main(String [] args) { @@ -84,6 +125,4 @@ public class TestSupBookRecord .println("Testing org.apache.poi.hssf.record.SupBookRecord"); junit.textui.TestRunner.run(TestSupBookRecord.class); } - - } diff --git a/src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java b/src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java index 767f507e73..ecb55ca82c 100755 --- a/src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java +++ b/src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -16,11 +15,12 @@ limitations under the License. ==================================================================== */ - - package org.apache.poi.hssf.record; import java.io.ByteArrayInputStream; + +import junit.framework.Assert; + import org.apache.poi.util.LittleEndian; /** @@ -33,6 +33,14 @@ import org.apache.poi.util.LittleEndian; public class TestcaseRecordInputStream extends RecordInputStream { + /** + * Convenience constructor + */ + public TestcaseRecordInputStream(int sid, byte[] data) + { + super(new ByteArrayInputStream(mergeDataAndSid((short)sid, (short)data.length, data))); + nextRecord(); + } public TestcaseRecordInputStream(short sid, short length, byte[] data) { super(new ByteArrayInputStream(mergeDataAndSid(sid, length, data))); @@ -46,4 +54,18 @@ public class TestcaseRecordInputStream System.arraycopy(data, 0, result, 4, data.length); return result; } + /** + * Confirms data sections are equal + * @param expectedData - just raw data (without sid or size short ints) + * @param actualRecordBytes this includes 4 prefix bytes (sid & size) + */ + public static void confirmRecordEncoding(int expectedSid, byte[] expectedData, byte[] actualRecordBytes) { + int expectedDataSize = expectedData.length; + Assert.assertEquals(actualRecordBytes.length - 4, expectedDataSize); + Assert.assertEquals(expectedSid, LittleEndian.getShort(actualRecordBytes, 0)); + Assert.assertEquals(expectedDataSize, LittleEndian.getShort(actualRecordBytes, 2)); + for (int i = 0; i < expectedDataSize; i++) + Assert.assertEquals("At offset " + i, expectedData[i], actualRecordBytes[i+4]); + + } } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java b/src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java index 21e6a8ba3c..0912b97611 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -28,6 +27,7 @@ import junit.framework.TestCase; import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; /** * Convenient abstract class to reduce the amount of boilerplate code needed @@ -35,8 +35,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; * * @author Daniel Noll (daniel at nuix dot com dot au) */ -public abstract class AbstractPtgTestCase extends TestCase -{ +public abstract class AbstractPtgTestCase extends TestCase { /** Directory containing the test data. */ private static String dataDir = System.getProperty("HSSF.testdata.path"); @@ -51,16 +50,16 @@ public abstract class AbstractPtgTestCase extends TestCase throws IOException { File file = new File(dataDir, filename); InputStream stream = new BufferedInputStream(new FileInputStream(file)); - try - { - return new HSSFWorkbook(stream); - } - finally - { + // TODO - temp workaround to keep stdout quiet due to warning msg in POIFS + // When that warning msg is disabled, remove this wrapper and the close() call, + InputStream wrappedStream = POIFSFileSystem.createNonClosingInputStream(stream); + try { + return new HSSFWorkbook(wrappedStream); + } finally { stream.close(); } } - + /** * Creates a new Workbook and adds one sheet with the specified name */ @@ -73,5 +72,4 @@ public abstract class AbstractPtgTestCase extends TestCase book.setSheetName(0, sheetName); return book; } - } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java b/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java index b126813387..92ca4ba044 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record.formula; @@ -33,7 +32,8 @@ public class AllFormulaTests { result.addTestSuite(TestArea3DPtg.class); result.addTestSuite(TestAreaErrPtg.class); result.addTestSuite(TestAreaPtg.class); - result.addTestSuite(TestErrPtg.class); + result.addTestSuite(TestErrPtg.class); + result.addTestSuite(TestExternalFunctionFormulas.class); result.addTestSuite(TestFuncPtg.class); result.addTestSuite(TestIntersectionPtg.class); result.addTestSuite(TestPercentPtg.class); diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java b/src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java new file mode 100755 index 0000000000..8c89dadea7 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java @@ -0,0 +1,56 @@ +/* ==================================================================== + 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; + +import java.io.FileInputStream; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +/** + * Tests for functions from external workbooks (e.g. YEARFRAC). + * + * + * @author Josh Micich + */ +public final class TestExternalFunctionFormulas extends TestCase { + + + /** + * tests NameXPtg.toFormulaString(Workbook) and logic in Workbook below that + */ + public void testReadFormulaContainingExternalFunction() { + String filePath = System.getProperty("HSSF.testdata.path")+ "/" + + "externalFunctionExample.xls"; + HSSFWorkbook wb; + try { + FileInputStream fin = new FileInputStream(filePath); + wb = new HSSFWorkbook( fin ); + } catch (IOException e) { + throw new RuntimeException(e); + } + + String expectedFormula = "YEARFRAC(B1,C1)"; + HSSFSheet sht = wb.getSheetAt(0); + String cellFormula = sht.getRow(0).getCell((short)0).getCellFormula(); + assertEquals(expectedFormula, cellFormula); + } + +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java b/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java new file mode 100755 index 0000000000..cbc555da36 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java @@ -0,0 +1,71 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.usermodel; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Collects all tests for the org.apache.poi.hssf.usermodel package. + * + * @author Josh Micich + */ +public class AllUserModelTests { + + public static Test suite() { + TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.usermodel"); + + result.addTestSuite(TestBugs.class); + result.addTestSuite(TestCellStyle.class); + result.addTestSuite(TestCloneSheet.class); + result.addTestSuite(TestDataValidation.class); + result.addTestSuite(TestEscherGraphics.class); + result.addTestSuite(TestEscherGraphics2d.class); + result.addTestSuite(TestFontDetails.class); + result.addTestSuite(TestFormulas.class); + result.addTestSuite(TestHSSFCell.class); + result.addTestSuite(TestHSSFClientAnchor.class); + result.addTestSuite(TestHSSFComment.class); + result.addTestSuite(TestHSSFDateUtil.class); + result.addTestSuite(TestHSSFHeaderFooter.class); + result.addTestSuite(TestHSSFHyperlink.class); + result.addTestSuite(TestHSSFPalette.class); + result.addTestSuite(TestHSSFPicture.class); + result.addTestSuite(TestHSSFPictureData.class); + result.addTestSuite(TestHSSFRichTextString.class); + result.addTestSuite(TestHSSFRow.class); + result.addTestSuite(TestHSSFSheet.class); + result.addTestSuite(TestHSSFSheetOrder.class); + result.addTestSuite(TestHSSFSheetSetOrder.class); + result.addTestSuite(TestHSSFWorkbook.class); + result.addTestSuite(TestNamedRange.class); + result.addTestSuite(TestOLE2Embeding.class); + result.addTestSuite(TestReadWriteChart.class); + result.addTestSuite(TestSanityChecker.class); + result.addTestSuite(TestSheetHiding.class); + result.addTestSuite(TestSheetShiftRows.class); + if (false) { // deliberately avoiding this one + result.addTestSuite(TestUnfixedBugs.class); + } + result.addTestSuite(TestUnicodeWorkbook.class); + result.addTestSuite(TestUppercaseWorkbook.class); + result.addTestSuite(TestWorkbook.class); + + return result; + } +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java index 25d6684c47..fd01001f84 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java @@ -16,25 +16,28 @@ */ package org.apache.poi.hssf.usermodel; -import junit.framework.TestCase; - -import java.io.IOException; -import java.io.FileInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; /** * Test HSSFPicture. * * @author Yegor Kozlov (yegor at apache.org) */ -public class TestHSSFPicture extends TestCase{ +public final class TestHSSFPicture extends TestCase{ - public void testResize() throws Exception { + public void testResize() { HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sh1 = wb.createSheet(); HSSFPatriarch p1 = sh1.createDrawingPatriarch(); - int idx1 = loadPicture( "src/resources/logos/logoKarmokar4.png", wb); + byte[] pictureData = getTestDataFileContent("logoKarmokar4.png"); + int idx1 = wb.addPicture( pictureData, HSSFWorkbook.PICTURE_TYPE_PNG ); HSSFPicture picture1 = p1.createPicture(new HSSFClientAnchor(), idx1); HSSFClientAnchor anchor1 = picture1.getPreferredSize(); @@ -52,28 +55,25 @@ public class TestHSSFPicture extends TestCase{ /** * Copied from org.apache.poi.hssf.usermodel.examples.OfficeDrawing */ - private static int loadPicture( String path, HSSFWorkbook wb ) throws IOException - { - int pictureIndex; - FileInputStream fis = null; - ByteArrayOutputStream bos = null; - try - { - fis = new FileInputStream( path); - bos = new ByteArrayOutputStream( ); - int c; - while ( (c = fis.read()) != -1) - bos.write( c ); - pictureIndex = wb.addPicture( bos.toByteArray(), HSSFWorkbook.PICTURE_TYPE_PNG ); - } - finally - { - if (fis != null) - fis.close(); - if (bos != null) - bos.close(); - } - return pictureIndex; - } + private static byte[] getTestDataFileContent(String fileName) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + String readFilename = System.getProperty("HSSF.testdata.path"); + try { + InputStream fis = new FileInputStream(readFilename+File.separator+fileName); + byte[] buf = new byte[512]; + while(true) { + int bytesRead = fis.read(buf); + if(bytesRead < 1) { + break; + } + bos.write(buf, 0, bytesRead); + } + fis.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return bos.toByteArray(); + } } -- 2.39.5