Implement CountA, CountIf, Index, Rows and Columns functions. Patch from Josh Micich in bug #44345

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@618230 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2008-02-04 10:48:29 +00:00
parent 1b9d7218c5
commit 741fce3ed1
13 changed files with 1041 additions and 25 deletions

View File

@ -36,6 +36,7 @@
<!-- Don't forget to update status.xml too! --> <!-- Don't forget to update status.xml too! -->
<release version="3.1-beta1" date="2008-??-??"> <release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">44345 - Implement CountA, CountIf, Index, Rows and Columns functions</action>
<action dev="POI-DEVELOPERS" type="fix">44336 - Properly escape sheet names as required when figuring out the text of formulas</action> <action dev="POI-DEVELOPERS" type="fix">44336 - Properly escape sheet names as required when figuring out the text of formulas</action>
<action dev="POI-DEVELOPERS" type="add">44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions</action> <action dev="POI-DEVELOPERS" type="add">44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions</action>
<action dev="POI-DEVELOPERS" type="add">Support for a completed Record based "pull" stream, via org.apache.poi.hssf.eventusermodel.HSSFRecordStream, to complement the existing "push" Event User Model listener stuff</action> <action dev="POI-DEVELOPERS" type="add">Support for a completed Record based "pull" stream, via org.apache.poi.hssf.eventusermodel.HSSFRecordStream, to complement the existing "push" Event User Model listener stuff</action>

View File

@ -33,6 +33,7 @@
<!-- Don't forget to update changes.xml too! --> <!-- Don't forget to update changes.xml too! -->
<changes> <changes>
<release version="3.1-beta1" date="2008-??-??"> <release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">44345 - Implement CountA, CountIf, Index, Rows and Columns functions</action>
<action dev="POI-DEVELOPERS" type="fix">44336 - Properly escape sheet names as required when figuring out the text of formulas</action> <action dev="POI-DEVELOPERS" type="fix">44336 - Properly escape sheet names as required when figuring out the text of formulas</action>
<action dev="POI-DEVELOPERS" type="add">44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions</action> <action dev="POI-DEVELOPERS" type="add">44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions</action>
<action dev="POI-DEVELOPERS" type="add">Support for a completed Record based "pull" stream, via org.apache.poi.hssf.eventusermodel.HSSFRecordStream, to complement the existing "push" Event User Model listener stuff</action> <action dev="POI-DEVELOPERS" type="add">Support for a completed Record based "pull" stream, via org.apache.poi.hssf.eventusermodel.HSSFRecordStream, to complement the existing "push" Event User Model listener stuff</action>

View File

@ -14,12 +14,46 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
public class Columns extends NotImplementedFunction { import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
/**
* Implementation for Excel COLUMNS function.
*
* @author Josh Micich
*/
public final class Columns implements Function {
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
switch(args.length) {
case 1:
// expected
break;
case 0:
// too few arguments
return ErrorEval.VALUE_INVALID;
default:
// too many arguments
return ErrorEval.VALUE_INVALID;
}
Eval firstArg = args[0];
int result;
if (firstArg instanceof AreaEval) {
AreaEval ae = (AreaEval) firstArg;
result = ae.getLastColumn() - ae.getFirstColumn() + 1;
} else if (firstArg instanceof RefEval) {
result = 1;
} else { // anything else is not valid argument
return ErrorEval.VALUE_INVALID;
}
return new NumberEval(result);
}
} }

View File

@ -14,12 +14,107 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
public class Counta extends NotImplementedFunction { import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* Counts the number of cells that contain data within the list of arguments.
*
* Excel Syntax
* COUNTA(value1,value2,...)
* Value1, value2, ... are 1 to 30 arguments representing the values or ranges to be counted.
*
* @author Josh Micich
*/
public final class Counta implements Function {
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
int nArgs = args.length;
if (nArgs < 1) {
// too few arguments
return ErrorEval.VALUE_INVALID;
}
if (nArgs > 30) {
// too many arguments
return ErrorEval.VALUE_INVALID;
}
int temp = 0;
// Note - observed behavior of Excel:
// Error values like #VALUE!, #REF!, #DIV/0!, #NAME? etc don't cause this COUNTA to return an error
// in fact, they seem to get counted
for(int i=0; i<nArgs; i++) {
temp += countArg(args[i]);
} }
return new NumberEval(temp);
}
private static int countArg(Eval eval) {
if (eval instanceof AreaEval) {
AreaEval ae = (AreaEval) eval;
return countAreaEval(ae);
}
if (eval instanceof RefEval) {
RefEval refEval = (RefEval)eval;
return countValue(refEval.getInnerValueEval());
}
if (eval instanceof NumberEval) {
return 1;
}
if (eval instanceof StringEval) {
return 1;
}
throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
}
private static int countAreaEval(AreaEval ae) {
int temp = 0;
ValueEval[] values = ae.getValues();
for (int i = 0; i < values.length; i++) {
ValueEval val = values[i];
if(val == null) {
// seems to occur. Really we would have expected BlankEval
continue;
}
temp += countValue(val);
}
return temp;
}
private static int countValue(ValueEval valueEval) {
if(valueEval == BlankEval.INSTANCE) {
return 0;
}
if(valueEval instanceof BlankEval) {
// wouldn't need this if BlankEval was final
return 0;
}
if(valueEval instanceof ErrorEval) {
// note - error values are counted
return 1;
}
// also empty strings and zeros are counted too
return 1;
}
}

