Bugzilla 47721 - Added implementation for INDIRECT()

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@806759 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2009-08-21 23:37:17 +00:00
parent ba518d2221
commit dd9c89cc43
21 changed files with 795 additions and 197 deletions

View File

@ -33,6 +33,7 @@
<changes> <changes>
<release version="3.5-beta7" date="2009-??-??"> <release version="3.5-beta7" date="2009-??-??">
<action dev="POI-DEVELOPERS" type="add">47721 - Added implementation for INDIRECT()</action>
<action dev="POI-DEVELOPERS" type="add">45583 - Avoid exception when reading ClipboardData packet in OLE property sets</action> <action dev="POI-DEVELOPERS" type="add">45583 - Avoid exception when reading ClipboardData packet in OLE property sets</action>
<action dev="POI-DEVELOPERS" type="add">47652 - Added support for reading encrypted workbooks</action> <action dev="POI-DEVELOPERS" type="add">47652 - Added support for reading encrypted workbooks</action>
<action dev="POI-DEVELOPERS" type="add">47604 - Implementation of an XML to XLSX Importer using Custom XML Mapping</action> <action dev="POI-DEVELOPERS" type="add">47604 - Implementation of an XML to XLSX Importer using Custom XML Mapping</action>

View File

@ -22,7 +22,7 @@ import java.util.Map;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.EvaluationWorkbook; import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.NotImplementedException; import org.apache.poi.ss.formula.eval.NotImplementedException;
public final class AnalysisToolPak { public final class AnalysisToolPak {
@ -34,8 +34,7 @@ public final class AnalysisToolPak {
_functionName = functionName; _functionName = functionName;
} }
public ValueEval evaluate(ValueEval[] args, EvaluationWorkbook workbook, int srcCellSheet, public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
int srcCellRow, int srcCellCol) {
throw new NotImplementedException(_functionName); throw new NotImplementedException(_functionName);
} }
}; };

View File

@ -23,7 +23,7 @@ import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.EvaluationWorkbook; import org.apache.poi.ss.formula.OperationEvaluationContext;
/** /**
* Implementation of Excel 'Analysis ToolPak' function ISEVEN() ISODD()<br/> * Implementation of Excel 'Analysis ToolPak' function ISEVEN() ISODD()<br/>
* *
@ -39,15 +39,14 @@ final class ParityFunction implements FreeRefFunction {
_desiredParity = desiredParity; _desiredParity = desiredParity;
} }
public ValueEval evaluate(ValueEval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
int srcCellCol) {
if (args.length != 1) { if (args.length != 1) {
return ErrorEval.VALUE_INVALID; return ErrorEval.VALUE_INVALID;
} }
int val; int val;
try { try {
val = evaluateArgParity(args[0], srcCellRow, srcCellCol); val = evaluateArgParity(args[0], ec.getRowIndex(), ec.getColumnIndex());
} catch (EvaluationException e) { } catch (EvaluationException e) {
return e.getErrorEval(); return e.getErrorEval();
} }

View File

@ -28,7 +28,7 @@ import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.EvaluationWorkbook; import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.ss.usermodel.DateUtil;
/** /**
* Implementation of Excel 'Analysis ToolPak' function YEARFRAC()<br/> * Implementation of Excel 'Analysis ToolPak' function YEARFRAC()<br/>
@ -58,9 +58,9 @@ final class YearFrac implements FreeRefFunction {
// enforce singleton // enforce singleton
} }
public ValueEval evaluate(ValueEval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
int srcCellCol) { int srcCellRow = ec.getRowIndex();
int srcCellCol = ec.getColumnIndex();
double result; double result;
try { try {
int basis = 0; // default int basis = 0; // default

View File

@ -20,63 +20,8 @@ package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
import org.apache.poi.hssf.record.formula.function.FunctionMetadata; import org.apache.poi.hssf.record.formula.function.FunctionMetadata;
import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry;
import org.apache.poi.hssf.record.formula.functions.AggregateFunction; import org.apache.poi.hssf.record.formula.functions.*;
import org.apache.poi.hssf.record.formula.functions.And; import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.hssf.record.formula.functions.CalendarFieldFunction;
import org.apache.poi.hssf.record.formula.functions.Choose;
import org.apache.poi.hssf.record.formula.functions.Column;
import org.apache.poi.hssf.record.formula.functions.Columns;
import org.apache.poi.hssf.record.formula.functions.Count;
import org.apache.poi.hssf.record.formula.functions.Counta;
import org.apache.poi.hssf.record.formula.functions.Countif;
import org.apache.poi.hssf.record.formula.functions.DateFunc;
import org.apache.poi.hssf.record.formula.functions.Errortype;
import org.apache.poi.hssf.record.formula.functions.Even;
import org.apache.poi.hssf.record.formula.functions.False;
import org.apache.poi.hssf.record.formula.functions.FinanceFunction;
import org.apache.poi.hssf.record.formula.functions.Find;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.hssf.record.formula.functions.Function;
import org.apache.poi.hssf.record.formula.functions.Hlookup;
import org.apache.poi.hssf.record.formula.functions.Hyperlink;
import org.apache.poi.hssf.record.formula.functions.If;
import org.apache.poi.hssf.record.formula.functions.Index;
import org.apache.poi.hssf.record.formula.functions.Indirect;
import org.apache.poi.hssf.record.formula.functions.IsError;
import org.apache.poi.hssf.record.formula.functions.IsNa;
import org.apache.poi.hssf.record.formula.functions.Isblank;
import org.apache.poi.hssf.record.formula.functions.Isref;
import org.apache.poi.hssf.record.formula.functions.LogicalFunction;
import org.apache.poi.hssf.record.formula.functions.Lookup;
import org.apache.poi.hssf.record.formula.functions.Match;
import org.apache.poi.hssf.record.formula.functions.MinaMaxa;
import org.apache.poi.hssf.record.formula.functions.Mode;
import org.apache.poi.hssf.record.formula.functions.Na;
import org.apache.poi.hssf.record.formula.functions.Not;
import org.apache.poi.hssf.record.formula.functions.NotImplementedFunction;
import org.apache.poi.hssf.record.formula.functions.Now;
import org.apache.poi.hssf.record.formula.functions.NumericFunction;
import org.apache.poi.hssf.record.formula.functions.Odd;
import org.apache.poi.hssf.record.formula.functions.Offset;
import org.apache.poi.hssf.record.formula.functions.Or;
import org.apache.poi.hssf.record.formula.functions.Pi;
import org.apache.poi.hssf.record.formula.functions.Rand;
import org.apache.poi.hssf.record.formula.functions.Replace;
import org.apache.poi.hssf.record.formula.functions.Row;
import org.apache.poi.hssf.record.formula.functions.Rows;
import org.apache.poi.hssf.record.formula.functions.Substitute;
import org.apache.poi.hssf.record.formula.functions.Sumif;
import org.apache.poi.hssf.record.formula.functions.Sumproduct;
import org.apache.poi.hssf.record.formula.functions.Sumx2my2;
import org.apache.poi.hssf.record.formula.functions.Sumx2py2;
import org.apache.poi.hssf.record.formula.functions.Sumxmy2;
import org.apache.poi.hssf.record.formula.functions.T;
import org.apache.poi.hssf.record.formula.functions.TextFunction;
import org.apache.poi.hssf.record.formula.functions.Time;
import org.apache.poi.hssf.record.formula.functions.Today;
import org.apache.poi.hssf.record.formula.functions.True;
import org.apache.poi.hssf.record.formula.functions.Value;
import org.apache.poi.hssf.record.formula.functions.Vlookup;
import org.apache.poi.ss.formula.eval.NotImplementedException; import org.apache.poi.ss.formula.eval.NotImplementedException;
/** /**
@ -99,26 +44,10 @@ public final class FunctionEval implements OperationEval {
// convenient access to namespace // convenient access to namespace
private static final FunctionID ID = null; private static final FunctionID ID = null;
protected static final Function[] functions = produceFunctions();
/** /**
* @return <code>null</code> if specified function * Array elements corresponding to unimplemented functions are <code>null</code>
*/ */
private Function getFunction() { protected static final Function[] functions = produceFunctions();
short fidx = getFunctionIndex();
return functions[fidx];
}
public boolean isFreeRefFunction() {
return getFreeRefFunction() != null;
}
public FreeRefFunction getFreeRefFunction() {
switch (getFunctionIndex()) {
case FunctionID.INDIRECT: return Indirect.instance;
case FunctionID.EXTERNAL_FUNC: return UserDefinedFunction.instance;
}
return null;
}
private static Function[] produceFunctions() { private static Function[] produceFunctions() {
Function[] retval = new Function[368]; Function[] retval = new Function[368];
@ -299,19 +228,26 @@ public final class FunctionEval implements OperationEval {
_delegate = funcPtg; _delegate = funcPtg;
} }
public ValueEval evaluate(ValueEval[] operands, int srcRow, short srcCol) { public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
Function f = getFunction(); int fidx = _delegate.getFunctionIndex();
if (f == null) { // check for 'free ref' functions first
throw new NotImplementedException("FuncIx=" + getFunctionIndex()); switch (fidx) {
case FunctionID.INDIRECT:
return Indirect.instance.evaluate(args, ec);
case FunctionID.EXTERNAL_FUNC:
return UserDefinedFunction.instance.evaluate(args, ec);
} }
return f.evaluate(operands, srcRow, srcCol); // else - must be plain function
Function f = functions[fidx];
if (f == null) {
throw new NotImplementedException("FuncIx=" + fidx);
}
int srcCellRow = ec.getRowIndex();
int srcCellCol = ec.getColumnIndex();
return f.evaluate(args, srcCellRow, (short) srcCellCol);
} }
public int getNumberOfOperands() { public int getNumberOfOperands() {
return _delegate.getNumberOfOperands(); return _delegate.getNumberOfOperands();
} }
private short getFunctionIndex() {
return _delegate.getFunctionIndex();
}
} }

