diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml
index 1381dd8d28..5d7dce206f 100644
--- a/src/documentation/content/xdocs/changes.xml
+++ b/src/documentation/content/xdocs/changes.xml
@@ -46,6 +46,9 @@
- The POI team is pleased to announce the release of 3.1 BETA1 which is one of the final steps before 3.1 FINAL. + The POI team is pleased to announce the release of 3.1 BETA2 which is one of the final steps before 3.1 FINAL. The status of this release is a beta, meaning that we encourage users to try it out. If you find any bugs, please report them to the POI bug database or to the POI Developer List. @@ -54,7 +54,7 @@
The release is also available from the central Maven repository - under Group ID "org.apache.poi" and Version "3.1-beta1". + under Group ID "org.apache.poi" and Version "3.1-beta2".
- * Initially used to count a goto - * @param index - * @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; - } - - private int getPtgSize(int start, int end) { - int count = 0; - int index = start; - Iterator ptgIterator = tokens.listIterator(index); - while (ptgIterator.hasNext() && index <= end) { - Ptg ptg = (Ptg)ptgIterator.next(); - count+=ptg.getSize(); - index++; - } - - return count; - } /** * Generates the variable function ptg for the formula. *
@@ -362,84 +319,35 @@ public final class FormulaParser {
* @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, int numArgs, List argumentPointers) {
+ private ParseNode getFunction(String name, NamePtg namePtg, ParseNode[] args) {
- boolean isVarArgs;
- int funcIx;
FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase());
+ int numArgs = args.length;
if(fm == null) {
+ if (namePtg == null) {
+ throw new IllegalStateException("NamePtg must be supplied for external functions");
+ }
// must be external function
- isVarArgs = true;
- funcIx = FunctionMetadataRegistry.FUNCTION_INDEX_EXTERNAL;
- } else {
- isVarArgs = !fm.hasFixedArgsLength();
- funcIx = fm.getIndex();
- validateNumArgs(numArgs, fm);
+ ParseNode[] allArgs = new ParseNode[numArgs+1];
+ allArgs[0] = new ParseNode(namePtg);
+ System.arraycopy(args, 0, allArgs, 1, numArgs);
+ return new ParseNode(new FuncVarPtg(name, (byte)(numArgs+1)), allArgs);
}
+
+ if (namePtg != null) {
+ throw new IllegalStateException("NamePtg no applicable to internal functions");
+ }
+ boolean isVarArgs = !fm.hasFixedArgsLength();
+ int funcIx = fm.getIndex();
+ validateNumArgs(args.length, fm);
+
AbstractFunctionPtg retval;
if(isVarArgs) {
retval = new FuncVarPtg(name, (byte)numArgs);
} else {
retval = new FuncPtg(funcIx);
}
- 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;
+ return new ParseNode(retval, args);
}
private void validateNumArgs(int numArgs, FunctionMetadata fm) {
@@ -470,10 +378,12 @@ public final class FormulaParser {
}
/** get arguments to a function */
- private int Arguments(List argumentPointers) {
+ private ParseNode[] Arguments() {
+ //average 2 args per function
+ List temp = new ArrayList(2);
SkipWhite();
if(look == ')') {
- return 0;
+ return ParseNode.EMPTY_ARRAY;
}
boolean missedPrevArg = true;
@@ -482,8 +392,7 @@ public final class FormulaParser {
SkipWhite();
if (isArgumentDelimiter(look)) {
if (missedPrevArg) {
- tokens.add(new MissingArgPtg());
- addArgumentPointer(argumentPointers);
+ temp.add(new ParseNode(new MissingArgPtg()));
numArgs++;
}
if (look == ')') {
@@ -493,8 +402,7 @@ public final class FormulaParser {
missedPrevArg = true;
continue;
}
- comparisonExpression();
- addArgumentPointer(argumentPointers);
+ temp.add(comparisonExpression());
numArgs++;
missedPrevArg = false;
SkipWhite();
@@ -502,32 +410,34 @@ public final class FormulaParser {
throw expected("',' or ')'");
}
}
- return numArgs;
+ ParseNode[] result = new ParseNode[temp.size()];
+ temp.toArray(result);
+ return result;
}
/** Parse and Translate a Math Factor */
- private void powerFactor() {
- percentFactor();
+ private ParseNode powerFactor() {
+ ParseNode result = percentFactor();
while(true) {
SkipWhite();
if(look != '^') {
- return;
+ return result;
}
Match('^');
- percentFactor();
- tokens.add(new PowerPtg());
+ ParseNode other = percentFactor();
+ result = new ParseNode(new PowerPtg(), result, other);
}
}
- private void percentFactor() {
- tokens.add(parseSimpleFactor());
+ private ParseNode percentFactor() {
+ ParseNode result = parseSimpleFactor();
while(true) {
SkipWhite();
if(look != '%') {
- return;
+ return result;
}
Match('%');
- tokens.add(new PercentPtg());
+ result = new ParseNode(new PercentPtg(), result);
}
}
@@ -535,32 +445,30 @@ public final class FormulaParser {
/**
* factors (without ^ or % )
*/
- private Ptg parseSimpleFactor() {
+ private ParseNode parseSimpleFactor() {
SkipWhite();
switch(look) {
case '#':
- return parseErrorLiteral();
+ return new ParseNode(parseErrorLiteral());
case '-':
Match('-');
- powerFactor();
- return new UnaryMinusPtg();
+ return new ParseNode(new UnaryMinusPtg(), powerFactor());
case '+':
Match('+');
- powerFactor();
- return new UnaryPlusPtg();
+ return new ParseNode(new UnaryPlusPtg(), powerFactor());
case '(':
Match('(');
- comparisonExpression();
+ ParseNode inside = comparisonExpression();
Match(')');
- return new ParenthesisPtg();
+ return new ParseNode(new ParenthesisPtg(), inside);
case '"':
- return parseStringLiteral();
+ return new ParseNode(parseStringLiteral());
}
if (IsAlpha(look) || look == '\''){
- return parseIdent();
+ return parseFunctionOrIdentifier();
}
// else - assume number
- return parseNumber();
+ return new ParseNode(parseNumber());
}
@@ -716,28 +624,30 @@ public final class FormulaParser {
}
/** Parse and Translate a Math Term */
- private void Term() {
- powerFactor();
+ private ParseNode Term() {
+ ParseNode result = powerFactor();
while(true) {
SkipWhite();
+ Ptg operator;
switch(look) {
case '*':
Match('*');
- powerFactor();
- tokens.add(new MultiplyPtg());
- continue;
+ operator = new MultiplyPtg();
+ break;
case '/':
Match('/');
- powerFactor();
- tokens.add(new DividePtg());
- continue;
+ operator = new DividePtg();
+ break;
+ default:
+ return result; // finished with Term
}
- return; // finished with Term
+ ParseNode other = powerFactor();
+ result = new ParseNode(operator, result, other);
}
}
- private void comparisonExpression() {
- concatExpression();
+ private ParseNode comparisonExpression() {
+ ParseNode result = concatExpression();
while (true) {
SkipWhite();
switch(look) {
@@ -745,11 +655,11 @@ public final class FormulaParser {
case '>':
case '<':
Ptg comparisonToken = getComparisonToken();
- concatExpression();
- tokens.add(comparisonToken);
+ ParseNode other = concatExpression();
+ result = new ParseNode(comparisonToken, result, other);
continue;
}
- return; // finished with predicate expression
+ return result; // finished with predicate expression
}
}
@@ -779,38 +689,41 @@ public final class FormulaParser {
}
- private void concatExpression() {
- additiveExpression();
+ private ParseNode concatExpression() {
+ ParseNode result = additiveExpression();
while (true) {
SkipWhite();
if(look != '&') {
break; // finished with concat expression
}
Match('&');
- additiveExpression();
- tokens.add(new ConcatPtg());
+ ParseNode other = additiveExpression();
+ result = new ParseNode(new ConcatPtg(), result, other);
}
+ return result;
}
/** Parse and Translate an Expression */
- private void additiveExpression() {
- Term();
+ private ParseNode additiveExpression() {
+ ParseNode result = Term();
while (true) {
SkipWhite();
+ Ptg operator;
switch(look) {
case '+':
Match('+');
- Term();
- tokens.add(new AddPtg());
- continue;
+ operator = new AddPtg();
+ break;
case '-':
Match('-');
- Term();
- tokens.add(new SubtractPtg());
- continue;
+ operator = new SubtractPtg();
+ break;
+ default:
+ return result; // finished with additive expression
}
- return; // finished with additive expression
+ ParseNode other = Term();
+ result = new ParseNode(operator, result, other);
}
}
@@ -835,7 +748,7 @@ end;
public void parse() {
pointer=0;
GetChar();
- comparisonExpression();
+ _rootNode = comparisonExpression();
if(pointer <= formulaLength) {
String msg = "Unused input [" + formulaString.substring(pointer-1)
@@ -858,87 +771,12 @@ end;
}
public Ptg[] getRPNPtg(int formulaType) {
- Node node = createTree();
+ OperandClassTransformer oct = new OperandClassTransformer(formulaType);
// RVA is for 'operand class': 'reference', 'value', 'array'
- setRootLevelRVA(node, formulaType);
- setParameterRVA(node,formulaType);
- return (Ptg[]) tokens.toArray(new Ptg[0]);
+ oct.transformFormula(_rootNode);
+ return ParseNode.toTokenArray(_rootNode);
}
- private void setRootLevelRVA(Node n, int formulaType) {
- //Pg 16, excelfileformat.pdf @ openoffice.org
- Ptg p = n.getValue();
- if (formulaType == FormulaParser.FORMULA_TYPE_NAMEDRANGE) {
- if (p.getDefaultOperandClass() == Ptg.CLASS_REF) {
- setClass(n,Ptg.CLASS_REF);
- } else {
- setClass(n,Ptg.CLASS_ARRAY);
- }
- } else {
- setClass(n,Ptg.CLASS_VALUE);
- }
-
- }
-
- private void setParameterRVA(Node n, int formulaType) {
- Ptg p = n.getValue();
- int numOperands = n.getNumChildren();
- if (p instanceof AbstractFunctionPtg) {
- for (int i =0;ifalse
if this token is classified as 'reference', 'value', or 'array'
+ */
+ public abstract boolean isBaseToken();
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/RangePtg.java b/src/java/org/apache/poi/hssf/record/formula/RangePtg.java
index 09bedaecf6..05cb6defe1 100644
--- a/src/java/org/apache/poi/hssf/record/formula/RangePtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/RangePtg.java
@@ -23,8 +23,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
/**
* @author Daniel Noll (daniel at nuix dot com dot au)
*/
-public class RangePtg extends OperationPtg
-{
+public final class RangePtg extends OperationPtg {
public final static int SIZE = 1;
public final static byte sid = 0x11;
@@ -37,6 +36,10 @@ public class RangePtg extends OperationPtg
// No contents
}
+ public final boolean isBaseToken() {
+ return true;
+ }
+
public int getSize()
{
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 1ae56cf52e..fa1563c040 100644
--- a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
@@ -35,8 +35,7 @@ import org.apache.poi.util.LittleEndian;
* @author Jason Height (jheight at chariot dot net dot au)
* @version 1.0-pre
*/
-
-public class Ref3DPtg extends Ptg {
+public class Ref3DPtg extends OperandPtg {
public final static byte sid = 0x3a;
private final static int SIZE = 7; // 6 + 1 for Ptg
private short field_1_index_extern_sheet;
diff --git a/src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java b/src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java
index 031fa41120..fed32ddade 100755
--- a/src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java
@@ -28,9 +28,8 @@ import org.apache.poi.hssf.record.RecordInputStream;
* RefError - handles deleted cell reference
* @author Jason Height (jheight at chariot dot net dot au)
*/
+public final class RefErrorPtg extends OperandPtg {
-public class RefErrorPtg extends Ptg
-{
private final static int SIZE = 5;
public final static byte sid = 0x2a;
private int field_1_reserved;
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 4486ec087c..d06507e50e 100644
--- a/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
@@ -30,14 +30,14 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Andrew C. Oliver (acoliver@apache.org)
* @author Jason Height (jheight at chariot dot net dot au)
*/
-public class ReferencePtg extends Ptg {
+public class ReferencePtg extends OperandPtg {
/**
* TODO - (May-2008) fix subclasses of ReferencePtg 'RefN~' which are used in shared formulas.
* (See bugzilla 44921)
- * The 'RefN~' instances do not work properly, and are expected to be converted by
- * SharedFormulaRecord.convertSharedFormulas().
- * This conversion currently does not take place for formulas of named ranges, conditional
- * format rules and data validation rules.
+ * The 'RefN~' instances do not work properly, and are expected to be converted by
+ * SharedFormulaRecord.convertSharedFormulas().
+ * This conversion currently does not take place for formulas of named ranges, conditional
+ * format rules and data validation rules.
* Furthermore, conversion is probably not appropriate in those instances.
*/
protected final RuntimeException notImplemented() {
@@ -46,14 +46,14 @@ 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;
+ private final static int MAX_ROW_NUMBER = 65536;
/** 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
+ /** Field 2
+ * - lower 8 bits is the zero based unsigned byte column index
* - bit 16 - isRowRelative
- * - bit 15 - isColumnRelative
+ * - bit 15 - isColumnRelative
*/
private int field_2_col;
private static final BitField rowRelative = BitFieldFactory.getInstance(0x8000);
@@ -63,9 +63,9 @@ public class ReferencePtg extends Ptg {
protected ReferencePtg() {
//Required for clone methods
}
-
+
/**
- * Takes in a String represnetation of a cell reference and fills out the
+ * Takes in a String represnetation of a cell reference and fills out the
* numeric fields.
*/
public ReferencePtg(String cellref) {
@@ -75,13 +75,13 @@ public class ReferencePtg extends Ptg {
setColRelative(!c.isColAbsolute());
setRowRelative(!c.isRowAbsolute());
}
-
+
public ReferencePtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) {
setRow(row);
setColumn(column);
setRowRelative(isRowRelative);
setColRelative(isColumnRelative);
- }
+ }
/** Creates new ValueReferencePtg */
@@ -90,22 +90,19 @@ public class ReferencePtg extends Ptg {
field_1_row = in.readUShort();
field_2_col = in.readUShort();
}
-
+
public String getRefPtgName() {
return "ReferencePtg";
- }
+ }
- public String toString()
- {
- StringBuffer buffer = new StringBuffer("[");
- buffer.append(getRefPtgName());
- buffer.append("]\n");
-
- buffer.append("row = ").append(getRow()).append("\n");
- buffer.append("col = ").append(getColumn()).append("\n");
- buffer.append("rowrelative = ").append(isRowRelative()).append("\n");
- buffer.append("colrelative = ").append(isColRelative()).append("\n");
- return buffer.toString();
+ public String toString() {
+ CellReference cr = new CellReference(getRow(), getColumn(), !isRowRelative(),!isColRelative());
+ StringBuffer sb = new StringBuffer();
+ sb.append(getClass().getName());
+ sb.append(" [");
+ sb.append(cr.formatAsString());
+ sb.append("]");
+ return sb.toString();
}
public void writeBytes(byte [] array, int offset)
@@ -147,16 +144,16 @@ public class ReferencePtg extends Ptg {
{
return rowRelative.isSet(field_2_col);
}
-
+
public void setRowRelative(boolean rel) {
field_2_col=rowRelative.setBoolean(field_2_col,rel);
}
-
+
public boolean isColRelative()
{
return colRelative.isSet(field_2_col);
}
-
+
public void setColRelative(boolean rel) {
field_2_col=colRelative.setBoolean(field_2_col,rel);
}
@@ -193,11 +190,11 @@ public class ReferencePtg extends Ptg {
//TODO -- should we store a cellreference instance in this ptg?? but .. memory is an issue, i believe!
return (new CellReference(getRowAsInt(),getColumn(),!isRowRelative(),!isColRelative())).formatAsString();
}
-
+
public byte getDefaultOperandClass() {
return Ptg.CLASS_REF;
}
-
+
public Object clone() {
ReferencePtg ptg = new ReferencePtg();
ptg.field_1_row = field_1_row;
diff --git a/src/java/org/apache/poi/hssf/record/formula/ScalarConstantPtg.java b/src/java/org/apache/poi/hssf/record/formula/ScalarConstantPtg.java
new file mode 100644
index 0000000000..43b8c13920
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/formula/ScalarConstantPtg.java
@@ -0,0 +1,31 @@
+/* ====================================================================
+ 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;
+
+/**
+ * @author Josh Micich
+ */
+abstract class ScalarConstantPtg extends Ptg {
+ public boolean isBaseToken() {
+ return true;
+ }
+ public final byte getDefaultOperandClass() {
+ return Ptg.CLASS_VALUE;
+ }
+
+}
diff --git a/src/java/org/apache/poi/hssf/record/formula/StringPtg.java b/src/java/org/apache/poi/hssf/record/formula/StringPtg.java
index c90590d1b8..6cd65005e2 100644
--- a/src/java/org/apache/poi/hssf/record/formula/StringPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/StringPtg.java
@@ -31,7 +31,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Jason Height (jheight at chariot dot net dot au)
* @author Bernard Chesnoy
*/
-public final class StringPtg extends Ptg {
+public final class StringPtg extends ScalarConstantPtg {
public final static int SIZE = 9;
public final static byte sid = 0x17;
private static final BitField fHighByte = BitFieldFactory.getInstance(0x01);
@@ -124,10 +124,6 @@ public final class StringPtg extends Ptg {
return sb.toString();
}
- public byte getDefaultOperandClass() {
- return Ptg.CLASS_VALUE;
- }
-
public Object clone() {
StringPtg ptg = new StringPtg();
ptg.field_1_length = field_1_length;
diff --git a/src/java/org/apache/poi/hssf/record/formula/SubtractPtg.java b/src/java/org/apache/poi/hssf/record/formula/SubtractPtg.java
index 6d1d1e860d..fc99293fdc 100644
--- a/src/java/org/apache/poi/hssf/record/formula/SubtractPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/SubtractPtg.java
@@ -26,10 +26,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author andy
* @author Jason Height (jheight at chariot dot net dot au)
*/
-
-public class SubtractPtg
- extends OperationPtg
-{
+public final class SubtractPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x04;
diff --git a/src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java b/src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java
index d85cc4913e..296cf25c59 100644
--- a/src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java
@@ -28,8 +28,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Avik Sengupta
*/
-public class UnaryMinusPtg extends OperationPtg
-{
+public final class UnaryMinusPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x13;
@@ -82,8 +81,6 @@ public class UnaryMinusPtg extends OperationPtg
return buffer.toString();
}
- public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
-
public Object clone() {
return new UnaryPlusPtg();
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/UnaryPlusPtg.java b/src/java/org/apache/poi/hssf/record/formula/UnaryPlusPtg.java
index 6ae89cf2c1..eef161e441 100644
--- a/src/java/org/apache/poi/hssf/record/formula/UnaryPlusPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/UnaryPlusPtg.java
@@ -28,8 +28,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Avik Sengupta
*/
-public class UnaryPlusPtg extends OperationPtg
-{
+public final class UnaryPlusPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x12;
@@ -82,8 +81,6 @@ public class UnaryPlusPtg extends OperationPtg
return buffer.toString();
}
- public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
-
public Object clone() {
return new UnaryPlusPtg();
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/UnionPtg.java b/src/java/org/apache/poi/hssf/record/formula/UnionPtg.java
index 3b671e22fd..4abc33b868 100644
--- a/src/java/org/apache/poi/hssf/record/formula/UnionPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/UnionPtg.java
@@ -23,8 +23,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
/**
* @author Glen Stampoultzis (glens at apache.org)
*/
-public class UnionPtg extends OperationPtg
-{
+public final class UnionPtg extends OperationPtg {
public final static byte sid = 0x10;
@@ -37,6 +36,9 @@ public class UnionPtg extends OperationPtg
// doesn't need anything
}
+ public final boolean isBaseToken() {
+ return true;
+ }
public int getSize()
{
diff --git a/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java b/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java
index af5ebc8441..07749022ed 100644
--- a/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java
@@ -24,10 +24,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author andy
* @author Jason Height (jheight at chariot dot net dot au)
*/
-
-public class UnknownPtg
- extends Ptg
-{
+public class UnknownPtg extends Ptg {
private short size = 1;
/** Creates new UnknownPtg */
@@ -36,12 +33,13 @@ public class UnknownPtg
{
}
- public UnknownPtg(RecordInputStream in)
- {
-
+ public UnknownPtg(RecordInputStream in) {
// doesn't need anything
}
+ public boolean isBaseToken() {
+ return true;
+ }
public void writeBytes(byte [] array, int offset)
{
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/ValueOperatorPtg.java b/src/java/org/apache/poi/hssf/record/formula/ValueOperatorPtg.java
new file mode 100644
index 0000000000..4ef6ab595d
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/formula/ValueOperatorPtg.java
@@ -0,0 +1,37 @@
+/* ====================================================================
+ 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 superclass of all value operators.
+ * Subclasses include all unary and binary operators except for the reference operators (IntersectionPtg, RangePtg, UnionPtg)
+ *
+ * @author Josh Micich
+ */
+public abstract class ValueOperatorPtg extends OperationPtg {
+
+ /**
+ * All Operator Ptgs are base tokens (i.e. are not RVA classifed)
+ */
+ public final boolean isBaseToken() {
+ return true;
+ }
+ public final byte getDefaultOperandClass() {
+ return Ptg.CLASS_VALUE;
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java b/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java
index b1c5c66e08..697c33b9e2 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java
@@ -55,36 +55,72 @@ public class HSSFObjectData
this.record = record;
this.poifs = poifs;
}
+
+ /**
+ * Returns the OLE2 Class Name of the object
+ */
+ public String getOLE2ClassName() {
+ EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
+ return subRecord.field_5_ole_classname;
+ }
/**
- * Gets the object data.
+ * Gets the object data. Only call for ones that have
+ * data though. See {@link #hasDirectoryEntry()}
*
* @return the object data as an OLE2 directory.
* @throws IOException if there was an error reading the data.
*/
- public DirectoryEntry getDirectory() throws IOException
- {
- Iterator subRecordIter = record.getSubRecords().iterator();
- while (subRecordIter.hasNext())
- {
- Object subRecord = subRecordIter.next();
- if (subRecord instanceof EmbeddedObjectRefSubRecord)
- {
- int streamId = ((EmbeddedObjectRefSubRecord) subRecord).getStreamId();
- String streamName = "MBD" + HexDump.toHex(streamId);
+ public DirectoryEntry getDirectory() throws IOException {
+ EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
- Entry entry = poifs.getRoot().getEntry(streamName);
- if (entry instanceof DirectoryEntry)
- {
- return (DirectoryEntry) entry;
- }
- else
- {
- throw new IOException("Stream " + streamName + " was not an OLE2 directory");
- }
+ int streamId = ((EmbeddedObjectRefSubRecord) subRecord).getStreamId();
+ String streamName = "MBD" + HexDump.toHex(streamId);
+
+ Entry entry = poifs.getRoot().getEntry(streamName);
+ if (entry instanceof DirectoryEntry) {
+ return (DirectoryEntry) entry;
+ } else {
+ throw new IOException("Stream " + streamName + " was not an OLE2 directory");
+ }
+ }
+
+ /**
+ * Returns the data portion, for an ObjectData
+ * that doesn't have an associated POIFS Directory
+ * Entry
+ */
+ public byte[] getObjectData() {
+ EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
+ return subRecord.remainingBytes;
+ }
+
+ /**
+ * Does this ObjectData have an associated POIFS
+ * Directory Entry?
+ * (Not all do, those that don't have a data portion)
+ */
+ public boolean hasDirectoryEntry() {
+ EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
+
+ // Field 6 tells you
+ return (subRecord.field_6_stream_id != 0);
+ }
+
+ /**
+ * Finds the EmbeddedObjectRefSubRecord, or throws an
+ * Exception if there wasn't one
+ */
+ protected EmbeddedObjectRefSubRecord findObjectRecord() {
+ Iterator subRecordIter = record.getSubRecords().iterator();
+
+ while (subRecordIter.hasNext()) {
+ Object subRecord = subRecordIter.next();
+ if (subRecord instanceof EmbeddedObjectRefSubRecord) {
+ return (EmbeddedObjectRefSubRecord)subRecord;
}
}
-
+
throw new IllegalStateException("Object data does not contain a reference to an embedded object OLE2 directory");
}
}
diff --git a/src/java/org/apache/poi/ss/usermodel/DateUtil.java b/src/java/org/apache/poi/ss/usermodel/DateUtil.java
index 0a9bdcfe88..0215af0126 100644
--- a/src/java/org/apache/poi/ss/usermodel/DateUtil.java
+++ b/src/java/org/apache/poi/ss/usermodel/DateUtil.java
@@ -220,9 +220,13 @@ public class DateUtil
// switching stuff, which we can ignore
fs = fs.replaceAll(";@", "");
- // If it starts with [$-...], then it is a date, but
+ // If it starts with [$-...], then could be a date, but
// who knows what that starting bit is all about
- fs = fs.replaceAll("\\[\\$\\-.*?\\]", "");
+ fs = fs.replaceAll("^\\[\\$\\-.*?\\]", "");
+
+ // If it starts with something like [Black] or [Yellow],
+ // then it could be a date
+ fs = fs.replaceAll("^\\[[a-zA-Z]+\\]", "");
// Otherwise, check it's only made up, in any case, of:
// y m d h s - / , . :
diff --git a/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java b/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java
index 36916e9c81..0ecde63049 100644
--- a/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java
+++ b/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java
@@ -330,31 +330,27 @@ public class FormulaEvaluator {
}
private static ValueEval evaluateCell(Workbook workbook, Sheet sheet,
int srcRowNum, short srcColNum, String cellFormulaText) {
-
- FormulaParser parser =
- new FormulaParser(cellFormulaText, workbook);
-
- parser.parse();
- Ptg[] ptgs = parser.getRPNPtg();
- // -- parsing over --
-
+ Ptg[] ptgs = FormulaParser.parse(cellFormulaText, workbook);
Stack stack = new Stack();
for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
// since we don't know how to handle these yet :(
Ptg ptg = ptgs[i];
- if (ptg instanceof ControlPtg) { continue; }
+ if (ptg instanceof ControlPtg) {
+ // skip Parentheses, Attr, etc
+ continue;
+ }
if (ptg instanceof MemErrPtg) { continue; }
if (ptg instanceof MissingArgPtg) { continue; }
if (ptg instanceof NamePtg) {
- // named ranges, macro functions
+ // named ranges, macro functions
NamePtg namePtg = (NamePtg) ptg;
stack.push(new NameEval(namePtg.getIndex()));
continue;
}
if (ptg instanceof NameXPtg) {
- // TODO - external functions
+ // TODO - external functions
continue;
}
if (ptg instanceof UnknownPtg) { continue; }
@@ -362,9 +358,6 @@ public class FormulaEvaluator {
if (ptg instanceof OperationPtg) {
OperationPtg optg = (OperationPtg) ptg;
- // parens can be ignored since we have RPN tokens
- if (optg instanceof ParenthesisPtg) { continue; }
- if (optg instanceof AttrPtg) { continue; }
if (optg instanceof UnionPtg) { continue; }
OperationEval operation = OperationEvaluatorFactory.create(optg);
diff --git a/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt b/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt
index 8a85f42841..31694d5d76 100644
--- a/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt
+++ b/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt
@@ -15,6 +15,7 @@
# Created by (org.apache.poi.hssf.record.formula.function.ExcelFileFormatDocFunctionExtractor)
# from source file 'excelfileformat.odt' (size=356107, md5=0x8f789cb6e75594caf068f8e193004ef4)
+# ! + some manual edits !
#
#Columns: (index, name, minParams, maxParams, returnClass, paramClasses, isVolatile, hasFootnote )
@@ -78,8 +79,8 @@
58 NPER 3 5 V V V V V V
59 PMT 3 5 V V V V V V
60 RATE 3 6 V V V V V V V
-61 MIRR 3 3 V R V V
-62 IRR 1 2 V R V
+61 MIRR 3 3 V A V V
+62 IRR 1 2 V A V
63 RAND 0 0 V - x
64 MATCH 2 3 V V R R
65 DATE 3 3 V V V V
@@ -93,8 +94,8 @@
73 SECOND 1 1 V V
74 NOW 0 0 V - x
75 AREAS 1 1 V R
-76 ROWS 1 1 V R
-77 COLUMNS 1 1 V R
+76 ROWS 1 1 V A
+77 COLUMNS 1 1 V A
78 OFFSET 3 5 R R V V V V x
82 SEARCH 2 3 V V V V
83 TRANSPOSE 1 1 A A
diff --git a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFChart.java b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFChart.java
index 5b48501938..e3f5bb23e9 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFChart.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFChart.java
@@ -213,6 +213,23 @@ public class HSSFChart
charts.toArray( new HSSFChart[charts.size()] );
}
+ /** Get the X offset of the chart */
+ public int getChartX() { return chartRecord.getX(); }
+ /** Get the Y offset of the chart */
+ public int getChartY() { return chartRecord.getY(); }
+ /** Get the width of the chart. {@link ChartRecord} */
+ public int getChartWidth() { return chartRecord.getWidth(); }
+ /** Get the height of the chart. {@link ChartRecord} */
+ public int getChartHeight() { return chartRecord.getHeight(); }
+
+ /** Sets the X offset of the chart */
+ public void setChartX(int x) { chartRecord.setX(x); }
+ /** Sets the Y offset of the chart */
+ public void setChartY(int y) { chartRecord.setY(y); }
+ /** Sets the width of the chart. {@link ChartRecord} */
+ public void setChartWidth(int width) { chartRecord.setWidth(width); }
+ /** Sets the height of the chart. {@link ChartRecord} */
+ public void setChartHeight(int height) { chartRecord.setHeight(height); }
/**
* Returns the series of the chart
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestHSSFChart.java b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestHSSFChart.java
index 184d46d2fe..d28b8a8777 100644
--- a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestHSSFChart.java
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestHSSFChart.java
@@ -53,6 +53,12 @@ public class TestHSSFChart extends TestCase {
assertEquals("1st Column", charts[0].getSeries()[0].getSeriesTitle());
assertEquals("2nd Column", charts[0].getSeries()[1].getSeriesTitle());
assertEquals(null, charts[0].getChartTitle());
+
+ // Check x, y, width, height
+ assertEquals(0, charts[0].getChartX());
+ assertEquals(0, charts[0].getChartY());
+ assertEquals(26492928, charts[0].getChartWidth());
+ assertEquals(15040512, charts[0].getChartHeight());
}
public void testTwoCharts() throws Exception {
diff --git a/src/testcases/org/apache/poi/hssf/data/ex42564-elementOrder.xls b/src/testcases/org/apache/poi/hssf/data/ex42564-elementOrder.xls
new file mode 100644
index 0000000000..3c49fc2572
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/ex42564-elementOrder.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/testRVA.xls b/src/testcases/org/apache/poi/hssf/data/testRVA.xls
new file mode 100644
index 0000000000..327edbb4cb
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/testRVA.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/model/AllModelTests.java b/src/testcases/org/apache/poi/hssf/model/AllModelTests.java
index 19ef437063..045e371a22 100755
--- a/src/testcases/org/apache/poi/hssf/model/AllModelTests.java
+++ b/src/testcases/org/apache/poi/hssf/model/AllModelTests.java
@@ -33,6 +33,9 @@ public final class AllModelTests {
result.addTestSuite(TestDrawingManager2.class);
result.addTestSuite(TestFormulaParser.class);
result.addTestSuite(TestFormulaParserEval.class);
+ result.addTestSuite(TestFormulaParserIf.class);
+ result.addTestSuite(TestOperandClassTransformer.class);
+ result.addTestSuite(TestRVA.class);
result.addTestSuite(TestSheet.class);
result.addTestSuite(TestSheetAdditional.class);
return result;
diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
index f2821140f3..929279a3ce 100644
--- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
+++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
@@ -33,12 +33,9 @@ import org.apache.poi.hssf.record.formula.ErrPtg;
import org.apache.poi.hssf.record.formula.FuncPtg;
import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.IntPtg;
-import org.apache.poi.hssf.record.formula.LessEqualPtg;
-import org.apache.poi.hssf.record.formula.LessThanPtg;
import org.apache.poi.hssf.record.formula.MissingArgPtg;
import org.apache.poi.hssf.record.formula.MultiplyPtg;
import org.apache.poi.hssf.record.formula.NamePtg;
-import org.apache.poi.hssf.record.formula.NotEqualPtg;
import org.apache.poi.hssf.record.formula.NumberPtg;
import org.apache.poi.hssf.record.formula.PercentPtg;
import org.apache.poi.hssf.record.formula.PowerPtg;
@@ -62,10 +59,8 @@ public final class TestFormulaParser extends TestCase {
/**
* @return parsed token array already confirmed not null
*/
- private static Ptg[] parseFormula(String s) {
- FormulaParser fp = new FormulaParser(s, null);
- fp.parse();
- Ptg[] result = fp.getRPNPtg();
+ /* package */ static Ptg[] parseFormula(String formula) {
+ Ptg[] result = FormulaParser.parse(formula, null);
assertNotNull("Ptg array should not be null", result);
return result;
}
@@ -105,83 +100,6 @@ public final class TestFormulaParser extends TestCase {
assertEquals(true, flag.getValue());
}
- public void testYN() {
- Ptg[] ptgs = parseFormula("IF(TRUE,\"Y\",\"N\")");
- assertEquals(7, ptgs.length);
-
- BoolPtg flag = (BoolPtg) ptgs[0];
- AttrPtg funif = (AttrPtg) ptgs[1];
- StringPtg y = (StringPtg) ptgs[2];
- AttrPtg goto1 = (AttrPtg) ptgs[3];
- StringPtg n = (StringPtg) ptgs[4];
-
-
- assertEquals(true, flag.getValue());
- assertEquals("Y", y.getValue());
- assertEquals("N", n.getValue());
- assertEquals("IF", funif.toFormulaString((HSSFWorkbook) null));
- assertTrue("Goto ptg exists", goto1.isGoto());
- }
-
- public void testSimpleIf() {
- String formula = "IF(1=1,0,1)";
-
- Class[] expectedClasses = {
- IntPtg.class,
- IntPtg.class,
- EqualPtg.class,
- AttrPtg.class,
- IntPtg.class,
- AttrPtg.class,
- IntPtg.class,
- AttrPtg.class,
- FuncVarPtg.class,
- };
- confirmTokenClasses(formula, expectedClasses);
-
- Ptg[] ptgs = parseFormula(formula);
-
- AttrPtg ifPtg = (AttrPtg) ptgs[3];
- AttrPtg ptgGoto= (AttrPtg) ptgs[5];
- assertEquals("Goto 1 Length", 10, ptgGoto.getData());
-
- AttrPtg ptgGoto2 = (AttrPtg) ptgs[7];
- assertEquals("Goto 2 Length", 3, ptgGoto2.getData());
- assertEquals("If FALSE offset", 7, ifPtg.getData());
- }
-
- /**
- * Make sure the ptgs are generated properly with two functions embedded
- *
- */
- public void testNestedFunctionIf() {
- Ptg[] ptgs = parseFormula("IF(A1=B1,AVERAGE(A1:B1),AVERAGE(A2:B2))");
- assertEquals(11, ptgs.length);
-
- assertTrue("IF Attr set correctly", (ptgs[3] instanceof AttrPtg));
- AttrPtg ifFunc = (AttrPtg)ptgs[3];
- assertTrue("It is not an if", ifFunc.isOptimizedIf());
-
- assertTrue("Average Function set correctly", (ptgs[5] instanceof FuncVarPtg));
- }
-
- public void testIfSingleCondition(){
- Ptg[] ptgs = parseFormula("IF(1=1,10)");
- assertEquals(7, ptgs.length);
-
- assertTrue("IF Attr set correctly", (ptgs[3] instanceof AttrPtg));
- AttrPtg ifFunc = (AttrPtg)ptgs[3];
- assertTrue("It is not an if", ifFunc.isOptimizedIf());
-
- assertTrue("Single Value is not an IntPtg", (ptgs[4] instanceof IntPtg));
- IntPtg intPtg = (IntPtg)ptgs[4];
- assertEquals("Result", (short)10, intPtg.getValue());
-
- assertTrue("Ptg is not a Variable Function", (ptgs[6] instanceof FuncVarPtg));
- FuncVarPtg funcPtg = (FuncVarPtg)ptgs[6];
- assertEquals("Arguments", 2, funcPtg.getNumberOfOperands());
- }
-
public void testSumIf() {
Ptg[] ptgs = parseFormula("SUMIF(A1:A5,\">4000\",B1:B5)");
assertEquals(4, ptgs.length);
@@ -203,33 +121,9 @@ public final class TestFormulaParser extends TestCase {
//the PTG order isn't 100% correct but it still works - dmui
}
- public void testSimpleLogical() {
- Ptg[] ptgs = parseFormula("IF(A1INDEX(PI(),1)
, Excel encodes PI() as 'array'. It is not clear
+ * what rule justifies this. POI currently encodes it as 'value' which Excel(2007) seems to
+ * tolerate. Changing the metadata for INDEX to have first parameter as 'array' class breaks
+ * other formulas involving INDEX. It seems like a special case needs to be made. Perhaps an
+ * important observation is that INDEX is one of very few functions that returns 'reference' type.
+ *
+ * This test has been added but disabled in order to document this issue.
+ */
+ public void DISABLED_testIndexPi1() {
+ String formula = "INDEX(PI(),1)";
+ Ptg[] ptgs = FormulaParser.parse(formula, null);
+
+ confirmFuncClass(ptgs, 1, "PI", Ptg.CLASS_ARRAY); // fails as of POI 3.1
+ confirmFuncClass(ptgs, 2, "INDEX", Ptg.CLASS_VALUE);
+ }
+
+ public void testComplexIRR_bug45041() {
+ String formula = "(1+IRR(SUMIF(A:A,ROW(INDIRECT(MIN(A:A)&\":\"&MAX(A:A))),B:B),0))^365-1";
+ Ptg[] ptgs = FormulaParser.parse(formula, null);
+
+ FuncVarPtg rowFunc = (FuncVarPtg) ptgs[10];
+ FuncVarPtg sumifFunc = (FuncVarPtg) ptgs[12];
+ assertEquals("ROW", rowFunc.getName());
+ assertEquals("SUMIF", sumifFunc.getName());
+
+ if (rowFunc.getPtgClass() == Ptg.CLASS_VALUE || sumifFunc.getPtgClass() == Ptg.CLASS_VALUE) {
+ throw new AssertionFailedError("Identified bug 45041");
+ }
+ confirmTokenClass(ptgs, 1, Ptg.CLASS_REF);
+ confirmTokenClass(ptgs, 2, Ptg.CLASS_REF);
+ confirmFuncClass(ptgs, 3, "MIN", Ptg.CLASS_VALUE);
+ confirmTokenClass(ptgs, 6, Ptg.CLASS_REF);
+ confirmFuncClass(ptgs, 7, "MAX", Ptg.CLASS_VALUE);
+ confirmFuncClass(ptgs, 9, "INDIRECT", Ptg.CLASS_REF);
+ confirmFuncClass(ptgs, 10, "ROW", Ptg.CLASS_ARRAY);
+ confirmTokenClass(ptgs, 11, Ptg.CLASS_REF);
+ confirmFuncClass(ptgs, 12, "SUMIF", Ptg.CLASS_ARRAY);
+ confirmFuncClass(ptgs, 14, "IRR", Ptg.CLASS_VALUE);
+ }
+
+ private void confirmFuncClass(Ptg[] ptgs, int i, String expectedFunctionName, byte operandClass) {
+ confirmTokenClass(ptgs, i, operandClass);
+ AbstractFunctionPtg afp = (AbstractFunctionPtg) ptgs[i];
+ assertEquals(expectedFunctionName, afp.getName());
+ }
+
+ private void confirmTokenClass(Ptg[] ptgs, int i, byte operandClass) {
+ Ptg ptg = ptgs[i];
+ if (operandClass != ptg.getPtgClass()) {
+ throw new AssertionFailedError("Wrong operand class for function ptg ("
+ + ptg.toString() + "). Expected " + getOperandClassName(operandClass)
+ + " but got " + getOperandClassName(ptg.getPtgClass()));
+ }
+ }
+
+ private static String getOperandClassName(byte ptgClass) {
+ switch (ptgClass) {
+ case Ptg.CLASS_REF:
+ return "R";
+ case Ptg.CLASS_VALUE:
+ return "V";
+ case Ptg.CLASS_ARRAY:
+ return "A";
+ }
+ throw new RuntimeException("Unknown operand class (" + ptgClass + ")");
+ }
+}
diff --git a/src/testcases/org/apache/poi/hssf/model/TestRVA.java b/src/testcases/org/apache/poi/hssf/model/TestRVA.java
new file mode 100644
index 0000000000..cb51c17bd7
--- /dev/null
+++ b/src/testcases/org/apache/poi/hssf/model/TestRVA.java
@@ -0,0 +1,156 @@
+/* ====================================================================
+ 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 junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.hssf.record.formula.AttrPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.ReferencePtg;
+import org.apache.poi.hssf.usermodel.FormulaExtractor;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+/**
+ * Tests 'operand class' transformation performed by
+ * OperandClassTransformer by comparing its results with those
+ * directly produced by Excel (in a sample spreadsheet).
+ *
+ * @author Josh Micich
+ */
+public final class TestRVA extends TestCase {
+
+ private static final String NEW_LINE = System.getProperty("line.separator");
+
+ public void testFormulas() {
+ HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("testRVA.xls");
+ HSSFSheet sheet = wb.getSheetAt(0);
+
+ int countFailures = 0;
+ int countErrors = 0;
+
+ int rowIx = 0;
+ while (rowIx < 65535) {
+ HSSFRow row = sheet.getRow(rowIx);
+ if (row == null) {
+ break;
+ }
+ HSSFCell cell = row.getCell(0);
+ if (cell == null || cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) {
+ break;
+ }
+ String formula = cell.getCellFormula();
+ try {
+ confirmCell(cell, formula);
+ } catch (AssertionFailedError e) {
+ System.err.println("Problem with row[" + rowIx + "] formula '" + formula + "'");
+ System.err.println(e.getMessage());
+ countFailures++;
+ } catch (RuntimeException e) {
+ System.err.println("Problem with row[" + rowIx + "] formula '" + formula + "'");
+ countErrors++;
+ e.printStackTrace();
+ }
+ rowIx++;
+ }
+ if (countErrors + countFailures > 0) {
+ String msg = "One or more RVA tests failed: countFailures=" + countFailures
+ + " countFailures=" + countErrors + ". See stderr for details.";
+ throw new AssertionFailedError(msg);
+ }
+ }
+
+ private void confirmCell(HSSFCell formulaCell, String formula) {
+ Ptg[] excelPtgs = FormulaExtractor.getPtgs(formulaCell);
+ Ptg[] poiPtgs = FormulaParser.parse(formula, null);
+ int nExcelTokens = excelPtgs.length;
+ int nPoiTokens = poiPtgs.length;
+ if (nExcelTokens != nPoiTokens) {
+ if (nExcelTokens == nPoiTokens + 1 && excelPtgs[0].getClass() == AttrPtg.class) {
+ // compensate for missing tAttrVolatile, which belongs in any formula
+ // involving OFFSET() et al. POI currently does not insert where required
+ Ptg[] temp = new Ptg[nExcelTokens];
+ temp[0] = excelPtgs[0];
+ System.arraycopy(poiPtgs, 0, temp, 1, nPoiTokens);
+ poiPtgs = temp;
+ } else {
+ throw new RuntimeException("Expected " + nExcelTokens + " tokens but got "
+ + nPoiTokens);
+ }
+ }
+ boolean hasMismatch = false;
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < nExcelTokens; i++) {
+ Ptg poiPtg = poiPtgs[i];
+ Ptg excelPtg = excelPtgs[i];
+ if (!areTokenClassesSame(poiPtg, excelPtg)) {
+ hasMismatch = true;
+ sb.append(" mismatch token type[" + i + "] " + getShortClassName(excelPtg) + " "
+ + getOperandClassName(excelPtg) + " - " + getShortClassName(poiPtg) + " "
+ + getOperandClassName(poiPtg));
+ sb.append(NEW_LINE);
+ continue;
+ }
+ if (poiPtg.isBaseToken()) {
+ continue;
+ }
+ sb.append(" token[" + i + "] " + excelPtg.toString() + " "
+ + getOperandClassName(excelPtg));
+
+ if (excelPtg.getPtgClass() != poiPtg.getPtgClass()) {
+ hasMismatch = true;
+ sb.append(" - was " + getOperandClassName(poiPtg));
+ }
+ sb.append(NEW_LINE);
+ }
+ if (hasMismatch) {
+ throw new AssertionFailedError(sb.toString());
+ }
+ }
+
+ private boolean areTokenClassesSame(Ptg poiPtg, Ptg excelPtg) {
+ if (excelPtg.getClass() == poiPtg.getClass()) {
+ return true;
+ }
+ if (poiPtg.getClass() == ReferencePtg.class) {
+ // TODO - remove funny subclasses of ReferencePtg
+ return excelPtg instanceof ReferencePtg;
+ }
+ return false;
+ }
+
+ private String getShortClassName(Object o) {
+ String cn = o.getClass().getName();
+ int pos = cn.lastIndexOf('.');
+ return cn.substring(pos + 1);
+ }
+
+ private static String getOperandClassName(Ptg ptg) {
+ byte ptgClass = ptg.getPtgClass();
+ switch (ptgClass) {
+ case Ptg.CLASS_REF: return "R";
+ case Ptg.CLASS_VALUE: return "V";
+ case Ptg.CLASS_ARRAY: return "A";
+ }
+ throw new RuntimeException("Unknown operand class (" + ptgClass + ")");
+ }
+}
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestArrayPtg.java b/src/testcases/org/apache/poi/hssf/record/formula/TestArrayPtg.java
index 16f80bb791..39464b5e03 100644
--- a/src/testcases/org/apache/poi/hssf/record/formula/TestArrayPtg.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/TestArrayPtg.java
@@ -19,8 +19,10 @@ package org.apache.poi.hssf.record.formula;
import java.util.Arrays;
+import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.record.TestcaseRecordInputStream;
import org.apache.poi.hssf.record.UnicodeString;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
@@ -77,7 +79,7 @@ public final class TestArrayPtg extends TestCase {
}
/**
- * make sure constant elements are stored row by row
+ * Excel stores array elements column by column. This test makes sure POI does the same.
*/
public void testElementOrdering() {
ArrayPtg ptg = new ArrayPtgV(new TestcaseRecordInputStream(ArrayPtgV.sid, ENCODED_PTG_DATA));
@@ -86,10 +88,27 @@ public final class TestArrayPtg extends TestCase {
assertEquals(2, ptg.getRowCount());
assertEquals(0, ptg.getValueIndex(0, 0));
- assertEquals(1, ptg.getValueIndex(1, 0));
- assertEquals(2, ptg.getValueIndex(2, 0));
- assertEquals(3, ptg.getValueIndex(0, 1));
- assertEquals(4, ptg.getValueIndex(1, 1));
+ assertEquals(2, ptg.getValueIndex(1, 0));
+ assertEquals(4, ptg.getValueIndex(2, 0));
+ assertEquals(1, ptg.getValueIndex(0, 1));
+ assertEquals(3, ptg.getValueIndex(1, 1));
assertEquals(5, ptg.getValueIndex(2, 1));
}
+
+ /**
+ * Test for a bug which was temporarily introduced by the fix for bug 42564.
+ * A spreadsheet was added to make the ordering clearer.
+ */
+ public void testElementOrderingInSpreadsheet() {
+ HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("ex42564-elementOrder.xls");
+
+ // The formula has an array with 3 rows and 5 column
+ String formula = wb.getSheetAt(0).getRow(0).getCell((short)0).getCellFormula();
+ // TODO - These number literals should not have '.0'. Excel has different number rendering rules
+
+ if (formula.equals("SUM({1.0,6.0,11.0;2.0,7.0,12.0;3.0,8.0,13.0;4.0,9.0,14.0;5.0,10.0,15.0})")) {
+ throw new AssertionFailedError("Identified bug 42564 b");
+ }
+ assertEquals("SUM({1.0,2.0,3.0;4.0,5.0,6.0;7.0,8.0,9.0;10.0,11.0,12.0;13.0,14.0,15.0})", formula);
+ }
}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/FormulaExtractor.java b/src/testcases/org/apache/poi/hssf/usermodel/FormulaExtractor.java
new file mode 100644
index 0000000000..d657647eae
--- /dev/null
+++ b/src/testcases/org/apache/poi/hssf/usermodel/FormulaExtractor.java
@@ -0,0 +1,49 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel;
+
+import java.util.List;
+
+import org.apache.poi.hssf.record.CellValueRecordInterface;
+import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
+import org.apache.poi.hssf.record.formula.Ptg;
+
+/**
+ * Test utility class to get Ptg arrays out of formula cells
+ *
+ * @author Josh Micich
+ */
+public final class FormulaExtractor {
+
+ private FormulaExtractor() {
+ // no instances of this class
+ }
+
+ public static Ptg[] getPtgs(HSSFCell cell) {
+ CellValueRecordInterface vr = cell.getCellValueRecord();
+ if (!(vr instanceof FormulaRecordAggregate)) {
+ throw new IllegalArgumentException("Not a formula cell");
+ }
+ FormulaRecordAggregate fra = (FormulaRecordAggregate) vr;
+ List tokens = fra.getFormulaRecord().getParsedExpression();
+ Ptg[] result = new Ptg[tokens.size()];
+ tokens.toArray(result);
+ return result;
+ }
+
+}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
index 0e0a656341..419bc33bb4 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
@@ -18,9 +18,11 @@
package org.apache.poi.hssf.usermodel;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
+import java.util.List;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
@@ -28,6 +30,7 @@ import junit.framework.TestCase;
import org.apache.poi.ss.util.Region;
import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.hssf.record.EmbeddedObjectRefSubRecord;
import org.apache.poi.util.TempFile;
/**
@@ -951,4 +954,40 @@ public final class TestBugs extends TestCase {
writeOutAndReadBack(wb);
assertTrue("no errors writing sample xls", true);
}
+
+ /**
+ * Problems with extracting check boxes from
+ * HSSFObjectData
+ * @throws Exception
+ */
+ public void test44840() throws Exception {
+ HSSFWorkbook wb = openSample("WithCheckBoxes.xls");
+
+ // Take a look at the embeded objects
+ List objects = wb.getAllEmbeddedObjects();
+ assertEquals(1, objects.size());
+
+ HSSFObjectData obj = (HSSFObjectData)objects.get(0);
+ assertNotNull(obj);
+
+ // Peek inside the underlying record
+ EmbeddedObjectRefSubRecord rec = obj.findObjectRecord();
+ assertNotNull(rec);
+
+ assertEquals(32, rec.field_1_stream_id_offset);
+ assertEquals(0, rec.field_6_stream_id); // WRONG!
+ assertEquals("Forms.CheckBox.1", rec.field_5_ole_classname);
+ assertEquals(12, rec.remainingBytes.length);
+
+ // Doesn't have a directory
+ assertFalse(obj.hasDirectoryEntry());
+ assertNotNull(obj.getObjectData());
+ assertEquals(12, obj.getObjectData().length);
+ assertEquals("Forms.CheckBox.1", obj.getOLE2ClassName());
+
+ try {
+ obj.getDirectory();
+ fail();
+ } catch(FileNotFoundException e) {}
+ }
}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java
index 76c098da2d..4f526b61c0 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java
@@ -257,9 +257,15 @@ public class TestHSSFDateUtil extends TestCase {
// (who knows what they mean though...)
"[$-F800]dddd\\,\\ mmm\\ dd\\,\\ yyyy",
"[$-F900]ddd/mm/yyy",
+ // These ones specify colours, who knew that was allowed?
+ "[BLACK]dddd/mm/yy",
+ "[yeLLow]yyyy-mm-dd"
};
for(int i=0; i