Lines 18-31
Link Here
|
18 |
package org.apache.poi.hssf.record.formula.functions; |
18 |
package org.apache.poi.hssf.record.formula.functions; |
19 |
|
19 |
|
20 |
import org.apache.poi.hssf.record.formula.eval.AreaEval; |
20 |
import org.apache.poi.hssf.record.formula.eval.AreaEval; |
21 |
import org.apache.poi.hssf.record.formula.eval.BoolEval; |
|
|
22 |
import org.apache.poi.hssf.record.formula.eval.ErrorEval; |
21 |
import org.apache.poi.hssf.record.formula.eval.ErrorEval; |
23 |
import org.apache.poi.hssf.record.formula.eval.Eval; |
22 |
import org.apache.poi.hssf.record.formula.eval.Eval; |
|
|
23 |
import org.apache.poi.hssf.record.formula.eval.EvaluationException; |
24 |
import org.apache.poi.hssf.record.formula.eval.NumberEval; |
24 |
import org.apache.poi.hssf.record.formula.eval.NumberEval; |
25 |
import org.apache.poi.hssf.record.formula.eval.NumericValueEval; |
25 |
import org.apache.poi.hssf.record.formula.eval.NumericValueEval; |
|
|
26 |
import org.apache.poi.hssf.record.formula.eval.OperandResolver; |
26 |
import org.apache.poi.hssf.record.formula.eval.RefEval; |
27 |
import org.apache.poi.hssf.record.formula.eval.RefEval; |
27 |
import org.apache.poi.hssf.record.formula.eval.StringEval; |
28 |
import org.apache.poi.hssf.record.formula.eval.StringEval; |
28 |
import org.apache.poi.hssf.record.formula.eval.ValueEval; |
29 |
import org.apache.poi.hssf.record.formula.eval.ValueEval; |
|
|
30 |
import org.apache.poi.hssf.record.formula.functions.LookupUtils.CompareResult; |
31 |
import org.apache.poi.hssf.record.formula.functions.LookupUtils.LookupValueComparer; |
29 |
|
32 |
|
30 |
/** |
33 |
/** |
31 |
* Implementation for the MATCH() Excel function.<p/> |
34 |
* Implementation for the MATCH() Excel function.<p/> |
Lines 62-79
Link Here
|
62 |
*/ |
65 |
*/ |
63 |
public final class Match implements Function { |
66 |
public final class Match implements Function { |
64 |
|
67 |
|
65 |
private static final class EvalEx extends Exception { |
|
|
66 |
private final ErrorEval _error; |
67 |
|
68 |
|
68 |
public EvalEx(ErrorEval error) { |
|
|
69 |
_error = error; |
70 |
} |
71 |
public ErrorEval getError() { |
72 |
return _error; |
73 |
} |
74 |
} |
75 |
|
76 |
|
77 |
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { |
69 |
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { |
78 |
|
70 |
|
79 |
double match_type = 1; // default |
71 |
double match_type = 1; // default |
Lines 82-88
Link Here
|
82 |
case 3: |
74 |
case 3: |
83 |
try { |
75 |
try { |
84 |
match_type = evaluateMatchTypeArg(args[2], srcCellRow, srcCellCol); |
76 |
match_type = evaluateMatchTypeArg(args[2], srcCellRow, srcCellCol); |
85 |
} catch (EvalEx e) { |
77 |
} catch (EvaluationException e) { |
86 |
// Excel/MATCH() seems to have slightly abnormal handling of errors with |
78 |
// Excel/MATCH() seems to have slightly abnormal handling of errors with |
87 |
// the last parameter. Errors do not propagate up. Every error gets |
79 |
// the last parameter. Errors do not propagate up. Every error gets |
88 |
// translated into #REF! |
80 |
// translated into #REF! |
Lines 100-208
Link Here
|
100 |
|
92 |
|
101 |
|
93 |
|
102 |
try { |
94 |
try { |
103 |
ValueEval lookupValue = evaluateLookupValue(args[0], srcCellRow, srcCellCol); |
95 |
ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); |
104 |
ValueEval[] lookupRange = evaluateLookupRange(args[1]); |
96 |
ValueEval[] lookupRange = evaluateLookupRange(args[1]); |
105 |
int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual); |
97 |
int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual); |
106 |
return new NumberEval(index + 1); // +1 to convert to 1-based |
98 |
return new NumberEval(index + 1); // +1 to convert to 1-based |
107 |
} catch (EvalEx e) { |
99 |
} catch (EvaluationException e) { |
108 |
return e.getError(); |
100 |
return e.getErrorEval(); |
109 |
} |
101 |
} |
110 |
} |
102 |
} |
111 |
|
103 |
|
112 |
private static ValueEval chooseSingleElementFromArea(AreaEval ae, |
104 |
private static ValueEval[] evaluateLookupRange(Eval eval) throws EvaluationException { |
113 |
int srcCellRow, short srcCellCol) throws EvalEx { |
|
|
114 |
if (ae.isColumn()) { |
115 |
if(ae.isRow()) { |
116 |
return ae.getValues()[0]; |
117 |
} |
118 |
if(!ae.containsRow(srcCellRow)) { |
119 |
throw new EvalEx(ErrorEval.VALUE_INVALID); |
120 |
} |
121 |
return ae.getValueAt(srcCellRow, ae.getFirstColumn()); |
122 |
} |
123 |
if(!ae.isRow()) { |
124 |
throw new EvalEx(ErrorEval.VALUE_INVALID); |
125 |
} |
126 |
if(!ae.containsColumn(srcCellCol)) { |
127 |
throw new EvalEx(ErrorEval.VALUE_INVALID); |
128 |
} |
129 |
return ae.getValueAt(ae.getFirstRow(), srcCellCol); |
130 |
|
131 |
} |
132 |
|
133 |
private static ValueEval evaluateLookupValue(Eval eval, int srcCellRow, short srcCellCol) |
134 |
throws EvalEx { |
135 |
if (eval instanceof RefEval) { |
105 |
if (eval instanceof RefEval) { |
136 |
RefEval re = (RefEval) eval; |
106 |
RefEval re = (RefEval) eval; |
137 |
return re.getInnerValueEval(); |
|
|
138 |
} |
139 |
if (eval instanceof AreaEval) { |
140 |
return chooseSingleElementFromArea((AreaEval) eval, srcCellRow, srcCellCol); |
141 |
} |
142 |
if (eval instanceof ValueEval) { |
143 |
return (ValueEval) eval; |
144 |
} |
145 |
throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")"); |
146 |
} |
147 |
|
148 |
|
149 |
private static ValueEval[] evaluateLookupRange(Eval eval) throws EvalEx { |
150 |
if (eval instanceof RefEval) { |
151 |
RefEval re = (RefEval) eval; |
152 |
return new ValueEval[] { re.getInnerValueEval(), }; |
107 |
return new ValueEval[] { re.getInnerValueEval(), }; |
153 |
} |
108 |
} |
154 |
if (eval instanceof AreaEval) { |
109 |
if (eval instanceof AreaEval) { |
155 |
AreaEval ae = (AreaEval) eval; |
110 |
AreaEval ae = (AreaEval) eval; |
156 |
if(!ae.isColumn() && !ae.isRow()) { |
111 |
if(!ae.isColumn() && !ae.isRow()) { |
157 |
throw new EvalEx(ErrorEval.NA); |
112 |
throw new EvaluationException(ErrorEval.NA); |
158 |
} |
113 |
} |
159 |
return ae.getValues(); |
114 |
return ae.getValues(); |
160 |
} |
115 |
} |
161 |
|
116 |
|
162 |
// Error handling for lookup_range arg is also unusual |
117 |
// Error handling for lookup_range arg is also unusual |
163 |
if(eval instanceof NumericValueEval) { |
118 |
if(eval instanceof NumericValueEval) { |
164 |
throw new EvalEx(ErrorEval.NA); |
119 |
throw new EvaluationException(ErrorEval.NA); |
165 |
} |
120 |
} |
166 |
if (eval instanceof StringEval) { |
121 |
if (eval instanceof StringEval) { |
167 |
StringEval se = (StringEval) eval; |
122 |
StringEval se = (StringEval) eval; |
168 |
Double d = parseDouble(se.getStringValue()); |
123 |
Double d = OperandResolver.parseDouble(se.getStringValue()); |
169 |
if(d == null) { |
124 |
if(d == null) { |
170 |
// plain string |
125 |
// plain string |
171 |
throw new EvalEx(ErrorEval.VALUE_INVALID); |
126 |
throw new EvaluationException(ErrorEval.VALUE_INVALID); |
172 |
} |
127 |
} |
173 |
// else looks like a number |
128 |
// else looks like a number |
174 |
throw new EvalEx(ErrorEval.NA); |
129 |
throw new EvaluationException(ErrorEval.NA); |
175 |
} |
130 |
} |
176 |
throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")"); |
131 |
throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")"); |
177 |
} |
132 |
} |
178 |
|
133 |
|
179 |
|
134 |
|
180 |
private static Double parseDouble(String stringValue) { |
|
|
181 |
// TODO find better home for parseDouble |
182 |
return Countif.parseDouble(stringValue); |
183 |
} |
184 |
|
135 |
|
185 |
|
|
|
186 |
|
187 |
private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol) |
136 |
private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol) |
188 |
throws EvalEx { |
137 |
throws EvaluationException { |
189 |
Eval match_type = arg; |
138 |
Eval match_type = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); |
190 |
if(arg instanceof AreaEval) { |
139 |
|
191 |
AreaEval ae = (AreaEval) arg; |
|
|
192 |
// an area ref can work as a scalar value if it is 1x1 |
193 |
if(ae.isColumn() && ae.isRow()) { |
194 |
match_type = ae.getValues()[0]; |
195 |
} else { |
196 |
match_type = chooseSingleElementFromArea(ae, srcCellRow, srcCellCol); |
197 |
} |
198 |
} |
199 |
|
200 |
if(match_type instanceof RefEval) { |
201 |
RefEval re = (RefEval) match_type; |
202 |
match_type = re.getInnerValueEval(); |
203 |
} |
204 |
if(match_type instanceof ErrorEval) { |
140 |
if(match_type instanceof ErrorEval) { |
205 |
throw new EvalEx((ErrorEval)match_type); |
141 |
throw new EvaluationException((ErrorEval)match_type); |
206 |
} |
142 |
} |
207 |
if(match_type instanceof NumericValueEval) { |
143 |
if(match_type instanceof NumericValueEval) { |
208 |
NumericValueEval ne = (NumericValueEval) match_type; |
144 |
NumericValueEval ne = (NumericValueEval) match_type; |
Lines 210-221
Link Here
|
210 |
} |
146 |
} |
211 |
if (match_type instanceof StringEval) { |
147 |
if (match_type instanceof StringEval) { |
212 |
StringEval se = (StringEval) match_type; |
148 |
StringEval se = (StringEval) match_type; |
213 |
Double d = parseDouble(se.getStringValue()); |
149 |
Double d = OperandResolver.parseDouble(se.getStringValue()); |
214 |
if(d == null) { |
150 |
if(d == null) { |
215 |
// plain string |
151 |
// plain string |
216 |
throw new EvalEx(ErrorEval.VALUE_INVALID); |
152 |
throw new EvaluationException(ErrorEval.VALUE_INVALID); |
217 |
} |
153 |
} |
218 |
// if the string parses as a number, it is ok |
154 |
// if the string parses as a number, it is OK |
219 |
return d.doubleValue(); |
155 |
return d.doubleValue(); |
220 |
} |
156 |
} |
221 |
throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")"); |
157 |
throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")"); |
Lines 225-312
Link Here
|
225 |
* @return zero based index |
161 |
* @return zero based index |
226 |
*/ |
162 |
*/ |
227 |
private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange, |
163 |
private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange, |
228 |
boolean matchExact, boolean findLargestLessThanOrEqual) throws EvalEx { |
164 |
boolean matchExact, boolean findLargestLessThanOrEqual) throws EvaluationException { |
229 |
// TODO - wildcard matching when matchExact and lookupValue is text containing * or ? |
165 |
|
|
|
166 |
LookupValueComparer lookupComparer = createLookupComparer(lookupValue, matchExact); |
167 |
|
230 |
if(matchExact) { |
168 |
if(matchExact) { |
231 |
for (int i = 0; i < lookupRange.length; i++) { |
169 |
for (int i = 0; i < lookupRange.length; i++) { |
232 |
ValueEval lri = lookupRange[i]; |
170 |
if(lookupComparer.compareTo(lookupRange[i]).isEqual()) { |
233 |
if(lri.getClass() != lookupValue.getClass()) { |
|
|
234 |
continue; |
235 |
} |
236 |
if(compareValues(lookupValue, lri) == 0) { |
237 |
return i; |
171 |
return i; |
238 |
} |
172 |
} |
239 |
} |
173 |
} |
240 |
} else { |
174 |
throw new EvaluationException(ErrorEval.NA); |
|
|
175 |
} |
176 |
|
177 |
if(findLargestLessThanOrEqual) { |
241 |
// Note - backward iteration |
178 |
// Note - backward iteration |
242 |
if(findLargestLessThanOrEqual) { |
179 |
for (int i = lookupRange.length - 1; i>=0; i--) { |
243 |
for (int i = lookupRange.length - 1; i>=0; i--) { |
180 |
CompareResult cmp = lookupComparer.compareTo(lookupRange[i]); |
244 |
ValueEval lri = lookupRange[i]; |
181 |
if(cmp.isTypeMismatch()) { |
245 |
if(lri.getClass() != lookupValue.getClass()) { |
182 |
continue; |
246 |
continue; |
|
|
247 |
} |
248 |
int cmp = compareValues(lookupValue, lri); |
249 |
if(cmp == 0) { |
250 |
return i; |
251 |
} |
252 |
if(cmp > 0) { |
253 |
return i; |
254 |
} |
255 |
} |
183 |
} |
256 |
} else { |
184 |
if(!cmp.isLessThan()) { |
257 |
// find smallest greater than or equal to |
185 |
return i; |
258 |
for (int i = 0; i<lookupRange.length; i++) { |
|
|
259 |
ValueEval lri = lookupRange[i]; |
260 |
if(lri.getClass() != lookupValue.getClass()) { |
261 |
continue; |
262 |
} |
263 |
int cmp = compareValues(lookupValue, lri); |
264 |
if(cmp == 0) { |
265 |
return i; |
266 |
} |
267 |
if(cmp > 0) { |
268 |
if(i<1) { |
269 |
throw new EvalEx(ErrorEval.NA); |
270 |
} |
271 |
return i-1; |
272 |
} |
273 |
} |
186 |
} |
274 |
|
|
|
275 |
} |
187 |
} |
|
|
188 |
throw new EvaluationException(ErrorEval.NA); |
276 |
} |
189 |
} |
|
|
190 |
|
191 |
// else - find smallest greater than or equal to |
192 |
// TODO - is binary search used for (match_type==+1) ? |
193 |
for (int i = 0; i<lookupRange.length; i++) { |
194 |
CompareResult cmp = lookupComparer.compareTo(lookupRange[i]); |
195 |
if(cmp.isEqual()) { |
196 |
return i; |
197 |
} |
198 |
if(cmp.isGreaterThan()) { |
199 |
if(i<1) { |
200 |
throw new EvaluationException(ErrorEval.NA); |
201 |
} |
202 |
return i-1; |
203 |
} |
204 |
} |
277 |
|
205 |
|
278 |
throw new EvalEx(ErrorEval.NA); |
206 |
throw new EvaluationException(ErrorEval.NA); |
279 |
} |
207 |
} |
280 |
|
208 |
|
281 |
|
209 |
private static LookupValueComparer createLookupComparer(ValueEval lookupValue, boolean matchExact) throws EvaluationException { |
282 |
/** |
210 |
if (matchExact && lookupValue instanceof StringEval) { |
283 |
* This method can only compare a pair of <tt>NumericValueEval</tt>s, <tt>StringEval</tt>s |
211 |
String stringValue = ((StringEval) lookupValue).getStringValue(); |
284 |
* or <tt>BoolEval</tt>s |
212 |
if(isLookupValueWild(stringValue)) { |
285 |
* @return negative for a<b, positive for a>b and 0 for a = b |
213 |
throw new RuntimeException("Wildcard lookup values '" + stringValue + "' not supported yet"); |
286 |
*/ |
|
|
287 |
private static int compareValues(ValueEval a, ValueEval b) { |
288 |
if (a instanceof StringEval) { |
289 |
StringEval sa = (StringEval) a; |
290 |
StringEval sb = (StringEval) b; |
291 |
return sa.getStringValue().compareToIgnoreCase(sb.getStringValue()); |
292 |
} |
293 |
if (a instanceof NumericValueEval) { |
294 |
NumericValueEval na = (NumericValueEval) a; |
295 |
NumericValueEval nb = (NumericValueEval) b; |
296 |
return Double.compare(na.getNumberValue(), nb.getNumberValue()); |
297 |
} |
298 |
if (a instanceof BoolEval) { |
299 |
boolean ba = ((BoolEval) a).getBooleanValue(); |
300 |
boolean bb = ((BoolEval) b).getBooleanValue(); |
301 |
if(ba == bb) { |
302 |
return 0; |
303 |
} |
214 |
} |
304 |
// TRUE > FALSE |
215 |
|
305 |
if(ba) { |
|
|
306 |
return +1; |
307 |
} |
308 |
return -1; |
309 |
} |
216 |
} |
310 |
throw new RuntimeException("bad eval type (" + a.getClass().getName() + ")"); |
217 |
return LookupUtils.createLookupComparer(lookupValue); |
311 |
} |
218 |
} |
|
|
219 |
|
220 |
private static boolean isLookupValueWild(String stringValue) { |
221 |
if(stringValue.indexOf('?') >=0 || stringValue.indexOf('*') >=0) { |
222 |
return true; |
223 |
} |
224 |
return false; |
225 |
} |
312 |
} |
226 |
} |