-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
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 *
/**
* 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 :
* <expression> ::= <term> [<addop> <term>]*
* @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.<br/>
+ * Primarily used by test cases when testing for specific parsing exceptions.</p>
+ *
+ */
+ static final class FormulaParseException extends RuntimeException {
+ // This class was given package scope until it would become clear that it is useful to
+ // general client code.
+ public FormulaParseException(String msg) {
+ super(msg);
+ }
+ }
+
public static 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() {
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 <b>not</b> 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();
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).
* <p>
* @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;
count+=ptg.getSize();
index++;
}
-
+
return count;
}
/**
* Generates the variable function ptg for the formula.
* <p>
- * 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 }
/**
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);
} else {
setClass(n,Ptg.CLASS_VALUE);
}
-
+
}
-
+
private void setParameterRVA(Node n, int formulaType) {
Ptg p = n.getValue();
int numOperands = n.getNumChildren();
for (int i =0;i<numOperands;i++) {
setParameterRVA(n.getChild(i),formulaType);
}
- }
+ }
}
private void setParameterRVA(Node n, int expectedClass,int formulaType) {
- Ptg p = (Ptg) n.getValue();
- if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1
+ Ptg p = n.getValue();
+ if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1
if (p.getDefaultOperandClass() == Ptg.CLASS_REF ) {
setClass(n, Ptg.CLASS_REF);
}
} else {
setClass(n,Ptg.CLASS_VALUE);
}
- } else { //Array class, pg 16.
+ } else { //Array class, pg 16.
if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE &&
(formulaType==FORMULA_TYPE_CELL || formulaType == FORMULA_TYPE_SHARED)) {
setClass(n,Ptg.CLASS_VALUE);
}
}
}
-
+
private void setClass(Node n, byte theClass) {
- Ptg p = (Ptg) n.getValue();
+ Ptg p = n.getValue();
if (p instanceof AbstractFunctionPtg || !(p instanceof OperationPtg)) {
p.setClass(theClass);
} else {
}
/**
* Convience method which takes in a list then passes it to the
- * other toFormulaString signature.
+ * other toFormulaString signature.
* @param book workbook for 3D and named references
* @param lptgs list of Ptg, can be null or empty
* @return a human readable String
* @return a human readable String
*/
public String toFormulaString(List lptgs) {
- return toFormulaString(book, lptgs);
+ return toFormulaString(book, lptgs);
}
-
+
/**
* Static method to convert an array of Ptgs in RPN order
* to a human readable string format in infix mode.
* @return a human readable String
*/
public static String toFormulaString(Workbook book, Ptg[] ptgs) {
- if (ptgs == null || ptgs.length == 0) return "#NAME";
- java.util.Stack stack = new java.util.Stack();
- AttrPtg ifptg = null;
+ if (ptgs == null || ptgs.length == 0) {
+ // TODO - what is the justification for returning "#NAME" (which is not "#NAME?", btw)
+ return "#NAME";
+ }
+ Stack stack = new Stack();
// Excel allows to have AttrPtg at position 0 (such as Blanks) which
// do not have any operands. Skip them.
int i;
if(ptgs[0] instanceof AttrPtg) {
- // TODO -this requirement is unclear and is not addressed by any junits
- stack.push(ptgs[0].toFormulaString(book));
- i=1;
+ AttrPtg attrPtg0 = (AttrPtg) ptgs[0];
+ if(attrPtg0.isSemiVolatile()) {
+ // no visible formula for semi-volatile
+ } else {
+ // TODO -this requirement is unclear and is not addressed by any junits
+ stack.push(ptgs[0].toFormulaString(book));
+ }
+ i=1;
} else {
- i=0;
+ i=0;
}
-
+
for ( ; i < ptgs.length; i++) {
Ptg ptg = ptgs[i];
+ // TODO - what about MemNoMemPtg?
+ if(ptg instanceof MemAreaPtg || ptg instanceof MemFuncPtg || ptg instanceof MemErrPtg) {
+ // marks the start of a list of area expressions which will be naturally combined
+ // by their trailing operators (e.g. UnionPtg)
+ // TODO - put comment and throw exception in toFormulaString() of these classes
+ continue;
+ }
if (! (ptg instanceof OperationPtg)) {
stack.push(ptg.toFormulaString(book));
continue;
}
-
+
if (ptg instanceof AttrPtg && ((AttrPtg) ptg).isOptimizedIf()) {
- ifptg = (AttrPtg) ptg;
continue;
}
-
- final OperationPtg o = (OperationPtg) ptg;
- final String[] operands = new String[o.getNumberOfOperands()];
-
- for (int j = operands.length; j > 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
* @return a human readable String
*/
public String toFormulaString(Ptg[] ptgs) {
- return toFormulaString(book, ptgs);
+ return toFormulaString(book, ptgs);
}
*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<numPtgs;i++) {
if (tokens.get(i) instanceof OperationPtg) {
-
+
o = (OperationPtg) tokens.get(i);
numOperands = o.getNumberOfOperands();
operands = new Node[numOperands];
for (int j=0;j<numOperands;j++) {
- operands[numOperands-j-1] = (Node) stack.pop();
+ operands[numOperands-j-1] = (Node) stack.pop();
}
Node result = new Node(o);
result.setChildren(operands);
}
return (Node) stack.pop();
}
-
+
/** toString on the parser instance returns the RPN ordered list of tokens
* Useful for testing
*/
for (int i=0;i<tokens.size();i++) {
buf.append( ( (Ptg)tokens.get(i)).toFormulaString(book));
buf.append(' ');
- }
+ }
return buf.toString();
}
-
-}
+
/** Private helper class, used to create a tree representation of the formula*/
- class Node {
+ private static final class Node {
private Ptg value=null;
private Node[] children=new Node[0];
private int numChild=0;
public Node(Ptg val) {
- value = val;
+ value = val;
}
public void setChildren(Node[] child) {children = child;numChild=child.length;}
public int getNumChildren() {return numChild;}
public Node getChild(int number) {return children[number];}
public Ptg getValue() {return value;}
}
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.model;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.poi.hssf.record.CRNCountRecord;
+import org.apache.poi.hssf.record.CRNRecord;
+import org.apache.poi.hssf.record.CountryRecord;
+import org.apache.poi.hssf.record.ExternSheetRecord;
+import org.apache.poi.hssf.record.ExternSheetSubRecord;
+import org.apache.poi.hssf.record.ExternalNameRecord;
+import org.apache.poi.hssf.record.NameRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.SupBookRecord;
+
+/**
+ * Link Table (OOO pdf reference: 4.10.3 ) <p/>
+ *
+ * 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.
+ * <p/>
+ *
+ * In BIFF8 the Link Table consists of
+ * <ul>
+ * <li>one or more EXTERNALBOOK Blocks<p/>
+ * each consisting of
+ * <ul>
+ * <li>exactly one EXTERNALBOOK (0x01AE) record</li>
+ * <li>zero or more EXTERNALNAME (0x0023) records</li>
+ * <li>zero or more CRN Blocks<p/>
+ * each consisting of
+ * <ul>
+ * <li>exactly one XCT (0x0059)record</li>
+ * <li>zero or more CRN (0x005A) records (documentation says one or more)</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * </li>
+ * <li>exactly one EXTERNSHEET (0x0017) record</li>
+ * <li>zero or more DEFINEDNAME (0x0018) records</li>
+ * </ul>
+ *
+ *
+ * @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);
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.model;
+
+import java.util.List;
+
+import org.apache.poi.hssf.record.Record;
+/**
+ * Simplifies iteration over a sequence of <tt>Record</tt> 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. <code>null</code> if this stream is exhausted.
+ */
+ public Class peekNextClass() {
+ if(_nextIndex >= _list.size()) {
+ return null;
+ }
+ return _list.get(_nextIndex).getClass();
+ }
+
+ public int getCountRead() {
+ return _countRead;
+ }
+}
*/
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
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
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++) {
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);
break;
}
}
-
- retval.records.setRecords(records);
if (retval.windowOne == null) {
retval.windowOne = (WindowOneRecord) retval.createWindowOne();
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() );
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 );
// 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;
* @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();
records.add(records.getBspos()+1, bsr);
records.setBspos( records.getBspos() + 1 );
boundsheets.add(bsr);
+ getOrCreateLinkTable().checkExternSheet(sheetnum);
fixTabIdRecord();
}
}
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);
}
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);
}
*/
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 ,
* @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
* @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());
}
*/
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
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);
+ }
}
+
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record;
+
+import org.apache.poi.util.LittleEndian;
+/**
+ * XCT \96 CRN Count <P>
+ *
+ * REFERENCE: 5.114<P>
+ *
+ * @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;
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record;
+
+import org.apache.poi.hssf.record.constant.ConstantValueParser;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * Title: CRN <P>
+ * Description: This record stores the contents of an external cell or cell range <P>
+ * REFERENCE: 5.23<P>
+ *
+ * @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;
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.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<p/>
+ *
+ * @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 <code>true</code>, 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();
+ }
+}
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);
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
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 <P>
- * Description: A Extrenal Workbook Deciption (Sup Book)
+ * Title: Sup Book (EXTERNALBOOK) <P>
+ * Description: A External Workbook Description (Suplemental Book)
* Its only a dummy record for making new ExternSheet Record <P>
- * REFERENCE: <P>
+ * REFERENCE: 5.38<P>
* @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<field_3_sheet_names.length; i++) {
+ urs = new UnicodeRecordStats();
+ field_3_sheet_names[i].getRecordSize(urs);
+ sum += urs.recordSize;
+ }
+ return sum;
}
/**
* @param data byte array containing instance data
* @return number of bytes written
*/
- public int serialize(int offset, byte [] data)
- {
+ public int serialize(int offset, byte [] data) {
LittleEndian.putShort(data, 0 + offset, sid);
- LittleEndian.putShort(data, 2 + offset, (short) 4);
+ int dataSize = getDataSize();
+ LittleEndian.putShort(data, 2 + offset, (short) dataSize);
LittleEndian.putShort(data, 4 + offset, field_1_number_of_sheets);
- LittleEndian.putShort(data, 6 + offset, field_2_flag);
-
- return getRecordSize();
+
+ if(isExternalReferences()) {
+
+ int currentOffset = 6 + offset;
+ UnicodeRecordStats urs = new UnicodeRecordStats();
+ field_2_encoded_url.serialize(urs, currentOffset, data);
+ currentOffset += urs.recordSize;
+
+ for(int i=0; i<field_3_sheet_names.length; i++) {
+ urs = new UnicodeRecordStats();
+ field_3_sheet_names[i].serialize(urs, currentOffset, data);
+ currentOffset += urs.recordSize;
+ }
+ } else {
+ short field2val = _isAddInFunctions ? TAG_ADD_IN_FUNCTIONS : TAG_INTERNAL_REFERENCES;
+
+ LittleEndian.putShort(data, 6 + offset, field2val);
+ }
+ return dataSize + 4;
}
public void setNumberOfSheets(short number){
return field_1_number_of_sheets;
}
- public void setFlag(short flag){
- field_2_flag = flag;
- }
-
- public short getFlag() {
- return field_2_flag;
- }
-
- public int getRecordSize()
- {
- return 4 + 4;
+ public int getRecordSize() {
+ return getDataSize() + 4;
}
public short getSid()
{
return sid;
}
+ public UnicodeString getURL() {
+ return field_2_encoded_url;
+ }
+ public UnicodeString[] getSheetNames() {
+ return (UnicodeString[]) field_3_sheet_names.clone();
+ }
}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.constant;
+
+import org.apache.poi.hssf.record.RecordInputStream;
+import org.apache.poi.hssf.record.UnicodeString;
+import org.apache.poi.hssf.record.UnicodeString.UnicodeRecordStats;
+
+/**
+ * To support Constant Values (2.5.7) as required by the CRN record.
+ * This class should probably also be used for two dimensional arrays which are encoded by
+ * EXTERNALNAME (5.39) records and Array tokens.<p/>
+ * 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;
+ }
+}
* @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();
public String getName() {
return lookupName(field_2_fnc_index);
}
+ /**
+ * external functions get some special processing
+ * @return <code>true</code> if this is an external function
+ */
+ public boolean isExternalFunction() {
+ return field_2_fnc_index == FUNCTION_INDEX_EXTERNAL;
+ }
public String toFormulaString(Workbook book) {
return getName();
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;i<operands.length;i++) {
- buf.append(operands[i]);
- buf.append(',');
- }
- buf.deleteCharAt(buf.length()-1);
- }
- if (field_2_fnc_index != 1) {
- buf.append(")");
- }
+
+ if(isExternalFunction()) {
+ buf.append(operands[0]); // first operand is actually the function name
+ appendArgs(buf, 1, operands);
+ } else {
+ buf.append(getName());
+ appendArgs(buf, 0, operands);
+ }
return buf.toString();
}
+
+ private static void appendArgs(StringBuffer buf, int firstArgIx, String[] operands) {
+ buf.append('(');
+ for (int i=firstArgIx;i<operands.length;i++) {
+ if (i>firstArgIx) {
+ 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
+ * <p>
+ * The name matching is case insensitive.
+ * @return <code>true</code> if the name specifies a standard worksheet function,
+ * <code>false</code> 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.
+ * <p>
+ * The name matching is case insensitive.
+ * @return the standard worksheet function index if found, otherwise <tt>FUNCTION_INDEX_EXTERNAL</tt>
+ */
+ 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;
}
/**
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");
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");
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
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;
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 <code>true</code> if the specified value is within the range of values
+ * <tt>IntPtg</tt> 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)
{
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();
+ }
}
{
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;
//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 */
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)
{
*
* @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
/** Creates new NamePtg */
- public NameXPtg(String name)
- {
- //TODO
- }
-
- /** Creates new NamePtg */
-
public NameXPtg(RecordInputStream in)
{
field_1_ixals = in.readShort();
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;}
//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;
//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();
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();
putNumber(data, offset, value, SHORT_SIZE);
}
+ /**
+ * executes:<p/>
+ * <code>
+ * data[offset] = (byte)value;
+ * </code></p>
+ * 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
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
}
- 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() {
* 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;
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class ConcatEval extends StringOperationEval {
+public final class ConcatEval extends StringOperationEval {
private ConcatPtg delegate;
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() {
* 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;
* @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
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
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)
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);
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.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
+ * <tt>AbstractFunctionPtg.field_2_fnc_index</tt> == 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);
+ }
+
+}
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;
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) {
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
* 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;
* @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
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() {
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.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();
+ }
+}
package org.apache.poi.hssf.record.formula.eval;
-
/**
* Provides functionality for evaluating arguments to functions and operators.
*
*/
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;
* Some examples:<br/>
* " 123 " -> 123.0<br/>
* ".123" -> 0.123<br/>
+ * These not supported yet:<br/>
* " $ 1,000.00 " -> 1000.0<br/>
* "$1.25E4" -> 12500.0<br/>
+ * "5**2" -> 500<br/>
+ * "250%" -> 2.5<br/>
*
* @param text
* @return <code>null</code> 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;
} catch (NumberFormatException e) {
return null;
}
- return new Double(val);
+ return new Double(isPositive ? +val : -val);
}
-
+ /**
+ * @param ve must be a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>, or <tt>BlankEval</tt>
+ * @return the converted string value. never <code>null</code>
+ */
+ 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() + ")");
+ }
}
* 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;
* @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
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() {
* 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;
- }
-
}
* 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;
/**
*/
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();
}
-
}
* 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;
/**
* 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();
-
}
* 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;
* @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
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
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
* 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;
* @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
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() {
* 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;
* @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() {
public int getType() {
return delegate.getType();
}
-
}
* @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;
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;
}
/**
// most common case - least worries :)
else if (eval instanceof NumberEval) {
- retval = (NumberEval) eval;
+ retval = eval;
}
// booleval
* @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() + ")");
}
/**
* @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;
}
-
}
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
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE
//| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
- | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
));
/**
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
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE
//| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
- | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
));
/**
* 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;
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
* 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) {
}
return retval;
}
-
}
* @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;
}
-
-
}
* 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);
+ }
}
* 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();
+ }
+ }
}
* 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;
* @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
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;
/**
*
* @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.
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
}
- 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!),
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
* 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;
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
}
}
-
- 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.
* 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) {
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
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
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE
//| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
- | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
));
/**
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() {
* 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()<p/>
+ *
+ * Calculates the sum of differences of squares in two arrays of the same size.<br/>
+ * <b>Syntax</b>:<br/>
+ * <b>SUMX2MY2</b>(<b>arrayX</b>, <b>arrayY</b>)<p/>
+ *
+ * result = Σ<sub>i: 0..n</sub>(x<sub>i</sub><sup>2</sup>-y<sub>i</sub><sup>2</sup>)
+ *
* @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);
}
}
* 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()<p/>
+ *
+ * Calculates the sum of squares in two arrays of the same size.<br/>
+ * <b>Syntax</b>:<br/>
+ * <b>SUMX2PY2</b>(<b>arrayX</b>, <b>arrayY</b>)<p/>
+ *
+ * result = Σ<sub>i: 0..n</sub>(x<sub>i</sub><sup>2</sup>+y<sub>i</sub><sup>2</sup>)
+ *
* @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);
}
}
* 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()<p/>
+ *
+ * Calculates the sum of squares of differences between two arrays of the same size.<br/>
+ * <b>Syntax</b>:<br/>
+ * <b>SUMXMY2</b>(<b>arrayX</b>, <b>arrayY</b>)<p/>
+ *
+ * result = Σ<sub>i: 0..n</sub>(x<sub>i</sub>-y<sub>i</sub>)<sup>2</sup>
+ *
* @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);
}
}
*/
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;
/**
* 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();
+ }
+ }
}
* 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<iSize; i++) {
- Eval xEval = xops[i];
- Eval yEval = yops[i];
-
- if (isNumberEval(xEval) && isNumberEval(yEval)) {
- retval[X] = ensureCapacity(retval[X], count);
- retval[Y] = ensureCapacity(retval[Y], count);
- retval[X][count] = getDoubleValue(xEval);
- retval[Y][count] = getDoubleValue(yEval);
- if (Double.isNaN(retval[X][count]) || Double.isNaN(retval[Y][count])) {
- retval = null;
- break;
- }
- count++;
- }
- }
- }
-
- if (retval != null) {
- double[][] temp = retval;
- retval[X] = trimToSize(retval[X], count);
- retval[Y] = trimToSize(retval[Y], count);
- }
-
- return retval;
- }
-
- protected double[][] getValues(Eval[] operands, int srcCellRow, short srcCellCol) {
- double[][] retval = null;
-
- outer: do {
- if (operands.length == 2) {
- Eval[] xEvals = new Eval[1];
- Eval[] yEvals = new Eval[1];
- if (operands[X] instanceof AreaEval) {
- AreaEval ae = (AreaEval) operands[0];
- xEvals = ae.getValues();
- }
- else if (operands[X] instanceof ErrorEval) {
- break outer;
- }
- else {
- xEvals[0] = operands[X];
- }
-
- if (operands[Y] instanceof AreaEval) {
- AreaEval ae = (AreaEval) operands[Y];
- yEvals = ae.getValues();
- }
- else if (operands[Y] instanceof ErrorEval) {
- break outer;
- }
- else {
- yEvals[0] = operands[Y];
- }
-
- retval = getNumberArray(xEvals, yEvals, srcCellRow, srcCellCol);
- }
- } while (false);
+ for (int i=0, iSize=nArrayItems; i<iSize; i++) {
+ Eval xEval = xops[i];
+ Eval yEval = yops[i];
+
+ if (isNumberEval(xEval) && isNumberEval(yEval)) {
+ xResult[count] = getDoubleValue(xEval);
+ yResult[count] = getDoubleValue(yEval);
+ if (Double.isNaN(xResult[count]) || Double.isNaN(xResult[count])) {
+ throw new EvaluationException(ErrorEval.NUM_ERROR);
+ }
+ count++;
+ }
+ }
- return retval;
+ return new double[][] {
+ trimToSize(xResult, count),
+ trimToSize(yResult, count),
+ };
}
-
- protected static double[] ensureCapacity(double[] arr, int pos) {
- double[] temp = arr;
- while (pos >= 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];
return tarr;
}
- protected static boolean isNumberEval(Eval eval) {
+ private static boolean isNumberEval(Eval eval) {
boolean retval = false;
if (eval instanceof NumberEval) {
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;
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;
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();
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; }
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 <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>,
+ * <tt>BlankEval</tt> or <tt>ErrorEval</tt>. Never <code>null</code>.
+ */
+ private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) {
+ if (evaluationResult instanceof RefEval) {
+ RefEval rv = (RefEval) evaluationResult;
return rv.getInnerValueEval();
}
- if (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];
}
return ErrorEval.VALUE_INVALID;
}
- return value;
+ return evaluationResult;
}
private static Eval invokeOperation(OperationEval operation, Eval[] ops, int srcRowNum, short srcColNum,
// (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)];
// (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)];
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() + ")");
}
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() + ")");
}
==================================================================== */
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;
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;
}
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;
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;
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;
+ }
}
}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.eval;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Collects all tests the package <tt>org.apache.poi.hssf.record.formula.eval</tt>.
+ *
+ * @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;
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.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());
+ }
+}
// 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);
result.addTestSuite(TestSumproduct.class);
result.addTestSuite(TestStatsLib.class);
result.addTestSuite(TestTFunc.class);
+ result.addTestSuite(TestTrim.class);
+ result.addTestSuite(TestXYNumericFunction.class);
return result;
}
* 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);
}
}
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)";
}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.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);
+
+ }
+}
};
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);
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.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());
+
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.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);
+ }
+}
// 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, "");
}
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),
* 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);
}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.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);
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.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);
+
+ }
+}
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
+
package org.apache.poi.hssf;
import junit.framework.Test;
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;
*
* @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;
}
}
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;
public void tearDown() {
}
+ /**
+ * @return parsed token array already confirmed not <code>null</code>
+ */
+ 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);
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);
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);
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);
}
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);
};
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;
+ }
+ }
}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.record;
+
+import org.apache.poi.hssf.record.formula.AllFormulaTests;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Collects all tests for package <tt>org.apache.poi.hssf.record</tt>.
+ *
+ * @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;
+ }
+}
*
* @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)
{
/**
* 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) {
.println("Testing org.apache.poi.hssf.record.SupBookRecord");
junit.textui.TestRunner.run(TestSupBookRecord.class);
}
-
-
}
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
limitations under the License.
==================================================================== */
-
-
package org.apache.poi.hssf.record;
import java.io.ByteArrayInputStream;
+
+import junit.framework.Assert;
+
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)));
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]);
+
+ }
}
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
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
*
* @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");
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
*/
book.setSheetName(0, sheetName);
return book;
}
-
}
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.record.formula;
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);
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.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 <tt>NameXPtg.toFormulaString(Workbook)</tt> 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);
+ }
+
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Collects all tests for the <tt>org.apache.poi.hssf.usermodel</tt> 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;
+ }
+}
*/\r
package org.apache.poi.hssf.usermodel;\r
\r
-import junit.framework.TestCase;\r
-\r
-import java.io.IOException;\r
-import java.io.FileInputStream;\r
import java.io.ByteArrayOutputStream;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+\r
+import junit.framework.TestCase;\r
\r
/**\r
* Test <code>HSSFPicture</code>.\r
*\r
* @author Yegor Kozlov (yegor at apache.org)\r
*/\r
-public class TestHSSFPicture extends TestCase{\r
+public final class TestHSSFPicture extends TestCase{\r
\r
- public void testResize() throws Exception {\r
+ public void testResize() {\r
HSSFWorkbook wb = new HSSFWorkbook();\r
HSSFSheet sh1 = wb.createSheet();\r
HSSFPatriarch p1 = sh1.createDrawingPatriarch();\r
\r
- int idx1 = loadPicture( "src/resources/logos/logoKarmokar4.png", wb);\r
+ byte[] pictureData = getTestDataFileContent("logoKarmokar4.png");\r
+ int idx1 = wb.addPicture( pictureData, HSSFWorkbook.PICTURE_TYPE_PNG );\r
HSSFPicture picture1 = p1.createPicture(new HSSFClientAnchor(), idx1);\r
HSSFClientAnchor anchor1 = picture1.getPreferredSize();\r
\r
/**\r
* Copied from org.apache.poi.hssf.usermodel.examples.OfficeDrawing\r
*/\r
- private static int loadPicture( String path, HSSFWorkbook wb ) throws IOException\r
- {\r
- int pictureIndex;\r
- FileInputStream fis = null;\r
- ByteArrayOutputStream bos = null;\r
- try\r
- {\r
- fis = new FileInputStream( path);\r
- bos = new ByteArrayOutputStream( );\r
- int c;\r
- while ( (c = fis.read()) != -1)\r
- bos.write( c );\r
- pictureIndex = wb.addPicture( bos.toByteArray(), HSSFWorkbook.PICTURE_TYPE_PNG );\r
- }\r
- finally\r
- {\r
- if (fis != null)\r
- fis.close();\r
- if (bos != null)\r
- bos.close();\r
- }\r
- return pictureIndex;\r
- }\r
+ private static byte[] getTestDataFileContent(String fileName) {\r
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();\r
+\r
+ String readFilename = System.getProperty("HSSF.testdata.path");\r
+ try {\r
+ InputStream fis = new FileInputStream(readFilename+File.separator+fileName);\r
\r
+ byte[] buf = new byte[512];\r
+ while(true) {\r
+ int bytesRead = fis.read(buf);\r
+ if(bytesRead < 1) {\r
+ break;\r
+ }\r
+ bos.write(buf, 0, bytesRead);\r
+ }\r
+ fis.close();\r
+ } catch (IOException e) {\r
+ throw new RuntimeException(e);\r
+ }\r
+ return bos.toByteArray();\r
+ }\r
}\r