From 1fc10ad6693a5ea100ae5fbbe004bdc2c0723602 Mon Sep 17 00:00:00 2001
From: Ugo Cei
+ 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 91641b13b..c59cae1a3 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 000000000..73b9e42df
--- /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 2846f5066..011c0a435 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 000000000..771603c85
--- /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 ac260ffa4..33278e25e 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 57628f19b..515d07dd4 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 000000000..5a0a21e86
--- /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 908c8d5e3..be34e0074 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 996f40e39..596b38623 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 b9d55a09e..8a6b2c03b 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 df3e5a70b..4983c9d07 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 ba796db3b..8e47cbe7a 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 67f455797..f906e91a4 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 sSH8kUntR6
zq(eGPvh|5cqBX>E2wdhCH=s{syhy8wQRJ_5Xir31gi$0Stxo~C*~>#dV&Z0#v%4yl
zNz8JZ;h37v>;mAH#U!PYvuiKsf!i5|neCQV_>)t@MkZl4GxIJJpI^#&wu4KOz~$?q
z@iqVdA@9Azn%Lg9(U}xF1VUFN380{eG(i+Gp?3lZR*;S&B~-D8rl_a^6;u=uMFm?F
z5erJNV#i)lKrECf2_Q9OeQSc-y?xJqzxQ{pbIw2C;kra-C3!NlX3ewir`=B+)-+8X
zeM#P`Fd!s!K!y)2Gv^zYpmhdDzYy_wg4Uj3Gq@eR*9!fndR_65!4zr`EQHlw1jGFl
zX>|HkmD%QUNG%y{vi?X$AsV=Zo 7pc;C@#=fvjwmC9_JGGSKH(+Qa-C=I>VF!
zMcJq=G%Zg~pJkC;m61%#F%?^9^UiIaUo4dld``QjE^atHNtl7P(M~-OZ20mWVD1?8
z=IGTXaek_1wEQsjV%lVu0E)?%+Iwdmve}>dXX_j}#F;`D+0fTFdYFX8xnzMZM#gZq
z>XqH?1t%lvtLVukSEY);v!h1x&O6T^qICILM_5 cWK^E%;YN4@7T8S6yh)yNL9|)jN;4pQIl!Ki0VguM-Haf=T~TQG`q!CDy-O
zvuDKE&FzYN(Sw&B)tqZ)^wIsEDZUk2a*)23FplnTe=cZupT+a?PQ||5PL>ev?>azt
z9y0H+>G0F*1<{ppKQ3WAeipY0)h~OHYJT@)7p9E
+ *
+ *
+ *
+ * @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 000000000..03177c7c2
--- /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 2ba50857c..8fa3010a4 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 000000000..4c9e4425c
--- /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 17f8cda64..2f56eb092 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 3b1c413d5..a06bc8aed 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 000000000..6ec831e4a
--- /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 555c50294..c7c57280d 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 2974eec64..42dc11fa3 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 372b1850e..12166b796 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 d418afa7e..5405481a0 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 b1f280a01..ccf5ab6fc 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 510eebb03..84ff659b3 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 42773d4a3..0a3172889 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 0c9807b5a..54229a16a 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 771db767b..ef9acfe60 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 d55429840..472fd8b8b 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 b883d71b1..2278649cb 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 2d8c58ef3..e54cd483f 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 6dd3db23d..021168ad7 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 43ef6c512..e8e197d20 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 000000000..7a23901b2
--- /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 000000000..b1d81e652
--- /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 d1420b2e8..533c604a0 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 ecada85d5..22d87b7e4 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 000000000..682394b3c
--- /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 000000000..be1cda5f8
--- /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 437c24e40..651c5d2aa 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 7b24cb062..898d7a861 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 acedbe766..622d68632 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 bb72adc4a..e462586d7 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 01af4e843..27a9c6a62 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 b692f01ea..46c12236b 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 4bd77029f..85a384529 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 b4975eefc..ef6f533ea 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 847aa56fa..edcc7bee7 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 5ffa2faee..1abcf34d2 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 592402b80..bf888b97d 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 349110917..404304071 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 8eb7e841d..c054c6dac 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 000000000..56d285543
--- /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 8bac3d0c0..40ed1da49 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 90dbd591b..7aba5db72 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 c7464ffed..935e7cdbb 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 6e8f84b34..c0e482e5a 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 c0cb39b26..0bc49b407 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 f98ccca7e..be1d0d0f9 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 000000000..d6a848962
--- /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 a998a870f..21ba47b56 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 2840c8988..0e7cce217 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 ab446c9f2..13522294f 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 3d8cc1ae3..4dae76d98 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 6a4eb8edb..aabffab2f 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 fef7e0346..7995e66c3 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 12fa5d7bd..9f6eafa4d 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 f4e1959be..b74b4161a 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 8e3122407..30ad5ec23 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 deb7675a4..dfd730d12 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 c62a0b762..a1b2fec9b 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 686c40b62..b322cd985 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 5e9d91c7c..87e29ee34 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 ad8b88daf..7d27491df 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 1e6955ad9..b989c33a2 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 000000000..a06cd201e
--- /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 f60a6adaa..3fce30655 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 0000000000000000000000000000000000000000..2c1632ebae3348a5115dae7fae8b2e430e5f425e
GIT binary patch
literal 87040
zcmeFa2V4~A)(8ANv$KujHdw%*3?PV=j$#|Yjv|O)M_8JoC`wV2=mix^ED4)XN-!O=Eh_GXWdN5j;9v2i)1KLznoJKcIJ0Ht1ZAp2>0H%v{9DDzIX4kSW=m
zO(Ej6#|yujp#=Zu!1c6Rg<4L;ZsFbgoMui@tk;0}GLZew_u8er{Rc;p#2-;8Tg9#L
zUk;pJepy!Jp4Cm4CQ|vE(wxc`l!@K6285}2y$?)q$?HaRgMFUWP*&NVGQlIg?An4T
zFnGmz`(*cyExBB7)t$08WnE?DbJm+tE@Gz6HLNt^Dtpfcua{3OdJzmai5B0&md54c
zC0tP)w
8R=H7-Tf{OTP&==;|?CFkcnAF3+Om(O})a({}lsgSmg_)XJue&i{-
z+pyE;Z>CMlVElaEunos((O1JZ{8W@gYt!C0YQeuvBiV@a*{}|9czmeELV8NMWXDml
zwle0DroD|}o69HdZY3K|ddFduj8R%6n$5BD&Yn}glisyIWR2*(MOsYiPZdV>y-^zj
z+kDt44WkLPwvNjJ