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:
parent
1b9d7218c5
commit
741fce3ed1
@ -36,6 +36,7 @@
|
||||
|
||||
<!-- 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>
|
||||
|
@ -33,6 +33,7 @@
|
||||
<!-- 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>
|
||||
|
@ -14,12 +14,46 @@
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
@ -14,12 +14,107 @@
|
||||
* 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 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;
|
||||
}
|
||||
}
|
||||
|
@ -14,12 +14,231 @@
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
@ -14,12 +14,95 @@
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
@ -14,12 +14,46 @@
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user