View File

@ -17,6 +17,8 @@
package org.apache.poi.hssf.record.formula.eval; package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.ss.formula.OperationEvaluationContext;
/** /**
* Common interface for implementations of Excel formula operations. * Common interface for implementations of Excel formula operations.
* *
@ -29,11 +31,10 @@ public interface OperationEval {
* @param args the evaluated operation arguments. Elements of this array typically implement * @param args the evaluated operation arguments. Elements of this array typically implement
* {@link ValueEval}. Empty values are represented with {@link BlankEval} or {@link * {@link ValueEval}. Empty values are represented with {@link BlankEval} or {@link
* MissingArgEval}, never <code>null</code>. * MissingArgEval}, never <code>null</code>.
* @param srcRowIndex row index of the cell containing the formula under evaluation * @param ec used to identify the current cell under evaluation, and potentially to
* @param srcColumnIndex column index of the cell containing the formula under evaluation * dynamically create references
* @return The evaluated result, possibly an {@link ErrorEval}, never <code>null</code>. * @return The evaluated result, possibly an {@link ErrorEval}, never <code>null</code>.
*/ */
ValueEval evaluate(ValueEval[] args, int srcRowIndex, short srcColumnIndex); ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec);
int getNumberOfOperands(); int getNumberOfOperands();
} }

View File

@ -20,6 +20,7 @@ package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.atp.AnalysisToolPak; import org.apache.poi.hssf.record.formula.atp.AnalysisToolPak;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.EvaluationWorkbook; import org.apache.poi.ss.formula.EvaluationWorkbook;
import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.NotImplementedException; import org.apache.poi.ss.formula.eval.NotImplementedException;
/** /**
* *
@ -36,8 +37,7 @@ final class UserDefinedFunction implements FreeRefFunction {
// enforce singleton // enforce singleton
} }
public ValueEval evaluate(ValueEval[] args, EvaluationWorkbook workbook, public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
int srcCellSheet, int srcCellRow,int srcCellCol) {
int nIncomingArgs = args.length; int nIncomingArgs = args.length;
if(nIncomingArgs < 1) { if(nIncomingArgs < 1) {
@ -49,7 +49,7 @@ final class UserDefinedFunction implements FreeRefFunction {
if (nameArg instanceof NameEval) { if (nameArg instanceof NameEval) {
targetFunc = findInternalUserDefinedFunction((NameEval) nameArg); targetFunc = findInternalUserDefinedFunction((NameEval) nameArg);
} else if (nameArg instanceof NameXEval) { } else if (nameArg instanceof NameXEval) {
targetFunc = findExternalUserDefinedFunction(workbook, (NameXEval) nameArg); targetFunc = findExternalUserDefinedFunction(ec.getWorkbook(), (NameXEval) nameArg);
} else { } else {
throw new RuntimeException("First argument should be a NameEval, but got (" throw new RuntimeException("First argument should be a NameEval, but got ("
+ nameArg.getClass().getName() + ")"); + nameArg.getClass().getName() + ")");
@ -57,7 +57,7 @@ final class UserDefinedFunction implements FreeRefFunction {
int nOutGoingArgs = nIncomingArgs -1; int nOutGoingArgs = nIncomingArgs -1;
ValueEval[] outGoingArgs = new ValueEval[nOutGoingArgs]; ValueEval[] outGoingArgs = new ValueEval[nOutGoingArgs];
System.arraycopy(args, 1, outGoingArgs, 0, nOutGoingArgs); System.arraycopy(args, 1, outGoingArgs, 0, nOutGoingArgs);
return targetFunc.evaluate(outGoingArgs, workbook, srcCellSheet, srcCellRow, srcCellCol); return targetFunc.evaluate(outGoingArgs, ec);
} }
private static FreeRefFunction findExternalUserDefinedFunction(EvaluationWorkbook workbook, private static FreeRefFunction findExternalUserDefinedFunction(EvaluationWorkbook workbook,

View File

@ -18,7 +18,7 @@
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.ss.formula.EvaluationWorkbook; import org.apache.poi.ss.formula.OperationEvaluationContext;
/** /**
@ -28,28 +28,24 @@ import org.apache.poi.ss.formula.EvaluationWorkbook;
* argument.<br/> * argument.<br/>
* Two important functions with this feature are <b>INDIRECT</b> and <b>OFFSET</b><p/> * Two important functions with this feature are <b>INDIRECT</b> and <b>OFFSET</b><p/>
* *
* In POI, the <tt>HSSFFormulaEvaluator</tt> evaluates every cell in each reference argument before * When POI evaluates formulas, each reference argument is capable of evaluating any cell inside
* calling the function. This means that functions using fixed references do not need access to * its range. Actually, even cells outside the reference range but on the same sheet can be
* the rest of the workbook to execute. Hence the <tt>evaluate()</tt> method on the common * evaluated. This allows <b>OFFSET</b> to be implemented like most other functions - taking only
* interface <tt>Function</tt> does not take a workbook parameter.<p> * the arguments, and source cell coordinates.
* *
* This interface recognises the requirement of some functions to freely create and evaluate * For the moment this interface only exists to serve the <b>INDIRECT</b> which can decode
* references beyond those passed in as arguments. * arbitrary text into cell references, and evaluate them..
* *
* @author Josh Micich * @author Josh Micich
*/ */
public interface FreeRefFunction { public interface FreeRefFunction {
/** /**
*
* @param args the pre-evaluated arguments for this function. args is never <code>null</code>, * @param args the pre-evaluated arguments for this function. args is never <code>null</code>,
* nor are any of its elements. * nor are any of its elements.
* @param srcCellSheet zero based sheet index of the cell containing the currently evaluating formula * @param ec primarily used to identify the source cell containing the formula being evaluated.
* @param srcCellRow zero based row index of the cell containing the currently evaluating formula * may also be used to dynamically create reference evals.
* @param srcCellCol zero based column index of the cell containing the currently evaluating formula
* @param workbook is the workbook containing the formula/cell being evaluated
* @return never <code>null</code>. Possibly an instance of <tt>ErrorEval</tt> in the case of * @return never <code>null</code>. Possibly an instance of <tt>ErrorEval</tt> in the case of
* a specified Excel error (Exceptions are never thrown to represent Excel errors). * a specified Excel error (Exceptions are never thrown to represent Excel errors).
*
*/ */
ValueEval evaluate(ValueEval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol); ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec);
} }

