Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Numeric Array Formula and Matrix Function Patch Applied
  • Loading branch information
Bob95132 committed Aug 30, 2017
1 parent 914ae60 commit 8a43c35
Show file tree
Hide file tree
Showing 16 changed files with 954 additions and 12 deletions.
13 changes: 13 additions & 0 deletions src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationCell.java
Expand Up @@ -20,6 +20,8 @@ Licensed to the Apache Software Foundation (ASF) under one or more
import org.apache.poi.ss.formula.EvaluationCell;
import org.apache.poi.ss.formula.EvaluationSheet;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.util.CellRangeAddress;

/**
* HSSF wrapper for a cell under evaluation
*/
Expand Down Expand Up @@ -93,6 +95,17 @@ public EvaluationSheet getSheet() {
public String getStringCellValue() {
return _cell.getRichStringCellValue().getString();
}

@Override
public CellRangeAddress getArrayFormulaRange() {
return _cell.getArrayFormulaRange();
}

@Override
public boolean isPartOfArrayFormulaGroup() {
return _cell.isPartOfArrayFormulaGroup();
}

/**
* Will return {@link CellType} in a future version of POI.
* For forwards compatibility, do not hard-code cell type literals in your code.
Expand Down
131 changes: 131 additions & 0 deletions src/java/org/apache/poi/ss/formula/CacheAreaEval.java
@@ -0,0 +1,131 @@
/* ====================================================================
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.formula.TwoDEval;
import org.apache.poi.ss.formula.eval.AreaEval;
import org.apache.poi.ss.formula.eval.AreaEvalBase;
import org.apache.poi.ss.formula.eval.BlankEval;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.ptg.AreaI;
import org.apache.poi.ss.formula.ptg.AreaI.OffsetArea;
import org.apache.poi.ss.util.CellReference;

/**
* @author Robert Hulbert
* Provides holding structure for temporary values in arrays during the evaluation process.
* As such, Row/Column references do not actually correspond to data in the file.
*/

public final class CacheAreaEval extends AreaEvalBase {

/* Value Containter */
private final ValueEval[] _values;

public CacheAreaEval(AreaI ptg, ValueEval[] values) {
super(ptg);
_values = values;
}

public CacheAreaEval(int firstRow, int firstColumn, int lastRow, int lastColumn, ValueEval[] values) {
super(firstRow, firstColumn, lastRow, lastColumn);
_values = values;
}

public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) {
return getRelativeValue(-1, relativeRowIndex, relativeColumnIndex);
}

public ValueEval getRelativeValue(int sheetIndex, int relativeRowIndex, int relativeColumnIndex) {
int oneDimensionalIndex = relativeRowIndex * getWidth() + relativeColumnIndex;
return _values[oneDimensionalIndex];
}

public AreaEval offset(int relFirstRowIx, int relLastRowIx,
int relFirstColIx, int relLastColIx) {

AreaI area = new OffsetArea(getFirstRow(), getFirstColumn(),
relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);

int height = area.getLastRow() - area.getFirstRow() + 1;
int width = area.getLastColumn() - area.getFirstColumn() + 1;

ValueEval newVals[] = new ValueEval[height * width];

int startRow = area.getFirstRow() - getFirstRow();
int startCol = area.getFirstColumn() - getFirstColumn();

for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
ValueEval temp;

/* CacheAreaEval is only temporary value representation, does not equal sheet selection
* so any attempts going beyond the selection results in BlankEval
*/
if (startRow + j > getLastRow() || startCol + i > getLastColumn()) {
temp = BlankEval.instance;
}
else {
temp = _values[(startRow + j) * getWidth() + (startCol + i)];
}
newVals[j * width + i] = temp;
}
}

return new CacheAreaEval(area, newVals);
}

public TwoDEval getRow(int rowIndex) {
if (rowIndex >= getHeight()) {
throw new IllegalArgumentException("Invalid rowIndex " + rowIndex
+ ". Allowable range is (0.." + getHeight() + ").");
}
int absRowIndex = getFirstRow() + rowIndex;
ValueEval[] values = new ValueEval[getWidth()];

for (int i = 0; i < values.length; i++) {
values[i] = getRelativeValue(rowIndex, i);
}
return new CacheAreaEval(absRowIndex, getFirstColumn() , absRowIndex, getLastColumn(), values);
}