View File

@ -14,12 +14,231 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
public class Countif extends NotImplementedFunction { import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* Implementation for the function COUNTIF<p/>
*
* Syntax: COUNTIF ( range, criteria )
* <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions">
* <tr><th>range&nbsp;&nbsp;&nbsp;</th><td>is the range of cells to be counted based on the criteria</td></tr>
* <tr><th>criteria</th><td>is used to determine which cells to count</td></tr>
* </table>
* <p/>
*
* @author Josh Micich
*/
public final class Countif implements Function {
/**
* Common interface for the matching criteria.
*/
private interface I_MatchPredicate {
boolean matches(Eval x);
}
private static final class NumberMatcher implements I_MatchPredicate {
private final double _value;
public NumberMatcher(double value) {
_value = value;
}
public boolean matches(Eval x) {
if(x instanceof StringEval) {
// if the target(x) is a string, but parses as a number
// it may still count as a match
StringEval se = (StringEval)x;
Double val = parseDouble(se.getStringValue());
if(val == null) {
// x is text that is not a number
return false;
}
return val.doubleValue() == _value;
}
if(!(x instanceof NumberEval)) {
return false;
}
NumberEval ne = (NumberEval) x;
return ne.getNumberValue() == _value;
}
}
private static final class BooleanMatcher implements I_MatchPredicate {
private final boolean _value;
public BooleanMatcher(boolean value) {
_value = value;
}
public boolean matches(Eval x) {
if(x instanceof StringEval) {
StringEval se = (StringEval)x;
Boolean val = parseBoolean(se.getStringValue());
if(val == null) {
// x is text that is not a boolean
return false;
}
if (true) { // change to false to observe more intuitive behaviour
// Note - Unlike with numbers, it seems that COUNTA never matches
// boolean values when the target(x) is a string
return false;
}
return val.booleanValue() == _value;
}
if(!(x instanceof BoolEval)) {
return false;
}
BoolEval be = (BoolEval) x;
return be.getBooleanValue() == _value;
}
}
private static final class StringMatcher implements I_MatchPredicate {
private final String _value;
public StringMatcher(String value) {
_value = value;
}
public boolean matches(Eval x) {
if(!(x instanceof StringEval)) {
return false;
}
StringEval se = (StringEval) x;
return se.getStringValue() == _value;
}
}
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
switch(args.length) {
case 2:
// expected
break;
default:
// TODO - it doesn't seem to be possible to enter COUNTIF() into Excel with the wrong arg count
// perhaps this should be an exception
return ErrorEval.VALUE_INVALID;
}
AreaEval range = (AreaEval) args[0];
Eval criteriaArg = args[1];
if(criteriaArg instanceof RefEval) {
// criteria is not a literal value, but a cell reference
// for example COUNTIF(B2:D4, E1)
RefEval re = (RefEval)criteriaArg;
criteriaArg = re.getInnerValueEval();
} else {
// other non literal tokens such as function calls, have been fully evaluated
// for example COUNTIF(B2:D4, COLUMN(E1))
}
I_MatchPredicate mp = createCriteriaPredicate(criteriaArg);
return countMatchingCellsInArea(range, mp);
}
/**
* @return the number of evaluated cells in the range that match the specified criteria
*/
private Eval countMatchingCellsInArea(AreaEval range, I_MatchPredicate criteriaPredicate) {
ValueEval[] values = range.getValues();
int result = 0;
for (int i = 0; i < values.length; i++) {
if(criteriaPredicate.matches(values[i])) {
result++;
}
}
return new NumberEval(result);
}
private static I_MatchPredicate createCriteriaPredicate(Eval evaluatedCriteriaArg) {
if(evaluatedCriteriaArg instanceof NumberEval) {
return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue());
}
if(evaluatedCriteriaArg instanceof BoolEval) {
return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue());
}
if(evaluatedCriteriaArg instanceof StringEval) {
return createGeneralMatchPredicate((StringEval)evaluatedCriteriaArg);
}
throw new RuntimeException("Unexpected type for criteria ("
+ evaluatedCriteriaArg.getClass().getName() + ")");
}
/**
* When the second argument is a string, many things are possible
*/
private static I_MatchPredicate createGeneralMatchPredicate(StringEval stringEval) {
String value = stringEval.getStringValue();
char firstChar = value.charAt(0);
Boolean booleanVal = parseBoolean(value);
if(booleanVal != null) {
return new BooleanMatcher(booleanVal.booleanValue());
}
Double doubleVal = parseDouble(value);
if(doubleVal != null) {
return new NumberMatcher(doubleVal.doubleValue());
}
switch(firstChar) {
case '>':
case '<':
case '=':
throw new RuntimeException("Incomplete code - criteria expressions such as '"
+ value + "' not supported yet");
}
//else - just a plain string with no interpretation.
return new StringMatcher(value);
}
/**
* Under certain circumstances COUNTA will equate a plain number with a string representation of that number
*/
/* package */ static Double parseDouble(String strRep) {
if(!Character.isDigit(strRep.charAt(0))) {
// avoid using NumberFormatException to tell when string is not a number
return null;
}
// TODO - support notation like '1E3' (==1000)
double val;
try {
val = Double.parseDouble(strRep);
} catch (NumberFormatException e) {
return null;
}
return new Double(val);
}
/**
* Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers.
*/
/* package */ static Boolean parseBoolean(String strRep) {
switch(strRep.charAt(0)) {
case 't':
case 'T':
if("TRUE".equalsIgnoreCase(strRep)) {
return Boolean.TRUE;
}
break;
case 'f':
case 'F':
if("FALSE".equalsIgnoreCase(strRep)) {
return Boolean.FALSE;
}
break;
}
return null;
}
} }