View File

@ -17,9 +17,13 @@
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.MissingArgEval;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.ss.formula.EvaluationWorkbook; import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.NotImplementedException;
/** /**
* Implementation for Excel function INDIRECT<p/> * Implementation for Excel function INDIRECT<p/>
@ -29,11 +33,10 @@ import org.apache.poi.ss.formula.eval.NotImplementedException;
* <b>Syntax</b>:</br> * <b>Syntax</b>:</br>
* <b>INDIRECT</b>(<b>ref_text</b>,isA1Style)<p/> * <b>INDIRECT</b>(<b>ref_text</b>,isA1Style)<p/>
* *
* <b>ref_text</b> a string representation of the desired reference as it would normally be written * <b>ref_text</b> a string representation of the desired reference as it would
* in a cell formula.<br/> * normally be written in a cell formula.<br/>
* <b>isA1Style</b> (default TRUE) specifies whether the ref_text should be interpreted as A1-style * <b>isA1Style</b> (default TRUE) specifies whether the ref_text should be
* or R1C1-style. * interpreted as A1-style or R1C1-style.
*
* *
* @author Josh Micich * @author Josh Micich
*/ */
@ -45,8 +48,192 @@ public final class Indirect implements FreeRefFunction {
// enforce singleton // enforce singleton
} }
public ValueEval evaluate(ValueEval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol) { public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
// TODO - implement INDIRECT() if (args.length < 1) {
throw new NotImplementedException("INDIRECT"); return ErrorEval.VALUE_INVALID;
}
boolean isA1style;
String text;
try {
ValueEval ve = OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec
.getColumnIndex());
text = OperandResolver.coerceValueToString(ve);
switch (args.length) {
case 1:
isA1style = true;
break;
case 2:
isA1style = evaluateBooleanArg(args[1], ec);
break;
default:
return ErrorEval.VALUE_INVALID;
}
} catch (EvaluationException e) {
return e.getErrorEval();
}
return evaluateIndirect(ec, text, isA1style);
}
private static boolean evaluateBooleanArg(ValueEval arg, OperationEvaluationContext ec)
throws EvaluationException {
ValueEval ve = OperandResolver.getSingleValue(arg, ec.getRowIndex(), ec.getColumnIndex());
if (ve == BlankEval.INSTANCE || ve == MissingArgEval.instance) {
return false;
}
// numeric quantities follow standard boolean conversion rules
// for strings, only "TRUE" and "FALSE" (case insensitive) are valid
return OperandResolver.coerceValueToBoolean(ve, false).booleanValue();
}
private static ValueEval evaluateIndirect(OperationEvaluationContext ec, String text,
boolean isA1style) {
// Search backwards for '!' because sheet names can contain '!'
int plingPos = text.lastIndexOf('!');
String workbookName;
String sheetName;
String refText; // whitespace around this gets trimmed OK
if (plingPos < 0) {
workbookName = null;
sheetName = null;
refText = text;
} else {
String[] parts = parseWorkbookAndSheetName(text.subSequence(0, plingPos));
if (parts == null) {
return ErrorEval.REF_INVALID;
}
workbookName = parts[0];
sheetName = parts[1];
refText = text.substring(plingPos + 1);
}
String refStrPart1;
String refStrPart2;
int colonPos = refText.indexOf(':');
if (colonPos < 0) {
refStrPart1 = refText.trim();
refStrPart2 = null;
} else {
refStrPart1 = refText.substring(0, colonPos).trim();
refStrPart2 = refText.substring(colonPos + 1).trim();
}
return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style);
}
/**
* @return array of length 2: {workbookName, sheetName,}. Second element will always be
* present. First element may be null if sheetName is unqualified.
* Returns <code>null</code> if text cannot be parsed.
*/
private static String[] parseWorkbookAndSheetName(CharSequence text) {
int lastIx = text.length() - 1;
if (lastIx < 0) {
return null;
}
if (canTrim(text)) {
return null;
}
char firstChar = text.charAt(0);
if (Character.isWhitespace(firstChar)) {
return null;
}
if (firstChar == '\'') {
// workbookName or sheetName needs quoting
// quotes go around both
if (text.charAt(lastIx) != '\'') {
return null;
}
firstChar = text.charAt(1);
if (Character.isWhitespace(firstChar)) {
return null;
}
String wbName;
int sheetStartPos;
if (firstChar == '[') {
int rbPos = text.toString().lastIndexOf(']');
if (rbPos < 0) {
return null;
}
wbName = unescapeString(text.subSequence(2, rbPos));
if (wbName == null || canTrim(wbName)) {
return null;
}
sheetStartPos = rbPos + 1;
} else {
wbName = null;
sheetStartPos = 1;
}
// else - just sheet name
String sheetName = unescapeString(text.subSequence(sheetStartPos, lastIx));
if (sheetName == null) { // note - when quoted, sheetName can
// start/end with whitespace
return null;
}
return new String[] { wbName, sheetName, };
}
if (firstChar == '[') {
int rbPos = text.toString().lastIndexOf(']');
if (rbPos < 0) {
return null;
}
CharSequence wbName = text.subSequence(1, rbPos);
if (canTrim(wbName)) {
return null;
}
CharSequence sheetName = text.subSequence(rbPos + 1, text.length());
if (canTrim(sheetName)) {
return null;
}
return new String[] { wbName.toString(), sheetName.toString(), };
}
// else - just sheet name
return new String[] { null, text.toString(), };
}
/**
* @return <code>null</code> if there is a syntax error in any escape sequence
* (the typical syntax error is a single quote character not followed by another).
*/
private static String unescapeString(CharSequence text) {
int len = text.length();
StringBuilder sb = new StringBuilder(len);
int i = 0;
while (i < len) {
char ch = text.charAt(i);
if (ch == '\'') {
// every quote must be followed by another
i++;
if (i >= len) {
return null;
}
ch = text.charAt(i);
if (ch != '\'') {
return null;
}
}
sb.append(ch);
i++;
}
return sb.toString();
}
private static boolean canTrim(CharSequence text) {
int lastIx = text.length() - 1;
if (lastIx < 0) {
return false;
}
if (Character.isWhitespace(text.charAt(0))) {
return true;
}
if (Character.isWhitespace(text.charAt(lastIx))) {
return true;
}
return false;
} }
} }

View File