public TwoDEval getColumn(int columnIndex) {
if (columnIndex >= getWidth()) {
throw new IllegalArgumentException("Invalid columnIndex " + columnIndex
+ ". Allowable range is (0.." + getWidth() + ").");
}
int absColIndex = getFirstColumn() + columnIndex;
ValueEval[] values = new ValueEval[getHeight()];

for (int i = 0; i < values.length; i++) {
values[i] = getRelativeValue(i, columnIndex);
}

return new CacheAreaEval(getFirstRow(), absColIndex, getLastRow(), absColIndex, values);
}

public String toString() {
CellReference crA = new CellReference(getFirstRow(), getFirstColumn());
CellReference crB = new CellReference(getLastRow(), getLastColumn());
return getClass().getName() + "[" +
crA.formatAsString() +
':' +
crB.formatAsString() +
"]";
}
}
3 changes: 3 additions & 0 deletions src/java/org/apache/poi/ss/formula/EvaluationCell.java
Expand Up @@ -18,6 +18,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more
package org.apache.poi.ss.formula;

import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.util.CellRangeAddress;

/**
* Abstracts a cell for the purpose of formula evaluation. This interface represents both formula
Expand Down Expand Up @@ -56,6 +57,8 @@ public interface EvaluationCell {
String getStringCellValue();
boolean getBooleanCellValue();
int getErrorCellValue();
CellRangeAddress getArrayFormulaRange();
boolean isPartOfArrayFormulaGroup();

/**
* Will return {@link CellType} in a future version of POI.
Expand Down
40 changes: 40 additions & 0 deletions src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java
Expand Up @@ -22,11 +22,15 @@ Licensed to the Apache Software Foundation (ASF) under one or more
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.ptg.Area3DPtg;
Expand Down Expand Up @@ -338,6 +342,42 @@ public ValueEval getArea3DEval(Area3DPxg aptg) {
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?
Expand Down
Expand Up @@ -52,6 +52,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more
import org.apache.poi.ss.formula.eval.UnaryPlusEval;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.function.FunctionMetadataRegistry;
import org.apache.poi.ss.formula.functions.ArrayFunction;
import org.apache.poi.ss.formula.functions.Function;
import org.apache.poi.ss.formula.functions.Indirect;

Expand Down Expand Up @@ -116,6 +117,12 @@ public static ValueEval evaluate(OperationPtg ptg, ValueEval[] args,
Function result = _instancesByPtgClass.get(ptg);

if (result != null) {
EvaluationSheet evalSheet = ec.getWorkbook().getSheet(ec.getSheetIndex());
EvaluationCell evalCell = evalSheet.getCell(ec.getRowIndex(), ec.getColumnIndex());

if (evalCell.isPartOfArrayFormulaGroup() && result instanceof ArrayFunction)
return ((ArrayFunction) result).evaluateArray(args, ec.getRowIndex(), ec.getColumnIndex());

return result.evaluate(args, ec.getRowIndex(), (short) ec.getColumnIndex());
}

Expand Down
40 changes: 32 additions & 8 deletions src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
Expand Up @@ -530,14 +530,8 @@ private NotImplementedException addExceptionInfo(NotImplementedException inner,
throw new IllegalStateException("evaluation stack not empty");
}

// "unwrap" result to just the value relevant for the source cell if needed
ValueEval result;
if (ec.isSingleValue()) {
result = dereferenceResult(value, ec.getRowIndex(), ec.getColumnIndex());
} else {
result = value;
}

ValueEval result = dereferenceResult(value, ec);

if (dbgEvaluationOutputIndent > 0) {
EVAL_LOG.log(POILogger.INFO, dbgIndentStr + "finshed eval of "
+ new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString()
Expand Down Expand Up @@ -573,6 +567,31 @@ private static int countTokensToBeSkipped(Ptg[] ptgs, int startIndex, int distIn
}
return index-startIndex;
}

/**
* Dereferences a single value from any AreaEval or RefEval evaluation
* result. If the supplied evaluationResult is just a plain value, it is
* returned as-is.
*
* @return a {@link NumberEval}, {@link StringEval}, {@link BoolEval}, or
* {@link ErrorEval}. Never <code>null</code>. {@link BlankEval} is
* converted to {@link NumberEval#ZERO}
*/
private static ValueEval dereferenceResult(ValueEval evaluationResult, OperationEvaluationContext ec) {

This comment has been minimized.

Copy link
@pjfanning

pjfanning Aug 30, 2017

I get 3 test failures in TestWorkbookEvaluator with NullPointerExceptions due to ec.getWorkbook() being null.

ValueEval value;

EvaluationSheet evalSheet = ec.getWorkbook().getSheet(ec.getSheetIndex());
EvaluationCell evalCell = evalSheet.getCell(ec.getRowIndex(), ec.getColumnIndex());

if (evalCell.isPartOfArrayFormulaGroup() && evaluationResult instanceof AreaEval) {
value = OperandResolver.getElementFromArray((AreaEval) evaluationResult, evalCell);
}
else {
value = dereferenceResult(evaluationResult, ec.getRowIndex(), ec.getColumnIndex());
}

return value;
}