View File

@ -14,12 +14,95 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
public class Index extends NotImplementedFunction { import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
/**
* Implementation for the Excel function INDEX<p/>
*
* Syntax : <br/>
* INDEX ( reference, row_num[, column_num [, area_num]])</br>
* INDEX ( array, row_num[, column_num])
* <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions">
* <tr><th>reference</th><td>typically an area reference, possibly a union of areas</td></tr>
* <tr><th>array</th><td>a literal array value (currently not supported)</td></tr>
* <tr><th>row_num</th><td>selects the row within the array or area reference</td></tr>
* <tr><th>column_num</th><td>selects column within the array or area reference. default is 1</td></tr>
* <tr><th>area_num</th><td>used when reference is a union of areas</td></tr>
* </table>
* <p/>
*
* @author Josh Micich
*/
public final class Index implements Function {
// TODO - javadoc for interface method
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
int nArgs = args.length;
if(nArgs < 2) {
// too few arguments
return ErrorEval.VALUE_INVALID;
}
Eval firstArg = args[0];
if(firstArg instanceof AreaEval) {
AreaEval reference = (AreaEval) firstArg;
int rowIx = 0;
int columnIx = 0;
int areaIx = 0;
switch(nArgs) {
case 4:
areaIx = convertIndexArgToZeroBase(args[3]);
throw new RuntimeException("Incomplete code" +
" - don't know how to support the 'area_num' parameter yet)");
// Excel expression might look like this "INDEX( (A1:B4, C3:D6, D2:E5 ), 1, 2, 3)
// In this example, the 3rd area would be used i.e. D2:E5, and the overall result would be E2
// Token array might be encoded like this: MemAreaPtg, AreaPtg, AreaPtg, UnionPtg, UnionPtg, ParenthesesPtg
// The formula parser doesn't seem to support this yet. Not sure if the evaluator does either
case 3:
columnIx = convertIndexArgToZeroBase(args[2]);
case 2:
rowIx = convertIndexArgToZeroBase(args[1]);
break;
default:
// too many arguments
return ErrorEval.VALUE_INVALID;
}
int nColumns = reference.getLastColumn()-reference.getFirstColumn()+1;
int index = rowIx * nColumns + columnIx;
return reference.getValues()[index];
}
// else the other variation of this function takes an array as the first argument
// it seems like interface 'ArrayEval' does not even exist yet
throw new RuntimeException("Incomplete code - cannot handle first arg of type ("
+ firstArg.getClass().getName() + ")");
}
/**
* takes a NumberEval representing a 1-based index and returns the zero-based int value
*/
private static int convertIndexArgToZeroBase(Eval ev) {
NumberEval ne;
if(ev instanceof RefEval) {
// TODO - write junit to justify this
RefEval re = (RefEval) ev;
ne = (NumberEval) re.getInnerValueEval();
} else {
ne = (NumberEval)ev;
}
return (int)ne.getNumberValue() - 1;
}
} }

