diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java index f5a941d67..cfe88369e 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java @@ -109,6 +109,7 @@ public final class FunctionEval { retval[76] = new Rows(); retval[77] = new Columns(); retval[ID.OFFSET] = new Offset(); + retval[82] = TextFunction.SEARCH; retval[97] = NumericFunction.ATAN2; retval[98] = NumericFunction.ASIN; @@ -131,7 +132,7 @@ public final class FunctionEval { retval[119] = new Replace(); retval[120] = new Substitute(); - retval[124] = new Find(); + retval[124] = TextFunction.FIND; retval[127] = LogicalFunction.IsText; retval[128] = LogicalFunction.IsNumber; diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Days360.java b/src/java/org/apache/poi/hssf/record/formula/functions/Days360.java index 5087348aa..9435b0754 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Days360.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Days360.java @@ -4,6 +4,9 @@ import java.util.Calendar; import java.util.GregorianCalendar; 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; /** @@ -11,14 +14,39 @@ import org.apache.poi.ss.usermodel.DateUtil; * (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 * twelve 30-day months. - * - * + * * @author PUdalau */ -public class Days360 extends NumericFunction.TwoArg { +public class Days360 extends Var2or3ArgFunction { - @Override - protected double evaluate(double d0, double d1) throws EvaluationException { + public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { + 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 endingDate = getEndingDateAccordingToStartingDate(d1, startingDate); 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; } - private Calendar getDate(double date) { + private static Calendar getDate(double date) { Calendar processedDate = new GregorianCalendar(); processedDate.setTime(DateUtil.getJavaDate(date, false)); return processedDate; } - private Calendar getStartingDate(double date) { + private static Calendar getStartingDate(double date) { Calendar startingDate = getDate(date); if (isLastDayOfMonth(startingDate)) { startingDate.set(Calendar.DAY_OF_MONTH, 30); @@ -41,7 +69,7 @@ public class Days360 extends NumericFunction.TwoArg { return startingDate; } - private Calendar getEndingDateAccordingToStartingDate(double date, Calendar startingDate) { + private static Calendar getEndingDateAccordingToStartingDate(double date, Calendar startingDate) { Calendar endingDate = getDate(date); endingDate.setTime(DateUtil.getJavaDate(date, false)); if (isLastDayOfMonth(endingDate)) { @@ -52,7 +80,7 @@ public class Days360 extends NumericFunction.TwoArg { return endingDate; } - private boolean isLastDayOfMonth(Calendar date) { + private static boolean isLastDayOfMonth(Calendar date) { Calendar clone = (Calendar) date.clone(); clone.add(java.util.Calendar.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; } - private Calendar getFirstDayOfNextMonth(Calendar date) { + private static Calendar getFirstDayOfNextMonth(Calendar date) { Calendar newDate = (Calendar) date.clone(); if (date.get(Calendar.MONTH) < Calendar.DECEMBER) { newDate.set(Calendar.MONTH, date.get(Calendar.MONTH) + 1); diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Find.java b/src/java/org/apache/poi/hssf/record/formula/functions/Find.java deleted file mode 100644 index 0341eac32..000000000 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Find.java +++ /dev/null @@ -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.

- * - * Syntax:
- * FIND(find_text, within_text, start_num)

- * - * FIND returns the character position of the first occurrence of find_text inside - * within_text. The third parameter, start_num, is optional (default=1) - * and specifies where to start searching from. Character positions are 1-based.

- * - * @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); - } -} diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/If.java b/src/java/org/apache/poi/hssf/record/formula/functions/If.java index 1cddb23d8..3f7401984 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/If.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/If.java @@ -18,39 +18,34 @@ 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.ErrorEval; 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.ValueEval; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * */ -public final class If implements Function { +public final class If extends Var2or3ArgFunction { - public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { - ValueEval falseResult; - switch (args.length) { - case 3: - falseResult = args[2]; - break; - case 2: - falseResult = BoolEval.FALSE; - break; - default: - return ErrorEval.VALUE_INVALID; - } + public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { boolean b; try { - b = evaluateFirstArg(args[0], srcCellRow, srcCellCol); + b = evaluateFirstArg(arg0, srcRowIndex, srcColumnIndex); } catch (EvaluationException e) { return e.getErrorEval(); } - if (b) { - return args[1]; + return b ? arg1 : BoolEval.FALSE; + } + + 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) diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java b/src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java index fd90f1da4..6a2bafe7e 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java @@ -18,7 +18,6 @@ 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.ErrorEval; 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.ValueEval; @@ -38,24 +37,19 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector; * * @author Josh Micich */ -public final class Lookup implements Function { - - 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 final class Lookup extends Var2or3ArgFunction { + 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 { - ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); - AreaEval aeLookupVector = LookupUtils.resolveTableArrayArg(args[1]); - AreaEval aeResultVector = LookupUtils.resolveTableArrayArg(args[2]); + ValueEval lookupValue = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); + AreaEval aeLookupVector = LookupUtils.resolveTableArrayArg(arg1); + AreaEval aeResultVector = LookupUtils.resolveTableArrayArg(arg2); ValueVector lookupVector = createVector(aeLookupVector); ValueVector resultVector = createVector(aeResultVector); diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Match.java b/src/java/org/apache/poi/hssf/record/formula/functions/Match.java index 139b794ca..c2208196f 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Match.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Match.java @@ -63,37 +63,40 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector; * * @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) { - case 3: - try { - match_type = evaluateMatchTypeArg(args[2], srcCellRow, srcCellCol); - } catch (EvaluationException e) { - // Excel/MATCH() seems to have slightly abnormal handling of errors with - // the last parameter. Errors do not propagate up. Every error gets - // translated into #REF! - return ErrorEval.REF_INVALID; - } - case 2: - break; - default: - return ErrorEval.VALUE_INVALID; + try { + match_type = evaluateMatchTypeArg(arg2, srcRowIndex, srcColumnIndex); + } catch (EvaluationException e) { + // Excel/MATCH() seems to have slightly abnormal handling of errors with + // the last parameter. Errors do not propagate up. Every error gets + // translated into #REF! + return ErrorEval.REF_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; // Note - Excel does not strictly require -1 and +1 boolean findLargestLessThanOrEqual = match_type > 0; - try { - ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); - ValueVector lookupRange = evaluateLookupRange(args[1]); + ValueEval lookupValue = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); + ValueVector lookupRange = evaluateLookupRange(arg1); int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual); return new NumberEval(index + 1); // +1 to convert to 1-based } catch (EvaluationException e) { diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Sumif.java b/src/java/org/apache/poi/hssf/record/formula/functions/Sumif.java index 224e05292..8bc36ffe2 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Sumif.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Sumif.java @@ -38,32 +38,37 @@ import org.apache.poi.hssf.record.formula.functions.CountUtils.I_MatchPredicate; *

* @author Josh Micich */ -public final class Sumif implements Function { +public final class Sumif extends Var2or3ArgFunction { - public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { - if (args.length < 2) { - return ErrorEval.VALUE_INVALID; + public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { + + 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 aeSum; try { - aeRange = convertRangeArg(args[0]); - - switch (args.length) { - case 2: - aeSum = aeRange; - break; - case 3: - aeSum = createSumRange(args[2], aeRange); - break; - default: - return ErrorEval.VALUE_INVALID; - } + aeRange = convertRangeArg(arg0); + aeSum = createSumRange(arg2, aeRange); } catch (EvaluationException e) { 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); return new NumberEval(result); } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/TextFunction.java b/src/java/org/apache/poi/hssf/record/formula/functions/TextFunction.java index 360b8db76..e62425986 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/TextFunction.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/TextFunction.java @@ -190,4 +190,72 @@ public abstract class TextFunction implements Function { 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.

+ * + * Syntax:
+ * FIND(find_text, within_text, start_num)

+ * + * FIND returns the character position of the first (case sensitive) occurrence of + * find_text inside within_text. The third parameter, + * start_num, is optional (default=1) and specifies where to start searching + * from. Character positions are 1-based.

+ * + * @author Torstein Tauno Svendsen (torstei@officenet.no) + */ + public static final Function FIND = new SearchFind(true); + /** + * Implementation of the FIND() function.

+ * + * Syntax:
+ * SEARCH(find_text, within_text, start_num)

+ * + * SEARCH is a case-insensitive version of FIND() + */ + public static final Function SEARCH = new SearchFind(false); } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Var2or3ArgFunction.java b/src/java/org/apache/poi/hssf/record/formula/functions/Var2or3ArgFunction.java new file mode 100644 index 000000000..9f5483fa0 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Var2or3ArgFunction.java @@ -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); +}