@ -35,6 +35,12 @@ import java.util.Set;
*/ */
public final class CollaboratingWorkbooksEnvironment { public final class CollaboratingWorkbooksEnvironment {
public static final class WorkbookNotFoundException extends Exception {
WorkbookNotFoundException(String msg) {
super(msg);
}
}
public static final CollaboratingWorkbooksEnvironment EMPTY = new CollaboratingWorkbooksEnvironment(); public static final CollaboratingWorkbooksEnvironment EMPTY = new CollaboratingWorkbooksEnvironment();
private final Map<String, WorkbookEvaluator> _evaluatorsByName; private final Map<String, WorkbookEvaluator> _evaluatorsByName;
@ -99,8 +105,11 @@ public final class CollaboratingWorkbooksEnvironment {
for(int i=0; i<nItems; i++) { for(int i=0; i<nItems; i++) {
evaluators[i].attachToEnvironment(env, cache, i); evaluators[i].attachToEnvironment(env, cache, i);
} }
} }
/**
* Completely dismantles all workbook environments that the supplied evaluators are part of
*/
private void unhookOldEnvironments(WorkbookEvaluator[] evaluators) { private void unhookOldEnvironments(WorkbookEvaluator[] evaluators) {
Set<CollaboratingWorkbooksEnvironment> oldEnvs = new HashSet<CollaboratingWorkbooksEnvironment>(); Set<CollaboratingWorkbooksEnvironment> oldEnvs = new HashSet<CollaboratingWorkbooksEnvironment>();
for(int i=0; i<evaluators.length; i++) { for(int i=0; i<evaluators.length; i++) {
@ -114,10 +123,11 @@ public final class CollaboratingWorkbooksEnvironment {
} }
/** /**
* * Tell all contained evaluators that this environment should be discarded
*/ */
private void unhook() { private void unhook() {
if (_evaluators.length < 1) { if (_evaluators.length < 1) {
// Never dismantle the EMPTY environment
return; return;
} }
for (int i = 0; i < _evaluators.length; i++) { for (int i = 0; i < _evaluators.length; i++) {
@ -126,7 +136,7 @@ public final class CollaboratingWorkbooksEnvironment {
_unhooked = true; _unhooked = true;
} }
public WorkbookEvaluator getWorkbookEvaluator(String workbookName) { public WorkbookEvaluator getWorkbookEvaluator(String workbookName) throws WorkbookNotFoundException {
if (_unhooked) { if (_unhooked) {
throw new IllegalStateException("This environment has been unhooked"); throw new IllegalStateException("This environment has been unhooked");
} }
@ -148,7 +158,7 @@ public final class CollaboratingWorkbooksEnvironment {
} }
sb.append(")"); sb.append(")");
} }
throw new RuntimeException(sb.toString()); throw new WorkbookNotFoundException(sb.toString());
} }
return result; return result;
} }

View File

@ -34,6 +34,10 @@ public interface EvaluationWorkbook {
* @return -1 if the specified sheet is from a different book * @return -1 if the specified sheet is from a different book
*/ */
int getSheetIndex(EvaluationSheet sheet); int getSheetIndex(EvaluationSheet sheet);
/**
* Finds a sheet index by case insensitive name.
* @return the index of the sheet matching the specified name. -1 if not found
*/
int getSheetIndex(String sheetName); int getSheetIndex(String sheetName);
EvaluationSheet getSheet(int sheetIndex); EvaluationSheet getSheet(int sheetIndex);

View File

@ -34,13 +34,18 @@ final class LazyRefEval extends RefEvalBase {
private final SheetRefEvaluator _evaluator; private final SheetRefEvaluator _evaluator;
public LazyRefEval(RefPtg ptg, SheetRefEvaluator sre) { public LazyRefEval(int rowIndex, int columnIndex, SheetRefEvaluator sre) {
super(ptg.getRow(), ptg.getColumn()); super(rowIndex, columnIndex);
if (sre == null) {
throw new IllegalArgumentException("sre must not be null");
}
_evaluator = sre; _evaluator = sre;
} }
public LazyRefEval(RefPtg ptg, SheetRefEvaluator sre) {
this(ptg.getRow(), ptg.getColumn(), sre);
}
public LazyRefEval(Ref3DPtg ptg, SheetRefEvaluator sre) { public LazyRefEval(Ref3DPtg ptg, SheetRefEvaluator sre) {
super(ptg.getRow(), ptg.getColumn()); this(ptg.getRow(), ptg.getColumn(), sre);
_evaluator = sre;
} }
public ValueEval getInnerValueEval() { public ValueEval getInnerValueEval() {

View File

@ -0,0 +1,259 @@
/* ====================================================================
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.ss.formula;
import org.apache.poi.hssf.record.formula.AreaI;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.util.CellReference.NameType;
/**
* Contains all the contextual information required to evaluate an operation
* within a formula
*
* For POI internal use only
*
* @author Josh Micich
*/
public final class OperationEvaluationContext {
private final EvaluationWorkbook _workbook;
private final int _sheetIndex;
private final int _rowIndex;
private final int _columnIndex;
private final EvaluationTracker _tracker;
private final WorkbookEvaluator _bookEvaluator;
public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum,
int srcColNum, EvaluationTracker tracker) {
_bookEvaluator = bookEvaluator;
_workbook = workbook;
_sheetIndex = sheetIndex;
_rowIndex = srcRowNum;
_columnIndex = srcColNum;
_tracker = tracker;
}
public EvaluationWorkbook getWorkbook() {
return _workbook;
}
public int getRowIndex() {
return _rowIndex;
}
public int getColumnIndex() {
return _columnIndex;
}
/* package */ SheetRefEvaluator createExternSheetRefEvaluator(ExternSheetReferenceToken ptg) {
int externSheetIndex = ptg.getExternSheetIndex();
ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
WorkbookEvaluator targetEvaluator;
int otherSheetIndex;
if (externalSheet == null) {
// sheet is in same workbook
otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex);
targetEvaluator = _bookEvaluator;
} else {
// look up sheet by name from external workbook
String workbookName = externalSheet.getWorkbookName();
try {
targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
} catch (WorkbookNotFoundException e) {
throw new RuntimeException(e.getMessage());
}
otherSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName());
if (otherSheetIndex < 0) {
throw new RuntimeException("Invalid sheet name '" + externalSheet.getSheetName()
+ "' in bool '" + workbookName + "'.");
}
}
return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex);
}
/**
* @return <code>null</code> if either workbook or sheet is not found
*/
private SheetRefEvaluator createExternSheetRefEvaluator(String workbookName, String sheetName) {
WorkbookEvaluator targetEvaluator;
if (workbookName == null) {
targetEvaluator = _bookEvaluator;
} else {
if (sheetName == null) {
throw new IllegalArgumentException("sheetName must not be null if workbookName is provided");
}
try {
targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
} catch (WorkbookNotFoundException e) {
return null;
}
}
int otherSheetIndex = sheetName == null ? _sheetIndex : targetEvaluator.getSheetIndex(sheetName);
if (otherSheetIndex < 0) {
return null;
}
return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex);
}
public SheetRefEvaluator getRefEvaluatorForCurrentSheet() {
return new SheetRefEvaluator(_bookEvaluator, _tracker, _sheetIndex);
}
/**
* Resolves a cell or area reference dynamically.
* @param workbookName the name of the workbook containing the reference. If <code>null</code>
* the current workbook is assumed. Note - to evaluate formulas which use multiple workbooks,
* a {@link CollaboratingWorkbooksEnvironment} must be set up.
* @param sheetName the name of the sheet containing the reference. May be <code>null</code>
* (when <tt>workbookName</tt> is also null) in which case the current workbook and sheet is
* assumed.
* @param refStrPart1 the single cell reference or first part of the area reference. Must not
* be <code>null</code>.
* @param refStrPart2 the second part of the area reference. For single cell references this
* parameter must be <code>null</code>
* @param isA1Style specifies the format for <tt>refStrPart1</tt> and <tt>refStrPart2</tt>.
* Pass <code>true</code> for 'A1' style and <code>false</code> for 'R1C1' style.
* TODO - currently POI only supports 'A1' reference style
* @return a {@link RefEval} or {@link AreaEval}
*/
public ValueEval getDynamicReference(String workbookName, String sheetName, String refStrPart1,
String refStrPart2, boolean isA1Style) {
if (!isA1Style) {
throw new RuntimeException("R1C1 style not supported yet");
}
SheetRefEvaluator sre = createExternSheetRefEvaluator(workbookName, sheetName);
if (sre == null) {
return ErrorEval.REF_INVALID;
}
// ugly typecast - TODO - make spreadsheet version more easily accessible
SpreadsheetVersion ssVersion = ((FormulaParsingWorkbook)_workbook).getSpreadsheetVersion();
NameType part1refType = classifyCellReference(refStrPart1, ssVersion);
switch (part1refType) {
case BAD_CELL_OR_NAMED_RANGE:
return ErrorEval.REF_INVALID;
case NAMED_RANGE:
throw new RuntimeException("Cannot evaluate '" + refStrPart1
+ "'. Indirect evaluation of defined names not supported yet");
}
if (refStrPart2 == null) {
// no ':'
switch (part1refType) {
case COLUMN:
case ROW:
return ErrorEval.REF_INVALID;
case CELL:
CellReference cr = new CellReference(refStrPart1);
return new LazyRefEval(cr.getRow(), cr.getCol(), sre);
}
throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'.");
}
NameType part2refType = classifyCellReference(refStrPart1, ssVersion);
switch (part2refType) {
case BAD_CELL_OR_NAMED_RANGE:
return ErrorEval.REF_INVALID;
case NAMED_RANGE:
throw new RuntimeException("Cannot evaluate '" + refStrPart1
+ "'. Indirect evaluation of defined names not supported yet");
}
if (part2refType != part1refType) {
// LHS and RHS of ':' must be compatible
return ErrorEval.REF_INVALID;
}
int firstRow, firstCol, lastRow, lastCol;
switch (part1refType) {
case COLUMN:
firstRow =0;
lastRow = ssVersion.getLastRowIndex();
firstCol = parseColRef(refStrPart1);
lastCol = parseColRef(refStrPart2);
break;
case ROW:
firstCol = 0;
lastCol = ssVersion.getLastColumnIndex();
firstRow = parseRowRef(refStrPart1);
lastRow = parseRowRef(refStrPart2);
break;
case CELL:
CellReference cr;
cr = new CellReference(refStrPart1);
firstRow = cr.getRow();
firstCol = cr.getCol();
cr = new CellReference(refStrPart2);
lastRow = cr.getRow();
lastCol = cr.getCol();
break;
default:
throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'.");
}
return new LazyAreaEval(new AI(firstRow, firstCol, lastRow, lastCol), sre);
}
private static int parseRowRef(String refStrPart) {
return CellReference.convertColStringToIndex(refStrPart);
}
private static int parseColRef(String refStrPart) {
return Integer.parseInt(refStrPart) - 1;
}
private static final class AI implements AreaI {
private final int _fr;
private final int _lr;
private final int _fc;
private final int _lc;
public AI(int fr, int fc, int lr, int lc) {
_fr = Math.min(fr, lr);
_lr = Math.max(fr, lr);
_fc = Math.min(fc, lc);
_lc = Math.max(fc, lc);
}
public int getFirstColumn() {
return _fc;
}
public int getFirstRow() {
return _fr;
}
public int getLastColumn() {
return _lc;
}
public int getLastRow() {
return _lr;
}
}
private static NameType classifyCellReference(String str, SpreadsheetVersion ssVersion) {
int len = str.length();
if (len < 1) {
return CellReference.NameType.BAD_CELL_OR_NAMED_RANGE;
}
return CellReference.classifyCellReference(str, ssVersion);
}
}

View File

@ -106,8 +106,8 @@ final class OperationEvaluatorFactory {
_numberOfOperands = argCount; _numberOfOperands = argCount;
} }
public ValueEval evaluate(ValueEval[] args, int rowIndex, short columnIndex) { public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
return _function.evaluate(args, rowIndex, columnIndex); return _function.evaluate(args, ec.getRowIndex(), (short) ec.getColumnIndex());
} }
public int getNumberOfOperands() { public int getNumberOfOperands() {

View File

@ -27,14 +27,15 @@ final class SheetRefEvaluator {
private final WorkbookEvaluator _bookEvaluator; private final WorkbookEvaluator _bookEvaluator;
private final EvaluationTracker _tracker; private final EvaluationTracker _tracker;
private final EvaluationSheet _sheet;
private final int _sheetIndex; private final int _sheetIndex;
private EvaluationSheet _sheet;
public SheetRefEvaluator(WorkbookEvaluator bookEvaluator, EvaluationTracker tracker, public SheetRefEvaluator(WorkbookEvaluator bookEvaluator, EvaluationTracker tracker, int sheetIndex) {
EvaluationWorkbook _workbook, int sheetIndex) { if (sheetIndex < 0) {
throw new IllegalArgumentException("Invalid sheetIndex: " + sheetIndex + ".");
}
_bookEvaluator = bookEvaluator; _bookEvaluator = bookEvaluator;
_tracker = tracker; _tracker = tracker;
_sheet = _workbook.getSheet(sheetIndex);
_sheetIndex = sheetIndex; _sheetIndex = sheetIndex;
} }
@ -43,6 +44,13 @@ final class SheetRefEvaluator {
} }
public ValueEval getEvalForCell(int rowIndex, int columnIndex) { public ValueEval getEvalForCell(int rowIndex, int columnIndex) {
return _bookEvaluator.evaluateReference(_sheet, _sheetIndex, rowIndex, columnIndex, _tracker); return _bookEvaluator.evaluateReference(getSheet(), _sheetIndex, rowIndex, columnIndex, _tracker);
}
private EvaluationSheet getSheet() {
if (_sheet == null) {
_sheet = _bookEvaluator.getSheet(_sheetIndex);
}
return _sheet;
} }
} }