/**
* Dereferences a single value from any AreaEval or RefEval evaluation
Expand Down Expand Up @@ -666,6 +685,11 @@ private ValueEval getEvalForPtg(Ptg ptg, OperationEvaluationContext ec) {
AreaPtg aptg = (AreaPtg) ptg;
return ec.getAreaEval(aptg.getFirstRow(), aptg.getFirstColumn(), aptg.getLastRow(), aptg.getLastColumn());
}

if (ptg instanceof ArrayPtg) {
ArrayPtg aptg = (ArrayPtg) ptg;
return ec.getAreaValueEval(0, 0, aptg.getRowCount() - 1, aptg.getColumnCount() - 1, aptg.getTokenArrayValues());
}

if (ptg instanceof UnknownPtg) {
// POI uses UnknownPtg when the encoded Ptg array seems to be corrupted.
Expand Down
5 changes: 5 additions & 0 deletions src/java/org/apache/poi/ss/formula/eval/FunctionEval.java
Expand Up @@ -145,6 +145,7 @@ private static Function[] produceFunctions() {

retval[82] = TextFunction.SEARCH;
// 83: TRANSPOSE
retval[83] = MatrixFunction.TRANSPOSE;

// 86: TYPE

Expand Down Expand Up @@ -182,6 +183,10 @@ private static Function[] produceFunctions() {
retval[FunctionID.INDIRECT] = null; // Indirect.evaluate has different signature

retval[162] = TextFunction.CLEAN;

retval[163] = MatrixFunction.MDETERM;
retval[164] = MatrixFunction.MINVERSE;
retval[165] = MatrixFunction.MMULT;

retval[167] = new IPMT();
retval[168] = new PPMT();
Expand Down
37 changes: 37 additions & 0 deletions src/java/org/apache/poi/ss/formula/eval/OperandResolver.java
Expand Up @@ -17,6 +17,9 @@ Licensed to the Apache Software Foundation (ASF) under one or more

package org.apache.poi.ss.formula.eval;

import org.apache.poi.ss.formula.EvaluationCell;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.formula.eval.ErrorEval;
import java.util.regex.Pattern;

/**
Expand Down Expand Up @@ -70,6 +73,40 @@ public static ValueEval getSingleValue(ValueEval arg, int srcCellRow, int srcCel
}
return result;
}

/**
* Retrieves a single value from an area evaluation utilizing the 2D indices of the cell
* within its own area reference to index the value in the area evaluation.
*
* @param ae area reference after evaluation
* @param cell the source cell of the formula that contains its 2D indices
* @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt> or <tt>BlankEval</tt>. or <tt>ErrorEval<tt>
* Never <code>null</code>.
*/

public static ValueEval getElementFromArray(AreaEval ae, EvaluationCell cell) {
CellRangeAddress range = cell.getArrayFormulaRange();
int relativeRowIndex = cell.getRowIndex() - range.getFirstRow();
int relativeColIndex = cell.getColumnIndex() - range.getFirstColumn();
//System.out.println("Row: " + relativeRowIndex + " Col: " + relativeColIndex);

if (ae.isColumn()) {
if (ae.isRow()) {
return ae.getRelativeValue(0, 0);
}
else if(relativeRowIndex < ae.getHeight()) {
return ae.getRelativeValue(relativeRowIndex, 0);
}
}
else if (!ae.isRow() && relativeRowIndex < ae.getHeight() && relativeColIndex < ae.getWidth()) {
return ae.getRelativeValue(relativeRowIndex, relativeColIndex);
}
else if (ae.isRow() && relativeColIndex < ae.getWidth()) {
return ae.getRelativeValue(0, relativeColIndex);
}

return ErrorEval.NA;
}

/**
* Implements (some perhaps not well known) Excel functionality to select a single cell from an
Expand Down

0 comments on commit 8a43c35

Please sign in to comment.