refactored functions taking optionally 2 or 3 parameters

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@883041 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2009-11-22 06:27:55 +00:00
parent 18f9851823
commit f56c779723
9 changed files with 223 additions and 147 deletions

View File

@ -109,6 +109,7 @@ public final class FunctionEval {
retval[76] = new Rows(); retval[76] = new Rows();
retval[77] = new Columns(); retval[77] = new Columns();
retval[ID.OFFSET] = new Offset(); retval[ID.OFFSET] = new Offset();
retval[82] = TextFunction.SEARCH;
retval[97] = NumericFunction.ATAN2; retval[97] = NumericFunction.ATAN2;
retval[98] = NumericFunction.ASIN; retval[98] = NumericFunction.ASIN;
@ -131,7 +132,7 @@ public final class FunctionEval {
retval[119] = new Replace(); retval[119] = new Replace();
retval[120] = new Substitute(); retval[120] = new Substitute();
retval[124] = new Find(); retval[124] = TextFunction.FIND;
retval[127] = LogicalFunction.IsText; retval[127] = LogicalFunction.IsText;
retval[128] = LogicalFunction.IsNumber; retval[128] = LogicalFunction.IsNumber;

View File

@ -4,6 +4,9 @@ import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import org.apache.poi.hssf.record.formula.eval.EvaluationException; import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.ss.usermodel.DateUtil;
/** /**
@ -11,14 +14,39 @@ import org.apache.poi.ss.usermodel.DateUtil;
* (twelve 30-day months), which is used in some accounting calculations. Use * (twelve 30-day months), which is used in some accounting calculations. Use
* this function to help compute payments if your accounting system is based on * this function to help compute payments if your accounting system is based on
* twelve 30-day months. * twelve 30-day months.
* *
*
* @author PUdalau * @author PUdalau
*/ */
public class Days360 extends NumericFunction.TwoArg { public class Days360 extends Var2or3ArgFunction {
@Override public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
protected double evaluate(double d0, double d1) throws EvaluationException { double result;
try {
double d0 = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex);
double d1 = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex);
result = evaluate(d0, d1, false);
} catch (EvaluationException e) {
return e.getErrorEval();
}
return new NumberEval(result);
}
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
ValueEval arg2) {
double result;
try {
double d0 = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex);
double d1 = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex);
ValueEval ve = OperandResolver.getSingleValue(arg2, srcRowIndex, srcColumnIndex);
Boolean method = OperandResolver.coerceValueToBoolean(ve, false);
result = evaluate(d0, d1, method == null ? false : method.booleanValue());
} catch (EvaluationException e) {
return e.getErrorEval();
}
return new NumberEval(result);
}
private static double evaluate(double d0, double d1, boolean method) {
Calendar startingDate = getStartingDate(d0); Calendar startingDate = getStartingDate(d0);
Calendar endingDate = getEndingDateAccordingToStartingDate(d1, startingDate); Calendar endingDate = getEndingDateAccordingToStartingDate(d1, startingDate);
long startingDay = startingDate.get(Calendar.MONTH) * 30 + startingDate.get(Calendar.DAY_OF_MONTH); long startingDay = startingDate.get(Calendar.MONTH) * 30 + startingDate.get(Calendar.DAY_OF_MONTH);
@ -27,13 +55,13 @@ public class Days360 extends NumericFunction.TwoArg {
return endingDay - startingDay; return endingDay - startingDay;
} }
private Calendar getDate(double date) { private static Calendar getDate(double date) {
Calendar processedDate = new GregorianCalendar(); Calendar processedDate = new GregorianCalendar();
processedDate.setTime(DateUtil.getJavaDate(date, false)); processedDate.setTime(DateUtil.getJavaDate(date, false));
return processedDate; return processedDate;
} }
private Calendar getStartingDate(double date) { private static Calendar getStartingDate(double date) {
Calendar startingDate = getDate(date); Calendar startingDate = getDate(date);
if (isLastDayOfMonth(startingDate)) { if (isLastDayOfMonth(startingDate)) {
startingDate.set(Calendar.DAY_OF_MONTH, 30); startingDate.set(Calendar.DAY_OF_MONTH, 30);
@ -41,7 +69,7 @@ public class Days360 extends NumericFunction.TwoArg {
return startingDate; return startingDate;
} }
private Calendar getEndingDateAccordingToStartingDate(double date, Calendar startingDate) { private static Calendar getEndingDateAccordingToStartingDate(double date, Calendar startingDate) {
Calendar endingDate = getDate(date); Calendar endingDate = getDate(date);
endingDate.setTime(DateUtil.getJavaDate(date, false)); endingDate.setTime(DateUtil.getJavaDate(date, false));
if (isLastDayOfMonth(endingDate)) { if (isLastDayOfMonth(endingDate)) {
@ -52,7 +80,7 @@ public class Days360 extends NumericFunction.TwoArg {
return endingDate; return endingDate;
} }
private boolean isLastDayOfMonth(Calendar date) { private static boolean isLastDayOfMonth(Calendar date) {
Calendar clone = (Calendar) date.clone(); Calendar clone = (Calendar) date.clone();
clone.add(java.util.Calendar.MONTH, 1); clone.add(java.util.Calendar.MONTH, 1);
clone.add(java.util.Calendar.DAY_OF_MONTH, -1); clone.add(java.util.Calendar.DAY_OF_MONTH, -1);
@ -60,7 +88,7 @@ public class Days360 extends NumericFunction.TwoArg {
return date.get(Calendar.DAY_OF_MONTH) == lastDayOfMonth; return date.get(Calendar.DAY_OF_MONTH) == lastDayOfMonth;
} }
private Calendar getFirstDayOfNextMonth(Calendar date) { private static Calendar getFirstDayOfNextMonth(Calendar date) {
Calendar newDate = (Calendar) date.clone(); Calendar newDate = (Calendar) date.clone();
if (date.get(Calendar.MONTH) < Calendar.DECEMBER) { if (date.get(Calendar.MONTH) < Calendar.DECEMBER) {
newDate.set(Calendar.MONTH, date.get(Calendar.MONTH) + 1); newDate.set(Calendar.MONTH, date.get(Calendar.MONTH) + 1);

View File

@ -1,64 +0,0 @@
/* ====================================================================
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.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* Implementation of the FIND() function.<p/>
*
* <b>Syntax</b>:<br/>
* <b>FIND</b>(<b>find_text</b>, <b>within_text</b>, start_num)<p/>
*
* FIND returns the character position of the first occurrence of <tt>find_text</tt> inside
* <tt>within_text</tt>. The third parameter, <tt>start_num</tt>, is optional (default=1)
* and specifies where to start searching from. Character positions are 1-based.<p/>
*
* @author Torstein Tauno Svendsen (torstei@officenet.no)
*/
public final class Find extends TextFunction {
protected ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol)
throws EvaluationException {
int nArgs = args.length;
if (nArgs < 2 || nArgs > 3) {
return ErrorEval.VALUE_INVALID;
}
String needle = evaluateStringArg(args[0], srcCellRow, srcCellCol);
String haystack = evaluateStringArg(args[1], srcCellRow, srcCellCol);
int startpos;
if (nArgs == 3) {
startpos = evaluateIntArg(args[2], srcCellRow, srcCellCol);
if (startpos <= 0) {
return ErrorEval.VALUE_INVALID;
}
startpos--; // convert 1-based to zero based
} else {
startpos = 0;
}
int result = haystack.indexOf(needle, startpos);
if (result == -1) {
return ErrorEval.VALUE_INVALID;
}
return new NumberEval(result + 1);
}
}

View File

@ -18,39 +18,34 @@
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.BoolEval; 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.EvaluationException; import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
/** /**
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt; * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/ */
public final class If implements Function { public final class If extends Var2or3ArgFunction {
public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
ValueEval falseResult;
switch (args.length) {
case 3:
falseResult = args[2];
break;
case 2:
falseResult = BoolEval.FALSE;
break;
default:
return ErrorEval.VALUE_INVALID;
}
boolean b; boolean b;
try { try {
b = evaluateFirstArg(args[0], srcCellRow, srcCellCol); b = evaluateFirstArg(arg0, srcRowIndex, srcColumnIndex);
} catch (EvaluationException e) { } catch (EvaluationException e) {
return e.getErrorEval(); return e.getErrorEval();
} }
if (b) { return b ? arg1 : BoolEval.FALSE;
return args[1]; }
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
ValueEval arg2) {
boolean b;
try {
b = evaluateFirstArg(arg0, srcRowIndex, srcColumnIndex);
} catch (EvaluationException e) {
return e.getErrorEval();
} }
return falseResult; return b ? arg1 : arg2;
} }
public static boolean evaluateFirstArg(ValueEval arg, int srcCellRow, int srcCellCol) public static boolean evaluateFirstArg(ValueEval arg, int srcCellRow, int srcCellCol)

View File

@ -18,7 +18,6 @@
package org.apache.poi.hssf.record.formula.functions; 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.AreaEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException; import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
@ -38,24 +37,19 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
* *
* @author Josh Micich * @author Josh Micich
*/ */
public final class Lookup implements Function { public final class Lookup extends Var2or3ArgFunction {
public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) {
switch(args.length) {
case 3:
break;
case 2:
// complex rules to choose lookupVector and resultVector from the single area ref
throw new RuntimeException("Two arg version of LOOKUP not supported yet");
default:
return ErrorEval.VALUE_INVALID;
}
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
// complex rules to choose lookupVector and resultVector from the single area ref
throw new RuntimeException("Two arg version of LOOKUP not supported yet");
}
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
ValueEval arg2) {
try { try {
ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); ValueEval lookupValue = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
AreaEval aeLookupVector = LookupUtils.resolveTableArrayArg(args[1]); AreaEval aeLookupVector = LookupUtils.resolveTableArrayArg(arg1);
AreaEval aeResultVector = LookupUtils.resolveTableArrayArg(args[2]); AreaEval aeResultVector = LookupUtils.resolveTableArrayArg(arg2);
ValueVector lookupVector = createVector(aeLookupVector); ValueVector lookupVector = createVector(aeLookupVector);
ValueVector resultVector = createVector(aeResultVector); ValueVector resultVector = createVector(aeResultVector);

View File

@ -63,37 +63,40 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
* *
* @author Josh Micich * @author Josh Micich
*/ */
public final class Match implements Function { public final class Match extends Var2or3ArgFunction {
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
// default match_type is 1.0
return eval(srcRowIndex, srcColumnIndex, arg0, arg1, 1.0);
}
public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
ValueEval arg2) {
double match_type = 1; // default double match_type;
switch(args.length) { try {
case 3: match_type = evaluateMatchTypeArg(arg2, srcRowIndex, srcColumnIndex);
try { } catch (EvaluationException e) {
match_type = evaluateMatchTypeArg(args[2], srcCellRow, srcCellCol); // Excel/MATCH() seems to have slightly abnormal handling of errors with
} catch (EvaluationException e) { // the last parameter. Errors do not propagate up. Every error gets
// Excel/MATCH() seems to have slightly abnormal handling of errors with // translated into #REF!
// the last parameter. Errors do not propagate up. Every error gets return ErrorEval.REF_INVALID;
// translated into #REF!
return ErrorEval.REF_INVALID;
}
case 2:
break;
default:
return ErrorEval.VALUE_INVALID;
} }
return eval(srcRowIndex, srcColumnIndex, arg0, arg1, match_type);
}
private static ValueEval eval(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
double match_type) {
boolean matchExact = match_type == 0; boolean matchExact = match_type == 0;
// Note - Excel does not strictly require -1 and +1 // Note - Excel does not strictly require -1 and +1
boolean findLargestLessThanOrEqual = match_type > 0; boolean findLargestLessThanOrEqual = match_type > 0;
try { try {
ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); ValueEval lookupValue = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
ValueVector lookupRange = evaluateLookupRange(args[1]); ValueVector lookupRange = evaluateLookupRange(arg1);
int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual); int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual);
return new NumberEval(index + 1); // +1 to convert to 1-based return new NumberEval(index + 1); // +1 to convert to 1-based
} catch (EvaluationException e) { } catch (EvaluationException e) {

View File

@ -38,32 +38,37 @@ import org.apache.poi.hssf.record.formula.functions.CountUtils.I_MatchPredicate;
* </p> * </p>
* @author Josh Micich * @author Josh Micich
*/ */
public final class Sumif implements Function { public final class Sumif extends Var2or3ArgFunction {
public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
if (args.length < 2) {
return ErrorEval.VALUE_INVALID; AreaEval aeRange;
try {
aeRange = convertRangeArg(arg0);
} catch (EvaluationException e) {
return e.getErrorEval();
} }
return eval(srcRowIndex, srcColumnIndex, arg1, aeRange, aeRange);
}
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
ValueEval arg2) {
AreaEval aeRange; AreaEval aeRange;
AreaEval aeSum; AreaEval aeSum;
try { try {
aeRange = convertRangeArg(args[0]); aeRange = convertRangeArg(arg0);
aeSum = createSumRange(arg2, aeRange);
switch (args.length) {
case 2:
aeSum = aeRange;
break;
case 3:
aeSum = createSumRange(args[2], aeRange);
break;
default:
return ErrorEval.VALUE_INVALID;
}
} catch (EvaluationException e) { } catch (EvaluationException e) {
return e.getErrorEval(); return e.getErrorEval();
} }
I_MatchPredicate mp = Countif.createCriteriaPredicate(args[1], srcRowIndex, srcColumnIndex); return eval(srcRowIndex, srcColumnIndex, arg1, aeRange, aeSum);
}
private static ValueEval eval(int srcRowIndex, int srcColumnIndex, ValueEval arg1, AreaEval aeRange,
AreaEval aeSum) {
// TODO - junit to prove last arg must be srcColumnIndex and not srcRowIndex
I_MatchPredicate mp = Countif.createCriteriaPredicate(arg1, srcRowIndex, srcColumnIndex);
double result = sumMatchingCells(aeRange, mp, aeSum); double result = sumMatchingCells(aeRange, mp, aeSum);
return new NumberEval(result); return new NumberEval(result);
} }

View File

@ -190,4 +190,72 @@ public abstract class TextFunction implements Function {
return BoolEval.valueOf(s0.equals(s1)); return BoolEval.valueOf(s0.equals(s1));
} }
}; };
private static final class SearchFind extends Var2or3ArgFunction {
private final boolean _isCaseSensitive;
public SearchFind(boolean isCaseSensitive) {
_isCaseSensitive = isCaseSensitive;
}
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
try {
String needle = TextFunction.evaluateStringArg(arg0, srcRowIndex, srcColumnIndex);
String haystack = TextFunction.evaluateStringArg(arg1, srcRowIndex, srcColumnIndex);
return eval(haystack, needle, 0);
} catch (EvaluationException e) {
return e.getErrorEval();
}
}
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
ValueEval arg2) {
try {
String needle = TextFunction.evaluateStringArg(arg0, srcRowIndex, srcColumnIndex);
String haystack = TextFunction.evaluateStringArg(arg1, srcRowIndex, srcColumnIndex);
// evaluate third arg and convert from 1-based to 0-based index
int startpos = TextFunction.evaluateIntArg(arg2, srcRowIndex, srcColumnIndex) - 1;
if (startpos < 0) {
return ErrorEval.VALUE_INVALID;
}
return eval(haystack, needle, startpos);
} catch (EvaluationException e) {
return e.getErrorEval();
}
}
private ValueEval eval(String haystack, String needle, int startIndex) {
int result;
if (_isCaseSensitive) {
result = haystack.indexOf(needle, startIndex);
} else {
result = haystack.toUpperCase().indexOf(needle.toUpperCase(), startIndex);
}
if (result == -1) {
return ErrorEval.VALUE_INVALID;
}
return new NumberEval(result + 1);
}
}
/**
* Implementation of the FIND() function.<p/>
*
* <b>Syntax</b>:<br/>
* <b>FIND</b>(<b>find_text</b>, <b>within_text</b>, start_num)<p/>
*
* FIND returns the character position of the first (case sensitive) occurrence of
* <tt>find_text</tt> inside <tt>within_text</tt>. The third parameter,
* <tt>start_num</tt>, is optional (default=1) and specifies where to start searching
* from. Character positions are 1-based.<p/>
*
* @author Torstein Tauno Svendsen (torstei@officenet.no)
*/
public static final Function FIND = new SearchFind(true);
/**
* Implementation of the FIND() function.<p/>
*
* <b>Syntax</b>:<br/>
* <b>SEARCH</b>(<b>find_text</b>, <b>within_text</b>, start_num)<p/>
*
* SEARCH is a case-insensitive version of FIND()
*/
public static final Function SEARCH = new SearchFind(false);
} }

View File

@ -0,0 +1,46 @@
/* ====================================================================
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.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* Convenience base class for any function which must take two or three
* arguments
*
* @author Josh Micich
*/
abstract class Var2or3ArgFunction implements Function {
public final ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
switch (args.length) {
case 2:
return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1]);
case 3:
return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], args[2]);
}
return ErrorEval.VALUE_INVALID;
}
public abstract ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0,
ValueEval arg1, ValueEval arg2);
public abstract ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0,
ValueEval arg1);
}