git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1808297 13f79535-47bb-0310-9956-ffa450edef68tags/REL_4_0_0_FINAL
<classpathentry exported="true" kind="lib" path="lib/commons-codec-1.10.jar"/> | <classpathentry exported="true" kind="lib" path="lib/commons-codec-1.10.jar"/> | ||||
<classpathentry exported="true" kind="lib" path="lib/commons-logging-1.2.jar"/> | <classpathentry exported="true" kind="lib" path="lib/commons-logging-1.2.jar"/> | ||||
<classpathentry exported="true" kind="lib" path="lib/commons-collections4-4.1.jar"/> | <classpathentry exported="true" kind="lib" path="lib/commons-collections4-4.1.jar"/> | ||||
<classpathentry kind="lib" path="lib/commons-math3-3.6.1.jar"/> | |||||
<classpathentry kind="output" path="bin"/> | <classpathentry kind="output" path="bin"/> | ||||
</classpath> | </classpath> |
// See https://github.com/melix/japicmp-gradle-plugin | // See https://github.com/melix/japicmp-gradle-plugin | ||||
apply plugin: 'me.champeau.gradle.japicmp' | apply plugin: 'me.champeau.gradle.japicmp' | ||||
version = '3.16-beta3' | |||||
version = '3.18-beta1' | |||||
ext { | ext { | ||||
japicmpversion = '3.15' | japicmpversion = '3.15' | ||||
} | } | ||||
compile 'commons-codec:commons-codec:1.10' | compile 'commons-codec:commons-codec:1.10' | ||||
compile 'commons-logging:commons-logging:1.2' | compile 'commons-logging:commons-logging:1.2' | ||||
compile 'org.apache.commons:commons-collections4:4.1' | compile 'org.apache.commons:commons-collections4:4.1' | ||||
compile 'org.apache.commons:commons-math3:3.6.1' | |||||
testCompile 'junit:junit:4.12' | testCompile 'junit:junit:4.12' | ||||
} | } | ||||
dependencies { | dependencies { | ||||
compile 'org.apache.xmlbeans:xmlbeans:2.6.0' | compile 'org.apache.xmlbeans:xmlbeans:2.6.0' | ||||
compile 'org.apache.commons:commons-collections4:4.1' | compile 'org.apache.commons:commons-collections4:4.1' | ||||
compile 'org.apache.commons:commons-math3:3.6.1' | |||||
compile 'org.apache.santuario:xmlsec:2.0.6' | compile 'org.apache.santuario:xmlsec:2.0.6' | ||||
compile 'org.bouncycastle:bcpkix-jdk15on:1.54' | compile 'org.bouncycastle:bcpkix-jdk15on:1.54' | ||||
compile 'com.github.virtuald:curvesapi:1.04' | compile 'com.github.virtuald:curvesapi:1.04' |
<property name="main.commons-collections4.jar" location="${main.lib}/commons-collections4-4.1.jar"/> | <property name="main.commons-collections4.jar" location="${main.lib}/commons-collections4-4.1.jar"/> | ||||
<property name="main.commons-collections4.url" | <property name="main.commons-collections4.url" | ||||
value="${repository.m2}/maven2/org/apache/commons/commons-collections4/4.1/commons-collections4-4.1.jar"/> | value="${repository.m2}/maven2/org/apache/commons/commons-collections4/4.1/commons-collections4-4.1.jar"/> | ||||
<property name="main.commons-math3.jar" location="${main.lib}/commons-math3-3.6.1.jar"/> | |||||
<property name="main.commons-math3.url" | |||||
value="${repository.m2}/maven2/org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1.jar"/> | |||||
<!-- xml signature libs --> | <!-- xml signature libs --> | ||||
<property name="dsig.xmlsec.jar" location="${compile.lib}/xmlsec-2.0.6.jar"/> | <property name="dsig.xmlsec.jar" location="${compile.lib}/xmlsec-2.0.6.jar"/> | ||||
<pathelement location="${main.commons-logging.jar}"/> | <pathelement location="${main.commons-logging.jar}"/> | ||||
<pathelement location="${main.commons-codec.jar}"/> | <pathelement location="${main.commons-codec.jar}"/> | ||||
<pathelement location="${main.commons-collections4.jar}"/> | <pathelement location="${main.commons-collections4.jar}"/> | ||||
<pathelement location="${main.commons-math3.jar}"/> | |||||
</path> | </path> | ||||
<!-- some libraries should only be required for compiling/running tests --> | <!-- some libraries should only be required for compiling/running tests --> | ||||
<available file="${dsig.xmlsec.jar}"/> | <available file="${dsig.xmlsec.jar}"/> | ||||
<available file="${dsig.sl4j-api.jar}"/> | <available file="${dsig.sl4j-api.jar}"/> | ||||
<available file="${main.commons-collections4.jar}"/> | <available file="${main.commons-collections4.jar}"/> | ||||
<available file="${main.commons-math3.jar}"/> | |||||
</and> | </and> | ||||
<isset property="disconnected"/> | <isset property="disconnected"/> | ||||
</or> | </or> | ||||
<downloadfile src="${asm.url}" dest="${asm.jar}"/> | <downloadfile src="${asm.url}" dest="${asm.jar}"/> | ||||
<downloadfile src="${jacoco.url}" dest="${jacoco.zip}"/> | <downloadfile src="${jacoco.url}" dest="${jacoco.zip}"/> | ||||
<downloadfile src="${main.commons-collections4.url}" dest="${main.commons-collections4.jar}"/> | <downloadfile src="${main.commons-collections4.url}" dest="${main.commons-collections4.jar}"/> | ||||
<downloadfile src="${main.commons-math3.url}" dest="${main.commons-math3.jar}"/> | |||||
<unzip src="${jacoco.zip}" dest="."> | <unzip src="${jacoco.zip}" dest="."> | ||||
<patternset> | <patternset> | ||||
<include name="lib/*.jar"/> | <include name="lib/*.jar"/> | ||||
<include name="commons-codec-*.jar"/> | <include name="commons-codec-*.jar"/> | ||||
<include name="commons-logging-*.jar"/> | <include name="commons-logging-*.jar"/> | ||||
<include name="commons-collections4-*.jar"/> | <include name="commons-collections4-*.jar"/> | ||||
<include name="commons-math3-*.jar"/> | |||||
<include name="junit-*.jar"/> | <include name="junit-*.jar"/> | ||||
<include name="log4j-*.jar"/> | <include name="log4j-*.jar"/> | ||||
</fileset> | </fileset> | ||||
<auxClasspath path="${ooxml.curvesapi.jar}" /> | <auxClasspath path="${ooxml.curvesapi.jar}" /> | ||||
<auxClasspath path="${ooxml.xmlbeans26.jar}" /> | <auxClasspath path="${ooxml.xmlbeans26.jar}" /> | ||||
<auxClasspath path="${main.commons-collections4.jar}" /> | <auxClasspath path="${main.commons-collections4.jar}" /> | ||||
<auxClasspath path="${main.commons-math3.jar}" /> | |||||
<auxClasspath path="${main.commons-codec.jar}" /> | <auxClasspath path="${main.commons-codec.jar}" /> | ||||
<auxClasspath path="${main.commons-logging.jar}" /> | <auxClasspath path="${main.commons-logging.jar}" /> | ||||
<auxClasspath path="${main.junit.jar}" /> | <auxClasspath path="${main.junit.jar}" /> |
<artifactId>commons-collections4</artifactId> | <artifactId>commons-collections4</artifactId> | ||||
<version>4.1</version> | <version>4.1</version> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>org.apache.commons</groupId> | |||||
<artifactId>commons-math3</artifactId> | |||||
<version>3.6.1</version> | |||||
</dependency> | |||||
<dependency> | <dependency> | ||||
<groupId>commons-codec</groupId> | <groupId>commons-codec</groupId> | ||||
<artifactId>commons-codec</artifactId> | <artifactId>commons-codec</artifactId> |
import org.apache.poi.ss.formula.EvaluationCell; | import org.apache.poi.ss.formula.EvaluationCell; | ||||
import org.apache.poi.ss.formula.EvaluationSheet; | import org.apache.poi.ss.formula.EvaluationSheet; | ||||
import org.apache.poi.ss.usermodel.CellType; | import org.apache.poi.ss.usermodel.CellType; | ||||
import org.apache.poi.ss.util.CellRangeAddress; | |||||
/** | /** | ||||
* HSSF wrapper for a cell under evaluation | * HSSF wrapper for a cell under evaluation | ||||
*/ | */ | ||||
public String getStringCellValue() { | public String getStringCellValue() { | ||||
return _cell.getRichStringCellValue().getString(); | 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. | * Will return {@link CellType} in a future version of POI. | ||||
* For forwards compatibility, do not hard-code cell type literals in your code. | * For forwards compatibility, do not hard-code cell type literals in your code. |
/* ==================================================================== | |||||
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() + | |||||
"]"; | |||||
} | |||||
} |
package org.apache.poi.ss.formula; | package org.apache.poi.ss.formula; | ||||
import org.apache.poi.ss.usermodel.CellType; | 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 | * Abstracts a cell for the purpose of formula evaluation. This interface represents both formula | ||||
String getStringCellValue(); | String getStringCellValue(); | ||||
boolean getBooleanCellValue(); | boolean getBooleanCellValue(); | ||||
int getErrorCellValue(); | int getErrorCellValue(); | ||||
CellRangeAddress getArrayFormulaRange(); | |||||
boolean isPartOfArrayFormulaGroup(); | |||||
/** | /** | ||||
* Will return {@link CellType} in a future version of POI. | * Will return {@link CellType} in a future version of POI. |
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalName; | import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalName; | ||||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet; | import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet; | ||||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheetRange; | 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.AreaEval; | ||||
import org.apache.poi.ss.formula.eval.BoolEval; | |||||
import org.apache.poi.ss.formula.eval.ErrorEval; | import org.apache.poi.ss.formula.eval.ErrorEval; | ||||
import org.apache.poi.ss.formula.eval.ExternalNameEval; | import org.apache.poi.ss.formula.eval.ExternalNameEval; | ||||
import org.apache.poi.ss.formula.eval.FunctionNameEval; | 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.RefEval; | ||||
import org.apache.poi.ss.formula.eval.StringEval; | |||||
import org.apache.poi.ss.formula.eval.ValueEval; | import org.apache.poi.ss.formula.eval.ValueEval; | ||||
import org.apache.poi.ss.formula.functions.FreeRefFunction; | import org.apache.poi.ss.formula.functions.FreeRefFunction; | ||||
import org.apache.poi.ss.formula.ptg.Area3DPtg; | import org.apache.poi.ss.formula.ptg.Area3DPtg; | ||||
return new LazyAreaEval(aptg.getFirstRow(), aptg.getFirstColumn(), | return new LazyAreaEval(aptg.getFirstRow(), aptg.getFirstColumn(), | ||||
aptg.getLastRow(), aptg.getLastColumn(), sre); | 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) { | public ValueEval getNameXEval(NameXPtg nameXPtg) { | ||||
// Is the name actually on our workbook? | // Is the name actually on our workbook? |
import org.apache.poi.ss.formula.eval.UnaryPlusEval; | import org.apache.poi.ss.formula.eval.UnaryPlusEval; | ||||
import org.apache.poi.ss.formula.eval.ValueEval; | import org.apache.poi.ss.formula.eval.ValueEval; | ||||
import org.apache.poi.ss.formula.function.FunctionMetadataRegistry; | 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.Function; | ||||
import org.apache.poi.ss.formula.functions.Indirect; | import org.apache.poi.ss.formula.functions.Indirect; | ||||
Function result = _instancesByPtgClass.get(ptg); | Function result = _instancesByPtgClass.get(ptg); | ||||
if (result != null) { | 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()); | return result.evaluate(args, ec.getRowIndex(), (short) ec.getColumnIndex()); | ||||
} | } | ||||
throw new IllegalStateException("evaluation stack not empty"); | throw new IllegalStateException("evaluation stack not empty"); | ||||
} | } | ||||
// "unwrap" result to just the value relevant for the source cell if needed | |||||
ValueEval result; | ValueEval result; | ||||
if (ec.isSingleValue()) { | if (ec.isSingleValue()) { | ||||
result = dereferenceResult(value, ec.getRowIndex(), ec.getColumnIndex()); | |||||
} else { | |||||
result = dereferenceResult(value, ec); | |||||
} | |||||
else { | |||||
result = value; | result = value; | ||||
} | } | ||||
if (dbgEvaluationOutputIndent > 0) { | if (dbgEvaluationOutputIndent > 0) { | ||||
EVAL_LOG.log(POILogger.INFO, dbgIndentStr + "finshed eval of " | EVAL_LOG.log(POILogger.INFO, dbgIndentStr + "finshed eval of " | ||||
+ new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString() | + new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString() | ||||
} | } | ||||
return index-startIndex; | 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) { | |||||
ValueEval value; | |||||
if (ec == null) { | |||||
throw new IllegalArgumentException("OperationEvaluationContext ec is null"); | |||||
} | |||||
if (ec.getWorkbook() == null) { | |||||
throw new IllegalArgumentException("OperationEvaluationContext ec.getWorkbook() is null"); | |||||
} | |||||
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 | * Dereferences a single value from any AreaEval or RefEval evaluation | ||||
AreaPtg aptg = (AreaPtg) ptg; | AreaPtg aptg = (AreaPtg) ptg; | ||||
return ec.getAreaEval(aptg.getFirstRow(), aptg.getFirstColumn(), aptg.getLastRow(), aptg.getLastColumn()); | 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) { | if (ptg instanceof UnknownPtg) { | ||||
// POI uses UnknownPtg when the encoded Ptg array seems to be corrupted. | // POI uses UnknownPtg when the encoded Ptg array seems to be corrupted. |
retval[82] = TextFunction.SEARCH; | retval[82] = TextFunction.SEARCH; | ||||
// 83: TRANSPOSE | // 83: TRANSPOSE | ||||
retval[83] = MatrixFunction.TRANSPOSE; | |||||
// 86: TYPE | // 86: TYPE | ||||
retval[FunctionID.INDIRECT] = null; // Indirect.evaluate has different signature | retval[FunctionID.INDIRECT] = null; // Indirect.evaluate has different signature | ||||
retval[162] = TextFunction.CLEAN; | retval[162] = TextFunction.CLEAN; | ||||
retval[163] = MatrixFunction.MDETERM; | |||||
retval[164] = MatrixFunction.MINVERSE; | |||||
retval[165] = MatrixFunction.MMULT; | |||||
retval[167] = new IPMT(); | retval[167] = new IPMT(); | ||||
retval[168] = new PPMT(); | retval[168] = new PPMT(); |
package org.apache.poi.ss.formula.eval; | 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; | import java.util.regex.Pattern; | ||||
/** | /** | ||||
} | } | ||||
return result; | 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 | * Implements (some perhaps not well known) Excel functionality to select a single cell from an |
package org.apache.poi.ss.formula.eval; | package org.apache.poi.ss.formula.eval; | ||||
import org.apache.poi.ss.formula.functions.ArrayFunction; | |||||
import org.apache.poi.ss.formula.functions.Fixed2ArgFunction; | import org.apache.poi.ss.formula.functions.Fixed2ArgFunction; | ||||
import org.apache.poi.ss.formula.functions.Function; | import org.apache.poi.ss.formula.functions.Function; | ||||
import org.apache.poi.ss.formula.functions.MatrixFunction.MutableValueCollector; | |||||
import org.apache.poi.ss.formula.functions.MatrixFunction.TwoArrayArg; | |||||
/** | /** | ||||
* @author Josh Micich | * @author Josh Micich | ||||
*/ | */ | ||||
public abstract class TwoOperandNumericOperation extends Fixed2ArgFunction { | |||||
public abstract class TwoOperandNumericOperation extends Fixed2ArgFunction implements ArrayFunction { | |||||
protected final double singleOperandEvaluate(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException { | protected final double singleOperandEvaluate(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException { | ||||
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); | ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); | ||||
return OperandResolver.coerceValueToDouble(ve); | return OperandResolver.coerceValueToDouble(ve); | ||||
} | } | ||||
public ValueEval evaluateArray(ValueEval args[], int srcRowIndex, int srcColumnIndex) { | |||||
if (args.length != 2) { | |||||
return ErrorEval.VALUE_INVALID; | |||||
} | |||||
return new ArrayEval().evaluate(srcRowIndex, srcColumnIndex, args[0], args[1]); | |||||
} | |||||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | ||||
double result; | double result; | ||||
try { | try { | ||||
protected abstract double evaluate(double d0, double d1) throws EvaluationException; | protected abstract double evaluate(double d0, double d1) throws EvaluationException; | ||||
private final class ArrayEval extends TwoArrayArg { | |||||
private final MutableValueCollector instance = new MutableValueCollector(false, true); | |||||
protected double[] collectValues(ValueEval arg) throws EvaluationException { | |||||
return instance.collectValues(arg); | |||||
} | |||||
protected double[][] evaluate(double[][] d1, double[][] d2) throws IllegalArgumentException, EvaluationException { | |||||
int width = (d1[0].length < d2[0].length) ? d1[0].length : d2[0].length; | |||||
int height = (d1.length < d2.length) ? d1.length : d2.length; | |||||
double result[][] = new double[height][width]; | |||||
for (int j = 0; j < height; j++) { | |||||
for (int i = 0; i < width; i++) { | |||||
result[j][i] = TwoOperandNumericOperation.this.evaluate(d1[j][i], d2[j][i]); | |||||
} | |||||
} | |||||
return result; | |||||
} | |||||
} | |||||
public static final Function AddEval = new TwoOperandNumericOperation() { | public static final Function AddEval = new TwoOperandNumericOperation() { | ||||
protected double evaluate(double d0, double d1) { | protected double evaluate(double d0, double d1) { | ||||
return d0+d1; | return d0+d1; |
import org.apache.poi.ss.formula.eval.ValueEval; | import org.apache.poi.ss.formula.eval.ValueEval; | ||||
import org.apache.poi.ss.usermodel.Cell; | import org.apache.poi.ss.usermodel.Cell; | ||||
import org.apache.poi.ss.usermodel.CellType; | import org.apache.poi.ss.usermodel.CellType; | ||||
import org.apache.poi.ss.util.CellRangeAddress; | |||||
/** | /** | ||||
* Represents a cell being used for forked evaluation that has had a value set different from the | * Represents a cell being used for forked evaluation that has had a value set different from the | ||||
public int getColumnIndex() { | public int getColumnIndex() { | ||||
return _masterCell.getColumnIndex(); | return _masterCell.getColumnIndex(); | ||||
} | } | ||||
@Override | |||||
public CellRangeAddress getArrayFormulaRange() { | |||||
return _masterCell.getArrayFormulaRange(); | |||||
} | |||||
@Override | |||||
public boolean isPartOfArrayFormulaGroup() { | |||||
return _masterCell.isPartOfArrayFormulaGroup(); | |||||
} | |||||
/** | /** | ||||
* Will return {@link CellType} in a future version of POI. | * Will return {@link CellType} in a future version of POI. | ||||
* For forwards compatibility, do not hard-code cell type literals in your code. | * For forwards compatibility, do not hard-code cell type literals in your code. |
/* ==================================================================== | |||||
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.functions; | |||||
import org.apache.poi.ss.formula.eval.BlankEval; | |||||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||||
import org.apache.poi.ss.formula.eval.MissingArgEval; | |||||
import org.apache.poi.ss.formula.eval.ValueEval; | |||||
/** | |||||
* @author Robert Hulbert | |||||
* Common Interface for any excel built-in function that has implemented array formula functionality. | |||||
*/ | |||||
public interface ArrayFunction { | |||||
/** | |||||
* @param args the evaluated function arguments. Empty values are represented with | |||||
* {@link BlankEval} or {@link MissingArgEval}, never <code>null</code>. | |||||
* @param srcRowIndex row index of the cell containing the formula under evaluation | |||||
* @param srcColumnIndex column index of the cell containing the formula under evaluation | |||||
* @return The evaluated result, possibly an {@link ErrorEval}, never <code>null</code>. | |||||
* <b>Note</b> - Excel uses the error code <i>#NUM!</i> instead of IEEE <i>NaN</i>, so when | |||||
* numeric functions evaluate to {@link Double#NaN} be sure to translate the result to {@link | |||||
* ErrorEval#NUM_ERROR}. | |||||
*/ | |||||
ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex); | |||||
} |
/* ==================================================================== | |||||
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.functions; | |||||
import org.apache.poi.ss.formula.CacheAreaEval; | |||||
import org.apache.poi.ss.formula.eval.AreaEval; | |||||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||||
import org.apache.poi.ss.formula.eval.EvaluationException; | |||||
import org.apache.poi.ss.formula.eval.NumberEval; | |||||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||||
import org.apache.poi.ss.formula.eval.ValueEval; | |||||
import org.apache.commons.math3.exception.DimensionMismatchException; | |||||
import org.apache.commons.math3.linear.Array2DRowRealMatrix; | |||||
import org.apache.commons.math3.linear.LUDecomposition; | |||||
import org.apache.commons.math3.linear.MatrixUtils; | |||||
/** | |||||
* @author Robert Hulbert | |||||
*/ | |||||
public abstract class MatrixFunction implements Function{ | |||||
public static final void checkValues(double[] results) throws EvaluationException { | |||||
for (int idx = 0; idx < results.length; idx++) { | |||||
if (Double.isNaN(results[idx]) || Double.isInfinite(results[idx])) { | |||||
throw new EvaluationException(ErrorEval.NUM_ERROR); | |||||
} | |||||
} | |||||
} | |||||
protected final double singleOperandEvaluate(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException { | |||||
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); | |||||
return OperandResolver.coerceValueToDouble(ve); | |||||
} | |||||
/* converts 1D array to 2D array for calculations */ | |||||
private static double[][] fillDoubleArray(double[] vector, int rows, int cols) throws EvaluationException { | |||||
int i = 0, j = 0; | |||||
if (rows < 1 || cols < 1 || vector.length < 1) { | |||||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||||
} | |||||
double[][] matrix = new double[rows][cols]; | |||||
for (int idx = 0; idx < vector.length; idx++) { | |||||
if (j < matrix.length) { | |||||
if (i == matrix[0].length) { | |||||
i = 0; | |||||
j++; | |||||
} | |||||
matrix[j][i++] = vector[idx]; | |||||
} | |||||
} | |||||
return matrix; | |||||
} | |||||
/* retrieves 1D array from 2D array after calculations */ | |||||
private static double[] extractDoubleArray(double[][] matrix) throws EvaluationException { | |||||
int idx = 0; | |||||
if (matrix == null || matrix.length < 1 || matrix[0].length < 1) { | |||||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||||
} | |||||
double[] vector = new double[matrix.length * matrix[0].length]; | |||||
for (int j = 0; j < matrix.length; j++) { | |||||
for (int i = 0; i < matrix[0].length; i++) { | |||||
vector[idx++] = matrix[j][i]; | |||||
} | |||||
} | |||||
return vector; | |||||
} | |||||
public static abstract class OneArrayArg extends Fixed1ArgFunction { | |||||
protected OneArrayArg() { | |||||
//no fields to initialize | |||||
} | |||||
@Override | |||||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||||
if (arg0 instanceof AreaEval) { | |||||
double result[] = null, resultArray[][]; | |||||
int width = 1, height = 1; | |||||
try { | |||||
double values[] = collectValues(arg0); | |||||
double array[][] = fillDoubleArray(values,((AreaEval) arg0).getHeight(),((AreaEval) arg0).getWidth()); | |||||
resultArray = evaluate(array); | |||||
width = resultArray[0].length; | |||||
height = resultArray.length; | |||||
result = extractDoubleArray(resultArray); | |||||
checkValues(result); | |||||
} | |||||
catch(EvaluationException e){ | |||||
return e.getErrorEval(); | |||||
} | |||||
ValueEval vals[] = new ValueEval[result.length]; | |||||
for (int idx = 0; idx < result.length; idx++) { | |||||
vals[idx] = new NumberEval(result[idx]); | |||||
} | |||||
if (result.length == 1) { | |||||
return vals[0]; | |||||
} | |||||
else { | |||||
/* find a better solution */ | |||||
return new CacheAreaEval(((AreaEval) arg0).getFirstRow(), ((AreaEval) arg0).getFirstColumn(), | |||||
((AreaEval) arg0).getFirstRow() + height - 1, | |||||
((AreaEval) arg0).getFirstColumn() + width - 1, vals); | |||||
} | |||||
} | |||||
else { | |||||
double result[][] = null; | |||||
try { | |||||
double value = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||||
double temp[][] = {{value}}; | |||||
result = evaluate(temp); | |||||
NumericFunction.checkValue(result[0][0]); | |||||
} | |||||
catch (EvaluationException e) { | |||||
return e.getErrorEval(); | |||||
} | |||||
return new NumberEval(result[0][0]); | |||||
} | |||||
} | |||||
protected abstract double[][] evaluate(double[][] d1) throws EvaluationException; | |||||
protected abstract double[] collectValues(ValueEval arg) throws EvaluationException; | |||||
} | |||||
public static abstract class TwoArrayArg extends Fixed2ArgFunction { | |||||
protected TwoArrayArg() { | |||||
//no fields to initialize | |||||
} | |||||
@Override | |||||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||||
double result[]; | |||||
int width = 1, height = 1; | |||||
try { | |||||
double array0[][], array1[][], resultArray[][]; | |||||
if (arg0 instanceof AreaEval) { | |||||
try { | |||||
double values[] = collectValues(arg0); | |||||
array0 = fillDoubleArray(values, ((AreaEval) arg0).getHeight(), ((AreaEval) arg0).getWidth()); | |||||
} | |||||
catch(EvaluationException e) { | |||||
return e.getErrorEval(); | |||||
} | |||||
} | |||||
else { | |||||
try { | |||||
double value = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||||
array0 = new double[][] {{value}}; | |||||
} | |||||
catch (EvaluationException e) { | |||||
return e.getErrorEval(); | |||||
} | |||||
} | |||||
if (arg1 instanceof AreaEval) { | |||||
try { | |||||
double values[] = collectValues(arg1); | |||||
array1 = fillDoubleArray(values, ((AreaEval) arg1).getHeight(),((AreaEval) arg1).getWidth()); | |||||
} | |||||
catch (EvaluationException e) { | |||||
return e.getErrorEval(); | |||||
} | |||||
} | |||||
else { | |||||
try { | |||||
double value = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||||
array1 = new double[][] {{value}}; | |||||
} | |||||
catch (EvaluationException e) { | |||||
return e.getErrorEval(); | |||||
} | |||||
} | |||||
resultArray = evaluate(array0, array1); | |||||
width = resultArray[0].length; | |||||
height = resultArray.length; | |||||
result = extractDoubleArray(resultArray); | |||||
checkValues(result); | |||||
} | |||||
catch (EvaluationException e) { | |||||
return e.getErrorEval(); | |||||
} | |||||
catch (IllegalArgumentException e) { | |||||
return ErrorEval.VALUE_INVALID; | |||||
} | |||||
ValueEval vals[] = new ValueEval[result.length]; | |||||
for (int idx = 0; idx < result.length; idx++) { | |||||
vals[idx] = new NumberEval(result[idx]); | |||||
} | |||||
if (result.length == 1) | |||||
return vals[0]; | |||||
else { | |||||
return new CacheAreaEval(((AreaEval) arg0).getFirstRow(), ((AreaEval) arg0).getFirstColumn(), | |||||
((AreaEval) arg0).getFirstRow() + height - 1, | |||||
((AreaEval) arg0).getFirstColumn() + width - 1, vals); | |||||
} | |||||
} | |||||
protected abstract double[][] evaluate(double[][] d1, double[][] d2) throws EvaluationException; | |||||
protected abstract double[] collectValues(ValueEval arg) throws EvaluationException; | |||||
} | |||||
public static final class MutableValueCollector extends MultiOperandNumericFunction { | |||||
public MutableValueCollector(boolean isReferenceBoolCounted, boolean isBlankCounted) { | |||||
super(isReferenceBoolCounted, isBlankCounted); | |||||
} | |||||
public double[] collectValues(ValueEval...operands) throws EvaluationException { | |||||
return getNumberArray(operands); | |||||
} | |||||
protected double evaluate(double[] values) { | |||||
throw new IllegalStateException("should not be called"); | |||||
} | |||||
} | |||||
public static final Function MINVERSE = new OneArrayArg() { | |||||
private final MutableValueCollector instance = new MutableValueCollector(false, false); | |||||
protected double[] collectValues(ValueEval arg) throws EvaluationException { | |||||
double[] values = instance.collectValues(arg); | |||||
/* handle case where MDETERM is operating on an array that that is not completely filled*/ | |||||
if (arg instanceof AreaEval && values.length == 1) | |||||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||||
return values; | |||||
} | |||||
protected double[][] evaluate(double[][] d1) throws EvaluationException { | |||||
if (d1.length != d1[0].length) { | |||||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||||
} | |||||
Array2DRowRealMatrix temp = new Array2DRowRealMatrix(d1); | |||||
return MatrixUtils.inverse(temp).getData(); | |||||
} | |||||
}; | |||||
public static final Function TRANSPOSE = new OneArrayArg() { | |||||
private final MutableValueCollector instance = new MutableValueCollector(false, true); | |||||
protected double[] collectValues(ValueEval arg) throws EvaluationException { | |||||
return instance.collectValues(arg); | |||||
} | |||||
protected double[][] evaluate(double[][] d1) throws EvaluationException { | |||||
Array2DRowRealMatrix temp = new Array2DRowRealMatrix(d1); | |||||
return temp.transpose().getData(); | |||||
} | |||||
}; | |||||
public static final Function MDETERM = new OneArrayArg() { | |||||
private final MutableValueCollector instance = new MutableValueCollector(false, false); | |||||
protected double[] collectValues(ValueEval arg) throws EvaluationException { | |||||
double[] values = instance.collectValues(arg); | |||||
/* handle case where MDETERM is operating on an array that that is not completely filled*/ | |||||
if (arg instanceof AreaEval && values.length == 1) | |||||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||||
return instance.collectValues(arg); | |||||
} | |||||
protected double[][] evaluate(double[][] d1) throws EvaluationException { | |||||
if (d1.length != d1[0].length) { | |||||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||||
} | |||||
double result[][] = new double[1][1]; | |||||
Array2DRowRealMatrix temp = new Array2DRowRealMatrix(d1); | |||||
result[0][0] = (new LUDecomposition(temp)).getDeterminant(); | |||||
return result; | |||||
} | |||||
}; | |||||
public static final Function MMULT = new TwoArrayArg() { | |||||
private final MutableValueCollector instance = new MutableValueCollector(false, false); | |||||
protected double[] collectValues(ValueEval arg) throws EvaluationException { | |||||
double values[] = instance.collectValues(arg); | |||||
/* handle case where MMULT is operating on an array that is not completely filled*/ | |||||
if (arg instanceof AreaEval && values.length == 1) | |||||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||||
return values; | |||||
} | |||||
protected double[][] evaluate(double[][] d1, double[][] d2) throws EvaluationException{ | |||||
Array2DRowRealMatrix first = new Array2DRowRealMatrix(d1); | |||||
Array2DRowRealMatrix second = new Array2DRowRealMatrix(d2); | |||||
try { | |||||
MatrixUtils.checkMultiplicationCompatible(first, second); | |||||
} | |||||
catch (DimensionMismatchException e) { | |||||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||||
} | |||||
return first.multiply(second).getData(); | |||||
} | |||||
}; | |||||
} |
import org.apache.poi.ss.formula.EvaluationCell; | import org.apache.poi.ss.formula.EvaluationCell; | ||||
import org.apache.poi.ss.formula.EvaluationSheet; | import org.apache.poi.ss.formula.EvaluationSheet; | ||||
import org.apache.poi.ss.usermodel.CellType; | import org.apache.poi.ss.usermodel.CellType; | ||||
import org.apache.poi.ss.util.CellRangeAddress; | |||||
import org.apache.poi.util.Internal; | import org.apache.poi.util.Internal; | ||||
/** | /** | ||||
public String getStringCellValue() { | public String getStringCellValue() { | ||||
return _cell.getRichStringCellValue().getString(); | 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. | * Will return {@link CellType} in a future version of POI. | ||||
* For forwards compatibility, do not hard-code cell type literals in your code. | * For forwards compatibility, do not hard-code cell type literals in your code. |
} | } | ||||
CTCellFormula f = _cell.getF(); | CTCellFormula f = _cell.getF(); | ||||
if (isPartOfArrayFormulaGroup() && f == null) { | |||||
XSSFCell cell = getSheet().getFirstCellInArrayFormula(this); | |||||
return cell.getCellFormula(fpb); | |||||
if (isPartOfArrayFormulaGroup()) { | |||||
/* In an excel generated array formula, the formula property might be set, but the string is empty in slave cells */ | |||||
if (f == null || f.getStringValue().isEmpty()) { | |||||
XSSFCell cell = getSheet().getFirstCellInArrayFormula(this); | |||||
return cell.getCellFormula(fpb); | |||||
} | |||||
} | } | ||||
if (f.getT() == STCellFormulaType.SHARED) { | if (f.getT() == STCellFormulaType.SHARED) { | ||||
return convertSharedFormula((int)f.getSi(), fpb == null ? XSSFEvaluationWorkbook.create(getSheet().getWorkbook()) : fpb); | return convertSharedFormula((int)f.getSi(), fpb == null ? XSSFEvaluationWorkbook.create(getSheet().getWorkbook()) : fpb); |
import org.apache.poi.ss.formula.EvaluationCell; | import org.apache.poi.ss.formula.EvaluationCell; | ||||
import org.apache.poi.ss.formula.EvaluationSheet; | import org.apache.poi.ss.formula.EvaluationSheet; | ||||
import org.apache.poi.ss.usermodel.CellType; | import org.apache.poi.ss.usermodel.CellType; | ||||
import org.apache.poi.ss.util.CellRangeAddress; | |||||
import org.apache.poi.util.Internal; | import org.apache.poi.util.Internal; | ||||
/** | /** | ||||
public String getStringCellValue() { | public String getStringCellValue() { | ||||
return _cell.getRichStringCellValue().getString(); | 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. | * Will return {@link CellType} in a future version of POI. | ||||
* For forwards compatibility, do not hard-code cell type literals in your code. | * For forwards compatibility, do not hard-code cell type literals in your code. |
package org.apache.poi.xssf.usermodel; | |||||
import static org.junit.Assert.assertEquals; | |||||
import static org.junit.Assert.assertNotNull; | |||||
import static org.junit.Assert.fail; | |||||
import java.util.ArrayList; | |||||
import java.util.Collection; | |||||
import java.util.List; | |||||
import java.util.Locale; | |||||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||||
import org.apache.poi.ss.formula.functions.TestMathX; | |||||
import org.apache.poi.ss.usermodel.Cell; | |||||
import org.apache.poi.ss.usermodel.CellType; | |||||
import org.apache.poi.ss.usermodel.CellValue; | |||||
import org.apache.poi.ss.usermodel.FormulaEvaluator; | |||||
import org.apache.poi.ss.usermodel.Row; | |||||
import org.apache.poi.ss.usermodel.Sheet; | |||||
import org.apache.poi.util.LocaleUtil; | |||||
import org.apache.poi.xssf.XSSFTestDataSamples; | |||||
import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator; | |||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook; | |||||
import org.junit.AfterClass; | |||||
import org.junit.Test; | |||||
import org.junit.runner.RunWith; | |||||
import org.junit.runners.Parameterized; | |||||
import org.junit.runners.Parameterized.Parameter; | |||||
import org.junit.runners.Parameterized.Parameters; | |||||
import junit.framework.AssertionFailedError; | |||||
@RunWith(Parameterized.class) | |||||
public final class TestMatrixFormulasFromXMLSpreadsheet { | |||||
private static XSSFWorkbook workbook; | |||||
private static Sheet sheet; | |||||
private static FormulaEvaluator evaluator; | |||||
private static Locale userLocale; | |||||
/* | |||||
* Unlike TestFormulaFromSpreadsheet which this class is modified from, there is no | |||||
* differentiation between operators and functions, if more functionality is implemented with | |||||
* array formulas then it might be worth it to separate operators from functions | |||||
* | |||||
* Also, output matrices are statically 3x3, if larger matrices wanted to be tested | |||||
* then adding matrix size parameter would be useful and parsing would be based off that. | |||||
*/ | |||||
private static interface Navigator { | |||||
/** | |||||
* Name of the test spreadsheet (found in the standard test data folder) | |||||
*/ | |||||
String FILENAME = "MatrixFormulaEvalTestData.xlsx"; | |||||
/** | |||||
* Row (zero-based) in the spreadsheet where operations start | |||||
*/ | |||||
int START_OPERATORS_ROW_INDEX = 1; | |||||
/** | |||||
* Column (zero-based) in the spreadsheet where operations start | |||||
*/ | |||||
int START_OPERATORS_COL_INDEX = 0; | |||||
/** | |||||
* Column (zero-based) in the spreadsheet where evaluations start | |||||
*/ | |||||
int START_RESULT_COL_INDEX = 7; | |||||
/** | |||||
* Column separation in the spreadsheet between evaluations and expected results | |||||
*/ | |||||
int COL_OFF_EXPECTED_RESULT = 3; | |||||
/** | |||||
* Row separation in the spreadsheet between operations | |||||
*/ | |||||
int ROW_OFF_NEXT_OP = 4; | |||||
/** | |||||
* Used to indicate when there are no more operations left | |||||
*/ | |||||
String END_OF_TESTS = "<END>"; | |||||
} | |||||
/* Parameters for test case */ | |||||
@Parameter(0) | |||||
public String targetFunctionName; | |||||
@Parameter(1) | |||||
public int formulasRowIdx; | |||||
@AfterClass | |||||
public static void closeResource() throws Exception { | |||||
LocaleUtil.setUserLocale(userLocale); | |||||
workbook.close(); | |||||
} | |||||
/* generating parameter instances */ | |||||
@Parameters(name="{0}") | |||||
public static Collection<Object[]> data() throws Exception { | |||||
// Function "Text" uses custom-formats which are locale specific | |||||
// can't set the locale on a per-testrun execution, as some settings have been | |||||
// already set, when we would try to change the locale by then | |||||
userLocale = LocaleUtil.getUserLocale(); | |||||
LocaleUtil.setUserLocale(Locale.ROOT); | |||||
workbook = XSSFTestDataSamples.openSampleWorkbook(Navigator.FILENAME); | |||||
sheet = workbook.getSheetAt(0); | |||||
evaluator = new XSSFFormulaEvaluator(workbook); | |||||
List<Object[]> data = new ArrayList<Object[]>(); | |||||
processFunctionGroup(data, Navigator.START_OPERATORS_ROW_INDEX, null); | |||||
return data; | |||||
} | |||||
/** | |||||
* @param startRowIndex row index in the spreadsheet where the first function/operator is found | |||||
* @param testFocusFunctionName name of a single function/operator to test alone. | |||||
* Typically pass <code>null</code> to test all functions | |||||
*/ | |||||
private static void processFunctionGroup(List<Object[]> data, int startRowIndex, String testFocusFunctionName) { | |||||
for (int rowIndex = startRowIndex; true; rowIndex += Navigator.ROW_OFF_NEXT_OP) { | |||||
Row r = sheet.getRow(rowIndex); | |||||
String targetFunctionName = getTargetFunctionName(r); | |||||
assertNotNull("Test spreadsheet cell empty on row (" | |||||
+ (rowIndex) + "). Expected function name or '" | |||||
+ Navigator.END_OF_TESTS + "'", targetFunctionName); | |||||
if(targetFunctionName.equals(Navigator.END_OF_TESTS)) { | |||||
// found end of functions list | |||||
break; | |||||
} | |||||
if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) { | |||||
data.add(new Object[]{targetFunctionName, rowIndex}); | |||||
} | |||||
} | |||||
} | |||||
@Test | |||||
public void processFunctionRow() { | |||||
int endColNum = Navigator.START_RESULT_COL_INDEX + Navigator.COL_OFF_EXPECTED_RESULT; | |||||
for (int rowNum = formulasRowIdx; rowNum < formulasRowIdx + Navigator.ROW_OFF_NEXT_OP - 1; rowNum++) { | |||||
for (int colNum = Navigator.START_RESULT_COL_INDEX; colNum < endColNum; colNum++) { | |||||
Row r = sheet.getRow(rowNum); | |||||
/* mainly to escape row failures on MDETERM which only returns a scalar */ | |||||
if (r == null) { | |||||
continue; | |||||
} | |||||
Cell c = sheet.getRow(rowNum).getCell(colNum); | |||||
if (c == null || c.getCellTypeEnum() != CellType.FORMULA) { | |||||
continue; | |||||
} | |||||
CellValue actValue = evaluator.evaluate(c); | |||||
Cell expValue = sheet.getRow(rowNum).getCell(colNum + Navigator.COL_OFF_EXPECTED_RESULT); | |||||
String msg = String.format(Locale.ROOT, "Function '%s': Formula: %s @ %d:%d" | |||||
, targetFunctionName, c.getCellFormula(), rowNum, colNum); | |||||
assertNotNull(msg + " - Bad setup data expected value is null", expValue); | |||||
assertNotNull(msg + " - actual value was null", actValue); | |||||
final CellType cellType = expValue.getCellTypeEnum(); | |||||
switch (cellType) { | |||||
case BLANK: | |||||
assertEquals(msg, CellType.BLANK, actValue.getCellTypeEnum()); | |||||
break; | |||||
case BOOLEAN: | |||||
assertEquals(msg, CellType.BOOLEAN, actValue.getCellTypeEnum()); | |||||
assertEquals(msg, expValue.getBooleanCellValue(), actValue.getBooleanValue()); | |||||
break; | |||||
case ERROR: | |||||
assertEquals(msg, CellType.ERROR, actValue.getCellTypeEnum()); | |||||
assertEquals(msg, ErrorEval.getText(expValue.getErrorCellValue()), ErrorEval.getText(actValue.getErrorValue())); | |||||
break; | |||||
case FORMULA: // will never be used, since we will call method after formula evaluation | |||||
fail("Cannot expect formula as result of formula evaluation: " + msg); | |||||
case NUMERIC: | |||||
assertEquals(msg, CellType.NUMERIC, actValue.getCellTypeEnum()); | |||||
TestMathX.assertEquals(msg, expValue.getNumericCellValue(), actValue.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR); | |||||
break; | |||||
case STRING: | |||||
assertEquals(msg, CellType.STRING, actValue.getCellTypeEnum()); | |||||
assertEquals(msg, expValue.getRichStringCellValue().getString(), actValue.getStringValue()); | |||||
break; | |||||
default: | |||||
fail("Unexpected cell type: " + cellType); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* @return <code>null</code> if cell is missing, empty or blank | |||||
*/ | |||||
private static String getTargetFunctionName(Row r) { | |||||
if(r == null) { | |||||
System.err.println("Warning - given null row, can't figure out function name"); | |||||
return null; | |||||
} | |||||
Cell cell = r.getCell(Navigator.START_OPERATORS_COL_INDEX); | |||||
System.err.println(String.valueOf(Navigator.START_OPERATORS_COL_INDEX)); | |||||
if(cell == null) { | |||||
System.err.println("Warning - Row " + r.getRowNum() + " has no cell " + Navigator.START_OPERATORS_COL_INDEX + ", can't figure out function name"); | |||||
return null; | |||||
} | |||||
if(cell.getCellTypeEnum() == CellType.BLANK) { | |||||
return null; | |||||
} | |||||
if(cell.getCellTypeEnum() == CellType.STRING) { | |||||
return cell.getRichStringCellValue().getString(); | |||||
} | |||||
throw new AssertionFailedError("Bad cell type for 'function name' column: (" | |||||
+ cell.getCellTypeEnum() + ") row (" + (r.getRowNum() +1) + ")"); | |||||
} | |||||
} |
package org.apache.poi.hssf.usermodel; | |||||
import static org.junit.Assert.assertEquals; | |||||
import static org.junit.Assert.assertNotNull; | |||||
import static org.junit.Assert.fail; | |||||
import java.util.ArrayList; | |||||
import java.util.Collection; | |||||
import java.util.List; | |||||
import java.util.Locale; | |||||
import org.apache.poi.hssf.HSSFTestDataSamples; | |||||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||||
import org.apache.poi.ss.formula.functions.TestMathX; | |||||
import org.apache.poi.ss.usermodel.Cell; | |||||
import org.apache.poi.ss.usermodel.CellType; | |||||
import org.apache.poi.ss.usermodel.CellValue; | |||||
import org.apache.poi.ss.usermodel.FormulaEvaluator; | |||||
import org.apache.poi.ss.usermodel.Row; | |||||
import org.apache.poi.ss.usermodel.Sheet; | |||||
import org.apache.poi.util.LocaleUtil; | |||||
import org.junit.AfterClass; | |||||
import org.junit.Test; | |||||
import org.junit.runner.RunWith; | |||||
import org.junit.runners.Parameterized; | |||||
import org.junit.runners.Parameterized.Parameter; | |||||
import org.junit.runners.Parameterized.Parameters; | |||||
import junit.framework.AssertionFailedError; | |||||
@RunWith(Parameterized.class) | |||||
public final class TestMatrixFormulasFromBinarySpreadsheet { | |||||
private static HSSFWorkbook workbook; | |||||
private static Sheet sheet; | |||||
private static FormulaEvaluator evaluator; | |||||
private static Locale userLocale; | |||||
/* | |||||
* Unlike TestFormulaFromSpreadsheet which this class is modified from, there is no | |||||
* differentiation between operators and functions, if more functionality is implemented with | |||||
* array formulas then it might be worth it to separate operators from functions | |||||
* | |||||
* Also, output matrices are statically 3x3, if larger matrices wanted to be tested | |||||
* then adding matrix size parameter would be useful and parsing would be based off that. | |||||
*/ | |||||
private static interface Navigator { | |||||
/** | |||||
* Name of the test spreadsheet (found in the standard test data folder) | |||||
*/ | |||||
String FILENAME = "MatrixFormulaEvalTestData.xls"; | |||||
/** | |||||
* Row (zero-based) in the spreadsheet where operations start | |||||
*/ | |||||
int START_OPERATORS_ROW_INDEX = 1; | |||||
/** | |||||
* Column (zero-based) in the spreadsheet where operations start | |||||
*/ | |||||
int START_OPERATORS_COL_INDEX = 0; | |||||
/** | |||||
* Column (zero-based) in the spreadsheet where evaluations start | |||||
*/ | |||||
int START_RESULT_COL_INDEX = 7; | |||||
/** | |||||
* Column separation in the spreadsheet between evaluations and expected results | |||||
*/ | |||||
int COL_OFF_EXPECTED_RESULT = 3; | |||||
/** | |||||
* Row separation in the spreadsheet between operations | |||||
*/ | |||||
int ROW_OFF_NEXT_OP = 4; | |||||
/** | |||||
* Used to indicate when there are no more operations left | |||||
*/ | |||||
String END_OF_TESTS = "<END>"; | |||||
} | |||||
/* Parameters for test case */ | |||||
@Parameter(0) | |||||
public String targetFunctionName; | |||||
@Parameter(1) | |||||
public int formulasRowIdx; | |||||
@AfterClass | |||||
public static void closeResource() throws Exception { | |||||
LocaleUtil.setUserLocale(userLocale); | |||||
workbook.close(); | |||||
} | |||||
/* generating parameter instances */ | |||||
@Parameters(name="{0}") | |||||
public static Collection<Object[]> data() throws Exception { | |||||
// Function "Text" uses custom-formats which are locale specific | |||||
// can't set the locale on a per-testrun execution, as some settings have been | |||||
// already set, when we would try to change the locale by then | |||||
userLocale = LocaleUtil.getUserLocale(); | |||||
LocaleUtil.setUserLocale(Locale.ROOT); | |||||
workbook = HSSFTestDataSamples.openSampleWorkbook(Navigator.FILENAME); | |||||
sheet = workbook.getSheetAt(0); | |||||
evaluator = new HSSFFormulaEvaluator(workbook); | |||||
List<Object[]> data = new ArrayList<Object[]>(); | |||||
processFunctionGroup(data, Navigator.START_OPERATORS_ROW_INDEX, null); | |||||
return data; | |||||
} | |||||
/** | |||||
* @param startRowIndex row index in the spreadsheet where the first function/operator is found | |||||
* @param testFocusFunctionName name of a single function/operator to test alone. | |||||
* Typically pass <code>null</code> to test all functions | |||||
*/ | |||||
private static void processFunctionGroup(List<Object[]> data, int startRowIndex, String testFocusFunctionName) { | |||||
for (int rowIndex = startRowIndex; true; rowIndex += Navigator.ROW_OFF_NEXT_OP) { | |||||
Row r = sheet.getRow(rowIndex); | |||||
String targetFunctionName = getTargetFunctionName(r); | |||||
assertNotNull("Test spreadsheet cell empty on row (" | |||||
+ (rowIndex) + "). Expected function name or '" | |||||
+ Navigator.END_OF_TESTS + "'", targetFunctionName); | |||||
if(targetFunctionName.equals(Navigator.END_OF_TESTS)) { | |||||
// found end of functions list | |||||
break; | |||||
} | |||||
if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) { | |||||
data.add(new Object[]{targetFunctionName, rowIndex}); | |||||
} | |||||
} | |||||
} | |||||
@Test | |||||
public void processFunctionRow() { | |||||
int endColNum = Navigator.START_RESULT_COL_INDEX + Navigator.COL_OFF_EXPECTED_RESULT; | |||||
for (int rowNum = formulasRowIdx; rowNum < formulasRowIdx + Navigator.ROW_OFF_NEXT_OP - 1; rowNum++) { | |||||
for (int colNum = Navigator.START_RESULT_COL_INDEX; colNum < endColNum; colNum++) { | |||||
Row r = sheet.getRow(rowNum); | |||||
/* mainly to escape row failures on MDETERM which only returns a scalar */ | |||||
if (r == null) { | |||||
continue; | |||||
} | |||||
Cell c = sheet.getRow(rowNum).getCell(colNum); | |||||
if (c == null || c.getCellTypeEnum() != CellType.FORMULA) { | |||||
continue; | |||||
} | |||||
CellValue actValue = evaluator.evaluate(c); | |||||
Cell expValue = sheet.getRow(rowNum).getCell(colNum + Navigator.COL_OFF_EXPECTED_RESULT); | |||||
String msg = String.format(Locale.ROOT, "Function '%s': Formula: %s @ %d:%d" | |||||
, targetFunctionName, c.getCellFormula(), rowNum, colNum); | |||||
assertNotNull(msg + " - Bad setup data expected value is null", expValue); | |||||
assertNotNull(msg + " - actual value was null", actValue); | |||||
final CellType cellType = expValue.getCellTypeEnum(); | |||||
switch (cellType) { | |||||
case BLANK: | |||||
assertEquals(msg, CellType.BLANK, actValue.getCellTypeEnum()); | |||||
break; | |||||
case BOOLEAN: | |||||
assertEquals(msg, CellType.BOOLEAN, actValue.getCellTypeEnum()); | |||||
assertEquals(msg, expValue.getBooleanCellValue(), actValue.getBooleanValue()); | |||||
break; | |||||
case ERROR: | |||||
assertEquals(msg, CellType.ERROR, actValue.getCellTypeEnum()); | |||||
assertEquals(msg, ErrorEval.getText(expValue.getErrorCellValue()), ErrorEval.getText(actValue.getErrorValue())); | |||||
break; | |||||
case FORMULA: // will never be used, since we will call method after formula evaluation | |||||
fail("Cannot expect formula as result of formula evaluation: " + msg); | |||||
case NUMERIC: | |||||
assertEquals(msg, CellType.NUMERIC, actValue.getCellTypeEnum()); | |||||
TestMathX.assertEquals(msg, expValue.getNumericCellValue(), actValue.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR); | |||||
break; | |||||
case STRING: | |||||
assertEquals(msg, CellType.STRING, actValue.getCellTypeEnum()); | |||||
assertEquals(msg, expValue.getRichStringCellValue().getString(), actValue.getStringValue()); | |||||
break; | |||||
default: | |||||
fail("Unexpected cell type: " + cellType); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* @return <code>null</code> if cell is missing, empty or blank | |||||
*/ | |||||
private static String getTargetFunctionName(Row r) { | |||||
if(r == null) { | |||||
System.err.println("Warning - given null row, can't figure out function name"); | |||||
return null; | |||||
} | |||||
Cell cell = r.getCell(Navigator.START_OPERATORS_COL_INDEX); | |||||
System.err.println(String.valueOf(Navigator.START_OPERATORS_COL_INDEX)); | |||||
if(cell == null) { | |||||
System.err.println("Warning - Row " + r.getRowNum() + " has no cell " + Navigator.START_OPERATORS_COL_INDEX + ", can't figure out function name"); | |||||
return null; | |||||
} | |||||
if(cell.getCellTypeEnum() == CellType.BLANK) { | |||||
return null; | |||||
} | |||||
if(cell.getCellTypeEnum() == CellType.STRING) { | |||||
return cell.getRichStringCellValue().getString(); | |||||
} | |||||
throw new AssertionFailedError("Bad cell type for 'function name' column: (" | |||||
+ cell.getCellTypeEnum() + ") row (" + (r.getRowNum() +1) + ")"); | |||||
} | |||||
} |
import org.apache.poi.hssf.HSSFTestDataSamples; | import org.apache.poi.hssf.HSSFTestDataSamples; | ||||
import org.apache.poi.hssf.usermodel.HSSFCell; | import org.apache.poi.hssf.usermodel.HSSFCell; | ||||
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; | |||||
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; | import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; | ||||
import org.apache.poi.hssf.usermodel.HSSFRow; | import org.apache.poi.hssf.usermodel.HSSFRow; | ||||
import org.apache.poi.hssf.usermodel.HSSFSheet; | import org.apache.poi.hssf.usermodel.HSSFSheet; | ||||
private static final double EPSILON = 0.0000001; | private static final double EPSILON = 0.0000001; | ||||
private static ValueEval evaluateFormula(Ptg[] ptgs) { | private static ValueEval evaluateFormula(Ptg[] ptgs) { | ||||
OperationEvaluationContext ec = new OperationEvaluationContext(null, null, 0, 0, 0, null); | |||||
HSSFWorkbook wb = new HSSFWorkbook(); | |||||
wb.createSheet().createRow(0).createCell(0); | |||||
EvaluationWorkbook ewb = HSSFEvaluationWorkbook.create(wb); | |||||
OperationEvaluationContext ec = new OperationEvaluationContext(null, ewb, 0, 0, 0, null); | |||||
return new WorkbookEvaluator(null, null, null).evaluateFormula(ec, ptgs); | return new WorkbookEvaluator(null, null, null).evaluateFormula(ec, ptgs); | ||||
} | } | ||||