View File

@ -51,7 +51,6 @@ import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval; import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval; import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.FunctionEval;
import org.apache.poi.hssf.record.formula.eval.MissingArgEval; import org.apache.poi.hssf.record.formula.eval.MissingArgEval;
import org.apache.poi.hssf.record.formula.eval.NameEval; import org.apache.poi.hssf.record.formula.eval.NameEval;
import org.apache.poi.hssf.record.formula.eval.NameXEval; import org.apache.poi.hssf.record.formula.eval.NameXEval;
@ -61,7 +60,7 @@ import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.util.CellReference; import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet; import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
import org.apache.poi.ss.formula.eval.NotImplementedException; import org.apache.poi.ss.formula.eval.NotImplementedException;
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Cell;
@ -80,10 +79,12 @@ public final class WorkbookEvaluator {
private final EvaluationWorkbook _workbook; private final EvaluationWorkbook _workbook;
private EvaluationCache _cache; private EvaluationCache _cache;
/** part of cache entry key (useful when evaluating multiple workbooks) */
private int _workbookIx; private int _workbookIx;
private final IEvaluationListener _evaluationListener; private final IEvaluationListener _evaluationListener;
private final Map<EvaluationSheet, Integer> _sheetIndexesBySheet; private final Map<EvaluationSheet, Integer> _sheetIndexesBySheet;
private final Map<String, Integer> _sheetIndexesByName;
private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment; private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
private final IStabilityClassifier _stabilityClassifier; private final IStabilityClassifier _stabilityClassifier;
@ -96,6 +97,7 @@ public final class WorkbookEvaluator {
_evaluationListener = evaluationListener; _evaluationListener = evaluationListener;
_cache = new EvaluationCache(evaluationListener); _cache = new EvaluationCache(evaluationListener);
_sheetIndexesBySheet = new IdentityHashMap<EvaluationSheet, Integer>(); _sheetIndexesBySheet = new IdentityHashMap<EvaluationSheet, Integer>();
_sheetIndexesByName = new IdentityHashMap<String, Integer>();
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY; _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
_workbookIx = 0; _workbookIx = 0;
_stabilityClassifier = stabilityClassifier; _stabilityClassifier = stabilityClassifier;
@ -108,6 +110,10 @@ public final class WorkbookEvaluator {
return _workbook.getSheetName(sheetIndex); return _workbook.getSheetName(sheetIndex);
} }
/* package */ EvaluationSheet getSheet(int sheetIndex) {
return _workbook.getSheet(sheetIndex);
}
private static boolean isDebugLogEnabled() { private static boolean isDebugLogEnabled() {
return false; return false;
} }
@ -125,11 +131,22 @@ public final class WorkbookEvaluator {
return _collaboratingWorkbookEnvironment; return _collaboratingWorkbookEnvironment;
} }
/**
* Discards the current workbook environment and attaches to the default 'empty' environment.
* Also resets evaluation cache.
*/
/* package */ void detachFromEnvironment() { /* package */ void detachFromEnvironment() {
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY; _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
_cache = new EvaluationCache(_evaluationListener); _cache = new EvaluationCache(_evaluationListener);
_workbookIx = 0; _workbookIx = 0;
} }
/**
* @return the evaluator for another workbook which is part of the same {@link CollaboratingWorkbooksEnvironment}
*/
/* package */ WorkbookEvaluator getOtherWorkbookEvaluator(String workbookName) throws WorkbookNotFoundException {
return _collaboratingWorkbookEnvironment.getWorkbookEvaluator(workbookName);
}
/* package */ IEvaluationListener getEvaluationListener() { /* package */ IEvaluationListener getEvaluationListener() {
return _evaluationListener; return _evaluationListener;
} }
@ -179,6 +196,23 @@ public final class WorkbookEvaluator {
return evaluateAny(srcCell, sheetIndex, srcCell.getRowIndex(), srcCell.getColumnIndex(), new EvaluationTracker(_cache)); return evaluateAny(srcCell, sheetIndex, srcCell.getRowIndex(), srcCell.getColumnIndex(), new EvaluationTracker(_cache));
} }
/**
* Case-insensitive.
* @return -1 if sheet with specified name does not exist
*/
/* package */ int getSheetIndex(String sheetName) {
Integer result = _sheetIndexesByName.get(sheetName);
if (result == null) {
int sheetIndex = _workbook.getSheetIndex(sheetName);
if (sheetIndex < 0) {
return -1;
}
result = new Integer(sheetIndex);
_sheetIndexesByName.put(sheetName, result);
}
return result.intValue();
}
/** /**
* @return never <code>null</code>, never {@link BlankEval} * @return never <code>null</code>, never {@link BlankEval}
@ -207,15 +241,16 @@ public final class WorkbookEvaluator {
if (!tracker.startEvaluate(cce)) { if (!tracker.startEvaluate(cce)) {
return ErrorEval.CIRCULAR_REF_ERROR; return ErrorEval.CIRCULAR_REF_ERROR;
} }
OperationEvaluationContext ec = new OperationEvaluationContext(this, _workbook, sheetIndex, rowIndex, columnIndex, tracker);
try { try {
Ptg[] ptgs = _workbook.getFormulaTokens(srcCell); Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
if (evalListener == null) { if (evalListener == null) {
result = evaluateFormula(sheetIndex, rowIndex, columnIndex, ptgs, tracker); result = evaluateFormula(ec, ptgs);
} else { } else {
evalListener.onStartEvaluate(srcCell, cce, ptgs); evalListener.onStartEvaluate(srcCell, cce, ptgs);
result = evaluateFormula(sheetIndex, rowIndex, columnIndex, ptgs, tracker); result = evaluateFormula(ec, ptgs);
evalListener.onEndEvaluate(cce, result); evalListener.onEndEvaluate(cce, result);
} }
@ -286,7 +321,7 @@ public final class WorkbookEvaluator {
throw new RuntimeException("Unexpected cell type (" + cellType + ")"); throw new RuntimeException("Unexpected cell type (" + cellType + ")");
} }
// visibility raised for testing // visibility raised for testing
/* package */ ValueEval evaluateFormula(int sheetIndex, int srcRowNum, int srcColNum, Ptg[] ptgs, EvaluationTracker tracker) { /* package */ ValueEval evaluateFormula(OperationEvaluationContext ec, Ptg[] ptgs) {
Stack<ValueEval> stack = new Stack<ValueEval>(); Stack<ValueEval> stack = new Stack<ValueEval>();
for (int i = 0, iSize = ptgs.length; i < iSize; i++) { for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
@ -329,12 +364,12 @@ public final class WorkbookEvaluator {
ops[j] = p; ops[j] = p;
} }
// logDebug("invoke " + operation + " (nAgs=" + numops + ")"); // logDebug("invoke " + operation + " (nAgs=" + numops + ")");
opResult = invokeOperation(operation, ops, _workbook, sheetIndex, srcRowNum, srcColNum); opResult = operation.evaluate(ops, ec);
if (opResult == MissingArgEval.instance) { if (opResult == MissingArgEval.instance) {
opResult = BlankEval.INSTANCE; opResult = BlankEval.INSTANCE;
} }
} else { } else {
opResult = getEvalForPtg(ptg, sheetIndex, tracker); opResult = getEvalForPtg(ptg, ec);
} }
if (opResult == null) { if (opResult == null) {
throw new RuntimeException("Evaluation result must not be null"); throw new RuntimeException("Evaluation result must not be null");
@ -347,7 +382,7 @@ public final class WorkbookEvaluator {
if (!stack.isEmpty()) { if (!stack.isEmpty()) {
throw new IllegalStateException("evaluation stack not empty"); throw new IllegalStateException("evaluation stack not empty");
} }
value = dereferenceValue(value, srcRowNum, srcColNum); value = dereferenceValue(value, ec.getRowIndex(), ec.getColumnIndex());
if (value == BlankEval.INSTANCE) { if (value == BlankEval.INSTANCE) {
// Note Excel behaviour here. A blank final final value is converted to zero. // Note Excel behaviour here. A blank final final value is converted to zero.
return NumberEval.ZERO; return NumberEval.ZERO;
@ -384,31 +419,6 @@ public final class WorkbookEvaluator {
return evaluationResult; return evaluationResult;
} }
private static ValueEval invokeOperation(OperationEval operation, ValueEval[] ops,
EvaluationWorkbook workbook, int sheetIndex, int srcRowNum, int srcColNum) {
if(operation instanceof FunctionEval) {
FunctionEval fe = (FunctionEval) operation;
if(fe.isFreeRefFunction()) {
return fe.getFreeRefFunction().evaluate(ops, workbook, sheetIndex, srcRowNum, srcColNum);
}
}
return operation.evaluate(ops, srcRowNum, (short)srcColNum);
}
private SheetRefEvaluator createExternSheetRefEvaluator(EvaluationTracker tracker,
ExternSheetReferenceToken ptg) {
int externSheetIndex = ptg.getExternSheetIndex();
ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
if (externalSheet != null) {
WorkbookEvaluator otherEvaluator = _collaboratingWorkbookEnvironment.getWorkbookEvaluator(externalSheet.getWorkbookName());
EvaluationWorkbook otherBook = otherEvaluator._workbook;
int otherSheetIndex = otherBook.getSheetIndex(externalSheet.getSheetName());
return new SheetRefEvaluator(otherEvaluator, tracker, otherBook, otherSheetIndex);
}
int otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex);
return new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
}
/** /**
* returns an appropriate Eval impl instance for the Ptg. The Ptg must be * returns an appropriate Eval impl instance for the Ptg. The Ptg must be
@ -416,7 +426,7 @@ public final class WorkbookEvaluator {
* StringPtg, BoolPtg <br/>special Note: OperationPtg subtypes cannot be * StringPtg, BoolPtg <br/>special Note: OperationPtg subtypes cannot be
* passed here! * passed here!
*/ */
private ValueEval getEvalForPtg(Ptg ptg, int sheetIndex, EvaluationTracker tracker) { private ValueEval getEvalForPtg(Ptg ptg, OperationEvaluationContext ec) {
// consider converting all these (ptg instanceof XxxPtg) expressions to (ptg.getClass() == XxxPtg.class) // consider converting all these (ptg instanceof XxxPtg) expressions to (ptg.getClass() == XxxPtg.class)
if (ptg instanceof NamePtg) { if (ptg instanceof NamePtg) {
@ -427,7 +437,7 @@ public final class WorkbookEvaluator {
return new NameEval(nameRecord.getNameText()); return new NameEval(nameRecord.getNameText());
} }
if (nameRecord.hasFormula()) { if (nameRecord.hasFormula()) {
return evaluateNameFormula(nameRecord.getNameDefinition(), sheetIndex, tracker); return evaluateNameFormula(nameRecord.getNameDefinition(), ec);
} }
throw new RuntimeException("Don't now how to evalate name '" + nameRecord.getNameText() + "'"); throw new RuntimeException("Don't now how to evalate name '" + nameRecord.getNameText() + "'");
@ -460,15 +470,15 @@ public final class WorkbookEvaluator {
} }
if (ptg instanceof Ref3DPtg) { if (ptg instanceof Ref3DPtg) {
Ref3DPtg refPtg = (Ref3DPtg) ptg; Ref3DPtg refPtg = (Ref3DPtg) ptg;
SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, refPtg); SheetRefEvaluator sre = ec.createExternSheetRefEvaluator(refPtg);
return new LazyRefEval(refPtg, sre); return new LazyRefEval(refPtg, sre);
} }
if (ptg instanceof Area3DPtg) { if (ptg instanceof Area3DPtg) {
Area3DPtg aptg = (Area3DPtg) ptg; Area3DPtg aptg = (Area3DPtg) ptg;
SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, aptg); SheetRefEvaluator sre = ec.createExternSheetRefEvaluator(aptg);
return new LazyAreaEval(aptg, sre); return new LazyAreaEval(aptg, sre);
} }
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, sheetIndex); SheetRefEvaluator sre = ec.getRefEvaluatorForCurrentSheet();
if (ptg instanceof RefPtg) { if (ptg instanceof RefPtg) {
return new LazyRefEval(((RefPtg) ptg), sre); return new LazyRefEval(((RefPtg) ptg), sre);
} }
@ -490,11 +500,11 @@ public final class WorkbookEvaluator {
throw new RuntimeException("Unexpected ptg class (" + ptg.getClass().getName() + ")"); throw new RuntimeException("Unexpected ptg class (" + ptg.getClass().getName() + ")");
} }
private ValueEval evaluateNameFormula(Ptg[] ptgs, int sheetIndex, EvaluationTracker tracker) { private ValueEval evaluateNameFormula(Ptg[] ptgs, OperationEvaluationContext ec) {
if (ptgs.length > 1) { if (ptgs.length > 1) {
throw new RuntimeException("Complex name formulas not supported yet"); throw new RuntimeException("Complex name formulas not supported yet");
} }
return getEvalForPtg(ptgs[0], sheetIndex, tracker); return getEvalForPtg(ptgs[0], ec);
} }
/** /**

View File

@ -89,7 +89,7 @@ public final class TestEqualEval extends TestCase {
new StringEval(a), new StringEval(a),
new StringEval(b), new StringEval(b),
}; };
ValueEval result = cmpOp.evaluate(args, 10, (short)20); ValueEval result = evaluate(cmpOp, args, 10, 20);
assertEquals(BoolEval.class, result.getClass()); assertEquals(BoolEval.class, result.getClass());
BoolEval be = (BoolEval) result; BoolEval be = (BoolEval) result;
return be.getBooleanValue(); return be.getBooleanValue();

View File

@ -64,7 +64,7 @@ public final class TestRangeEval extends TestCase {
createRefEval(refB), createRefEval(refB),
}; };
AreaReference ar = new AreaReference(expectedAreaRef); AreaReference ar = new AreaReference(expectedAreaRef);
ValueEval result = RangeEval.instance.evaluate(args, 0, (short)0); ValueEval result = EvalInstances.Range.evaluate(args, 0, (short)0);
assertTrue(result instanceof AreaEval); assertTrue(result instanceof AreaEval);
AreaEval ae = (AreaEval) result; AreaEval ae = (AreaEval) result;
assertEquals(ar.getFirstCell().getRow(), ae.getFirstRow()); assertEquals(ar.getFirstCell().getRow(), ae.getFirstRow());

View File

@ -51,7 +51,7 @@ public final class TestUnaryPlusEval extends TestCase {
EvalFactory.createAreaEval(areaPtg, values), EvalFactory.createAreaEval(areaPtg, values),
}; };
double result = NumericFunctionInvoker.invoke(UnaryPlusEval.instance, args, 10, (short)20); double result = NumericFunctionInvoker.invoke(EvalInstances.UnaryPlus, args, 10, (short)20);
assertEquals(35, result, 0); assertEquals(35, result, 0);
} }

View File

@ -0,0 +1,182 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.functions;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
/**
* Tests for the INDIRECT() function.</p>
*
* @author Josh Micich
*/
public final class TestIndirect extends TestCase {
// convenient access to namespace
private static final ErrorEval EE = null;
private static void createDataRow(HSSFSheet sheet, int rowIndex, double... vals) {
HSSFRow row = sheet.createRow(rowIndex);
for (int i = 0; i < vals.length; i++) {
row.createCell(i).setCellValue(vals[i]);
}
}
private static HSSFWorkbook createWBA() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet1 = wb.createSheet("Sheet1");
HSSFSheet sheet2 = wb.createSheet("Sheet2");
HSSFSheet sheet3 = wb.createSheet("John's sales");
createDataRow(sheet1, 0, 11, 12, 13, 14);
createDataRow(sheet1, 1, 21, 22, 23, 24);
createDataRow(sheet1, 2, 31, 32, 33, 34);
createDataRow(sheet2, 0, 50, 55, 60, 65);
createDataRow(sheet2, 1, 51, 56, 61, 66);
createDataRow(sheet2, 2, 52, 57, 62, 67);
createDataRow(sheet3, 0, 30, 31, 32);
createDataRow(sheet3, 1, 33, 34, 35);
return wb;
}
private static HSSFWorkbook createWBB() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet1 = wb.createSheet("Sheet1");
HSSFSheet sheet2 = wb.createSheet("Sheet2");
HSSFSheet sheet3 = wb.createSheet("## Look here!");
createDataRow(sheet1, 0, 400, 440, 480, 520);
createDataRow(sheet1, 1, 420, 460, 500, 540);
createDataRow(sheet2, 0, 50, 55, 60, 65);
createDataRow(sheet2, 1, 51, 56, 61, 66);
createDataRow(sheet3, 0, 42);
return wb;
}
public void testBasic() {
HSSFWorkbook wbA = createWBA();
HSSFCell c = wbA.getSheetAt(0).createRow(5).createCell(2);
HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA);
// non-error cases
confirm(feA, c, "INDIRECT(\"C2\")", 23);
confirm(feA, c, "INDIRECT(\"$C2\")", 23);
confirm(feA, c, "INDIRECT(\"C$2\")", 23);
confirm(feA, c, "SUM(INDIRECT(\"Sheet2!B1:C3\"))", 351); // area ref
confirm(feA, c, "SUM(INDIRECT(\"Sheet2! B1 : C3 \"))", 351); // spaces in area ref
confirm(feA, c, "SUM(INDIRECT(\"'John''s sales'!A1:C1\"))", 93); // special chars in sheet name
confirm(feA, c, "INDIRECT(\"'Sheet1'!B3\")", 32); // redundant sheet name quotes
confirm(feA, c, "INDIRECT(\"sHeet1!B3\")", 32); // case-insensitive sheet name
confirm(feA, c, "INDIRECT(\" D3 \")", 34); // spaces around cell ref
confirm(feA, c, "INDIRECT(\"Sheet1! D3 \")", 34); // spaces around cell ref
confirm(feA, c, "INDIRECT(\"A1\", TRUE)", 11); // explicit arg1. only TRUE supported so far
confirm(feA, c, "INDIRECT(\"A1:G1\")", 13); // de-reference area ref (note formula is in C4)
// simple error propagation:
// arg0 is evaluated to text first
confirm(feA, c, "INDIRECT(#DIV/0!)", EE.DIV_ZERO);
confirm(feA, c, "INDIRECT(#DIV/0!)", EE.DIV_ZERO);
confirm(feA, c, "INDIRECT(#NAME?, \"x\")", EE.NAME_INVALID);
confirm(feA, c, "INDIRECT(#NUM!, #N/A)", EE.NUM_ERROR);
// arg1 is evaluated to boolean before arg0 is decoded
confirm(feA, c, "INDIRECT(\"garbage\", #N/A)", EE.NA);
confirm(feA, c, "INDIRECT(\"garbage\", \"\")", EE.VALUE_INVALID); // empty string is not valid boolean
confirm(feA, c, "INDIRECT(\"garbage\", \"flase\")", EE.VALUE_INVALID); // must be "TRUE" or "FALSE"
// spaces around sheet name (with or without quotes makes no difference)
confirm(feA, c, "INDIRECT(\"'Sheet1 '!D3\")", EE.REF_INVALID);
confirm(feA, c, "INDIRECT(\" Sheet1!D3\")", EE.REF_INVALID);
confirm(feA, c, "INDIRECT(\"'Sheet1' !D3\")", EE.REF_INVALID);
confirm(feA, c, "SUM(INDIRECT(\"'John's sales'!A1:C1\"))", EE.REF_INVALID); // bad quote escaping
confirm(feA, c, "INDIRECT(\"[Book1]Sheet1!A1\")", EE.REF_INVALID); // unknown external workbook
confirm(feA, c, "INDIRECT(\"Sheet3!A1\")", EE.REF_INVALID); // unknown sheet
if (false) { // TODO - support evaluation of defined names
confirm(feA, c, "INDIRECT(\"Sheet1!IW1\")", EE.REF_INVALID); // bad column
confirm(feA, c, "INDIRECT(\"Sheet1!A65537\")", EE.REF_INVALID); // bad row
}
confirm(feA, c, "INDIRECT(\"Sheet1!A 1\")", EE.REF_INVALID); // space in cell ref
}
public void testMultipleWorkbooks() {
HSSFWorkbook wbA = createWBA();
HSSFCell cellA = wbA.getSheetAt(0).createRow(10).createCell(0);
HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA);
HSSFWorkbook wbB = createWBB();
HSSFCell cellB = wbB.getSheetAt(0).createRow(10).createCell(0);
HSSFFormulaEvaluator feB = new HSSFFormulaEvaluator(wbB);
String[] workbookNames = { "MyBook", "Figures for January", };
HSSFFormulaEvaluator[] evaluators = { feA, feB, };
HSSFFormulaEvaluator.setupEnvironment(workbookNames, evaluators);
confirm(feB, cellB, "INDIRECT(\"'[Figures for January]## Look here!'!A1\")", 42); // same wb
confirm(feA, cellA, "INDIRECT(\"'[Figures for January]## Look here!'!A1\")", 42); // across workbooks
// 2 level recursion
confirm(feB, cellB, "INDIRECT(\"[MyBook]Sheet2!A1\")", 50); // set up (and check) first level
confirm(feA, cellA, "INDIRECT(\"'[Figures for January]Sheet1'!A11\")", 50); // points to cellB
}
private static void confirm(FormulaEvaluator fe, Cell cell, String formula,
double expectedResult) {
fe.clearAllCachedResultValues();
cell.setCellFormula(formula);
CellValue cv = fe.evaluate(cell);
if (cv.getCellType() != Cell.CELL_TYPE_NUMERIC) {
throw new AssertionFailedError("expected numeric cell type but got " + cv.formatAsString());
}
assertEquals(expectedResult, cv.getNumberValue(), 0.0);
}
private static void confirm(FormulaEvaluator fe, Cell cell, String formula,
ErrorEval expectedResult) {
fe.clearAllCachedResultValues();
cell.setCellFormula(formula);
CellValue cv = fe.evaluate(cell);
if (cv.getCellType() != Cell.CELL_TYPE_ERROR) {
throw new AssertionFailedError("expected error cell type but got " + cv.formatAsString());
}
int expCode = expectedResult.getErrorCode();
if (cv.getErrorValue() != expCode) {
throw new AssertionFailedError("Expected error '" + EE.getText(expCode)
+ "' but got '" + cv.formatAsString() + "'.");
}
}
}