View File

@ -14,12 +14,46 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
public class Rows extends NotImplementedFunction { import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
/**
* Implementation for Excel COLUMNS function.
*
* @author Josh Micich
*/
public final class Rows implements Function {
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
switch(args.length) {
case 1:
// expected
break;
case 0:
// too few arguments
return ErrorEval.VALUE_INVALID;
default:
// too many arguments
return ErrorEval.VALUE_INVALID;
}
Eval firstArg = args[0];
int result;
if (firstArg instanceof AreaEval) {
AreaEval ae = (AreaEval) firstArg;
result = ae.getLastRow() - ae.getFirstRow() + 1;
} else if (firstArg instanceof RefEval) {
result = 1;
} else { // anything else is not valid argument
return ErrorEval.VALUE_INVALID;
}
return new NumberEval(result);
}
} }

View File

@ -0,0 +1,44 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.functions;
import junit.framework.Test;
import junit.framework.TestSuite;
/**
* Direct tests for all implementors of <code>Function</code>.
*
* @author Josh Micich
*/
public final class AllIndividualFunctionEvaluationTests {
// TODO - have this suite incorporated into a higher level one
public static Test suite() {
TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.functions");
result.addTestSuite(TestCountFuncs.class);
result.addTestSuite(TestDate.class);
result.addTestSuite(TestFinanceLib.class);
result.addTestSuite(TestIndex.class);
result.addTestSuite(TestMathX.class);
result.addTestSuite(TestRowCol.class);
result.addTestSuite(TestStatsLib.class);
return result;
}
}

View File

@ -0,0 +1,63 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.ReferencePtg;
import org.apache.poi.hssf.record.formula.eval.Area2DEval;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* Test helper class for creating mock <code>Eval</code> objects
*
* @author Josh Micich
*/
final class EvalFactory {
private static final NumberEval ZERO = new NumberEval(0);
private EvalFactory() {
// no instances of this class
}
/**
* Creates a dummy AreaEval (filled with zeros)
* <p/>
* nCols and nRows could have been derived
*/
public static AreaEval createAreaEval(String areaRefStr, int nCols, int nRows) {
int nValues = nCols * nRows;
ValueEval[] values = new ValueEval[nValues];
for (int i = 0; i < nValues; i++) {
values[i] = ZERO;
}
return new Area2DEval(new AreaPtg(areaRefStr), values);
}
/**
* Creates a single RefEval (with value zero)
*/
public static RefEval createRefEval(String refStr) {
return new Ref2DEval(new ReferencePtg(refStr), ZERO, true);
}
}

View File

@ -0,0 +1,101 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.functions;
import junit.framework.AssertionFailedError;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
/**
* Test helper class for invoking functions with numeric results.
*
* @author Josh Micich
*/
final class NumericFunctionInvoker {
private NumericFunctionInvoker() {
// no instances of this class
}
private static final class NumericEvalEx extends Exception {
public NumericEvalEx(String msg) {
super(msg);
}
}
/**
* Invokes the specified function with the arguments.
* <p/>
* Assumes that the cell coordinate parameters of
* <code>Function.evaluate(args, srcCellRow, srcCellCol)</code>
* are not required.
* <p/>
* This method cannot be used for confirming error return codes. Any non-numeric evaluation
* result causes the current junit test to fail.
*/
public static double invoke(Function f, Eval[] args) {
try {
return invokeInternal(f, args, -1, -1);
} catch (NumericEvalEx e) {
throw new AssertionFailedError("Evaluation of function (" + f.getClass().getName()
+ ") failed: " + e.getMessage());
}
}
/**
* Formats nicer error messages for the junit output
*/
private static double invokeInternal(Function f, Eval[] args, int srcCellRow, int srcCellCol)
throws NumericEvalEx {
Eval evalResult = f.evaluate(args, srcCellRow, (short)srcCellCol);
if(evalResult == null) {
throw new NumericEvalEx("Result object was null");
}
if(evalResult instanceof ErrorEval) {
ErrorEval ee = (ErrorEval) evalResult;
throw new NumericEvalEx(formatErrorMessage(ee));
}
if(!(evalResult instanceof NumericValueEval)) {
throw new NumericEvalEx("Result object type (" + evalResult.getClass().getName()
+ ") is invalid. Expected implementor of ("
+ NumericValueEval.class.getName() + ")");
}
NumericValueEval result = (NumericValueEval) evalResult;
return result.getNumberValue();
}
private static String formatErrorMessage(ErrorEval ee) {
if(errorCodesAreEqual(ee, ErrorEval.FUNCTION_NOT_IMPLEMENTED)) {
return "Function not implemented";
}
if(errorCodesAreEqual(ee, ErrorEval.UNKNOWN_ERROR)) {
return "Unknown error";
}
return "Error code=" + ee.getErrorCode();
}
private static boolean errorCodesAreEqual(ErrorEval a, ErrorEval b) {
if(a==b) {
return true;
}
return a.getErrorCode() == b.getErrorCode();
}
}

