123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- /* ====================================================================
- 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.ss.formula.functions;
-
- import org.apache.poi.ss.formula.eval.ErrorEval;
- import org.apache.poi.ss.formula.eval.EvaluationException;
- import org.apache.poi.ss.formula.eval.NumberEval;
- import org.apache.poi.ss.formula.eval.NumericValueEval;
- import org.apache.poi.ss.formula.eval.OperandResolver;
- import org.apache.poi.ss.formula.eval.RefEval;
- import org.apache.poi.ss.formula.eval.StringEval;
- import org.apache.poi.ss.formula.eval.ValueEval;
- import org.apache.poi.ss.formula.functions.LookupUtils.CompareResult;
- import org.apache.poi.ss.formula.functions.LookupUtils.LookupValueComparer;
- import org.apache.poi.ss.formula.functions.LookupUtils.ValueVector;
- import org.apache.poi.ss.formula.TwoDEval;
-
- /**
- * Implementation for the MATCH() Excel function.<p/>
- *
- * <b>Syntax:</b><br/>
- * <b>MATCH</b>(<b>lookup_value</b>, <b>lookup_array</b>, match_type)<p/>
- *
- * Returns a 1-based index specifying at what position in the <b>lookup_array</b> the specified
- * <b>lookup_value</b> is found.<p/>
- *
- * Specific matching behaviour can be modified with the optional <b>match_type</b> parameter.
- *
- * <table border="0" cellpadding="1" cellspacing="0" summary="match_type parameter description">
- * <tr><th>Value</th><th>Matching Behaviour</th></tr>
- * <tr><td>1</td><td>(default) find the largest value that is less than or equal to lookup_value.
- * The lookup_array must be in ascending <i>order</i>*.</td></tr>
- * <tr><td>0</td><td>find the first value that is exactly equal to lookup_value.
- * The lookup_array can be in any order.</td></tr>
- * <tr><td>-1</td><td>find the smallest value that is greater than or equal to lookup_value.
- * The lookup_array must be in descending <i>order</i>*.</td></tr>
- * </table>
- *
- * * Note regarding <i>order</i> - For the <b>match_type</b> cases that require the lookup_array to
- * be ordered, MATCH() can produce incorrect results if this requirement is not met. Observed
- * behaviour in Excel is to return the lowest index value for which every item after that index
- * breaks the match rule.<br>
- * The (ascending) sort order expected by MATCH() is:<br/>
- * numbers (low to high), strings (A to Z), boolean (FALSE to TRUE)<br/>
- * MATCH() ignores all elements in the lookup_array with a different type to the lookup_value.
- * Type conversion of the lookup_array elements is never performed.
- *
- *
- * @author Josh Micich
- * @author Cedric Walter at innoveo.com
- */
- 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(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
- ValueEval arg2) {
-
- double match_type;
-
- 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(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) {
- return e.getErrorEval();
- }
- }
-
- private static final class SingleValueVector implements ValueVector {
-
- private final ValueEval _value;
-
- public SingleValueVector(ValueEval value) {
- _value = value;
- }
-
- public ValueEval getItem(int index) {
- if (index != 0) {
- throw new RuntimeException("Invalid index ("
- + index + ") only zero is allowed");
- }
- return _value;
- }
-
- public int getSize() {
- return 1;
- }
- }
-
- private static ValueVector evaluateLookupRange(ValueEval eval) throws EvaluationException {
- if (eval instanceof RefEval) {
- RefEval re = (RefEval) eval;
- return new SingleValueVector(re.getInnerValueEval());
- }
- if (eval instanceof TwoDEval) {
- ValueVector result = LookupUtils.createVector((TwoDEval)eval);
- if (result == null) {
- throw new EvaluationException(ErrorEval.NA);
- }
- return result;
- }
-
- // Error handling for lookup_range arg is also unusual
- if(eval instanceof NumericValueEval) {
- throw new EvaluationException(ErrorEval.NA);
- }
- if (eval instanceof StringEval) {
- StringEval se = (StringEval) eval;
- Double d = OperandResolver.parseDouble(se.getStringValue());
- if(d == null) {
- // plain string
- throw new EvaluationException(ErrorEval.VALUE_INVALID);
- }
- // else looks like a number
- throw new EvaluationException(ErrorEval.NA);
- }
- throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
- }
-
-
-
- private static double evaluateMatchTypeArg(ValueEval arg, int srcCellRow, int srcCellCol)
- throws EvaluationException {
- ValueEval match_type = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
-
- if(match_type instanceof ErrorEval) {
- throw new EvaluationException((ErrorEval)match_type);
- }
- if(match_type instanceof NumericValueEval) {
- NumericValueEval ne = (NumericValueEval) match_type;
- return ne.getNumberValue();
- }
- if (match_type instanceof StringEval) {
- StringEval se = (StringEval) match_type;
- Double d = OperandResolver.parseDouble(se.getStringValue());
- if(d == null) {
- // plain string
- throw new EvaluationException(ErrorEval.VALUE_INVALID);
- }
- // if the string parses as a number, it is OK
- return d.doubleValue();
- }
- throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")");
- }
-
- /**
- * @return zero based index
- */
- private static int findIndexOfValue(ValueEval lookupValue, ValueVector lookupRange,
- boolean matchExact, boolean findLargestLessThanOrEqual) throws EvaluationException {
-
- LookupValueComparer lookupComparer = createLookupComparer(lookupValue, matchExact);
-
- int size = lookupRange.getSize();
- if(matchExact) {
- for (int i = 0; i < size; i++) {
- if(lookupComparer.compareTo(lookupRange.getItem(i)).isEqual()) {
- return i;
- }
- }
- throw new EvaluationException(ErrorEval.NA);
- }
-
- if(findLargestLessThanOrEqual) {
- // Note - backward iteration
- for (int i = size - 1; i>=0; i--) {
- CompareResult cmp = lookupComparer.compareTo(lookupRange.getItem(i));
- if(cmp.isTypeMismatch()) {
- continue;
- }
- if(!cmp.isLessThan()) {
- return i;
- }
- }
- throw new EvaluationException(ErrorEval.NA);
- }
-
- // else - find smallest greater than or equal to
- // TODO - is binary search used for (match_type==+1) ?
- for (int i = 0; i<size; i++) {
- CompareResult cmp = lookupComparer.compareTo(lookupRange.getItem(i));
- if(cmp.isEqual()) {
- return i;
- }
- if(cmp.isGreaterThan()) {
- if(i<1) {
- throw new EvaluationException(ErrorEval.NA);
- }
- return i-1;
- }
- }
-
- throw new EvaluationException(ErrorEval.NA);
- }
-
- private static LookupValueComparer createLookupComparer(ValueEval lookupValue, boolean matchExact) {
- return LookupUtils.createLookupComparer(lookupValue, matchExact, true);
- }
-
- private static boolean isLookupValueWild(String stringValue) {
- if(stringValue.indexOf('?') >=0 || stringValue.indexOf('*') >=0) {
- return true;
- }
- return false;
- }
- }
|