View File

@ -42,8 +42,9 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
*/ */
public class TestWorkbookEvaluator extends TestCase { public class TestWorkbookEvaluator extends TestCase {
private static WorkbookEvaluator createEvaluator() { private static ValueEval evaluateFormula(Ptg[] ptgs) {
return new WorkbookEvaluator(null, null); OperationEvaluationContext ec = new OperationEvaluationContext(null, null, 0, 0, 0, null);
return new WorkbookEvaluator(null, null).evaluateFormula(ec, ptgs);
} }
/** /**
@ -57,7 +58,7 @@ public class TestWorkbookEvaluator extends TestCase {
AttrPtg.SUM, AttrPtg.SUM,
}; };
ValueEval result = createEvaluator().evaluateFormula(0, 0, 0, ptgs, null); ValueEval result = evaluateFormula(ptgs);
assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0); assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
} }
@ -78,7 +79,7 @@ public class TestWorkbookEvaluator extends TestCase {
ptg, ptg,
}; };
ValueEval result = createEvaluator().evaluateFormula(0, 0, 0, ptgs, null); ValueEval result = evaluateFormula(ptgs);
assertEquals(ErrorEval.REF_INVALID, result); assertEquals(ErrorEval.REF_INVALID, result);
} }
@ -93,7 +94,7 @@ public class TestWorkbookEvaluator extends TestCase {
AttrPtg.SUM, AttrPtg.SUM,
}; };
ValueEval result = createEvaluator().evaluateFormula(0, 0, 0, ptgs, null); ValueEval result = evaluateFormula(ptgs);
assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0); assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
} }