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:
parent
78101afee8
commit
f501145768
@ -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];
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 + ")");
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Binary file not shown.
@ -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]);
|
||||
|
||||
}
|
||||
}
|
@ -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 + ")");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user