123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548 |
- /* ====================================================================
- 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.ss.SpreadsheetVersion;
- import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
- import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalName;
- import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
- import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheetRange;
- import org.apache.poi.ss.formula.constant.ErrorConstant;
- import org.apache.poi.ss.formula.eval.AreaEval;
- import org.apache.poi.ss.formula.eval.BoolEval;
- import org.apache.poi.ss.formula.eval.ErrorEval;
- import org.apache.poi.ss.formula.eval.ExternalNameEval;
- import org.apache.poi.ss.formula.eval.FunctionNameEval;
- import org.apache.poi.ss.formula.eval.NumberEval;
- import org.apache.poi.ss.formula.eval.RefEval;
- import org.apache.poi.ss.formula.eval.StringEval;
- import org.apache.poi.ss.formula.eval.ValueEval;
- import org.apache.poi.ss.formula.functions.FreeRefFunction;
- import org.apache.poi.ss.formula.functions.Function;
- import org.apache.poi.ss.formula.ptg.*;
- 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
- */
- public final class OperationEvaluationContext {
- public static final FreeRefFunction UDF = UserDefinedFunction.instance;
- private final EvaluationWorkbook _workbook;
- private final int _sheetIndex;
- private final int _rowIndex;
- private final int _columnIndex;
- private final EvaluationTracker _tracker;
- private final WorkbookEvaluator _bookEvaluator;
- private final boolean _isSingleValue;
- private final boolean _isInArrayContext;
-
- public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum,
- int srcColNum, EvaluationTracker tracker) {
- this(bookEvaluator, workbook, sheetIndex, srcRowNum, srcColNum, tracker, true);
- }
-
- public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum,
- int srcColNum, EvaluationTracker tracker, boolean isSingleValue) {
- this(bookEvaluator, workbook, sheetIndex, srcRowNum, srcColNum, tracker, isSingleValue, null);
- }
-
- public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum,
- int srcColNum, EvaluationTracker tracker, boolean isSingleValue, Ptg[] ptgs) {
- _bookEvaluator = bookEvaluator;
- _workbook = workbook;
- _sheetIndex = sheetIndex;
- _rowIndex = srcRowNum;
- _columnIndex = srcColNum;
- _tracker = tracker;
- _isSingleValue = isSingleValue;
-
- _isInArrayContext = isInArrayContext(ptgs);
- }
-
- /**
- * Check if the given formula should be evaluated in array mode.
- *
- * <p>
- * Normally, array formulas are recognized from their definition:
- * pressing Ctrl+Shift+Enter in Excel marks the input as an array entered formula.
- *</p>
- * <p>
- * However, in some cases Excel evaluates tokens in array mode depending on the context.
- * The <code>INDEX( area, row_num, [column_num])</code> function is an example:
- *
- * If the array argument includes more than one row and row_num is omitted or set to 0,
- * the Excel INDEX function returns an array of the entire column. Similarly, if array
- * includes more than one column and the column_num argument is omitted or set to 0,
- * the INDEX formula returns the entire row
- * </p>
- *
- * @param ptgs parsed formula to analyze
- * @return whether the formula should be evaluated in array mode
- */
- private boolean isInArrayContext(Ptg[] ptgs){
- boolean arrayMode = false;
- if(ptgs != null) for(int j = ptgs.length - 1; j >= 0; j--){
- if(ptgs[j] instanceof FuncVarPtg){
- FuncVarPtg f = (FuncVarPtg)ptgs[j];
- if(f.getName().equals("INDEX") && f.getNumberOfOperands() <= 3){
- // check 2nd and 3rd arguments.
- arrayMode = (ptgs[j - 1] instanceof IntPtg && ((IntPtg)ptgs[j - 1]).getValue() == 0)
- || (ptgs[j - 2] instanceof IntPtg && ((IntPtg)ptgs[j - 2]).getValue() == 0);
- if(arrayMode) break;
- }
- }
- }
- return arrayMode;
- }
-
- public boolean isInArrayContext(){
- return _isInArrayContext;
- }
-
- public EvaluationWorkbook getWorkbook() {
- return _workbook;
- }
-
- public int getRowIndex() {
- return _rowIndex;
- }
-
- public int getColumnIndex() {
- return _columnIndex;
- }
-
- SheetRangeEvaluator createExternSheetRefEvaluator(ExternSheetReferenceToken ptg) {
- return createExternSheetRefEvaluator(ptg.getExternSheetIndex());
- }
- SheetRangeEvaluator createExternSheetRefEvaluator(String firstSheetName, String lastSheetName, int externalWorkbookNumber) {
- ExternalSheet externalSheet = _workbook.getExternalSheet(firstSheetName, lastSheetName, externalWorkbookNumber);
- return createExternSheetRefEvaluator(externalSheet);
- }
- SheetRangeEvaluator createExternSheetRefEvaluator(int externSheetIndex) {
- ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
- return createExternSheetRefEvaluator(externalSheet);
- }
- SheetRangeEvaluator createExternSheetRefEvaluator(ExternalSheet externalSheet) {
- WorkbookEvaluator targetEvaluator;
- int otherFirstSheetIndex;
- int otherLastSheetIndex = -1;
- if (externalSheet == null || externalSheet.getWorkbookName() == null) {
- // sheet is in same workbook
- targetEvaluator = _bookEvaluator;
- if(externalSheet == null) {
- otherFirstSheetIndex = 0;
- } else {
- otherFirstSheetIndex = _workbook.getSheetIndex(externalSheet.getSheetName());
- }
-
- if (externalSheet instanceof ExternalSheetRange) {
- String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName();
- otherLastSheetIndex = _workbook.getSheetIndex(lastSheetName);
- }
- } 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(), e);
- }
-
- otherFirstSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName());
- if (externalSheet instanceof ExternalSheetRange) {
- String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName();
- otherLastSheetIndex = targetEvaluator.getSheetIndex(lastSheetName);
- }
-
- if (otherFirstSheetIndex < 0) {
- throw new RuntimeException("Invalid sheet name '" + externalSheet.getSheetName()
- + "' in bool '" + workbookName + "'.");
- }
- }
-
- if (otherLastSheetIndex == -1) {
- // Reference to just one sheet
- otherLastSheetIndex = otherFirstSheetIndex;
- }
-
- SheetRefEvaluator[] evals = new SheetRefEvaluator[otherLastSheetIndex-otherFirstSheetIndex+1];
- for (int i=0; i<evals.length; i++) {
- int otherSheetIndex = i+otherFirstSheetIndex;
- evals[i] = new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex);
- }
- return new SheetRangeEvaluator(otherFirstSheetIndex, otherLastSheetIndex, evals);
- }
-
- /**
- * @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 SheetRangeEvaluator getRefEvaluatorForCurrentSheet() {
- SheetRefEvaluator sre = new SheetRefEvaluator(_bookEvaluator, _tracker, _sheetIndex);
- return new SheetRangeEvaluator(_sheetIndex, sre);
- }
-
-
-
- /**
- * 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 se = createExternSheetRefEvaluator(workbookName, sheetName);
- if (se == null) {
- return ErrorEval.REF_INVALID;
- }
- SheetRangeEvaluator sre = new SheetRangeEvaluator(_sheetIndex, se);
-
- // ugly typecast - TODO - make spreadsheet version more easily accessible
- SpreadsheetVersion ssVersion = _workbook.getSpreadsheetVersion();
-
- NameType part1refType = classifyCellReference(refStrPart1, ssVersion);
- switch (part1refType) {
- case BAD_CELL_OR_NAMED_RANGE:
- return ErrorEval.REF_INVALID;
- case NAMED_RANGE:
- EvaluationName nm = _workbook.getName(refStrPart1, _sheetIndex);
- if(!nm.isRange()){
- throw new RuntimeException("Specified name '" + refStrPart1 + "' is not a range as expected.");
- }
- return _bookEvaluator.evaluateNameFormula(nm.getNameDefinition(), this);
- }
- 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;
- if (part2refType.equals(NameType.COLUMN))
- {
- lastRow = ssVersion.getLastRowIndex();
- firstCol = parseRowRef(refStrPart1);
- lastCol = parseRowRef(refStrPart2);
- }
- else {
- lastRow = ssVersion.getLastRowIndex();
- firstCol = parseColRef(refStrPart1);
- lastCol = parseColRef(refStrPart2);
- }
- break;
- case ROW:
- // support of cell range in the form of integer:integer
- firstCol = 0;
- if (part2refType.equals(NameType.ROW))
- {
- firstRow = parseColRef(refStrPart1);
- lastRow = parseColRef(refStrPart2);
- lastCol = ssVersion.getLastColumnIndex();
- } else {
- 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(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 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);
- }
-
- public FreeRefFunction findUserDefinedFunction(String functionName) {
- return _bookEvaluator.findUserDefinedFunction(functionName);
- }
-
- public ValueEval getRefEval(int rowIndex, int columnIndex) {
- SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet();
- return new LazyRefEval(rowIndex, columnIndex, sre);
- }
- public ValueEval getRef3DEval(Ref3DPtg rptg) {
- SheetRangeEvaluator sre = createExternSheetRefEvaluator(rptg.getExternSheetIndex());
- return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre);
- }
- public ValueEval getRef3DEval(Ref3DPxg rptg) {
- SheetRangeEvaluator sre = createExternSheetRefEvaluator(
- rptg.getSheetName(), rptg.getLastSheetName(), rptg.getExternalWorkbookNumber());
- return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre);
- }
-
- public ValueEval getAreaEval(int firstRowIndex, int firstColumnIndex,
- int lastRowIndex, int lastColumnIndex) {
- SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet();
- return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre);
- }
- public ValueEval getArea3DEval(Area3DPtg aptg) {
- SheetRangeEvaluator sre = createExternSheetRefEvaluator(aptg.getExternSheetIndex());
- return new LazyAreaEval(aptg.getFirstRow(), aptg.getFirstColumn(),
- aptg.getLastRow(), aptg.getLastColumn(), sre);
- }
- public ValueEval getArea3DEval(Area3DPxg aptg) {
- SheetRangeEvaluator sre = createExternSheetRefEvaluator(
- aptg.getSheetName(), aptg.getLastSheetName(), aptg.getExternalWorkbookNumber());
- return new LazyAreaEval(aptg.getFirstRow(), aptg.getFirstColumn(),
- aptg.getLastRow(), aptg.getLastColumn(), sre);
- }
-
- public ValueEval getAreaValueEval(int firstRowIndex, int firstColumnIndex,
- int lastRowIndex, int lastColumnIndex, Object[][] tokens) {
-
- ValueEval values[] = new ValueEval[tokens.length * tokens[0].length];
-
- int index = 0;
- for (int jdx = 0; jdx < tokens.length; jdx++) {
- for (int idx = 0; idx < tokens[0].length; idx++) {
- values[index++] = convertObjectEval(tokens[jdx][idx]);
- }
- }
-
- return new CacheAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex,
- lastColumnIndex, values);
- }
-
- private ValueEval convertObjectEval(Object token) {
- if (token == null) {
- throw new RuntimeException("Array item cannot be null");
- }
- if (token instanceof String) {
- return new StringEval((String)token);
- }
- if (token instanceof Double) {
- return new NumberEval(((Double)token).doubleValue());
- }
- if (token instanceof Boolean) {
- return BoolEval.valueOf(((Boolean)token).booleanValue());
- }
- if (token instanceof ErrorConstant) {
- return ErrorEval.valueOf(((ErrorConstant)token).getErrorCode());
- }
- throw new IllegalArgumentException("Unexpected constant class (" + token.getClass().getName() + ")");
- }
-
-
- public ValueEval getNameXEval(NameXPtg nameXPtg) {
- // Is the name actually on our workbook?
- ExternalSheet externSheet = _workbook.getExternalSheet(nameXPtg.getSheetRefIndex());
- if(externSheet == null || externSheet.getWorkbookName() == null) {
- // External reference to our own workbook's name
- return getLocalNameXEval(nameXPtg);
- }
-
- // Look it up for the external workbook
- String workbookName = externSheet.getWorkbookName();
- ExternalName externName = _workbook.getExternalName(
- nameXPtg.getSheetRefIndex(),
- nameXPtg.getNameIndex()
- );
- return getExternalNameXEval(externName, workbookName);
- }
- public ValueEval getNameXEval(NameXPxg nameXPxg) {
- ExternalSheet externSheet = _workbook.getExternalSheet(nameXPxg.getSheetName(), null, nameXPxg.getExternalWorkbookNumber());
- if(externSheet == null || externSheet.getWorkbookName() == null) {
- // External reference to our own workbook's name
- return getLocalNameXEval(nameXPxg);
- }
-
- // Look it up for the external workbook
- String workbookName = externSheet.getWorkbookName();
- ExternalName externName = _workbook.getExternalName(
- nameXPxg.getNameName(),
- nameXPxg.getSheetName(),
- nameXPxg.getExternalWorkbookNumber()
- );
- return getExternalNameXEval(externName, workbookName);
- }
-
- private ValueEval getLocalNameXEval(NameXPxg nameXPxg) {
- // Look up the sheet, if present
- int sIdx = -1;
- if (nameXPxg.getSheetName() != null) {
- sIdx = _workbook.getSheetIndex(nameXPxg.getSheetName());
- }
-
- // Is it a name or a function?
- String name = nameXPxg.getNameName();
- EvaluationName evalName = _workbook.getName(name, sIdx);
- if (evalName != null) {
- // Process it as a name
- return new ExternalNameEval(evalName);
- } else {
- // Must be an external function
- return new FunctionNameEval(name);
- }
- }
- private ValueEval getLocalNameXEval(NameXPtg nameXPtg) {
- String name = _workbook.resolveNameXText(nameXPtg);
-
- // Try to parse it as a name
- int sheetNameAt = name.indexOf('!');
- EvaluationName evalName = null;
- if (sheetNameAt > -1) {
- // Sheet based name
- String sheetName = name.substring(0, sheetNameAt);
- String nameName = name.substring(sheetNameAt+1);
- evalName = _workbook.getName(nameName, _workbook.getSheetIndex(sheetName));
- } else {
- // Workbook based name
- evalName = _workbook.getName(name, -1);
- }
-
- if (evalName != null) {
- // Process it as a name
- return new ExternalNameEval(evalName);
- } else {
- // Must be an external function
- return new FunctionNameEval(name);
- }
- }
- public int getSheetIndex() {
- return _sheetIndex;
- }
-
- /**
- * default true
- * @return flag indicating whether evaluation should "unwrap" the result to a single value based on the context row/column
- */
- public boolean isSingleValue() {
- return _isSingleValue;
- }
-
- private ValueEval getExternalNameXEval(ExternalName externName, String workbookName) {
- try {
- // Fetch the workbook this refers to, and the name as defined with that
- WorkbookEvaluator refWorkbookEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
- EvaluationName evaluationName = refWorkbookEvaluator.getName(externName.getName(),externName.getIx()-1);
- if (evaluationName != null && evaluationName.hasFormula()){
- if (evaluationName.getNameDefinition().length > 1) {
- throw new RuntimeException("Complex name formulas not supported yet");
- }
-
- // Need to evaluate the reference in the context of the other book
- OperationEvaluationContext refWorkbookContext = new OperationEvaluationContext(
- refWorkbookEvaluator, refWorkbookEvaluator.getWorkbook(), -1, -1, -1, _tracker,
- true, evaluationName.getNameDefinition());
-
- Ptg ptg = evaluationName.getNameDefinition()[0];
- if (ptg instanceof Ref3DPtg){
- Ref3DPtg ref3D = (Ref3DPtg)ptg;
- return refWorkbookContext.getRef3DEval(ref3D);
- } else if (ptg instanceof Ref3DPxg){
- Ref3DPxg ref3D = (Ref3DPxg)ptg;
- return refWorkbookContext.getRef3DEval(ref3D);
- } else if(ptg instanceof Area3DPtg){
- Area3DPtg area3D = (Area3DPtg)ptg;
- return refWorkbookContext.getArea3DEval(area3D);
- } else if(ptg instanceof Area3DPxg){
- Area3DPxg area3D = (Area3DPxg)ptg;
- return refWorkbookContext.getArea3DEval(area3D);
- }
- }
- return ErrorEval.REF_INVALID;
- } catch(WorkbookNotFoundException wnfe){
- return ErrorEval.REF_INVALID;
- }
- }
-
- }
|