View File

@ -0,0 +1,150 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.functions;
import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.ReferencePtg;
import org.apache.poi.hssf.record.formula.eval.Area2DEval;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK()
*
* @author Josh Micich
*/
public final class TestCountFuncs extends TestCase {
public TestCountFuncs(String testName) {
super(testName);
}
public void testCountA() {
Eval[] args;
args = new Eval[] {
new NumberEval(0),
};
confirmCountA(1, args);
args = new Eval[] {
new NumberEval(0),
new NumberEval(0),
new StringEval(""),
};
confirmCountA(3, args);
args = new Eval[] {
EvalFactory.createAreaEval("D2:F5", 3, 4),
};
confirmCountA(12, args);
args = new Eval[] {
EvalFactory.createAreaEval("D1:F5", 3, 5), // 15
EvalFactory.createRefEval("A1"),
EvalFactory.createAreaEval("A1:F6", 7, 6), // 42
new NumberEval(0),
};
confirmCountA(59, args);
}
public void testCountIf() {
AreaEval range;
ValueEval[] values;
// when criteria is a boolean value
values = new ValueEval[] {
new NumberEval(0),
new StringEval("TRUE"), // note - does not match boolean TRUE
BoolEval.TRUE,
BoolEval.FALSE,
BoolEval.TRUE,
BlankEval.INSTANCE,
};
range = createAreaEval("A1:B2", values);
confirmCountIf(2, range, BoolEval.TRUE);
// when criteria is numeric
values = new ValueEval[] {
new NumberEval(0),
new StringEval("2"),
new StringEval("2.001"),
new NumberEval(2),
new NumberEval(2),
BoolEval.TRUE,
BlankEval.INSTANCE,
};
range = createAreaEval("A1:B2", values);
confirmCountIf(3, range, new NumberEval(2));
// note - same results when criteria is a string that parses as the number with the same value
confirmCountIf(3, range, new StringEval("2.00"));
if (false) { // not supported yet:
// when criteria is an expression (starting with a comparison operator)
confirmCountIf(4, range, new StringEval(">1"));
}
}
/**
* special case where the criteria argument is a cell reference
*/
public void testCountIfWithCriteriaReference() {
ValueEval[] values = {
new NumberEval(22),
new NumberEval(25),
new NumberEval(21),
new NumberEval(25),
new NumberEval(25),
new NumberEval(25),
};
Area2DEval arg0 = new Area2DEval(new AreaPtg("C1:C6"), values);
Ref2DEval criteriaArg = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(25), true);
Eval[] args= { arg0, criteriaArg, };
double actual = NumericFunctionInvoker.invoke(new Countif(), args);
assertEquals(4, actual, 0D);
}
private static AreaEval createAreaEval(String areaRefStr, ValueEval[] values) {
return new Area2DEval(new AreaPtg(areaRefStr), values);
}
private static void confirmCountA(int expected, Eval[] args) {
double result = NumericFunctionInvoker.invoke(new Counta(), args);
assertEquals(expected, result, 0);
}
private static void confirmCountIf(int expected, AreaEval range, Eval criteria) {
Eval[] args = { range, criteria, };
double result = NumericFunctionInvoker.invoke(new Countif(), args);
assertEquals(expected, result, 0);
}
}

View File

