Added support for parsing array constants in formulas. (Helping investigation for bug 45752)

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@693591 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-09-09 20:25:16 +00:00
parent 78101afee8
commit f501145768
9 changed files with 322 additions and 129 deletions

View File

@ -22,9 +22,12 @@ import java.util.List;
import java.util.Stack;
//import PTGs .. since we need everything, import *
import org.apache.poi.hssf.record.UnicodeString;
import org.apache.poi.hssf.record.constant.ErrorConstant;
import org.apache.poi.hssf.record.formula.*;
import org.apache.poi.hssf.record.formula.function.FunctionMetadata;
import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry;
import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFName;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
@ -69,9 +72,9 @@ public final class FormulaParser {
public static final int FORMULA_TYPE_ARRAY =2;
public static final int FORMULA_TYPE_CONDFORMAT = 3;
public static final int FORMULA_TYPE_NAMEDRANGE = 4;
// this constant is currently very specific. The exact differences from general data
// this constant is currently very specific. The exact differences from general data
// validation formulas or conditional format formulas is not known yet
public static final int FORMULA_TYPE_DATAVALIDATION_LIST = 5;
public static final int FORMULA_TYPE_DATAVALIDATION_LIST = 5;
private final String formulaString;
private final int formulaLength;
@ -139,9 +142,9 @@ public final class FormulaParser {
/** Report What Was Expected */
private RuntimeException expected(String s) {
String msg;
if (look == '=' && formulaString.substring(0, pointer-1).trim().length() < 1) {
msg = "The specified formula '" + formulaString
msg = "The specified formula '" + formulaString
+ "' starts with an equals sign which is not allowed.";
} else {
msg = "Parse error near char " + (pointer-1) + " '" + look + "'"
@ -193,8 +196,8 @@ public final class FormulaParser {
/**
* Parses a sheet name, named range name, or simple cell reference.<br/>
* Note - identifiers in Excel can contain dots, so this method may return a String
* which may need to be converted to an area reference. For example, this method
* may return a value like "A1..B2", in which case the caller must convert it to
* which may need to be converted to an area reference. For example, this method
* may return a value like "A1..B2", in which case the caller must convert it to
* an area reference like "A1:B2"
*/
private String parseIdentifier() {
@ -250,7 +253,7 @@ public final class FormulaParser {
}
private Ptg parseNameOrReference(String name) {
AreaReference areaRef = parseArea(name);
if (areaRef != null) {
// will happen if dots are used instead of colon
@ -372,29 +375,29 @@ public final class FormulaParser {
private ParseNode function(String name) {
Ptg nameToken = null;
if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) {
// user defined function
// user defined function
// in the token tree, the name is more or less the first argument
int nameIndex = book.getNameIndex(name);
if (nameIndex >= 0) {
HSSFName hName = book.getNameAt(nameIndex);
if (!hName.isFunctionName()) {
throw new FormulaParseException("Attempt to use name '" + name
+ "' as a function, but defined name in workbook does not refer to a function");
}
// calls to user-defined functions within the workbook
// get a Name token which points to a defined name record
nameToken = new NamePtg(name, this.book);
} else {
nameToken = book.getNameXPtg(name);
if (nameToken == null) {
throw new FormulaParseException("Name '" + name
+ "' is completely unknown in the current workbook");
}
}
int nameIndex = book.getNameIndex(name);
if (nameIndex >= 0) {
HSSFName hName = book.getNameAt(nameIndex);
if (!hName.isFunctionName()) {
throw new FormulaParseException("Attempt to use name '" + name
+ "' as a function, but defined name in workbook does not refer to a function");
}
// calls to user-defined functions within the workbook
// get a Name token which points to a defined name record
nameToken = new NamePtg(name, this.book);
} else {
nameToken = book.getNameXPtg(name);
if (nameToken == null) {
throw new FormulaParseException("Name '" + name
+ "' is completely unknown in the current workbook");
}
}
}
Match('(');
@ -542,7 +545,7 @@ public final class FormulaParser {
SkipWhite();
switch(look) {
case '#':
return new ParseNode(parseErrorLiteral());
return new ParseNode(ErrPtg.valueOf(parseErrorLiteral()));
case '-':
Match('-');
return new ParseNode(UnaryMinusPtg.instance, powerFactor());
@ -555,7 +558,12 @@ public final class FormulaParser {
Match(')');
return new ParseNode(ParenthesisPtg.instance, inside);
case '"':
return new ParseNode(parseStringLiteral());
return new ParseNode(new StringPtg(parseStringLiteral()));
case '{':
Match('{');
ParseNode arrayNode = parseArray();
Match('}');
return arrayNode;
}
if (IsAlpha(look) || look == '\''){
return parseFunctionReferenceOrName();
@ -565,6 +573,95 @@ public final class FormulaParser {
}
private ParseNode parseArray() {
List rowsData = new ArrayList();
while(true) {
Object[] singleRowData = parseArrayRow();
rowsData.add(singleRowData);
if (look == '}') {
break;
}
if (look != ';') {
throw expected("'}' or ';'");
}
Match(';');
}
int nRows = rowsData.size();
Object[][] values2d = new Object[nRows][];
rowsData.toArray(values2d);
int nColumns = values2d[0].length;
checkRowLengths(values2d, nColumns);
return new ParseNode(new ArrayPtg(values2d));
}
private void checkRowLengths(Object[][] values2d, int nColumns) {
for (int i = 0; i < values2d.length; i++) {
int rowLen = values2d[i].length;
if (rowLen != nColumns) {
throw new FormulaParseException("Array row " + i + " has length " + rowLen
+ " but row 0 has length " + nColumns);
}
}
}
private Object[] parseArrayRow() {
List temp = new ArrayList();
while (true) {
temp.add(parseArrayItem());
SkipWhite();
switch(look) {
case '}':
case ';':
break;
case ',':
Match(',');
continue;
default:
throw expected("'}' or ','");
}
break;
}
Object[] result = new Object[temp.size()];
temp.toArray(result);
return result;
}
private Object parseArrayItem() {
SkipWhite();
switch(look) {
case '"': return new UnicodeString(parseStringLiteral());
case '#': return ErrorConstant.valueOf(parseErrorLiteral());
case 'F': case 'f':
case 'T': case 't':
return parseBooleanLiteral();
}
// else assume number
return convertArrayNumber(parseNumber());
}
private Boolean parseBooleanLiteral() {
String iden = parseIdentifier();
if ("TRUE".equalsIgnoreCase(iden)) {
return Boolean.TRUE;
}
if ("FALSE".equalsIgnoreCase(iden)) {
return Boolean.FALSE;
}
throw expected("'TRUE' or 'FALSE'");
}
private static Double convertArrayNumber(Ptg ptg) {
if (ptg instanceof IntPtg) {
return new Double(((IntPtg)ptg).getValue());
}
if (ptg instanceof NumberPtg) {
return new Double(((NumberPtg)ptg).getValue());
}
throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")");
}
private Ptg parseNumber() {
String number2 = null;
String exponent = null;
@ -601,7 +698,7 @@ public final class FormulaParser {
}
private ErrPtg parseErrorLiteral() {
private int parseErrorLiteral() {
Match('#');
String part1 = parseIdentifier().toUpperCase();
@ -609,13 +706,13 @@ public final class FormulaParser {
case 'V':
if(part1.equals("VALUE")) {
Match('!');
return ErrPtg.VALUE_INVALID;
return HSSFErrorConstants.ERROR_VALUE;
}
throw expected("#VALUE!");
case 'R':
if(part1.equals("REF")) {
Match('!');
return ErrPtg.REF_INVALID;
return HSSFErrorConstants.ERROR_REF;
}
throw expected("#REF!");
case 'D':
@ -623,21 +720,21 @@ public final class FormulaParser {
Match('/');
Match('0');
Match('!');
return ErrPtg.DIV_ZERO;
return HSSFErrorConstants.ERROR_DIV_0;
}
throw expected("#DIV/0!");
case 'N':
if(part1.equals("NAME")) {
Match('?'); // only one that ends in '?'
return ErrPtg.NAME_INVALID;
return HSSFErrorConstants.ERROR_NAME;
}
if(part1.equals("NUM")) {
Match('!');
return ErrPtg.NUM_ERROR;
return HSSFErrorConstants.ERROR_NUM;
}
if(part1.equals("NULL")) {
Match('!');
return ErrPtg.NULL_INTERSECTION;
return HSSFErrorConstants.ERROR_NULL;
}
if(part1.equals("N")) {
Match('/');
@ -646,7 +743,7 @@ public final class FormulaParser {
}
Match(look);
// Note - no '!' or '?' suffix
return ErrPtg.N_A;
return HSSFErrorConstants.ERROR_NA;
}
throw expected("#NAME?, #NUM!, #NULL! or #N/A");
@ -699,7 +796,7 @@ public final class FormulaParser {
}
private StringPtg parseStringLiteral() {
private String parseStringLiteral() {
Match('"');
StringBuffer token = new StringBuffer();
@ -713,7 +810,7 @@ public final class FormulaParser {
token.append(look);
GetChar();
}
return new StringPtg(token.toString());
return token.toString();
}
/** Parse and Translate a Math Term */
@ -970,7 +1067,7 @@ end;
}
return result;
}
private static String[] getOperands(Stack stack, int nOperands) {
String[] operands = new String[nOperands];

View File

@ -74,7 +74,7 @@ final class OperandClassTransformer {
+ _formulaType + ") not supported yet");
}
transformNode(rootNode, rootNodeOperandClass, false, false);
transformNode(rootNode, rootNodeOperandClass, false);
}
/**
@ -83,22 +83,35 @@ final class OperandClassTransformer {
* the function return value).
*/
private void transformNode(ParseNode node, byte desiredOperandClass,
boolean callerForceArrayFlag, boolean isDirectChildOfValueOperator) {
boolean callerForceArrayFlag) {
Ptg token = node.getToken();
ParseNode[] children = node.getChildren();
boolean isSimpleValueFunc = isSimpleValueFunction(token);
if (isSimpleValueFunc) {
boolean localForceArray = desiredOperandClass == Ptg.CLASS_ARRAY;
for (int i = 0; i < children.length; i++) {
transformNode(children[i], desiredOperandClass, localForceArray);
}
setSimpleValueFuncClass((AbstractFunctionPtg) token, desiredOperandClass, callerForceArrayFlag);
return;
}
if (token instanceof ValueOperatorPtg || token instanceof ControlPtg) {
// Value Operator Ptgs and Control are base tokens, so token will be unchanged
// but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag
// As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1"
// All direct operands of value operators that are initially 'R' type will
// be converted to 'V' type.
byte localDesiredOperandClass = desiredOperandClass == Ptg.CLASS_REF ? Ptg.CLASS_VALUE : desiredOperandClass;
for (int i = 0; i < children.length; i++) {
ParseNode child = children[i];
transformNode(child, desiredOperandClass, callerForceArrayFlag, true);
transformNode(children[i], localDesiredOperandClass, callerForceArrayFlag);
}
return;
}
if (token instanceof AbstractFunctionPtg) {
transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass,
callerForceArrayFlag);
transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass, callerForceArrayFlag);
return;
}
if (children.length > 0) {
@ -109,17 +122,26 @@ final class OperandClassTransformer {
// nothing to do
return;
}
if (isDirectChildOfValueOperator) {
// As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1"
// All direct operands of value operators that are initially 'R' type will
// be converted to 'V' type.
if (token.getPtgClass() == Ptg.CLASS_REF) {
token.setClass(Ptg.CLASS_VALUE);
}
}
token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag));
}
private static boolean isSimpleValueFunction(Ptg token) {
if (token instanceof AbstractFunctionPtg) {
AbstractFunctionPtg aptg = (AbstractFunctionPtg) token;
if (aptg.getDefaultOperandClass() != Ptg.CLASS_VALUE) {
return false;
}
int numberOfOperands = aptg.getNumberOfOperands();
for (int i=numberOfOperands-1; i>=0; i--) {
if (aptg.getParameterClass(i) != Ptg.CLASS_VALUE) {
return false;
}
}
return true;
}
return false;
}
private byte transformClass(byte currentOperandClass, byte desiredOperandClass,
boolean callerForceArrayFlag) {
switch (desiredOperandClass) {
@ -185,6 +207,7 @@ final class OperandClassTransformer {
switch (defaultReturnOperandClass) {
case Ptg.CLASS_REF:
afp.setClass(Ptg.CLASS_REF);
// afp.setClass(Ptg.CLASS_ARRAY);
break;
case Ptg.CLASS_VALUE:
afp.setClass(Ptg.CLASS_ARRAY);
@ -220,7 +243,17 @@ final class OperandClassTransformer {
for (int i = 0; i < children.length; i++) {
ParseNode child = children[i];
byte paramOperandClass = afp.getParameterClass(i);
transformNode(child, paramOperandClass, localForceArrayFlag, false);
transformNode(child, paramOperandClass, localForceArrayFlag);
}
}
private void setSimpleValueFuncClass(AbstractFunctionPtg afp,
byte desiredOperandClass, boolean callerForceArrayFlag) {
if (callerForceArrayFlag || desiredOperandClass == Ptg.CLASS_ARRAY) {
afp.setClass(Ptg.CLASS_ARRAY);
} else {
afp.setClass(Ptg.CLASS_VALUE);
}
}
}

View File

@ -44,8 +44,11 @@ public final class ArrayPtg extends Ptg {
* (not including the data which comes after all formula tokens)
*/
public static final int PLAIN_TOKEN_SIZE = 1+RESERVED_FIELD_LEN;
private static final byte[] DEFAULT_RESERVED_DATA = new byte[RESERVED_FIELD_LEN];
// TODO - fix up field visibility and subclasses
private byte[] field_1_reserved;
private final byte[] field_1_reserved;
// data from these fields comes after the Ptg data of all tokens in current formula
private short token_1_columns;
@ -59,8 +62,42 @@ public final class ArrayPtg extends Ptg {
field_1_reserved[i] = in.readByte();
}
}
public Object[] getTokenArrayValues() {
return (Object[]) token_3_arrayValues.clone();
/**
* @param values2d array values arranged in rows
*/
public ArrayPtg(Object[][] values2d) {
int nColumns = values2d[0].length;
int nRows = values2d.length;
// convert 2-d to 1-d array (row by row according to getValueIndex())
token_1_columns = (short) nColumns;
token_2_rows = (short) nRows;
Object[] vv = new Object[token_1_columns * token_2_rows];
for (int r=0; r<nRows; r++) {
Object[] rowData = values2d[r];
for (int c=0; c<nColumns; c++) {
vv[getValueIndex(c, r)] = rowData[c];
}
}
token_3_arrayValues = vv;
field_1_reserved = DEFAULT_RESERVED_DATA;
}
/**
* @return 2-d array (inner index is rowIx, outer index is colIx)
*/
public Object[][] getTokenArrayValues() {
if (token_3_arrayValues == null) {
throw new IllegalStateException("array values not read yet");
}
Object[][] result = new Object[token_2_rows][token_1_columns];
for (int r = 0; r < token_2_rows; r++) {
Object[] rowData = result[r];
for (int c = 0; c < token_1_columns; c++) {
rowData[c] = token_3_arrayValues[getValueIndex(c, r)];
}
}
return result;
}
public boolean isBaseToken() {
@ -88,27 +125,21 @@ public final class ArrayPtg extends Ptg {
token_3_arrayValues = ConstantValueParser.parse(in, totalCount);
}
public String toString()
{
StringBuffer buffer = new StringBuffer("[ArrayPtg]\n");
public String toString() {
StringBuffer sb = new StringBuffer("[ArrayPtg]\n");
buffer.append("columns = ").append(getColumnCount()).append("\n");
buffer.append("rows = ").append(getRowCount()).append("\n");
sb.append("nRows = ").append(getRowCount()).append("\n");
sb.append("nCols = ").append(getColumnCount()).append("\n");
if (token_3_arrayValues == null) {
buffer.append(" #values#uninitialised#\n");
sb.append(" #values#uninitialised#\n");
} else {
for (int x=0;x<getColumnCount();x++) {
for (int y=0;y<getRowCount();y++) {
Object o = token_3_arrayValues[getValueIndex(x, y)];
buffer.append("[").append(x).append("][").append(y).append("] = ").append(o).append("\n");
}
}
sb.append(" ").append(formatAsString());
}
return buffer.toString();
return sb.toString();
}
/**
* Note - (2D) array elements are stored column by column
* Note - (2D) array elements are stored row by row
* @return the index into the internal 1D array for the specified column and row
*/
/* package */ int getValueIndex(int colIx, int rowIx) {
@ -120,7 +151,7 @@ public final class ArrayPtg extends Ptg {
throw new IllegalArgumentException("Specified rowIx (" + rowIx
+ ") is outside the allowed range (0.." + (token_2_rows-1) + ")");
}
return rowIx + token_2_rows * colIx;
return rowIx * token_1_columns + colIx;
}
public void writeBytes(byte[] data, int offset) {
@ -153,16 +184,15 @@ public final class ArrayPtg extends Ptg {
+ ConstantValueParser.getEncodedSize(token_3_arrayValues);
}
public String toFormulaString(HSSFWorkbook book)
{
public String formatAsString() {
StringBuffer b = new StringBuffer();
b.append("{");
for (int x=0;x<getColumnCount();x++) {
if (x > 0) {
for (int y=0;y<getRowCount();y++) {
if (y > 0) {
b.append(";");
}
for (int y=0;y<getRowCount();y++) {
if (y > 0) {
for (int x=0;x<getColumnCount();x++) {
if (x > 0) {
b.append(",");
}
Object o = token_3_arrayValues[getValueIndex(x, y)];
@ -172,11 +202,14 @@ public final class ArrayPtg extends Ptg {
b.append("}");
return b.toString();
}
public String toFormulaString(HSSFWorkbook book) {
return formatAsString();
}
private static String getConstantText(Object o) {
if (o == null) {
return ""; // TODO - how is 'empty value' represented in formulas?
throw new RuntimeException("Array item cannot be null");
}
if (o instanceof UnicodeString) {
return "\"" + ((UnicodeString)o).getString() + "\"";
@ -196,11 +229,4 @@ public final class ArrayPtg extends Ptg {
public byte getDefaultOperandClass() {
return Ptg.CLASS_ARRAY;
}
public Object clone() {
ArrayPtg ptg = (ArrayPtg) super.clone();
ptg.field_1_reserved = (byte[]) field_1_reserved.clone();
ptg.token_3_arrayValues = (Object[]) token_3_arrayValues.clone();
return ptg;
}
}

View File

@ -24,12 +24,12 @@ import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
* @author Daniel Noll (daniel at nuix dot com dot au)
*/
public final class ErrPtg extends ScalarConstantPtg {
// convenient access to namespace
private static final HSSFErrorConstants EC = null;
/** <b>#NULL!</b> - Intersection of two cell ranges is empty */
public static final ErrPtg NULL_INTERSECTION = new ErrPtg(EC.ERROR_NULL);
public static final ErrPtg NULL_INTERSECTION = new ErrPtg(EC.ERROR_NULL);
/** <b>#DIV/0!</b> - Division by zero */
public static final ErrPtg DIV_ZERO = new ErrPtg(EC.ERROR_DIV_0);
/** <b>#VALUE!</b> - Wrong type of operand */
@ -37,28 +37,28 @@ public final class ErrPtg extends ScalarConstantPtg {
/** <b>#REF!</b> - Illegal or deleted cell reference */
public static final ErrPtg REF_INVALID = new ErrPtg(EC.ERROR_REF);
/** <b>#NAME?</b> - Wrong function or range name */
public static final ErrPtg NAME_INVALID = new ErrPtg(EC.ERROR_NAME);
public static final ErrPtg NAME_INVALID = new ErrPtg(EC.ERROR_NAME);
/** <b>#NUM!</b> - Value range overflow */
public static final ErrPtg NUM_ERROR = new ErrPtg(EC.ERROR_NUM);
/** <b>#N/A</b> - Argument or function not available */
public static final ErrPtg N_A = new ErrPtg(EC.ERROR_NA);
public static final short sid = 0x1c;
private static final int SIZE = 2;
private final int field_1_error_code;
/** Creates new ErrPtg */
public ErrPtg(int errorCode) {
private ErrPtg(int errorCode) {
if(!HSSFErrorConstants.isValidCode(errorCode)) {
throw new IllegalArgumentException("Invalid error code (" + errorCode + ")");
}
field_1_error_code = errorCode;
}
public ErrPtg(RecordInputStream in) {
this(in.readByte());
public static ErrPtg read(RecordInputStream in) {
return valueOf(in.readByte());
}
public void writeBytes(byte [] array, int offset)
@ -78,4 +78,17 @@ public final class ErrPtg extends ScalarConstantPtg {
public int getErrorCode() {
return field_1_error_code;
}
public static ErrPtg valueOf(int code) {
switch(code) {
case HSSFErrorConstants.ERROR_DIV_0: return DIV_ZERO;
case HSSFErrorConstants.ERROR_NA: return N_A;
case HSSFErrorConstants.ERROR_NAME: return NAME_INVALID;
case HSSFErrorConstants.ERROR_NULL: return NULL_INTERSECTION;
case HSSFErrorConstants.ERROR_NUM: return NUM_ERROR;
case HSSFErrorConstants.ERROR_REF: return REF_INVALID;
case HSSFErrorConstants.ERROR_VALUE: return VALUE_INVALID;
}
throw new RuntimeException("Unexpected error code (" + code + ")");
}
}

View File

@ -162,7 +162,7 @@ public abstract class Ptg implements Cloneable {
case StringPtg.sid: return new StringPtg(in); // 0x17
case AttrPtg.sid:
case 0x1a: return new AttrPtg(in); // 0x19
case ErrPtg.sid: return new ErrPtg(in); // 0x1c
case ErrPtg.sid: return ErrPtg.read(in); // 0x1c
case BoolPtg.sid: return new BoolPtg(in); // 0x1d
case IntPtg.sid: return new IntPtg(in); // 0x1e
case NumberPtg.sid: return new NumberPtg(in); // 0x1f

View File

@ -22,10 +22,12 @@ import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.model.FormulaParser.FormulaParseException;
import org.apache.poi.hssf.record.constant.ErrorConstant;
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
import org.apache.poi.hssf.record.formula.AddPtg;
import org.apache.poi.hssf.record.formula.AreaI;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.ArrayPtg;
import org.apache.poi.hssf.record.formula.AttrPtg;
import org.apache.poi.hssf.record.formula.BoolPtg;
import org.apache.poi.hssf.record.formula.ConcatPtg;
@ -48,6 +50,7 @@ import org.apache.poi.hssf.record.formula.SubtractPtg;
import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
import org.apache.poi.hssf.usermodel.HSSFName;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
@ -862,4 +865,18 @@ public final class TestFormulaParser extends TestCase {
assertEquals(65535, aptg.getLastRow());
}
public void testParseArray() {
Ptg[] ptgs;
ptgs = parseFormula("mode({1,2,2,#REF!;FALSE,3,3,2})");
assertEquals(2, ptgs.length);
Ptg ptg0 = ptgs[0];
assertEquals(ArrayPtg.class, ptg0.getClass());
assertEquals("{1.0,2.0,2.0,#REF!;FALSE,3.0,3.0,2.0}", ptg0.toFormulaString(null));
ArrayPtg aptg = (ArrayPtg) ptg0;
Object[][] values = aptg.getTokenArrayValues();
assertEquals(ErrorConstant.valueOf(HSSFErrorConstants.ERROR_REF), values[0][3]);
assertEquals(Boolean.FALSE, values[1][0]);
}
}

View File

@ -17,6 +17,10 @@
package org.apache.poi.hssf.model;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
@ -42,12 +46,21 @@ public final class TestRVA extends TestCase {
public void testFormulas() {
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("testRVA.xls");
try {
wb = new HSSFWorkbook(new FileInputStream("C:/josh/client/poi/svn/trunk-h2/src/testcases/org/apache/poi/hssf/data/testRVA.xls"));
} catch (FileNotFoundException e1) {
throw new RuntimeException(e1);
} catch (IOException e1) {
throw new RuntimeException(e1);
}
HSSFSheet sheet = wb.getSheetAt(0);
int countFailures = 0;
int countErrors = 0;
int rowIx = 0;
// rowIx = 34;
// rowIx =32;
while (rowIx < 65535) {
HSSFRow row = sheet.getRow(rowIx);
if (row == null) {
@ -61,8 +74,10 @@ public final class TestRVA extends TestCase {
try {
confirmCell(cell, formula, wb);
} catch (AssertionFailedError e) {
System.out.flush();
System.err.println("Problem with row[" + rowIx + "] formula '" + formula + "'");
System.err.println(e.getMessage());
System.err.flush();
countFailures++;
} catch (RuntimeException e) {
System.err.println("Problem with row[" + rowIx + "] formula '" + formula + "'");
@ -70,6 +85,8 @@ public final class TestRVA extends TestCase {
e.printStackTrace();
}
rowIx++;
// if (rowIx>30) break;
// break;
}
if (countErrors + countFailures > 0) {
String msg = "One or more RVA tests failed: countFailures=" + countFailures
@ -104,8 +121,8 @@ public final class TestRVA extends TestCase {
if (excelPtg.getClass() != poiPtg.getClass()) {
hasMismatch = true;
sb.append(" mismatch token type[" + i + "] " + getShortClassName(excelPtg) + " "
+ getOperandClassName(excelPtg) + " - " + getShortClassName(poiPtg) + " "
+ getOperandClassName(poiPtg));
+ excelPtg.getRVAType() + " - " + getShortClassName(poiPtg) + " "
+ poiPtg.getRVAType());
sb.append(NEW_LINE);
continue;
}
@ -113,16 +130,16 @@ public final class TestRVA extends TestCase {
continue;
}
sb.append(" token[" + i + "] " + excelPtg.toString() + " "
+ getOperandClassName(excelPtg));
+ excelPtg.getRVAType());
if (excelPtg.getPtgClass() != poiPtg.getPtgClass()) {
hasMismatch = true;
sb.append(" - was " + getOperandClassName(poiPtg));
sb.append(" - was " + poiPtg.getRVAType());
}
sb.append(NEW_LINE);
}
if (false) { // set 'true' to see trace of RVA values
System.out.println(formula);
System.out.println(formulaCell.getRowIndex() + " " + formula);
System.out.println(sb.toString());
}
if (hasMismatch) {
@ -135,14 +152,4 @@ public final class TestRVA extends TestCase {
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 + ")");
}
}

View File

@ -60,15 +60,15 @@ public final class TestArrayPtg extends TestCase {
ptg.readTokenValues(new TestcaseRecordInputStream(0, ENCODED_CONSTANT_DATA));
assertEquals(3, ptg.getColumnCount());
assertEquals(2, ptg.getRowCount());
Object[] values = ptg.getTokenArrayValues();
assertEquals(6, values.length);
Object[][] values = ptg.getTokenArrayValues();
assertEquals(2, values.length);
assertEquals(Boolean.TRUE, values[0]);
assertEquals(new UnicodeString("ABCD"), values[1]);
assertEquals(new Double(0), values[3]);
assertEquals(Boolean.FALSE, values[4]);
assertEquals(new UnicodeString("FG"), values[5]);
assertEquals(Boolean.TRUE, values[0][0]);
assertEquals(new UnicodeString("ABCD"), values[0][1]);
assertEquals(new Double(0), values[1][0]);
assertEquals(Boolean.FALSE, values[1][1]);
assertEquals(new UnicodeString("FG"), values[1][2]);
byte[] outBuf = new byte[ENCODED_CONSTANT_DATA.length];
ptg.writeTokenValueBytes(outBuf, 0);
@ -89,10 +89,10 @@ public final class TestArrayPtg extends TestCase {
assertEquals(2, ptg.getRowCount());
assertEquals(0, ptg.getValueIndex(0, 0));
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(1, ptg.getValueIndex(1, 0));
assertEquals(2, ptg.getValueIndex(2, 0));
assertEquals(3, ptg.getValueIndex(0, 1));
assertEquals(4, ptg.getValueIndex(1, 1));
assertEquals(5, ptg.getValueIndex(2, 1));
}
@ -110,7 +110,7 @@ public final class TestArrayPtg extends TestCase {
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);
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);
}
public void testToFormulaString() {
@ -127,7 +127,7 @@ public final class TestArrayPtg extends TestCase {
}
throw e;
}
assertEquals("{TRUE,\"ABCD\";\"E\",0.0;FALSE,\"FG\"}", actualFormula);
assertEquals("{TRUE,\"ABCD\",\"E\";0.0,FALSE,\"FG\"}", actualFormula);
}
/**