diff --git a/build.xml b/build.xml
index 8905dc13f5..3c40743da6 100644
--- a/build.xml
+++ b/build.xml
@@ -557,8 +557,7 @@ under the License.
+ In early 2008, Microsoft made a fairly complete set of documentation + on the binary file formats freely and publicly available. These were + released under the Open + Specification Promise, which does allow us to use them for + building open source software under the + Apache Software License. +
++ You can download the documentation on Excel, Word, PowerPoint and + Escher (drawing) from + http://www.microsoft.com/interop/docs/OfficeBinaryFormats.mspx. + Documentation on a few of the supporting technologies used in these + file formats can be downloaded from + http://www.microsoft.com/interop/docs/supportingtechnologies.mspx. +
++ Previously, Microsoft published a book on the Excel 97 file format. + It can still be of plenty of use, and is handy dead tree form. Pick up + a copy of "Excel 97 Developer's Kit" from your favourite second hand + book store. +
++ The newer Office Open XML (ooxml) file formats are documented as part + of the ECMA / ISO standardisation effort for the formats. This + documentation is quite large, but you can normally find the bit you + need without too much effort! This can be downloaded from + http://www.ecma-international.org/publications/standards/Ecma-376.htm, + and is also under the OSP. +
++ It is also worth checking the documentation and code of the other + open source implementations of the file formats. +
+In short, stay away, stay far far away. Implementing these file formats @@ -66,13 +103,14 @@
If you've ever received information regarding the OLE 2 Compound Document Format under any type of exclusionary agreement from Microsoft, or - (probably illegally) received such information from a person bound by - such an agreement, you cannot participate in this project. (Sorry) + (possibly illegally) received such information from a person bound by + such an agreement, you cannot participate in this project. (Sorry)
Those submitting patches that show insight into the file format may be - asked to state explicitly that they are eligible or possibly sign an - agreement. + asked to state explicitly that they have only ever read the publicly + available file format information, and not any received under an NDA + or similar.
The quick guide documentation provides
information on using this API. Comments and fixes gratefully accepted on the POI
diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml
index 91641b13ba..c59cae1a3d 100644
--- a/src/documentation/content/xdocs/status.xml
+++ b/src/documentation/content/xdocs/status.xml
@@ -33,6 +33,36 @@
+ * Primarily used by test cases when testing for specific parsing exceptions.
@@ -378,17 +336,17 @@ public class FormulaParser { * @return int */ private int getPtgSize(int index) { - int count = 0; - - Iterator ptgIterator = tokens.listIterator(index); - while (ptgIterator.hasNext()) { - Ptg ptg = (Ptg)ptgIterator.next(); - count+=ptg.getSize(); - } - - return count; + int count = 0; + + Iterator ptgIterator = tokens.listIterator(index); + while (ptgIterator.hasNext()) { + Ptg ptg = (Ptg)ptgIterator.next(); + count+=ptg.getSize(); + } + + return count; } - + private int getPtgSize(int start, int end) { int count = 0; int index = start; @@ -398,390 +356,430 @@ public class FormulaParser { count+=ptg.getSize(); index++; } - + return count; } /** * Generates the variable function ptg for the formula. *
- * For IF Formulas, additional PTGs are added to the tokens
+ * For IF Formulas, additional PTGs are added to the tokens
* @param name
* @param numArgs
* @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
*/
- private AbstractFunctionPtg getFunction(String name, byte numArgs) {
- AbstractFunctionPtg retval = null;
-
- if (name.equals("IF")) {
- retval = new FuncVarPtg(AbstractFunctionPtg.ATTR_NAME, numArgs);
-
- //simulated pop, no bounds checking because this list better be populated by function()
- List argumentPointers = (List)this.functionTokens.get(0);
-
-
- AttrPtg ifPtg = new AttrPtg();
- ifPtg.setData((short)7); //mirroring excel output
- ifPtg.setOptimizedIf(true);
-
- if (argumentPointers.size() != 2 && argumentPointers.size() != 3) {
- throw new IllegalArgumentException("["+argumentPointers.size()+"] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]");
- }
-
- //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are
- //tracked in the argument pointers
- //The beginning first argument pointer is the last ptg of the condition
- int ifIndex = tokens.indexOf(argumentPointers.get(0))+1;
- tokens.add(ifIndex, ifPtg);
-
- //we now need a goto ptgAttr to skip to the end of the formula after a true condition
- //the true condition is should be inserted after the last ptg in the first argument
-
- int gotoIndex = tokens.indexOf(argumentPointers.get(1))+1;
-
- AttrPtg goto1Ptg = new AttrPtg();
- goto1Ptg.setGoto(true);
-
-
- tokens.add(gotoIndex, goto1Ptg);
-
-
- if (numArgs > 2) { //only add false jump if there is a false condition
-
- //second goto to skip past the function ptg
- AttrPtg goto2Ptg = new AttrPtg();
- goto2Ptg.setGoto(true);
- goto2Ptg.setData((short)(retval.getSize()-1));
- //Page 472 of the Microsoft Excel Developer's kit states that:
- //The b(or w) field specifies the number byes (or words to skip, minus 1
-
- tokens.add(goto2Ptg); //this goes after all the arguments are defined
- }
-
- //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit)
- //count the number of bytes after the ifPtg to the False Subexpression
- //doesn't specify -1 in the documentation
- ifPtg.setData((short)(getPtgSize(ifIndex+1, gotoIndex)));
-
- //count all the additional (goto) ptgs but dont count itself
- int ptgCount = this.getPtgSize(gotoIndex)-goto1Ptg.getSize()+retval.getSize();
- if (ptgCount > (int)Short.MAX_VALUE) {
- throw new RuntimeException("Ptg Size exceeds short when being specified for a goto ptg in an if");
- }
-
- goto1Ptg.setData((short)(ptgCount-1));
-
- } else {
-
- retval = new FuncVarPtg(name,numArgs);
+ private AbstractFunctionPtg getFunction(String name, int numArgs, List argumentPointers) {
+
+ AbstractFunctionPtg retval = new FuncVarPtg(name, (byte)numArgs);
+ if (!name.equals(AbstractFunctionPtg.FUNCTION_NAME_IF)) {
+ // early return for everything else besides IF()
+ return retval;
}
-
+
+
+ AttrPtg ifPtg = new AttrPtg();
+ ifPtg.setData((short)7); //mirroring excel output
+ ifPtg.setOptimizedIf(true);
+
+ if (argumentPointers.size() != 2 && argumentPointers.size() != 3) {
+ throw new IllegalArgumentException("["+argumentPointers.size()+"] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]");
+ }
+
+ //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are
+ //tracked in the argument pointers
+ //The beginning first argument pointer is the last ptg of the condition
+ int ifIndex = tokens.indexOf(argumentPointers.get(0))+1;
+ tokens.add(ifIndex, ifPtg);
+
+ //we now need a goto ptgAttr to skip to the end of the formula after a true condition
+ //the true condition is should be inserted after the last ptg in the first argument
+
+ int gotoIndex = tokens.indexOf(argumentPointers.get(1))+1;
+
+ AttrPtg goto1Ptg = new AttrPtg();
+ goto1Ptg.setGoto(true);
+
+
+ tokens.add(gotoIndex, goto1Ptg);
+
+
+ if (numArgs > 2) { //only add false jump if there is a false condition
+
+ //second goto to skip past the function ptg
+ AttrPtg goto2Ptg = new AttrPtg();
+ goto2Ptg.setGoto(true);
+ goto2Ptg.setData((short)(retval.getSize()-1));
+ //Page 472 of the Microsoft Excel Developer's kit states that:
+ //The b(or w) field specifies the number byes (or words to skip, minus 1
+
+ tokens.add(goto2Ptg); //this goes after all the arguments are defined
+ }
+
+ //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit)
+ //count the number of bytes after the ifPtg to the False Subexpression
+ //doesn't specify -1 in the documentation
+ ifPtg.setData((short)(getPtgSize(ifIndex+1, gotoIndex)));
+
+ //count all the additional (goto) ptgs but dont count itself
+ int ptgCount = this.getPtgSize(gotoIndex)-goto1Ptg.getSize()+retval.getSize();
+ if (ptgCount > Short.MAX_VALUE) {
+ throw new RuntimeException("Ptg Size exceeds short when being specified for a goto ptg in an if");
+ }
+
+ goto1Ptg.setData((short)(ptgCount-1));
+
return retval;
}
+
+ private static boolean isArgumentDelimiter(char ch) {
+ return ch == ',' || ch == ')';
+ }
/** get arguments to a function */
- private int Arguments() {
- int numArgs = 0;
- if (look != ')') {
- numArgs++;
- Expression();
- addArgumentPointer();
+ private int Arguments(List argumentPointers) {
+ SkipWhite();
+ if(look == ')') {
+ return 0;
}
- while (look == ',' || look == ';') { //TODO handle EmptyArgs
- if(look == ',') {
- Match(',');
+
+ 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;
}
- else {
- Match(';');
- }
- 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());
- }
- 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 powerFactor() {
+ percentFactor();
+ while(true) {
+ SkipWhite();
+ if(look != '^') {
+ return;
}
-
- if (look == 'E') {
- GetChar();
-
- String sign = "";
- if (look == '+') {
- GetChar();
- } else if (look == '-') {
- GetChar();
- sign = "-";
- }
-
- String number = GetNum();
- if (number == null) {
- Expected("Integer");
- }
- exponent = sign + number;
- }
-
- if (number1 == null && number2 == null) {
- Expected("Integer");
- }
-
- tokens.add(getNumberPtgFromString(number1, number2, exponent));
+ Match('^');
+ percentFactor();
+ tokens.add(new PowerPtg());
}
}
- /**
- * 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 void percentFactor() {
+ tokens.add(parseSimpleFactor());
+ while(true) {
+ SkipWhite();
+ if(look != '%') {
+ return;
+ }
+ 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();
+ } else if (look == '-') {
+ GetChar();
+ sign = "-";
+ }
+
+ String number = GetNum();
+ if (number == null) {
+ throw expected("Integer");
+ }
+ exponent = sign + number;
+ }
+
+ if (number1 == null && number2 == null) {
+ throw expected("Integer");
+ }
+
+ return getNumberPtgFromString(number1, number2, exponent);
+ }
+
+
+ private 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);
- }
-
- String numberStr = number.toString();
-
- try {
- return new IntPtg(numberStr);
- } catch (NumberFormatException e) {
- return new NumberPtg(numberStr);
- }
- } else {
- if (number1 != null) {
- number.append(number1);
- }
-
- number.append('.');
- number.append(number2);
-
+
+ if (number2 == null) {
+ number.append(number1);
+
if (exponent != null) {
number.append('E');
number.append(exponent);
}
-
- 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());
+ String numberStr = number.toString();
+ int intVal;
+ try {
+ intVal = Integer.parseInt(numberStr);
+ } catch (NumberFormatException e) {
+ return new NumberPtg(numberStr);
+ }
+ if (IntPtg.isInRange(intVal)) {
+ return new IntPtg(intVal);
+ }
+ return new NumberPtg(numberStr);
+ }
+
+ if (number1 != null) {
+ number.append(number1);
+ }
+
+ number.append('.');
+ number.append(number2);
+
+ if (exponent != null) {
+ number.append('E');
+ number.append(exponent);
+ }
+
+ return new NumberPtg(number.toString());
}
-
-
+
+
+ private StringPtg parseStringLiteral()
+ {
+ Match('"');
+
+ StringBuffer token = new StringBuffer();
+ while (true) {
+ if (look == '"') {
+ GetChar();
+ if (look != '"') {
+ break;
+ }
+ }
+ token.append(look);
+ GetChar();
+ }
+ return new StringPtg(token.toString());
+ }
+
/** Parse and Translate a Math Term */
- private void Term(){
- Factor();
- while (look == '*' || look == '/' || look == '^' || look == '&') {
-
- ///TODO do we need to do anything here??
- if (look == '*') Multiply();
- else if (look == '/') Divide();
- else if (look == '^') Power();
- else if (look == '&') Concat();
+ 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 an Add */
- private void Add() {
- Match('+');
- Term();
- tokens.add(new AddPtg());
+ 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 Concatination */
- private void Concat() {
- Match('&');
- Term();
- tokens.add(new ConcatPtg());
- }
-
- /** Recognize and Translate a test for Equality */
- private void Equal() {
- Match('=');
- Expression();
- tokens.add(new EqualPtg());
- }
-
- /** Recognize and Translate a Subtract */
- private void Subtract() {
- Match('-');
- Term();
- tokens.add(new SubtractPtg());
- }
- private void Power() {
- Match('^');
- Term();
- tokens.add(new PowerPtg());
+ 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();
}
+
+ private void concatExpression() {
+ additiveExpression();
+ while (true) {
+ SkipWhite();
+ if(look != '&') {
+ break; // finished with concat expression
+ }
+ Match('&');
+ additiveExpression();
+ tokens.add(new ConcatPtg());
+ }
+ }
+
/** Parse and Translate an Expression */
- private void Expression() {
+ private void additiveExpression() {
Term();
- while (IsAddop(look)) {
- if (look == '+' ) Add();
- else if (look == '-') Subtract();
+ while (true) {
+ SkipWhite();
+ switch(look) {
+ case '+':
+ Match('+');
+ Term();
+ tokens.add(new AddPtg());
+ continue;
+ case '-':
+ Match('-');
+ Term();
+ tokens.add(new SubtractPtg());
+ continue;
+ }
+ return; // finished with additive expression
}
-
- /*
- * This isn't quite right since it would allow multiple comparison operators.
- */
-
- if(look == '=' || look == '>' || look == '<') {
- if (look == '=') Equal();
- else if (look == '>') GreaterThan();
- else if (look == '<') LessThan();
- return;
- }
-
-
}
-
- /** Recognize and Translate a Greater Than */
- private void GreaterThan() {
- Match('>');
- if(look == '=')
- GreaterEqual();
- else {
- Expression();
- tokens.add(new GreaterThanPtg());
- }
- }
-
- /** Recognize and Translate a Less Than */
- private void LessThan() {
- Match('<');
- if(look == '=')
- LessEqual();
- else if(look == '>')
- NotEqual();
- else {
- Expression();
- tokens.add(new LessThanPtg());
- }
- }
-
- /**
- * Recognize and translate Greater than or Equal
- *
- */
- private void GreaterEqual() {
- Match('=');
- Expression();
- tokens.add(new GreaterEqualPtg());
- }
-
- /**
- * Recognize and translate Less than or Equal
- *
- */
-
- private void LessEqual() {
- Match('=');
- Expression();
- tokens.add(new LessEqualPtg());
- }
-
- /**
- * Recognize and not Equal
- *
- */
-
- private void NotEqual() {
- Match('>');
- Expression();
- tokens.add(new NotEqualPtg());
- }
-
//{--------------------------------------------------------------}
//{ Parse and Translate an Assignment Statement }
/**
@@ -794,48 +792,46 @@ begin
end;
**/
-
-
- /** Initialize */
-
- private void init() {
- GetChar();
- SkipWhite();
- }
-
+
+
/** API call to execute the parsing of the formula
*
*/
public void parse() {
- synchronized (tokens) {
- init();
- Expression();
+ pointer=0;
+ GetChar();
+ comparisonExpression();
+
+ if(pointer <= formulaLength) {
+ String msg = "Unused input [" + formulaString.substring(pointer-1)
+ + "] after attempting to parse the formula [" + formulaString + "]";
+ throw new FormulaParseException(msg);
}
}
-
-
+
+
/*********************************
* PARSER IMPLEMENTATION ENDS HERE
* EXCEL SPECIFIC METHODS BELOW
*******************************/
-
- /** API call to retrive the array of Ptgs created as
+
+ /** API call to retrive the array of Ptgs created as
* a result of the parsing
*/
public Ptg[] getRPNPtg() {
return getRPNPtg(FORMULA_TYPE_CELL);
}
-
+
public Ptg[] getRPNPtg(int formulaType) {
Node node = createTree();
setRootLevelRVA(node, formulaType);
setParameterRVA(node,formulaType);
return (Ptg[]) tokens.toArray(new Ptg[0]);
}
-
+
private void setRootLevelRVA(Node n, int formulaType) {
//Pg 16, excelfileformat.pdf @ openoffice.org
- Ptg p = (Ptg) n.getValue();
+ Ptg p = n.getValue();
if (formulaType == FormulaParser.FORMULA_TYPE_NAMEDRANGE) {
if (p.getDefaultOperandClass() == Ptg.CLASS_REF) {
setClass(n,Ptg.CLASS_REF);
@@ -845,9 +841,9 @@ end;
} else {
setClass(n,Ptg.CLASS_VALUE);
}
-
+
}
-
+
private void setParameterRVA(Node n, int formulaType) {
Ptg p = n.getValue();
int numOperands = n.getNumChildren();
@@ -863,11 +859,11 @@ end;
for (int i =0;i
+ *
+ * REFERENCE: 5.114
+ *
+ * @author Josh Micich
+ */
+public final class CRNCountRecord extends Record {
+ public final static short sid = 0x59;
+
+ private static final short BASE_RECORD_SIZE = 4;
+
+
+ private int field_1_number_crn_records;
+ private int field_2_sheet_table_index;
+
+ public CRNCountRecord() {
+ throw new RuntimeException("incomplete code");
+ }
+
+ public CRNCountRecord(RecordInputStream in) {
+ super(in);
+ }
+
+ protected void validateSid(short id) {
+ if (id != sid) {
+ throw new RecordFormatException("NOT An XCT RECORD");
+ }
+ }
+
+ public int getNumberOfCRNs() {
+ return field_1_number_crn_records;
+ }
+
+
+ protected void fillFields(RecordInputStream in) {
+ field_1_number_crn_records = in.readShort();
+ if(field_1_number_crn_records < 0) {
+ // TODO - seems like the sign bit of this field might be used for some other purpose
+ // see example file for test case "TestBugs.test19599()"
+ field_1_number_crn_records = (short)-field_1_number_crn_records;
+ }
+ field_2_sheet_table_index = in.readShort();
+ }
+
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(getClass().getName()).append(" [XCT");
+ sb.append(" nCRNs=").append(field_1_number_crn_records);
+ sb.append(" sheetIx=").append(field_2_sheet_table_index);
+ sb.append("]");
+ return sb.toString();
+ }
+
+ public int serialize(int offset, byte [] data) {
+ LittleEndian.putShort(data, 0 + offset, sid);
+ LittleEndian.putShort(data, 2 + offset, BASE_RECORD_SIZE);
+ LittleEndian.putShort(data, 4 + offset, (short)field_1_number_crn_records);
+ LittleEndian.putShort(data, 6 + offset, (short)field_2_sheet_table_index);
+ return getRecordSize();
+ }
+
+ public int getRecordSize() {
+ return BASE_RECORD_SIZE + 4;
+ }
+
+ /**
+ * return the non static version of the id for this record.
+ */
+ public short getSid() {
+ return sid;
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/record/CRNRecord.java b/src/java/org/apache/poi/hssf/record/CRNRecord.java
new file mode 100755
index 0000000000..73b9e42dfa
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/CRNRecord.java
@@ -0,0 +1,99 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record;
+
+import org.apache.poi.hssf.record.constant.ConstantValueParser;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * Title: CRN
+ * Description: This record stores the contents of an external cell or cell range
+ * REFERENCE: 5.23
+ *
+ * @author josh micich
+ */
+public final class CRNRecord extends Record {
+ public final static short sid = 0x5A;
+
+ private int field_1_last_column_index;
+ private int field_2_first_column_index;
+ private int field_3_row_index;
+ private Object[] field_4_constant_values;
+
+ public CRNRecord() {
+ throw new RuntimeException("incomplete code");
+ }
+
+ public CRNRecord(RecordInputStream in) {
+ super(in);
+ }
+
+ protected void validateSid(short id) {
+ if (id != sid) {
+ throw new RecordFormatException("NOT An XCT RECORD");
+ }
+ }
+
+ public int getNumberOfCRNs() {
+ return field_1_last_column_index;
+ }
+
+
+ protected void fillFields(RecordInputStream in) {
+ field_1_last_column_index = in.readByte() & 0x00FF;
+ field_2_first_column_index = in.readByte() & 0x00FF;
+ field_3_row_index = in.readShort();
+ int nValues = field_1_last_column_index - field_2_first_column_index + 1;
+ field_4_constant_values = ConstantValueParser.parse(in, nValues);
+ }
+
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(getClass().getName()).append(" [CRN");
+ sb.append(" rowIx=").append(field_3_row_index);
+ sb.append(" firstColIx=").append(field_2_first_column_index);
+ sb.append(" lastColIx=").append(field_1_last_column_index);
+ sb.append("]");
+ return sb.toString();
+ }
+ private int getDataSize() {
+ return 4 + ConstantValueParser.getEncodedSize(field_4_constant_values);
+ }
+
+ public int serialize(int offset, byte [] data) {
+ int dataSize = getDataSize();
+ LittleEndian.putShort(data, 0 + offset, sid);
+ LittleEndian.putShort(data, 2 + offset, (short) dataSize);
+ LittleEndian.putByte(data, 4 + offset, field_1_last_column_index);
+ LittleEndian.putByte(data, 5 + offset, field_2_first_column_index);
+ LittleEndian.putShort(data, 6 + offset, (short) field_3_row_index);
+ return getRecordSize();
+ }
+
+ public int getRecordSize() {
+ return getDataSize() + 4;
+ }
+
+ /**
+ * return the non static version of the id for this record.
+ */
+ public short getSid() {
+ return sid;
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/record/DVALRecord.java b/src/java/org/apache/poi/hssf/record/DVALRecord.java
index 2846f5066c..011c0a4355 100644
--- a/src/java/org/apache/poi/hssf/record/DVALRecord.java
+++ b/src/java/org/apache/poi/hssf/record/DVALRecord.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Copyright 2002-2004 Apache Software Foundation
@@ -20,13 +19,11 @@ package org.apache.poi.hssf.record;
import org.apache.poi.util.LittleEndian;
/**
- * Title: DVAL Record
+ * Title: DATAVALIDATIONS Record
* Description: used in data validation ;
- * This record is the list header of all data validation records in the current sheet.
+ * This record is the list header of all data validation records (0x01BE) in the current sheet.
* @author Dragos Buleandra (dragos.buleandra@trade2b.ro)
- * @version 2.0-pre
*/
-
public class DVALRecord extends Record
{
public final static short sid = 0x01B2;
@@ -41,13 +38,14 @@ public class DVALRecord extends Record
/** Object ID of the drop down arrow object for list boxes ;
* in our case this will be always FFFF , until
* MSODrawingGroup and MSODrawing records are implemented */
- private int field_cbo_id = 0xFFFFFFFF;
+ private int field_cbo_id;
/** Number of following DV Records */
- private int field_5_dv_no = 0x00000000;
+ private int field_5_dv_no;
- public DVALRecord()
- {
+ public DVALRecord() {
+ field_cbo_id = 0xFFFFFFFF;
+ field_5_dv_no = 0x00000000;
}
/**
diff --git a/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java b/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java
new file mode 100755
index 0000000000..771603c859
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java
@@ -0,0 +1,179 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record;
+
+import java.util.List;
+import java.util.Stack;
+
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.StringUtil;
+
+/**
+ * EXTERNALNAME
- * Description: A Extrenal Workbook Deciption (Sup Book)
+ * Title: Sup Book (EXTERNALBOOK)
+ * Description: A External Workbook Description (Suplemental Book)
* Its only a dummy record for making new ExternSheet Record
- * REFERENCE:
+ * REFERENCE: 5.38
* @author Libin Roman (Vista Portal LDT. Developer)
* @author Andrew C. Oliver (acoliver@apache.org)
*
*/
-public class SupBookRecord extends Record
-{
+public final class SupBookRecord extends Record {
+
public final static short sid = 0x1AE;
+
+ private static final short SMALL_RECORD_SIZE = 4;
+ private static final short TAG_INTERNAL_REFERENCES = 0x0401;
+ private static final short TAG_ADD_IN_FUNCTIONS = 0x3A01;
+
private short field_1_number_of_sheets;
- private short field_2_flag;
+ private UnicodeString field_2_encoded_url;
+ private UnicodeString[] field_3_sheet_names;
+ private boolean _isAddInFunctions;
-
- public SupBookRecord()
- {
- setFlag((short)0x401);
+
+ 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");
}
}
- /**
- * @param in the RecordInputstream to read the record from
- */
- 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
- field_1_number_of_sheets = in.readShort();
- field_2_flag = in.readShort();
+ 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;
+ }
+ /**
+ * 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) {
+ field_1_number_of_sheets = 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
+ * The name matching is case insensitive.
+ * @return
+ * The name matching is case insensitive.
+ * @return the standard worksheet function index if found, otherwise FUNCTION_INDEX_EXTERNAL
+ */
+ protected static short lookupIndex(String name) {
+ Integer index = (Integer) map.getKeyForValue(name.toUpperCase());
if (index != null) return index.shortValue();
- return INDEX_EXTERNAL;
+ return FUNCTION_INDEX_EXTERNAL;
}
/**
@@ -115,7 +143,7 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
BinaryTree dmap = new BinaryTree();
dmap.put(new Integer(0),"COUNT");
- dmap.put(new Integer(1),"specialflag");
+ dmap.put(new Integer(1),FUNCTION_NAME_IF);
dmap.put(new Integer(2),"ISNA");
dmap.put(new Integer(3),"ISERROR");
dmap.put(new Integer(4),"SUM");
@@ -354,7 +382,7 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
dmap.put(new Integer(252),"FREQUENCY");
dmap.put(new Integer(253),"ADDTOOLBAR");
dmap.put(new Integer(254),"DELETETOOLBAR");
- dmap.put(new Integer(255),"externalflag");
+ dmap.put(new Integer(FUNCTION_INDEX_EXTERNAL),"externalflag");
dmap.put(new Integer(256),"RESETTOOLBAR");
dmap.put(new Integer(257),"EVALUATE");
dmap.put(new Integer(258),"GETTOOLBAR");
diff --git a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
index ac260ffa4e..33278e25ed 100644
--- a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
@@ -17,15 +17,13 @@
package org.apache.poi.hssf.record.formula;
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.hssf.util.AreaReference;
-import org.apache.poi.hssf.util.CellReference;
-import org.apache.poi.hssf.util.SheetReferences;
-
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.RecordInputStream;
+import org.apache.poi.hssf.util.AreaReference;
+import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
+import org.apache.poi.util.LittleEndian;
/**
@@ -38,15 +36,15 @@ import org.apache.poi.util.BitFieldFactory;
* @version 1.0-pre
*/
-public class Area3DPtg extends Ptg
+public class Area3DPtg extends Ptg implements AreaI
{
public final static byte sid = 0x3b;
private final static int SIZE = 11; // 10 + 1 for Ptg
private short field_1_index_extern_sheet;
- private short field_2_first_row;
- private short field_3_last_row;
- private short field_4_first_column;
- private short field_5_last_column;
+ private int field_2_first_row;
+ private int field_3_last_row;
+ private int field_4_first_column;
+ private int field_5_last_column;
private BitField rowRelative = BitFieldFactory.getInstance( 0x8000 );
private BitField colRelative = BitFieldFactory.getInstance( 0x4000 );
@@ -66,10 +64,24 @@ public class Area3DPtg extends Ptg
public Area3DPtg(RecordInputStream in)
{
field_1_index_extern_sheet = in.readShort();
- field_2_first_row = in.readShort();
- field_3_last_row = in.readShort();
- field_4_first_column = in.readShort();
- field_5_last_column = in.readShort();
+ field_2_first_row = in.readUShort();
+ field_3_last_row = in.readUShort();
+ field_4_first_column = in.readUShort();
+ field_5_last_column = in.readUShort();
+ }
+
+ public Area3DPtg(short firstRow, short lastRow, short firstColumn, short lastColumn,
+ boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative,
+ short externalSheetIndex) {
+ setFirstRow(firstRow);
+ setLastRow(lastRow);
+ setFirstColumn(firstColumn);
+ setLastColumn(lastColumn);
+ setFirstRowRelative(firstRowRelative);
+ setLastRowRelative(lastRowRelative);
+ setFirstColRelative(firstColRelative);
+ setLastColRelative(lastColRelative);
+ setExternSheetIndex(externalSheetIndex);
}
public String toString()
@@ -87,7 +99,7 @@ public class Area3DPtg extends Ptg
buffer.append( "lastColRowRel = "
+ isLastRowRelative() ).append( "\n" );
buffer.append( "firstColRel = " + isFirstColRelative() ).append( "\n" );
- buffer.append( "lastColRel = " + isLastColRelative() ).append( "\n" );
+ buffer.append( "lastColRel = " + isLastColRelative() ).append( "\n" );
return buffer.toString();
}
@@ -95,10 +107,10 @@ public class Area3DPtg extends Ptg
{
array[0 + offset] = (byte) ( sid + ptgClass );
LittleEndian.putShort( array, 1 + offset, getExternSheetIndex() );
- LittleEndian.putShort( array, 3 + offset, getFirstRow() );
- LittleEndian.putShort( array, 5 + offset, getLastRow() );
- LittleEndian.putShort( array, 7 + offset, getFirstColumnRaw() );
- LittleEndian.putShort( array, 9 + offset, getLastColumnRaw() );
+ LittleEndian.putShort( array, 3 + offset, (short)getFirstRow() );
+ LittleEndian.putShort( array, 5 + offset, (short)getLastRow() );
+ LittleEndian.putShort( array, 7 + offset, (short)getFirstColumnRaw() );
+ LittleEndian.putShort( array, 9 + offset, (short)getLastColumnRaw() );
}
public int getSize()
@@ -116,32 +128,32 @@ public class Area3DPtg extends Ptg
field_1_index_extern_sheet = index;
}
- public short getFirstRow()
+ public int getFirstRow()
{
return field_2_first_row;
}
- public void setFirstRow( short row )
+ public void setFirstRow( int row )
{
field_2_first_row = row;
}
- public short getLastRow()
+ public int getLastRow()
{
return field_3_last_row;
}
- public void setLastRow( short row )
+ public void setLastRow( int row )
{
field_3_last_row = row;
}
- public short getFirstColumn()
+ public int getFirstColumn()
{
- return (short) ( field_4_first_column & 0xFF );
+ return field_4_first_column & 0xFF;
}
- public short getFirstColumnRaw()
+ public int getFirstColumnRaw()
{
return field_4_first_column;
}
@@ -167,12 +179,12 @@ public class Area3DPtg extends Ptg
field_4_first_column = column;
}
- public short getLastColumn()
+ public int getLastColumn()
{
- return (short) ( field_5_last_column & 0xFF );
+ return field_5_last_column & 0xFF;
}
- public short getLastColumnRaw()
+ public int getLastColumnRaw()
{
return field_5_last_column;
}
@@ -204,7 +216,7 @@ public class Area3DPtg extends Ptg
*/
public void setFirstRowRelative( boolean rel )
{
- field_4_first_column = rowRelative.setShortBoolean( field_4_first_column, rel );
+ field_4_first_column = rowRelative.setBoolean( field_4_first_column, rel );
}
/**
@@ -212,7 +224,7 @@ public class Area3DPtg extends Ptg
*/
public void setFirstColRelative( boolean rel )
{
- field_4_first_column = colRelative.setShortBoolean( field_4_first_column, rel );
+ field_4_first_column = colRelative.setBoolean( field_4_first_column, rel );
}
/**
@@ -221,7 +233,7 @@ public class Area3DPtg extends Ptg
*/
public void setLastRowRelative( boolean rel )
{
- field_5_last_column = rowRelative.setShortBoolean( field_5_last_column, rel );
+ field_5_last_column = rowRelative.setBoolean( field_5_last_column, rel );
}
/**
@@ -229,7 +241,7 @@ public class Area3DPtg extends Ptg
*/
public void setLastColRelative( boolean rel )
{
- field_5_last_column = colRelative.setShortBoolean( field_5_last_column, rel );
+ field_5_last_column = colRelative.setBoolean( field_5_last_column, rel );
}
@@ -243,39 +255,38 @@ public class Area3DPtg extends Ptg
public void setArea( String ref )
{
AreaReference ar = new AreaReference( ref );
- CellReference[] crs = ar.getCells();
- CellReference firstCell = crs[0];
- CellReference lastCell = firstCell;
- if(crs.length > 1) {
- lastCell = crs[1];
- }
+ CellReference frstCell = ar.getFirstCell();
+ CellReference lastCell = ar.getLastCell();
- setFirstRow( (short) firstCell.getRow() );
- setFirstColumn( (short) firstCell.getCol() );
- setLastRow( (short) lastCell.getRow() );
- setLastColumn( (short) lastCell.getCol() );
- setFirstColRelative( !firstCell.isColAbsolute() );
+ setFirstRow( (short) frstCell.getRow() );
+ setFirstColumn( frstCell.getCol() );
+ setLastRow( (short) lastCell.getRow() );
+ setLastColumn( lastCell.getCol() );
+ setFirstColRelative( !frstCell.isColAbsolute() );
setLastColRelative( !lastCell.isColAbsolute() );
- setFirstRowRelative( !firstCell.isRowAbsolute() );
+ setFirstRowRelative( !frstCell.isRowAbsolute() );
setLastRowRelative( !lastCell.isRowAbsolute() );
}
- /**
- * @return text representation of this area reference that can be used in text
- * formulas. The sheet name will get properly delimited if required.
- */
+ /**
+ * @return text representation of this area reference that can be used in text
+ * formulas. The sheet name will get properly delimited if required.
+ */
public String toFormulaString(Workbook book)
{
+ // First do the sheet name
StringBuffer retval = new StringBuffer();
String sheetName = Ref3DPtg.getSheetName(book, field_1_index_extern_sheet);
if(sheetName != null) {
SheetNameFormatter.appendFormat(retval, sheetName);
retval.append( '!' );
}
- retval.append( ( new CellReference( getFirstRow(), getFirstColumn(), !isFirstRowRelative(), !isFirstColRelative() ) ).toString() );
- retval.append( ':' );
- retval.append( ( new CellReference( getLastRow(), getLastColumn(), !isLastRowRelative(), !isLastColRelative() ) ).toString() );
+
+ // Now the normal area bit
+ retval.append( AreaPtg.toFormulaString(this, book) );
+
+ // All done
return retval.toString();
}
@@ -292,7 +303,7 @@ public class Area3DPtg extends Ptg
ptg.field_3_last_row = field_3_last_row;
ptg.field_4_first_column = field_4_first_column;
ptg.field_5_last_column = field_5_last_column;
- ptg.setClass(ptgClass);
+ ptg.setClass(ptgClass);
return ptg;
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaAPtg.java b/src/java/org/apache/poi/hssf/record/formula/AreaAPtg.java
index 57628f19b8..515d07dd41 100644
--- a/src/java/org/apache/poi/hssf/record/formula/AreaAPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/AreaAPtg.java
@@ -36,16 +36,14 @@ import org.apache.poi.hssf.model.Workbook;
* @author Jason Height (jheight at chariot dot net dot au)
*/
-public class AreaAPtg
- extends AreaPtg
-{
+public final class AreaAPtg extends AreaPtg {
public final static short sid = 0x65;
protected AreaAPtg() {
//Required for clone methods
}
- public AreaAPtg(short firstRow, short lastRow, short firstColumn, short lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
+ public AreaAPtg(int firstRow, int lastRow, int firstColumn, int lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
super(firstRow, lastRow, firstColumn, lastColumn, firstRowRelative, lastRowRelative, firstColRelative, lastColRelative);
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaI.java b/src/java/org/apache/poi/hssf/record/formula/AreaI.java
new file mode 100644
index 0000000000..5a0a21e861
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/formula/AreaI.java
@@ -0,0 +1,60 @@
+/* ====================================================================
+ 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;
+
+/**
+ * Common interface for AreaPtg and Area3DPtg, and their
+ * child classes.
+ */
+public interface AreaI {
+ /**
+ * @return the first row in the area
+ */
+ public int getFirstRow();
+
+ /**
+ * @return last row in the range (x2 in x1,y1-x2,y2)
+ */
+ public int getLastRow();
+
+ /**
+ * @return the first column number in the area.
+ */
+ public int getFirstColumn();
+
+ /**
+ * @return lastcolumn in the area
+ */
+ public int getLastColumn();
+
+ /**
+ * @return isrelative first column to relative or not
+ */
+ public boolean isFirstColRelative();
+ /**
+ * @return lastcol relative or not
+ */
+ public boolean isLastColRelative();
+ /**
+ * @return whether or not the first row is a relative reference or not.
+ */
+ public boolean isFirstRowRelative();
+ /**
+ * @return last row relative or not
+ */
+ public boolean isLastRowRelative();
+}
\ No newline at end of file
diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java b/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java
index 908c8d5e3c..be34e0074a 100644
--- a/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java
@@ -34,14 +34,18 @@ import org.apache.poi.hssf.record.RecordInputStream;
*/
public class AreaPtg
- extends Ptg
+ extends Ptg implements AreaI
{
public final static short sid = 0x25;
private final static int SIZE = 9;
- private short field_1_first_row;
- private short field_2_last_row;
- private short field_3_first_column;
- private short field_4_last_column;
+ /** zero based, unsigned 16 bit */
+ private int field_1_first_row;
+ /** zero based, unsigned 16 bit */
+ private int field_2_last_row;
+ /** zero based, unsigned 8 bit */
+ private int field_3_first_column;
+ /** zero based, unsigned 8 bit */
+ private int field_4_last_column;
private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000);
private final static BitField colRelative = BitFieldFactory.getInstance(0x4000);
@@ -53,17 +57,25 @@ public class AreaPtg
public AreaPtg(String arearef) {
AreaReference ar = new AreaReference(arearef);
- setFirstRow((short)ar.getCells()[0].getRow());
- setFirstColumn((short)ar.getCells()[0].getCol());
- setLastRow((short)ar.getCells()[1].getRow());
- setLastColumn((short)ar.getCells()[1].getCol());
- setFirstColRelative(!ar.getCells()[0].isColAbsolute());
- setLastColRelative(!ar.getCells()[1].isColAbsolute());
- setFirstRowRelative(!ar.getCells()[0].isRowAbsolute());
- setLastRowRelative(!ar.getCells()[1].isRowAbsolute());
+ CellReference firstCell = ar.getFirstCell();
+ CellReference lastCell = ar.getLastCell();
+ setFirstRow(firstCell.getRow());
+ setFirstColumn(firstCell.getCol());
+ setLastRow(lastCell.getRow());
+ setLastColumn(lastCell.getCol());
+ setFirstColRelative(!firstCell.isColAbsolute());
+ setLastColRelative(!lastCell.isColAbsolute());
+ setFirstRowRelative(!firstCell.isRowAbsolute());
+ setLastRowRelative(!lastCell.isRowAbsolute());
}
- public AreaPtg(short firstRow, short lastRow, short firstColumn, short lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
+ public AreaPtg(int firstRow, int lastRow, int firstColumn, int lastColumn,
+ boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
+
+ checkColumnBounds(firstColumn);
+ checkColumnBounds(lastColumn);
+ checkRowBounds(firstRow);
+ checkRowBounds(lastRow);
setFirstRow(firstRow);
setLastRow(lastRow);
setFirstColumn(firstColumn);
@@ -74,12 +86,23 @@ public class AreaPtg
setLastColRelative(lastColRelative);
}
+ private static void checkColumnBounds(int colIx) {
+ if((colIx & 0x0FF) != colIx) {
+ throw new IllegalArgumentException("colIx (" + colIx + ") is out of range");
+ }
+ }
+ private static void checkRowBounds(int rowIx) {
+ if((rowIx & 0x0FFFF) != rowIx) {
+ throw new IllegalArgumentException("rowIx (" + rowIx + ") is out of range");
+ }
+ }
+
public AreaPtg(RecordInputStream in)
{
- field_1_first_row = in.readShort();
- field_2_last_row = in.readShort();
- field_3_first_column = in.readShort();
- field_4_last_column = in.readShort();
+ field_1_first_row = in.readUShort();
+ field_2_last_row = in.readUShort();
+ field_3_first_column = in.readUShort();
+ field_4_last_column = in.readUShort();
//System.out.println(toString());
}
@@ -108,10 +131,10 @@ public class AreaPtg
public void writeBytes(byte [] array, int offset) {
array[offset] = (byte) (sid + ptgClass);
- LittleEndian.putShort(array,offset+1,field_1_first_row);
- LittleEndian.putShort(array,offset+3,field_2_last_row);
- LittleEndian.putShort(array,offset+5,field_3_first_column);
- LittleEndian.putShort(array,offset+7,field_4_last_column);
+ LittleEndian.putShort(array,offset+1,(short)field_1_first_row);
+ LittleEndian.putShort(array,offset+3,(short)field_2_last_row);
+ LittleEndian.putShort(array,offset+5,(short)field_3_first_column);
+ LittleEndian.putShort(array,offset+7,(short)field_4_last_column);
}
public int getSize()
@@ -122,42 +145,42 @@ public class AreaPtg
/**
* @return the first row in the area
*/
- public short getFirstRow()
+ public int getFirstRow()
{
return field_1_first_row;
}
/**
* sets the first row
- * @param row number (0-based)
+ * @param rowIx number (0-based)
*/
- public void setFirstRow(short row)
- {
- field_1_first_row = row;
+ public void setFirstRow(int rowIx) {
+ checkRowBounds(rowIx);
+ field_1_first_row = rowIx;
}
/**
* @return last row in the range (x2 in x1,y1-x2,y2)
*/
- public short getLastRow()
+ public int getLastRow()
{
return field_2_last_row;
}
/**
- * @param row last row number in the area
+ * @param rowIx last row number in the area
*/
- public void setLastRow(short row)
- {
- field_2_last_row = row;
+ public void setLastRow(int rowIx) {
+ checkRowBounds(rowIx);
+ field_2_last_row = rowIx;
}
/**
* @return the first column number in the area.
*/
- public short getFirstColumn()
+ public int getFirstColumn()
{
- return columnMask.getShortValue(field_3_first_column);
+ return columnMask.getValue(field_3_first_column);
}
/**
@@ -165,7 +188,7 @@ public class AreaPtg
*/
public short getFirstColumnRaw()
{
- return field_3_first_column;
+ return (short) field_3_first_column; // TODO
}
/**
@@ -181,7 +204,7 @@ public class AreaPtg
* @param rel is relative or not.
*/
public void setFirstRowRelative(boolean rel) {
- field_3_first_column=rowRelative.setShortBoolean(field_3_first_column,rel);
+ field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel);
}
/**
@@ -196,21 +219,21 @@ public class AreaPtg
* set whether the first column is relative
*/
public void setFirstColRelative(boolean rel) {
- field_3_first_column=colRelative.setShortBoolean(field_3_first_column,rel);
+ field_3_first_column=colRelative.setBoolean(field_3_first_column,rel);
}
/**
* set the first column in the area
*/
- public void setFirstColumn(short column)
- {
- field_3_first_column=columnMask.setShortValue(field_3_first_column, column);
+ public void setFirstColumn(int colIx) {
+ checkColumnBounds(colIx);
+ field_3_first_column=columnMask.setValue(field_3_first_column, colIx);
}
/**
* set the first column irespective of the bitmasks
*/
- public void setFirstColumnRaw(short column)
+ public void setFirstColumnRaw(int column)
{
field_3_first_column = column;
}
@@ -218,9 +241,9 @@ public class AreaPtg
/**
* @return lastcolumn in the area
*/
- public short getLastColumn()
+ public int getLastColumn()
{
- return columnMask.getShortValue(field_4_last_column);
+ return columnMask.getValue(field_4_last_column);
}
/**
@@ -228,7 +251,7 @@ public class AreaPtg
*/
public short getLastColumnRaw()
{
- return field_4_last_column;
+ return (short) field_4_last_column;
}
/**
@@ -245,7 +268,7 @@ public class AreaPtg
*
@@ -42,8 +40,14 @@ public class Ref3DPtg extends Ptg {
public final static byte sid = 0x3a;
private final static int SIZE = 7; // 6 + 1 for Ptg
private short field_1_index_extern_sheet;
- private short field_2_row;
- private short field_3_column;
+ /** The row index - zero based unsigned 16 bit value */
+ private int field_2_row;
+ /** Field 2
+ * - lower 8 bits is the zero based unsigned byte column index
+ * - bit 16 - isRowRelative
+ * - bit 15 - isColumnRelative
+ */
+ private int field_3_column;
private BitField rowRelative = BitFieldFactory.getInstance(0x8000);
private BitField colRelative = BitFieldFactory.getInstance(0x4000);
@@ -58,8 +62,8 @@ public class Ref3DPtg extends Ptg {
public Ref3DPtg(String cellref, short externIdx ) {
CellReference c= new CellReference(cellref);
- setRow((short) c.getRow());
- setColumn((short) c.getCol());
+ setRow(c.getRow());
+ setColumn(c.getCol());
setColRelative(!c.isColAbsolute());
setRowRelative(!c.isRowAbsolute());
setExternSheetIndex(externIdx);
@@ -81,8 +85,8 @@ public class Ref3DPtg extends Ptg {
public void writeBytes(byte [] array, int offset) {
array[ 0 + offset ] = (byte) (sid + ptgClass);
LittleEndian.putShort(array, 1 + offset , getExternSheetIndex());
- LittleEndian.putShort(array, 3 + offset , getRow());
- LittleEndian.putShort(array, 5 + offset , getColumnRaw());
+ LittleEndian.putShort(array, 3 + offset , (short)getRow());
+ LittleEndian.putShort(array, 5 + offset , (short)getColumnRaw());
}
public int getSize() {
@@ -97,19 +101,19 @@ public class Ref3DPtg extends Ptg {
field_1_index_extern_sheet = index;
}
- public short getRow() {
+ public int getRow() {
return field_2_row;
}
- public void setRow(short row) {
+ public void setRow(int row) {
field_2_row = row;
}
- public short getColumn() {
- return ( short ) (field_3_column & 0xFF);
+ public int getColumn() {
+ return field_3_column & 0xFF;
}
- public short getColumnRaw() {
+ public int getColumnRaw() {
return field_3_column;
}
@@ -119,7 +123,7 @@ public class Ref3DPtg extends Ptg {
}
public void setRowRelative(boolean rel) {
- field_3_column=rowRelative.setShortBoolean(field_3_column,rel);
+ field_3_column=rowRelative.setBoolean(field_3_column,rel);
}
public boolean isColRelative()
@@ -128,7 +132,7 @@ public class Ref3DPtg extends Ptg {
}
public void setColRelative(boolean rel) {
- field_3_column=colRelative.setShortBoolean(field_3_column,rel);
+ field_3_column=colRelative.setBoolean(field_3_column,rel);
}
public void setColumn(short column) {
field_3_column &= 0xFF00;
@@ -183,7 +187,7 @@ public class Ref3DPtg extends Ptg {
SheetNameFormatter.appendFormat(retval, sheetName);
retval.append( '!' );
}
- retval.append((new CellReference(getRow(),getColumn(),!isRowRelative(),!isColRelative())).toString());
+ retval.append((new CellReference(getRow(),getColumn(),!isRowRelative(),!isColRelative())).formatAsString());
return retval.toString();
}
@@ -197,5 +201,4 @@ public class Ref3DPtg extends Ptg {
ptg.setClass(ptgClass);
return ptg;
}
-
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/RefAPtg.java b/src/java/org/apache/poi/hssf/record/formula/RefAPtg.java
index 996f40e393..596b386235 100644
--- a/src/java/org/apache/poi/hssf/record/formula/RefAPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/RefAPtg.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -16,34 +15,23 @@
limitations under the License.
==================================================================== */
-/*
- * ValueReferencePtg.java
- *
- * Created on November 21, 2001, 5:27 PM
- */
package org.apache.poi.hssf.record.formula;
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.BitField;
-
import org.apache.poi.hssf.record.RecordInputStream;
-import org.apache.poi.hssf.util.CellReference;
-import org.apache.poi.hssf.model.Workbook;
/**
* RefNAPtg
* @author Jason Height (jheight at chariot dot net dot au)
*/
-public class RefAPtg extends ReferencePtg
-{
+public final class RefAPtg extends ReferencePtg {
public final static byte sid = 0x64;
protected RefAPtg() {
super();
}
- public RefAPtg(short row, short column, boolean isRowRelative, boolean isColumnRelative) {
+ public RefAPtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) {
super(row, column, isRowRelative, isColumnRelative);
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/RefVPtg.java b/src/java/org/apache/poi/hssf/record/formula/RefVPtg.java
index b9d55a09ec..8a6b2c03b4 100644
--- a/src/java/org/apache/poi/hssf/record/formula/RefVPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/RefVPtg.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -18,27 +17,20 @@
package org.apache.poi.hssf.record.formula;
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.BitField;
-
import org.apache.poi.hssf.record.RecordInputStream;
-import org.apache.poi.hssf.util.CellReference;
-import org.apache.poi.hssf.model.Workbook;
/**
* RefVPtg
* @author Jason Height (jheight at chariot dot net dot au)
*/
-
-public class RefVPtg extends ReferencePtg
-{
+public final class RefVPtg extends ReferencePtg {
public final static byte sid = 0x44;
protected RefVPtg() {
super();
}
- public RefVPtg(short row, short column, boolean isRowRelative, boolean isColumnRelative) {
+ public RefVPtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) {
super(row, column, isRowRelative, isColumnRelative);
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java b/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
index df3e5a70bc..4983c9d070 100644
--- a/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
@@ -31,26 +31,22 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Jason Height (jheight at chariot dot net dot au)
*/
-public class ReferencePtg extends Ptg
-{
+public class ReferencePtg extends Ptg {
private final static int SIZE = 5;
public final static byte sid = 0x24;
private final static int MAX_ROW_NUMBER = 65536;
- //public final static byte sid = 0x44;
- /**
- * The row number, between 0 and 65535, but stored as a signed
- * short between -32767 and 32768.
- * Take care about which version you fetch back!
+ /** The row index - zero based unsigned 16 bit value */
+ private int field_1_row;
+ /** Field 2
+ * - lower 8 bits is the zero based unsigned byte column index
+ * - bit 16 - isRowRelative
+ * - bit 15 - isColumnRelative
*/
- private short field_1_row;
- /**
- * The column number, between 0 and ??
- */
- private short field_2_col;
- private BitField rowRelative = BitFieldFactory.getInstance(0x8000);
- private BitField colRelative = BitFieldFactory.getInstance(0x4000);
- private BitField column = BitFieldFactory.getInstance(0x3FFF);
+ private int field_2_col;
+ private static final BitField rowRelative = BitFieldFactory.getInstance(0x8000);
+ private static final BitField colRelative = BitFieldFactory.getInstance(0x4000);
+ private static final BitField column = BitFieldFactory.getInstance(0x00FF);
protected ReferencePtg() {
//Required for clone methods
@@ -62,13 +58,13 @@ public class ReferencePtg extends Ptg
*/
public ReferencePtg(String cellref) {
CellReference c= new CellReference(cellref);
- setRow((short) c.getRow());
- setColumn((short) c.getCol());
+ setRow(c.getRow());
+ setColumn(c.getCol());
setColRelative(!c.isColAbsolute());
setRowRelative(!c.isRowAbsolute());
}
- public ReferencePtg(short row, short column, boolean isRowRelative, boolean isColumnRelative) {
+ public ReferencePtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) {
setRow(row);
setColumn(column);
setRowRelative(isRowRelative);
@@ -79,8 +75,8 @@ public class ReferencePtg extends Ptg
public ReferencePtg(RecordInputStream in)
{
- field_1_row = in.readShort();
- field_2_col = in.readShort();
+ field_1_row = in.readUShort();
+ field_2_col = in.readUShort();
}
public String getRefPtgName() {
@@ -104,33 +100,23 @@ public class ReferencePtg extends Ptg
{
array[offset] = (byte) (sid + ptgClass);
- LittleEndian.putShort(array,offset+1,field_1_row);
- LittleEndian.putShort(array,offset+3,field_2_col);
+ LittleEndian.putShort(array, offset+1, (short)field_1_row);
+ LittleEndian.putShort(array, offset+3, (short)field_2_col);
}
- public void setRow(short row)
- {
- field_1_row = row;
- }
public void setRow(int row)
{
if(row < 0 || row >= MAX_ROW_NUMBER) {
throw new IllegalArgumentException("The row number, when specified as an integer, must be between 0 and " + MAX_ROW_NUMBER);
}
-
- // Save, wrapping as needed
- if(row > Short.MAX_VALUE) {
- field_1_row = (short)(row - MAX_ROW_NUMBER);
- } else {
- field_1_row = (short)row;
- }
+ field_1_row = row;
}
/**
* Returns the row number as a short, which will be
* wrapped (negative) for values between 32769 and 65535
*/
- public short getRow()
+ public int getRow()
{
return field_1_row;
}
@@ -151,7 +137,7 @@ public class ReferencePtg extends Ptg
}
public void setRowRelative(boolean rel) {
- field_2_col=rowRelative.setShortBoolean(field_2_col,rel);
+ field_2_col=rowRelative.setBoolean(field_2_col,rel);
}
public boolean isColRelative()
@@ -160,27 +146,29 @@ public class ReferencePtg extends Ptg
}
public void setColRelative(boolean rel) {
- field_2_col=colRelative.setShortBoolean(field_2_col,rel);
+ field_2_col=colRelative.setBoolean(field_2_col,rel);
}
- public void setColumnRaw(short col)
+ public void setColumnRaw(int col)
{
field_2_col = col;
}
- public short getColumnRaw()
+ public int getColumnRaw()
{
return field_2_col;
}
- public void setColumn(short col)
+ public void setColumn(int col)
{
- field_2_col = column.setShortValue(field_2_col, col);
+ if(col < 0 || col > 0x100) {
+ throw new IllegalArgumentException("Specified colIx (" + col + ") is out of range");
+ }
+ field_2_col = column.setValue(field_2_col, col);
}
- public short getColumn()
- {
- return column.getShortValue(field_2_col);
+ public int getColumn() {
+ return column.getValue(field_2_col);
}
public int getSize()
@@ -191,7 +179,7 @@ public class ReferencePtg extends Ptg
public String toFormulaString(Workbook book)
{
//TODO -- should we store a cellreference instance in this ptg?? but .. memory is an issue, i believe!
- return (new CellReference(getRowAsInt(),getColumn(),!isRowRelative(),!isColRelative())).toString();
+ return (new CellReference(getRowAsInt(),getColumn(),!isRowRelative(),!isColRelative())).formatAsString();
}
public byte getDefaultOperandClass() {
diff --git a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java
index ba796db3b2..8e47cbe7a0 100755
--- a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java
+++ b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java
@@ -26,7 +26,7 @@ import java.util.regex.Pattern;
*
* @author Josh Micich
*/
-final class SheetNameFormatter {
+public final class SheetNameFormatter {
private static final String BIFF8_LAST_COLUMN = "IV";
private static final int BIFF8_LAST_COLUMN_TEXT_LEN = BIFF8_LAST_COLUMN.length();
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java
index 67f4557972..f906e91a49 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java
@@ -456,7 +456,7 @@ public class HSSFCell implements Cell
boolRec.setColumn(col);
if (setValue)
{
- boolRec.setValue(getBooleanCellValue());
+ boolRec.setValue(convertCellValueToBoolean());
}
boolRec.setXFIndex(styleIndex);
boolRec.setRow(row);
@@ -644,7 +644,7 @@ public class HSSFCell implements Cell
//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;
@@ -830,6 +830,34 @@ public class HSSFCell implements Cell
}
(( BoolErrRecord ) record).setValue(value);
}
+ /**
+ * Chooses a new boolean value for the cell when its type is changing.
+ *
+ * This interface recognises the requirement of some functions to freely create and evaluate
+ * references beyond those passed in as arguments.
+ *
+ * @author Josh Micich
+ */
+public interface FreeRefFunction {
+ /**
+ *
+ * @param args the pre-evaluated arguments for this function. args is never
+ *
+ *
+ *
+ * @author Josh Micich
+ */
+final class LinkTable {
+
+ private static final class CRNBlock {
+
+ private final CRNCountRecord _countRecord;
+ private final CRNRecord[] _crns;
+
+ public CRNBlock(RecordStream rs) {
+ _countRecord = (CRNCountRecord) rs.getNext();
+ int nCRNs = _countRecord.getNumberOfCRNs();
+ CRNRecord[] crns = new CRNRecord[nCRNs];
+ for (int i = 0; i < crns.length; i++) {
+ crns[i] = (CRNRecord) rs.getNext();
+ }
+ _crns = crns;
+ }
+ public CRNRecord[] getCrns() {
+ return (CRNRecord[]) _crns.clone();
+ }
+ }
+
+ private static final class ExternalBookBlock {
+ private final SupBookRecord _externalBookRecord;
+ private final ExternalNameRecord[] _externalNameRecords;
+ private final CRNBlock[] _crnBlocks;
+
+ public ExternalBookBlock(RecordStream rs) {
+ _externalBookRecord = (SupBookRecord) rs.getNext();
+ List temp = new ArrayList();
+ while(rs.peekNextClass() == ExternalNameRecord.class) {
+ temp.add(rs.getNext());
+ }
+ _externalNameRecords = new ExternalNameRecord[temp.size()];
+ temp.toArray(_externalNameRecords);
+
+ temp.clear();
+
+ while(rs.peekNextClass() == CRNCountRecord.class) {
+ temp.add(new CRNBlock(rs));
+ }
+ _crnBlocks = new CRNBlock[temp.size()];
+ temp.toArray(_crnBlocks);
+ }
+
+ public ExternalBookBlock(short numberOfSheets) {
+ _externalBookRecord = SupBookRecord.createInternalReferences(numberOfSheets);
+ _externalNameRecords = new ExternalNameRecord[0];
+ _crnBlocks = new CRNBlock[0];
+ }
+
+ public SupBookRecord getExternalBookRecord() {
+ return _externalBookRecord;
+ }
+
+ public String getNameText(int definedNameIndex) {
+ return _externalNameRecords[definedNameIndex].getText();
+ }
+ }
+
+ private final ExternalBookBlock[] _externalBookBlocks;
+ private final ExternSheetRecord _externSheetRecord;
+ private final List _definedNames;
+ private final int _recordCount;
+ private final WorkbookRecordList _workbookRecordList; // TODO - would be nice to remove this
+
+ public LinkTable(List inputList, int startIndex, WorkbookRecordList workbookRecordList) {
+
+ _workbookRecordList = workbookRecordList;
+ RecordStream rs = new RecordStream(inputList, startIndex);
+
+ List temp = new ArrayList();
+ while(rs.peekNextClass() == SupBookRecord.class) {
+ temp.add(new ExternalBookBlock(rs));
+ }
+ if(temp.size() < 1) {
+ throw new RuntimeException("Need at least one EXTERNALBOOK blocks");
+ }
+ _externalBookBlocks = new ExternalBookBlock[temp.size()];
+ temp.toArray(_externalBookBlocks);
+ temp.clear();
+
+ // If link table is present, there is always 1 of ExternSheetRecord
+ Record next = rs.getNext();
+ _externSheetRecord = (ExternSheetRecord)next;
+ _definedNames = new ArrayList();
+ // collect zero or more DEFINEDNAMEs id=0x18
+ while(rs.peekNextClass() == NameRecord.class) {
+ NameRecord nr = (NameRecord)rs.getNext();
+ _definedNames.add(nr);
+ }
+
+ _recordCount = rs.getCountRead();
+ _workbookRecordList.getRecords().addAll(inputList.subList(startIndex, startIndex + _recordCount));
+ }
+
+ public LinkTable(short numberOfSheets, WorkbookRecordList workbookRecordList) {
+ _workbookRecordList = workbookRecordList;
+ _definedNames = new ArrayList();
+ _externalBookBlocks = new ExternalBookBlock[] {
+ new ExternalBookBlock(numberOfSheets),
+ };
+ _externSheetRecord = new ExternSheetRecord();
+ _recordCount = 2;
+
+ // tell _workbookRecordList about the 2 new records
+
+ SupBookRecord supbook = _externalBookBlocks[0].getExternalBookRecord();
+
+ int idx = findFirstRecordLocBySid(CountryRecord.sid);
+ if(idx < 0) {
+ throw new RuntimeException("CountryRecord not found");
+ }
+ _workbookRecordList.add(idx+1, _externSheetRecord);
+ _workbookRecordList.add(idx+1, supbook);
+ }
+
+ /**
+ * TODO - would not be required if calling code used RecordStream or similar
+ */
+ public int getRecordCount() {
+ return _recordCount;
+ }
+
+
+ public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex) {
+
+ Iterator iterator = _definedNames.iterator();
+ while (iterator.hasNext()) {
+ NameRecord record = ( NameRecord ) iterator.next();
+
+ //print areas are one based
+ if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) {
+ return record;
+ }
+ }
+
+ return null;
+ }
+
+ public void removeBuiltinRecord(byte name, int sheetIndex) {
+ //the name array is smaller so searching through it should be faster than
+ //using the findFirstXXXX methods
+ NameRecord record = getSpecificBuiltinRecord(name, sheetIndex);
+ if (record != null) {
+ _definedNames.remove(record);
+ }
+ // TODO - do we need "Workbook.records.remove(...);" similar to that in Workbook.removeName(int namenum) {}?
+ }
+
+ public int getNumNames() {
+ return _definedNames.size();
+ }
+
+ public NameRecord getNameRecord(int index) {
+ return (NameRecord) _definedNames.get(index);
+ }
+
+ public void addName(NameRecord name) {
+ _definedNames.add(name);
+
+ // TODO - this is messy
+ // Not the most efficient way but the other way was causing too many bugs
+ int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
+ if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
+ if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
+ int countNames = _definedNames.size();
+ _workbookRecordList.add(idx+countNames, name);
+
+ }
+
+ public void removeName(int namenum) {
+ _definedNames.remove(namenum);
+ }
+
+ public short getIndexToSheet(short num) {
+ return _externSheetRecord.getREFRecordAt(num).getIndexToFirstSupBook();
+ }
+
+ public int getSheetIndexFromExternSheetIndex(int externSheetNumber) {
+ if (externSheetNumber >= _externSheetRecord.getNumOfREFStructures()) {
+ return -1;
+ }
+ return _externSheetRecord.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook();
+ }
+
+ public short addSheetIndexToExternSheet(short sheetNumber) {
+
+ ExternSheetSubRecord record = new ExternSheetSubRecord();
+ record.setIndexToFirstSupBook(sheetNumber);
+ record.setIndexToLastSupBook(sheetNumber);
+ _externSheetRecord.addREFRecord(record);
+ _externSheetRecord.setNumOfREFStructures((short)(_externSheetRecord.getNumOfREFStructures() + 1));
+ return (short)(_externSheetRecord.getNumOfREFStructures() - 1);
+ }
+
+ public short checkExternSheet(int sheetNumber) {
+
+ //Trying to find reference to this sheet
+ int nESRs = _externSheetRecord.getNumOfREFStructures();
+ for(short i=0; i< nESRs; i++) {
+ ExternSheetSubRecord esr = _externSheetRecord.getREFRecordAt(i);
+
+ if (esr.getIndexToFirstSupBook() == sheetNumber
+ && esr.getIndexToLastSupBook() == sheetNumber){
+ return i;
+ }
+ }
+
+ //We Haven't found reference to this sheet
+ return addSheetIndexToExternSheet((short) sheetNumber);
+ }
+
+
+ /**
+ * copied from Workbook
+ */
+ private int findFirstRecordLocBySid(short sid) {
+ int index = 0;
+ for (Iterator iterator = _workbookRecordList.iterator(); iterator.hasNext(); ) {
+ Record record = ( Record ) iterator.next();
+
+ if (record.getSid() == sid) {
+ return index;
+ }
+ index ++;
+ }
+ return -1;
+ }
+
+ public int getNumberOfREFStructures() {
+ return _externSheetRecord.getNumOfREFStructures();
+ }
+
+ public String resolveNameXText(int refIndex, int definedNameIndex) {
+ short extBookIndex = _externSheetRecord.getREFRecordAt(refIndex).getIndexToSupBook();
+ return _externalBookBlocks[extBookIndex].getNameText(definedNameIndex);
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/model/RecordStream.java b/src/java/org/apache/poi/hssf/model/RecordStream.java
new file mode 100755
index 0000000000..03177c7c22
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/model/RecordStream.java
@@ -0,0 +1,65 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.model;
+
+import java.util.List;
+
+import org.apache.poi.hssf.record.Record;
+/**
+ * Simplifies iteration over a sequence of Record objects.
+ *
+ * @author Josh Micich
+ */
+final class RecordStream {
+
+ private final List _list;
+ private int _nextIndex;
+ private int _countRead;
+
+ public RecordStream(List inputList, int startIndex) {
+ _list = inputList;
+ _nextIndex = startIndex;
+ _countRead = 0;
+ }
+
+ public boolean hasNext() {
+ return _nextIndex < _list.size();
+ }
+
+ public Record getNext() {
+ if(_nextIndex >= _list.size()) {
+ throw new RuntimeException("Attempt to read past end of record stream");
+ }
+ _countRead ++;
+ return (Record) _list.get(_nextIndex++);
+ }
+
+ /**
+ * @return the {@link Class} of the next Record.
+ *
+ *
+ *
+ * null
if this stream is exhausted.
+ */
+ public Class peekNextClass() {
+ if(_nextIndex >= _list.size()) {
+ return null;
+ }
+ return _list.get(_nextIndex).getClass();
+ }
+
+ public int getCountRead() {
+ return _countRead;
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java
index 2ba50857ca..8fa3010a4b 100644
--- a/src/java/org/apache/poi/hssf/model/Workbook.java
+++ b/src/java/org/apache/poi/hssf/model/Workbook.java
@@ -79,10 +79,8 @@ public class Workbook implements Model
*/
protected SSTRecord sst = null;
- /**
- * Holds the Extern Sheet with references to bound sheets
- */
- protected ExternSheetRecord externSheet= null;
+
+ private LinkTable linkTable; // optionally occurs if there are references in the document. (4.10.3)
/**
* holds the "boundsheet" records (aka bundlesheet) so that they can have their
@@ -92,8 +90,6 @@ public class Workbook implements Model
protected ArrayList formats = new ArrayList();
- protected ArrayList names = new ArrayList();
-
protected ArrayList hyperlinks = new ArrayList();
protected int numxfs = 0; // hold the number of extended format records
@@ -134,6 +130,7 @@ public class Workbook implements Model
new Integer(recs.size()));
Workbook retval = new Workbook();
ArrayList records = new ArrayList(recs.size() / 3);
+ retval.records.setRecords(records);
int k;
for (k = 0; k < recs.size(); k++) {
@@ -192,21 +189,16 @@ public class Workbook implements Model
retval.records.setBackuppos( k );
break;
case ExternSheetRecord.sid :
- if (log.check( POILogger.DEBUG ))
- log.log(DEBUG, "found extern sheet record at " + k);
- retval.externSheet = ( ExternSheetRecord ) rec;
- break;
+ throw new RuntimeException("Extern sheet is part of LinkTable");
case NameRecord.sid :
- if (log.check( POILogger.DEBUG ))
- log.log(DEBUG, "found name record at " + k);
- retval.names.add(rec);
- // retval.records.namepos = k;
- break;
+ throw new RuntimeException("DEFINEDNAME is part of LinkTable");
case SupBookRecord.sid :
if (log.check( POILogger.DEBUG ))
log.log(DEBUG, "found SupBook record at " + k);
+ retval.linkTable = new LinkTable(recs, k, retval.records);
// retval.records.supbookpos = k;
- break;
+ k+=retval.linkTable.getRecordCount() - 1;
+ continue;
case FormatRecord.sid :
if (log.check( POILogger.DEBUG ))
log.log(DEBUG, "found format record at " + k);
@@ -262,8 +254,6 @@ public class Workbook implements Model
break;
}
}
-
- retval.records.setRecords(records);
if (retval.windowOne == null) {
retval.windowOne = (WindowOneRecord) retval.createWindowOne();
@@ -283,6 +273,7 @@ public class Workbook implements Model
log.log( DEBUG, "creating new workbook from scratch" );
Workbook retval = new Workbook();
ArrayList records = new ArrayList( 30 );
+ retval.records.setRecords(records);
ArrayList formats = new ArrayList( 8 );
records.add( retval.createBOF() );
@@ -339,8 +330,9 @@ public class Workbook implements Model
records.add( retval.createStyle( k ) );
}
records.add( retval.createUseSelFS() );
- for ( int k = 0; k < 1; k++ )
- { // now just do 1
+
+ int nBoundSheets = 1; // now just do 1
+ for ( int k = 0; k < nBoundSheets; k++ ) {
BoundSheetRecord bsr =
(BoundSheetRecord) retval.createBoundSheet( k );
@@ -351,12 +343,14 @@ public class Workbook implements Model
// retval.records.supbookpos = retval.records.bspos + 1;
// retval.records.namepos = retval.records.supbookpos + 2;
records.add( retval.createCountry() );
+ for ( int k = 0; k < nBoundSheets; k++ ) {
+ retval.getOrCreateLinkTable().checkExternSheet(k);
+ }
retval.sst = (SSTRecord) retval.createSST();
records.add( retval.sst );
records.add( retval.createExtendedSST() );
records.add( retval.createEOF() );
- retval.records.setRecords(records);
if (log.check( POILogger.DEBUG ))
log.log( DEBUG, "exit create new workbook from scratch" );
return retval;
@@ -369,36 +363,20 @@ public class Workbook implements Model
* @param sheetIndex Index to match
* @return null if no builtin NameRecord matches
*/
- public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex)
- {
- Iterator iterator = names.iterator();
- while (iterator.hasNext()) {
- NameRecord record = ( NameRecord ) iterator.next();
-
- //print areas are one based
- if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) {
- return record;
- }
- }
-
- return null;
-
- }
+ public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex)
+ {
+ return getOrCreateLinkTable().getSpecificBuiltinRecord(name, sheetIndex);
+ }
/**
* Removes the specified Builtin NameRecord that matches the name and index
* @param name byte representation of the builtin to match
* @param sheetIndex zero-based sheet reference
*/
- public void removeBuiltinRecord(byte name, int sheetIndex) {
- //the name array is smaller so searching through it should be faster than
- //using the findFirstXXXX methods
- NameRecord record = getSpecificBuiltinRecord(name, sheetIndex);
- if (record != null) {
- names.remove(record);
- }
-
- }
+ public void removeBuiltinRecord(byte name, int sheetIndex) {
+ linkTable.removeBuiltinRecord(name, sheetIndex);
+ // TODO - do we need "this.records.remove(...);" similar to that in this.removeName(int namenum) {}?
+ }
public int getNumRecords() {
return records.size();
@@ -614,6 +592,7 @@ public class Workbook implements Model
records.add(records.getBspos()+1, bsr);
records.setBspos( records.getBspos() + 1 );
boundsheets.add(bsr);
+ getOrCreateLinkTable().checkExternSheet(sheetnum);
fixTabIdRecord();
}
}
@@ -1824,14 +1803,26 @@ public class Workbook implements Model
protected Record createEOF() {
return new EOFRecord();
}
+
+ /**
+ * lazy initialization
+ * Note - creating the link table causes creation of 1 EXTERNALBOOK and 1 EXTERNALSHEET record
+ */
+ private LinkTable getOrCreateLinkTable() {
+ if(linkTable == null) {
+ linkTable = new LinkTable((short) getNumSheets(), records);
+ }
+ return linkTable;
+ }
public SheetReferences getSheetReferences() {
SheetReferences refs = new SheetReferences();
- if (externSheet != null) {
- for (int k = 0; k < externSheet.getNumOfREFStructures(); k++) {
+ if (linkTable != null) {
+ int numRefStructures = linkTable.getNumberOfREFStructures();
+ for (short k = 0; k < numRefStructures; k++) {
- String sheetName = findSheetNameFromExternSheet((short)k);
+ String sheetName = findSheetNameFromExternSheet(k);
refs.addSheetReference(sheetName, k);
}
@@ -1846,7 +1837,8 @@ public class Workbook implements Model
public String findSheetNameFromExternSheet(short num){
String result="";
- short indexToSheet = externSheet.getREFRecordAt(num).getIndexToFirstSupBook();
+ short indexToSheet = linkTable.getIndexToSheet(num);
+
if (indexToSheet>-1) { //error check, bail out gracefully!
result = getSheetName(indexToSheet);
}
@@ -1861,10 +1853,7 @@ public class Workbook implements Model
*/
public int getSheetIndexFromExternSheetIndex(int externSheetNumber)
{
- if (externSheetNumber >= externSheet.getNumOfREFStructures())
- return -1;
- else
- return externSheet.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook();
+ return linkTable.getSheetIndexFromExternSheetIndex(externSheetNumber);
}
/** returns the extern sheet number for specific sheet number ,
@@ -1873,58 +1862,17 @@ public class Workbook implements Model
* @return index to extern sheet
*/
public short checkExternSheet(int sheetNumber){
-
- int i = 0;
- boolean flag = false;
- short result = 0;
-
- if (externSheet == null) {
- externSheet = createExternSheet();
- }
-
- //Trying to find reference to this sheet
- while (i < externSheet.getNumOfREFStructures() && !flag){
- ExternSheetSubRecord record = externSheet.getREFRecordAt(i);
-
- if (record.getIndexToFirstSupBook() == sheetNumber &&
- record.getIndexToLastSupBook() == sheetNumber){
- flag = true;
- result = (short) i;
- }
-
- ++i;
- }
-
- //We Havent found reference to this sheet
- if (!flag) {
- result = addSheetIndexToExternSheet((short) sheetNumber);
- }
-
- return result;
+ return getOrCreateLinkTable().checkExternSheet(sheetNumber);
}
- private short addSheetIndexToExternSheet(short sheetNumber){
- short result;
-
- ExternSheetSubRecord record = new ExternSheetSubRecord();
- record.setIndexToFirstSupBook(sheetNumber);
- record.setIndexToLastSupBook(sheetNumber);
- externSheet.addREFRecord(record);
- externSheet.setNumOfREFStructures((short)(externSheet.getNumOfREFStructures() + 1));
- result = (short)(externSheet.getNumOfREFStructures() - 1);
-
- return result;
- }
-
-
-
/** gets the total number of names
* @return number of names
*/
public int getNumNames(){
- int result = names.size();
-
- return result;
+ if(linkTable == null) {
+ return 0;
+ }
+ return linkTable.getNumNames();
}
/** gets the name record
@@ -1932,28 +1880,14 @@ public class Workbook implements Model
* @return name record
*/
public NameRecord getNameRecord(int index){
- NameRecord result = (NameRecord) names.get(index);
-
- return result;
-
+ return linkTable.getNameRecord(index);
}
/** creates new name
* @return new name record
*/
public NameRecord createName(){
-
- NameRecord name = new NameRecord();
-
- // Not the most efficient way but the other way was causing too many bugs
- int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
- if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
- if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
-
- records.add(idx+names.size()+1, name);
- names.add(name);
-
- return name;
+ return addName(new NameRecord());
}
@@ -1962,67 +1896,41 @@ public class Workbook implements Model
*/
public NameRecord addName(NameRecord name)
{
- // Not the most efficient way but the other way was causing too many bugs
- int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
- if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
- if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
- records.add(idx+names.size()+1, name);
- names.add(name);
+
+ getOrCreateLinkTable().addName(name);
return name;
}
- /**Generates a NameRecord to represent a built-in region
- * @return a new NameRecord unless the index is invalid
- */
- public NameRecord createBuiltInName(byte builtInName, int index)
- {
- if (index == -1 || index+1 > (int)Short.MAX_VALUE)
- throw new IllegalArgumentException("Index is not valid ["+index+"]");
-
- NameRecord name = new NameRecord(builtInName, (short)(index));
-
- addName(name);
-
- return name;
- }
+ /**Generates a NameRecord to represent a built-in region
+ * @return a new NameRecord unless the index is invalid
+ */
+ public NameRecord createBuiltInName(byte builtInName, int index)
+ {
+ if (index == -1 || index+1 > Short.MAX_VALUE)
+ throw new IllegalArgumentException("Index is not valid ["+index+"]");
+
+ NameRecord name = new NameRecord(builtInName, (short)(index));
+
+ addName(name);
+
+ return name;
+ }
/** removes the name
* @param namenum name index
*/
public void removeName(int namenum){
- if (names.size() > namenum) {
+
+ if (linkTable.getNumNames() > namenum) {
int idx = findFirstRecordLocBySid(NameRecord.sid);
records.remove(idx + namenum);
- names.remove(namenum);
+ linkTable.removeName(namenum);
}
}
- /** creates a new extern sheet record
- * @return the new extern sheet record
- */
- protected ExternSheetRecord createExternSheet(){
- ExternSheetRecord externSheet = new ExternSheetRecord();
-
- int idx = findFirstRecordLocBySid(CountryRecord.sid);
-
- records.add(idx+1, externSheet);
-// records.add(records.supbookpos + 1 , rec);
-
- //We also adds the supBook for internal reference
- SupBookRecord supbook = new SupBookRecord();
-
- supbook.setNumberOfSheets((short)getNumSheets());
- //supbook.setFlag();
-
- records.add(idx+1, supbook);
-// records.add(records.supbookpos + 1 , supbook);
-
- return externSheet;
- }
-
/**
* Returns a format index that matches the passed in format. It does not tie into HSSFDataFormat.
* @param format the format string
@@ -2392,6 +2300,17 @@ public class Workbook implements Model
}
return this.fileShare;
}
+
+ /**
+ * is the workbook protected with a password (not encrypted)?
+ */
+ public boolean isWriteProtected() {
+ if (this.fileShare == null) {
+ return false;
+ }
+ FileSharingRecord frec = getFileSharing();
+ return (frec.getReadOnly() == 1);
+ }
/**
* protect a workbook with a password (not encypted, just sets writeprotect
@@ -2419,5 +2338,14 @@ public class Workbook implements Model
writeProtect = null;
}
+ /**
+ * @param refIndex Index to REF entry in EXTERNSHEET record in the Link Table
+ * @param definedNameIndex zero-based to DEFINEDNAME or EXTERNALNAME record
+ * @return the string representation of the defined or external name
+ */
+ public String resolveNameXText(int refIndex, int definedNameIndex) {
+ return linkTable.resolveNameXText(refIndex, definedNameIndex);
+ }
}
+
diff --git a/src/java/org/apache/poi/hssf/record/CRNCountRecord.java b/src/java/org/apache/poi/hssf/record/CRNCountRecord.java
new file mode 100755
index 0000000000..4c9e4425cd
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/CRNCountRecord.java
@@ -0,0 +1,94 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record;
+
+import org.apache.poi.util.LittleEndian;
+/**
+ * XCT – CRN Count true
, this denotes the 'StdDocumentName'
+ */
+ public boolean isStdDocumentNameIdentifier() {
+ return (field_1_option_flag & OPT_STD_DOCUMENT_NAME) != 0;
+ }
+ public boolean isOLELink() {
+ return (field_1_option_flag & OPT_OLE_LINK) != 0;
+ }
+ public boolean isIconifiedPictureLink() {
+ return (field_1_option_flag & OPT_ICONIFIED_PICTURE_LINK) != 0;
+ }
+ /**
+ * @return the standard String representation of this name
+ */
+ public String getText() {
+ return field_4_name;
+ }
+
+
+ /**
+ * called by constructor, should throw runtime exception in the event of a
+ * record passed with a differing ID.
+ *
+ * @param id alleged id for this record
+ */
+ protected void validateSid(short id) {
+ if (id != sid) {
+ throw new RecordFormatException("NOT A valid ExternalName RECORD");
+ }
+ }
+
+ private int getDataSize(){
+ return 2 + 2 + field_4_name.length() + 2 + getNameDefinitionSize();
+ }
+
+ /**
+ * called by the class that is responsible for writing this sucker.
+ * Subclasses should implement this so that their data is passed back in a
+ * byte array.
+ *
+ * @param offset to begin writing at
+ * @param data byte array containing instance data
+ * @return number of bytes written
+ */
+ public int serialize( int offset, byte[] data ) {
+ // TODO - junit tests
+ int dataSize = getDataSize();
+
+ LittleEndian.putShort( data, 0 + offset, sid );
+ LittleEndian.putShort( data, 2 + offset, (short) dataSize );
+ LittleEndian.putShort( data, 4 + offset, field_1_option_flag );
+ LittleEndian.putShort( data, 6 + offset, field_2_index );
+ LittleEndian.putShort( data, 8 + offset, field_3_not_used );
+ short nameLen = (short) field_4_name.length();
+ LittleEndian.putShort( data, 10 + offset, nameLen );
+ StringUtil.putCompressedUnicode( field_4_name, data, 10 + offset );
+ short defLen = (short) getNameDefinitionSize();
+ LittleEndian.putShort( data, 12 + nameLen + offset, defLen );
+ Ptg.serializePtgStack(field_5_name_definition, data, 12 + nameLen + offset );
+ return dataSize + 4;
+ }
+
+ private int getNameDefinitionSize() {
+ int result = 0;
+ List list = field_5_name_definition;
+
+ for (int k = 0; k < list.size(); k++)
+ {
+ Ptg ptg = ( Ptg ) list.get(k);
+
+ result += ptg.getSize();
+ }
+ return result;
+ }
+
+
+ public int getRecordSize(){
+ return 6 + 2 + field_4_name.length() + 2 + getNameDefinitionSize();
+ }
+
+
+ protected void fillFields(RecordInputStream in) {
+ field_1_option_flag = in.readShort();
+ field_2_index = in.readShort();
+ field_3_not_used = in.readShort();
+ short nameLength = in.readShort();
+ field_4_name = in.readCompressedUnicode(nameLength);
+ short formulaLen = in.readShort();
+ field_5_name_definition = Ptg.createParsedExpressionTokens(formulaLen, in);
+ }
+
+ public short getSid() {
+ return sid;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(getClass().getName()).append(" [EXTERNALNAME ");
+ sb.append(" ").append(field_4_name);
+ sb.append(" ix=").append(field_2_index);
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/record/FileSharingRecord.java b/src/java/org/apache/poi/hssf/record/FileSharingRecord.java
index 17f8cda64a..2f56eb092a 100644
--- a/src/java/org/apache/poi/hssf/record/FileSharingRecord.java
+++ b/src/java/org/apache/poi/hssf/record/FileSharingRecord.java
@@ -19,6 +19,8 @@
package org.apache.poi.hssf.record;
import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
import org.apache.poi.util.StringUtil;
/**
@@ -29,6 +31,8 @@ import org.apache.poi.util.StringUtil;
*/
public class FileSharingRecord extends Record {
+ private static POILogger logger = POILogFactory.getLogger(FileSharingRecord.class);
+
public final static short sid = 0x5b;
private short field_1_readonly;
private short field_2_password;
@@ -58,8 +62,23 @@ public class FileSharingRecord extends Record {
field_1_readonly = in.readShort();
field_2_password = in.readShort();
field_3_username_length = in.readByte();
+
+ // Is this really correct? The latest docs
+ // seem to hint there's nothing between the
+ // username length and the username string
field_4_unknown = in.readShort();
- field_5_username = in.readCompressedUnicode(field_3_username_length);
+
+ // Ensure we don't try to read more data than
+ // there actually is
+ if(field_3_username_length > in.remaining()) {
+ logger.log(POILogger.WARN, "FileSharingRecord defined a username of length " + field_3_username_length + ", but only " + in.remaining() + " bytes were left, truncating");
+ field_3_username_length = (byte)in.remaining();
+ }
+ if(field_3_username_length > 0) {
+ field_5_username = in.readCompressedUnicode(field_3_username_length);
+ } else {
+ field_5_username = "";
+ }
}
//this is the world's lamest "security". thanks to Wouter van Vugt for making me
diff --git a/src/java/org/apache/poi/hssf/record/NameRecord.java b/src/java/org/apache/poi/hssf/record/NameRecord.java
index 3b1c413d58..a06bc8aedd 100644
--- a/src/java/org/apache/poi/hssf/record/NameRecord.java
+++ b/src/java/org/apache/poi/hssf/record/NameRecord.java
@@ -726,7 +726,7 @@ public class NameRecord extends Record {
for(int i=0; i
+ * As it turns out, this is not a problem, because in these circumstances, the existing value
+ * for parsedExpression is perfectly OK.
+ *
+ * This method may also be used for setting breakpoints to help diagnose issues regarding the
+ * abnormally-set 'shared formula' flags.
+ * (see TestValueRecordsAggregate.testSpuriousSharedFormulaFlag()).
+ *
+ * The method currently does nothing but do not delete it without finding a nice home for this
+ * comment.
+ */
+ private static void handleMissingSharedFormulaRecord(FormulaRecord formula) {
+ // could log an info message here since this is a fairly unusual occurrence.
+ }
+
/**
* called by the class that is responsible for writing this sucker.
* Subclasses should implement this so that their data is passed back in a
@@ -300,7 +322,7 @@ public class ValueRecordsAggregate
return rec;
}
- public class MyIterator implements Iterator {
+ private final class MyIterator implements Iterator {
short nextColumn=-1;
int nextRow,lastRow;
diff --git a/src/java/org/apache/poi/hssf/record/constant/ConstantValueParser.java b/src/java/org/apache/poi/hssf/record/constant/ConstantValueParser.java
new file mode 100755
index 0000000000..6ec831e4a0
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/constant/ConstantValueParser.java
@@ -0,0 +1,111 @@
+/* ====================================================================
+ 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.
+ * TODO - code in ArrayPtg should be merged with this code. It currently supports only 2 of the constant types
+ *
+ * @author Josh Micich
+ */
+public final class ConstantValueParser {
+ // note - value 3 seems to be unused
+ private static final int TYPE_EMPTY = 0;
+ private static final int TYPE_NUMBER = 1;
+ private static final int TYPE_STRING = 2;
+ private static final int TYPE_BOOLEAN = 4;
+
+ private static final int TRUE_ENCODING = 1;
+ private static final int FALSE_ENCODING = 0;
+
+ // TODO - is this the best way to represent 'EMPTY'?
+ private static final Object EMPTY_REPRESENTATION = null;
+
+ private ConstantValueParser() {
+ // no instances of this class
+ }
+
+ public static Object[] parse(RecordInputStream in, int nValues) {
+ Object[] result = new Object[nValues];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = readAConstantValue(in);
+ }
+ return result;
+ }
+
+ private static Object readAConstantValue(RecordInputStream in) {
+ byte grbit = in.readByte();
+ switch(grbit) {
+ case TYPE_EMPTY:
+ in.readLong(); // 8 byte 'not used' field
+ return EMPTY_REPRESENTATION;
+ case TYPE_NUMBER:
+ return new Double(in.readDouble());
+ case TYPE_STRING:
+ return in.readUnicodeString();
+ case TYPE_BOOLEAN:
+ return readBoolean(in);
+ }
+ return null;
+ }
+
+ private static Object readBoolean(RecordInputStream in) {
+ byte val = in.readByte();
+ in.readLong(); // 8 byte 'not used' field
+ switch(val) {
+ case FALSE_ENCODING:
+ return Boolean.FALSE;
+ case TRUE_ENCODING:
+ return Boolean.TRUE;
+ }
+ // Don't tolerate unusual boolean encoded values (unless it becomes evident that they occur)
+ throw new RuntimeException("unexpected boolean encoding (" + val + ")");
+ }
+
+ public static int getEncodedSize(Object[] values) {
+ // start with one byte 'type' code for each value
+ int result = values.length * 1;
+ for (int i = 0; i < values.length; i++) {
+ result += getEncodedSize(values[i]);
+ }
+ return 0;
+ }
+
+ /**
+ * @return encoded size without the 'type' code byte
+ */
+ private static int getEncodedSize(Object object) {
+ if(object == EMPTY_REPRESENTATION) {
+ return 8;
+ }
+ Class cls = object.getClass();
+ if(cls == Boolean.class || cls == Double.class) {
+ return 8;
+ }
+ UnicodeString strVal = (UnicodeString)object;
+ UnicodeRecordStats urs = new UnicodeRecordStats();
+ strVal.getRecordSize(urs);
+ return urs.recordSize;
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java b/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java
index 555c502949..c7c57280d3 100644
--- a/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java
@@ -29,10 +29,13 @@ import org.apache.poi.hssf.model.Workbook;
* @author Andrew C. Oliver (acoliver at apache dot org)
*/
public abstract class AbstractFunctionPtg extends OperationPtg {
- //constant used allow a ptgAttr to be mapped properly for its functionPtg
- public static final String ATTR_NAME = "specialflag";
-
- public static final short INDEX_EXTERNAL = 255;
+
+ /**
+ * The name of the IF function (i.e. "IF"). Extracted as a constant for clarity.
+ */
+ public static final String FUNCTION_NAME_IF = "IF";
+ /** All external functions have function index 255 */
+ private static final short FUNCTION_INDEX_EXTERNAL = 255;
private static BinaryTree map = produceHash();
protected static Object[][] functionData = produceFunctionData();
@@ -66,6 +69,13 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
public String getName() {
return lookupName(field_2_fnc_index);
}
+ /**
+ * external functions get some special processing
+ * @return true
if this is an external function
+ */
+ public boolean isExternalFunction() {
+ return field_2_fnc_index == FUNCTION_INDEX_EXTERNAL;
+ }
public String toFormulaString(Workbook book) {
return getName();
@@ -73,39 +83,57 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
public String toFormulaString(String[] operands) {
StringBuffer buf = new StringBuffer();
-
- if (field_2_fnc_index != 1) {
- buf.append(getName());
- buf.append('(');
- }
- if (operands.length >0) {
- for (int i=0;itrue
if the name specifies a standard worksheet function,
+ * false
if the name should be assumed to be an external function.
+ */
+ public static final boolean isInternalFunctionName(String name) {
+ return map.containsValue(name.toUpperCase());
+ }
protected String lookupName(short index) {
return ((String)map.get(new Integer(index)));
}
- protected short lookupIndex(String name) {
- Integer index = (Integer) map.getKeyForValue(name);
+ /**
+ * Resolves internal function names into function indexes.
+ * false
*/
public void setLastRowRelative(boolean rel) {
- field_4_last_column=rowRelative.setShortBoolean(field_4_last_column,rel);
+ field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel);
}
/**
@@ -260,16 +283,16 @@ public class AreaPtg
* set whether the last column should be relative or not
*/
public void setLastColRelative(boolean rel) {
- field_4_last_column=colRelative.setShortBoolean(field_4_last_column,rel);
+ field_4_last_column=colRelative.setBoolean(field_4_last_column,rel);
}
/**
* set the last column in the area
*/
- public void setLastColumn(short column)
- {
- field_4_last_column=columnMask.setShortValue(field_4_last_column, column);
+ public void setLastColumn(int colIx) {
+ checkColumnBounds(colIx);
+ field_4_last_column=columnMask.setValue(field_4_last_column, colIx);
}
/**
@@ -279,11 +302,20 @@ public class AreaPtg
{
field_4_last_column = column;
}
-
+
public String toFormulaString(Workbook book)
{
- return (new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative())).toString() + ":" +
- (new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative())).toString();
+ return toFormulaString(this, book);
+ }
+ protected static String toFormulaString(AreaI area, Workbook book) {
+ CellReference topLeft = new CellReference(area.getFirstRow(),area.getFirstColumn(),!area.isFirstRowRelative(),!area.isFirstColRelative());
+ CellReference botRight = new CellReference(area.getLastRow(),area.getLastColumn(),!area.isLastRowRelative(),!area.isLastColRelative());
+
+ if(AreaReference.isWholeColumnReference(topLeft, botRight)) {
+ return (new AreaReference(topLeft, botRight)).formatAsString();
+ } else {
+ return topLeft.formatAsString() + ":" + botRight.formatAsString();
+ }
}
public byte getDefaultOperandClass() {
diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaVPtg.java b/src/java/org/apache/poi/hssf/record/formula/AreaVPtg.java
index 2974eec64e..42dc11fa32 100644
--- a/src/java/org/apache/poi/hssf/record/formula/AreaVPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/AreaVPtg.java
@@ -36,7 +36,7 @@ import org.apache.poi.hssf.model.Workbook;
* @author Jason Height (jheight at chariot dot net dot au)
*/
-public class AreaVPtg
+public final class AreaVPtg
extends AreaPtg
{
public final static short sid = 0x45;
@@ -45,7 +45,7 @@ public class AreaVPtg
//Required for clone methods
}
- public AreaVPtg(short firstRow, short lastRow, short firstColumn, short lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
+ public AreaVPtg(int firstRow, int lastRow, int firstColumn, int lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
super(firstRow, lastRow, firstColumn, lastColumn, firstRowRelative, lastRowRelative, firstColRelative, lastColRelative);
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java b/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java
index 372b1850e4..12166b7967 100644
--- a/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java
@@ -72,8 +72,10 @@ public class ArrayPtg extends Ptg
field_7_reserved = in.readByte();
}
- /** Read in the actual token (array) values. This occurs AFTER the last
- * Ptg in the expression.
+ /**
+ * Read in the actual token (array) values. This occurs
+ * AFTER the last Ptg in the expression.
+ * See page 304-305 of Excel97-2007BinaryFileFormat(xls)Specification.pdf
*/
public void readTokenValues(RecordInputStream in) {
token_1_columns = (short)(0x00ff & in.readByte());
@@ -88,18 +90,17 @@ public class ArrayPtg extends Ptg
token_3_arrayValues = new Object[token_1_columns][token_2_rows];
for (int x=0;xtrue
if the specified value is within the range of values
+ * IntPtg can represent.
+ */
+ public static boolean isInRange(int i) {
+ return i>=MIN_VALUE && i <=MAX_VALUE;
+ }
-public class IntPtg
- extends Ptg
-{
public final static int SIZE = 3;
public final static byte sid = 0x1e;
private int field_1_value;
- private IntPtg() {
- //Required for clone methods
+ public IntPtg(RecordInputStream in) {
+ this(in.readUShort());
}
- public IntPtg(RecordInputStream in)
- {
- setValue(in.readUShort());
- }
-
-
- // IntPtg should be able to create itself, shouldnt have to call setValue
- public IntPtg(String formulaToken) {
- setValue(Integer.parseInt(formulaToken));
- }
- /**
- * Sets the wrapped value.
- * Normally you should call with a positive int.
- */
- public void setValue(int value)
- {
- if(value < 0 || value > (Short.MAX_VALUE + 1)*2 )
- throw new IllegalArgumentException("Unsigned short is out of range: " + value);
+ public IntPtg(int value) {
+ if(!isInRange(value)) {
+ throw new IllegalArgumentException("value is out of range: " + value);
+ }
field_1_value = value;
}
- /**
- * Returns the value as a short, which may have
- * been wrapped into negative numbers
- */
- public int getValue()
- {
+ public int getValue() {
return field_1_value;
}
- /**
- * Returns the value as an unsigned positive int.
- */
- public int getValueAsInt()
- {
- if(field_1_value < 0) {
- return (Short.MAX_VALUE + 1)*2 + field_1_value;
- }
- return field_1_value;
- }
public void writeBytes(byte [] array, int offset)
{
@@ -94,20 +68,25 @@ public class IntPtg
LittleEndian.putUShort(array, offset + 1, getValue());
}
- public int getSize()
- {
+ public int getSize() {
return SIZE;
}
- public String toFormulaString(Workbook book)
- {
- return "" + getValue();
+ public String toFormulaString(Workbook book) {
+ return String.valueOf(getValue());
+ }
+ public byte getDefaultOperandClass() {
+ return Ptg.CLASS_VALUE;
}
- public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
- public Object clone() {
- IntPtg ptg = new IntPtg();
- ptg.field_1_value = field_1_value;
- return ptg;
- }
+ public Object clone() {
+ return new IntPtg(field_1_value);
+ }
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(field_1_value);
+ sb.append("]");
+ return sb.toString();
+ }
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/NamePtg.java b/src/java/org/apache/poi/hssf/record/formula/NamePtg.java
index d418afa7e7..5405481a09 100644
--- a/src/java/org/apache/poi/hssf/record/formula/NamePtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/NamePtg.java
@@ -33,6 +33,7 @@ public class NamePtg
{
public final static short sid = 0x23;
private final static int SIZE = 5;
+ /** one-based index to defined name record */
private short field_1_label_index;
private short field_2_zero; // reserved must be 0
boolean xtra=false;
@@ -42,24 +43,32 @@ public class NamePtg
//Required for clone methods
}
- /** Creates new NamePtg */
-
- public NamePtg(String name, Workbook book)
- {
- final short n = (short) (book.getNumNames() + 1);
+ /**
+ * Creates new NamePtg and sets its name index to that of the corresponding defined name record
+ * in the workbook. The search for the name record is case insensitive. If it is not found,
+ * it gets created.
+ */
+ public NamePtg(String name, Workbook book) {
+ field_1_label_index = (short)(1+getOrCreateNameRecord(book, name)); // convert to 1-based
+ }
+ /**
+ * @return zero based index of the found or newly created defined name record.
+ */
+ private static final int getOrCreateNameRecord(Workbook book, String name) {
+ // perhaps this logic belongs in Workbook
+ int countNames = book.getNumNames();
NameRecord rec;
- for (short i = 1; i < n; i++) {
- rec = book.getNameRecord(i - 1);
- if (name.equals(rec.getNameText())) {
- field_1_label_index = i;
- return;
+ for (int i = 0; i < countNames; i++) {
+ rec = book.getNameRecord(i);
+ if (name.equalsIgnoreCase(rec.getNameText())) {
+ return i;
}
}
rec = new NameRecord();
rec.setNameText(name);
rec.setNameTextLength((byte) name.length());
book.addName(rec);
- field_1_label_index = n;
+ return countNames;
}
/** Creates new NamePtg */
@@ -71,6 +80,13 @@ public class NamePtg
field_2_zero = in.readShort();
//if (data[offset+6]==0) xtra=true;
}
+
+ /**
+ * @return zero based index to a defined name record in the LinkTable
+ */
+ public int getIndex() {
+ return field_1_label_index-1; // convert to zero based
+ }
public void writeBytes(byte [] array, int offset)
{
diff --git a/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java b/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java
index b1f280a017..ccf5ab6fcd 100644
--- a/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java
@@ -25,13 +25,11 @@ import org.apache.poi.hssf.record.RecordInputStream;
*
* @author aviks
*/
-
-public class NameXPtg extends Ptg
-{
+public final class NameXPtg extends Ptg {
public final static short sid = 0x39;
private final static int SIZE = 7;
- private short field_1_ixals; // index to externsheet record
- private short field_2_ilbl; //index to name or externname table(1 based)
+ private short field_1_ixals; // index to REF entry in externsheet record
+ private short field_2_ilbl; //index to defined name or externname table(1 based)
private short field_3_reserved; // reserved must be 0
@@ -41,13 +39,6 @@ public class NameXPtg extends Ptg
/** Creates new NamePtg */
- public NameXPtg(String name)
- {
- //TODO
- }
-
- /** Creates new NamePtg */
-
public NameXPtg(RecordInputStream in)
{
field_1_ixals = in.readShort();
@@ -72,7 +63,8 @@ public class NameXPtg extends Ptg
public String toFormulaString(Workbook book)
{
- return "NO IDEA - NAME";
+ // -1 to convert definedNameIndex from 1-based to zero-based
+ return book.resolveNameXText(field_1_ixals, field_2_ilbl-1);
}
public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
diff --git a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
index 510eebb037..84ff659b33 100644
--- a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
@@ -18,16 +18,14 @@
package org.apache.poi.hssf.record.formula;
-import org.apache.poi.util.LittleEndian;
-
-import org.apache.poi.hssf.util.RangeAddress;
-import org.apache.poi.hssf.util.CellReference;
-import org.apache.poi.hssf.util.SheetReferences;
-import org.apache.poi.hssf.model.Workbook;
-import org.apache.poi.util.BitField;
-import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.RecordInputStream;
+import org.apache.poi.hssf.util.CellReference;
+import org.apache.poi.hssf.util.RangeAddress;
+import org.apache.poi.hssf.util.SheetReferences;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.BitFieldFactory;
+import org.apache.poi.util.LittleEndian;
/**
* Title: Reference 3D Ptg true
if the specified error code is a standard Excel error code.
+ */
+ public static final boolean isValidCode(int errorCode) {
+ // This method exists because it would be bad to force clients to catch
+ // IllegalArgumentException if there were potential for passing an invalid error code.
+ switch(errorCode) {
+ case ERROR_NULL:
+ case ERROR_DIV_0:
+ case ERROR_VALUE:
+ case ERROR_REF:
+ case ERROR_NAME:
+ case ERROR_NUM:
+ case ERROR_NA:
+ return true;
+ }
+ return false;
+ }
}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java
index 42773d4a33..0a31728899 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java
@@ -100,9 +100,11 @@ public class HSSFPalette implements Palette
for (short i = (short) PaletteRecord.FIRST_COLOR_INDEX; b != null;
b = palette.getColor(++i))
{
- int colorDistance = red - b[0] + green - b[1] + blue - b[2];
+ int colorDistance = Math.abs(red - b[0]) +
+ Math.abs(green - b[1]) + Math.abs(blue - b[2]);
if (colorDistance < minColorDistance)
{
+ minColorDistance = colorDistance;
result = getColor(i);
}
}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java
index 0c9807b5aa..54229a16ab 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java
@@ -159,28 +159,25 @@ public class HSSFRow
* @param cell to remove
*/
public void removeCell(Cell cell) {
- removeCell(cell, true);
+ removeCell((HSSFCell) cell, true);
}
-
- private void removeCell(Cell cell, boolean alsoRemoveRecords) {
-
- HSSFCell hcell = (HSSFCell) cell;
+ private void removeCell(HSSFCell cell, boolean alsoRemoveRecords) {
if(alsoRemoveRecords) {
- CellValueRecordInterface cval = hcell.getCellValueRecord();
+ CellValueRecordInterface cval = cell.getCellValueRecord();
sheet.removeValueRecord(getRowNum(), cval);
}
- short column=hcell.getCellNum();
- if(hcell!=null && columnfalse
if this area reference involves more than one cell
*/
- public CellReference[] getCells() {
- return cells;
+ public boolean isSingleCell() {
+ return _isSingleCell;
+ }
+
+ /**
+ * @return the first cell reference which defines this area. Usually this cell is in the upper
+ * left corner of the area (but this is not a requirement).
+ */
+ public CellReference getFirstCell() {
+ return _firstCell;
+ }
+
+ /**
+ * Note - if this area reference refers to a single cell, the return value of this method will
+ * be identical to that of getFirstCell()
+ * @return the second cell reference which defines this area. For multi-cell areas, this is
+ * cell diagonally opposite the 'first cell'. Usually this cell is in the lower right corner
+ * of the area (but this is not a requirement).
+ */
+ public CellReference getLastCell() {
+ return _lastCell;
}
/**
* Returns a reference to every cell covered by this area
*/
public CellReference[] getAllReferencedCells() {
// Special case for single cell reference
- if(cells.length == 1) {
- return cells;
+ if(_isSingleCell) {
+ return new CellReference[] { _firstCell, };
}
+
// Interpolate between the two
- int minRow = Math.min(cells[0].getRow(), cells[1].getRow());
- int maxRow = Math.max(cells[0].getRow(), cells[1].getRow());
- int minCol = Math.min(cells[0].getCol(), cells[1].getCol());
- int maxCol = Math.max(cells[0].getCol(), cells[1].getCol());
+ int minRow = Math.min(_firstCell.getRow(), _lastCell.getRow());
+ int maxRow = Math.max(_firstCell.getRow(), _lastCell.getRow());
+ int minCol = Math.min(_firstCell.getCol(), _lastCell.getCol());
+ int maxCol = Math.max(_firstCell.getCol(), _lastCell.getCol());
+ String sheetName = _firstCell.getSheetName();
ArrayList refs = new ArrayList();
for(int row=minRow; row<=maxRow; row++) {
for(int col=minCol; col<=maxCol; col++) {
- CellReference ref = new CellReference(row, col, cells[0].isRowAbsolute(), cells[0].isColAbsolute());
- ref.setSheetName(cells[0].getSheetName());
+ CellReference ref = new CellReference(sheetName, row, col, _firstCell.isRowAbsolute(), _firstCell.isColAbsolute());
refs.add(ref);
}
}
return (CellReference[])refs.toArray(new CellReference[refs.size()]);
}
- public String toString() {
- StringBuffer retval = new StringBuffer();
- for (int i=0;i
+ * Result Comment
+ * A1:A1 Single cell area reference without sheet
+ * A1:$C$1 Multi-cell area reference without sheet
+ * Sheet1!A$1:B4 Standard sheet name
+ *
+ * @return the text representation of this area reference as it would appear in a formula.
+ */
+ public String formatAsString() {
+ // Special handling for whole-column references
+ if(isWholeColumnReference()) {
+ return
+ CellReference.convertNumToColString(_firstCell.getCol())
+ + ":" +
+ CellReference.convertNumToColString(_lastCell.getCol());
+ }
+
+ StringBuffer sb = new StringBuffer(32);
+ sb.append(_firstCell.formatAsString());
+ if(!_isSingleCell) {
+ sb.append(CELL_DELIMITER);
+ if(_lastCell.getSheetName() == null) {
+ sb.append(_lastCell.formatAsString());
+ } else {
+ // don't want to include the sheet name twice
+ _lastCell.appendCellReference(sb);
+ }
}
- retval.deleteCharAt(0);
- return retval.toString();
+ return sb.toString();
+ }
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(formatAsString());
+ sb.append("]");
+ return sb.toString();
}
/**
- * seperates Area refs in two parts and returns them as seperate elements in a
- * String array
+ * Separates Area refs in two parts and returns them as separate elements in a String array,
+ * each qualified with the sheet name (if present)
+ *
+ * @return array with one or two elements. never 'O''Brien''s Sales'!B5:C6' Sheet name with special characters null
*/
- private String[] seperateAreaRefs(String reference) {
- String[] retval = null;
-
- int length = reference.length();
-
- int loc = reference.indexOf(':',0);
- if(loc == -1){
- retval = new String[1];
- retval[0] = reference;
+ private static String[] separateAreaRefs(String reference) {
+ // TODO - refactor cell reference parsing logic to one place.
+ // Current known incarnations:
+ // FormulaParser.GetName()
+ // CellReference.separateRefParts()
+ // AreaReference.separateAreaRefs() (here)
+ // SheetNameFormatter.format() (inverse)
+
+
+ int len = reference.length();
+ int delimiterPos = -1;
+ boolean insideDelimitedName = false;
+ for(int i=0; inull
if this is a 2D reference. Special characters are not
+ * escaped or delimited
+ */
+ public String getSheetName(){
+ return _sheetName;
+ }
- protected void setSheetName(String sheetName) {
- this.sheetName = sheetName;
- }
-
/**
* takes in a column reference portion of a CellRef and converts it from
* ALPHA-26 number format to 0-based base 10.
*/
private int convertColStringToNum(String ref) {
- int len = ref.length();
+ int lastIx = ref.length()-1;
int retval=0;
int pos = 0;
- for (int k = ref.length()-1; k > -1; k--) {
+ for (int k = lastIx; k > -1; k--) {
char thechar = ref.charAt(k);
if ( pos == 0) {
retval += (Character.getNumericValue(thechar)-9);
@@ -97,42 +121,86 @@ public class CellReference {
/**
- * Seperates the row from the columns and returns an array. Element in
- * position one is the substring containing the columns still in ALPHA-26
- * number format.
+ * Separates the row from the columns and returns an array of three Strings. The first element
+ * is the sheet name. Only the first element may be null. The second element in is the column
+ * name still in ALPHA-26 number format. The third element is the row.
*/
- private String[] separateRefParts(String reference) {
-
- // Look for end of sheet name. This will either set
- // start to 0 (if no sheet name present) or the
- // index after the sheet reference ends.
- String retval[] = new String[3];
-
- int start = reference.indexOf("!");
- if (start != -1) retval[0] = reference.substring(0, start);
- start += 1;
+ private static String[] separateRefParts(String reference) {
+
+ int plingPos = reference.lastIndexOf(SHEET_NAME_DELIMITER);
+ String sheetName = parseSheetName(reference, plingPos);
+ int start = plingPos+1;
int length = reference.length();
- char[] chars = reference.toCharArray();
int loc = start;
- if (chars[loc]=='$') loc++;
- for (; loc < chars.length; loc++) {
- if (Character.isDigit(chars[loc]) || chars[loc] == '$') {
+ // skip initial dollars
+ if (reference.charAt(loc)==ABSOLUTE_REFERENCE_MARKER) {
+ loc++;
+ }
+ // step over column name chars until first digit (or dollars) for row number.
+ for (; loc < length; loc++) {
+ char ch = reference.charAt(loc);
+ if (Character.isDigit(ch) || ch == ABSOLUTE_REFERENCE_MARKER) {
break;
}
}
+ return new String[] {
+ sheetName,
+ reference.substring(start,loc),
+ reference.substring(loc),
+ };
+ }
- retval[1] = reference.substring(start,loc);
- retval[2] = reference.substring(loc);
- return retval;
+ private static String parseSheetName(String reference, int indexOfSheetNameDelimiter) {
+ if(indexOfSheetNameDelimiter < 0) {
+ return null;
+ }
+
+ boolean isQuoted = reference.charAt(0) == SPECIAL_NAME_DELIMITER;
+ if(!isQuoted) {
+ return reference.substring(0, indexOfSheetNameDelimiter);
+ }
+ int lastQuotePos = indexOfSheetNameDelimiter-1;
+ if(reference.charAt(lastQuotePos) != SPECIAL_NAME_DELIMITER) {
+ throw new RuntimeException("Mismatched quotes: (" + reference + ")");
+ }
+
+ // TODO - refactor cell reference parsing logic to one place.
+ // Current known incarnations:
+ // FormulaParser.GetName()
+ // CellReference.parseSheetName() (here)
+ // AreaReference.separateAreaRefs()
+ // SheetNameFormatter.format() (inverse)
+
+ StringBuffer sb = new StringBuffer(indexOfSheetNameDelimiter);
+
+ for(int i=1; i
+ *
+ * @return the text representation of this cell reference as it would appear in a formula.
+ */
+ public String formatAsString() {
+ StringBuffer sb = new StringBuffer(32);
+ if(_sheetName != null) {
+ SheetNameFormatter.appendFormat(sb, _sheetName);
+ sb.append(SHEET_NAME_DELIMITER);
+ }
+ appendCellReference(sb);
+ return sb.toString();
+ }
+
public String toString() {
- StringBuffer retval = new StringBuffer();
- retval.append( (colAbs)?"$":"");
- retval.append( convertNumToColString(col));
- retval.append((rowAbs)?"$":"");
- retval.append(row+1);
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(formatAsString());
+ sb.append("]");
+ return sb.toString();
+ }
- return retval.toString();
+ /**
+ * Appends cell reference with '$' markers for absolute values as required.
+ * Sheet name is not included.
+ */
+ /* package */ void appendCellReference(StringBuffer sb) {
+ if(_isColAbs) {
+ sb.append(ABSOLUTE_REFERENCE_MARKER);
+ }
+ sb.append( convertNumToColString(_colIndex));
+ if(_isRowAbs) {
+ sb.append(ABSOLUTE_REFERENCE_MARKER);
+ }
+ sb.append(_rowIndex+1);
}
}
diff --git a/src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java b/src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java
index 771db767be..ef9acfe60b 100644
--- a/src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java
+++ b/src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java
@@ -19,6 +19,7 @@
package org.apache.poi.poifs.filesystem;
+import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -30,6 +31,8 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.apache.poi.poifs.dev.POIFSViewable;
import org.apache.poi.poifs.property.DirectoryProperty;
import org.apache.poi.poifs.property.Property;
@@ -58,6 +61,33 @@ import org.apache.poi.util.LongField;
public class POIFSFileSystem
implements POIFSViewable
{
+ private static final Log _logger = LogFactory.getLog(POIFSFileSystem.class);
+
+
+ private static final class CloseIgnoringInputStream extends InputStream {
+
+ private final InputStream _is;
+ public CloseIgnoringInputStream(InputStream is) {
+ _is = is;
+ }
+ public int read() throws IOException {
+ return _is.read();
+ }
+ public int read(byte[] b, int off, int len) throws IOException {
+ return _is.read(b, off, len);
+ }
+ public void close() {
+ // do nothing
+ }
+ }
+
+ /**
+ * Convenience method for clients that want to avoid the auto-close behaviour of the constructor.
+ */
+ public static InputStream createNonClosingInputStream(InputStream is) {
+ return new CloseIgnoringInputStream(is);
+ }
+
private PropertyTable _property_table;
private List _documents;
private DirectoryNode _root;
@@ -74,23 +104,52 @@ public class POIFSFileSystem
}
/**
- * Create a POIFSFileSystem from an InputStream
+ * Create a POIFSFileSystem from an InputStream. Normally the stream is read until
+ * EOF. The stream is always closed.
+ *
+ * Some streams are usable after reaching EOF (typically those that return
+ * Result Comment
+ * A1 Cell reference without sheet
+ * Sheet1!A1 Standard sheet name
+ * 'O''Brien''s Sales'!A1' Sheet name with special characters true
+ * for markSupported()). In the unlikely case that the caller has such a stream
+ * and needs to use it after this constructor completes, a work around is to wrap the
+ * stream in order to trap the close() call. A convenience method (
+ * createNonClosingInputStream()) has been provided for this purpose:
+ *
+ * InputStream wrappedStream = POIFSFileSystem.createNonClosingInputStream(is);
+ * HSSFWorkbook wb = new HSSFWorkbook(wrappedStream);
+ * is.reset();
+ * doSomethingElse(is);
+ *
+ * Note also the special case of ByteArrayInputStream for which the close()
+ * method does nothing.
+ *
+ * ByteArrayInputStream bais = ...
+ * HSSFWorkbook wb = new HSSFWorkbook(bais); // calls bais.close() !
+ * bais.reset(); // no problem
+ * doSomethingElse(bais);
+ *
*
* @param stream the InputStream from which to read the data
*
* @exception IOException on errors reading, or on invalid data
*/
- public POIFSFileSystem(final InputStream stream)
+ public POIFSFileSystem(InputStream stream)
throws IOException
{
this();
+ boolean success = false;
// read the header block from the stream
- HeaderBlockReader header_block_reader = new HeaderBlockReader(stream);
-
+ HeaderBlockReader header_block_reader;
// read the rest of the stream into blocks
- RawDataBlockList data_blocks = new RawDataBlockList(stream);
+ RawDataBlockList data_blocks;
+ try {
+ header_block_reader = new HeaderBlockReader(stream);
+ data_blocks = new RawDataBlockList(stream);
+ success = true;
+ } finally {
+ closeInputStream(stream, success);
+ }
+
// set up the block allocation table (necessary for the
// data_blocks to be manageable
@@ -112,7 +171,32 @@ public class POIFSFileSystem
.getSBATStart()), data_blocks, properties.getRoot()
.getChildren(), null);
}
-
+ /**
+ * @param stream the stream to be closed
+ * @param success false
if an exception is currently being thrown in the calling method
+ */
+ private void closeInputStream(InputStream stream, boolean success) {
+
+ if(stream.markSupported() && !(stream instanceof ByteArrayInputStream)) {
+ String msg = "POIFS is closing the supplied input stream of type ("
+ + stream.getClass().getName() + ") which supports mark/reset. "
+ + "This will be a problem for the caller if the stream will still be used. "
+ + "If that is the case the caller should wrap the input stream to avoid this close logic. "
+ + "This warning is only temporary and will not be present in future versions of POI.";
+ _logger.warn(msg);
+ }
+ try {
+ stream.close();
+ } catch (IOException e) {
+ if(success) {
+ throw new RuntimeException(e);
+ }
+ // else not success? Try block did not complete normally
+ // just print stack trace and leave original ex to be thrown
+ e.printStackTrace();
+ }
+ }
+
/**
* Checks that the supplied InputStream (which MUST
* support mark and reset, or be a PushbackInputStream)
@@ -123,23 +207,23 @@ public class POIFSFileSystem
* @param inp An InputStream which supports either mark/reset, or is a PushbackInputStream
*/
public static boolean hasPOIFSHeader(InputStream inp) throws IOException {
- // We want to peek at the first 8 bytes
- inp.mark(8);
+ // We want to peek at the first 8 bytes
+ inp.mark(8);
- byte[] header = new byte[8];
- IOUtils.readFully(inp, header);
+ byte[] header = new byte[8];
+ IOUtils.readFully(inp, header);
LongField signature = new LongField(HeaderBlockConstants._signature_offset, header);
// Wind back those 8 bytes
if(inp instanceof PushbackInputStream) {
- PushbackInputStream pin = (PushbackInputStream)inp;
- pin.unread(header);
+ PushbackInputStream pin = (PushbackInputStream)inp;
+ pin.unread(header);
} else {
- inp.reset();
+ inp.reset();
}
-
- // Did it match the signature?
- return (signature.get() == HeaderBlockConstants._signature);
+
+ // Did it match the signature?
+ return (signature.get() == HeaderBlockConstants._signature);
}
/**
diff --git a/src/java/org/apache/poi/poifs/storage/RawDataBlock.java b/src/java/org/apache/poi/poifs/storage/RawDataBlock.java
index d554298400..472fd8b8b5 100644
--- a/src/java/org/apache/poi/poifs/storage/RawDataBlock.java
+++ b/src/java/org/apache/poi/poifs/storage/RawDataBlock.java
@@ -21,6 +21,8 @@ package org.apache.poi.poifs.storage;
import org.apache.poi.poifs.common.POIFSConstants;
import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
import java.io.*;
@@ -35,6 +37,7 @@ public class RawDataBlock
{
private byte[] _data;
private boolean _eof;
+ private static POILogger log = POILogFactory.getLogger(RawDataBlock.class);
/**
* Constructor RawDataBlock
@@ -75,9 +78,12 @@ public class RawDataBlock
String type = " byte" + ((count == 1) ? ("")
: ("s"));
- throw new IOException("Unable to read entire block; " + count
- + type + " read before EOF; expected "
- + blockSize + " bytes");
+ log.log(POILogger.ERROR,
+ "Unable to read entire block; " + count
+ + type + " read before EOF; expected "
+ + blockSize + " bytes. Your document"
+ + " has probably been truncated!"
+ );
}
else {
_eof = false;
diff --git a/src/java/org/apache/poi/util/LittleEndian.java b/src/java/org/apache/poi/util/LittleEndian.java
index b883d71b18..2278649cb8 100644
--- a/src/java/org/apache/poi/util/LittleEndian.java
+++ b/src/java/org/apache/poi/util/LittleEndian.java
@@ -245,6 +245,16 @@ public class LittleEndian
putNumber(data, offset, value, SHORT_SIZE);
}
+ /**
+ * executes:
+ *
+ * data[offset] = (byte)value;
+ *
+ * (b ? BoolEval.TRUE : BoolEval.FALSE)
+ * @return a BoolEval instance representing b.
+ */
+ public static final BoolEval valueOf(boolean b) {
+ // TODO - find / replace all occurrences
+ return b ? TRUE : FALSE;
+ }
public BoolEval(Ptg ptg) {
this.value = ((BoolPtg) ptg).getValue();
@@ -48,10 +58,17 @@ public class BoolEval implements NumericValueEval, StringValueEval {
}
public double getNumberValue() {
- return value ? (short) 1 : (short) 0;
+ return value ? 1 : 0;
}
public String getStringValue() {
return value ? "TRUE" : "FALSE";
}
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(getStringValue());
+ sb.append("]");
+ return sb.toString();
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java
index 2d8c58ef3e..e54cd483f1 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 8, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.ConcatPtg;
@@ -27,7 +24,7 @@ import org.apache.poi.hssf.record.formula.Ptg;
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class ConcatEval extends StringOperationEval {
+public final class ConcatEval extends StringOperationEval {
private ConcatPtg delegate;
@@ -35,36 +32,27 @@ public class ConcatEval extends StringOperationEval {
this.delegate = (ConcatPtg) ptg;
}
- public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
- Eval retval = null;
- StringBuffer sb = null;
-
- switch (operands.length) {
- default: // paranoid check :)
- retval = ErrorEval.UNKNOWN_ERROR;
- break;
- case 2:
- sb = new StringBuffer();
- for (int i = 0, iSize = 2; retval == null && i < iSize; i++) {
-
- ValueEval ve = singleOperandEvaluate(operands[i], srcRow, srcCol);
- if (ve instanceof StringValueEval) {
- StringValueEval sve = (StringValueEval) ve;
- sb.append(sve.getStringValue());
- }
- else if (ve instanceof BlankEval) {
- // do nothing
- }
- else { // must be an error eval
- retval = ve;
- }
+ public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ if(args.length != 2) {
+ return ErrorEval.VALUE_INVALID;
+ }
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < 2; i++) {
+
+ ValueEval ve = singleOperandEvaluate(args[i], srcRow, srcCol);
+ if (ve instanceof StringValueEval) {
+ StringValueEval sve = (StringValueEval) ve;
+ sb.append(sve.getStringValue());
+ }
+ else if (ve instanceof BlankEval) {
+ // do nothing
+ }
+ else { // must be an error eval
+ return ve;
}
}
- if (retval == null) {
- retval = new StringEval(sb.toString());
- }
- return retval;
+ return new StringEval(sb.toString());
}
public int getNumberOfOperands() {
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java
index 6dd3db23de..021168ad79 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 8, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.DividePtg;
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class DivideEval extends NumericOperationEval {
+public final class DivideEval extends NumericOperationEval {
private DividePtg delegate;
private static final ValueEvalToNumericXlator NUM_XLATOR =
new ValueEvalToNumericXlator((short)
( ValueEvalToNumericXlator.BOOL_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@@ -49,18 +44,28 @@ public class DivideEval extends NumericOperationEval {
return NUM_XLATOR;
}
- public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
+ public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ if(args.length != 2) {
+ return ErrorEval.VALUE_INVALID;
+ }
Eval retval = null;
double d0 = 0;
double d1 = 0;
- switch (operands.length) {
- default: // will rarely happen. currently the parser itself fails.
- retval = ErrorEval.UNKNOWN_ERROR;
- break;
- case 2:
- ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
+ ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
+ if (ve instanceof NumericValueEval) {
+ d0 = ((NumericValueEval) ve).getNumberValue();
+ }
+ else if (ve instanceof BlankEval) {
+ // do nothing
+ }
+ else {
+ retval = ErrorEval.VALUE_INVALID;
+ }
+
+ if (retval == null) { // no error yet
+ ve = singleOperandEvaluate(args[1], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
- d0 = ((NumericValueEval) ve).getNumberValue();
+ d1 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
@@ -68,20 +73,7 @@ public class DivideEval extends NumericOperationEval {
else {
retval = ErrorEval.VALUE_INVALID;
}
-
- if (retval == null) { // no error yet
- ve = singleOperandEvaluate(operands[1], srcRow, srcCol);
- if (ve instanceof NumericValueEval) {
- d1 = ((NumericValueEval) ve).getNumberValue();
- }
- else if (ve instanceof BlankEval) {
- // do nothing
- }
- else {
- retval = ErrorEval.VALUE_INVALID;
- }
- }
- } // end switch
+ }
if (retval == null) {
retval = (d1 == 0)
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java
index 43ef6c5127..e8e197d201 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java
@@ -14,51 +14,100 @@
* 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.usermodel.HSSFErrorConstants;
+
/**
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
- *
+ *
*/
-public class ErrorEval implements ValueEval {
+public final class ErrorEval implements ValueEval {
- private int errorCode;
+ // convenient access to namespace
+ private static final HSSFErrorConstants EC = null;
+
+ /** #NULL! - Intersection of two cell ranges is empty */
+ public static final ErrorEval NULL_INTERSECTION = new ErrorEval(EC.ERROR_NULL);
+ /** #DIV/0! - Division by zero */
+ public static final ErrorEval DIV_ZERO = new ErrorEval(EC.ERROR_DIV_0);
+ /** #VALUE! - Wrong type of operand */
+ public static final ErrorEval VALUE_INVALID = new ErrorEval(EC.ERROR_VALUE);
+ /** #REF! - Illegal or deleted cell reference */
+ public static final ErrorEval REF_INVALID = new ErrorEval(EC.ERROR_REF);
+ /** #NAME? - Wrong function or range name */
+ public static final ErrorEval NAME_INVALID = new ErrorEval(EC.ERROR_NAME);
+ /** #NUM! - Value range overflow */
+ public static final ErrorEval NUM_ERROR = new ErrorEval(EC.ERROR_NUM);
+ /** #N/A - Argument or function not available */
+ public static final ErrorEval NA = new ErrorEval(EC.ERROR_NA);
- public static final ErrorEval NAME_INVALID = new ErrorEval(525);
+ // POI internal error codes
+ private static final int CIRCULAR_REF_ERROR_CODE = 0xFFFFFFC4;
+ private static final int FUNCTION_NOT_IMPLEMENTED_CODE = 0xFFFFFFE2;
- public static final ErrorEval VALUE_INVALID = new ErrorEval(519);
+ 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);
-
- // Non std error codes
- public static final ErrorEval UNKNOWN_ERROR = new ErrorEval(-20);
- public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(-30);
+ /**
+ * Translates an Excel internal error code into the corresponding POI ErrorEval instance
+ * @param errorCode
+ */
+ public static ErrorEval valueOf(int errorCode) {
+ switch(errorCode) {
+ case HSSFErrorConstants.ERROR_NULL: return NULL_INTERSECTION;
+ case HSSFErrorConstants.ERROR_DIV_0: return DIV_ZERO;
+ case HSSFErrorConstants.ERROR_VALUE: return VALUE_INVALID;
+ case HSSFErrorConstants.ERROR_REF: return REF_INVALID;
+ case HSSFErrorConstants.ERROR_NAME: return NAME_INVALID;
+ case HSSFErrorConstants.ERROR_NUM: return NUM_ERROR;
+ case HSSFErrorConstants.ERROR_NA: return NA;
+ // non-std errors (conditions modeled as errors by POI)
+ case CIRCULAR_REF_ERROR_CODE: return CIRCULAR_REF_ERROR;
+ case FUNCTION_NOT_IMPLEMENTED_CODE: return FUNCTION_NOT_IMPLEMENTED;
+ }
+ throw new RuntimeException("Unexpected error code (" + errorCode + ")");
+ }
- public static final ErrorEval REF_INVALID = new ErrorEval(-40);
-
- public static final ErrorEval NA = new ErrorEval(-50);
-
- public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(-60);
-
- public static final ErrorEval DIV_ZERO = new ErrorEval(-70);
-
- public static final ErrorEval NUM_ERROR = new ErrorEval(-80);
+ /**
+ * Converts error codes to text. Handles non-standard error codes OK.
+ * For debug/test purposes (and for formatting error messages).
+ * @return the String representation of the specified Excel error code.
+ */
+ public static String getText(int errorCode) {
+ if(HSSFErrorConstants.isValidCode(errorCode)) {
+ return HSSFErrorConstants.getText(errorCode);
+ }
+ // It is desirable to make these (arbitrary) strings look clearly different from any other
+ // value expression that might appear in a formula. In addition these error strings should
+ // look unlike the standard Excel errors. Hence tilde ('~') was used.
+ switch(errorCode) {
+ case CIRCULAR_REF_ERROR_CODE: return "~CIRCULAR~REF~";
+ case FUNCTION_NOT_IMPLEMENTED_CODE: return "~FUNCTION~NOT~IMPLEMENTED~";
+ }
+ return "~non~std~err(" + errorCode + ")~";
+ }
+ private int _errorCode;
+ /**
+ * @param errorCode an 8-bit value
+ */
private ErrorEval(int errorCode) {
- this.errorCode = errorCode;
+ _errorCode = errorCode;
}
public int getErrorCode() {
- return errorCode;
+ return _errorCode;
}
-
- public String getStringValue() {
- return "Err:" + Integer.toString(errorCode);
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(getText(_errorCode));
+ sb.append("]");
+ return sb.toString();
}
-
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java
new file mode 100755
index 0000000000..7a23901b25
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java
@@ -0,0 +1,134 @@
+/* ====================================================================
+ 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;
+
+/**
+ * This class is used to simplify error handling logic within operator and function
+ * implementations. Note - OperationEval.evaluate() and Function.evaluate()
+ * method signatures do not throw this exception so it cannot propagate outside.
+ *
+ * Here is an example coded without EvaluationException, to show how it can help:
+ *
+ * public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ * // ...
+ * Eval arg0 = args[0];
+ * if(arg0 instanceof ErrorEval) {
+ * return arg0;
+ * }
+ * if(!(arg0 instanceof AreaEval)) {
+ * return ErrorEval.VALUE_INVALID;
+ * }
+ * double temp = 0;
+ * AreaEval area = (AreaEval)arg0;
+ * ValueEval[] values = area.getValues();
+ * for (int i = 0; i < values.length; i++) {
+ * ValueEval ve = values[i];
+ * if(ve instanceof ErrorEval) {
+ * return ve;
+ * }
+ * if(!(ve instanceof NumericValueEval)) {
+ * return ErrorEval.VALUE_INVALID;
+ * }
+ * temp += ((NumericValueEval)ve).getNumberValue();
+ * }
+ * // ...
+ * }
+ *
+ * In this example, if any error is encountered while processing the arguments, an error is
+ * returned immediately. This code is difficult to refactor due to all the points where errors
+ * are returned.
+ * Using EvaluationException allows the error returning code to be consolidated to one
+ * place.
+ *
+ * public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ * try {
+ * // ...
+ * AreaEval area = getAreaArg(args[0]);
+ * double temp = sumValues(area.getValues());
+ * // ...
+ * } catch (EvaluationException e) {
+ * return e.getErrorEval();
+ * }
+ *}
+ *
+ *private static AreaEval getAreaArg(Eval arg0) throws EvaluationException {
+ * if (arg0 instanceof ErrorEval) {
+ * throw new EvaluationException((ErrorEval) arg0);
+ * }
+ * if (arg0 instanceof AreaEval) {
+ * return (AreaEval) arg0;
+ * }
+ * throw EvaluationException.invalidValue();
+ *}
+ *
+ *private double sumValues(ValueEval[] values) throws EvaluationException {
+ * double temp = 0;
+ * for (int i = 0; i < values.length; i++) {
+ * ValueEval ve = values[i];
+ * if (ve instanceof ErrorEval) {
+ * throw new EvaluationException((ErrorEval) ve);
+ * }
+ * if (!(ve instanceof NumericValueEval)) {
+ * throw EvaluationException.invalidValue();
+ * }
+ * temp += ((NumericValueEval) ve).getNumberValue();
+ * }
+ * return temp;
+ *}
+ *
+ * It is not mandatory to use EvaluationException, doing so might give the following advantages:
+ * - Methods can more easily be extracted, allowing for re-use.
+ * - Type management (typecasting etc) is simpler because error conditions have been separated from
+ * intermediate calculation values.
+ * - Fewer local variables are required. Local variables can have stronger types.
+ * - It is easier to mimic common Excel error handling behaviour (exit upon encountering first
+ * error), because exceptions conveniently propagate up the call stack regardless of execution
+ * points or the number of levels of nested calls.
+ *
+ * Note - Only standard evaluation errors are represented by EvaluationException (
+ * i.e. conditions expected to be encountered when evaluating arbitrary Excel formulas). Conditions
+ * that could never occur in an Excel spreadsheet should result in runtime exceptions. Care should
+ * be taken to not translate any POI internal error into an Excel evaluation error code.
+ *
+ * @author Josh Micich
+ */
+public final class EvaluationException extends Exception {
+ private final ErrorEval _errorEval;
+
+ public EvaluationException(ErrorEval errorEval) {
+ _errorEval = errorEval;
+ }
+ // some convenience factory methods
+
+ /** #VALUE! - Wrong type of operand */
+ public static EvaluationException invalidValue() {
+ return new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ /** #REF! - Illegal or deleted cell reference */
+ public static EvaluationException invalidRef() {
+ return new EvaluationException(ErrorEval.REF_INVALID);
+ }
+ /** #NUM! - Value range overflow */
+ public static EvaluationException numberError() {
+ return new EvaluationException(ErrorEval.NUM_ERROR);
+ }
+
+ public ErrorEval getErrorEval() {
+ return _errorEval;
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java
new file mode 100755
index 0000000000..b1d81e6524
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java
@@ -0,0 +1,81 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.eval;
+
+import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+/**
+ *
+ * Common entry point for all external functions (where
+ * AbstractFunctionPtg.field_2_fnc_index == 255)
+ *
+ * @author Josh Micich
+ */
+final class ExternalFunction implements FreeRefFunction {
+
+ public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
+
+ int nIncomingArgs = args.length;
+ if(nIncomingArgs < 1) {
+ throw new RuntimeException("function name argument missing");
+ }
+
+ if (!(args[0] instanceof NameEval)) {
+ throw new RuntimeException("First argument should be a NameEval, but got ("
+ + args[0].getClass().getName() + ")");
+ }
+ NameEval functionNameEval = (NameEval) args[0];
+
+ int nOutGoingArgs = nIncomingArgs -1;
+ Eval[] outGoingArgs = new Eval[nOutGoingArgs];
+ System.arraycopy(args, 1, outGoingArgs, 0, nOutGoingArgs);
+
+ FreeRefFunction targetFunc;
+ try {
+ targetFunc = findTargetFunction(workbook, functionNameEval);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+
+ return targetFunc.evaluate(outGoingArgs, srcCellRow, srcCellCol, workbook, sheet);
+ }
+
+ private FreeRefFunction findTargetFunction(HSSFWorkbook workbook, NameEval functionNameEval) throws EvaluationException {
+
+ int numberOfNames = workbook.getNumberOfNames();
+
+ int nameIndex = functionNameEval.getIndex();
+ if(nameIndex < 0 || nameIndex >= numberOfNames) {
+ throw new RuntimeException("Bad name index (" + nameIndex
+ + "). Allowed range is (0.." + (numberOfNames-1) + ")");
+ }
+
+ String functionName = workbook.getNameName(nameIndex);
+ if(false) {
+ System.out.println("received call to external function index (" + functionName + ")");
+ }
+ // TODO - detect if the NameRecord corresponds to a named range, function, or something undefined
+ // throw the right errors in these cases
+
+ // TODO find the implementation for the external function e.g. "YEARFRAC" or "ISEVEN"
+
+ throw new EvaluationException(ErrorEval.FUNCTION_NOT_IMPLEMENTED);
+ }
+
+}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java
index d1420b2e85..533c604a0c 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java
@@ -20,6 +20,9 @@
*/
package org.apache.poi.hssf.record.formula.eval;
+import java.util.HashMap;
+import java.util.Map;
+
import org.apache.poi.hssf.record.formula.functions.*;
/**
@@ -27,12 +30,49 @@ import org.apache.poi.hssf.record.formula.functions.*;
*
*/
public abstract class FunctionEval implements OperationEval {
+ /**
+ * Some function IDs that require special treatment
+ */
+ private static final class FunctionID {
+ /** 78 */
+ 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;
+
protected static Function[] functions = produceFunctions();
+ private static Map freeRefFunctionsByIdMap;
+
+ static {
+ 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) {
+ m.put(createFRFKey(offset), frf);
+ }
+ private static Integer createFRFKey(int functionIndex) {
+ return new Integer(functionIndex);
+ }
+
+
public Function getFunction() {
short fidx = getFunctionIndex();
return functions[fidx];
}
+ public boolean isFreeRefFunction() {
+ return freeRefFunctionsByIdMap.containsKey(createFRFKey(getFunctionIndex()));
+ }
+ public FreeRefFunction getFreeRefFunction() {
+ return (FreeRefFunction) freeRefFunctionsByIdMap.get(createFRFKey(getFunctionIndex()));
+ }
public abstract short getFunctionIndex();
@@ -115,7 +155,7 @@ public abstract class FunctionEval implements OperationEval {
retval[75] = new Areas(); // AREAS
retval[76] = new Rows(); // ROWS
retval[77] = new Columns(); // COLUMNS
- retval[78] = new Offset(); // OFFSET
+ retval[ID.OFFSET] = null; // Offset.evaluate has a different signature
retval[79] = new Absref(); // ABSREF
retval[80] = new Relref(); // RELREF
retval[81] = new Argument(); // ARGUMENT
@@ -185,7 +225,7 @@ public abstract class FunctionEval implements OperationEval {
retval[145] = new NotImplementedFunction(); // GETDEF
retval[146] = new Reftext(); // REFTEXT
retval[147] = new Textref(); // TEXTREF
- retval[148] = new Indirect(); // INDIRECT
+ retval[ID.INDIRECT] = null; // Indirect.evaluate has different signature
retval[149] = new NotImplementedFunction(); // REGISTER
retval[150] = new Call(); // CALL
retval[151] = new NotImplementedFunction(); // ADDBAR
@@ -278,7 +318,7 @@ public abstract class FunctionEval implements OperationEval {
retval[252] = new Frequency(); // FREQUENCY
retval[253] = new NotImplementedFunction(); // ADDTOOLBAR
retval[254] = new NotImplementedFunction(); // DELETETOOLBAR
- retval[255] = new NotImplementedFunction(); // EXTERNALFLAG
+ retval[ID.EXTERNAL_FUNC] = null; // ExternalFunction is a FreeREfFunction
retval[256] = new NotImplementedFunction(); // RESETTOOLBAR
retval[257] = new Evaluate(); // EVALUATE
retval[258] = new NotImplementedFunction(); // GETTOOLBAR
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java
index ecada85d56..22d87b7e4d 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 8, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.MultiplyPtg;
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class MultiplyEval extends NumericOperationEval {
+public final class MultiplyEval extends NumericOperationEval {
private MultiplyPtg delegate;
private static final ValueEvalToNumericXlator NUM_XLATOR =
new ValueEvalToNumericXlator((short)
( ValueEvalToNumericXlator.BOOL_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@@ -49,46 +44,39 @@ public class MultiplyEval extends NumericOperationEval {
return NUM_XLATOR;
}
- public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
- Eval retval = null;
+ public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ if(args.length != 2) {
+ return ErrorEval.VALUE_INVALID;
+ }
+
double d0 = 0;
double d1 = 0;
- switch (operands.length) {
- default: // will rarely happen. currently the parser itself fails.
- retval = ErrorEval.UNKNOWN_ERROR;
- break;
- case 2:
- ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
- if (ve instanceof NumericValueEval) {
- d0 = ((NumericValueEval) ve).getNumberValue();
- }
- else if (ve instanceof BlankEval) {
- // do nothing
- }
- else {
- retval = ErrorEval.VALUE_INVALID;
- }
-
- if (retval == null) { // no error yet
- ve = singleOperandEvaluate(operands[1], srcRow, srcCol);
- if (ve instanceof NumericValueEval) {
- d1 = ((NumericValueEval) ve).getNumberValue();
- }
- else if (ve instanceof BlankEval) {
- // do nothing
- }
- else {
- retval = ErrorEval.VALUE_INVALID;
- }
- }
- } // end switch
-
- if (retval == null) {
- retval = (Double.isNaN(d0) || Double.isNaN(d1))
- ? (ValueEval) ErrorEval.VALUE_INVALID
- : new NumberEval(d0 * d1);
+ ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
+ if (ve instanceof NumericValueEval) {
+ d0 = ((NumericValueEval) ve).getNumberValue();
}
- return retval;
+ 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;
+ }
+
+ if (Double.isNaN(d0) || Double.isNaN(d1)) {
+ return ErrorEval.NUM_ERROR;
+ }
+ return new NumberEval(d0 * d1);
}
public int getNumberOfOperands() {
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java
new file mode 100755
index 0000000000..682394b3c2
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java
@@ -0,0 +1,48 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.eval;
+
+/**
+ * @author Josh Micich
+ */
+public final class NameEval implements Eval {
+
+ private final int _index;
+
+ /**
+ * @param index zero based index to a defined name record
+ */
+ public NameEval(int index) {
+ _index = index;
+ }
+
+ /**
+ * @return zero based index to a defined name record
+ */
+ public int getIndex() {
+ return _index;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(_index);
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java
new file mode 100755
index 0000000000..be1cda5f8e
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java
@@ -0,0 +1,277 @@
+/* ====================================================================
+ 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;
+
+/**
+ * Provides functionality for evaluating arguments to functions and operators.
+ *
+ * @author Josh Micich
+ */
+public final class OperandResolver {
+
+ private OperandResolver() {
+ // no instances of this class
+ }
+
+ /**
+ * Retrieves a single value from a variety of different argument types according to standard
+ * Excel rules. Does not perform any type conversion.
+ * @param arg the evaluated argument as passed to the function or operator.
+ * @param srcCellRow used when arg is a single column AreaRef
+ * @param srcCellCol used when arg is a single row AreaRef
+ * @return a NumberEval, StringEval, BoolEval or BlankEval.
+ * Never null
or ErrorEval.
+ * @throws EvaluationException(#VALUE!) if srcCellRow or srcCellCol do not properly index into
+ * an AreaEval. If the actual value retrieved is an ErrorEval, a corresponding
+ * EvaluationException is thrown.
+ */
+ public static ValueEval getSingleValue(Eval arg, int srcCellRow, short srcCellCol)
+ throws EvaluationException {
+ Eval result;
+ if (arg instanceof RefEval) {
+ result = ((RefEval) arg).getInnerValueEval();
+ } else if (arg instanceof AreaEval) {
+ result = chooseSingleElementFromArea((AreaEval) arg, srcCellRow, srcCellCol);
+ } else {
+ result = arg;
+ }
+ if (result instanceof ErrorEval) {
+ throw new EvaluationException((ErrorEval) result);
+ }
+ if (result instanceof ValueEval) {
+ return (ValueEval) result;
+ }
+ throw new RuntimeException("Unexpected eval type (" + result.getClass().getName() + ")");
+ }
+
+ /**
+ * Implements (some perhaps not well known) Excel functionality to select a single cell from an
+ * area depending on the coordinates of the calling cell. Here is an example demonstrating
+ * both selection from a single row area and a single column area in the same formula.
+ *
+ *
+ *
+ *
+ * If the formula "=1000+A1:B1+D2:D3" is put into the 9 cells from A2 to C4, the spreadsheet
+ * will look like this:
+ *
+ *
+ * A B C D
+ * 1 15 20 25
+ * 2 200
+ * 3 300
+ * 3 400
+ *
+ *
+ * Note that the row area (A1:B1) does not include column C and the column area (D2:D3) does
+ * not include row 4, so the values in C1(=25) and D4(=400) are not accessible to the formula
+ * as written, but in the 4 cells A2:B3, the row and column selection works ok.
+ *
+ * The same concept is extended to references across sheets, such that even multi-row,
+ * multi-column areas can be useful.
+ *
+ * Of course with carefully (or carelessly) chosen parameters, cyclic references can occur and
+ * hence this method can throw a 'circular reference' EvaluationException. Note that
+ * this method does not attempt to detect cycles. Every cell in the specified Area ae
+ * has already been evaluated prior to this method call. Any cell (or cells) part of
+ * ae that would incur a cyclic reference error if selected by this method, will
+ * already have the value
+ * A B C D
+ * 1 15 20 25
+ * 2 1215 1220 #VALUE! 200
+ * 3 1315 1320 #VALUE! 300
+ * 4 #VALUE! #VALUE! #VALUE! 400 null
. Never
+ * ErrorEval.
+ * @throws EvaluationException if there is a problem with indexing into the area, or if the
+ * evaluated cell has an error.
+ */
+ public static ValueEval chooseSingleElementFromArea(AreaEval ae,
+ int srcCellRow, short srcCellCol) throws EvaluationException {
+ ValueEval result = chooseSingleElementFromAreaInternal(ae, srcCellRow, srcCellCol);
+ if(result == null) {
+ // This seems to be required because AreaEval.values() array may contain nulls.
+ // perhaps that should not be allowed.
+ result = BlankEval.INSTANCE;
+ }
+ if (result instanceof ErrorEval) {
+ throw new EvaluationException((ErrorEval) result);
+
+ }
+ return result;
+ }
+
+ /**
+ * @return possibly ErrorEval, and null
+ */
+ private static ValueEval chooseSingleElementFromAreaInternal(AreaEval ae,
+ int srcCellRow, short srcCellCol) throws EvaluationException {
+
+ if(false) {
+ // this is too simplistic
+ if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
+ throw new EvaluationException(ErrorEval.CIRCULAR_REF_ERROR);
+ }
+ /*
+ Circular references are not dealt with directly here, but it is worth noting some issues.
+
+ ANY one of the return statements in this method could return a cell that is identical
+ to the one immediately being evaluated. The evaluating cell is identified by srcCellRow,
+ srcCellRow AND sheet. The sheet is not available in any nearby calling method, so that's
+ one reason why circular references are not easy to detect here. (The sheet of the returned
+ cell can be obtained from ae if it is an Area3DEval.)
+
+ Another reason there's little value in attempting to detect circular references here is
+ that only direct circular references could be detected. If the cycle involved two or more
+ cells this method could not detect it.
+
+ Logic to detect evaluation cycles of all kinds has been coded in EvaluationCycleDetector
+ (and HSSFFormulaEvaluator).
+ */
+ }
+
+ if (ae.isColumn()) {
+ if(ae.isRow()) {
+ return ae.getValues()[0];
+ }
+ if(!ae.containsRow(srcCellRow)) {
+ throw EvaluationException.invalidValue();
+ }
+ return ae.getValueAt(srcCellRow, ae.getFirstColumn());
+ }
+ if(!ae.isRow()) {
+ // multi-column, multi-row area
+ if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
+ return ae.getValueAt(ae.getFirstRow(), ae.getFirstColumn());
+ }
+ throw EvaluationException.invalidValue();
+ }
+ if(!ae.containsColumn(srcCellCol)) {
+ throw EvaluationException.invalidValue();
+ }
+ return ae.getValueAt(ae.getFirstRow(), srcCellCol);
+ }
+
+ /**
+ * Applies some conversion rules if the supplied value is not already an integer.
+ * Value is first coerced to a double ( See coerceValueToDouble() ).
+ *
+ * Excel typically converts doubles to integers by truncating toward negative infinity.
+ * The equivalent java code is:
+ * return (int)Math.floor(d);
+ * not:
+ * return (int)d; // wrong - rounds toward zero
+ *
+ */
+ public static int coerceValueToInt(ValueEval ev) throws EvaluationException {
+ double d = coerceValueToDouble(ev);
+ // Note - the standard java type conversion from double to int truncates toward zero.
+ // but Math.floor() truncates toward negative infinity
+ return (int)Math.floor(d);
+ }
+
+ /**
+ * Applies some conversion rules if the supplied value is not already a number.
+ * Note - BlankEval is not supported and must be handled by the caller.
+ * @param ev must be a NumberEval, StringEval or BoolEval
+ * @return actual, parsed or interpreted double value (respectively).
+ * @throws EvaluationException(#VALUE!) only if a StringEval is supplied and cannot be parsed
+ * as a double (See parseDouble() for allowable formats).
+ * @throws RuntimeException if the supplied parameter is not NumberEval,
+ * StringEval or BoolEval
+ */
+ public static double coerceValueToDouble(ValueEval ev) throws EvaluationException {
+
+ if (ev instanceof NumericValueEval) {
+ // this also handles booleans
+ return ((NumericValueEval)ev).getNumberValue();
+ }
+ if (ev instanceof StringEval) {
+ Double dd = parseDouble(((StringEval) ev).getStringValue());
+ if (dd == null) {
+ throw EvaluationException.invalidValue();
+ }
+ return dd.doubleValue();
+ }
+ throw new RuntimeException("Unexpected arg eval type (" + ev.getClass().getName() + ")");
+ }
+
+ /**
+ * Converts a string to a double using standard rules that Excel would use.
+ * Tolerates currency prefixes, commas, leading and trailing spaces.
+ *
+ * Some examples:
+ * " 123 " -> 123.0
+ * ".123" -> 0.123
+ * These not supported yet:
+ * " $ 1,000.00 " -> 1000.0
+ * "$1.25E4" -> 12500.0
+ * "5**2" -> 500
+ * "250%" -> 2.5
+ *
+ * @param text
+ * @return null
if the specified text cannot be parsed as a number
+ */
+ public static Double parseDouble(String 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;
+ }
+ // TODO - support notation like '1E3' (==1000)
+
+ double val;
+ try {
+ val = Double.parseDouble(text);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ return new Double(isPositive ? +val : -val);
+ }
+
+ /**
+ * @param ve must be a NumberEval, StringEval, BoolEval, or BlankEval
+ * @return the converted string value. never null
+ */
+ public static String coerceValueToString(ValueEval ve) {
+ if (ve instanceof StringValueEval) {
+ StringValueEval sve = (StringValueEval) ve;
+ return sve.getStringValue();
+ }
+ if (ve instanceof NumberEval) {
+ NumberEval neval = (NumberEval) ve;
+ return neval.getStringValue();
+ }
+
+ if (ve instanceof BlankEval) {
+ return "";
+ }
+ throw new IllegalArgumentException("Unexpected eval class (" + ve.getClass().getName() + ")");
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java
index 437c24e40d..651c5d2aa2 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 8, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.PowerPtg;
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class PowerEval extends NumericOperationEval {
+public final class PowerEval extends NumericOperationEval {
private PowerPtg delegate;
private static final ValueEvalToNumericXlator NUM_XLATOR =
new ValueEvalToNumericXlator((short)
( ValueEvalToNumericXlator.BOOL_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@@ -49,48 +44,40 @@ public class PowerEval extends NumericOperationEval {
return NUM_XLATOR;
}
- public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
- Eval retval = null;
+ public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ if(args.length != 2) {
+ return ErrorEval.VALUE_INVALID;
+ }
double d0 = 0;
double d1 = 0;
- switch (operands.length) {
- default: // will rarely happen. currently the parser itself fails.
- retval = ErrorEval.UNKNOWN_ERROR;
- break;
- case 2:
- ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
- if (ve instanceof NumericValueEval) {
- d0 = ((NumericValueEval) ve).getNumberValue();
- }
- else if (ve instanceof BlankEval) {
- // do nothing
- }
- else {
- retval = ErrorEval.VALUE_INVALID;
- }
-
- if (retval == null) { // no error yet
- ve = singleOperandEvaluate(operands[1], srcRow, srcCol);
- if (ve instanceof NumericValueEval) {
- d1 = ((NumericValueEval) ve).getNumberValue();
- }
- else if (ve instanceof BlankEval) {
- // do nothing
- }
- else {
- retval = ErrorEval.VALUE_INVALID;
- }
- }
- } // end switch
-
- if (retval == null) {
- double p = Math.pow(d0, d1);
- retval = (Double.isNaN(p))
- ? (ValueEval) ErrorEval.VALUE_INVALID
- : new NumberEval(p);
+ ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
+ if (ve instanceof NumericValueEval) {
+ d0 = ((NumericValueEval) ve).getNumberValue();
}
- return retval;
+ 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;
+ }
+
+ double p = Math.pow(d0, d1);
+ if (Double.isNaN(p)) {
+ return ErrorEval.VALUE_INVALID;
+ }
+ return new NumberEval(p);
}
public int getNumberOfOperands() {
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java
index 7b24cb062b..898d7a8618 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java
@@ -14,47 +14,37 @@
* 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) {
+ if(ve == null) {
+ throw new IllegalArgumentException("ve must not be null");
+ }
+ if(false && ptg == null) { // TODO - fix dodgy code in MultiOperandNumericFunction
+ throw new IllegalArgumentException("ptg must not be null");
+ }
+ value = ve;
+ delegate = ptg;
}
-
public ValueEval getInnerValueEval() {
return value;
}
-
- public short getRow() {
+ public int getRow() {
return delegate.getRow();
}
-
- public short getColumn() {
+ public int getColumn() {
return delegate.getColumn();
}
-
- public boolean isEvaluated() {
- return evaluated;
- }
-
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java
index acedbe766d..622d686329 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java
@@ -14,47 +14,40 @@
* 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;
/**
* @author Amol S. Deshmukh
*
*/
-public class Ref3DEval implements RefEval {
+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) {
+ if(ve == null) {
+ throw new IllegalArgumentException("ve must not be null");
+ }
+ if(ptg == null) {
+ throw new IllegalArgumentException("ptg must not be null");
+ }
+ value = ve;
+ delegate = ptg;
}
-
public ValueEval getInnerValueEval() {
return value;
}
-
- public short getRow() {
+ public int getRow() {
return delegate.getRow();
}
-
- public short getColumn() {
+ public int getColumn() {
return delegate.getColumn();
}
-
- public boolean isEvaluated() {
- return evaluated;
+ public int getExternSheetIndex() {
+ return delegate.getExternSheetIndex();
}
-
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java
index bb72adc4a0..e462586d72 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java
@@ -14,11 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 9, 2005
- *
- *
- */
+
package org.apache.poi.hssf.record.formula.eval;
/**
@@ -44,26 +40,12 @@ public interface RefEval extends ValueEval {
public ValueEval getInnerValueEval();
/**
- * returns the column index.
+ * returns the zero based column index.
*/
- public short getColumn();
+ public int getColumn();
/**
- * returns the row index.
+ * returns the zero based 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();
-
+ public int getRow();
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringEval.java
index 01af4e8436..27a9c6a627 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringEval.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 8, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,21 +24,31 @@ import org.apache.poi.hssf.record.formula.StringPtg;
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class StringEval implements StringValueEval {
+public final class StringEval implements StringValueEval {
public static final StringEval EMPTY_INSTANCE = new StringEval("");
- private String value;
+ private final String value;
public StringEval(Ptg ptg) {
- this.value = ((StringPtg) ptg).getValue();
+ this(((StringPtg) ptg).getValue());
}
public StringEval(String value) {
+ if(value == null) {
+ throw new IllegalArgumentException("value must not be null");
+ }
this.value = value;
}
public String getStringValue() {
return value;
}
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(value);
+ sb.append("]");
+ return sb.toString();
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringValueEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringValueEval.java
index b692f01ea2..46c12236b9 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringValueEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringValueEval.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 8, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.eval;
/**
@@ -26,5 +23,8 @@ package org.apache.poi.hssf.record.formula.eval;
*/
public interface StringValueEval extends ValueEval {
- public String getStringValue();
+ /**
+ * @return never null
, possibly empty string.
+ */
+ String getStringValue();
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java
index 4bd77029f7..85a3845299 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 8, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.SubtractPtg;
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class SubtractEval extends NumericOperationEval {
+public final class SubtractEval extends NumericOperationEval {
private SubtractPtg delegate;
private static final ValueEvalToNumericXlator NUM_XLATOR =
new ValueEvalToNumericXlator((short)
( ValueEvalToNumericXlator.BOOL_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@@ -49,18 +44,28 @@ public class SubtractEval extends NumericOperationEval {
return NUM_XLATOR;
}
- public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
+ public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ if(args.length != 2) {
+ return ErrorEval.VALUE_INVALID;
+ }
Eval retval = null;
double d0 = 0;
double d1 = 0;
- switch (operands.length) {
- default: // will rarely happen. currently the parser itself fails.
- retval = ErrorEval.UNKNOWN_ERROR;
- break;
- case 2:
- ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
+ ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
+ if (ve instanceof NumericValueEval) {
+ d0 = ((NumericValueEval) ve).getNumberValue();
+ }
+ else if (ve instanceof BlankEval) {
+ // do nothing
+ }
+ else {
+ retval = ErrorEval.VALUE_INVALID;
+ }
+
+ if (retval == null) { // no error yet
+ ve = singleOperandEvaluate(args[1], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
- d0 = ((NumericValueEval) ve).getNumberValue();
+ d1 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
@@ -68,21 +73,8 @@ public class SubtractEval extends NumericOperationEval {
else {
retval = ErrorEval.VALUE_INVALID;
}
-
- if (retval == null) { // no error yet
- ve = singleOperandEvaluate(operands[1], srcRow, srcCol);
- if (ve instanceof NumericValueEval) {
- d1 = ((NumericValueEval) ve).getNumberValue();
- }
- else if (ve instanceof BlankEval) {
- // do nothing
- }
- else {
- retval = ErrorEval.VALUE_INVALID;
- }
- }
- } // end switch
-
+ }
+
if (retval == null) {
retval = (Double.isNaN(d0) || Double.isNaN(d1))
? (ValueEval) ErrorEval.VALUE_INVALID
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java
index b4975eefcf..ef6f533ea5 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 8, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,14 +24,12 @@ import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class UnaryMinusEval extends NumericOperationEval {
+public final class UnaryMinusEval extends NumericOperationEval {
private UnaryMinusPtg delegate;
private static final ValueEvalToNumericXlator NUM_XLATOR =
new ValueEvalToNumericXlator((short)
( ValueEvalToNumericXlator.BOOL_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@@ -49,32 +44,24 @@ public class UnaryMinusEval extends NumericOperationEval {
return NUM_XLATOR;
}
- public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
- ValueEval retval = null;
+ public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ if(args.length != 1) {
+ return ErrorEval.VALUE_INVALID;
+ }
double d = 0;
- switch (operands.length) {
- default:
- retval = ErrorEval.UNKNOWN_ERROR;
- break;
- case 1:
- ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
- if (ve instanceof NumericValueEval) {
- d = ((NumericValueEval) ve).getNumberValue();
- }
- else if (ve instanceof BlankEval) {
- // do nothing
- }
- else if (ve instanceof ErrorEval) {
- retval = ve;
- }
+ ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
+ if (ve instanceof NumericValueEval) {
+ d = ((NumericValueEval) ve).getNumberValue();
+ }
+ else if (ve instanceof BlankEval) {
+ // do nothing
+ }
+ else if (ve instanceof ErrorEval) {
+ return ve;
}
- if (retval == null) {
- retval = new NumberEval(-d);
- }
-
- return retval;
+ return new NumberEval(-d);
}
public int getNumberOfOperands() {
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java
index 847aa56fa6..edcc7bee79 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 8, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,111 +24,38 @@ import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class UnaryPlusEval implements OperationEval /*extends NumericOperationEval*/ {
+public final class UnaryPlusEval implements OperationEval {
private UnaryPlusPtg delegate;
- /*
- * COMMENT FOR COMMENTED CODE IN THIS FILE
- *
- * In excel the programmer seems to not have cared to
- * think about how strings were handled in other numeric
- * operations when he/she was implementing this operation :P
- *
- * Here's what I mean:
- *
- * Q. If the formula -"hello" evaluates to #VALUE! in excel, what should
- * the formula +"hello" evaluate to?
- *
- * A. +"hello" evaluates to "hello" (what the...?)
- *
+ /**
+ * called by reflection
*/
-
-
-// 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
-// ));
-
-
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(ae.getFirstRow(), srcCol);
- 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() {
@@ -141,5 +65,4 @@ public class UnaryPlusEval implements OperationEval /*extends NumericOperationEv
public int getType() {
return delegate.getType();
}
-
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java
index 5ffa2faeef..1abcf34d2b 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java
@@ -24,7 +24,7 @@ package org.apache.poi.hssf.record.formula.eval;
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class ValueEvalToNumericXlator {
+public final class ValueEvalToNumericXlator {
public static final int STRING_IS_PARSED = 0x0001;
public static final int BOOL_IS_PARSED = 0x0002;
@@ -34,26 +34,18 @@ public class ValueEvalToNumericXlator {
public static final int REF_BOOL_IS_PARSED = 0x0010;
public static final int REF_BLANK_IS_PARSED = 0x0020;
- public static final int EVALUATED_REF_STRING_IS_PARSED = 0x0040;
- public static final int EVALUATED_REF_BOOL_IS_PARSED = 0x0080;
- public static final int EVALUATED_REF_BLANK_IS_PARSED = 0x0100;
-
- public static final int STRING_TO_BOOL_IS_PARSED = 0x0200;
- public static final int REF_STRING_TO_BOOL_IS_PARSED = 0x0400;
-
public static final int STRING_IS_INVALID_VALUE = 0x0800;
- public static final int REF_STRING_IS_INVALID_VALUE = 0x1000;
-
-// public static final int BOOL_IS_BLANK = 0x2000;
-// public static final int REF_BOOL_IS_BLANK = 0x4000;
-// public static final int STRING_IS_BLANK = 0x8000;
-// public static final int REF_STRING_IS_BLANK = 0x10000;
private final int flags;
public ValueEvalToNumericXlator(int flags) {
- this.flags = flags;
+
+ if (false) { // uncomment to see who is using this class
+ System.err.println(new Throwable().getStackTrace()[1].getClassName() + "\t0x"
+ + Integer.toHexString(flags).toUpperCase());
+ }
+ this.flags = flags;
}
/**
@@ -71,7 +63,7 @@ public class ValueEvalToNumericXlator {
// most common case - least worries :)
else if (eval instanceof NumberEval) {
- retval = (NumberEval) eval;
+ retval = eval;
}
// booleval
@@ -125,50 +117,33 @@ public class ValueEvalToNumericXlator {
* @param eval
*/
private ValueEval xlateRefEval(RefEval reval) {
- ValueEval retval = null;
- ValueEval eval = (ValueEval) reval.getInnerValueEval();
+ ValueEval eval = reval.getInnerValueEval();
// most common case - least worries :)
if (eval instanceof NumberEval) {
- retval = (NumberEval) eval;
+ return eval;
}
- // booleval
- else if (eval instanceof BoolEval) {
- retval = ((flags & REF_BOOL_IS_PARSED) > 0)
+ if (eval instanceof BoolEval) {
+ return ((flags & REF_BOOL_IS_PARSED) > 0)
? (ValueEval) eval
: BlankEval.INSTANCE;
}
- // stringeval
- else if (eval instanceof StringEval) {
- retval = xlateRefStringEval((StringEval) eval);
+ if (eval instanceof StringEval) {
+ return xlateRefStringEval((StringEval) eval);
}
- // erroreval
- else if (eval instanceof ErrorEval) {
- retval = eval;
+ if (eval instanceof ErrorEval) {
+ return eval;
}
- // refeval
- else if (eval instanceof RefEval) {
- RefEval re = (RefEval) eval;
- retval = xlateRefEval(re);
+ if (eval instanceof BlankEval) {
+ return xlateBlankEval(REF_BLANK_IS_PARSED);
}
- else if (eval instanceof BlankEval) {
- retval = xlateBlankEval(reval.isEvaluated() ? EVALUATED_REF_BLANK_IS_PARSED : REF_BLANK_IS_PARSED);
- }
-
- // probably AreaEval ? then not acceptable.
- else {
- throw new RuntimeException("Invalid ValueEval type passed for conversion: " + eval.getClass());
- }
-
-
-
-
- return retval;
+ throw new RuntimeException("Invalid ValueEval type passed for conversion: ("
+ + eval.getClass().getName() + ")");
}
/**
@@ -176,93 +151,38 @@ public class ValueEvalToNumericXlator {
* @param eval
*/
private ValueEval xlateStringEval(StringEval eval) {
- ValueEval retval = null;
+
if ((flags & STRING_IS_PARSED) > 0) {
String s = eval.getStringValue();
- try {
- double d = Double.parseDouble(s);
- retval = new NumberEval(d);
- }
- catch (Exception e) {
- if ((flags & STRING_TO_BOOL_IS_PARSED) > 0) {
- try {
- boolean b = Boolean.getBoolean(s);
- retval = b ? BoolEval.TRUE : BoolEval.FALSE;
- }
- catch (Exception e2) { retval = ErrorEval.VALUE_INVALID; }
- }
- else {
- retval = ErrorEval.VALUE_INVALID;
- }
+ Double d = OperandResolver.parseDouble(s);
+ if(d == null) {
+ return ErrorEval.VALUE_INVALID;
}
+ return new NumberEval(d.doubleValue());
}
- else if ((flags & STRING_TO_BOOL_IS_PARSED) > 0) {
- String s = eval.getStringValue();
- try {
- boolean b = Boolean.getBoolean(s);
- retval = b ? BoolEval.TRUE : BoolEval.FALSE;
- }
- catch (Exception e) { retval = ErrorEval.VALUE_INVALID; }
- }
-
// strings are errors?
- else if ((flags & STRING_IS_INVALID_VALUE) > 0) {
- retval = ErrorEval.VALUE_INVALID;
+ if ((flags & STRING_IS_INVALID_VALUE) > 0) {
+ return ErrorEval.VALUE_INVALID;
}
// ignore strings
- else {
- retval = xlateBlankEval(BLANK_IS_PARSED);
- }
- return retval;
+ return xlateBlankEval(BLANK_IS_PARSED);
}
/**
* uses the relevant flags to decode the StringEval
* @param eval
*/
- private ValueEval xlateRefStringEval(StringEval eval) {
- ValueEval retval = null;
+ private ValueEval xlateRefStringEval(StringEval sve) {
if ((flags & REF_STRING_IS_PARSED) > 0) {
- StringEval sve = (StringEval) eval;
String s = sve.getStringValue();
- try {
- double d = Double.parseDouble(s);
- retval = new NumberEval(d);
- }
- catch (Exception e) {
- if ((flags & REF_STRING_TO_BOOL_IS_PARSED) > 0) {
- try {
- boolean b = Boolean.getBoolean(s);
- retval = b ? BoolEval.TRUE : BoolEval.FALSE;
- }
- catch (Exception e2) { retval = ErrorEval.VALUE_INVALID; }
- }
- else {
- retval = ErrorEval.VALUE_INVALID;
- }
+ Double d = OperandResolver.parseDouble(s);
+ if(d == null) {
+ return ErrorEval.VALUE_INVALID;
}
+ return new NumberEval(d.doubleValue());
}
- else if ((flags & REF_STRING_TO_BOOL_IS_PARSED) > 0) {
- StringEval sve = (StringEval) eval;
- String s = sve.getStringValue();
- try {
- boolean b = Boolean.getBoolean(s);
- retval = b ? BoolEval.TRUE : BoolEval.FALSE;;
- }
- catch (Exception e) { retval = ErrorEval.VALUE_INVALID; }
- }
-
- // strings are errors?
- else if ((flags & REF_STRING_IS_INVALID_VALUE) > 0) {
- retval = ErrorEval.VALUE_INVALID;
- }
-
// strings are blanks
- else {
- retval = BlankEval.INSTANCE;
- }
- return retval;
+ return BlankEval.INSTANCE;
}
-
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java
index 592402b80d..bf888b97df 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java
@@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
public class Avedev extends MultiOperandNumericFunction {
private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
- new ValueEvalToNumericXlator((short) (0
- // ValueEvalToNumericXlator.BOOL_IS_PARSED
+ new ValueEvalToNumericXlator((short) (
+ ValueEvalToNumericXlator.BOOL_IS_PARSED
//| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
@@ -44,7 +44,6 @@ public class Avedev extends MultiOperandNumericFunction {
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE
//| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
- | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
));
/**
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java
index 3491109177..4043040713 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java
@@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
public class Average extends MultiOperandNumericFunction {
private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
- new ValueEvalToNumericXlator((short) (0
- // ValueEvalToNumericXlator.BOOL_IS_PARSED
+ new ValueEvalToNumericXlator((short) (
+ ValueEvalToNumericXlator.BOOL_IS_PARSED
//| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
@@ -44,7 +44,6 @@ public class Average extends MultiOperandNumericFunction {
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE
//| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
- | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
));
/**
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java
index 8eb7e841d6..c054c6dac4 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on Jun 20, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
@@ -38,13 +35,10 @@ public abstract class FinanceFunction extends NumericFunction {
new ValueEvalToNumericXlator((short) (0
| ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.BLANK_IS_PARSED
| ValueEvalToNumericXlator.REF_BLANK_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE
//| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
@@ -56,11 +50,11 @@ public abstract class FinanceFunction extends NumericFunction {
* if they desire to return a different ValueEvalToNumericXlator instance
* than the default.
*/
- protected ValueEvalToNumericXlator getXlator() {
+ protected final ValueEvalToNumericXlator getXlator() {
return DEFAULT_NUM_XLATOR;
}
- protected ValueEval singleOperandNumericAsBoolean(Eval eval, int srcRow, short srcCol) {
+ protected final ValueEval singleOperandNumericAsBoolean(Eval eval, int srcRow, short srcCol) {
ValueEval retval = null;
retval = singleOperandEvaluate(eval, srcRow, srcCol);
if (retval instanceof NumericValueEval) {
@@ -74,5 +68,4 @@ public abstract class FinanceFunction extends NumericFunction {
}
return retval;
}
-
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java
new file mode 100755
index 0000000000..56d285543e
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java
@@ -0,0 +1,57 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+
+/**
+ * For most Excel functions, involving references ((cell, area), (2d, 3d)), the references are
+ * passed in as arguments, and the exact location remains fixed. However, a select few Excel
+ * functions have the ability to access cells that were not part of any reference passed as an
+ * argument.
+ * Two important functions with this feature are INDIRECT and OFFSET
+ *
+ * In POI, the HSSFFormulaEvaluator evaluates every cell in each reference argument before
+ * calling the function. This means that functions using fixed references do not need access to
+ * the rest of the workbook to execute. Hence the evaluate() method on the common
+ * interface Function does not take a workbook parameter.null
,
+ * nor are any of its elements.
+ * @param srcCellRow zero based row index of the cell containing the currently evaluating formula
+ * @param srcCellCol zero based column index of the cell containing the currently evaluating formula
+ * @param workbook is the workbook containing the formula/cell being evaluated
+ * @param sheet is the sheet containing the formula/cell being evaluated
+ * @return never null
. Possibly an instance of ErrorEval in the case of
+ * a specified Excel error (Exceptions are never thrown to represent Excel errors).
+ *
+ */
+ ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet);
+}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Hlookup.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Hlookup.java
index 8bac3d0c02..40ed1da490 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Hlookup.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Hlookup.java
@@ -1,25 +1,123 @@
-/*
-* 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.
-*/
-/*
- * Created on May 15, 2005
- *
- */
+/* ====================================================================
+ 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;
-public class Hlookup extends NotImplementedFunction {
+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.OperandResolver;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
+/**
+ * Implementation of the VLOOKUP() function.
+ * HLOOKUP(lookup_value, table_array, row_index_num, range_lookup)
+ *
+ * lookup_value The value to be found in the first column of the table array.
+ * table_array> An area reference for the lookup data.
+ * row_index_num a 1 based index specifying which row value of the lookup data will be returned.
+ * range_lookup If TRUE (default), HLOOKUP finds the largest value less than or equal to
+ * the lookup_value. If FALSE, only exact matches will be considered
+ *
+ * @author Josh Micich
+ */
+public final class Hlookup implements Function {
+
+ private static final class RowVector implements ValueVector {
+ private final AreaEval _tableArray;
+ private final int _size;
+ private final int _rowAbsoluteIndex;
+ private final int _firstColumnAbsoluteIndex;
+
+ public RowVector(AreaEval tableArray, int rowIndex) {
+ _rowAbsoluteIndex = tableArray.getFirstRow() + rowIndex;
+ if(!tableArray.containsRow(_rowAbsoluteIndex)) {
+ int lastRowIx = tableArray.getLastRow() - tableArray.getFirstRow();
+ throw new IllegalArgumentException("Specified row index (" + rowIndex
+ + ") is outside the allowed range (0.." + lastRowIx + ")");
+ }
+ _tableArray = tableArray;
+ _size = tableArray.getLastColumn() - tableArray.getFirstColumn() + 1;
+ if(_size < 1) {
+ throw new RuntimeException("bad table array size zero");
+ }
+ _firstColumnAbsoluteIndex = tableArray.getFirstColumn();
+ }
+
+ public ValueEval getItem(int index) {
+ if(index>_size) {
+ throw new ArrayIndexOutOfBoundsException("Specified index (" + index
+ + ") is outside the allowed range (0.." + (_size-1) + ")");
+ }
+ return _tableArray.getValueAt(_rowAbsoluteIndex, (short) (_firstColumnAbsoluteIndex + index));
+ }
+ public int getSize() {
+ return _size;
+ }
+ }
+
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+ Eval arg3 = null;
+ switch(args.length) {
+ case 4:
+ arg3 = args[3]; // important: assumed array element is never null
+ case 3:
+ break;
+ default:
+ // wrong number of arguments
+ return ErrorEval.VALUE_INVALID;
+ }
+ try {
+ // Evaluation order:
+ // arg0 lookup_value, arg1 table_array, arg3 range_lookup, find lookup value, arg2 row_index, fetch result
+ ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
+ AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]);
+ boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol);
+ int colIndex = LookupUtils.lookupIndexOfValue(lookupValue, new RowVector(tableArray, 0), isRangeLookup);
+ ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol);
+ int rowIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex);
+ ValueVector resultCol = createResultColumnVector(tableArray, rowIndex);
+ return resultCol.getItem(colIndex);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ }
+
+
+ /**
+ * Returns one column from an AreaEval
+ *
+ * @throws EvaluationException (#VALUE!) if colIndex is negative, (#REF!) if colIndex is too high
+ */
+ private ValueVector createResultColumnVector(AreaEval tableArray, int colIndex) throws EvaluationException {
+ if(colIndex < 0) {
+ throw EvaluationException.invalidValue();
+ }
+ int nCols = tableArray.getLastColumn() - tableArray.getFirstRow() + 1;
+
+ if(colIndex >= nCols) {
+ throw EvaluationException.invalidRef();
+ }
+ return new RowVector(tableArray, colIndex);
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java
index 90dbd591b7..7aba5db72a 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java
@@ -28,28 +28,22 @@ import org.apache.poi.hssf.record.formula.eval.Eval;
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class If implements Function {
+public final class If implements Function {
+
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
- public Eval evaluate(Eval[] evals, int srcCellRow, short srcCellCol) {
- Eval retval = null;
Eval evalWhenFalse = BoolEval.FALSE;
- switch (evals.length) {
+ switch (args.length) {
case 3:
- evalWhenFalse = evals[2];
+ evalWhenFalse = args[2];
case 2:
- BoolEval beval = (BoolEval) evals[0];
+ BoolEval beval = (BoolEval) args[0]; // TODO - class cast exception
if (beval.getBooleanValue()) {
- retval = evals[1];
+ return args[1];
}
- else {
- retval = evalWhenFalse;
- }
- break;
+ return evalWhenFalse;
default:
- retval = ErrorEval.UNKNOWN_ERROR;
+ return ErrorEval.VALUE_INVALID;
}
- return retval;
}
-
-
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java
index c7464ffed8..935e7cdbbd 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java
@@ -14,12 +14,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.functions;
-public class Indirect extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+/**
+ * Implementation for Excel function INDIRECT
+ *
+ * INDIRECT() returns the cell or area reference denoted by the text argument.
+ *
+ * Syntax:
+ * INDIRECT(ref_text,isA1Style)
+ *
+ * ref_text a string representation of the desired reference as it would normally be written
+ * in a cell formula.
+ * isA1Style (default TRUE) specifies whether the ref_text should be interpreted as A1-style
+ * or R1C1-style.
+ *
+ *
+ * @author Josh Micich
+ */
+public final class Indirect implements FreeRefFunction {
+
+ public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
+ // TODO - implement INDIRECT()
+ return ErrorEval.FUNCTION_NOT_IMPLEMENTED;
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java
index 6e8f84b342..c0e482e5a8 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java
@@ -14,79 +14,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.functions;
-import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
-import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.EvaluationException;
+import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class Isblank implements Function {
+public final class Isblank implements Function {
- public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
- ValueEval retval = null;
- boolean b = false;
-
- switch (operands.length) {
- default:
- retval = ErrorEval.VALUE_INVALID;
- break;
- case 1:
- if (operands[0] instanceof BlankEval) {
- b = true;
- }
- else if (operands[0] instanceof AreaEval) {
- AreaEval ae = (AreaEval) operands[0];
- if (ae.contains(srcCellRow, srcCellCol)) { // circular ref!
- retval = ErrorEval.CIRCULAR_REF_ERROR;
- }
- else if (ae.isRow()) {
- if (ae.containsColumn(srcCellCol)) {
- ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCellCol);
- b = (ve instanceof BlankEval);
- }
- else {
- b = false;
- }
- }
- else if (ae.isColumn()) {
- if (ae.containsRow(srcCellRow)) {
- ValueEval ve = ae.getValueAt(srcCellRow, ae.getFirstColumn());
- b = (ve instanceof BlankEval);
- }
- else {
- b = false;
- }
- }
- else {
- b = false;
- }
- }
- else if (operands[0] instanceof RefEval) {
- RefEval re = (RefEval) operands[0];
- b = (!re.isEvaluated()) && re.getInnerValueEval() instanceof BlankEval;
- }
- else {
- b = false;
- }
- }
-
- if (retval == null) {
- retval = b
- ? BoolEval.TRUE
- : BoolEval.FALSE;
- }
- return retval;
- }
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+ if(args.length != 1) {
+ return ErrorEval.VALUE_INVALID;
+ }
+ Eval arg = args[0];
+
+ ValueEval singleCellValue;
+ try {
+ singleCellValue = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
+ } catch (EvaluationException e) {
+ return BoolEval.FALSE;
+ }
+ return BoolEval.valueOf(singleCellValue instanceof BlankEval);
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java
index c0cb39b268..0bc49b4070 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java
@@ -14,125 +14,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.functions;
-import org.apache.poi.hssf.record.formula.eval.AreaEval;
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.RefEval;
-import org.apache.poi.hssf.record.formula.eval.StringValueEval;
+import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class Len extends TextFunction {
+public final class Len extends TextFunction {
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+
+ if(args.length != 1) {
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ try {
+ ValueEval veval = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
- 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;
- }
-
-
- /**
- * converts from Different ValueEval types to StringEval.
- * Note: AreaEvals are not handled, if arg is an AreaEval,
- * the returned value is ErrorEval.VALUE_INVALID
- * @param ve
- */
- protected ValueEval attemptXlateToText(ValueEval ve) {
- ValueEval retval;
- if (ve instanceof StringValueEval || ve instanceof RefEval) {
- retval = ve;
- }
- else if (ve instanceof BlankEval) {
- retval = ve;
- }
- else {
- retval = ErrorEval.VALUE_INVALID;
- }
- return retval;
- }
+ String str = OperandResolver.coerceValueToString(veval);
+
+ return new NumberEval(str.length());
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Lookup.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Lookup.java
index f98ccca7e3..be1d0d0f94 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Lookup.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Lookup.java
@@ -1,25 +1,96 @@
-/*
-* 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.
-*/
-/*
- * Created on May 15, 2005
- *
- */
+/* ====================================================================
+ 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;
-public class Lookup extends NotImplementedFunction {
+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.OperandResolver;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
+/**
+ * Implementation of Excel function LOOKUP.
+ *
+ * LOOKUP finds an index row in a lookup table by the first column value and returns the value from another column.
+ *
+ * Syntax:
+ * VLOOKUP(lookup_value, lookup_vector, result_vector)
+ *
+ * lookup_value The value to be found in the lookup vector.
+ * lookup_vector> An area reference for the lookup data.
+ * result_vector Single row or single column area reference from which the result value is chosen.
+ *
+ * @author Josh Micich
+ */
+public final class Lookup implements Function {
+ private static final class SimpleValueVector implements ValueVector {
+ private final ValueEval[] _values;
+
+ public SimpleValueVector(ValueEval[] values) {
+ _values = values;
+ }
+ public ValueEval getItem(int index) {
+ return _values[index];
+ }
+ public int getSize() {
+ return _values.length;
+ }
+ }
+
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+ switch(args.length) {
+ case 3:
+ break;
+ case 2:
+ // complex rules to choose lookupVector and resultVector from the single area ref
+ throw new RuntimeException("Two arg version of LOOKUP not supported yet");
+ default:
+ return ErrorEval.VALUE_INVALID;
+ }
+
+
+ try {
+ ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
+ AreaEval aeLookupVector = LookupUtils.resolveTableArrayArg(args[1]);
+ AreaEval aeResultVector = LookupUtils.resolveTableArrayArg(args[2]);
+
+ ValueVector lookupVector = createVector(aeLookupVector);
+ ValueVector resultVector = createVector(aeResultVector);
+ if(lookupVector.getSize() > resultVector.getSize()) {
+ // Excel seems to handle this by accessing past the end of the result vector.
+ throw new RuntimeException("Lookup vector and result vector of differing sizes not supported yet");
+ }
+ int index = LookupUtils.lookupIndexOfValue(lookupValue, lookupVector, true);
+
+ return resultVector.getItem(index);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ }
+
+ private static ValueVector createVector(AreaEval ae) {
+
+ if(!ae.isRow() && !ae.isColumn()) {
+ // extra complexity required to emulate the way LOOKUP can handles these abnormal cases.
+ throw new RuntimeException("non-vector lookup or result areas not supported yet");
+ }
+ return new SimpleValueVector(ae.getValues());
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/LookupUtils.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/LookupUtils.java
new file mode 100644
index 0000000000..d6a8489623
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/LookupUtils.java
@@ -0,0 +1,530 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.eval.Area2DEval;
+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.EvaluationException;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
+import org.apache.poi.hssf.record.formula.eval.OperandResolver;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Common functionality used by VLOOKUP, HLOOKUP, LOOKUP and MATCH
+ *
+ * @author Josh Micich
+ */
+final class LookupUtils {
+
+ /**
+ * Represents a single row or column within an AreaEval.
+ */
+ public interface ValueVector {
+ ValueEval getItem(int index);
+ int getSize();
+ }
+ /**
+ * Enumeration to support 4 valued comparison results.
+ * Excel lookup functions have complex behaviour in the case where the lookup array has mixed
+ * types, and/or is unordered. Contrary to suggestions in some Excel documentation, there
+ * does not appear to be a universal ordering across types. The binary search algorithm used
+ * changes behaviour when the evaluated 'mid' value has a different type to the lookup value.
+ *
+ * A simple int might have done the same job, but there is risk in confusion with the well
+ * known Comparable.compareTo() and Comparator.compare() which both use
+ * a ubiquitous 3 value result encoding.
+ */
+ public static final class CompareResult {
+ private final boolean _isTypeMismatch;
+ private final boolean _isLessThan;
+ private final boolean _isEqual;
+ private final boolean _isGreaterThan;
+
+ private CompareResult(boolean isTypeMismatch, int simpleCompareResult) {
+ if(isTypeMismatch) {
+ _isTypeMismatch = true;
+ _isLessThan = false;
+ _isEqual = false;
+ _isGreaterThan = false;
+ } else {
+ _isTypeMismatch = false;
+ _isLessThan = simpleCompareResult < 0;
+ _isEqual = simpleCompareResult == 0;
+ _isGreaterThan = simpleCompareResult > 0;
+ }
+ }
+ public static final CompareResult TYPE_MISMATCH = new CompareResult(true, 0);
+ public static final CompareResult LESS_THAN = new CompareResult(false, -1);
+ public static final CompareResult EQUAL = new CompareResult(false, 0);
+ public static final CompareResult GREATER_THAN = new CompareResult(false, +1);
+
+ public static final CompareResult valueOf(int simpleCompareResult) {
+ if(simpleCompareResult < 0) {
+ return LESS_THAN;
+ }
+ if(simpleCompareResult > 0) {
+ return GREATER_THAN;
+ }
+ return EQUAL;
+ }
+
+ public boolean isTypeMismatch() {
+ return _isTypeMismatch;
+ }
+ public boolean isLessThan() {
+ return _isLessThan;
+ }
+ public boolean isEqual() {
+ return _isEqual;
+ }
+ public boolean isGreaterThan() {
+ return _isGreaterThan;
+ }
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(formatAsString());
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private String formatAsString() {
+ if(_isTypeMismatch) {
+ return "TYPE_MISMATCH";
+ }
+ if(_isLessThan) {
+ return "LESS_THAN";
+ }
+ if(_isEqual) {
+ return "EQUAL";
+ }
+ if(_isGreaterThan) {
+ return "GREATER_THAN";
+ }
+ // toString must be reliable
+ return "??error??";
+ }
+ }
+
+ public interface LookupValueComparer {
+ /**
+ * @return one of 4 instances or CompareResult: LESS_THAN, EQUAL,
+ * GREATER_THAN or TYPE_MISMATCH
+ */
+ CompareResult compareTo(ValueEval other);
+ }
+
+ private static abstract class LookupValueComparerBase implements LookupValueComparer {
+
+ private final Class _targetClass;
+ protected LookupValueComparerBase(ValueEval targetValue) {
+ if(targetValue == null) {
+ throw new RuntimeException("targetValue cannot be null");
+ }
+ _targetClass = targetValue.getClass();
+ }
+ public final CompareResult compareTo(ValueEval other) {
+ if (other == null) {
+ throw new RuntimeException("compare to value cannot be null");
+ }
+ if (_targetClass != other.getClass()) {
+ return CompareResult.TYPE_MISMATCH;
+ }
+ if (_targetClass == StringEval.class) {
+
+ }
+ return compareSameType(other);
+ }
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(getValueAsString());
+ sb.append("]");
+ return sb.toString();
+ }
+ protected abstract CompareResult compareSameType(ValueEval other);
+ /** used only for debug purposes */
+ protected abstract String getValueAsString();
+ }
+
+ private static final class StringLookupComparer extends LookupValueComparerBase {
+ private String _value;
+
+ protected StringLookupComparer(StringEval se) {
+ super(se);
+ _value = se.getStringValue();
+ }
+ protected CompareResult compareSameType(ValueEval other) {
+ StringEval se = (StringEval) other;
+ return CompareResult.valueOf(_value.compareToIgnoreCase(se.getStringValue()));
+ }
+ protected String getValueAsString() {
+ return _value;
+ }
+ }
+ private static final class NumberLookupComparer extends LookupValueComparerBase {
+ private double _value;
+
+ protected NumberLookupComparer(NumberEval ne) {
+ super(ne);
+ _value = ne.getNumberValue();
+ }
+ protected CompareResult compareSameType(ValueEval other) {
+ NumberEval ne = (NumberEval) other;
+ return CompareResult.valueOf(Double.compare(_value, ne.getNumberValue()));
+ }
+ protected String getValueAsString() {
+ return String.valueOf(_value);
+ }
+ }
+ private static final class BooleanLookupComparer extends LookupValueComparerBase {
+ private boolean _value;
+
+ protected BooleanLookupComparer(BoolEval be) {
+ super(be);
+ _value = be.getBooleanValue();
+ }
+ protected CompareResult compareSameType(ValueEval other) {
+ BoolEval be = (BoolEval) other;
+ boolean otherVal = be.getBooleanValue();
+ if(_value == otherVal) {
+ return CompareResult.EQUAL;
+ }
+ // TRUE > FALSE
+ if(_value) {
+ return CompareResult.GREATER_THAN;
+ }
+ return CompareResult.LESS_THAN;
+ }
+ protected String getValueAsString() {
+ return String.valueOf(_value);
+ }
+ }
+
+ /**
+ * Processes the third argument to VLOOKUP, or HLOOKUP (col_index_num
+ * or row_index_num respectively).
+ * Sample behaviour:
+ *
+ *
+ * Input Return Value Thrown Error
+ * 5 4
+ * 2.9 2
+ * "5" 4
+ * "2.18e1" 21
+ * "-$2" -3 *
+ * FALSE -1 *
+ * TRUE 0
+ * "TRUE" #REF!
+ * "abc" #REF!
+ * "" #REF!
+ * <blank> #VALUE!
+ *
+ * * Note - out of range errors (both too high and too low) are handled by the caller.
+ * @return column or row index as a zero-based value
+ *
+ */
+ public static int resolveRowOrColIndexArg(ValueEval veRowColIndexArg) throws EvaluationException {
+ if(veRowColIndexArg == null) {
+ throw new IllegalArgumentException("argument must not be null");
+ }
+ if(veRowColIndexArg instanceof BlankEval) {
+ throw EvaluationException.invalidValue();
+ }
+ if(veRowColIndexArg instanceof StringEval) {
+ StringEval se = (StringEval) veRowColIndexArg;
+ String strVal = se.getStringValue();
+ Double dVal = OperandResolver.parseDouble(strVal);
+ if(dVal == null) {
+ // String does not resolve to a number. Raise #VALUE! error.
+ throw EvaluationException.invalidRef();
+ // This includes text booleans "TRUE" and "FALSE". They are not valid.
+ }
+ // else - numeric value parses OK
+ }
+ // actual BoolEval values get interpreted as FALSE->0 and TRUE->1
+ return OperandResolver.coerceValueToInt(veRowColIndexArg) - 1;
+ }
+
+
+
+ /**
+ * The second argument (table_array) should be an area ref, but can actually be a cell ref, in
+ * which case it is interpreted as a 1x1 area ref. Other scalar values cause #VALUE! error.
+ */
+ public static AreaEval resolveTableArrayArg(Eval eval) throws EvaluationException {
+ if (eval instanceof AreaEval) {
+ return (AreaEval) eval;
+ }
+
+ if(eval instanceof RefEval) {
+ RefEval refEval = (RefEval) eval;
+ // Make this cell ref look like a 1x1 area ref.
+
+ // It doesn't matter if eval is a 2D or 3D ref, because that detail is never asked of AreaEval.
+ // This code only requires the value array item.
+ // anything would be ok for rowIx and colIx, but may as well get it right.
+ int rowIx = refEval.getRow();
+ int colIx = refEval.getColumn();
+ AreaPtg ap = new AreaPtg(rowIx, rowIx, colIx, colIx, false, false, false, false);
+ ValueEval value = refEval.getInnerValueEval();
+ return new Area2DEval(ap, new ValueEval[] { value, });
+ }
+ throw EvaluationException.invalidValue();
+ }
+
+
+ /**
+ * Resolves the last (optional) parameter (range_lookup) to the VLOOKUP and HLOOKUP functions.
+ * @param rangeLookupArg
+ * @param srcCellRow
+ * @param srcCellCol
+ * @return
+ * @throws EvaluationException
+ */
+ public static boolean resolveRangeLookupArg(Eval rangeLookupArg, int srcCellRow, short srcCellCol) throws EvaluationException {
+ if(rangeLookupArg == null) {
+ // range_lookup arg not provided
+ return true; // default is TRUE
+ }
+ ValueEval valEval = OperandResolver.getSingleValue(rangeLookupArg, srcCellRow, srcCellCol);
+ if(valEval instanceof BlankEval) {
+ // Tricky:
+ // fourth arg supplied but evaluates to blank
+ // this does not get the default value
+ return false;
+ }
+ if(valEval instanceof BoolEval) {
+ // Happy day flow
+ BoolEval boolEval = (BoolEval) valEval;
+ return boolEval.getBooleanValue();
+ }
+
+ if (valEval instanceof StringEval) {
+ String stringValue = ((StringEval) valEval).getStringValue();
+ if(stringValue.length() < 1) {
+ // More trickiness:
+ // Empty string is not the same as BlankEval. It causes #VALUE! error
+ throw EvaluationException.invalidValue();
+ }
+ // TODO move parseBoolean to OperandResolver
+ Boolean b = Countif.parseBoolean(stringValue);
+ if(b != null) {
+ // string converted to boolean OK
+ return b.booleanValue();
+ }
+ // Even more trickiness:
+ // Note - even if the StringEval represents a number value (for example "1"),
+ // Excel does not resolve it to a boolean.
+ throw EvaluationException.invalidValue();
+ // This is in contrast to the code below,, where NumberEvals values (for
+ // example 0.01) *do* resolve to equivalent boolean values.
+ }
+ if (valEval instanceof NumericValueEval) {
+ NumericValueEval nve = (NumericValueEval) valEval;
+ // zero is FALSE, everything else is TRUE
+ return 0.0 != nve.getNumberValue();
+ }
+ throw new RuntimeException("Unexpected eval type (" + valEval.getClass().getName() + ")");
+ }
+
+ public static int lookupIndexOfValue(ValueEval lookupValue, ValueVector vector, boolean isRangeLookup) throws EvaluationException {
+ LookupValueComparer lookupComparer = createLookupComparer(lookupValue);
+ int result;
+ if(isRangeLookup) {
+ result = performBinarySearch(vector, lookupComparer);
+ } else {
+ result = lookupIndexOfExactValue(lookupComparer, vector);
+ }
+ if(result < 0) {
+ throw new EvaluationException(ErrorEval.NA);
+ }
+ return result;
+ }
+
+
+ /**
+ * Finds first (lowest index) exact occurrence of specified value.
+ * @param lookupValue the value to be found in column or row vector
+ * @param vector the values to be searched. For VLOOKUP this is the first column of the
+ * tableArray. For HLOOKUP this is the first row of the tableArray.
+ * @return zero based index into the vector, -1 if value cannot be found
+ */
+ private static int lookupIndexOfExactValue(LookupValueComparer lookupComparer, ValueVector vector) {
+
+ // find first occurrence of lookup value
+ int size = vector.getSize();
+ for (int i = 0; i < size; i++) {
+ if(lookupComparer.compareTo(vector.getItem(i)).isEqual()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+
+ /**
+ * Encapsulates some standard binary search functionality so the unusual Excel behaviour can
+ * be clearly distinguished.
+ */
+ private static final class BinarySearchIndexes {
+
+ private int _lowIx;
+ private int _highIx;
+
+ public BinarySearchIndexes(int highIx) {
+ _lowIx = -1;
+ _highIx = highIx;
+ }
+
+ /**
+ * @return -1 if the search range is empty
+ */
+ public int getMidIx() {
+ int ixDiff = _highIx - _lowIx;
+ if(ixDiff < 2) {
+ return -1;
+ }
+ return _lowIx + (ixDiff / 2);
+ }
+
+ public int getLowIx() {
+ return _lowIx;
+ }
+ public int getHighIx() {
+ return _highIx;
+ }
+ public void narrowSearch(int midIx, boolean isLessThan) {
+ if(isLessThan) {
+ _highIx = midIx;
+ } else {
+ _lowIx = midIx;
+ }
+ }
+ }
+ /**
+ * Excel has funny behaviour when the some elements in the search vector are the wrong type.
+ *
+ */
+ private static int performBinarySearch(ValueVector vector, LookupValueComparer lookupComparer) {
+ // both low and high indexes point to values assumed too low and too high.
+ BinarySearchIndexes bsi = new BinarySearchIndexes(vector.getSize());
+
+ while(true) {
+ int midIx = bsi.getMidIx();
+
+ if(midIx < 0) {
+ return bsi.getLowIx();
+ }
+ CompareResult cr = lookupComparer.compareTo(vector.getItem(midIx));
+ if(cr.isTypeMismatch()) {
+ int newMidIx = handleMidValueTypeMismatch(lookupComparer, vector, bsi, midIx);
+ if(newMidIx < 0) {
+ continue;
+ }
+ midIx = newMidIx;
+ cr = lookupComparer.compareTo(vector.getItem(midIx));
+ }
+ if(cr.isEqual()) {
+ return findLastIndexInRunOfEqualValues(lookupComparer, vector, midIx, bsi.getHighIx());
+ }
+ bsi.narrowSearch(midIx, cr.isLessThan());
+ }
+ }
+ /**
+ * Excel seems to handle mismatched types initially by just stepping 'mid' ix forward to the
+ * first compatible value.
+ * @param midIx 'mid' index (value which has the wrong type)
+ * @return usually -1, signifying that the BinarySearchIndex has been narrowed to the new mid
+ * index. Zero or greater signifies that an exact match for the lookup value was found
+ */
+ private static int handleMidValueTypeMismatch(LookupValueComparer lookupComparer, ValueVector vector,
+ BinarySearchIndexes bsi, int midIx) {
+ int newMid = midIx;
+ int highIx = bsi.getHighIx();
+
+ while(true) {
+ newMid++;
+ if(newMid == highIx) {
+ // every element from midIx to highIx was the wrong type
+ // move highIx down to the low end of the mid values
+ bsi.narrowSearch(midIx, true);
+ return -1;
+ }
+ CompareResult cr = lookupComparer.compareTo(vector.getItem(newMid));
+ if(cr.isLessThan() && newMid == highIx-1) {
+ // move highIx down to the low end of the mid values
+ bsi.narrowSearch(midIx, true);
+ return -1;
+ // but only when "newMid == highIx-1"? slightly weird.
+ // It would seem more efficient to always do this.
+ }
+ if(cr.isTypeMismatch()) {
+ // keep stepping over values until the right type is found
+ continue;
+ }
+ if(cr.isEqual()) {
+ return newMid;
+ }
+ // Note - if moving highIx down (due to lookup
+ * MATCH(lookup_value, lookup_array, match_type)
+ *
+ * Returns a 1-based index specifying at what position in the lookup_array the specified
+ * lookup_value is found.
+ *
+ * Specific matching behaviour can be modified with the optional match_type parameter.
+ *
+ *
+ *
+ *
+ * * Note regarding order - For the match_type cases that require the lookup_array to
+ * be ordered, MATCH() can produce incorrect results if this requirement is not met. Observed
+ * behaviour in Excel is to return the lowest index value for which every item after that index
+ * breaks the match rule.
+ * Value Matching Behaviour
+ * 1 (default) find the largest value that is less than or equal to lookup_value.
+ * The lookup_array must be in ascending order*.
+ * 0 find the first value that is exactly equal to lookup_value.
+ * The lookup_array can be in any order.
+ * -1 find the smallest value that is greater than or equal to lookup_value.
+ * The lookup_array must be in descending order*.
+ * The (ascending) sort order expected by MATCH() is:
+ * numbers (low to high), strings (A to Z), boolean (FALSE to TRUE)
+ * MATCH() ignores all elements in the lookup_array with a different type to the lookup_value.
+ * Type conversion of the lookup_array elements is never performed.
+ *
+ *
+ * @author Josh Micich
+ */
+public final class Match implements Function {
+
+
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+
+ double match_type = 1; // default
+
+ switch(args.length) {
+ case 3:
+ try {
+ match_type = evaluateMatchTypeArg(args[2], srcCellRow, srcCellCol);
+ } catch (EvaluationException e) {
+ // Excel/MATCH() seems to have slightly abnormal handling of errors with
+ // the last parameter. Errors do not propagate up. Every error gets
+ // translated into #REF!
+ return ErrorEval.REF_INVALID;
+ }
+ case 2:
+ break;
+ default:
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ boolean matchExact = match_type == 0;
+ // Note - Excel does not strictly require -1 and +1
+ boolean findLargestLessThanOrEqual = match_type > 0;
+
+
+ try {
+ ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
+ ValueEval[] lookupRange = evaluateLookupRange(args[1]);
+ int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual);
+ return new NumberEval(index + 1); // +1 to convert to 1-based
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ }
+
+ private static ValueEval[] evaluateLookupRange(Eval eval) throws EvaluationException {
+ if (eval instanceof RefEval) {
+ RefEval re = (RefEval) eval;
+ return new ValueEval[] { re.getInnerValueEval(), };
+ }
+ if (eval instanceof AreaEval) {
+ AreaEval ae = (AreaEval) eval;
+ if(!ae.isColumn() && !ae.isRow()) {
+ throw new EvaluationException(ErrorEval.NA);
+ }
+ return ae.getValues();
+ }
+
+ // Error handling for lookup_range arg is also unusual
+ if(eval instanceof NumericValueEval) {
+ throw new EvaluationException(ErrorEval.NA);
+ }
+ if (eval instanceof StringEval) {
+ StringEval se = (StringEval) eval;
+ Double d = OperandResolver.parseDouble(se.getStringValue());
+ if(d == null) {
+ // plain string
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ // else looks like a number
+ throw new EvaluationException(ErrorEval.NA);
+ }
+ throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
+ }
+
+
+
+ private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol)
+ throws EvaluationException {
+ Eval match_type = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
+
+ if(match_type instanceof ErrorEval) {
+ throw new EvaluationException((ErrorEval)match_type);
+ }
+ if(match_type instanceof NumericValueEval) {
+ NumericValueEval ne = (NumericValueEval) match_type;
+ return ne.getNumberValue();
+ }
+ if (match_type instanceof StringEval) {
+ StringEval se = (StringEval) match_type;
+ Double d = OperandResolver.parseDouble(se.getStringValue());
+ if(d == null) {
+ // plain string
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ // if the string parses as a number, it is OK
+ return d.doubleValue();
+ }
+ throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")");
+ }
+
+ /**
+ * @return zero based index
+ */
+ private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange,
+ boolean matchExact, boolean findLargestLessThanOrEqual) throws EvaluationException {
+
+ LookupValueComparer lookupComparer = createLookupComparer(lookupValue, matchExact);
+
+ if(matchExact) {
+ for (int i = 0; i < lookupRange.length; i++) {
+ if(lookupComparer.compareTo(lookupRange[i]).isEqual()) {
+ return i;
+ }
+ }
+ throw new EvaluationException(ErrorEval.NA);
+ }
+
+ if(findLargestLessThanOrEqual) {
+ // Note - backward iteration
+ for (int i = lookupRange.length - 1; i>=0; i--) {
+ CompareResult cmp = lookupComparer.compareTo(lookupRange[i]);
+ if(cmp.isTypeMismatch()) {
+ continue;
+ }
+ if(!cmp.isLessThan()) {
+ return i;
+ }
+ }
+ throw new EvaluationException(ErrorEval.NA);
+ }
+
+ // else - find smallest greater than or equal to
+ // TODO - is binary search used for (match_type==+1) ?
+ for (int i = 0; i
MID returns a specific number of
+ * characters from a text string, starting at the specified position.
+ *
+ * Syntax:
MID(text, start_num,
+ * num_chars)
+ *
* @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.
+ * Returns a specific number of characters from a text string, starting at
+ * the position you specify, based on the number of characters you specify.
*
* @see org.apache.poi.hssf.record.formula.eval.Eval
*/
- public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
- Eval retval = null;
- String str = null;
- int startNum = 0;
- int numChars = 0;
-
- switch (operands.length) {
- default:
- retval = ErrorEval.VALUE_INVALID;
- case 3:
- // first operand is text string containing characters to extract
- // second operand is position of first character to extract
- // third operand is the number of characters to return
- ValueEval firstveval = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol);
- ValueEval secondveval = singleOperandEvaluate(operands[1], srcCellRow, srcCellCol);
- ValueEval thirdveval = singleOperandEvaluate(operands[2], srcCellRow, srcCellCol);
- if (firstveval instanceof StringValueEval
- && secondveval instanceof NumericValueEval
- && thirdveval instanceof NumericValueEval) {
-
- StringValueEval strEval = (StringValueEval) firstveval;
- str = strEval.getStringValue();
-
- NumericValueEval startNumEval = (NumericValueEval) secondveval;
- // NOTE: it is safe to cast to int here
- // because in Excel =MID("test", 1, 1.7) returns t
- // so 1.7 must be truncated to 1
- // and =MID("test", 1.9, 2) returns te
- // so 1.9 must be truncated to 1
- startNum = (int) startNumEval.getNumberValue();
-
- NumericValueEval numCharsEval = (NumericValueEval) thirdveval;
- numChars = (int) numCharsEval.getNumberValue();
-
- } else {
- retval = ErrorEval.VALUE_INVALID;
- }
- }
-
- if (retval == null) {
- if (startNum < 1 || numChars < 0) {
- retval = ErrorEval.VALUE_INVALID;
- } else if (startNum > str.length() || numChars == 0) {
- retval = BlankEval.INSTANCE;
- } else if (startNum + numChars > str.length()) {
- retval = new StringEval(str.substring(startNum - 1));
- } else {
- retval = new StringEval(str.substring(startNum - 1, numChars));
- }
- }
- return retval;
- }
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+ if (args.length != 3) {
+ return ErrorEval.VALUE_INVALID;
+ }
-}
+ String text;
+ int startIx; // zero based
+ int numChars;
+
+ try {
+ 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
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+
+ int len = text.length();
+ if (startIx < 0) {
+ return ErrorEval.VALUE_INVALID;
+ }
+ if (numChars < 0) {
+ return ErrorEval.VALUE_INVALID;
+ }
+ if (numChars < 0 || startIx > len) {
+ return new StringEval("");
+ }
+ int endIx = startIx + numChars;
+ if (endIx > len) {
+ endIx = len;
+ }
+ String result = text.substring(startIx, endIx);
+ return new StringEval(result);
+
+ }
+
+ 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!),
+ // but for num_chars causes empty string to be returned.
+ return 0;
+ }
+
+ return OperandResolver.coerceValueToInt(ev);
+ }
+}
\ No newline at end of file
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java
index a998a870f6..21ba47b569 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
@@ -35,7 +32,6 @@ public class Mina extends MultiOperandNumericFunction {
new ValueEvalToNumericXlator((short) (
ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
- | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
//| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
//| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java
index 2840c89883..0e7cce217e 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 22, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
@@ -36,32 +33,52 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
* where the order of operands does not matter
*/
public abstract class MultiOperandNumericFunction extends NumericFunction {
+ static final double[] EMPTY_DOUBLE_ARRAY = { };
+
+ private static class DoubleList {
+ private double[] _array;
+ private int _count;
- 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
- ));
+ public DoubleList() {
+ _array = new double[8];
+ _count = 0;
+ }
+
+ public double[] toArray() {
+ if(_count < 1) {
+ return EMPTY_DOUBLE_ARRAY;
+ }
+ double[] result = new double[_count];
+ System.arraycopy(_array, 0, result, 0, _count);
+ return result;
+ }
+
+ public void add(double[] values) {
+ int addLen = values.length;
+ ensureCapacity(_count + addLen);
+ System.arraycopy(values, 0, _array, _count, addLen);
+ _count += addLen;
+ }
+
+ private void ensureCapacity(int reqSize) {
+ if(reqSize > _array.length) {
+ int newSize = reqSize * 3 / 2; // grow with 50% extra
+ double[] newArr = new double[newSize];
+ System.arraycopy(_array, 0, newArr, 0, _count);
+ _array = newArr;
+ }
+ }
+
+ public void add(double value) {
+ ensureCapacity(_count + 1);
+ _array[_count] = value;
+ _count++;
+ }
+ }
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.
@@ -76,40 +93,26 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
* 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.
+ * excel cause this function to return null
.
*
* @param operands
* @param srcRow
* @param srcCol
*/
protected double[] getNumberArray(Eval[] operands, int srcRow, short srcCol) {
- double[] retval = new double[30];
- int count = 0;
-
- outer: do { // goto simulator loop
- if (operands.length > getMaxNumOperands()) {
- break outer;
- }
- else {
- for (int i=0, iSize=operands.length; ifalse
if any sub-array is missing, or is of different length
*/
- private static double[] putInArray(double[] arr, int pos, double d) {
- double[] tarr = arr;
- while (pos >= arr.length) {
- arr = new double[arr.length << 1];
- }
- if (tarr.length != arr.length) {
- System.arraycopy(tarr, 0, arr, 0, tarr.length);
- }
- arr[pos] = d;
- return arr;
- }
-
- private static double[] putInArray(double[] arr, int pos, double[] d) {
- double[] tarr = arr;
- while (pos+d.length >= arr.length) {
- arr = new double[arr.length << 1];
- }
- if (tarr.length != arr.length) {
- System.arraycopy(tarr, 0, arr, 0, tarr.length);
- }
- for (int i=0, iSize=d.length; i
+ * OFFSET(reference, rows, cols, height, width)
+ * reference is the base reference.
+ * rows is the number of rows up or down from the base reference.
+ * cols is the number of columns left or right from the base reference.
+ * height (default same height as base reference) is the row count for the returned area reference.
+ * width (default same width as base reference) is the column count for the returned area reference.
+ *
+ * @author Josh Micich
+ */
+public final class Offset implements FreeRefFunction {
+ // These values are specific to BIFF8
+ private static final int LAST_VALID_ROW_INDEX = 0xFFFF;
+ private static final int LAST_VALID_COLUMN_INDEX = 0xFF;
+
+ /**
+ * Exceptions are used within this class to help simplify flow control when error conditions
+ * are encountered
+ */
+ private static final class EvalEx extends Exception {
+ private final ErrorEval _error;
+
+ public EvalEx(ErrorEval error) {
+ _error = error;
+ }
+ public ErrorEval getError() {
+ return _error;
+ }
+ }
+
+ /**
+ * A one dimensional base + offset. Represents either a row range or a column range.
+ * Two instances of this class together specify an area range.
+ */
+ /* package */ static final class LinearOffsetRange {
+
+ private final int _offset;
+ private final int _length;
+
+ public LinearOffsetRange(int offset, int length) {
+ if(length == 0) {
+ // handled that condition much earlier
+ throw new RuntimeException("length may not be zero");
+ }
+ _offset = offset;
+ _length = length;
+ }
+
+ public short getFirstIndex() {
+ return (short) _offset;
+ }
+ public short getLastIndex() {
+ return (short) (_offset + _length - 1);
+ }
+ /**
+ * Moves the range by the specified translation amount.
+ *
+ * This method also 'normalises' the range: Excel specifies that the width and height
+ * parameters (length field here) cannot be negative. However, OFFSET() does produce
+ * sensible results in these cases. That behavior is replicated here.
+ *
+ * @param translationAmount may be zero negative or positive
+ *
+ * @return the equivalent LinearOffsetRange with a positive length, moved by the
+ * specified translationAmount.
+ */
+ public LinearOffsetRange normaliseAndTranslate(int translationAmount) {
+ if (_length > 0) {
+ if(translationAmount == 0) {
+ return this;
+ }
+ return new LinearOffsetRange(translationAmount + _offset, _length);
+ }
+ return new LinearOffsetRange(translationAmount + _offset + _length + 1, -_length);
+ }
+
+ public boolean isOutOfBounds(int lowValidIx, int highValidIx) {
+ if(_offset < lowValidIx) {
+ return true;
+ }
+ if(getLastIndex() > highValidIx) {
+ return true;
+ }
+ return false;
+ }
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(_offset).append("...").append(getLastIndex());
+ sb.append("]");
+ return sb.toString();
+ }
+ }
+
+
+ /**
+ * Encapsulates either an area or cell reference which may be 2d or 3d.
+ */
+ private static final class BaseRef {
+ private static final int INVALID_SHEET_INDEX = -1;
+ private final int _firstRowIndex;
+ private final int _firstColumnIndex;
+ private final int _width;
+ private final int _height;
+ private final int _externalSheetIndex;
+
+ public BaseRef(RefEval re) {
+ _firstRowIndex = re.getRow();
+ _firstColumnIndex = re.getColumn();
+ _height = 1;
+ _width = 1;
+ if (re instanceof Ref3DEval) {
+ Ref3DEval r3e = (Ref3DEval) re;
+ _externalSheetIndex = r3e.getExternSheetIndex();
+ } else {
+ _externalSheetIndex = INVALID_SHEET_INDEX;
+ }
+ }
+
+ public BaseRef(AreaEval ae) {
+ _firstRowIndex = ae.getFirstRow();
+ _firstColumnIndex = ae.getFirstColumn();
+ _height = ae.getLastRow() - ae.getFirstRow() + 1;
+ _width = ae.getLastColumn() - ae.getFirstColumn() + 1;
+ if (ae instanceof Area3DEval) {
+ Area3DEval a3e = (Area3DEval) ae;
+ _externalSheetIndex = a3e.getExternSheetIndex();
+ } else {
+ _externalSheetIndex = INVALID_SHEET_INDEX;
+ }
+ }
+
+ public int getWidth() {
+ return _width;
+ }
+
+ public int getHeight() {
+ return _height;
+ }
+
+ public int getFirstRowIndex() {
+ return _firstRowIndex;
+ }
+
+ public int getFirstColumnIndex() {
+ return _firstColumnIndex;
+ }
+
+ public boolean isIs3d() {
+ return _externalSheetIndex > 0;
+ }
+
+ public short getExternalSheetIndex() {
+ if(_externalSheetIndex < 0) {
+ throw new IllegalStateException("external sheet index only available for 3d refs");
+ }
+ return (short) _externalSheetIndex;
+ }
+
+ }
+
+ public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
+
+ if(args.length < 3 || args.length > 5) {
+ return ErrorEval.VALUE_INVALID;
+ }
+
+
+ try {
+ BaseRef baseRef = evaluateBaseRef(args[0]);
+ int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol);
+ int columnOffset = evaluateIntArg(args[2], srcCellRow, srcCellCol);
+ int height = baseRef.getHeight();
+ int width = baseRef.getWidth();
+ switch(args.length) {
+ case 5:
+ width = evaluateIntArg(args[4], srcCellRow, srcCellCol);
+ case 4:
+ height = evaluateIntArg(args[3], srcCellRow, srcCellCol);
+ }
+ // Zero height or width raises #REF! error
+ if(height == 0 || width == 0) {
+ return ErrorEval.REF_INVALID;
+ }
+ LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height);
+ LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width);
+ return createOffset(baseRef, rowOffsetRange, colOffsetRange, workbook, sheet);
+ } catch (EvalEx e) {
+ return e.getError();
+ }
+ }
+
+
+ private static AreaEval createOffset(BaseRef baseRef,
+ LinearOffsetRange rowOffsetRange, LinearOffsetRange colOffsetRange,
+ HSSFWorkbook workbook, HSSFSheet sheet) throws EvalEx {
+
+ LinearOffsetRange rows = rowOffsetRange.normaliseAndTranslate(baseRef.getFirstRowIndex());
+ LinearOffsetRange cols = colOffsetRange.normaliseAndTranslate(baseRef.getFirstColumnIndex());
+
+ if(rows.isOutOfBounds(0, LAST_VALID_ROW_INDEX)) {
+ throw new EvalEx(ErrorEval.REF_INVALID);
+ }
+ if(cols.isOutOfBounds(0, LAST_VALID_COLUMN_INDEX)) {
+ throw new EvalEx(ErrorEval.REF_INVALID);
+ }
+ if(baseRef.isIs3d()) {
+ Area3DPtg a3dp = new Area3DPtg(rows.getFirstIndex(), rows.getLastIndex(),
+ cols.getFirstIndex(), cols.getLastIndex(),
+ false, false, false, false,
+ baseRef.getExternalSheetIndex());
+ return HSSFFormulaEvaluator.evaluateArea3dPtg(workbook, a3dp);
+ }
+
+ AreaPtg ap = new AreaPtg(rows.getFirstIndex(), rows.getLastIndex(),
+ cols.getFirstIndex(), cols.getLastIndex(),
+ false, false, false, false);
+ return HSSFFormulaEvaluator.evaluateAreaPtg(sheet, workbook, ap);
+ }
+
+
+ private static BaseRef evaluateBaseRef(Eval eval) throws EvalEx {
+
+ if(eval instanceof RefEval) {
+ return new BaseRef((RefEval)eval);
+ }
+ if(eval instanceof AreaEval) {
+ return new BaseRef((AreaEval)eval);
+ }
+ if (eval instanceof ErrorEval) {
+ throw new EvalEx((ErrorEval) eval);
+ }
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+
+
+ /**
+ * OFFSET's numeric arguments (2..5) have similar processing rules
+ */
+ private static int evaluateIntArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
+
+ double d = evaluateDoubleArg(eval, srcCellRow, srcCellCol);
+ return convertDoubleToInt(d);
+ }
+
+ /**
+ * Fractional values are silently truncated by Excel.
+ * Truncation is toward negative infinity.
+ */
+ /* package */ static int convertDoubleToInt(double d) {
+ // Note - the standard java type conversion from double to int truncates toward zero.
+ // but Math.floor() truncates toward negative infinity
+ return (int)Math.floor(d);
+ }
+
+
+ private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
+ ValueEval ve = evaluateSingleValue(eval, srcCellRow, srcCellCol);
+
+ if (ve instanceof NumericValueEval) {
+ return ((NumericValueEval) ve).getNumberValue();
+ }
+ if (ve instanceof StringEval) {
+ StringEval se = (StringEval) ve;
+ Double d = parseDouble(se.getStringValue());
+ if(d == null) {
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+ return d.doubleValue();
+ }
+ if (ve instanceof BoolEval) {
+ // in the context of OFFSET, booleans resolve to 0 and 1.
+ if(((BoolEval) ve).getBooleanValue()) {
+ return 1;
+ }
+ return 0;
+ }
+ throw new RuntimeException("Unexpected eval type (" + ve.getClass().getName() + ")");
+ }
+
+ private static Double parseDouble(String s) {
+ // TODO - find a home for this method
+ // TODO - support various number formats: sign char, dollars, commas
+ // OFFSET and COUNTIF seem to handle these
+ return Countif.parseDouble(s);
+ }
+
+ private static ValueEval evaluateSingleValue(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
+ if(eval instanceof RefEval) {
+ return ((RefEval)eval).getInnerValueEval();
+ }
+ if(eval instanceof AreaEval) {
+ return chooseSingleElementFromArea((AreaEval)eval, srcCellRow, srcCellCol);
+ }
+ if (eval instanceof ValueEval) {
+ return (ValueEval) eval;
+ }
+ throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
+ }
+
+ // TODO - this code seems to get repeated a bit
+ private static ValueEval chooseSingleElementFromArea(AreaEval ae, int srcCellRow, short srcCellCol) throws EvalEx {
+ if (ae.isColumn()) {
+ if (ae.isRow()) {
+ return ae.getValues()[0];
+ }
+ if (!ae.containsRow(srcCellRow)) {
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+ return ae.getValueAt(srcCellRow, ae.getFirstColumn());
+ }
+ if (!ae.isRow()) {
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+ if (!ae.containsColumn(srcCellCol)) {
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+ return ae.getValueAt(ae.getFirstRow(), srcCellCol);
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java
index ab446c9f2d..13522294fd 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java
@@ -40,6 +40,9 @@ public class Rounddown extends NumericFunction {
break;
case 2:
ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
+ if(ve instanceof ErrorEval) {
+ return ve;
+ }
if (ve instanceof NumericValueEval) {
NumericValueEval ne = (NumericValueEval) ve;
d0 = ne.getNumberValue();
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java
index 3d8cc1ae34..4dae76d981 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java
@@ -40,6 +40,9 @@ public class Roundup extends NumericFunction {
break;
case 2:
ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
+ if(ve instanceof ErrorEval) {
+ return ve;
+ }
if (ve instanceof NumericValueEval) {
NumericValueEval ne = (NumericValueEval) ve;
d0 = ne.getNumberValue();
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java
index 6a4eb8edb7..aabffab2f6 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java
@@ -25,7 +25,7 @@ import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
/**
- * Implementation for Excel COLUMNS function.
+ * Implementation for Excel ROWS function.
*
* @author Josh Micich
*/
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java
index fef7e03464..7995e66c34 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java
@@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
public class Stdev extends MultiOperandNumericFunction {
private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
- new ValueEvalToNumericXlator((short) (0
- // ValueEvalToNumericXlator.BOOL_IS_PARSED
+ new ValueEvalToNumericXlator((short) (
+ ValueEvalToNumericXlator.BOOL_IS_PARSED
//| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
@@ -44,7 +44,6 @@ public class Stdev extends MultiOperandNumericFunction {
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE
//| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
- | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
));
/**
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java
index 12fa5d7bd5..9f6eafa4dc 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java
@@ -14,16 +14,228 @@
* 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.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
/**
- * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
- *
+ * Implementation for the Excel function SUMPRODUCT
+ *
+ * Syntax :
+ * SUMPRODUCT ( array1[, array2[, array3[, ...]]])
+ *
+ *
+ * array1, ... arrayN typically area references,
+ * possibly cell references or scalar values
+ *
+ * Let An(i,j) represent the element in the ith row jth column
+ * of the nth array
+ * Assuming each array has the same dimensions (W, H), the result is defined as:
+ * SUMPRODUCT = Σi: 1..H
+ * ( Σj: 1..W
+ * ( Πn: 1..N
+ * An(i,j)
+ * )
+ * )
+ *
+ * @author Josh Micich
*/
-public class Sumproduct extends NotImplementedFunction {
+public final class Sumproduct implements Function {
+
+ private static final class EvalEx extends Exception {
+ private final ErrorEval _error;
+
+ public EvalEx(ErrorEval error) {
+ _error = error;
+ }
+ public ErrorEval getError() {
+ return _error;
+ }
+ }
+
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+
+ int maxN = args.length;
+
+ if(maxN < 1) {
+ return ErrorEval.VALUE_INVALID;
+ }
+ Eval firstArg = args[0];
+ try {
+ if(firstArg instanceof NumericValueEval) {
+ return evaluateSingleProduct(args);
+ }
+ if(firstArg instanceof RefEval) {
+ return evaluateSingleProduct(args);
+ }
+ if(firstArg instanceof AreaEval) {
+ AreaEval ae = (AreaEval) firstArg;
+ if(ae.isRow() && ae.isColumn()) {
+ return evaluateSingleProduct(args);
+ }
+ return evaluateAreaSumProduct(args);
+ }
+ } catch (EvalEx e) {
+ return e.getError();
+ }
+ throw new RuntimeException("Invalid arg type for SUMPRODUCT: ("
+ + firstArg.getClass().getName() + ")");
+ }
+
+ private Eval evaluateSingleProduct(Eval[] evalArgs) throws EvalEx {
+ int maxN = evalArgs.length;
+
+ double term = 1D;
+ for(int n=0; nValueEval
.
+ * @param isScalarProduct false
for SUMPRODUCTs over area refs.
+ * @throws EvalEx if ve
represents an error value.
+ *
+ * Note - string values and empty cells are interpreted differently depending on
+ * isScalarProduct
. For scalar products, if any term is blank or a string, the
+ * error (#VALUE!) is raised. For area (sum)products, if any term is blank or a string, the
+ * result is zero.
+ */
+ private static double getProductTerm(ValueEval ve, boolean isScalarProduct) throws EvalEx {
+
+ if(ve instanceof BlankEval || ve == null) {
+ // TODO - shouldn't BlankEval.INSTANCE be used always instead of null?
+ // null seems to occur when the blank cell is part of an area ref (but not reliably)
+ if(isScalarProduct) {
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+ return 0;
+ }
+
+ if(ve instanceof ErrorEval) {
+ throw new EvalEx((ErrorEval)ve);
+ }
+ if(ve instanceof StringEval) {
+ if(isScalarProduct) {
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+ // Note for area SUMPRODUCTs, string values are interpreted as zero
+ // even if they would parse as valid numeric values
+ return 0;
+ }
+ if(ve instanceof NumericValueEval) {
+ NumericValueEval nve = (NumericValueEval) ve;
+ return nve.getNumberValue();
+ }
+ throw new RuntimeException("Unexpected value eval class ("
+ + ve.getClass().getName() + ")");
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java
index f4e1959be6..b74b4161ac 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java
@@ -33,18 +33,18 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
public class Sumsq extends MultiOperandNumericFunction {
private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
new ValueEvalToNumericXlator((short) (
- // ValueEvalToNumericXlator.BOOL_IS_PARSED
+ ValueEvalToNumericXlator.BOOL_IS_PARSED
//| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
- //| ValueEvalToNumericXlator.STRING_IS_PARSED
+ | ValueEvalToNumericXlator.STRING_IS_PARSED
//| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
//| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
//| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE
//| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
- ValueEvalToNumericXlator.REF_BLANK_IS_PARSED
- | ValueEvalToNumericXlator.BLANK_IS_PARSED
+ //| ValueEvalToNumericXlator.REF_BLANK_IS_PARSED
+ //| ValueEvalToNumericXlator.BLANK_IS_PARSED
));
protected ValueEvalToNumericXlator getXlator() {
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java
index 8e31224078..30ad5ec230 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java
@@ -14,50 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.functions;
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.Eval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-
/**
+ * Implementation of Excel function SUMX2MY2()
+ *
+ * Calculates the sum of differences of squares in two arrays of the same size.
+ * Syntax:
+ * SUMX2MY2(arrayX, arrayY)
+ *
+ * result = Σi: 0..n(xi2-yi2)
+ *
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
- *
*/
-public class Sumx2my2 extends XYNumericFunction {
+public final class Sumx2my2 extends XYNumericFunction {
-
- public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
- ValueEval retval = null;
- double[][] values = null;
-
- int checkLen = 0; // check to see that all array lengths are equal
- switch (operands.length) {
- default:
- retval = ErrorEval.VALUE_INVALID;
- break;
- case 2:
- values = getValues(operands, srcCellRow, srcCellCol);
- if (values==null
- || values[X] == null || values[Y] == null
- || values[X].length == 0 || values[Y].length == 0
- || values[X].length != values[Y].length) {
- retval = ErrorEval.VALUE_INVALID;
- }
- }
-
- if (retval == null) {
- double d = MathX.sumx2my2(values[X], values[Y]);
- retval = (Double.isNaN(d) || Double.isInfinite(d))
- ? (ValueEval) ErrorEval.NUM_ERROR
- : new NumberEval(d);
- }
-
- return retval;
+ protected double evaluate(double[] xArray, double[] yArray) {
+ return MathX.sumx2my2(xArray, yArray);
}
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java
index deb7675a4e..dfd730d12c 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java
@@ -14,50 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.functions;
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.Eval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-
/**
+ * Implementation of Excel function SUMX2PY2()
+ *
+ * Calculates the sum of squares in two arrays of the same size.
+ * Syntax:
+ * SUMX2PY2(arrayX, arrayY)
+ *
+ * result = Σi: 0..n(xi2+yi2)
+ *
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
- *
*/
-public class Sumx2py2 extends XYNumericFunction {
+public final class Sumx2py2 extends XYNumericFunction {
-
- public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
- ValueEval retval = null;
- double[][] values = null;
-
- int checkLen = 0; // check to see that all array lengths are equal
- switch (operands.length) {
- default:
- retval = ErrorEval.VALUE_INVALID;
- break;
- case 2:
- values = getValues(operands, srcCellRow, srcCellCol);
- if (values==null
- || values[X] == null || values[Y] == null
- || values[X].length == 0 || values[Y].length == 0
- || values[X].length != values[Y].length) {
- retval = ErrorEval.VALUE_INVALID;
- }
- }
-
- if (retval == null) {
- double d = MathX.sumx2py2(values[X], values[Y]);
- retval = (Double.isNaN(d) || Double.isInfinite(d))
- ? (ValueEval) ErrorEval.NUM_ERROR
- : new NumberEval(d);
- }
-
- return retval;
+ protected double evaluate(double[] xArray, double[] yArray) {
+ return MathX.sumx2py2(xArray, yArray);
}
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java
index c62a0b7622..a1b2fec9b2 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java
@@ -14,50 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.functions;
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.Eval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-
/**
+ * Implementation of Excel function SUMXMY2()
+ *
+ * Calculates the sum of squares of differences between two arrays of the same size.
+ * Syntax:
+ * SUMXMY2(arrayX, arrayY)
+ *
+ * result = Σi: 0..n(xi-yi)2
+ *
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
- *
*/
-public class Sumxmy2 extends XYNumericFunction {
+public final class Sumxmy2 extends XYNumericFunction {
-
- public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
- ValueEval retval = null;
- double[][] values = null;
-
- int checkLen = 0; // check to see that all array lengths are equal
- switch (operands.length) {
- default:
- retval = ErrorEval.VALUE_INVALID;
- break;
- case 2:
- values = getValues(operands, srcCellRow, srcCellCol);
- if (values==null
- || values[X] == null || values[Y] == null
- || values[X].length == 0 || values[Y].length == 0
- || values[X].length != values[Y].length) {
- retval = ErrorEval.VALUE_INVALID;
- }
- }
-
- if (retval == null) {
- double d = MathX.sumxmy2(values[X], values[Y]);
- retval = (Double.isNaN(d) || Double.isInfinite(d))
- ? (ValueEval) ErrorEval.NUM_ERROR
- : new NumberEval(d);
- }
-
- return retval;
+ protected double evaluate(double[] xArray, double[] yArray) {
+ return MathX.sumxmy2(xArray, yArray);
}
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java
index 686c40b627..b322cd985c 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java
@@ -22,28 +22,34 @@ 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.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-public class T implements Function {
-
-
+public final class T implements Function {
- public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
- ValueEval retval = null;
- switch (operands.length) {
- default:
- retval = ErrorEval.VALUE_INVALID;
- break;
- case 1:
- if (operands[0] instanceof StringEval
- || operands[0] instanceof ErrorEval) {
- retval = (ValueEval) operands[0];
- }
- else if (operands[0] instanceof ErrorEval) {
- retval = StringEval.EMPTY_INSTANCE;
- }
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+ switch (args.length) {
+ default:
+ return ErrorEval.VALUE_INVALID;
+ case 1:
+ break;
}
- return retval;
+ Eval arg = args[0];
+ if (arg instanceof RefEval) {
+ RefEval re = (RefEval) arg;
+ arg = re.getInnerValueEval();
+ }
+
+ if (arg instanceof StringEval) {
+ // Text values are returned unmodified
+ return arg;
+ }
+
+ if (arg instanceof ErrorEval) {
+ // Error values also returned unmodified
+ return arg;
+ }
+ // for all other argument types the result is empty string
+ return StringEval.EMPTY_INSTANCE;
}
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java
index 5e9d91c7cc..87e29ee34d 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java
@@ -16,12 +16,11 @@
*/
package org.apache.poi.hssf.record.formula.functions;
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.EvaluationException;
+import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.StringValueEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
@@ -30,46 +29,25 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
* value is string.
* @author Manda Wilson < wilson at c bio dot msk cc dot org >
*/
-public class Trim extends TextFunction {
+public final class Trim extends TextFunction {
- /**
- * Removes leading and trailing spaces from value if evaluated
- * operand value is string.
- * Returns StringEval only if evaluated operand is of type string
- * (and is not blank or null) or number. If evaluated operand is
- * of type string and is blank or null, or if evaluated operand is
- * of type blank, returns BlankEval. Otherwise returns ErrorEval.
- *
- * @see org.apache.poi.hssf.record.formula.eval.Eval
- */
- public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
- Eval retval = ErrorEval.VALUE_INVALID;
- String str = null;
-
- switch (operands.length) {
- default:
- break;
- case 1:
- ValueEval veval = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol);
- if (veval instanceof StringValueEval) {
- StringValueEval sve = (StringValueEval) veval;
- str = sve.getStringValue();
- if (str == null || str.trim().equals("")) {
- return BlankEval.INSTANCE;
- }
- }
- else if (veval instanceof NumberEval) {
- NumberEval neval = (NumberEval) veval;
- str = neval.getStringValue();
- }
- else if (veval instanceof BlankEval) {
- return BlankEval.INSTANCE;
- }
- }
-
- if (str != null) {
- retval = new StringEval(str.trim());
- }
- return retval;
- }
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+
+ if(args.length != 1) {
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ try {
+ ValueEval veval = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
+
+ String str = OperandResolver.coerceValueToString(veval);
+ str = str.trim();
+ if(str.length() < 1) {
+ return StringEval.EMPTY_INSTANCE;
+ }
+ return new StringEval(str);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Vlookup.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Vlookup.java
index ad8b88daf9..7d27491df1 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Vlookup.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Vlookup.java
@@ -1,25 +1,123 @@
-/*
-* 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.
-*/
-/*
- * Created on May 15, 2005
- *
- */
+/* ====================================================================
+ 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;
-public class Vlookup extends NotImplementedFunction {
+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.OperandResolver;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
+/**
+ * Implementation of the VLOOKUP() function.
+ *
+ * VLOOKUP finds a row in a lookup table by the first column value and returns the value from another column.
+ *
+ * Syntax:
+ * VLOOKUP(lookup_value, table_array, col_index_num, range_lookup)
+ *
+ * lookup_value The value to be found in the first column of the table array.
+ * table_array> An area reference for the lookup data.
+ * col_index_num a 1 based index specifying which column value of the lookup data will be returned.
+ * range_lookup If TRUE (default), VLOOKUP finds the largest value less than or equal to
+ * the lookup_value. If FALSE, only exact matches will be considered
+ *
+ * @author Josh Micich
+ */
+public final class Vlookup implements Function {
+
+ private static final class ColumnVector implements ValueVector {
+ private final AreaEval _tableArray;
+ private final int _size;
+ private final int _columnAbsoluteIndex;
+ private final int _firstRowAbsoluteIndex;
+
+ public ColumnVector(AreaEval tableArray, int columnIndex) {
+ _columnAbsoluteIndex = tableArray.getFirstColumn() + columnIndex;
+ if(!tableArray.containsColumn((short)_columnAbsoluteIndex)) {
+ int lastColIx = tableArray.getLastColumn() - tableArray.getFirstColumn();
+ throw new IllegalArgumentException("Specified column index (" + columnIndex
+ + ") is outside the allowed range (0.." + lastColIx + ")");
+ }
+ _tableArray = tableArray;
+ _size = tableArray.getLastRow() - tableArray.getFirstRow() + 1;
+ if(_size < 1) {
+ throw new RuntimeException("bad table array size zero");
+ }
+ _firstRowAbsoluteIndex = tableArray.getFirstRow();
+ }
+
+ public ValueEval getItem(int index) {
+ if(index>_size) {
+ throw new ArrayIndexOutOfBoundsException("Specified index (" + index
+ + ") is outside the allowed range (0.." + (_size-1) + ")");
+ }
+ return _tableArray.getValueAt(_firstRowAbsoluteIndex + index, (short)_columnAbsoluteIndex);
+ }
+ public int getSize() {
+ return _size;
+ }
+ }
+
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+ Eval arg3 = null;
+ switch(args.length) {
+ case 4:
+ arg3 = args[3]; // important: assumed array element is never null
+ case 3:
+ break;
+ default:
+ // wrong number of arguments
+ return ErrorEval.VALUE_INVALID;
+ }
+ try {
+ // Evaluation order:
+ // arg0 lookup_value, arg1 table_array, arg3 range_lookup, find lookup value, arg2 col_index, fetch result
+ ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
+ AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]);
+ boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol);
+ int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, new ColumnVector(tableArray, 0), isRangeLookup);
+ ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol);
+ int colIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex);
+ ValueVector resultCol = createResultColumnVector(tableArray, colIndex);
+ return resultCol.getItem(rowIndex);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ }
+
+
+ /**
+ * Returns one column from an AreaEval
+ *
+ * @throws EvaluationException (#VALUE!) if colIndex is negative, (#REF!) if colIndex is too high
+ */
+ private ValueVector createResultColumnVector(AreaEval tableArray, int colIndex) throws EvaluationException {
+ if(colIndex < 0) {
+ throw EvaluationException.invalidValue();
+ }
+ int nCols = tableArray.getLastColumn() - tableArray.getFirstColumn() + 1;
+
+ if(colIndex >= nCols) {
+ throw EvaluationException.invalidRef();
+ }
+ return new ColumnVector(tableArray, colIndex);
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java
index 1e6955ad94..b989c33a26 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java
@@ -14,154 +14,151 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 29, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
/**
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public abstract class XYNumericFunction extends NumericFunction {
+public abstract class XYNumericFunction implements Function {
protected static final int X = 0;
protected static final int Y = 1;
-
- 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
- ));
-
- /**
- * 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;
+ protected static final class DoubleArrayPair {
+
+ private final double[] _xArray;
+ private final double[] _yArray;
+
+ 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
+ */
+ private static final class CellEvaluationFrame {
+
+ private final HSSFWorkbook _workbook;
+ private final HSSFSheet _sheet;
+ private final int _srcRowNum;
+ private final int _srcColNum;
+
+ public CellEvaluationFrame(HSSFWorkbook workbook, HSSFSheet sheet, int srcRowNum, int srcColNum) {
+ if (workbook == null) {
+ throw new IllegalArgumentException("workbook must not be null");
+ }
+ if (sheet == null) {
+ throw new IllegalArgumentException("sheet must not be null");
+ }
+ _workbook = workbook;
+ _sheet = sheet;
+ _srcRowNum = srcRowNum;
+ _srcColNum = srcColNum;
+ }
+
+ public boolean equals(Object obj) {
+ CellEvaluationFrame other = (CellEvaluationFrame) obj;
+ if (_workbook != other._workbook) {
+ return false;
+ }
+ if (_sheet != other._sheet) {
+ return false;
+ }
+ if (_srcRowNum != other._srcRowNum) {
+ return false;
+ }
+ if (_srcColNum != other._srcColNum) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return human readable string for debug purposes
+ */
+ public String formatAsString() {
+ return "R=" + _srcRowNum + " C=" + _srcColNum + " ShIx=" + _workbook.getSheetIndex(_sheet);
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(formatAsString());
+ sb.append("]");
+ return sb.toString();
+ }
+ }
+
+ private final List _evaluationFrames;
+
+ public EvaluationCycleDetector() {
+ _evaluationFrames = new ArrayList();
+ }
+
+ /**
+ * Notifies this evaluation tracker that evaluation of the specified cell is
+ * about to start.
+ *
+ * In the case of a true
return code, the caller should
+ * continue evaluation of the specified cell, and also be sure to call
+ * endEvaluate() when complete.
+ *
+ * In the case of a false
return code, the caller should
+ * return an evaluation result of
+ * ErrorEval.CIRCULAR_REF_ERROR, and not call endEvaluate().
+ *
+ * @return true
if the specified cell has not been visited yet in the current
+ * evaluation. false
if the specified cell is already being evaluated.
+ */
+ public boolean startEvaluate(HSSFWorkbook workbook, HSSFSheet sheet, int srcRowNum, int srcColNum) {
+ CellEvaluationFrame cef = new CellEvaluationFrame(workbook, sheet, srcRowNum, srcColNum);
+ if (_evaluationFrames.contains(cef)) {
+ return false;
+ }
+ _evaluationFrames.add(cef);
+ return true;
+ }
+
+ /**
+ * Notifies this evaluation tracker that the evaluation of the specified
+ * cell is complete.
+ *
+ * Every successful call to startEvaluate must be followed by a
+ * call to endEvaluate (recommended in a finally block) to enable
+ * proper tracking of which cells are being evaluated at any point in time.
+ *
+ * Assuming a well behaved client, parameters to this method would not be
+ * required. However, they have been included to assert correct behaviour,
+ * and form more meaningful error messages.
+ */
+ public void endEvaluate(HSSFWorkbook workbook, HSSFSheet sheet, int srcRowNum, int srcColNum) {
+ int nFrames = _evaluationFrames.size();
+ if (nFrames < 1) {
+ throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate");
+ }
+
+ nFrames--;
+ CellEvaluationFrame cefExpected = (CellEvaluationFrame) _evaluationFrames.get(nFrames);
+ CellEvaluationFrame cefActual = new CellEvaluationFrame(workbook, sheet, srcRowNum, srcColNum);
+ if (!cefActual.equals(cefExpected)) {
+ throw new RuntimeException("Wrong cell specified. "
+ + "Corresponding startEvaluate() call was for cell {"
+ + cefExpected.formatAsString() + "} this endEvaluate() call is for cell {"
+ + cefActual.formatAsString() + "}");
+ }
+ // else - no problems so pop current frame
+ _evaluationFrames.remove(nFrames);
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java b/src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java
new file mode 100755
index 0000000000..a06cd201e2
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hssf/usermodel/EvaluationCycleDetectorManager.java
@@ -0,0 +1,46 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel;
+
+/**
+ * This class makes an EvaluationCycleDetector instance available to
+ * each thread via a ThreadLocal in order to avoid adding a parameter
+ * to a few protected methods within HSSFFormulaEvaluator.
+ *
+ * @author Josh Micich
+ */
+final class EvaluationCycleDetectorManager {
+
+ ThreadLocal tl = null;
+ private static ThreadLocal _tlEvaluationTracker = new ThreadLocal() {
+ protected synchronized Object initialValue() {
+ return new EvaluationCycleDetector();
+ }
+ };
+
+ /**
+ * @return
+ */
+ public static EvaluationCycleDetector getTracker() {
+ return (EvaluationCycleDetector) _tlEvaluationTracker.get();
+ }
+
+ private EvaluationCycleDetectorManager() {
+ // no instances of this class
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
index f60a6adaac..3fce306557 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 5, 2005
- *
- */
+
package org.apache.poi.hssf.usermodel;
import java.lang.reflect.Constructor;
@@ -74,11 +71,13 @@ import org.apache.poi.hssf.record.formula.eval.EqualEval;
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.FuncVarEval;
+import org.apache.poi.hssf.record.formula.eval.FunctionEval;
import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval;
import org.apache.poi.hssf.record.formula.eval.GreaterThanEval;
import org.apache.poi.hssf.record.formula.eval.LessEqualEval;
import org.apache.poi.hssf.record.formula.eval.LessThanEval;
import org.apache.poi.hssf.record.formula.eval.MultiplyEval;
+import org.apache.poi.hssf.record.formula.eval.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;
@@ -91,13 +90,10 @@ import org.apache.poi.hssf.record.formula.eval.SubtractEval;
import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval;
import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.usermodel.HSSFSheet;
/**
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
- * Limitations: Unfortunately, cyclic references will cause stackoverflow
- * exception
*/
public class HSSFFormulaEvaluator {
@@ -173,7 +169,7 @@ public class HSSFFormulaEvaluator {
* formula evaluated.
*/
public static FormulaParser getUnderlyingParser(HSSFWorkbook workbook, String formula) {
- return new FormulaParser(formula, workbook.getWorkbook());
+ return new FormulaParser(formula, workbook.getWorkbook());
}
/**
@@ -286,19 +282,19 @@ public class HSSFFormulaEvaluator {
CellValue cv = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook));
switch (cv.getCellType()) {
case HSSFCell.CELL_TYPE_BOOLEAN:
- cell.setCellType(HSSFCell.CELL_TYPE_BOOLEAN);
+ cell.setCellType(HSSFCell.CELL_TYPE_BOOLEAN);
cell.setCellValue(cv.getBooleanValue());
break;
case HSSFCell.CELL_TYPE_ERROR:
- cell.setCellType(HSSFCell.CELL_TYPE_ERROR);
+ cell.setCellType(HSSFCell.CELL_TYPE_ERROR);
cell.setCellValue(cv.getErrorValue());
break;
case HSSFCell.CELL_TYPE_NUMERIC:
- cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
+ cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
cell.setCellValue(cv.getNumberValue());
break;
case HSSFCell.CELL_TYPE_STRING:
- cell.setCellType(HSSFCell.CELL_TYPE_STRING);
+ cell.setCellType(HSSFCell.CELL_TYPE_STRING);
cell.setCellValue(cv.getRichTextStringValue());
break;
case HSSFCell.CELL_TYPE_BLANK:
@@ -337,6 +333,11 @@ public class HSSFFormulaEvaluator {
else if (eval instanceof BlankEval) {
retval = new CellValue(HSSFCell.CELL_TYPE_BLANK);
}
+ else if (eval instanceof ErrorEval) {
+ retval = new CellValue(HSSFCell.CELL_TYPE_ERROR);
+ retval.setErrorValue((byte)((ErrorEval)eval).getErrorCode());
+// retval.setRichTextStringValue(new HSSFRichTextString("#An error occurred. check cell.getErrorCode()"));
+ }
else {
retval = new CellValue(HSSFCell.CELL_TYPE_ERROR);
}
@@ -348,16 +349,26 @@ public class HSSFFormulaEvaluator {
* Dev. Note: Internal evaluate must be passed only a formula cell
* else a runtime exception will be thrown somewhere inside the method.
* (Hence this is a private method.)
- *
- * @param srcCell
- * @param srcRow
- * @param sheet
- * @param workbook
*/
- protected static ValueEval internalEvaluate(HSSFCell srcCell, HSSFRow srcRow, HSSFSheet sheet, HSSFWorkbook workbook) {
+ private static ValueEval internalEvaluate(HSSFCell srcCell, HSSFRow srcRow, HSSFSheet sheet, HSSFWorkbook workbook) {
int srcRowNum = srcRow.getRowNum();
short srcColNum = srcCell.getCellNum();
- FormulaParser parser = new FormulaParser(srcCell.getCellFormula(), workbook.getWorkbook());
+
+
+ EvaluationCycleDetector tracker = EvaluationCycleDetectorManager.getTracker();
+
+ if(!tracker.startEvaluate(workbook, sheet, srcRowNum, srcColNum)) {
+ return ErrorEval.CIRCULAR_REF_ERROR;
+ }
+ try {
+ return evaluateCell(workbook, sheet, srcRowNum, srcColNum, srcCell.getCellFormula());
+ } finally {
+ tracker.endEvaluate(workbook, sheet, srcRowNum, srcColNum);
+ }
+ }
+ private static ValueEval evaluateCell(HSSFWorkbook workbook, HSSFSheet sheet,
+ int srcRowNum, short srcColNum, String cellFormulaText) {
+ FormulaParser parser = new FormulaParser(cellFormulaText, workbook.getWorkbook());
parser.parse();
Ptg[] ptgs = parser.getRPNPtg();
// -- parsing over --
@@ -366,16 +377,25 @@ public class HSSFFormulaEvaluator {
Stack stack = new Stack();
for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
- // since we dont 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; }
+ // since we don't know how to handle these yet :(
+ 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; }
@@ -392,85 +412,151 @@ public class HSSFFormulaEvaluator {
Eval p = (Eval) stack.pop();
ops[j] = p;
}
- Eval opresult = operation.evaluate(ops, srcRowNum, srcColNum);
+ 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();
- HSSFRow row = sheet.getRow(rownum);
- HSSFCell cell = (row != null) ? row.getCell(colnum) : null;
- pushRef2DEval(ptg, stack, cell, row, sheet, workbook);
+ else if (ptg instanceof ReferencePtg) {
+ ReferencePtg refPtg = (ReferencePtg) ptg;
+ int colIx = refPtg.getColumn();
+ int rowIx = refPtg.getRow();
+ HSSFRow row = sheet.getRow(rowIx);
+ HSSFCell cell = (row != null) ? row.getCell(colIx) : null;
+ 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;
+ int colIx = refPtg.getColumn();
+ int rowIx = refPtg.getRow();
Workbook wb = workbook.getWorkbook();
- HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(ptg.getExternSheetIndex()));
- HSSFRow row = xsheet.getRow(rownum);
- HSSFCell cell = (row != null) ? row.getCell(colnum) : null;
- pushRef3DEval(ptg, stack, cell, row, xsheet, workbook);
+ HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(refPtg.getExternSheetIndex()));
+ HSSFRow row = xsheet.getRow(rowIx);
+ HSSFCell cell = (row != null) ? row.getCell(colIx) : null;
+ stack.push(createRef3DEval(refPtg, cell, row, xsheet, workbook));
}
- else if (ptgs[i] instanceof AreaPtg) {
- AreaPtg ap = (AreaPtg) ptgs[i];
- short row0 = ap.getFirstRow();
- short col0 = ap.getFirstColumn();
- short row1 = ap.getLastRow();
- short col1 = ap.getLastColumn();
- ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
- for (short x = row0; sheet != null && x < row1 + 1; x++) {
- HSSFRow row = sheet.getRow(x);
- for (short y = col0; row != null && y < col1 + 1; y++) {
- values[(x - row0) * (col1 - col0 + 1) + (y - col0)] =
- getEvalForCell(row.getCell(y), row, sheet, workbook);
- }
- }
- AreaEval ae = new Area2DEval(ap, values);
+ 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];
- short row0 = a3dp.getFirstRow();
- short col0 = a3dp.getFirstColumn();
- short row1 = a3dp.getLastRow();
- short col1 = a3dp.getLastColumn();
- Workbook wb = workbook.getWorkbook();
- HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(a3dp.getExternSheetIndex()));
- ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
- for (short x = row0; xsheet != null && x < row1 + 1; x++) {
- HSSFRow row = xsheet.getRow(x);
- for (short y = col0; row != null && y < col1 + 1; y++) {
- values[(x - row0) * (col1 - col0 + 1) + (y - col0)] =
- getEvalForCell(row.getCell(y), row, xsheet, workbook);
- }
- }
- AreaEval ae = new Area3DEval(a3dp, values);
+ 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;
- value = rv.getInnerValueEval();
+ if (!stack.isEmpty()) {
+ throw new IllegalStateException("evaluation stack not empty");
}
- else if (value instanceof AreaEval) {
- AreaEval ae = (AreaEval) value;
- if (ae.isRow())
- value = ae.getValueAt(ae.getFirstRow(), srcColNum);
- else if (ae.isColumn())
- value = ae.getValueAt(srcRowNum, ae.getFirstColumn());
- else
- value = ErrorEval.VALUE_INVALID;
+ value = dereferenceValue(value, srcRowNum, srcColNum);
+ if (value instanceof BlankEval) {
+ // Note Excel behaviour here. A blank final final value is converted to zero.
+ return NumberEval.ZERO;
+ // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to
+ // blank, the actual value is empty string. This can be verified with ISBLANK().
}
return value;
}
+ /**
+ * Dereferences a single value from any AreaEval or RefEval evaluation result.
+ * If the supplied evaluationResult is just a plain value, it is returned as-is.
+ * @return a NumberEval, StringEval, BoolEval,
+ * BlankEval or ErrorEval. Never null
.
+ */
+ private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) {
+ if (evaluationResult instanceof RefEval) {
+ RefEval rv = (RefEval) evaluationResult;
+ return rv.getInnerValueEval();
+ }
+ if (evaluationResult instanceof AreaEval) {
+ AreaEval ae = (AreaEval) evaluationResult;
+ if (ae.isRow()) {
+ if(ae.isColumn()) {
+ return ae.getValues()[0];
+ }
+ return ae.getValueAt(ae.getFirstRow(), srcColNum);
+ }
+ if (ae.isColumn()) {
+ return ae.getValueAt(srcRowNum, ae.getFirstColumn());
+ }
+ return ErrorEval.VALUE_INVALID;
+ }
+ return evaluationResult;
+ }
+
+ private static Eval invokeOperation(OperationEval operation, Eval[] ops, int srcRowNum, short srcColNum,
+ HSSFWorkbook workbook, HSSFSheet sheet) {
+
+ if(operation instanceof FunctionEval) {
+ FunctionEval fe = (FunctionEval) operation;
+ if(fe.isFreeRefFunction()) {
+ return fe.getFreeRefFunction().evaluate(ops, srcRowNum, srcColNum, workbook, sheet);
+ }
+ }
+ return operation.evaluate(ops, srcRowNum, srcColNum);
+ }
+
+ public static AreaEval evaluateAreaPtg(HSSFSheet sheet, HSSFWorkbook workbook, AreaPtg ap) {
+ int row0 = ap.getFirstRow();
+ int col0 = ap.getFirstColumn();
+ int row1 = ap.getLastRow();
+ int col1 = ap.getLastColumn();
+
+ // If the last row is -1, then the
+ // reference is for the rest of the column
+ // (eg C:C)
+ // TODO: Handle whole column ranges properly
+ if(row1 == -1 && row0 >= 0) {
+ row1 = (short)sheet.getLastRowNum();
+ }
+ ValueEval[] values = evalArea(workbook, sheet, row0, col0, row1, col1);
+ return new Area2DEval(ap, values);
+ }
+
+ public static AreaEval evaluateArea3dPtg(HSSFWorkbook workbook, Area3DPtg a3dp) {
+ int row0 = a3dp.getFirstRow();
+ int col0 = a3dp.getFirstColumn();
+ int row1 = a3dp.getLastRow();
+ int col1 = a3dp.getLastColumn();
+ Workbook wb = workbook.getWorkbook();
+ HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(a3dp.getExternSheetIndex()));
+
+ // If the last row is -1, then the
+ // reference is for the rest of the column
+ // (eg C:C)
+ // TODO: Handle whole column ranges properly
+ if(row1 == -1 && row0 >= 0) {
+ row1 = (short)xsheet.getLastRowNum();
+ }
+
+ ValueEval[] values = evalArea(workbook, xsheet, row0, col0, row1, col1);
+ return new Area3DEval(a3dp, values);
+ }
+
+ private static ValueEval[] evalArea(HSSFWorkbook workbook, HSSFSheet sheet,
+ int row0, int col0, int row1, int col1) {
+ ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
+ for (int x = row0; sheet != null && x < row1 + 1; x++) {
+ HSSFRow row = sheet.getRow(x);
+ for (int y = col0; y < col1 + 1; y++) {
+ ValueEval cellEval;
+ if(row == null) {
+ cellEval = BlankEval.INSTANCE;
+ } else {
+ cellEval = getEvalForCell(row.getCell(y), row, sheet, workbook);
+ }
+ values[(x - row0) * (col1 - col0 + 1) + (y - col0)] = cellEval;
+ }
+ }
+ return values;
+ }
+
/**
* returns the OperationEval concrete impl instance corresponding
* to the suplied operationPtg
@@ -544,104 +630,77 @@ public class HSSFFormulaEvaluator {
* @param workbook
*/
protected static ValueEval getEvalForCell(HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
- ValueEval retval = BlankEval.INSTANCE;
- if (cell != null) {
- switch (cell.getCellType()) {
- case HSSFCell.CELL_TYPE_NUMERIC:
- retval = new NumberEval(cell.getNumericCellValue());
- break;
- case HSSFCell.CELL_TYPE_STRING:
- retval = new StringEval(cell.getRichStringCellValue().getString());
- break;
- case HSSFCell.CELL_TYPE_FORMULA:
- retval = internalEvaluate(cell, row, sheet, workbook);
- break;
- case HSSFCell.CELL_TYPE_BOOLEAN:
- retval = cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE;
- break;
- case HSSFCell.CELL_TYPE_BLANK:
- retval = BlankEval.INSTANCE;
- break;
- case HSSFCell.CELL_TYPE_ERROR:
- retval = ErrorEval.UNKNOWN_ERROR; // TODO: think about this...
- break;
- }
+
+ if (cell == null) {
+ return BlankEval.INSTANCE;
}
- return retval;
+ switch (cell.getCellType()) {
+ case HSSFCell.CELL_TYPE_NUMERIC:
+ return new NumberEval(cell.getNumericCellValue());
+ case HSSFCell.CELL_TYPE_STRING:
+ return new StringEval(cell.getRichStringCellValue().getString());
+ case HSSFCell.CELL_TYPE_FORMULA:
+ return internalEvaluate(cell, row, sheet, workbook);
+ case HSSFCell.CELL_TYPE_BOOLEAN:
+ return BoolEval.valueOf(cell.getBooleanCellValue());
+ case HSSFCell.CELL_TYPE_BLANK:
+ return BlankEval.INSTANCE;
+ case HSSFCell.CELL_TYPE_ERROR:
+ return ErrorEval.valueOf(cell.getErrorCellValue());
+ }
+ throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
/**
- * create a Ref2DEval for ReferencePtg and push it on the stack.
+ * Creates a Ref2DEval for ReferencePtg.
* Non existent cells are treated as RefEvals containing BlankEval.
- * @param ptg
- * @param stack
- * @param cell
- * @param sheet
- * @param workbook
*/
- protected static void pushRef2DEval(ReferencePtg ptg, Stack stack,
- HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
- if (cell != null)
- switch (cell.getCellType()) {
- case HSSFCell.CELL_TYPE_NUMERIC:
- stack.push(new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()), false));
- break;
- case HSSFCell.CELL_TYPE_STRING:
- stack.push(new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false));
- break;
- case HSSFCell.CELL_TYPE_FORMULA:
- stack.push(new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true));
- break;
- case HSSFCell.CELL_TYPE_BOOLEAN:
- stack.push(new Ref2DEval(ptg, cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE, false));
- break;
- case HSSFCell.CELL_TYPE_BLANK:
- stack.push(new Ref2DEval(ptg, BlankEval.INSTANCE, false));
- break;
- case HSSFCell.CELL_TYPE_ERROR:
- stack.push(new Ref2DEval(ptg, ErrorEval.UNKNOWN_ERROR, false)); // TODO: think abt this
- break;
- }
- else {
- stack.push(new Ref2DEval(ptg, BlankEval.INSTANCE, false));
+ private static Ref2DEval createRef2DEval(ReferencePtg ptg, HSSFCell cell,
+ HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
+ if (cell == null) {
+ return new Ref2DEval(ptg, BlankEval.INSTANCE);
}
+
+ switch (cell.getCellType()) {
+ case HSSFCell.CELL_TYPE_NUMERIC:
+ return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()));
+ case HSSFCell.CELL_TYPE_STRING:
+ return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()));
+ case HSSFCell.CELL_TYPE_FORMULA:
+ return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook));
+ case HSSFCell.CELL_TYPE_BOOLEAN:
+ return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()));
+ case HSSFCell.CELL_TYPE_BLANK:
+ return new Ref2DEval(ptg, BlankEval.INSTANCE);
+ case HSSFCell.CELL_TYPE_ERROR:
+ return new Ref2DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()));
+ }
+ throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
/**
- * create a Ref3DEval for Ref3DPtg and push it on the stack.
- *
- * @param ptg
- * @param stack
- * @param cell
- * @param sheet
- * @param workbook
+ * create a Ref3DEval for Ref3DPtg.
*/
- protected static void pushRef3DEval(Ref3DPtg ptg, Stack stack, HSSFCell cell,
+ private static Ref3DEval createRef3DEval(Ref3DPtg ptg, HSSFCell cell,
HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
- if (cell != null)
- switch (cell.getCellType()) {
- case HSSFCell.CELL_TYPE_NUMERIC:
- stack.push(new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()), false));
- break;
- case HSSFCell.CELL_TYPE_STRING:
- stack.push(new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false));
- break;
- case HSSFCell.CELL_TYPE_FORMULA:
- stack.push(new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true));
- break;
- case HSSFCell.CELL_TYPE_BOOLEAN:
- stack.push(new Ref3DEval(ptg, cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE, false));
- break;
- case HSSFCell.CELL_TYPE_BLANK:
- stack.push(new Ref3DEval(ptg, BlankEval.INSTANCE, false));
- break;
- case HSSFCell.CELL_TYPE_ERROR:
- stack.push(new Ref3DEval(ptg, ErrorEval.UNKNOWN_ERROR, false)); // TODO: think abt this
- break;
- }
- else {
- stack.push(new Ref3DEval(ptg, BlankEval.INSTANCE, false));
+ if (cell == null) {
+ return new Ref3DEval(ptg, BlankEval.INSTANCE);
}
+ switch (cell.getCellType()) {
+ case HSSFCell.CELL_TYPE_NUMERIC:
+ return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()));
+ case HSSFCell.CELL_TYPE_STRING:
+ return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()));
+ case HSSFCell.CELL_TYPE_FORMULA:
+ return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook));
+ case HSSFCell.CELL_TYPE_BOOLEAN:
+ return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()));
+ case HSSFCell.CELL_TYPE_BLANK:
+ return new Ref3DEval(ptg, BlankEval.INSTANCE);
+ case HSSFCell.CELL_TYPE_ERROR:
+ return new Ref3DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()));
+ }
+ throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
/**
@@ -726,15 +785,15 @@ public class HSSFFormulaEvaluator {
/**
* @return Returns the richTextStringValue.
*/
- public HSSFRichTextString getRichTextStringValue() {
- return richTextStringValue;
- }
+ public HSSFRichTextString getRichTextStringValue() {
+ return richTextStringValue;
+ }
/**
* @param richTextStringValue The richTextStringValue to set.
*/
- public void setRichTextStringValue(HSSFRichTextString richTextStringValue) {
- this.richTextStringValue = richTextStringValue;
- }
+ public void setRichTextStringValue(HSSFRichTextString richTextStringValue) {
+ this.richTextStringValue = richTextStringValue;
+ }
}
/**
diff --git a/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk1.vsd b/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk1.vsd
new file mode 100755
index 0000000000..2c1632ebae
Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk1.vsd differ
diff --git a/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk2.vsd b/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk2.vsd
new file mode 100755
index 0000000000..7d9a3cefc6
Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk2.vsd differ
diff --git a/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk3.vsd b/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk3.vsd
new file mode 100755
index 0000000000..c8bd7a190d
Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hdgf/data/ShortChunk3.vsd differ
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-bad.xls b/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-bad.xls
deleted file mode 100644
index 54a7edb404..0000000000
Binary files a/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-bad.xls and /dev/null differ
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-ok.xls b/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-ok.xls
deleted file mode 100644
index 5ae84bc90e..0000000000
Binary files a/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-ok.xls and /dev/null differ
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls b/src/scratchpad/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls
deleted file mode 100644
index cf4b6fa501..0000000000
Binary files a/src/scratchpad/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls and /dev/null differ
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java b/src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java
index 9c2cb2b87f..a038a964ee 100644
--- a/src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java
@@ -16,14 +16,14 @@
==================================================================== */
package org.apache.poi.hssf.eventusermodel;
-import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
-import org.apache.poi.hssf.eventusermodel.HSSFListener;
-
import java.io.File;
import java.io.FileInputStream;
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
-import org.apache.poi.hssf.eventusermodel.HSSFRequest;
import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord;
import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord;
import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingRowDummyRecord;
@@ -31,31 +31,33 @@ import org.apache.poi.hssf.record.LabelSSTRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RowRecord;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
-
-import junit.framework.TestCase;
-
-public class TestMissingRecordAwareHSSFListener extends TestCase {
- private String dirname;
+/**
+ * Tests for MissingRecordAwareHSSFListener
+ */
+public final class TestMissingRecordAwareHSSFListener extends TestCase {
- public TestMissingRecordAwareHSSFListener() {
- dirname = System.getProperty("HSSF.testdata.path");
- }
-
- public void testMissingRowRecords() throws Exception {
+ private Record[] r;
+
+ public void setUp() {
+ String dirname = System.getProperty("HSSF.testdata.path");
File f = new File(dirname + "/MissingBits.xls");
-
+
HSSFRequest req = new HSSFRequest();
MockHSSFListener mockListen = new MockHSSFListener();
MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen);
req.addListenerForAllRecords(listener);
- POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f));
HSSFEventFactory factory = new HSSFEventFactory();
- factory.processWorkbookEvents(req, fs);
-
- // Check we got the dummy records
- Record[] r = (Record[])
- mockListen.records.toArray(new Record[mockListen.records.size()]);
+ try {
+ POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f));
+ factory.processWorkbookEvents(req, fs);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ r = mockListen.getRecords();
+ }
+
+ public void testMissingRowRecords() throws Exception {
// We have rows 0, 1, 2, 20 and 21
int row0 = -1;
@@ -105,20 +107,6 @@ public class TestMissingRecordAwareHSSFListener extends TestCase {
}
public void testEndOfRowRecords() throws Exception {
- File f = new File(dirname + "/MissingBits.xls");
-
- HSSFRequest req = new HSSFRequest();
- MockHSSFListener mockListen = new MockHSSFListener();
- MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen);
- req.addListenerForAllRecords(listener);
-
- POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f));
- HSSFEventFactory factory = new HSSFEventFactory();
- factory.processWorkbookEvents(req, fs);
-
- // Check we got the dummy records
- Record[] r = (Record[])
- mockListen.records.toArray(new Record[mockListen.records.size()]);
// Find the cell at 0,0
int cell00 = -1;
@@ -240,20 +228,6 @@ public class TestMissingRecordAwareHSSFListener extends TestCase {
public void testMissingCellRecords() throws Exception {
- File f = new File(dirname + "/MissingBits.xls");
-
- HSSFRequest req = new HSSFRequest();
- MockHSSFListener mockListen = new MockHSSFListener();
- MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen);
- req.addListenerForAllRecords(listener);
-
- POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f));
- HSSFEventFactory factory = new HSSFEventFactory();
- factory.processWorkbookEvents(req, fs);
-
- // Check we got the dummy records
- Record[] r = (Record[])
- mockListen.records.toArray(new Record[mockListen.records.size()]);
// Find the cell at 0,0
int cell00 = -1;
@@ -352,25 +326,35 @@ public class TestMissingRecordAwareHSSFListener extends TestCase {
assertEquals(10, mc.getColumn());
}
- private static class MockHSSFListener implements HSSFListener {
- private MockHSSFListener() {}
- private ArrayList records = new ArrayList();
+ private static final class MockHSSFListener implements HSSFListener {
+ public MockHSSFListener() {}
+ private final List _records = new ArrayList();
public void processRecord(Record record) {
- records.add(record);
+ _records.add(record);
if(record instanceof MissingRowDummyRecord) {
MissingRowDummyRecord mr = (MissingRowDummyRecord)record;
- System.out.println("Got dummy row " + mr.getRowNumber());
+ log("Got dummy row " + mr.getRowNumber());
}
if(record instanceof MissingCellDummyRecord) {
MissingCellDummyRecord mc = (MissingCellDummyRecord)record;
- System.out.println("Got dummy cell " + mc.getRow() + " " + mc.getColumn());
+ log("Got dummy cell " + mc.getRow() + " " + mc.getColumn());
}
if(record instanceof LastCellOfRowDummyRecord) {
LastCellOfRowDummyRecord lc = (LastCellOfRowDummyRecord)record;
- System.out.println("Got end-of row, row was " + lc.getRow() + ", last column was " + lc.getLastColumnNumber());
+ log("Got end-of row, row was " + lc.getRow() + ", last column was " + lc.getLastColumnNumber());
}
}
+ private static void log(String msg) {
+ if(false) { // successful tests should be quiet
+ System.out.println(msg);
+ }
+ }
+ public Record[] getRecords() {
+ Record[] result = new Record[_records.size()];
+ _records.toArray(result);
+ return result;
+ }
}
}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/model/TestFormulaParserSP.java b/src/scratchpad/testcases/org/apache/poi/hssf/model/TestFormulaParserSP.java
index aa73714a0f..0141e1b2a4 100644
--- a/src/scratchpad/testcases/org/apache/poi/hssf/model/TestFormulaParserSP.java
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/model/TestFormulaParserSP.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -15,30 +14,30 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
+
package org.apache.poi.hssf.model;
import junit.framework.TestCase;
+import org.apache.poi.hssf.model.FormulaParser.FormulaParseException;
import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.usermodel.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;
/**
* Test the low level formula parser functionality,
* but using parts which need to use the
* HSSFFormulaEvaluator, which is in scratchpad
*/
-public class TestFormulaParserSP extends TestCase {
+public final class TestFormulaParserSP extends TestCase {
- public TestFormulaParserSP(String name) {
- super(name);
- }
-
public void testWithNamedRange() throws Exception {
HSSFWorkbook workbook = new HSSFWorkbook();
FormulaParser fp;
@@ -80,4 +79,32 @@ public class TestFormulaParserSP extends TestCase {
assertEquals(FuncVarPtg.class, ptgs[1].getClass());
}
+ public void testEvaluateFormulaWithRowBeyond32768_Bug44539() {
+
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet();
+ wb.setSheetName(0, "Sheet1");
+
+ HSSFRow row = sheet.createRow(0);
+ HSSFCell cell = row.createCell((short)0);
+ cell.setCellFormula("SUM(A32769:A32770)");
+
+ // put some values in the cells to make the evaluation more interesting
+ sheet.createRow(32768).createCell((short)0).setCellValue(31);
+ sheet.createRow(32769).createCell((short)0).setCellValue(11);
+
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb);
+ fe.setCurrentRow(row);
+ CellValue result;
+ try {
+ result = fe.evaluate(cell);
+ } catch (FormulaParseException e) {
+ if(e.getMessage().equals("Found reference to named range \"A\", but that named range wasn't defined!")) {
+ fail("Identifed bug 44539");
+ }
+ throw new RuntimeException(e);
+ }
+ assertEquals(HSSFCell.CELL_TYPE_NUMERIC, result.getCellType());
+ assertEquals(42.0, result.getNumberValue(), 0.0);
+ }
}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java
new file mode 100755
index 0000000000..3260c371c6
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java
@@ -0,0 +1,38 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.eval;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Collects all tests the package org.apache.poi.hssf.record.formula.eval.
+ *
+ * @author Josh Micich
+ */
+public class AllFormulaEvalTests {
+
+ public static Test suite() {
+ TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.eval");
+ result.addTestSuite(TestCircularReferences.class);
+ result.addTestSuite(TestExternalFunction.class);
+ result.addTestSuite(TestFormulasFromSpreadsheet.class);
+ result.addTestSuite(TestUnaryPlusEval.class);
+ return result;
+ }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/GenericFormulaTestCase.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/GenericFormulaTestCase.java
deleted file mode 100644
index b53e8ebc3a..0000000000
--- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/GenericFormulaTestCase.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one or more
-* contributor license agreements. See the NOTICE file distributed with
-* this work for additional information regarding copyright ownership.
-* The ASF licenses this file to You under the Apache License, Version 2.0
-* (the "License"); you may not use this file except in compliance with
-* the License. You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-/*
- * Created on May 11, 2005
- *
- */
-package org.apache.poi.hssf.record.formula.eval;
-
-import java.io.FileInputStream;
-
-import junit.framework.AssertionFailedError;
-import junit.framework.TestCase;
-
-import org.apache.poi.hssf.record.formula.functions.TestMathX;
-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.util.CellReference;
-
-/**
- * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
- *
- */
-public class GenericFormulaTestCase extends TestCase {
-
- protected final static String FILENAME = System.getProperty("HSSF.testdata.path")+ "/FormulaEvalTestData.xls";
-
- protected static HSSFWorkbook workbook = null;
-
- protected CellReference beginCell;
- protected int getBeginRow() {
- return beginCell.getRow();
- }
-
- protected short getBeginCol() {
- return beginCell.getCol();
- }
-
- protected final HSSFCell getExpectedValueCell(HSSFSheet sheet, HSSFRow row, HSSFCell cell) {
- HSSFCell retval = null;
- if (sheet != null) {
- row = sheet.getRow(row.getRowNum()+1);
- if (row != null) {
- retval = row.getCell(cell.getCellNum());
- }
- }
-
- return retval;
- }
-
- protected void assertEquals(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) {
- if (expected != null && actual!=null) {
- if (expected!=null && expected.getCellType() == HSSFCell.CELL_TYPE_STRING) {
- String value = expected.getRichStringCellValue().getString();
- if (value.startsWith("#")) {
- expected.setCellType(HSSFCell.CELL_TYPE_ERROR);
- }
- }
- if (!(expected == null || actual == null)) {
- switch (expected.getCellType()) {
- case HSSFCell.CELL_TYPE_BLANK:
- assertEquals(msg, HSSFCell.CELL_TYPE_BLANK, actual.getCellType());
- break;
- case HSSFCell.CELL_TYPE_BOOLEAN:
- assertEquals(msg, HSSFCell.CELL_TYPE_BOOLEAN, actual.getCellType());
- assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue());
- break;
- case HSSFCell.CELL_TYPE_ERROR:
- assertEquals(msg, HSSFCell.CELL_TYPE_ERROR, actual.getCellType()); // TODO: check if exact error matches
- break;
- case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation
- throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg);
- case HSSFCell.CELL_TYPE_NUMERIC:
- assertEquals(msg, HSSFCell.CELL_TYPE_NUMERIC, actual.getCellType());
- TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR);
-// double delta = Math.abs(expected.getNumericCellValue()-actual.getNumberValue());
-// double pctExpected = Math.abs(0.00001*expected.getNumericCellValue());
-// assertTrue(msg, delta <= pctExpected);
- break;
- case HSSFCell.CELL_TYPE_STRING:
- assertEquals(msg, HSSFCell.CELL_TYPE_STRING, actual.getCellType());
- assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString());
- break;
- }
- }
- else {
- throw new AssertionFailedError("expected: " + expected + " got:" + actual);
- }
- }
- }
-
- public GenericFormulaTestCase(String beginCell) throws Exception {
- super("genericTest");
- if (workbook == null) {
- FileInputStream fin = new FileInputStream( FILENAME );
- workbook = new HSSFWorkbook( fin );
- fin.close();
- }
- this.beginCell = new CellReference(beginCell);
- }
-
- public void setUp() {
- }
-
- public void genericTest() throws Exception {
- HSSFSheet s = workbook.getSheetAt( 0 );
- HSSFRow r = s.getRow(getBeginRow());
- short endcolnum = r.getLastCellNum();
- HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(s, workbook);
- evaluator.setCurrentRow(r);
-
- HSSFCell c = null;
- for (short colnum=getBeginCol(); colnum < endcolnum; colnum++) {
- try {
- c = r.getCell(colnum);
- if (c==null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA)
- continue;
-
- HSSFFormulaEvaluator.CellValue actualValue = evaluator.evaluate(c);
-
- HSSFCell expectedValueCell = getExpectedValueCell(s, r, c);
- assertEquals("Formula: " + c.getCellFormula()
- + " @ " + getBeginRow() + ":" + colnum,
- expectedValueCell, actualValue);
- } catch (RuntimeException re) {
- throw new RuntimeException("CELL["+getBeginRow()+","+colnum+"]: "+re.getMessage(), re);
- }
- }
- }
-
-}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java
new file mode 100755
index 0000000000..72db658f77
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestCircularReferences.java
@@ -0,0 +1,125 @@
+/* ====================================================================
+ 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.AssertionFailedError;
+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 HSSFFormulaEvaluator for its handling of cell formula circular references.
+ *
+ * @author Josh Micich
+ */
+public final class TestCircularReferences extends TestCase {
+ /**
+ * Translates StackOverflowError into AssertionFailedError
+ */
+ private static CellValue evaluateWithCycles(HSSFWorkbook wb, HSSFSheet sheet, HSSFRow row, HSSFCell testCell)
+ throws AssertionFailedError {
+ HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, wb);
+ evaluator.setCurrentRow(row);
+ try {
+ return evaluator.evaluate(testCell);
+ } catch (StackOverflowError e) {
+ throw new AssertionFailedError( "circular reference caused stack overflow error");
+ }
+ }
+ /**
+ * Makes sure that the specified evaluated cell value represents a circular reference error.
+ */
+ private static void confirmCycleErrorCode(CellValue cellValue) {
+ assertTrue(cellValue.getCellType() == HSSFCell.CELL_TYPE_ERROR);
+ assertEquals(ErrorEval.CIRCULAR_REF_ERROR.getErrorCode(), cellValue.getErrorValue());
+ }
+
+
+ /**
+ * ASF Bugzilla Bug 44413
+ * "INDEX() formula cannot contain its own location in the data array range"
+ */
+ public void testIndexFormula() {
+
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("Sheet1");
+
+ short colB = 1;
+ sheet.createRow(0).createCell(colB).setCellValue(1);
+ sheet.createRow(1).createCell(colB).setCellValue(2);
+ sheet.createRow(2).createCell(colB).setCellValue(3);
+ HSSFRow row4 = sheet.createRow(3);
+ HSSFCell testCell = row4.createCell((short)0);
+ // This formula should evaluate to the contents of B2,
+ testCell.setCellFormula("INDEX(A1:B4,2,2)");
+ // However the range A1:B4 also includes the current cell A4. If the other parameters
+ // were 4 and 1, this would represent a circular reference. Since POI 'fully' evaluates
+ // arguments before invoking operators, POI must handle such potential cycles gracefully.
+
+
+ CellValue cellValue = evaluateWithCycles(wb, sheet, row4, testCell);
+
+ assertTrue(cellValue.getCellType() == HSSFCell.CELL_TYPE_NUMERIC);
+ assertEquals(2, cellValue.getNumberValue(), 0);
+ }
+
+ /**
+ * Cell A1 has formula "=A1"
+ */
+ public void testSimpleCircularReference() {
+
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("Sheet1");
+
+ HSSFRow row = sheet.createRow(0);
+ HSSFCell testCell = row.createCell((short)0);
+ testCell.setCellFormula("A1");
+
+ HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, wb);
+ evaluator.setCurrentRow(row);
+ CellValue cellValue = evaluateWithCycles(wb, sheet, row, testCell);
+
+ confirmCycleErrorCode(cellValue);
+ }
+
+ /**
+ * A1=B1, B1=C1, C1=D1, D1=A1
+ */
+ public void testMultiLevelCircularReference() {
+
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("Sheet1");
+
+ HSSFRow row = sheet.createRow(0);
+ row.createCell((short)0).setCellFormula("B1");
+ row.createCell((short)1).setCellFormula("C1");
+ row.createCell((short)2).setCellFormula("D1");
+ HSSFCell testCell = row.createCell((short)3);
+ testCell.setCellFormula("A1");
+
+ HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, wb);
+ evaluator.setCurrentRow(row);
+ CellValue cellValue = evaluateWithCycles(wb, sheet, row, testCell);
+
+ confirmCycleErrorCode(cellValue);
+ }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestEverything.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestEverything.java
deleted file mode 100644
index ac3ca2eb29..0000000000
--- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestEverything.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one or more
-* contributor license agreements. See the NOTICE file distributed with
-* this work for additional information regarding copyright ownership.
-* The ASF licenses this file to You under the Apache License, Version 2.0
-* (the "License"); you may not use this file except in compliance with
-* the License. You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-/*
- * Created on May 11, 2005
- *
- */
-package org.apache.poi.hssf.record.formula.eval;
-
-import junit.framework.TestSuite;
-
-/**
- * This is a test of all the Eval functions we have implemented.
- * Add newly implemented Eval functions in here to have them
- * tested.
- * For newly implemented functions,
- * @see org.apache.poi.hssf.record.formula.functions.TestEverything
- *
- * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
- */
-public class TestEverything extends TestSuite {
-
- public static TestSuite suite() throws Exception {
- TestSuite suite = new TestSuite("Tests for OperationEval concrete implementation classes.");
- suite.addTest(new GenericFormulaTestCase("D23")); // Add
- suite.addTest(new GenericFormulaTestCase("D27")); // ConcatEval
- suite.addTest(new GenericFormulaTestCase("D31")); // DivideEval
- suite.addTest(new GenericFormulaTestCase("D35")); // EqualEval
- suite.addTest(new GenericFormulaTestCase("D39")); // GreaterEqualEval
- suite.addTest(new GenericFormulaTestCase("D43")); // GreaterThanEval
- suite.addTest(new GenericFormulaTestCase("D47")); // LessEqualEval
- suite.addTest(new GenericFormulaTestCase("D51")); // LessThanEval
- suite.addTest(new GenericFormulaTestCase("D55")); // MultiplyEval
- suite.addTest(new GenericFormulaTestCase("D59")); // NotEqualEval
- suite.addTest(new GenericFormulaTestCase("D63")); // PowerEval
- suite.addTest(new GenericFormulaTestCase("D67")); // SubtractEval
- suite.addTest(new GenericFormulaTestCase("D71")); // UnaryMinusEval
- suite.addTest(new GenericFormulaTestCase("D75")); // UnaryPlusEval
-
- // Add newly implemented Eval functions here
- // (Formula functions go in
- // @see org.apache.poi.hssf.record.formula.functions.TestEverything )
-
- return suite;
- }
-}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java
new file mode 100755
index 0000000000..27e3338652
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java
@@ -0,0 +1,61 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.eval;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFName;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue;
+/**
+ *
+ * @author Josh Micich
+ */
+public final class TestExternalFunction extends TestCase {
+
+ /**
+ * Checks that an external function can get invoked from the formula evaluator.
+ */
+ public void testInvoke() {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet();
+ wb.setSheetName(0, "Sheet1");
+ HSSFRow row = sheet.createRow(0);
+ HSSFCell cell = row.createCell((short)0);
+
+ HSSFName hssfName = wb.createName();
+ hssfName.setNameName("myFunc");
+
+ cell.setCellFormula("myFunc()");
+ String actualFormula=cell.getCellFormula();
+ assertEquals("myFunc()", actualFormula);
+
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb);
+ fe.setCurrentRow(row);
+ CellValue evalResult = fe.evaluate(cell);
+
+ // Check the return value from ExternalFunction.evaluate()
+ // TODO - make this test assert something more interesting as soon as ExternalFunction works a bit better
+ assertEquals(HSSFCell.CELL_TYPE_ERROR, evalResult.getCellType());
+ assertEquals(ErrorEval.FUNCTION_NOT_IMPLEMENTED.getErrorCode(), evalResult.getErrorValue());
+ }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java
new file mode 100644
index 0000000000..f57221c9b0
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java
@@ -0,0 +1,329 @@
+/*
+* 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 java.io.FileInputStream;
+import java.io.PrintStream;
+
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.functions.TestMathX;
+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;
+
+/**
+ * Tests formulas and operators as loaded from a test data spreadsheet.
+ * This class does not test implementors of Function and OperationEval in
+ * isolation. Much of the evaluation engine (i.e. HSSFFormulaEvaluator, ...) gets
+ * exercised as well. Tests for bug fixes and specific/tricky behaviour can be found in the
+ * corresponding test class (TestXxxx) of the target (Xxxx) implementor,
+ * where execution can be observed more easily.
+ *
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ */
+public final class TestFormulasFromSpreadsheet extends TestCase {
+
+ private static final class Result {
+ public static final int SOME_EVALUATIONS_FAILED = -1;
+ public static final int ALL_EVALUATIONS_SUCCEEDED = +1;
+ public static final int NO_EVALUATIONS_FOUND = 0;
+ }
+
+ /**
+ * This class defines constants for navigating around the test data spreadsheet used for these tests.
+ */
+ private static final class SS {
+
+ /**
+ * Name of the test spreadsheet (found in the standard test data folder)
+ */
+ public final static String FILENAME = "FormulaEvalTestData.xls";
+ /**
+ * Row (zero-based) in the test spreadsheet where the operator examples start.
+ */
+ public static final int START_OPERATORS_ROW_INDEX = 22; // Row '23'
+ /**
+ * Row (zero-based) in the test spreadsheet where the function examples start.
+ */
+ public static final int START_FUNCTIONS_ROW_INDEX = 83; // Row '84'
+ /**
+ * Index of the column that contains the function names
+ */
+ public static final short COLUMN_INDEX_FUNCTION_NAME = 1; // Column 'B'
+
+ /**
+ * Used to indicate when there are no more functions left
+ */
+ public static final String FUNCTION_NAMES_END_SENTINEL = "null
to test all functions
+ */
+ private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) {
+
+ HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, workbook);
+
+ int rowIndex = startRowIndex;
+ while (true) {
+ HSSFRow r = sheet.getRow(rowIndex);
+ String targetFunctionName = getTargetFunctionName(r);
+ if(targetFunctionName == null) {
+ throw new AssertionFailedError("Test spreadsheet cell empty on row ("
+ + (rowIndex+1) + "). Expected function name or '"
+ + SS.FUNCTION_NAMES_END_SENTINEL + "'");
+ }
+ if(targetFunctionName.equals(SS.FUNCTION_NAMES_END_SENTINEL)) {
+ // found end of functions list
+ break;
+ }
+ if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) {
+
+ // expected results are on the row below
+ HSSFRow expectedValuesRow = sheet.getRow(rowIndex + 1);
+ if(expectedValuesRow == null) {
+ int missingRowNum = rowIndex + 2; //+1 for 1-based, +1 for next row
+ throw new AssertionFailedError("Missing expected values row for function '"
+ + targetFunctionName + " (row " + missingRowNum + ")");
+ }
+ switch(processFunctionRow(evaluator, targetFunctionName, r, expectedValuesRow)) {
+ case Result.ALL_EVALUATIONS_SUCCEEDED: _functionSuccessCount++; break;
+ case Result.SOME_EVALUATIONS_FAILED: _functionFailureCount++; break;
+ default:
+ throw new RuntimeException("unexpected result");
+ case Result.NO_EVALUATIONS_FOUND: // do nothing
+ }
+ }
+ rowIndex += SS.NUMBER_OF_ROWS_PER_FUNCTION;
+ }
+ }
+
+ /**
+ *
+ * @return a constant from the local Result class denoting whether there were any evaluation
+ * cases, and whether they all succeeded.
+ */
+ private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName,
+ HSSFRow formulasRow, HSSFRow expectedValuesRow) {
+
+ int result = Result.NO_EVALUATIONS_FOUND; // so far
+ short endcolnum = formulasRow.getLastCellNum();
+ evaluator.setCurrentRow(formulasRow);
+
+ // iterate across the row for all the evaluation cases
+ for (short colnum=SS.COLUMN_INDEX_FIRST_TEST_VALUE; colnum < endcolnum; colnum++) {
+ HSSFCell c = formulasRow.getCell(colnum);
+ if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
+ continue;
+ }
+
+ HSSFFormulaEvaluator.CellValue actualValue = evaluator.evaluate(c);
+
+ HSSFCell expectedValueCell = getExpectedValueCell(expectedValuesRow, colnum);
+ try {
+ confirmExpectedResult("Function '" + targetFunctionName + "': Formula: " + c.getCellFormula() + " @ " + formulasRow.getRowNum() + ":" + colnum,
+ expectedValueCell, actualValue);
+ _evaluationSuccessCount ++;
+ if(result != Result.SOME_EVALUATIONS_FAILED) {
+ result = Result.ALL_EVALUATIONS_SUCCEEDED;
+ }
+ } catch (AssertionFailedError e) {
+ _evaluationFailureCount ++;
+ printShortStackTrace(System.err, e);
+ result = Result.SOME_EVALUATIONS_FAILED;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Useful to keep output concise when expecting many failures to be reported by this test case
+ */
+ private static void printShortStackTrace(PrintStream ps, AssertionFailedError e) {
+ StackTraceElement[] stes = e.getStackTrace();
+
+ int startIx = 0;
+ // skip any top frames inside junit.framework.Assert
+ while(startIx
+ * The code for handling column operands had been copy-pasted from the row handling code.
+ */
+ public void testColumnOperand() {
+
+ short firstRow = (short)8;
+ short lastRow = (short)12;
+ short colNum = (short)5;
+ AreaPtg areaPtg = new AreaPtg(firstRow, lastRow, colNum, colNum, false, false, false, false);
+ ValueEval[] values = {
+ new NumberEval(27),
+ new NumberEval(29),
+ new NumberEval(35), // value in row 10
+ new NumberEval(37),
+ new NumberEval(38),
+ };
+ Eval areaEval = new Area2DEval(areaPtg, values);
+ Eval[] args = {
+ areaEval,
+ };
+
+ double result = NumericFunctionInvoker.invoke(new UnaryPlusEval(new UnaryPlusPtg()), args, 10, (short)20);
+
+ assertEquals(35, result, 0);
+ }
+
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
index b5e0843671..d3e9c4c412 100755
--- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
@@ -31,13 +31,23 @@ public final class AllIndividualFunctionEvaluationTests {
// TODO - have this suite incorporated into a higher level one
public static Test suite() {
TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.functions");
+ result.addTestSuite(TestAverage.class);
result.addTestSuite(TestCountFuncs.class);
result.addTestSuite(TestDate.class);
result.addTestSuite(TestFinanceLib.class);
result.addTestSuite(TestIndex.class);
+ result.addTestSuite(TestIsBlank.class);
+ result.addTestSuite(TestLen.class);
+ result.addTestSuite(TestMid.class);
result.addTestSuite(TestMathX.class);
+ result.addTestSuite(TestMatch.class);
+ result.addTestSuite(TestOffset.class);
result.addTestSuite(TestRowCol.class);
+ result.addTestSuite(TestSumproduct.class);
result.addTestSuite(TestStatsLib.class);
+ result.addTestSuite(TestTFunc.class);
+ result.addTestSuite(TestTrim.class);
+ result.addTestSuite(TestXYNumericFunction.class);
return result;
}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java
index 958c486649..a6e262b868 100755
--- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java
@@ -58,6 +58,6 @@ final class EvalFactory {
* Creates a single RefEval (with value zero)
*/
public static RefEval createRefEval(String refStr) {
- return new Ref2DEval(new ReferencePtg(refStr), ZERO, true);
+ return new Ref2DEval(new ReferencePtg(refStr), ZERO);
}
}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java
index 87405a4918..d477231349 100755
--- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java
@@ -23,13 +23,14 @@ import junit.framework.AssertionFailedError;
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.NumericValueEval;
+import org.apache.poi.hssf.record.formula.eval.OperationEval;
/**
* Test helper class for invoking functions with numeric results.
*
* @author Josh Micich
*/
-final class NumericFunctionInvoker {
+public final class NumericFunctionInvoker {
private NumericFunctionInvoker() {
// no instances of this class
@@ -59,13 +60,37 @@ final class NumericFunctionInvoker {
+ ") failed: " + e.getMessage());
}
+ }
+ /**
+ * Invokes the specified operator with the arguments.
+ *
+ * This method cannot be used for confirming error return codes. Any non-numeric evaluation
+ * result causes the current junit test to fail.
+ */
+ public static double invoke(OperationEval f, Eval[] args, int srcCellRow, int srcCellCol) {
+ try {
+ return invokeInternal(f, args, srcCellRow, srcCellCol);
+ } catch (NumericEvalEx e) {
+ throw new AssertionFailedError("Evaluation of function (" + f.getClass().getName()
+ + ") failed: " + e.getMessage());
+ }
+
}
/**
* Formats nicer error messages for the junit output
*/
- private static double invokeInternal(Function f, Eval[] args, int srcCellRow, int srcCellCol)
+ private static double invokeInternal(Object target, Eval[] args, int srcCellRow, int srcCellCol)
throws NumericEvalEx {
- Eval evalResult = f.evaluate(args, srcCellRow, (short)srcCellCol);
+ Eval evalResult;
+ // TODO - make OperationEval extend Function
+ if (target instanceof Function) {
+ Function ff = (Function) target;
+ evalResult = ff.evaluate(args, srcCellRow, (short)srcCellCol);
+ } else {
+ OperationEval ff = (OperationEval) target;
+ evalResult = ff.evaluate(args, srcCellRow, (short)srcCellCol);
+ }
+
if(evalResult == null) {
throw new NumericEvalEx("Result object was null");
}
@@ -86,8 +111,8 @@ final class NumericFunctionInvoker {
if(errorCodesAreEqual(ee, ErrorEval.FUNCTION_NOT_IMPLEMENTED)) {
return "Function not implemented";
}
- if(errorCodesAreEqual(ee, ErrorEval.UNKNOWN_ERROR)) {
- return "Unknown error";
+ if(errorCodesAreEqual(ee, ErrorEval.VALUE_INVALID)) {
+ return "Error code: #VALUE! (invalid value)";
}
return "Error code=" + ee.getErrorCode();
}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java
new file mode 100755
index 0000000000..4f0e5fff2c
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java
@@ -0,0 +1,103 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.BoolEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+/**
+ * Tests for Excel function AVERAGE()
+ *
+ * @author Josh Micich
+ */
+public final class TestAverage extends TestCase {
+
+
+ private static Eval invokeAverage(Eval[] args) {
+ return new Average().evaluate(args, -1, (short)-1);
+ }
+
+ private void confirmAverage(Eval[] args, double expected) {
+ Eval result = invokeAverage(args);
+ assertEquals(NumberEval.class, result.getClass());
+ assertEquals(expected, ((NumberEval)result).getNumberValue(), 0);
+ }
+
+ private void confirmAverage(Eval[] args, ErrorEval expectedError) {
+ Eval result = invokeAverage(args);
+ assertEquals(ErrorEval.class, result.getClass());
+ assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode());
+ }
+
+ public void testBasic() {
+
+ ValueEval[] values = {
+ new NumberEval(1),
+ new NumberEval(2),
+ new NumberEval(3),
+ new NumberEval(4),
+ };
+
+ confirmAverage(values, 2.5);
+
+ values = new ValueEval[] {
+ new NumberEval(1),
+ new NumberEval(2),
+ BlankEval.INSTANCE,
+ new NumberEval(3),
+ BlankEval.INSTANCE,
+ new NumberEval(4),
+ BlankEval.INSTANCE,
+ };
+
+ confirmAverage(values, 2.5);
+ }
+
+ /**
+ * Valid cases where values are not pure numbers
+ */
+ public void testUnusualArgs() {
+ ValueEval[] values = {
+ new NumberEval(1),
+ new NumberEval(2),
+ BoolEval.TRUE,
+ BoolEval.FALSE,
+ };
+
+ confirmAverage(values, 1.0);
+
+ }
+
+ // currently disabled because MultiOperandNumericFunction.getNumberArray(Eval[], int, short)
+ // does not handle error values properly yet
+ public void XtestErrors() {
+ ValueEval[] values = {
+ new NumberEval(1),
+ ErrorEval.NAME_INVALID,
+ new NumberEval(3),
+ ErrorEval.DIV_ZERO,
+ };
+ confirmAverage(values, ErrorEval.NAME_INVALID);
+
+ }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java
index fbaace9210..ae93a2d41b 100755
--- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java
@@ -125,7 +125,7 @@ public final class TestCountFuncs extends TestCase {
};
Area2DEval arg0 = new Area2DEval(new AreaPtg("C1:C6"), values);
- Ref2DEval criteriaArg = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(25), true);
+ Ref2DEval criteriaArg = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(25));
Eval[] args= { arg0, criteriaArg, };
double actual = NumericFunctionInvoker.invoke(new Countif(), args);
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestEverything.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestEverything.java
deleted file mode 100644
index 8337810216..0000000000
--- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestEverything.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one or more
-* contributor license agreements. See the NOTICE file distributed with
-* this work for additional information regarding copyright ownership.
-* The ASF licenses this file to You under the Apache License, Version 2.0
-* (the "License"); you may not use this file except in compliance with
-* the License. You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-/*
- * Created on May 11, 2005
- *
- */
-package org.apache.poi.hssf.record.formula.functions;
-
-import org.apache.poi.hssf.record.formula.eval.GenericFormulaTestCase;
-
-import junit.framework.TestSuite;
-
-/**
- * This is a test of all the normal formula functions we have implemented.
- * It should pick up newly implemented functions which are correctly added
- * to the test formula excel file, but tweak the rows below if you
- * add any past the end of what's currently checked.
- * For newly implemented eval functions,
- * @see org.apache.poi.hssf.record.formula.eval.TestEverything
- *
- * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
- */
-public class TestEverything extends TestSuite {
- public static TestSuite suite() throws Exception {
- TestSuite suite = new TestSuite("Tests for individual function classes");
- String s;
- for(int i=80; i<1485;i=i+4) {
- s = "D"+Integer.toString(i).trim();
- suite.addTest(new GenericFormulaTestCase(s));
- }
-// suite.addTest(new GenericFormulaTestCase("D1164"));
- return suite;
- }
-}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java
new file mode 100755
index 0000000000..7ce2bd245b
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java
@@ -0,0 +1,62 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue;
+/**
+ * Tests for Excel function ISBLANK()
+ *
+ * @author Josh Micich
+ */
+public final class TestIsBlank extends TestCase {
+
+
+
+ public void test3DArea() {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet1 = wb.createSheet();
+ wb.setSheetName(0, "Sheet1");
+ wb.createSheet();
+ wb.setSheetName(1, "Sheet2");
+ HSSFRow row = sheet1.createRow(0);
+ HSSFCell cell = row.createCell((short)0);
+
+
+ cell.setCellFormula("isblank(Sheet2!A1:A1)");
+
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet1, wb);
+ fe.setCurrentRow(row);
+ CellValue result = fe.evaluate(cell);
+ assertEquals(HSSFCell.CELL_TYPE_BOOLEAN, result.getCellType());
+ assertEquals(true, result.getBooleanValue());
+
+ cell.setCellFormula("isblank(D7:D7)");
+
+ result = fe.evaluate(cell);
+ assertEquals(HSSFCell.CELL_TYPE_BOOLEAN, result.getCellType());
+ assertEquals(true, result.getBooleanValue());
+
+ }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java
new file mode 100755
index 0000000000..a96fb4e2b0
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java
@@ -0,0 +1,73 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.BoolEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+/**
+ * Tests for Excel function LEN()
+ *
+ * @author Josh Micich
+ */
+public final class TestLen extends TestCase {
+
+
+ private static Eval invokeLen(Eval text) {
+ Eval[] args = new Eval[] { text, };
+ return new Len().evaluate(args, -1, (short)-1);
+ }
+
+ private void confirmLen(Eval text, int expected) {
+ Eval result = invokeLen(text);
+ assertEquals(NumberEval.class, result.getClass());
+ assertEquals(expected, ((NumberEval)result).getNumberValue(), 0);
+ }
+
+ private void confirmLen(Eval text, ErrorEval expectedError) {
+ Eval result = invokeLen(text);
+ assertEquals(ErrorEval.class, result.getClass());
+ assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode());
+ }
+
+ public void testBasic() {
+
+ confirmLen(new StringEval("galactic"), 8);
+ }
+
+ /**
+ * Valid cases where text arg is not exactly a string
+ */
+ public void testUnusualArgs() {
+
+ // text (first) arg type is number, other args are strings with fractional digits
+ confirmLen(new NumberEval(123456), 6);
+ confirmLen(BoolEval.FALSE, 5);
+ confirmLen(BoolEval.TRUE, 4);
+ confirmLen(BlankEval.INSTANCE, 0);
+ }
+
+ public void testErrors() {
+ confirmLen(ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID);
+ }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java
new file mode 100644
index 0000000000..071ca0f7d8
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java
@@ -0,0 +1,385 @@
+/*
+* 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 java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+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;
+import org.apache.poi.hssf.util.CellReference;
+
+/**
+ * Tests lookup functions (VLOOKUP, HLOOKUP, LOOKUP, MATCH) as loaded from a test data spreadsheet.
+ * These tests have been separated from the common function and operator tests because the lookup
+ * functions have more complex test cases and test data setup.
+ *
+ * Tests for bug fixes and specific/tricky behaviour can be found in the corresponding test class
+ * (TestXxxx) of the target (Xxxx) implementor, where execution can be observed
+ * more easily.
+ *
+ * @author Josh Micich
+ */
+public final class TestLookupFunctionsFromSpreadsheet extends TestCase {
+
+ private static final class Result {
+ public static final int SOME_EVALUATIONS_FAILED = -1;
+ public static final int ALL_EVALUATIONS_SUCCEEDED = +1;
+ public static final int NO_EVALUATIONS_FOUND = 0;
+ }
+
+ /**
+ * This class defines constants for navigating around the test data spreadsheet used for these tests.
+ */
+ private static final class SS {
+
+ /** Name of the test spreadsheet (found in the standard test data folder) */
+ public final static String FILENAME = "LookupFunctionsTestCaseData.xls";
+
+ /** Name of the first sheet in the spreadsheet (contains comments) */
+ public final static String README_SHEET_NAME = "Read Me";
+
+
+ /** Row (zero-based) in each sheet where the evaluation cases start. */
+ public static final int START_TEST_CASES_ROW_INDEX = 4; // Row '5'
+ /** Index of the column that contains the function names */
+ public static final short COLUMN_INDEX_MARKER = 0; // Column 'A'
+ public static final short COLUMN_INDEX_EVALUATION = 1; // Column 'B'
+ public static final short COLUMN_INDEX_EXPECTED_RESULT = 2; // Column 'C'
+ public static final short COLUMN_ROW_COMMENT = 3; // Column 'D'
+
+ /** Used to indicate when there are no more test cases on the current sheet */
+ public static final String TEST_CASES_END_MARKER = "new Area2DEval(new AreaPtg(ref), values)
+ */
+ private static AreaEval createAreaEval(String ref, ValueEval[] values) {
+ return new Area2DEval(new AreaPtg(ref), values);
+ }
+
+ public void testSimpleNumber() {
+
+ ValueEval[] values = {
+ new NumberEval(4),
+ new NumberEval(5),
+ new NumberEval(10),
+ new NumberEval(10),
+ new NumberEval(25),
+ };
+
+ AreaEval ae = createAreaEval("A1:A5", values);
+
+ confirmInt(2, invokeMatch(new NumberEval(5), ae, MATCH_LARGEST_LTE));
+ confirmInt(2, invokeMatch(new NumberEval(5), ae, MATCH_EXACT));
+ confirmInt(4, invokeMatch(new NumberEval(10), ae, MATCH_LARGEST_LTE));
+ confirmInt(3, invokeMatch(new NumberEval(10), ae, MATCH_EXACT));
+ confirmInt(4, invokeMatch(new NumberEval(20), ae, MATCH_LARGEST_LTE));
+ assertEquals(ErrorEval.NA, invokeMatch(new NumberEval(20), ae, MATCH_EXACT));
+ }
+
+ public void testReversedNumber() {
+
+ ValueEval[] values = {
+ new NumberEval(25),
+ new NumberEval(10),
+ new NumberEval(10),
+ new NumberEval(10),
+ new NumberEval(4),
+ };
+
+ AreaEval ae = createAreaEval("A1:A5", values);
+
+ confirmInt(2, invokeMatch(new NumberEval(10), ae, MATCH_SMALLEST_GTE));
+ confirmInt(2, invokeMatch(new NumberEval(10), ae, MATCH_EXACT));
+ confirmInt(4, invokeMatch(new NumberEval(9), ae, MATCH_SMALLEST_GTE));
+ confirmInt(1, invokeMatch(new NumberEval(20), ae, MATCH_SMALLEST_GTE));
+ assertEquals(ErrorEval.NA, invokeMatch(new NumberEval(20), ae, MATCH_EXACT));
+ assertEquals(ErrorEval.NA, invokeMatch(new NumberEval(26), ae, MATCH_SMALLEST_GTE));
+ }
+
+ public void testSimpleString() {
+
+ ValueEval[] values = {
+ new StringEval("Albert"),
+ new StringEval("Charles"),
+ new StringEval("Ed"),
+ new StringEval("Greg"),
+ new StringEval("Ian"),
+ };
+
+ AreaEval ae = createAreaEval("A1:A5", values);
+
+ // Note String comparisons are case insensitive
+ confirmInt(3, invokeMatch(new StringEval("Ed"), ae, MATCH_LARGEST_LTE));
+ confirmInt(3, invokeMatch(new StringEval("eD"), ae, MATCH_LARGEST_LTE));
+ confirmInt(3, invokeMatch(new StringEval("Ed"), ae, MATCH_EXACT));
+ confirmInt(3, invokeMatch(new StringEval("ed"), ae, MATCH_EXACT));
+ confirmInt(4, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE));
+ assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Hugh"), ae, MATCH_EXACT));
+ }
+
+ public void testSimpleBoolean() {
+
+ ValueEval[] values = {
+ BoolEval.FALSE,
+ BoolEval.FALSE,
+ BoolEval.TRUE,
+ BoolEval.TRUE,
+ };
+
+ AreaEval ae = createAreaEval("A1:A4", values);
+
+ // Note String comparisons are case insensitive
+ confirmInt(2, invokeMatch(BoolEval.FALSE, ae, MATCH_LARGEST_LTE));
+ confirmInt(1, invokeMatch(BoolEval.FALSE, ae, MATCH_EXACT));
+ confirmInt(4, invokeMatch(BoolEval.TRUE, ae, MATCH_LARGEST_LTE));
+ confirmInt(3, invokeMatch(BoolEval.TRUE, ae, MATCH_EXACT));
+ }
+
+ public void testHeterogeneous() {
+
+ ValueEval[] values = {
+ new NumberEval(4),
+ BoolEval.FALSE,
+ new NumberEval(5),
+ new StringEval("Albert"),
+ BoolEval.FALSE,
+ BoolEval.TRUE,
+ new NumberEval(10),
+ new StringEval("Charles"),
+ new StringEval("Ed"),
+ new NumberEval(10),
+ new NumberEval(25),
+ BoolEval.TRUE,
+ new StringEval("Ed"),
+ };
+
+ AreaEval ae = createAreaEval("A1:A13", values);
+
+ assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Aaron"), ae, MATCH_LARGEST_LTE));
+
+ confirmInt(5, invokeMatch(BoolEval.FALSE, ae, MATCH_LARGEST_LTE));
+ confirmInt(2, invokeMatch(BoolEval.FALSE, ae, MATCH_EXACT));
+ confirmInt(3, invokeMatch(new NumberEval(5), ae, MATCH_LARGEST_LTE));
+ confirmInt(3, invokeMatch(new NumberEval(5), ae, MATCH_EXACT));
+
+ confirmInt(8, invokeMatch(new StringEval("CHARLES"), ae, MATCH_EXACT));
+
+ confirmInt(4, invokeMatch(new StringEval("Ben"), ae, MATCH_LARGEST_LTE));
+
+ confirmInt(13, invokeMatch(new StringEval("ED"), ae, MATCH_LARGEST_LTE));
+ confirmInt(9, invokeMatch(new StringEval("ED"), ae, MATCH_EXACT));
+
+ confirmInt(13, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE));
+ assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Hugh"), ae, MATCH_EXACT));
+
+ confirmInt(11, invokeMatch(new NumberEval(30), ae, MATCH_LARGEST_LTE));
+ confirmInt(12, invokeMatch(BoolEval.TRUE, ae, MATCH_LARGEST_LTE));
+ }
+
+
+ /**
+ * Ensures that the match_type argument can be an AreaEval.
+ * Bugzilla 44421
+ */
+ public void testMatchArgTypeArea() {
+
+ ValueEval[] values = {
+ new NumberEval(4),
+ new NumberEval(5),
+ new NumberEval(10),
+ new NumberEval(10),
+ new NumberEval(25),
+ };
+
+ AreaEval ae = createAreaEval("A1:A5", values);
+
+ AreaEval matchAE = createAreaEval("C1:C1", new ValueEval[] { MATCH_LARGEST_LTE, });
+
+ try {
+ confirmInt(4, invokeMatch(new NumberEval(10), ae, matchAE));
+ } catch (RuntimeException e) {
+ if(e.getMessage().startsWith("Unexpected match_type type")) {
+ // identified bug 44421
+ fail(e.getMessage());
+ }
+ // some other error ??
+ throw e;
+ }
+ }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java
new file mode 100755
index 0000000000..dc3d595aed
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java
@@ -0,0 +1,115 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.ReferencePtg;
+import org.apache.poi.hssf.record.formula.eval.Area2DEval;
+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.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+import junit.framework.TestCase;
+/**
+ * Tests for Excel function MID()
+ *
+ * @author Josh Micich
+ */
+public final class TestMid extends TestCase {
+
+
+ private static Eval invokeMid(Eval text, Eval startPos, Eval numChars) {
+ Eval[] args = new Eval[] { text, startPos, numChars, };
+ return new Mid().evaluate(args, -1, (short)-1);
+ }
+
+ private void confirmMid(Eval text, Eval startPos, Eval numChars, String expected) {
+ Eval result = invokeMid(text, startPos, numChars);
+ assertEquals(StringEval.class, result.getClass());
+ assertEquals(expected, ((StringEval)result).getStringValue());
+ }
+
+ private void confirmMid(Eval text, Eval startPos, Eval numChars, ErrorEval expectedError) {
+ Eval result = invokeMid(text, startPos, numChars);
+ assertEquals(ErrorEval.class, result.getClass());
+ assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode());
+ }
+
+ public void testBasic() {
+
+ confirmMid(new StringEval("galactic"), new NumberEval(3), new NumberEval(4), "lact");
+ }
+
+ /**
+ * Valid cases where args are not precisely (string, int, int) but can be resolved OK.
+ */
+ public void testUnusualArgs() {
+ // startPos with fractional digits
+ confirmMid(new StringEval("galactic"), new NumberEval(3.1), new NumberEval(4), "lact");
+
+ // string startPos
+ confirmMid(new StringEval("galactic"), new StringEval("3"), new NumberEval(4), "lact");
+
+ // text (first) arg type is number, other args are strings with fractional digits
+ confirmMid(new NumberEval(123456), new StringEval("3.1"), new StringEval("2.9"), "34");
+
+ // 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));
+ 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, "");
+
+ }
+
+ /**
+ * Extreme values for startPos and numChars
+ */
+ public void testExtremes() {
+ confirmMid(new StringEval("galactic"), new NumberEval(4), new NumberEval(400), "actic");
+
+ confirmMid(new StringEval("galactic"), new NumberEval(30), new NumberEval(4), "");
+ confirmMid(new StringEval("galactic"), new NumberEval(3), new NumberEval(0), "");
+ }
+
+ /**
+ * All sorts of ways to make MID return defined errors.
+ */
+ public void testErrors() {
+ confirmMid(ErrorEval.NAME_INVALID, new NumberEval(3), new NumberEval(4), ErrorEval.NAME_INVALID);
+ confirmMid(new StringEval("galactic"), ErrorEval.NAME_INVALID, new NumberEval(4), ErrorEval.NAME_INVALID);
+ confirmMid(new StringEval("galactic"), new NumberEval(3), ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID);
+ confirmMid(new StringEval("galactic"), ErrorEval.DIV_ZERO, ErrorEval.NAME_INVALID, ErrorEval.DIV_ZERO);
+
+ confirmMid(new StringEval("galactic"), BlankEval.INSTANCE, new NumberEval(3.1), ErrorEval.VALUE_INVALID);
+
+ confirmMid(new StringEval("galactic"), new NumberEval(0), new NumberEval(4), ErrorEval.VALUE_INVALID);
+ confirmMid(new StringEval("galactic"), new NumberEval(1), new NumberEval(-1), ErrorEval.VALUE_INVALID);
+ }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java
new file mode 100755
index 0000000000..f114662985
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java
@@ -0,0 +1,92 @@
+/* ====================================================================
+ 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.functions.Offset.LinearOffsetRange;
+
+/**
+ * Tests for OFFSET function implementation
+ *
+ * @author Josh Micich
+ */
+public final class TestOffset extends TestCase {
+
+
+ private static void confirmDoubleConvert(double doubleVal, int expected) {
+ assertEquals(expected, Offset.convertDoubleToInt(doubleVal));
+ }
+ /**
+ * Excel's double to int conversion (for function 'OFFSET()') behaves more like Math.floor().
+ * Note - negative values are not symmetrical
+ */
+ public void testDoubleConversion() {
+
+ confirmDoubleConvert(100.09, 100);
+ confirmDoubleConvert(100.01, 100);
+ confirmDoubleConvert(100.00, 100);
+ confirmDoubleConvert(99.99, 99);
+
+ confirmDoubleConvert(+2.01, +2);
+ confirmDoubleConvert(+2.00, +2);
+ confirmDoubleConvert(+1.99, +1);
+ confirmDoubleConvert(+1.01, +1);
+ confirmDoubleConvert(+1.00, +1);
+ confirmDoubleConvert(+0.99, 0);
+ confirmDoubleConvert(+0.01, 0);
+ confirmDoubleConvert( 0.00, 0);
+ confirmDoubleConvert(-0.01, -1);
+ confirmDoubleConvert(-0.99, -1);
+ confirmDoubleConvert(-1.00, -1);
+ confirmDoubleConvert(-1.01, -2);
+ confirmDoubleConvert(-1.99, -2);
+ confirmDoubleConvert(-2.00, -2);
+ confirmDoubleConvert(-2.01, -3);
+ }
+
+ public void testLinearOffsetRange() {
+ LinearOffsetRange lor;
+
+ lor = new LinearOffsetRange(3, 2);
+ assertEquals(3, lor.getFirstIndex());
+ assertEquals(4, lor.getLastIndex());
+ lor = lor.normaliseAndTranslate(0); // expected no change
+ assertEquals(3, lor.getFirstIndex());
+ assertEquals(4, lor.getLastIndex());
+
+ lor = lor.normaliseAndTranslate(5);
+ assertEquals(8, lor.getFirstIndex());
+ assertEquals(9, lor.getLastIndex());
+
+ // negative length
+
+ lor = new LinearOffsetRange(6, -4).normaliseAndTranslate(0);
+ assertEquals(3, lor.getFirstIndex());
+ assertEquals(6, lor.getLastIndex());
+
+
+ // bounds checking
+ lor = new LinearOffsetRange(0, 100);
+ assertFalse(lor.isOutOfBounds(0, 16383));
+ lor = lor.normaliseAndTranslate(16300);
+ assertTrue(lor.isOutOfBounds(0, 16383));
+ assertFalse(lor.isOutOfBounds(0, 65535));
+ }
+
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRoundFuncs.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRoundFuncs.java
new file mode 100755
index 0000000000..a6ce345aeb
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRoundFuncs.java
@@ -0,0 +1,49 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for ROUND(), ROUNDUP(), ROUNDDOWN()
+ *
+ * @author Josh Micich
+ */
+public final class TestRoundFuncs extends TestCase {
+ public void testRounddownWithStringArg() {
+
+ Eval strArg = new StringEval("abc");
+ Eval[] args = { strArg, new NumberEval(2), };
+ Eval result = new Rounddown().evaluate(args, -1, (short)-1);
+ assertEquals(ErrorEval.VALUE_INVALID, result);
+ }
+
+ public void testRoundupWithStringArg() {
+
+ Eval strArg = new StringEval("abc");
+ Eval[] args = { strArg, new NumberEval(2), };
+ Eval result = new Roundup().evaluate(args, -1, (short)-1);
+ assertEquals(ErrorEval.VALUE_INVALID, result);
+ }
+
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java
new file mode 100755
index 0000000000..73043911f4
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java
@@ -0,0 +1,120 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import org.apache.poi.hssf.record.formula.ReferencePtg;
+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.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
+import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for SUMPRODUCT()
+ *
+ * @author Josh Micich
+ */
+public final class TestSumproduct extends TestCase {
+
+ private static Eval invokeSumproduct(Eval[] args) {
+ // srcCellRow and srcCellColumn are ignored by SUMPRODUCT
+ return new Sumproduct().evaluate(args, -1, (short)-1);
+ }
+ private static void confirmDouble(double expected, Eval actualEval) {
+ if(!(actualEval instanceof NumericValueEval)) {
+ fail("Expected numeric result");
+ }
+ NumericValueEval nve = (NumericValueEval)actualEval;
+ assertEquals(expected, nve.getNumberValue(), 0);
+ }
+
+ public void testScalarSimple() {
+
+ RefEval refEval = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(3));
+ Eval[] args = {
+ refEval,
+ new NumberEval(2),
+ };
+ Eval result = invokeSumproduct(args);
+ confirmDouble(6D, result);
+ }
+
+
+ public void testAreaSimple() {
+
+ AreaEval aeA = EvalFactory.createAreaEval("A1:A3", 1, 3);
+ AreaEval aeB = EvalFactory.createAreaEval("B1:B3", 1, 3);
+ ValueEval[] aValues = aeA.getValues();
+ ValueEval[] bValues = aeB.getValues();
+ aValues[0] = new NumberEval(2);
+ aValues[1] = new NumberEval(4);
+ aValues[2] = new NumberEval(5);
+ bValues[0] = new NumberEval(3);
+ bValues[1] = new NumberEval(6);
+ bValues[2] = new NumberEval(7);
+
+ Eval[] args = { aeA, aeB, };
+ Eval result = invokeSumproduct(args);
+ confirmDouble(65D, result);
+ }
+
+ /**
+ * For scalar products, the terms may be 1x1 area refs
+ */
+ public void testOneByOneArea() {
+
+ AreaEval ae = EvalFactory.createAreaEval("A1:A1", 1, 1);
+ ae.getValues()[0] = new NumberEval(7);
+
+ Eval[] args = {
+ ae,
+ new NumberEval(2),
+ };
+ Eval result = invokeSumproduct(args);
+ confirmDouble(14D, result);
+ }
+
+
+ public void testMismatchAreaDimensions() {
+
+ AreaEval aeA = EvalFactory.createAreaEval("A1:A3", 1, 3);
+ AreaEval aeB = EvalFactory.createAreaEval("B1:D1", 3, 1);
+
+ Eval[] args;
+ args = new Eval[] { aeA, aeB, };
+ assertEquals(ErrorEval.VALUE_INVALID, invokeSumproduct(args));
+
+ args = new Eval[] { aeA, new NumberEval(5), };
+ assertEquals(ErrorEval.VALUE_INVALID, invokeSumproduct(args));
+ }
+
+ public void testAreaWithErrorCell() {
+ AreaEval aeA = EvalFactory.createAreaEval("A1:A2", 1, 2);
+ AreaEval aeB = EvalFactory.createAreaEval("B1:B2", 1, 2);
+ aeB.getValues()[1] = ErrorEval.REF_INVALID;
+
+ Eval[] args = { aeA, aeB, };
+ assertEquals(ErrorEval.REF_INVALID, invokeSumproduct(args));
+ }
+
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java
new file mode 100755
index 0000000000..4d63cad1c5
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java
@@ -0,0 +1,118 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import org.apache.poi.hssf.record.formula.ReferencePtg;
+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.Ref2DEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for Excel function T()
+ *
+ * @author Josh Micich
+ */
+public final class TestTFunc extends TestCase {
+
+ /**
+ * @return the result of calling function T() with the specified argument
+ */
+ private static Eval invokeT(Eval arg) {
+ Eval[] args = { arg, };
+ Eval result = new T().evaluate(args, -1, (short)-1);
+ assertNotNull("result may never be null", result);
+ return result;
+ }
+ /**
+ * Simulates call: T(A1)
+ * 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);
+ return invokeT(arg);
+ }
+
+ private static void confirmText(String text) {
+ Eval arg = new StringEval(text);
+ Eval eval = invokeT(arg);
+ StringEval se = (StringEval) eval;
+ assertEquals(text, se.getStringValue());
+ }
+
+ public void testTextValues() {
+
+ confirmText("abc");
+ confirmText("");
+ confirmText(" ");
+ confirmText("~");
+ confirmText("123");
+ confirmText("TRUE");
+ }
+
+ private static void confirmError(Eval arg) {
+ Eval eval = invokeT(arg);
+ assertTrue(arg == eval);
+ }
+
+ public void testErrorValues() {
+
+ confirmError(ErrorEval.VALUE_INVALID);
+ confirmError(ErrorEval.NA);
+ confirmError(ErrorEval.REF_INVALID);
+ }
+
+ private static void confirmString(Eval eval, String expected) {
+ assertTrue(eval instanceof StringEval);
+ assertEquals(expected, ((StringEval)eval).getStringValue());
+ }
+
+ private static void confirmOther(Eval arg) {
+ Eval eval = invokeT(arg);
+ confirmString(eval, "");
+ }
+
+ public void testOtherValues() {
+ confirmOther(new NumberEval(2));
+ confirmOther(BoolEval.FALSE);
+ confirmOther(BlankEval.INSTANCE); // can this particular case be verified?
+ }
+
+ public void testRefValues() {
+ Eval eval;
+
+ eval = invokeTWithReference(new StringEval("def"));
+ confirmString(eval, "def");
+ eval = invokeTWithReference(new StringEval(" "));
+ confirmString(eval, " ");
+
+ eval = invokeTWithReference(new NumberEval(2));
+ confirmString(eval, "");
+ eval = invokeTWithReference(BoolEval.TRUE);
+ confirmString(eval, "");
+
+ eval = invokeTWithReference(ErrorEval.NAME_INVALID);
+ assertTrue(eval == ErrorEval.NAME_INVALID);
+ }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java
new file mode 100755
index 0000000000..076ac1fc7e
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java
@@ -0,0 +1,78 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.BoolEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+/**
+ * Tests for Excel function TRIM()
+ *
+ * @author Josh Micich
+ */
+public final class TestTrim extends TestCase {
+
+
+ private static Eval invokeTrim(Eval text) {
+ Eval[] args = new Eval[] { text, };
+ return new Trim().evaluate(args, -1, (short)-1);
+ }
+
+ private void confirmTrim(Eval text, String expected) {
+ Eval result = invokeTrim(text);
+ assertEquals(StringEval.class, result.getClass());
+ assertEquals(expected, ((StringEval)result).getStringValue());
+ }
+
+ private void confirmTrim(Eval text, ErrorEval expectedError) {
+ Eval result = invokeTrim(text);
+ assertEquals(ErrorEval.class, result.getClass());
+ assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode());
+ }
+
+ public void testBasic() {
+
+ confirmTrim(new StringEval(" hi "), "hi");
+ confirmTrim(new StringEval("hi "), "hi");
+ confirmTrim(new StringEval(" hi"), "hi");
+ confirmTrim(new StringEval(" hi there "), "hi there");
+ confirmTrim(new StringEval(""), "");
+ confirmTrim(new StringEval(" "), "");
+ }
+
+ /**
+ * Valid cases where text arg is not exactly a string
+ */
+ public void testUnusualArgs() {
+
+ // text (first) arg type is number, other args are strings with fractional digits
+ confirmTrim(new NumberEval(123456), "123456");
+ confirmTrim(BoolEval.FALSE, "FALSE");
+ confirmTrim(BoolEval.TRUE, "TRUE");
+ confirmTrim(BlankEval.INSTANCE, "");
+ }
+
+ public void testErrors() {
+ confirmTrim(ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID);
+ }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java
new file mode 100755
index 0000000000..c9f043bd3b
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java
@@ -0,0 +1,139 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.eval.Area2DEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+/**
+ * Tests for Excel functions SUMX2MY2(), SUMX2PY2(), SUMXMY2()
+ *
+ * @author Josh Micich
+ */
+public final class TestXYNumericFunction extends TestCase {
+ private static final Function SUM_SQUARES = new Sumx2py2();
+ private static final Function DIFF_SQUARES = new Sumx2my2();
+ private static final Function SUM_SQUARES_OF_DIFFS = new Sumxmy2();
+
+ private static Eval invoke(Function function, Eval xArray, Eval yArray) {
+ Eval[] args = new Eval[] { xArray, yArray, };
+ return function.evaluate(args, -1, (short)-1);
+ }
+
+ private void confirm(Function function, Eval xArray, Eval yArray, double expected) {
+ Eval result = invoke(function, xArray, yArray);
+ assertEquals(NumberEval.class, result.getClass());
+ assertEquals(expected, ((NumberEval)result).getNumberValue(), 0);
+ }
+ private void confirmError(Function function, Eval xArray, Eval yArray, ErrorEval expectedError) {
+ Eval result = invoke(function, xArray, yArray);
+ assertEquals(ErrorEval.class, result.getClass());
+ assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode());
+ }
+
+ private void confirmError(Eval xArray, Eval yArray, ErrorEval expectedError) {
+ confirmError(SUM_SQUARES, xArray, yArray, expectedError);
+ confirmError(DIFF_SQUARES, xArray, yArray, expectedError);
+ confirmError(SUM_SQUARES_OF_DIFFS, xArray, yArray, expectedError);
+ }
+
+ public void testBasic() {
+ ValueEval[] xValues = {
+ new NumberEval(1),
+ new NumberEval(2),
+ };
+ ValueEval areaEvalX = createAreaEval(xValues);
+ confirm(SUM_SQUARES, areaEvalX, areaEvalX, 10.0);
+ confirm(DIFF_SQUARES, areaEvalX, areaEvalX, 0.0);
+ confirm(SUM_SQUARES_OF_DIFFS, areaEvalX, areaEvalX, 0.0);
+
+ ValueEval[] yValues = {
+ new NumberEval(3),
+ new NumberEval(4),
+ };
+ ValueEval areaEvalY = createAreaEval(yValues);
+ confirm(SUM_SQUARES, areaEvalX, areaEvalY, 30.0);
+ confirm(DIFF_SQUARES, areaEvalX, areaEvalY, -20.0);
+ confirm(SUM_SQUARES_OF_DIFFS, areaEvalX, areaEvalY, 8.0);
+ }
+
+ /**
+ * number of items in array is not limited to 30
+ */
+ public void testLargeArrays() {
+ ValueEval[] xValues = createMockNumberArray(100, 3);
+ ValueEval[] yValues = createMockNumberArray(100, 2);
+
+ confirm(SUM_SQUARES, createAreaEval(xValues), createAreaEval(yValues), 1300.0);
+ confirm(DIFF_SQUARES, createAreaEval(xValues), createAreaEval(yValues), 500.0);
+ confirm(SUM_SQUARES_OF_DIFFS, createAreaEval(xValues), createAreaEval(yValues), 100.0);
+ }
+
+
+ private ValueEval[] createMockNumberArray(int size, double value) {
+ ValueEval[] result = new ValueEval[size];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = new NumberEval(value);
+ }
+ return result;
+ }
+
+ private static ValueEval createAreaEval(ValueEval[] values) {
+ String refStr = "A1:A" + values.length;
+ return new Area2DEval(new AreaPtg(refStr), values);
+ }
+
+ public void testErrors() {
+ ValueEval[] xValues = {
+ ErrorEval.REF_INVALID,
+ new NumberEval(2),
+ };
+ ValueEval areaEvalX = createAreaEval(xValues);
+ ValueEval[] yValues = {
+ new NumberEval(2),
+ ErrorEval.NULL_INTERSECTION,
+ };
+ ValueEval areaEvalY = createAreaEval(yValues);
+ ValueEval[] zValues = { // wrong size
+ new NumberEval(2),
+ };
+ ValueEval areaEvalZ = createAreaEval(zValues);
+
+ // if either arg is an error, that error propagates
+ confirmError(ErrorEval.REF_INVALID, ErrorEval.NAME_INVALID, ErrorEval.REF_INVALID);
+ confirmError(areaEvalX, ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID);
+ confirmError(ErrorEval.NAME_INVALID, areaEvalX, ErrorEval.NAME_INVALID);
+
+ // array sizes must match
+ confirmError(areaEvalX, areaEvalZ, ErrorEval.NA);
+ confirmError(areaEvalZ, areaEvalY, ErrorEval.NA);
+
+ // any error in an array item propagates up
+ confirmError(areaEvalX, areaEvalX, ErrorEval.REF_INVALID);
+
+ // search for errors array by array, not pair by pair
+ confirmError(areaEvalX, areaEvalY, ErrorEval.REF_INVALID);
+ confirmError(areaEvalY, areaEvalX, ErrorEval.NULL_INTERSECTION);
+
+ }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java
index 3b31cc03a3..c849fd4369 100644
--- a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java
@@ -21,14 +21,14 @@ import java.io.FileInputStream;
import java.util.Iterator;
import java.util.List;
-import org.apache.poi.hssf.record.FormulaRecord;
-import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
-import org.apache.poi.hssf.record.formula.ExpPtg;
-import org.apache.poi.hssf.util.CellReference;
-
import junit.framework.TestCase;
-public class TestBug42464 extends TestCase {
+import org.apache.poi.hssf.record.FormulaRecord;
+import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue;
+import org.apache.poi.hssf.util.CellReference;
+
+public final class TestBug42464 extends TestCase {
String dirname;
protected void setUp() throws Exception {
@@ -68,26 +68,27 @@ public class TestBug42464 extends TestCase {
Iterator it = row.cellIterator();
while(it.hasNext()) {
HSSFCell cell = (HSSFCell)it.next();
- if(cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {
- FormulaRecordAggregate record = (FormulaRecordAggregate)
- cell.getCellValueRecord();
- FormulaRecord r = record.getFormulaRecord();
- List ptgs = r.getParsedExpression();
-
- String cellRef = (new CellReference(row.getRowNum(), cell.getCellNum())).toString();
- if(cellRef.equals("BP24")) {
- System.out.print(cellRef);
- System.out.println(" - has " + r.getNumberOfExpressionTokens() + " ptgs over " + r.getExpressionLength() + " tokens:");
- for(int i=0; i
+ *
+ * @author Josh Micich
+ */
+public final class AllPOITests {
+ public static Test suite() {
+ TestSuite result = new TestSuite("Tests for org.apache.poi");
+ result.addTestSuite(TestPOIDocumentMain.class);
+ result.addTest(AllPOIDDFTests.suite());
+ result.addTest(AllPOIHPSFBasicTests.suite());
+ result.addTest(HSSFTests.suite());
+ result.addTest(AllPOIFSTests.suite());
+ result.addTest(AllPOIUtilTests.suite());
+ return result;
+ }
+}
diff --git a/src/testcases/org/apache/poi/TestPOIDocumentMain.java b/src/testcases/org/apache/poi/TestPOIDocumentMain.java
index be3d9b0a78..5d4d7df19e 100644
--- a/src/testcases/org/apache/poi/TestPOIDocumentMain.java
+++ b/src/testcases/org/apache/poi/TestPOIDocumentMain.java
@@ -85,7 +85,8 @@ public class TestPOIDocumentMain extends TestCase {
public void testWriteProperties() throws Exception {
// Just check we can write them back out into a filesystem
POIFSFileSystem outFS = new POIFSFileSystem();
- doc.writeProperties(outFS);
+ doc.readProperties();
+ doc.writeProperties(outFS);
// Should now hold them
assertNotNull(
@@ -101,7 +102,8 @@ public class TestPOIDocumentMain extends TestCase {
// Write them out
POIFSFileSystem outFS = new POIFSFileSystem();
- doc.writeProperties(outFS);
+ doc.readProperties();
+ doc.writeProperties(outFS);
outFS.writeFilesystem(baos);
// Create a new version
diff --git a/src/testcases/org/apache/poi/ddf/AllPOIDDFTests.java b/src/testcases/org/apache/poi/ddf/AllPOIDDFTests.java
new file mode 100755
index 0000000000..9b5fc0bfef
--- /dev/null
+++ b/src/testcases/org/apache/poi/ddf/AllPOIDDFTests.java
@@ -0,0 +1,47 @@
+/* ====================================================================
+ 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.ddf;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+/**
+ * Tests for org.apache.poi.ddf
+ *
+ * @author Josh Micich
+ */
+public final class AllPOIDDFTests {
+ public static Test suite() {
+ TestSuite result = new TestSuite("Tests for org.apache.poi.ddf");
+ result.addTestSuite(TestEscherBlipWMFRecord.class);
+ result.addTestSuite(TestEscherBoolProperty.class);
+ result.addTestSuite(TestEscherBSERecord.class);
+ result.addTestSuite(TestEscherChildAnchorRecord.class);
+ result.addTestSuite(TestEscherClientAnchorRecord.class);
+ result.addTestSuite(TestEscherClientDataRecord.class);
+ result.addTestSuite(TestEscherContainerRecord.class);
+ result.addTestSuite(TestEscherDggRecord.class);
+ result.addTestSuite(TestEscherDgRecord.class);
+ result.addTestSuite(TestEscherOptRecord.class);
+ result.addTestSuite(TestEscherPropertyFactory.class);
+ result.addTestSuite(TestEscherSpgrRecord.class);
+ result.addTestSuite(TestEscherSplitMenuColorsRecord.class);
+ result.addTestSuite(TestEscherSpRecord.class);
+ result.addTestSuite(TestUnknownEscherRecord.class);
+ return result;
+ }
+}
diff --git a/src/testcases/org/apache/poi/hpsf/basic/AllPOIHPSFBasicTests.java b/src/testcases/org/apache/poi/hpsf/basic/AllPOIHPSFBasicTests.java
new file mode 100755
index 0000000000..5954c9ca5f
--- /dev/null
+++ b/src/testcases/org/apache/poi/hpsf/basic/AllPOIHPSFBasicTests.java
@@ -0,0 +1,39 @@
+/* ====================================================================
+ 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.hpsf.basic;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+/**
+ * Test suite for org.apache.poi.hpsf.basic
+ *
+ * @author Josh Micich
+ */
+public final class AllPOIHPSFBasicTests {
+ public static Test suite() {
+ TestSuite result = new TestSuite("Tests for org.apache.poi.hpsf.basic");
+ result.addTestSuite(TestBasic.class);
+ result.addTestSuite(TestClassID.class);
+ result.addTestSuite(TestEmptyProperties.class);
+ result.addTestSuite(TestMetaDataIPI.class);
+ result.addTestSuite(TestUnicode.class);
+ result.addTestSuite(TestWrite.class);
+ result.addTestSuite(TestWriteWellKnown.class);
+ return result;
+ }
+}
diff --git a/src/testcases/org/apache/poi/hssf/HSSFTests.java b/src/testcases/org/apache/poi/hssf/HSSFTests.java
index 1e0edd6825..5b597a67c8 100644
--- a/src/testcases/org/apache/poi/hssf/HSSFTests.java
+++ b/src/testcases/org/apache/poi/hssf/HSSFTests.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -15,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
+
package org.apache.poi.hssf;
import junit.framework.Test;
@@ -26,80 +25,8 @@ import org.apache.poi.hssf.eventmodel.TestModelFactory;
import org.apache.poi.hssf.model.TestDrawingManager;
import org.apache.poi.hssf.model.TestFormulaParser;
import org.apache.poi.hssf.model.TestSheet;
-import org.apache.poi.hssf.record.TestAreaFormatRecord;
-import org.apache.poi.hssf.record.TestAreaRecord;
-import org.apache.poi.hssf.record.TestAxisLineFormatRecord;
-import org.apache.poi.hssf.record.TestAxisOptionsRecord;
-import org.apache.poi.hssf.record.TestAxisParentRecord;
-import org.apache.poi.hssf.record.TestAxisRecord;
-import org.apache.poi.hssf.record.TestAxisUsedRecord;
-import org.apache.poi.hssf.record.TestBarRecord;
-import org.apache.poi.hssf.record.TestBoundSheetRecord;
-import org.apache.poi.hssf.record.TestCategorySeriesAxisRecord;
-import org.apache.poi.hssf.record.TestChartRecord;
-import org.apache.poi.hssf.record.TestDatRecord;
-import org.apache.poi.hssf.record.TestDataFormatRecord;
-import org.apache.poi.hssf.record.TestDefaultDataLabelTextPropertiesRecord;
-import org.apache.poi.hssf.record.TestFontBasisRecord;
-import org.apache.poi.hssf.record.TestFontIndexRecord;
-import org.apache.poi.hssf.record.TestFormulaRecord;
-import org.apache.poi.hssf.record.TestFrameRecord;
-import org.apache.poi.hssf.record.TestLegendRecord;
-import org.apache.poi.hssf.record.TestLineFormatRecord;
-import org.apache.poi.hssf.record.TestLinkedDataRecord;
-import org.apache.poi.hssf.record.TestNameRecord;
-import org.apache.poi.hssf.record.TestNumberFormatIndexRecord;
-import org.apache.poi.hssf.record.TestObjectLinkRecord;
-import org.apache.poi.hssf.record.TestPaletteRecord;
-import org.apache.poi.hssf.record.TestPlotAreaRecord;
-import org.apache.poi.hssf.record.TestPlotGrowthRecord;
-import org.apache.poi.hssf.record.TestRecordFactory;
-import org.apache.poi.hssf.record.TestSCLRecord;
-import org.apache.poi.hssf.record.TestSSTDeserializer;
-import org.apache.poi.hssf.record.TestSSTRecord;
-import org.apache.poi.hssf.record.TestSSTRecordSizeCalculator;
-import org.apache.poi.hssf.record.TestSeriesChartGroupIndexRecord;
-import org.apache.poi.hssf.record.TestSeriesIndexRecord;
-import org.apache.poi.hssf.record.TestSeriesLabelsRecord;
-import org.apache.poi.hssf.record.TestSeriesListRecord;
-import org.apache.poi.hssf.record.TestSeriesRecord;
-import org.apache.poi.hssf.record.TestSeriesTextRecord;
-import org.apache.poi.hssf.record.TestSeriesToChartGroupRecord;
-import org.apache.poi.hssf.record.TestSheetPropertiesRecord;
-import org.apache.poi.hssf.record.TestStringRecord;
-import org.apache.poi.hssf.record.TestSupBookRecord;
-import org.apache.poi.hssf.record.TestTextRecord;
-import org.apache.poi.hssf.record.TestTickRecord;
-import org.apache.poi.hssf.record.TestUnicodeString;
-import org.apache.poi.hssf.record.TestUnitsRecord;
-import org.apache.poi.hssf.record.TestValueRangeRecord;
-import org.apache.poi.hssf.record.aggregates.TestRowRecordsAggregate;
-import org.apache.poi.hssf.record.aggregates.TestValueRecordsAggregate;
-import org.apache.poi.hssf.record.formula.AllFormulaTests;
-import org.apache.poi.hssf.usermodel.TestBugs;
-import org.apache.poi.hssf.usermodel.TestCellStyle;
-import org.apache.poi.hssf.usermodel.TestCloneSheet;
-import org.apache.poi.hssf.usermodel.TestEscherGraphics;
-import org.apache.poi.hssf.usermodel.TestEscherGraphics2d;
-import org.apache.poi.hssf.usermodel.TestFontDetails;
-import org.apache.poi.hssf.usermodel.TestFormulas;
-import org.apache.poi.hssf.usermodel.TestHSSFCell;
-import org.apache.poi.hssf.usermodel.TestHSSFClientAnchor;
-import org.apache.poi.hssf.usermodel.TestHSSFComment;
-import org.apache.poi.hssf.usermodel.TestHSSFDateUtil;
-import org.apache.poi.hssf.usermodel.TestHSSFHeaderFooter;
-import org.apache.poi.hssf.usermodel.TestHSSFPalette;
-import org.apache.poi.hssf.usermodel.TestHSSFRichTextString;
-import org.apache.poi.hssf.usermodel.TestHSSFRow;
-import org.apache.poi.hssf.usermodel.TestHSSFSheet;
-import org.apache.poi.hssf.usermodel.TestHSSFSheetOrder;
-import org.apache.poi.hssf.usermodel.TestHSSFSheetSetOrder;
-import org.apache.poi.hssf.usermodel.TestHSSFWorkbook;
-import org.apache.poi.hssf.usermodel.TestNamedRange;
-import org.apache.poi.hssf.usermodel.TestReadWriteChart;
-import org.apache.poi.hssf.usermodel.TestSanityChecker;
-import org.apache.poi.hssf.usermodel.TestSheetShiftRows;
-import org.apache.poi.hssf.usermodel.TestWorkbook;
+import org.apache.poi.hssf.record.AllRecordTests;
+import org.apache.poi.hssf.usermodel.AllUserModelTests;
import org.apache.poi.hssf.util.TestAreaReference;
import org.apache.poi.hssf.util.TestCellReference;
import org.apache.poi.hssf.util.TestRKUtil;
@@ -107,118 +34,36 @@ import org.apache.poi.hssf.util.TestRangeAddress;
import org.apache.poi.hssf.util.TestSheetReferences;
/**
- * Test Suite for running just HSSF tests. Mostly
- * this is for my convienience.
+ * Test Suite for all sub-packages of org.apache.poi.hssf
+ *
+ * Mostly this is for my convenience.
*
* @author Andrew C. Oliver acoliver@apache.org
*/
-public 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("Test for org.apache.poi.hssf.usermodel");
- //$JUnit-BEGIN$
-
- suite.addTest(new TestSuite(TestBugs.class));
- suite.addTest(new TestSuite(TestCloneSheet.class));
- suite.addTest(new TestSuite(TestEscherGraphics.class));
- suite.addTest(new TestSuite(TestEscherGraphics2d.class));
- suite.addTest(new TestSuite(TestFontDetails.class));
- suite.addTest(new TestSuite(TestHSSFClientAnchor.class));
- suite.addTest(new TestSuite(TestHSSFHeaderFooter.class));
- suite.addTest(new TestSuite(TestHSSFRichTextString.class));
- suite.addTest(new TestSuite(TestHSSFSheetOrder.class));
- suite.addTest(new TestSuite(TestHSSFSheetSetOrder.class));
- suite.addTest(new TestSuite(TestHSSFWorkbook.class));
- suite.addTest(new TestSuite(TestSanityChecker.class));
- suite.addTest(new TestSuite(TestSheetShiftRows.class));
-
- suite.addTest(new TestSuite(TestCellStyle.class));
- suite.addTest(new TestSuite(TestFormulas.class));
- suite.addTest(new TestSuite(TestHSSFCell.class));
- suite.addTest(new TestSuite(TestHSSFDateUtil.class));
- suite.addTest(new TestSuite(TestHSSFPalette.class));
- suite.addTest(new TestSuite(TestHSSFRow.class));
- suite.addTest(new TestSuite(TestHSSFSheet.class));
- suite.addTest(new TestSuite(TestNamedRange.class));
- suite.addTest(new TestSuite(TestReadWriteChart.class));
- suite.addTest(new TestSuite(TestWorkbook.class));
-
+ public static Test suite() {
+ TestSuite suite = new TestSuite("Tests for org.apache.poi.hssf");
+ // $JUnit-BEGIN$
+ suite.addTest(AllUserModelTests.suite());
+ suite.addTest(AllRecordTests.suite());
suite.addTest(new TestSuite(TestFormulaParser.class));
- suite.addTest(new TestSuite(TestAreaFormatRecord.class));
- suite.addTest(new TestSuite(TestAreaRecord.class));
- suite.addTest(new TestSuite(TestAxisLineFormatRecord.class));
- suite.addTest(new TestSuite(TestAxisOptionsRecord.class));
- suite.addTest(new TestSuite(TestAxisParentRecord.class));
- suite.addTest(new TestSuite(TestAxisRecord.class));
- suite.addTest(new TestSuite(TestAxisUsedRecord.class));
- suite.addTest(new TestSuite(TestBarRecord.class));
- suite.addTest(new TestSuite(TestBoundSheetRecord.class));
- suite.addTest(new TestSuite(TestCategorySeriesAxisRecord.class));
- suite.addTest(new TestSuite(TestChartRecord.class));
- suite.addTest(new TestSuite(TestDatRecord.class));
- suite.addTest(new TestSuite(TestDataFormatRecord.class));
- suite.addTest(
- new TestSuite(TestDefaultDataLabelTextPropertiesRecord.class));
- suite.addTest(new TestSuite(TestFontBasisRecord.class));
- suite.addTest(new TestSuite(TestFontIndexRecord.class));
- suite.addTest(new TestSuite(TestFormulaRecord.class));
- suite.addTest(new TestSuite(TestFrameRecord.class));
- suite.addTest(new TestSuite(TestLegendRecord.class));
- suite.addTest(new TestSuite(TestLineFormatRecord.class));
- suite.addTest(new TestSuite(TestLinkedDataRecord.class));
- suite.addTest(new TestSuite(TestNumberFormatIndexRecord.class));
- suite.addTest(new TestSuite(TestObjectLinkRecord.class));
- suite.addTest(new TestSuite(TestPaletteRecord.class));
- suite.addTest(new TestSuite(TestPlotAreaRecord.class));
- suite.addTest(new TestSuite(TestPlotGrowthRecord.class));
- suite.addTest(new TestSuite(TestRecordFactory.class));
- suite.addTest(new TestSuite(TestSCLRecord.class));
- suite.addTest(new TestSuite(TestSSTDeserializer.class));
- suite.addTest(new TestSuite(TestSSTRecord.class));
- suite.addTest(new TestSuite(TestSSTRecordSizeCalculator.class));
- suite.addTest(new TestSuite(TestSeriesChartGroupIndexRecord.class));
- suite.addTest(new TestSuite(TestSeriesIndexRecord.class));
- suite.addTest(new TestSuite(TestSeriesLabelsRecord.class));
- suite.addTest(new TestSuite(TestSeriesListRecord.class));
- suite.addTest(new TestSuite(TestSeriesRecord.class));
- suite.addTest(new TestSuite(TestSeriesTextRecord.class));
- suite.addTest(new TestSuite(TestSeriesToChartGroupRecord.class));
- suite.addTest(new TestSuite(TestSheetPropertiesRecord.class));
- suite.addTest(new TestSuite(TestStringRecord.class));
- suite.addTest(new TestSuite(TestSupBookRecord.class));
- suite.addTest(new TestSuite(TestTextRecord.class));
- suite.addTest(new TestSuite(TestTickRecord.class));
- suite.addTest(new TestSuite(TestUnicodeString.class));
- suite.addTest(new TestSuite(TestUnitsRecord.class));
- suite.addTest(new TestSuite(TestValueRangeRecord.class));
- suite.addTest(new TestSuite(TestRowRecordsAggregate.class));
suite.addTest(new TestSuite(TestAreaReference.class));
suite.addTest(new TestSuite(TestCellReference.class));
- suite.addTest(new TestSuite(TestRangeAddress.class));
+ suite.addTest(new TestSuite(TestRangeAddress.class));
suite.addTest(new TestSuite(TestRKUtil.class));
suite.addTest(new TestSuite(TestSheetReferences.class));
-
-
- suite.addTest(AllFormulaTests.suite());
- suite.addTest(new TestSuite(TestValueRecordsAggregate.class));
- suite.addTest(new TestSuite(TestNameRecord.class));
- suite.addTest(new TestSuite(TestEventRecordFactory.class));
- suite.addTest(new TestSuite(TestModelFactory.class));
- suite.addTest(new TestSuite(TestDrawingManager.class));
- suite.addTest(new TestSuite(TestSheet.class));
-
- suite.addTest(new TestSuite(TestHSSFComment.class));
- //$JUnit-END$
+ suite.addTest(new TestSuite(TestEventRecordFactory.class));
+ suite.addTest(new TestSuite(TestModelFactory.class));
+ suite.addTest(new TestSuite(TestDrawingManager.class));
+ suite.addTest(new TestSuite(TestSheet.class));
+ // $JUnit-END$
return suite;
}
}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls b/src/testcases/org/apache/poi/hssf/data/44297.xls
similarity index 100%
rename from src/scratchpad/testcases/org/apache/poi/hssf/data/44297.xls
rename to src/testcases/org/apache/poi/hssf/data/44297.xls
diff --git a/src/testcases/org/apache/poi/hssf/data/AbnormalSharedFormulaFlag.xls b/src/testcases/org/apache/poi/hssf/data/AbnormalSharedFormulaFlag.xls
new file mode 100755
index 0000000000..788865b3b9
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/AbnormalSharedFormulaFlag.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls
new file mode 100644
index 0000000000..6260d878bc
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/LookupFunctionsTestCaseData.xls b/src/testcases/org/apache/poi/hssf/data/LookupFunctionsTestCaseData.xls
new file mode 100755
index 0000000000..f4b35fb935
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/LookupFunctionsTestCaseData.xls differ
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/MissingBits.xls b/src/testcases/org/apache/poi/hssf/data/MissingBits.xls
similarity index 100%
rename from src/scratchpad/testcases/org/apache/poi/hssf/data/MissingBits.xls
rename to src/testcases/org/apache/poi/hssf/data/MissingBits.xls
diff --git a/src/testcases/org/apache/poi/hssf/data/OddStyleRecord.xls b/src/testcases/org/apache/poi/hssf/data/OddStyleRecord.xls
new file mode 100644
index 0000000000..dcaa79b5e1
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/OddStyleRecord.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/ReadOnlyRecommended.xls b/src/testcases/org/apache/poi/hssf/data/ReadOnlyRecommended.xls
new file mode 100644
index 0000000000..d479b94f60
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/ReadOnlyRecommended.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/SingleLetterRanges.xls b/src/testcases/org/apache/poi/hssf/data/SingleLetterRanges.xls
new file mode 100644
index 0000000000..e35058d410
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/SingleLetterRanges.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls b/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls
deleted file mode 100644
index 0b2a869486..0000000000
Binary files a/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls and /dev/null differ
diff --git a/src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls b/src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls
new file mode 100755
index 0000000000..07eafb4144
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png b/src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png
new file mode 100755
index 0000000000..90a915a3a9
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png differ
diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
index 1b41d87d4d..f922e75d82 100644
--- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
+++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -18,24 +17,35 @@
package org.apache.poi.hssf.model;
+import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
+import org.apache.poi.hssf.model.FormulaParser.FormulaParseException;
import org.apache.poi.hssf.record.formula.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;
@@ -49,27 +59,27 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
* Some tests are also done in scratchpad, if they need
* HSSFFormulaEvaluator, which is there
*/
-public class TestFormulaParser extends TestCase {
+public final class TestFormulaParser extends TestCase {
- public TestFormulaParser(String name) {
- super(name);
- }
- public void setUp(){
-
- }
-
- public void tearDown() {
-
+ /**
+ * @return parsed token array already confirmed not null
+ */
+ private static Ptg[] parseFormula(String s) {
+ FormulaParser fp = new FormulaParser(s, null);
+ fp.parse();
+ Ptg[] result = fp.getRPNPtg();
+ assertNotNull("Ptg array should not be null", result);
+ return result;
}
public void testSimpleFormula() {
- FormulaParser fp = new FormulaParser("2+2;",null);
+ FormulaParser fp = new FormulaParser("2+2",null);
fp.parse();
Ptg[] ptgs = fp.getRPNPtg();
assertTrue("three tokens expected, got "+ptgs.length,ptgs.length == 3);
}
public void testFormulaWithSpace1() {
- FormulaParser fp = new FormulaParser(" 2 + 2 ;",null);
+ FormulaParser fp = new FormulaParser(" 2 + 2 ",null);
fp.parse();
Ptg[] ptgs = fp.getRPNPtg();
assertTrue("three tokens expected, got "+ptgs.length,ptgs.length == 3);
@@ -82,7 +92,7 @@ public class TestFormulaParser extends TestCase {
public void testFormulaWithSpace2() {
Ptg[] ptgs;
FormulaParser fp;
- fp = new FormulaParser("2+ sum( 3 , 4) ;",null);
+ fp = new FormulaParser("2+ sum( 3 , 4) ",null);
fp.parse();
ptgs = fp.getRPNPtg();
assertTrue("five tokens expected, got "+ptgs.length,ptgs.length == 5);
@@ -91,7 +101,7 @@ public class TestFormulaParser extends TestCase {
public void testFormulaWithSpaceNRef() {
Ptg[] ptgs;
FormulaParser fp;
- fp = new FormulaParser("sum( A2:A3 );",null);
+ fp = new FormulaParser("sum( A2:A3 )",null);
fp.parse();
ptgs = fp.getRPNPtg();
assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2);
@@ -100,7 +110,7 @@ public class TestFormulaParser extends TestCase {
public void testFormulaWithString() {
Ptg[] ptgs;
FormulaParser fp;
- fp = new FormulaParser("\"hello\" & \"world\" ;",null);
+ fp = new FormulaParser("\"hello\" & \"world\" ",null);
fp.parse();
ptgs = fp.getRPNPtg();
assertTrue("three token expected, got " + ptgs.length, ptgs.length == 3);
@@ -273,20 +283,21 @@ public class TestFormulaParser extends TestCase {
}
public void testMacroFunction() {
- Workbook w = new Workbook();
+ Workbook w = Workbook.createWorkbook();
FormulaParser fp = new FormulaParser("FOO()", w);
fp.parse();
Ptg[] ptg = fp.getRPNPtg();
- AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[0];
- assertEquals("externalflag", tfunc.getName());
-
- NamePtg tname = (NamePtg) ptg[1];
+ // the name gets encoded as the first arg
+ NamePtg tname = (NamePtg) ptg[0];
assertEquals("FOO", tname.toFormulaString(w));
+
+ AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[1];
+ assertEquals("externalflag", tfunc.getName());
}
public void testEmbeddedSlash() {
- FormulaParser fp = new FormulaParser("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\");",null);
+ FormulaParser fp = new FormulaParser("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\")",null);
fp.parse();
Ptg[] ptg = fp.getRPNPtg();
assertTrue("first ptg is string",ptg[0] instanceof StringPtg);
@@ -397,7 +408,7 @@ public class TestFormulaParser extends TestCase {
public void testUnderscore() {
HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Cash_Flow");;
+ wb.createSheet("Cash_Flow");
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
@@ -438,7 +449,7 @@ public class TestFormulaParser extends TestCase {
public void testExponentialInSheet() throws Exception {
HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Cash_Flow");;
+ wb.createSheet("Cash_Flow");
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
@@ -514,7 +525,7 @@ public class TestFormulaParser extends TestCase {
public void testNumbers() {
HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Cash_Flow");;
+ wb.createSheet("Cash_Flow");
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
@@ -553,7 +564,7 @@ public class TestFormulaParser extends TestCase {
public void testRanges() {
HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Cash_Flow");;
+ wb.createSheet("Cash_Flow");
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
@@ -571,5 +582,266 @@ public class TestFormulaParser extends TestCase {
cell.setCellFormula("A1...A2");
formula = cell.getCellFormula();
assertEquals("A1:A2", formula);
- }
+ }
+
+ /**
+ * Test for bug observable at svn revision 618865 (5-Feb-2008)
+ * a formula consisting of a single no-arg function got rendered without the function braces
+ */
+ public void testToFormulaStringZeroArgFunction() {
+
+ Workbook book = Workbook.createWorkbook(); // not really used in this test
+
+ Ptg[] ptgs = {
+ new FuncPtg(10, 0),
+ };
+ 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 (FormulaParseException e) {
+ // expected during successful test
+ assertNotNull(e.getMessage());
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ fail("Wrong exception:" + e.getMessage());
+ }
+ }
+
+ public void testSetFormulaWithRowBeyond32768_Bug44539() {
+
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet();
+ wb.setSheetName(0, "Sheet1");
+
+ HSSFRow row = sheet.createRow(0);
+ HSSFCell cell = row.createCell((short)0);
+ cell.setCellFormula("SUM(A32769:A32770)");
+ if("SUM(A-32767:A-32766)".equals(cell.getCellFormula())) {
+ fail("Identified bug 44539");
+ }
+ assertEquals("SUM(A32769:A32770)", cell.getCellFormula());
+ }
}
diff --git a/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java b/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java
new file mode 100755
index 0000000000..b1acfeafa1
--- /dev/null
+++ b/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java
@@ -0,0 +1,104 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.record;
+
+import org.apache.poi.hssf.record.formula.AllFormulaTests;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Collects all tests for package org.apache.poi.hssf.record.
+ *
+ * @author Josh Micich
+ */
+public class AllRecordTests {
+
+ public static Test suite() {
+ TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record");
+
+ result.addTest(AllFormulaTests.suite());
+
+ result.addTestSuite(TestAreaFormatRecord.class);
+ result.addTestSuite(TestAreaRecord.class);
+ result.addTestSuite(TestAxisLineFormatRecord.class);
+ result.addTestSuite(TestAxisOptionsRecord.class);
+ result.addTestSuite(TestAxisParentRecord.class);
+ result.addTestSuite(TestAxisRecord.class);
+ result.addTestSuite(TestAxisUsedRecord.class);
+ result.addTestSuite(TestBOFRecord.class);
+ result.addTestSuite(TestBarRecord.class);
+ result.addTestSuite(TestBoundSheetRecord.class);
+ result.addTestSuite(TestCategorySeriesAxisRecord.class);
+ result.addTestSuite(TestChartRecord.class);
+ result.addTestSuite(TestChartTitleFormatRecord.class);
+ result.addTestSuite(TestCommonObjectDataSubRecord.class);
+ result.addTestSuite(TestDatRecord.class);
+ result.addTestSuite(TestDataFormatRecord.class);
+ result.addTestSuite(TestDefaultDataLabelTextPropertiesRecord.class);
+ result.addTestSuite(TestDrawingGroupRecord.class);
+ result.addTestSuite(TestEmbeddedObjectRefSubRecord.class);
+ result.addTestSuite(TestEndSubRecord.class);
+ result.addTestSuite(TestEscherAggregate.class);
+ result.addTestSuite(TestFontBasisRecord.class);
+ result.addTestSuite(TestFontIndexRecord.class);
+ result.addTestSuite(TestFormulaRecord.class);
+ result.addTestSuite(TestFrameRecord.class);
+ result.addTestSuite(TestHyperlinkRecord.class);
+ result.addTestSuite(TestLegendRecord.class);
+ result.addTestSuite(TestLineFormatRecord.class);
+ result.addTestSuite(TestLinkedDataRecord.class);
+ result.addTestSuite(TestMergeCellsRecord.class);
+ result.addTestSuite(TestNameRecord.class);
+ result.addTestSuite(TestNoteRecord.class);
+ result.addTestSuite(TestNoteStructureSubRecord.class);
+ result.addTestSuite(TestNumberFormatIndexRecord.class);
+ result.addTestSuite(TestObjRecord.class);
+ result.addTestSuite(TestObjectLinkRecord.class);
+ result.addTestSuite(TestPaletteRecord.class);
+ result.addTestSuite(TestPaneRecord.class);
+ result.addTestSuite(TestPlotAreaRecord.class);
+ result.addTestSuite(TestPlotGrowthRecord.class);
+ result.addTestSuite(TestRecordFactory.class);
+ result.addTestSuite(TestSCLRecord.class);
+ result.addTestSuite(TestSSTDeserializer.class);
+ result.addTestSuite(TestSSTRecord.class);
+ result.addTestSuite(TestSSTRecordSizeCalculator.class);
+ result.addTestSuite(TestSeriesChartGroupIndexRecord.class);
+ result.addTestSuite(TestSeriesIndexRecord.class);
+ result.addTestSuite(TestSeriesLabelsRecord.class);
+ result.addTestSuite(TestSeriesListRecord.class);
+ result.addTestSuite(TestSeriesRecord.class);
+ result.addTestSuite(TestSeriesTextRecord.class);
+ result.addTestSuite(TestSeriesToChartGroupRecord.class);
+ result.addTestSuite(TestSheetPropertiesRecord.class);
+ result.addTestSuite(TestStringRecord.class);
+ result.addTestSuite(TestSubRecord.class);
+ result.addTestSuite(TestSupBookRecord.class);
+ result.addTestSuite(TestTextObjectBaseRecord.class);
+ result.addTestSuite(TestTextObjectRecord.class);
+ result.addTestSuite(TestTextRecord.class);
+ result.addTestSuite(TestTickRecord.class);
+ result.addTestSuite(TestUnicodeNameRecord.class);
+ result.addTestSuite(TestUnicodeString.class);
+ result.addTestSuite(TestUnitsRecord.class);
+ result.addTestSuite(TestValueRangeRecord.class);
+ return result;
+ }
+}
diff --git a/src/testcases/org/apache/poi/hssf/record/TestDVALRecord.java b/src/testcases/org/apache/poi/hssf/record/TestDVALRecord.java
new file mode 100755
index 0000000000..bcfb76645d
--- /dev/null
+++ b/src/testcases/org/apache/poi/hssf/record/TestDVALRecord.java
@@ -0,0 +1,55 @@
+/* ====================================================================
+ 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.io.ByteArrayInputStream;
+
+import org.apache.poi.util.LittleEndian;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ * @author Josh Micich
+ */
+public final class TestDVALRecord extends TestCase {
+ public void testRead() {
+
+ byte[] data = new byte[22];
+ LittleEndian.putShort(data, 0, DVALRecord.sid);
+ LittleEndian.putShort(data, 2, (short)18);
+ LittleEndian.putShort(data, 4, (short)55);
+ LittleEndian.putInt(data, 6, 56);
+ LittleEndian.putInt(data, 10, 57);
+ LittleEndian.putInt(data, 14, 58);
+ LittleEndian.putInt(data, 18, 59);
+
+ RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(data));
+ in.nextRecord();
+ DVALRecord dv = new DVALRecord(in);
+
+ assertEquals(55, dv.getOptions());
+ assertEquals(56, dv.getHorizontalPos());
+ assertEquals(57, dv.getVerticalPos());
+ assertEquals(58, dv.getObjectID());
+ if(dv.getDVRecNo() == 0) {
+ fail("Identified bug 44510");
+ }
+ assertEquals(59, dv.getDVRecNo());
+ }
+}
diff --git a/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java b/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java
index fca5bae545..3727989efb 100644
--- a/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java
+++ b/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java
@@ -29,15 +29,25 @@ import junit.framework.TestCase;
*
* @author Andrew C. Oliver (acoliver at apache dot org)
*/
-public class TestSupBookRecord
- extends TestCase
-{
+public final class TestSupBookRecord extends TestCase {
/**
* This contains a fake data section of a SubBookRecord
*/
- byte[] data = new byte[] {
- (byte)0x04,(byte)0x00,(byte)0x01,(byte)0x04
+ byte[] dataIR = new byte[] {
+ (byte)0x04,(byte)0x00,(byte)0x01,(byte)0x04,
};
+ byte[] dataAIF = new byte[] {
+ (byte)0x01,(byte)0x00,(byte)0x01,(byte)0x3A,
+ };
+ byte[] dataER = new byte[] {
+ (byte)0x02,(byte)0x00,
+ (byte)0x07,(byte)0x00, (byte)0x00,
+ (byte)'t', (byte)'e', (byte)'s', (byte)'t', (byte)'U', (byte)'R', (byte)'L',
+ (byte)0x06,(byte)0x00, (byte)0x00,
+ (byte)'S', (byte)'h', (byte)'e', (byte)'e', (byte)'t', (byte)'1',
+ (byte)0x06,(byte)0x00, (byte)0x00,
+ (byte)'S', (byte)'h', (byte)'e', (byte)'e', (byte)'t', (byte)'2',
+ };
public TestSupBookRecord(String name)
{
@@ -47,36 +57,67 @@ public class TestSupBookRecord
/**
* tests that we can load the record
*/
- public void testLoad()
- throws Exception
- {
+ public void testLoadIR() {
- SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, (short)data.length, data));
- assertEquals( 0x401, record.getFlag()); //expected flag
+ SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, dataIR));
+ assertTrue( record.isInternalReferences() ); //expected flag
assertEquals( 0x4, record.getNumberOfSheets() ); //expected # of sheets
assertEquals( 8, record.getRecordSize() ); //sid+size+data
record.validateSid((short)0x01AE);
}
+ /**
+ * tests that we can load the record
+ */
+ public void testLoadER() {
+
+ SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, dataER));
+ assertTrue( record.isExternalReferences() ); //expected flag
+ assertEquals( 0x2, record.getNumberOfSheets() ); //expected # of sheets
+
+ assertEquals( 34, record.getRecordSize() ); //sid+size+data
+
+ assertEquals("testURL", record.getURL().getString());
+ UnicodeString[] sheetNames = record.getSheetNames();
+ assertEquals(2, sheetNames.length);
+ assertEquals("Sheet1", sheetNames[0].getString());
+ assertEquals("Sheet2", sheetNames[1].getString());
+
+ record.validateSid((short)0x01AE);
+ }
-
+ /**
+ * tests that we can load the record
+ */
+ public void testLoadAIF() {
+
+ SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, dataAIF));
+ assertTrue( record.isAddInFunctions() ); //expected flag
+ assertEquals( 0x1, record.getNumberOfSheets() ); //expected # of sheets
+ assertEquals( 8, record.getRecordSize() ); //sid+size+data
+ record.validateSid((short)0x01AE);
+ }
+
/**
* Tests that we can store the record
*
*/
- public void testStore()
- {
- SupBookRecord record = new SupBookRecord();
- record.setFlag( (short) 0x401 );
- record.setNumberOfSheets( (short)0x4 );
-
+ public void testStoreIR() {
+ SupBookRecord record = SupBookRecord.createInternalReferences((short)4);
+ 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);
- 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, dataER, record.serialize());
}
public static void main(String [] args) {
@@ -84,6 +125,4 @@ public class TestSupBookRecord
.println("Testing org.apache.poi.hssf.record.SupBookRecord");
junit.textui.TestRunner.run(TestSupBookRecord.class);
}
-
-
}
diff --git a/src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java b/src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java
index 767f507e73..ecb55ca82c 100755
--- a/src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java
+++ b/src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -16,11 +15,12 @@
limitations under the License.
==================================================================== */
-
-
package org.apache.poi.hssf.record;
import java.io.ByteArrayInputStream;
+
+import junit.framework.Assert;
+
import org.apache.poi.util.LittleEndian;
/**
@@ -33,6 +33,14 @@ import org.apache.poi.util.LittleEndian;
public class TestcaseRecordInputStream
extends RecordInputStream
{
+ /**
+ * Convenience constructor
+ */
+ public TestcaseRecordInputStream(int sid, byte[] data)
+ {
+ super(new ByteArrayInputStream(mergeDataAndSid((short)sid, (short)data.length, data)));
+ nextRecord();
+ }
public TestcaseRecordInputStream(short sid, short length, byte[] data)
{
super(new ByteArrayInputStream(mergeDataAndSid(sid, length, data)));
@@ -46,4 +54,18 @@ public class TestcaseRecordInputStream
System.arraycopy(data, 0, result, 4, data.length);
return result;
}
+ /**
+ * Confirms data sections are equal
+ * @param expectedData - just raw data (without sid or size short ints)
+ * @param actualRecordBytes this includes 4 prefix bytes (sid & size)
+ */
+ public static void confirmRecordEncoding(int expectedSid, byte[] expectedData, byte[] actualRecordBytes) {
+ int expectedDataSize = expectedData.length;
+ Assert.assertEquals(actualRecordBytes.length - 4, expectedDataSize);
+ Assert.assertEquals(expectedSid, LittleEndian.getShort(actualRecordBytes, 0));
+ Assert.assertEquals(expectedDataSize, LittleEndian.getShort(actualRecordBytes, 2));
+ for (int i = 0; i < expectedDataSize; i++)
+ Assert.assertEquals("At offset " + i, expectedData[i], actualRecordBytes[i+4]);
+
+ }
}
diff --git a/src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java b/src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java
index a3315c2978..8e8a72ece7 100755
--- a/src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java
+++ b/src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java
@@ -17,15 +17,30 @@
package org.apache.poi.hssf.record.aggregates;
-import junit.framework.TestCase;
-import org.apache.poi.hssf.record.*;
-
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.zip.CRC32;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.BlankRecord;
+import org.apache.poi.hssf.record.FormulaRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.SharedFormulaRecord;
+import org.apache.poi.hssf.record.UnknownRecord;
+import org.apache.poi.hssf.record.WindowOneRecord;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
public class TestValueRecordsAggregate extends TestCase
{
+ private static final String ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE = "AbnormalSharedFormulaFlag.xls";
ValueRecordsAggregate valueRecord = new ValueRecordsAggregate();
/**
@@ -203,4 +218,117 @@ public class TestValueRecordsAggregate extends TestCase
assertEquals( 36, valueRecord.getRecordSize() );
}
+
+ /**
+ * Sometimes the 'shared formula' flag (FormulaRecord.isSharedFormula()) is set when
+ * there is no corresponding SharedFormulaRecord available. SharedFormulaRecord definitions do
+ * not span multiple sheets. They are are only defined within a sheet, and thus they do not
+ * have a sheet index field (only row and column range fields).
+ * So it is important that the code which locates the SharedFormulaRecord for each
+ * FormulaRecord does not allow matches across sheets.
+ *
+ * Prior to bugzilla 44449 (Feb 2008), POI ValueRecordsAggregate.construct(int, List)
+ * allowed SharedFormulaRecords to be erroneously used across sheets. That incorrect
+ * behaviour is shown by this test.
+ *
+ * Notes on how to produce the test spreadsheet:
+ *
+ *
+ * Prior to the row delete action the spreadsheet has two SharedFormulaRecords. One
+ * for each sheet. To expose the bug, the shared formulas have been made to overlap.
+ * The row delete action (as described here) seems to to delete the
+ * SharedFormulaRecord from Sheet1 (but not clear the 'shared formula' flags.
+ * There are other variations on this theme to create the same effect.
+ *
+ */
+ public void testSpuriousSharedFormulaFlag() {
+ File dataDir = new File(System.getProperty("HSSF.testdata.path"));
+ File testFile = new File(dataDir, ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE);
+
+ long actualCRC = getFileCRC(testFile);
+ long expectedCRC = 2277445406L;
+ if(actualCRC != expectedCRC) {
+ System.err.println("Expected crc " + expectedCRC + " but got " + actualCRC);
+ throw failUnexpectedTestFileChange();
+ }
+ HSSFWorkbook wb;
+ try {
+ FileInputStream in = new FileInputStream(testFile);
+ wb = new HSSFWorkbook(in);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ HSSFSheet s = wb.getSheetAt(0); // Sheet1
+
+ String cellFormula;
+ cellFormula = getFormulaFromFirstCell(s, 0); // row "1"
+ // the problem is not observable in the first row of the shared formula
+ if(!cellFormula.equals("\"first formula\"")) {
+ throw new RuntimeException("Something else wrong with this test case");
+ }
+
+ // but the problem is observable in rows 2,3,4
+ cellFormula = getFormulaFromFirstCell(s, 1); // row "2"
+ if(cellFormula.equals("\"second formula\"")) {
+ throw new AssertionFailedError("found bug 44449 (Wrong SharedFormulaRecord was used).");
+ }
+ if(!cellFormula.equals("\"first formula\"")) {
+ throw new RuntimeException("Something else wrong with this test case");
+ }
+ }
+ private static String getFormulaFromFirstCell(HSSFSheet s, int rowIx) {
+ return s.getRow(rowIx).getCell((short)0).getCellFormula();
+ }
+
+ /**
+ * If someone opened this particular test file in Excel and saved it, the peculiar condition
+ * which causes the target bug would probably disappear. This test would then just succeed
+ * regardless of whether the fix was present. So a CRC check is performed to make it less easy
+ * for that to occur.
+ */
+ private static RuntimeException failUnexpectedTestFileChange() {
+ String msg = "Test file '" + ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE + "' has changed. "
+ + "This junit may not be properly testing for the target bug. "
+ + "Either revert the test file or ensure that the new version "
+ + "has the right characteristics to test the target bug.";
+ // A breakpoint in ValueRecordsAggregate.handleMissingSharedFormulaRecord(FormulaRecord)
+ // should get hit during parsing of Sheet1.
+ // If the test spreadsheet is created as directed, this condition should occur.
+ // It is easy to upset the test spreadsheet (for example re-saving will destroy the
+ // peculiar condition we are testing for).
+ throw new RuntimeException(msg);
+ }
+
+ /**
+ * gets a CRC checksum for the content of a file
+ */
+ private static long getFileCRC(File f) {
+ CRC32 crc = new CRC32();
+ byte[] buf = new byte[2048];
+ try {
+ InputStream is = new FileInputStream(f);
+ while(true) {
+ int bytesRead = is.read(buf);
+ if(bytesRead < 1) {
+ break;
+ }
+ crc.update(buf, 0, bytesRead);
+ }
+ is.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return crc.getValue();
+ }
+
}
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java b/src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java
index 21e6a8ba3c..0912b97611 100644
--- a/src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -28,6 +27,7 @@ import junit.framework.TestCase;
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
/**
* Convenient abstract class to reduce the amount of boilerplate code needed
@@ -35,8 +35,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
*
* @author Daniel Noll (daniel at nuix dot com dot au)
*/
-public abstract class AbstractPtgTestCase extends TestCase
-{
+public abstract class AbstractPtgTestCase extends TestCase {
/** Directory containing the test data. */
private static String dataDir = System.getProperty("HSSF.testdata.path");
@@ -51,16 +50,16 @@ public abstract class AbstractPtgTestCase extends TestCase
throws IOException {
File file = new File(dataDir, filename);
InputStream stream = new BufferedInputStream(new FileInputStream(file));
- try
- {
- return new HSSFWorkbook(stream);
- }
- finally
- {
+ // TODO - temp workaround to keep stdout quiet due to warning msg in POIFS
+ // When that warning msg is disabled, remove this wrapper and the close() call,
+ InputStream wrappedStream = POIFSFileSystem.createNonClosingInputStream(stream);
+ try {
+ return new HSSFWorkbook(wrappedStream);
+ } finally {
stream.close();
}
}
-
+
/**
* Creates a new Workbook and adds one sheet with the specified name
*/
@@ -73,5 +72,4 @@ public abstract class AbstractPtgTestCase extends TestCase
book.setSheetName(0, sheetName);
return book;
}
-
}
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java b/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java
index b126813387..92ca4ba044 100644
--- a/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java
@@ -14,7 +14,6 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.record.formula;
@@ -33,7 +32,8 @@ public class AllFormulaTests {
result.addTestSuite(TestArea3DPtg.class);
result.addTestSuite(TestAreaErrPtg.class);
result.addTestSuite(TestAreaPtg.class);
- result.addTestSuite(TestErrPtg.class);
+ result.addTestSuite(TestErrPtg.class);
+ result.addTestSuite(TestExternalFunctionFormulas.class);
result.addTestSuite(TestFuncPtg.class);
result.addTestSuite(TestIntersectionPtg.class);
result.addTestSuite(TestPercentPtg.class);
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java b/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java
index 522a5bcf28..3a7f2f29a4 100644
--- a/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/TestAreaPtg.java
@@ -27,15 +27,12 @@ import org.apache.poi.hssf.model.FormulaParser;
*
* @author Dmitriy Kumshayev
*/
-public class TestAreaPtg extends TestCase
-{
+public final class TestAreaPtg extends TestCase {
AreaPtg relative;
AreaPtg absolute;
- protected void setUp() throws Exception
- {
- super.setUp();
+ protected void setUp() {
short firstRow=5;
short lastRow=13;
short firstCol=7;
@@ -64,10 +61,9 @@ public class TestAreaPtg extends TestCase
}
- public void resetColumns(AreaPtg aptg)
- {
- short fc = aptg.getFirstColumn();
- short lc = aptg.getLastColumn();
+ private static void resetColumns(AreaPtg aptg) {
+ int fc = aptg.getFirstColumn();
+ int lc = aptg.getLastColumn();
aptg.setFirstColumn(fc);
aptg.setLastColumn(lc);
assertEquals(fc , aptg.getFirstColumn() );
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java b/src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java
new file mode 100755
index 0000000000..8c89dadea7
--- /dev/null
+++ b/src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java
@@ -0,0 +1,56 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+/**
+ * Tests for functions from external workbooks (e.g. YEARFRAC).
+ *
+ *
+ * @author Josh Micich
+ */
+public final class TestExternalFunctionFormulas extends TestCase {
+
+
+ /**
+ * tests NameXPtg.toFormulaString(Workbook) and logic in Workbook below that
+ */
+ public void testReadFormulaContainingExternalFunction() {
+ String filePath = System.getProperty("HSSF.testdata.path")+ "/"
+ + "externalFunctionExample.xls";
+ HSSFWorkbook wb;
+ try {
+ FileInputStream fin = new FileInputStream(filePath);
+ wb = new HSSFWorkbook( fin );
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ String expectedFormula = "YEARFRAC(B1,C1)";
+ HSSFSheet sht = wb.getSheetAt(0);
+ String cellFormula = sht.getRow(0).getCell((short)0).getCellFormula();
+ assertEquals(expectedFormula, cellFormula);
+ }
+
+}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java b/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java
new file mode 100755
index 0000000000..cbc555da36
--- /dev/null
+++ b/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java
@@ -0,0 +1,71 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Collects all tests for the org.apache.poi.hssf.usermodel package.
+ *
+ * @author Josh Micich
+ */
+public class AllUserModelTests {
+
+ public static Test suite() {
+ TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.usermodel");
+
+ result.addTestSuite(TestBugs.class);
+ result.addTestSuite(TestCellStyle.class);
+ result.addTestSuite(TestCloneSheet.class);
+ result.addTestSuite(TestDataValidation.class);
+ result.addTestSuite(TestEscherGraphics.class);
+ result.addTestSuite(TestEscherGraphics2d.class);
+ result.addTestSuite(TestFontDetails.class);
+ result.addTestSuite(TestFormulas.class);
+ result.addTestSuite(TestHSSFCell.class);
+ result.addTestSuite(TestHSSFClientAnchor.class);
+ result.addTestSuite(TestHSSFComment.class);
+ result.addTestSuite(TestHSSFDateUtil.class);
+ result.addTestSuite(TestHSSFHeaderFooter.class);
+ result.addTestSuite(TestHSSFHyperlink.class);
+ result.addTestSuite(TestHSSFPalette.class);
+ result.addTestSuite(TestHSSFPicture.class);
+ result.addTestSuite(TestHSSFPictureData.class);
+ result.addTestSuite(TestHSSFRichTextString.class);
+ result.addTestSuite(TestHSSFRow.class);
+ result.addTestSuite(TestHSSFSheet.class);
+ result.addTestSuite(TestHSSFSheetOrder.class);
+ result.addTestSuite(TestHSSFSheetSetOrder.class);
+ result.addTestSuite(TestHSSFWorkbook.class);
+ result.addTestSuite(TestNamedRange.class);
+ result.addTestSuite(TestOLE2Embeding.class);
+ result.addTestSuite(TestReadWriteChart.class);
+ result.addTestSuite(TestSanityChecker.class);
+ result.addTestSuite(TestSheetHiding.class);
+ result.addTestSuite(TestSheetShiftRows.class);
+ if (false) { // deliberately avoiding this one
+ result.addTestSuite(TestUnfixedBugs.class);
+ }
+ result.addTestSuite(TestUnicodeWorkbook.class);
+ result.addTestSuite(TestUppercaseWorkbook.class);
+ result.addTestSuite(TestWorkbook.class);
+
+ return result;
+ }
+}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
index 6dfdddad44..f9bb362c7b 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
@@ -1089,6 +1089,44 @@ extends TestCase {
// "EmptyStackException"
//assertEquals("=CHOOSE(2,A2,A3,A4)", c2.getCellFormula());
}
+
+ /**
+ * Crystal reports generates files with short
+ * StyleRecords, which is against the spec
+ */
+ public void test44471() throws Exception {
+ FileInputStream in = new FileInputStream(new File(cwd, "OddStyleRecord.xls"));
+
+ // Used to blow up with an ArrayIndexOutOfBounds
+ // when creating a StyleRecord
+ HSSFWorkbook wb = new HSSFWorkbook(in);
+ in.close();
+
+ assertEquals(1, wb.getNumberOfSheets());
+ }
+
+ /**
+ * Files with "read only recommended" were giving
+ * grief on the FileSharingRecord
+ */
+ public void test44536() throws Exception {
+ FileInputStream in = new FileInputStream(new File(cwd, "ReadOnlyRecommended.xls"));
+
+ // Used to blow up with an IllegalArgumentException
+ // when creating a FileSharingRecord
+ HSSFWorkbook wb = new HSSFWorkbook(in);
+ in.close();
+
+ // Check read only advised
+ assertEquals(3, wb.getNumberOfSheets());
+ assertTrue(wb.isWriteProtected());
+
+ // But also check that another wb isn't
+ in = new FileInputStream(new File(cwd, "SimpleWithChoose.xls"));
+ wb = new HSSFWorkbook(in);
+ in.close();
+ assertFalse(wb.isWriteProtected());
+ }
}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java b/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java
index f970ff26f1..34885e7a21 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java
@@ -51,7 +51,7 @@ public class TestDataValidation extends TestCase
public void testDataValidation() throws Exception
{
System.out.println("\nTest no. 2 - Test Excel's Data validation mechanism");
- String resultFile = System.getProperty("HSSF.testdata.path")+"/TestDataValidation.xls";
+ String resultFile = System.getProperty("java.io.tmpdir")+File.separator+"TestDataValidation.xls";
HSSFWorkbook wb = new HSSFWorkbook();
HSSFCellStyle style_1 = this.createStyle( wb, HSSFCellStyle.ALIGN_LEFT );
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java b/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java
index f1e8f49777..9eb12bd4e1 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java
@@ -302,10 +302,10 @@ extends TestCase {
}
c = r.getCell((short) y);
- CellReference cr= new CellReference(refx1,refy1);
- ref=cr.toString();
- cr=new CellReference(refx2,refy2);
- ref2=cr.toString();
+ CellReference cr= new CellReference(refx1,refy1, false, false);
+ ref=cr.formatAsString();
+ cr=new CellReference(refx2,refy2, false, false);
+ ref2=cr.formatAsString();
c = r.createCell((short) y);
c.setCellFormula("" + ref + operator + ref2);
@@ -379,10 +379,10 @@ extends TestCase {
}
c = r.getCell((short) y);
- CellReference cr= new CellReference(refx1,refy1);
- ref=cr.toString();
- cr=new CellReference(refx2,refy2);
- ref2=cr.toString();
+ CellReference cr= new CellReference(refx1, refy1, false, false);
+ ref=cr.formatAsString();
+ cr=new CellReference(refx2,refy2, false, false);
+ ref2=cr.formatAsString();
assertTrue("loop Formula is as expected "+ref+operator+ref2+"!="+c.getCellFormula(),(
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java
index c5674b9e76..a0f09696b5 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java
@@ -160,6 +160,49 @@ public class TestHSSFPalette extends TestCase
assertEquals("FFFF:0:FFFF", p.getColor((short)14).getHexString());
}
+ public void testFindSimilar() throws Exception {
+ HSSFWorkbook book = new HSSFWorkbook();
+ HSSFPalette p = book.getCustomPalette();
+
+
+ // Add a few edge colours in
+ p.setColorAtIndex((short)8, (byte)-1, (byte)0, (byte)0);
+ p.setColorAtIndex((short)9, (byte)0, (byte)-1, (byte)0);
+ p.setColorAtIndex((short)10, (byte)0, (byte)0, (byte)-1);
+
+ // And some near a few of them
+ p.setColorAtIndex((short)11, (byte)-1, (byte)2, (byte)2);
+ p.setColorAtIndex((short)12, (byte)-2, (byte)2, (byte)10);
+ p.setColorAtIndex((short)13, (byte)-4, (byte)0, (byte)0);
+ p.setColorAtIndex((short)14, (byte)-8, (byte)0, (byte)0);
+
+ assertEquals(
+ "FFFF:0:0", p.getColor((short)8).getHexString()
+ );
+
+ // Now check we get the right stuff back
+ assertEquals(
+ p.getColor((short)8).getHexString(),
+ p.findSimilarColor((byte)-1, (byte)0, (byte)0).getHexString()
+ );
+ assertEquals(
+ p.getColor((short)8).getHexString(),
+ p.findSimilarColor((byte)-2, (byte)0, (byte)0).getHexString()
+ );
+ assertEquals(
+ p.getColor((short)8).getHexString(),
+ p.findSimilarColor((byte)-1, (byte)1, (byte)0).getHexString()
+ );
+ assertEquals(
+ p.getColor((short)11).getHexString(),
+ p.findSimilarColor((byte)-1, (byte)2, (byte)1).getHexString()
+ );
+ assertEquals(
+ p.getColor((short)12).getHexString(),
+ p.findSimilarColor((byte)-1, (byte)2, (byte)10).getHexString()
+ );
+ }
+
/**
* Verifies that the generated gnumeric-format string values match the
* hardcoded values in the HSSFColor default color palette
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java
index 25d6684c47..fd01001f84 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java
@@ -16,25 +16,28 @@
*/
package org.apache.poi.hssf.usermodel;
-import junit.framework.TestCase;
-
-import java.io.IOException;
-import java.io.FileInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import junit.framework.TestCase;
/**
* Test HSSFPicture
.
*
* @author Yegor Kozlov (yegor at apache.org)
*/
-public class TestHSSFPicture extends TestCase{
+public final class TestHSSFPicture extends TestCase{
- public void testResize() throws Exception {
+ public void testResize() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sh1 = wb.createSheet();
HSSFPatriarch p1 = sh1.createDrawingPatriarch();
- int idx1 = loadPicture( "src/resources/logos/logoKarmokar4.png", wb);
+ byte[] pictureData = getTestDataFileContent("logoKarmokar4.png");
+ int idx1 = wb.addPicture( pictureData, HSSFWorkbook.PICTURE_TYPE_PNG );
HSSFPicture picture1 = p1.createPicture(new HSSFClientAnchor(), idx1);
HSSFClientAnchor anchor1 = picture1.getPreferredSize();
@@ -52,28 +55,25 @@ public class TestHSSFPicture extends TestCase{
/**
* Copied from org.apache.poi.hssf.usermodel.examples.OfficeDrawing
*/
- private static int loadPicture( String path, HSSFWorkbook wb ) throws IOException
- {
- int pictureIndex;
- FileInputStream fis = null;
- ByteArrayOutputStream bos = null;
- try
- {
- fis = new FileInputStream( path);
- bos = new ByteArrayOutputStream( );
- int c;
- while ( (c = fis.read()) != -1)
- bos.write( c );
- pictureIndex = wb.addPicture( bos.toByteArray(), HSSFWorkbook.PICTURE_TYPE_PNG );
- }
- finally
- {
- if (fis != null)
- fis.close();
- if (bos != null)
- bos.close();
- }
- return pictureIndex;
- }
+ private static byte[] getTestDataFileContent(String fileName) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ String readFilename = System.getProperty("HSSF.testdata.path");
+ try {
+ InputStream fis = new FileInputStream(readFilename+File.separator+fileName);
+
+ byte[] buf = new byte[512];
+ while(true) {
+ int bytesRead = fis.read(buf);
+ if(bytesRead < 1) {
+ break;
+ }
+ bos.write(buf, 0, bytesRead);
+ }
+ fis.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return bos.toByteArray();
+ }
}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java
index 44c03cd20d..b6f22022c6 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -15,34 +14,22 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.usermodel;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
import junit.framework.TestCase;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-
-import org.apache.poi.util.TempFile;
-
/**
* Test HSSFRow is okay.
*
* @author Glen Stampoultzis (glens at apache.org)
*/
-public class TestHSSFRow
- extends TestCase
-{
- public TestHSSFRow(String s)
- {
- super(s);
- }
+public final class TestHSSFRow extends TestCase {
- public void testLastAndFirstColumns()
- throws Exception
- {
+ public void testLastAndFirstColumns() {
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet();
HSSFRow row = sheet.createRow((short) 0);
@@ -51,127 +38,146 @@ public class TestHSSFRow
row.createCell((short) 2);
assertEquals(2, row.getFirstCellNum());
- assertEquals(2, row.getLastCellNum());
+ assertEquals(3, row.getLastCellNum());
row.createCell((short) 1);
assertEquals(1, row.getFirstCellNum());
- assertEquals(2, row.getLastCellNum());
-
+ assertEquals(3, row.getLastCellNum());
+
// check the exact case reported in 'bug' 43901 - notice that the cellNum is '0' based
row.createCell((short) 3);
assertEquals(1, row.getFirstCellNum());
- assertEquals(3, row.getLastCellNum());
-
+ assertEquals(4, row.getLastCellNum());
}
- public void testRemoveCell()
- throws Exception
- {
+ public void testRemoveCell() throws Exception {
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet();
HSSFRow row = sheet.createRow((short) 0);
assertEquals(-1, row.getLastCellNum());
assertEquals(-1, row.getFirstCellNum());
row.createCell((short) 1);
- assertEquals(1, row.getLastCellNum());
+ assertEquals(2, row.getLastCellNum());
assertEquals(1, row.getFirstCellNum());
row.createCell((short) 3);
- assertEquals(3, row.getLastCellNum());
+ assertEquals(4, row.getLastCellNum());
assertEquals(1, row.getFirstCellNum());
row.removeCell(row.getCell((short) 3));
- assertEquals(1, row.getLastCellNum());
+ assertEquals(2, row.getLastCellNum());
assertEquals(1, row.getFirstCellNum());
row.removeCell(row.getCell((short) 1));
assertEquals(-1, row.getLastCellNum());
assertEquals(-1, row.getFirstCellNum());
- // check the row record actually writes it out as 0's
+ // all cells on this row have been removed
+ // so check the row record actually writes it out as 0's
byte[] data = new byte[100];
row.getRowRecord().serialize(0, data);
assertEquals(0, data[6]);
assertEquals(0, data[8]);
- File file = TempFile.createTempFile("XXX", "XLS");
- FileOutputStream stream = new FileOutputStream(file);
- workbook.write(stream);
- stream.close();
- FileInputStream inputStream = new FileInputStream(file);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ workbook.write(baos);
+ baos.close();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(baos.toByteArray());
workbook = new HSSFWorkbook(inputStream);
sheet = workbook.getSheetAt(0);
- stream.close();
- file.delete();
+ inputStream.close();
+
assertEquals(-1, sheet.getRow((short) 0).getLastCellNum());
assertEquals(-1, sheet.getRow((short) 0).getFirstCellNum());
}
-
- public void testMoveCell() throws Exception {
+
+ public void testMoveCell() {
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet();
HSSFRow row = sheet.createRow((short) 0);
HSSFRow rowB = sheet.createRow((short) 1);
-
+
HSSFCell cellA2 = rowB.createCell((short)0);
assertEquals(0, rowB.getFirstCellNum());
assertEquals(0, rowB.getFirstCellNum());
-
+
assertEquals(-1, row.getLastCellNum());
assertEquals(-1, row.getFirstCellNum());
HSSFCell cellB2 = row.createCell((short) 1);
HSSFCell cellB3 = row.createCell((short) 2);
HSSFCell cellB4 = row.createCell((short) 3);
-
+
assertEquals(1, row.getFirstCellNum());
- assertEquals(3, row.getLastCellNum());
-
+ assertEquals(4, row.getLastCellNum());
+
// Try to move to somewhere else that's used
try {
- row.moveCell(cellB2, (short)3);
- fail();
- } catch(IllegalArgumentException e) {}
-
+ row.moveCell(cellB2, (short)3);
+ fail("IllegalArgumentException should have been thrown");
+ } catch(IllegalArgumentException e) {
+ // expected during successful test
+ }
+
// Try to move one off a different row
try {
- row.moveCell(cellA2, (short)3);
- fail();
- } catch(IllegalArgumentException e) {}
-
+ row.moveCell(cellA2, (short)3);
+ fail("IllegalArgumentException should have been thrown");
+ } catch(IllegalArgumentException e) {
+ // expected during successful test
+ }
+
// Move somewhere spare
assertNotNull(row.getCell((short)1));
- row.moveCell(cellB2, (short)5);
+ row.moveCell(cellB2, (short)5);
assertNull(row.getCell((short)1));
assertNotNull(row.getCell((short)5));
-
- assertEquals(5, cellB2.getCellNum());
+
+ assertEquals(5, cellB2.getCellNum());
assertEquals(2, row.getFirstCellNum());
- assertEquals(5, row.getLastCellNum());
-
+ assertEquals(6, row.getLastCellNum());
}
-
- public void testRowBounds()
- throws Exception
- {
+
+ public void testRowBounds() {
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet();
//Test low row bound
- HSSFRow row = sheet.createRow( (short) 0);
- //Test low row bound exception
- boolean caughtException = false;
+ sheet.createRow( (short) 0);
+ //Test low row bound exception
try {
- row = sheet.createRow(-1);
+ sheet.createRow(-1);
+ fail("IndexOutOfBoundsException should have been thrown");
} catch (IndexOutOfBoundsException ex) {
- caughtException = true;
- }
- assertTrue(caughtException);
- //Test high row bound
- row = sheet.createRow(65535);
- //Test high row bound exception
- caughtException = false;
+ // expected during successful test
+ }
+
+ //Test high row bound
+ sheet.createRow(65535);
+ //Test high row bound exception
try {
- row = sheet.createRow(65536);
+ sheet.createRow(65536);
+ fail("IndexOutOfBoundsException should have been thrown");
} catch (IndexOutOfBoundsException ex) {
- caughtException = true;
- }
- assertTrue(caughtException);
+ // expected during successful test
+ }
+ }
+
+ /**
+ * Prior to patch 43901, POI was producing files with the wrong last-column
+ * number on the row
+ */
+ public void testLastCellNumIsCorrectAfterAddCell_bug43901(){
+ HSSFWorkbook book = new HSSFWorkbook();
+ HSSFSheet sheet = book.createSheet("test");
+ HSSFRow row = sheet.createRow(0);
+
+ // New row has last col -1
+ assertEquals(-1, row.getLastCellNum());
+ if(row.getLastCellNum() == 0) {
+ fail("Identified bug 43901");
+ }
+
+ // Create two cells, will return one higher
+ // than that for the last number
+ row.createCell((short) 0);
+ assertEquals(1, row.getLastCellNum());
+ row.createCell((short) 255);
+ assertEquals(256, row.getLastCellNum());
}
-
}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java b/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java
index f22de1758c..afbbde026e 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java
@@ -578,18 +578,16 @@ public class TestNamedRange
// retrieve the cell at the named range and test its contents
AreaReference aref = new AreaReference(aNamedCell.getReference());
- CellReference[] crefs = aref.getCells();
- assertNotNull(crefs);
- assertEquals("Should be exactly 1 cell in the named cell :'" +cellName+"'", 1, crefs.length);
- for (int i=0, iSize=crefs.length; i
+ *
+ * @author Josh Micich
+ */
+public final class AllPOIFSPropertyTests {
+
+ public static Test suite() {
+ TestSuite result = new TestSuite("Tests for org.apache.poi.poifs.property");
+ result.addTestSuite(TestDirectoryProperty.class);
+ result.addTestSuite(TestDocumentProperty.class);
+ result.addTestSuite(TestPropertyFactory.class);
+ result.addTestSuite(TestPropertyTable.class);
+ result.addTestSuite(TestRootProperty.class);
+ return result;
+ }
+}
diff --git a/src/testcases/org/apache/poi/poifs/storage/AllPOIFSStorageTests.java b/src/testcases/org/apache/poi/poifs/storage/AllPOIFSStorageTests.java
new file mode 100755
index 0000000000..8c15d389e3
--- /dev/null
+++ b/src/testcases/org/apache/poi/poifs/storage/AllPOIFSStorageTests.java
@@ -0,0 +1,47 @@
+/* ====================================================================
+ 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.poifs.storage;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+/**
+ * Tests for org.apache.poi.poifs.storage
+ *
+ * @author Josh Micich
+ */
+public final class AllPOIFSStorageTests {
+
+ public static Test suite() {
+ TestSuite result = new TestSuite("Tests for org.apache.poi.poifs.storage");
+ result.addTestSuite(TestBATBlock.class);
+ result.addTestSuite(TestBlockAllocationTableReader.class);
+ result.addTestSuite(TestBlockAllocationTableWriter.class);
+ result.addTestSuite(TestBlockListImpl.class);
+ result.addTestSuite(TestDocumentBlock.class);
+ result.addTestSuite(TestHeaderBlockReader.class);
+ result.addTestSuite(TestHeaderBlockWriter.class);
+ result.addTestSuite(TestPropertyBlock.class);
+ result.addTestSuite(TestRawDataBlock.class);
+ result.addTestSuite(TestRawDataBlockList.class);
+ result.addTestSuite(TestSmallBlockTableReader.class);
+ result.addTestSuite(TestSmallBlockTableWriter.class);
+ result.addTestSuite(TestSmallDocumentBlock.class);
+ result.addTestSuite(TestSmallDocumentBlockList.class);
+ return result;
+ }
+}
diff --git a/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlock.java b/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlock.java
index 1473fa82ea..4c84f04b71 100644
--- a/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlock.java
+++ b/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlock.java
@@ -22,6 +22,9 @@ package org.apache.poi.poifs.storage;
import java.io.*;
import java.util.Random;
+import org.apache.poi.util.DummyPOILogger;
+import org.apache.poi.util.POILogFactory;
+
import junit.framework.*;
/**
@@ -43,6 +46,13 @@ public class TestRawDataBlock
public TestRawDataBlock(String name)
{
super(name);
+
+ // We always want to use our own
+ // logger
+ System.setProperty(
+ "org.apache.poi.util.POILogger",
+ "org.apache.poi.util.DummyPOILogger"
+ );
}
/**
@@ -99,11 +109,19 @@ public class TestRawDataBlock
/**
* Test creating a short RawDataBlock
+ * Will trigger a warning, but no longer an IOException,
+ * as people seem to have "valid" truncated files
*/
-
- public void testShortConstructor()
+ public void testShortConstructor() throws Exception
{
- for (int k = 1; k < 512; k++)
+ // Get the logger to be used
+ DummyPOILogger logger = (DummyPOILogger)POILogFactory.getLogger(
+ RawDataBlock.class
+ );
+ assertEquals(0, logger.logged.size());
+
+ // Test for various data sizes
+ for (int k = 1; k <= 512; k++)
{
byte[] data = new byte[ k ];
@@ -112,16 +130,33 @@ public class TestRawDataBlock
data[ j ] = ( byte ) j;
}
RawDataBlock block = null;
-
- try
- {
- block = new RawDataBlock(new ByteArrayInputStream(data));
- fail("Should have thrown IOException creating short block");
- }
- catch (IOException ignored)
- {
-
- // as expected
+
+ logger.reset();
+ assertEquals(0, logger.logged.size());
+
+ // Have it created
+ block = new RawDataBlock(new ByteArrayInputStream(data));
+ assertNotNull(block);
+
+ // Check for the warning is there for <512
+ if(k < 512) {
+ assertEquals(
+ "Warning on " + k + " byte short block",
+ 1, logger.logged.size()
+ );
+
+ // Build the expected warning message, and check
+ String bts = k + " byte";
+ if(k > 1) {
+ bts += "s";
+ }
+
+ assertEquals(
+ "7 - Unable to read entire block; "+bts+" read before EOF; expected 512 bytes. Your document has probably been truncated!",
+ (String)(logger.logged.get(0))
+ );
+ } else {
+ assertEquals(0, logger.logged.size());
}
}
}
@@ -132,6 +167,13 @@ public class TestRawDataBlock
* incorrectly think that there's not enough data
*/
public void testSlowInputStream() throws Exception {
+ // Get the logger to be used
+ DummyPOILogger logger = (DummyPOILogger)POILogFactory.getLogger(
+ RawDataBlock.class
+ );
+ assertEquals(0, logger.logged.size());
+
+ // Test for various ok data sizes
for (int k = 1; k < 512; k++) {
byte[] data = new byte[ 512 ];
for (int j = 0; j < data.length; j++) {
@@ -153,14 +195,17 @@ public class TestRawDataBlock
data[j] = (byte) j;
}
- // Shouldn't complain, as there is enough data
- try {
- RawDataBlock block =
- new RawDataBlock(new SlowInputStream(data, k));
- fail();
- } catch(IOException e) {
- // as expected
- }
+ logger.reset();
+ assertEquals(0, logger.logged.size());
+
+ // Should complain, as there isn't enough data
+ RawDataBlock block =
+ new RawDataBlock(new SlowInputStream(data, k));
+ assertNotNull(block);
+ assertEquals(
+ "Warning on " + k + " byte short block",
+ 1, logger.logged.size()
+ );
}
}
diff --git a/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java b/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java
index ee63825e24..ac6fc08c05 100644
--- a/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java
+++ b/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java
@@ -21,6 +21,9 @@ package org.apache.poi.poifs.storage;
import java.io.*;
+import org.apache.poi.util.DummyPOILogger;
+import org.apache.poi.util.POILogFactory;
+
import junit.framework.*;
/**
@@ -42,6 +45,13 @@ public class TestRawDataBlockList
public TestRawDataBlockList(String name)
{
super(name);
+
+ // We always want to use our own
+ // logger
+ System.setProperty(
+ "org.apache.poi.util.POILogger",
+ "org.apache.poi.util.DummyPOILogger"
+ );
}
/**
@@ -78,8 +88,15 @@ public class TestRawDataBlockList
* Test creating a short RawDataBlockList
*/
- public void testShortConstructor()
+ public void testShortConstructor() throws Exception
{
+ // Get the logger to be used
+ DummyPOILogger logger = (DummyPOILogger)POILogFactory.getLogger(
+ RawDataBlock.class
+ );
+ assertEquals(0, logger.logged.size());
+
+ // Test for various short sizes
for (int k = 2049; k < 2560; k++)
{
byte[] data = new byte[ k ];
@@ -88,16 +105,11 @@ public class TestRawDataBlockList
{
data[ j ] = ( byte ) j;
}
- try
- {
- new RawDataBlockList(new ByteArrayInputStream(data));
- fail("Should have thrown IOException creating short block");
- }
- catch (IOException ignored)
- {
- // as expected
- }
+ // Check we logged the error
+ logger.reset();
+ new RawDataBlockList(new ByteArrayInputStream(data));
+ assertEquals(1, logger.logged.size());
}
}
diff --git a/src/testcases/org/apache/poi/util/AllPOIUtilTests.java b/src/testcases/org/apache/poi/util/AllPOIUtilTests.java
new file mode 100755
index 0000000000..bb6d382047
--- /dev/null
+++ b/src/testcases/org/apache/poi/util/AllPOIUtilTests.java
@@ -0,0 +1,51 @@
+/* ====================================================================
+ 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.util;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+/**
+ * Test suite for all sub-packages of org.apache.poi.util
+ *
+ * @author Josh Micich
+ */
+public final class AllPOIUtilTests {
+
+ public static Test suite() {
+ TestSuite result = new TestSuite("Tests for org.apache.poi.util");
+ result.addTestSuite(TestArrayUtil.class);
+ result.addTestSuite(TestBinaryTree.class);
+ result.addTestSuite(TestBitField.class);
+ result.addTestSuite(TestByteField.class);
+ result.addTestSuite(TestDoubleList2d.class);
+ result.addTestSuite(TestHexDump.class);
+ result.addTestSuite(TestIntegerField.class);
+ result.addTestSuite(TestIntList.class);
+ result.addTestSuite(TestIntList2d.class);
+ result.addTestSuite(TestList2d.class);
+ result.addTestSuite(TestLittleEndian.class);
+ result.addTestSuite(TestLongField.class);
+ result.addTestSuite(TestPOILogFactory.class);
+ result.addTestSuite(TestPOILogger.class);
+ result.addTestSuite(TestShortField.class);
+ result.addTestSuite(TestShortList.class);
+ result.addTestSuite(TestStringUtil.class);
+ result.addTestSuite(TestTempFile.class);
+ return result;
+ }
+}
diff --git a/src/testcases/org/apache/poi/util/DummyPOILogger.java b/src/testcases/org/apache/poi/util/DummyPOILogger.java
new file mode 100644
index 0000000000..7efbfac293
--- /dev/null
+++ b/src/testcases/org/apache/poi/util/DummyPOILogger.java
@@ -0,0 +1,46 @@
+/* ====================================================================
+ 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.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * POILogger which logs into an ArrayList, so that
+ * tests can see what got logged
+ */
+public class DummyPOILogger extends POILogger {
+ public List logged = new ArrayList();
+
+ public void reset() {
+ logged = new ArrayList();
+ }
+
+ public boolean check(int level) {
+ return true;
+ }
+
+ public void initialize(String cat) {}
+
+ public void log(int level, Object obj1) {
+ logged.add(new String(level + " - " + obj1));
+ }
+
+ public void log(int level, Object obj1, Throwable exception) {
+ logged.add(new String(level + " - " + obj1 + " - " + exception));
+ }
+}