@ -0,0 +1,89 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.functions;
import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.eval.Area2DEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* Tests for the INDEX() function
*
* @author Josh Micich
*/
public final class TestIndex extends TestCase {
public TestIndex(String testName) {
super(testName);
}
private static final double[] TEST_VALUES0 = {
1, 2,
3, 4,
5, 6,
7, 8,
9, 10,
11, 12,
13, // excess array element. TODO - Area2DEval currently has no validation to ensure correct size of values array
};
/**
* For the case when the first argument to INDEX() is an area reference
*/
public void testEvaluateAreaReference() {
double[] values = TEST_VALUES0;
confirmAreaEval("C1:D6", values, 4, 1, 7);
confirmAreaEval("C1:D6", values, 6, 2, 12);
confirmAreaEval("C1:D6", values, 3, -1, 5);
// now treat same data as 3 columns, 4 rows
confirmAreaEval("C10:E13", values, 2, 2, 5);
confirmAreaEval("C10:E13", values, 4, -1, 10);
}
/**
* @param areaRefString in Excel notation e.g. 'D2:E97'
* @param dValues array of evaluated values for the area reference
* @param rowNum 1-based
* @param colNum 1-based, pass -1 to signify argument not present
*/
private static void confirmAreaEval(String areaRefString, double[] dValues,
int rowNum, int colNum, double expectedResult) {
ValueEval[] values = new ValueEval[dValues.length];
for (int i = 0; i < values.length; i++) {
values[i] = new NumberEval(dValues[i]);
}
Area2DEval arg0 = new Area2DEval(new AreaPtg(areaRefString), values);
Eval[] args;
if (colNum > 0) {
args = new Eval[] { arg0, new NumberEval(rowNum), new NumberEval(colNum), };
} else {
args = new Eval[] { arg0, new NumberEval(rowNum), };
}
double actual = NumericFunctionInvoker.invoke(new Index(), args);
assertEquals(expectedResult, actual, 0D);
}
}

View File

@ -0,0 +1,102 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.functions;
import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.eval.Eval;
/**
* Tests for ROW(), ROWS(), COLUMN(), COLUMNS()
*
* @author Josh Micich
*/
public final class TestRowCol extends TestCase {
public TestRowCol(String testName) {
super(testName);
}
public void testCol() {
Function target = new Column();
{
Eval[] args = { EvalFactory.createRefEval("C5"), };
double actual = NumericFunctionInvoker.invoke(target, args);
assertEquals(3, actual, 0D);
}
{
Eval[] args = { EvalFactory.createAreaEval("E2:H12", 4, 11), };
double actual = NumericFunctionInvoker.invoke(target, args);
assertEquals(5, actual, 0D);
}
}
public void testRow() {
Function target = new Row();
{
Eval[] args = { EvalFactory.createRefEval("C5"), };
double actual = NumericFunctionInvoker.invoke(target, args);
assertEquals(5, actual, 0D);
}
{
Eval[] args = { EvalFactory.createAreaEval("E2:H12", 4, 11), };
double actual = NumericFunctionInvoker.invoke(target, args);
assertEquals(2, actual, 0D);
}
}
public void testColumns() {
confirmColumnsFunc("A1:F1", 6, 1);
confirmColumnsFunc("A1:C2", 3, 2);
confirmColumnsFunc("A1:B3", 2, 3);
confirmColumnsFunc("A1:A6", 1, 6);
Eval[] args = { EvalFactory.createRefEval("C5"), };
double actual = NumericFunctionInvoker.invoke(new Columns(), args);
assertEquals(1, actual, 0D);
}
public void testRows() {
confirmRowsFunc("A1:F1", 6, 1);
confirmRowsFunc("A1:C2", 3, 2);
confirmRowsFunc("A1:B3", 2, 3);
confirmRowsFunc("A1:A6", 1, 6);
Eval[] args = { EvalFactory.createRefEval("C5"), };
double actual = NumericFunctionInvoker.invoke(new Rows(), args);
assertEquals(1, actual, 0D);
}
private static void confirmRowsFunc(String areaRefStr, int nCols, int nRows) {
Eval[] args = { EvalFactory.createAreaEval(areaRefStr, nCols, nRows), };
double actual = NumericFunctionInvoker.invoke(new Rows(), args);
assertEquals(nRows, actual, 0D);
}
private static void confirmColumnsFunc(String areaRefStr, int nCols, int nRows) {
Eval[] args = { EvalFactory.createAreaEval(areaRefStr, nCols, nRows), };
double actual = NumericFunctionInvoker.invoke(new Columns(), args);
assertEquals(nCols, actual, 0D);
}
}