<!-- Don't forget to update status.xml too! -->
<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="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>
<!-- Don't forget to update changes.xml too! -->
<changes>
<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="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>
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
+
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);
+ }
}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
+
+
+package org.apache.poi.hssf.record.formula.functions;
+
+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
*/
-package org.apache.poi.hssf.record.formula.functions;
+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;
+ }
-public class Counta extends NotImplementedFunction {
+ if(valueEval instanceof ErrorEval) {
+ // note - error values are counted
+ return 1;
+ }
+ // also empty strings and zeros are counted too
+ return 1;
+ }
}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
+
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 </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;
+ }
}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
+
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;
+ }
}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
+
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);
+ }
}
--- /dev/null
+/* ====================================================================
+ 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;
+ }
+
+}
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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();
+ }
+
+}
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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);
+ }
+}