mirror of https://github.com/apache/poi.git
Removing calls to AreaEval.getValues() from count and lookup functions
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@690112 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
76964bdcba
commit
4897063eab
|
@ -1,23 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* Created on May 8, 2005
|
||||
*
|
||||
*/
|
||||
/* ====================================================================
|
||||
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.eval;
|
||||
|
||||
/**
|
||||
|
@ -72,13 +69,9 @@ public interface AreaEval extends ValueEval {
|
|||
ValueEval[] getValues();
|
||||
|
||||
/**
|
||||
* returns the ValueEval from the values array at the specified
|
||||
* row and col index. The specified indexes should be absolute indexes
|
||||
* in the sheet and not relative indexes within the area. Also,
|
||||
* if contains(row, col) evaluates to true, a null value will
|
||||
* bre returned.
|
||||
* @param row
|
||||
* @param col
|
||||
* @return the ValueEval from within this area at the specified row and col index. Never
|
||||
* <code>null</code> (possibly {@link BlankEval}). The specified indexes should be absolute
|
||||
* indexes in the sheet and not relative indexes within the area.
|
||||
*/
|
||||
ValueEval getValueAt(int row, int col);
|
||||
|
||||
|
@ -105,5 +98,10 @@ public interface AreaEval extends ValueEval {
|
|||
|
||||
int getWidth();
|
||||
int getHeight();
|
||||
/**
|
||||
* @return the ValueEval from within this area at the specified relativeRowIndex and
|
||||
* relativeColumnIndex. Never <code>null</code> (possibly {@link BlankEval}). The
|
||||
* specified indexes should relative to the top left corner of this area.
|
||||
*/
|
||||
ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
/* ====================================================================
|
||||
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.eval;
|
||||
|
||||
|
@ -123,7 +123,11 @@ abstract class AreaEvalBase implements AreaEval {
|
|||
|
||||
public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) {
|
||||
int index = relativeRowIndex * _nColumns + relativeColumnIndex;
|
||||
return _values[index];
|
||||
ValueEval result = _values[index];
|
||||
if (result == null) {
|
||||
return BlankEval.INSTANCE;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
|
|
|
@ -1,30 +1,27 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* Created on May 9, 2005
|
||||
*
|
||||
*/
|
||||
/* ====================================================================
|
||||
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.eval;
|
||||
|
||||
/**
|
||||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > This class is a
|
||||
* marker class. It is a special value for empty cells.
|
||||
*/
|
||||
public class BlankEval implements ValueEval {
|
||||
public final class BlankEval implements ValueEval {
|
||||
|
||||
public static BlankEval INSTANCE = new BlankEval();
|
||||
|
||||
|
|
|
@ -1,32 +1,26 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* Created on May 15, 2005
|
||||
*
|
||||
*/
|
||||
/* ====================================================================
|
||||
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.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.ValueEval;
|
||||
import org.apache.poi.hssf.record.formula.functions.CountUtils.I_MatchPredicate;
|
||||
|
||||
/**
|
||||
* Counts the number of cells that contain numeric data within
|
||||
|
@ -39,7 +33,7 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
|||
* TODO: Check this properly matches excel on edge cases
|
||||
* like formula cells, error cells etc
|
||||
*/
|
||||
public class Count implements Function {
|
||||
public final class Count implements Function {
|
||||
|
||||
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
|
||||
int nArgs = args.length;
|
||||
|
@ -56,63 +50,23 @@ public class Count implements Function {
|
|||
int temp = 0;
|
||||
|
||||
for(int i=0; i<nArgs; i++) {
|
||||
temp += countArg(args[i]);
|
||||
temp += CountUtils.countArg(args[i], predicate);
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
|
||||
}
|
||||
private static final I_MatchPredicate predicate = new I_MatchPredicate() {
|
||||
|
||||
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;
|
||||
public boolean matches(Eval valueEval) {
|
||||
|
||||
if(valueEval instanceof NumberEval) {
|
||||
// only numbers are counted
|
||||
return true;
|
||||
}
|
||||
temp += countValue(val);
|
||||
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
private static int countValue(ValueEval valueEval) {
|
||||
|
||||
if(valueEval == BlankEval.INSTANCE) {
|
||||
return 0;
|
||||
// error values and string values not counted
|
||||
return false;
|
||||
}
|
||||
|
||||
if(valueEval instanceof BlankEval) {
|
||||
// wouldn't need this if BlankEval was final
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(valueEval instanceof ErrorEval) {
|
||||
// note - error values not counted
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(valueEval instanceof NumberEval)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/* ====================================================================
|
||||
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.AreaEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.Eval;
|
||||
import org.apache.poi.hssf.record.formula.eval.RefEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||
|
||||
/**
|
||||
* Common logic for COUNT, COUNTA and COUNTIF
|
||||
*
|
||||
* @author Josh Micich
|
||||
*/
|
||||
final class CountUtils {
|
||||
|
||||
private CountUtils() {
|
||||
// no instances of this class
|
||||
}
|
||||
|
||||
/**
|
||||
* Common interface for the matching criteria.
|
||||
*/
|
||||
public interface I_MatchPredicate {
|
||||
boolean matches(Eval x);
|
||||
}
|
||||
/**
|
||||
* @return the number of evaluated cells in the range that match the specified criteria
|
||||
*/
|
||||
public static int countMatchingCellsInArea(AreaEval areaEval, I_MatchPredicate criteriaPredicate) {
|
||||
int result = 0;
|
||||
|
||||
int height = areaEval.getHeight();
|
||||
int width = areaEval.getWidth();
|
||||
for (int rrIx=0; rrIx<height; rrIx++) {
|
||||
for (int rcIx=0; rcIx<width; rcIx++) {
|
||||
ValueEval ve = areaEval.getRelativeValue(rrIx, rcIx);
|
||||
if(criteriaPredicate.matches(ve)) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* @return 1 if the evaluated cell matches the specified criteria
|
||||
*/
|
||||
public static int countMatchingCell(RefEval refEval, I_MatchPredicate criteriaPredicate) {
|
||||
if(criteriaPredicate.matches(refEval.getInnerValueEval())) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
public static int countArg(Eval eval, I_MatchPredicate criteriaPredicate) {
|
||||
if (eval instanceof AreaEval) {
|
||||
return CountUtils.countMatchingCellsInArea((AreaEval) eval, criteriaPredicate);
|
||||
}
|
||||
if (eval instanceof RefEval) {
|
||||
return CountUtils.countMatchingCell((RefEval) eval, criteriaPredicate);
|
||||
}
|
||||
return criteriaPredicate.matches(eval) ? 1 : 0;
|
||||
}
|
||||
}
|
|
@ -1,31 +1,27 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
/* ====================================================================
|
||||
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.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;
|
||||
import org.apache.poi.hssf.record.formula.functions.CountUtils.I_MatchPredicate;
|
||||
|
||||
/**
|
||||
* Counts the number of cells that contain data within the list of arguments.
|
||||
|
@ -51,70 +47,26 @@ public final class Counta implements Function {
|
|||
}
|
||||
|
||||
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]);
|
||||
temp += CountUtils.countArg(args[i], predicate);
|
||||
|
||||
}
|
||||
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 final I_MatchPredicate predicate = new I_MatchPredicate() {
|
||||
|
||||
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;
|
||||
public boolean matches(Eval valueEval) {
|
||||
// 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
|
||||
|
||||
if(valueEval == BlankEval.INSTANCE) {
|
||||
return false;
|
||||
}
|
||||
temp += countValue(val);
|
||||
|
||||
// Note - everything but BlankEval counts
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
/* ====================================================================
|
||||
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;
|
||||
|
||||
|
@ -28,7 +28,7 @@ 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.RefEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||
import org.apache.poi.hssf.record.formula.functions.CountUtils.I_MatchPredicate;
|
||||
|
||||
/**
|
||||
* Implementation for the function COUNTIF<p/>
|
||||
|
@ -144,12 +144,6 @@ public final class Countif implements Function {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common interface for the matching criteria.
|
||||
*/
|
||||
/* package */ interface I_MatchPredicate {
|
||||
boolean matches(Eval x);
|
||||
}
|
||||
|
||||
private static final class NumberMatcher implements I_MatchPredicate {
|
||||
|
||||
|
@ -360,21 +354,12 @@ public final class Countif implements Function {
|
|||
* @return the number of evaluated cells in the range that match the specified criteria
|
||||
*/
|
||||
private Eval countMatchingCellsInArea(Eval rangeArg, I_MatchPredicate criteriaPredicate) {
|
||||
int result = 0;
|
||||
|
||||
int result;
|
||||
if (rangeArg instanceof RefEval) {
|
||||
RefEval refEval = (RefEval) rangeArg;
|
||||
if(criteriaPredicate.matches(refEval.getInnerValueEval())) {
|
||||
result++;
|
||||
}
|
||||
result = CountUtils.countMatchingCell((RefEval) rangeArg, criteriaPredicate);
|
||||
} else if (rangeArg instanceof AreaEval) {
|
||||
|
||||
AreaEval range = (AreaEval) rangeArg;
|
||||
ValueEval[] values = range.getValues();
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if(criteriaPredicate.matches(values[i])) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
result = CountUtils.countMatchingCellsInArea((AreaEval) rangeArg, criteriaPredicate);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Bad range arg type (" + rangeArg.getClass().getName() + ")");
|
||||
}
|
||||
|
|
|
@ -42,40 +42,6 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
|
|||
*/
|
||||
public final class Hlookup implements Function {
|
||||
|
||||
private static final class RowVector implements ValueVector {
|
||||
|
||||
private final AreaEval _tableArray;
|
||||
private final int _size;
|
||||
private final int _rowAbsoluteIndex;
|
||||
private final int _firstColumnAbsoluteIndex;
|
||||
|
||||
public RowVector(AreaEval tableArray, int rowIndex) {
|
||||
_rowAbsoluteIndex = tableArray.getFirstRow() + rowIndex;
|
||||
if(!tableArray.containsRow(_rowAbsoluteIndex)) {
|
||||
int lastRowIx = tableArray.getLastRow() - tableArray.getFirstRow();
|
||||
throw new IllegalArgumentException("Specified row index (" + rowIndex
|
||||
+ ") is outside the allowed range (0.." + lastRowIx + ")");
|
||||
}
|
||||
_tableArray = tableArray;
|
||||
_size = tableArray.getLastColumn() - tableArray.getFirstColumn() + 1;
|
||||
if(_size < 1) {
|
||||
throw new RuntimeException("bad table array size zero");
|
||||
}
|
||||
_firstColumnAbsoluteIndex = tableArray.getFirstColumn();
|
||||
}
|
||||
|
||||
public ValueEval getItem(int index) {
|
||||
if(index>_size) {
|
||||
throw new ArrayIndexOutOfBoundsException("Specified index (" + index
|
||||
+ ") is outside the allowed range (0.." + (_size-1) + ")");
|
||||
}
|
||||
return _tableArray.getValueAt(_rowAbsoluteIndex, (short) (_firstColumnAbsoluteIndex + index));
|
||||
}
|
||||
public int getSize() {
|
||||
return _size;
|
||||
}
|
||||
}
|
||||
|
||||
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
|
||||
Eval arg3 = null;
|
||||
switch(args.length) {
|
||||
|
@ -93,7 +59,7 @@ public final class Hlookup implements Function {
|
|||
ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
|
||||
AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]);
|
||||
boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol);
|
||||
int colIndex = LookupUtils.lookupIndexOfValue(lookupValue, new RowVector(tableArray, 0), isRangeLookup);
|
||||
int colIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createRowVector(tableArray, 0), isRangeLookup);
|
||||
ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol);
|
||||
int rowIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex);
|
||||
ValueVector resultCol = createResultColumnVector(tableArray, rowIndex);
|
||||
|
@ -113,11 +79,9 @@ public final class Hlookup implements Function {
|
|||
if(colIndex < 0) {
|
||||
throw EvaluationException.invalidValue();
|
||||
}
|
||||
int nCols = tableArray.getLastColumn() - tableArray.getFirstRow() + 1;
|
||||
|
||||
if(colIndex >= nCols) {
|
||||
if(colIndex >= tableArray.getWidth()) {
|
||||
throw EvaluationException.invalidRef();
|
||||
}
|
||||
return new RowVector(tableArray, colIndex);
|
||||
return LookupUtils.createRowVector(tableArray, colIndex);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,19 +40,6 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
|
|||
* @author Josh Micich
|
||||
*/
|
||||
public final class Lookup implements Function {
|
||||
private static final class SimpleValueVector implements ValueVector {
|
||||
private final ValueEval[] _values;
|
||||
|
||||
public SimpleValueVector(ValueEval[] values) {
|
||||
_values = values;
|
||||
}
|
||||
public ValueEval getItem(int index) {
|
||||
return _values[index];
|
||||
}
|
||||
public int getSize() {
|
||||
return _values.length;
|
||||
}
|
||||
}
|
||||
|
||||
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
|
||||
switch(args.length) {
|
||||
|
@ -86,11 +73,11 @@ public final class Lookup implements Function {
|
|||
}
|
||||
|
||||
private static ValueVector createVector(AreaEval ae) {
|
||||
|
||||
if(!ae.isRow() && !ae.isColumn()) {
|
||||
// extra complexity required to emulate the way LOOKUP can handles these abnormal cases.
|
||||
throw new RuntimeException("non-vector lookup or result areas not supported yet");
|
||||
ValueVector result = LookupUtils.createVector(ae);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
return new SimpleValueVector(ae.getValues());
|
||||
// extra complexity required to emulate the way LOOKUP can handles these abnormal cases.
|
||||
throw new RuntimeException("non-vector lookup or result areas not supported yet");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,11 +34,11 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
|||
|
||||
/**
|
||||
* Common functionality used by VLOOKUP, HLOOKUP, LOOKUP and MATCH
|
||||
*
|
||||
*
|
||||
* @author Josh Micich
|
||||
*/
|
||||
final class LookupUtils {
|
||||
|
||||
|
||||
/**
|
||||
* Represents a single row or column within an <tt>AreaEval</tt>.
|
||||
*/
|
||||
|
@ -46,14 +46,95 @@ final class LookupUtils {
|
|||
ValueEval getItem(int index);
|
||||
int getSize();
|
||||
}
|
||||
|
||||
|
||||
private static final class RowVector implements ValueVector {
|
||||
|
||||
private final AreaEval _tableArray;
|
||||
private final int _size;
|
||||
private final int _rowIndex;
|
||||
|
||||
public RowVector(AreaEval tableArray, int rowIndex) {
|
||||
_rowIndex = rowIndex;
|
||||
int _rowAbsoluteIndex = tableArray.getFirstRow() + rowIndex;
|
||||
if(!tableArray.containsRow(_rowAbsoluteIndex)) {
|
||||
int lastRowIx = tableArray.getLastRow() - tableArray.getFirstRow();
|
||||
throw new IllegalArgumentException("Specified row index (" + rowIndex
|
||||
+ ") is outside the allowed range (0.." + lastRowIx + ")");
|
||||
}
|
||||
_tableArray = tableArray;
|
||||
_size = tableArray.getWidth();
|
||||
}
|
||||
|
||||
public ValueEval getItem(int index) {
|
||||
if(index > _size) {
|
||||
throw new ArrayIndexOutOfBoundsException("Specified index (" + index
|
||||
+ ") is outside the allowed range (0.." + (_size-1) + ")");
|
||||
}
|
||||
return _tableArray.getRelativeValue(_rowIndex, index);
|
||||
}
|
||||
public int getSize() {
|
||||
return _size;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ColumnVector implements ValueVector {
|
||||
|
||||
private final AreaEval _tableArray;
|
||||
private final int _size;
|
||||
private final int _columnIndex;
|
||||
|
||||
public ColumnVector(AreaEval tableArray, int columnIndex) {
|
||||
_columnIndex = columnIndex;
|
||||
int _columnAbsoluteIndex = tableArray.getFirstColumn() + columnIndex;
|
||||
if(!tableArray.containsColumn((short)_columnAbsoluteIndex)) {
|
||||
int lastColIx = tableArray.getLastColumn() - tableArray.getFirstColumn();
|
||||
throw new IllegalArgumentException("Specified column index (" + columnIndex
|
||||
+ ") is outside the allowed range (0.." + lastColIx + ")");
|
||||
}
|
||||
_tableArray = tableArray;
|
||||
_size = _tableArray.getHeight();
|
||||
}
|
||||
|
||||
public ValueEval getItem(int index) {
|
||||
if(index > _size) {
|
||||
throw new ArrayIndexOutOfBoundsException("Specified index (" + index
|
||||
+ ") is outside the allowed range (0.." + (_size-1) + ")");
|
||||
}
|
||||
return _tableArray.getRelativeValue(index, _columnIndex);
|
||||
}
|
||||
public int getSize() {
|
||||
return _size;
|
||||
}
|
||||
}
|
||||
|
||||
public static ValueVector createRowVector(AreaEval tableArray, int relativeRowIndex) {
|
||||
return new RowVector(tableArray, relativeRowIndex);
|
||||
}
|
||||
public static ValueVector createColumnVector(AreaEval tableArray, int relativeColumnIndex) {
|
||||
return new ColumnVector(tableArray, relativeColumnIndex);
|
||||
}
|
||||
/**
|
||||
* @return <code>null</code> if the supplied area is neither a single row nor a single colum
|
||||
*/
|
||||
public static ValueVector createVector(AreaEval ae) {
|
||||
if (ae.isColumn()) {
|
||||
return createColumnVector(ae, 0);
|
||||
}
|
||||
if (ae.isRow()) {
|
||||
return createRowVector(ae, 0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration to support <b>4</b> valued comparison results.<p/>
|
||||
* Excel lookup functions have complex behaviour in the case where the lookup array has mixed
|
||||
* Excel lookup functions have complex behaviour in the case where the lookup array has mixed
|
||||
* types, and/or is unordered. Contrary to suggestions in some Excel documentation, there
|
||||
* does not appear to be a universal ordering across types. The binary search algorithm used
|
||||
* changes behaviour when the evaluated 'mid' value has a different type to the lookup value.<p/>
|
||||
*
|
||||
* A simple int might have done the same job, but there is risk in confusion with the well
|
||||
*
|
||||
* A simple int might have done the same job, but there is risk in confusion with the well
|
||||
* known <tt>Comparable.compareTo()</tt> and <tt>Comparator.compare()</tt> which both use
|
||||
* a ubiquitous 3 value result encoding.
|
||||
*/
|
||||
|
@ -80,7 +161,7 @@ final class LookupUtils {
|
|||
public static final CompareResult LESS_THAN = new CompareResult(false, -1);
|
||||
public static final CompareResult EQUAL = new CompareResult(false, 0);
|
||||
public static final CompareResult GREATER_THAN = new CompareResult(false, +1);
|
||||
|
||||
|
||||
public static final CompareResult valueOf(int simpleCompareResult) {
|
||||
if(simpleCompareResult < 0) {
|
||||
return LESS_THAN;
|
||||
|
@ -90,7 +171,7 @@ final class LookupUtils {
|
|||
}
|
||||
return EQUAL;
|
||||
}
|
||||
|
||||
|
||||
public boolean isTypeMismatch() {
|
||||
return _isTypeMismatch;
|
||||
}
|
||||
|
@ -128,17 +209,17 @@ final class LookupUtils {
|
|||
return "??error??";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface LookupValueComparer {
|
||||
/**
|
||||
* @return one of 4 instances or <tt>CompareResult</tt>: <tt>LESS_THAN</tt>, <tt>EQUAL</tt>,
|
||||
* @return one of 4 instances or <tt>CompareResult</tt>: <tt>LESS_THAN</tt>, <tt>EQUAL</tt>,
|
||||
* <tt>GREATER_THAN</tt> or <tt>TYPE_MISMATCH</tt>
|
||||
*/
|
||||
CompareResult compareTo(ValueEval other);
|
||||
}
|
||||
|
||||
|
||||
private static abstract class LookupValueComparerBase implements LookupValueComparer {
|
||||
|
||||
|
||||
private final Class _targetClass;
|
||||
protected LookupValueComparerBase(ValueEval targetValue) {
|
||||
if(targetValue == null) {
|
||||
|
@ -154,7 +235,7 @@ final class LookupUtils {
|
|||
return CompareResult.TYPE_MISMATCH;
|
||||
}
|
||||
if (_targetClass == StringEval.class) {
|
||||
|
||||
|
||||
}
|
||||
return compareSameType(other);
|
||||
}
|
||||
|
@ -169,7 +250,7 @@ final class LookupUtils {
|
|||
/** used only for debug purposes */
|
||||
protected abstract String getValueAsString();
|
||||
}
|
||||
|
||||
|
||||
private static final class StringLookupComparer extends LookupValueComparerBase {
|
||||
private String _value;
|
||||
|
||||
|
@ -223,9 +304,9 @@ final class LookupUtils {
|
|||
return String.valueOf(_value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes the third argument to VLOOKUP, or HLOOKUP (<b>col_index_num</b>
|
||||
* Processes the third argument to VLOOKUP, or HLOOKUP (<b>col_index_num</b>
|
||||
* or <b>row_index_num</b> respectively).<br>
|
||||
* Sample behaviour:
|
||||
* <table border="0" cellpadding="1" cellspacing="2" summary="Sample behaviour">
|
||||
|
@ -242,17 +323,17 @@ final class LookupUtils {
|
|||
* <tr><td>""</td><td> </td><td>#REF!</td></tr>
|
||||
* <tr><td><blank></td><td> </td><td>#VALUE!</td></tr>
|
||||
* </table><br/>
|
||||
*
|
||||
* * Note - out of range errors (both too high and too low) are handled by the caller.
|
||||
*
|
||||
* * Note - out of range errors (both too high and too low) are handled by the caller.
|
||||
* @return column or row index as a zero-based value
|
||||
*
|
||||
*
|
||||
*/
|
||||
public static int resolveRowOrColIndexArg(ValueEval veRowColIndexArg) throws EvaluationException {
|
||||
if(veRowColIndexArg == null) {
|
||||
throw new IllegalArgumentException("argument must not be null");
|
||||
}
|
||||
if(veRowColIndexArg instanceof BlankEval) {
|
||||
throw EvaluationException.invalidValue();
|
||||
throw EvaluationException.invalidValue();
|
||||
}
|
||||
if(veRowColIndexArg instanceof StringEval) {
|
||||
StringEval se = (StringEval) veRowColIndexArg;
|
||||
|
@ -260,7 +341,7 @@ final class LookupUtils {
|
|||
Double dVal = OperandResolver.parseDouble(strVal);
|
||||
if(dVal == null) {
|
||||
// String does not resolve to a number. Raise #VALUE! error.
|
||||
throw EvaluationException.invalidRef();
|
||||
throw EvaluationException.invalidRef();
|
||||
// This includes text booleans "TRUE" and "FALSE". They are not valid.
|
||||
}
|
||||
// else - numeric value parses OK
|
||||
|
@ -268,9 +349,9 @@ final class LookupUtils {
|
|||
// actual BoolEval values get interpreted as FALSE->0 and TRUE->1
|
||||
return OperandResolver.coerceValueToInt(veRowColIndexArg) - 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The second argument (table_array) should be an area ref, but can actually be a cell ref, in
|
||||
* which case it is interpreted as a 1x1 area ref. Other scalar values cause #VALUE! error.
|
||||
|
@ -279,13 +360,13 @@ final class LookupUtils {
|
|||
if (eval instanceof AreaEval) {
|
||||
return (AreaEval) eval;
|
||||
}
|
||||
|
||||
|
||||
if(eval instanceof RefEval) {
|
||||
RefEval refEval = (RefEval) eval;
|
||||
// Make this cell ref look like a 1x1 area ref.
|
||||
|
||||
|
||||
// It doesn't matter if eval is a 2D or 3D ref, because that detail is never asked of AreaEval.
|
||||
// This code only requires the value array item.
|
||||
// This code only requires the value array item.
|
||||
// anything would be ok for rowIx and colIx, but may as well get it right.
|
||||
int rowIx = refEval.getRow();
|
||||
int colIx = refEval.getColumn();
|
||||
|
@ -295,10 +376,10 @@ final class LookupUtils {
|
|||
}
|
||||
throw EvaluationException.invalidValue();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Resolves the last (optional) parameter (<b>range_lookup</b>) to the VLOOKUP and HLOOKUP functions.
|
||||
* Resolves the last (optional) parameter (<b>range_lookup</b>) to the VLOOKUP and HLOOKUP functions.
|
||||
* @param rangeLookupArg
|
||||
* @param srcCellRow
|
||||
* @param srcCellCol
|
||||
|
@ -318,7 +399,7 @@ final class LookupUtils {
|
|||
return false;
|
||||
}
|
||||
if(valEval instanceof BoolEval) {
|
||||
// Happy day flow
|
||||
// Happy day flow
|
||||
BoolEval boolEval = (BoolEval) valEval;
|
||||
return boolEval.getBooleanValue();
|
||||
}
|
||||
|
@ -327,7 +408,7 @@ final class LookupUtils {
|
|||
String stringValue = ((StringEval) valEval).getStringValue();
|
||||
if(stringValue.length() < 1) {
|
||||
// More trickiness:
|
||||
// Empty string is not the same as BlankEval. It causes #VALUE! error
|
||||
// Empty string is not the same as BlankEval. It causes #VALUE! error
|
||||
throw EvaluationException.invalidValue();
|
||||
}
|
||||
// TODO move parseBoolean to OperandResolver
|
||||
|
@ -337,10 +418,10 @@ final class LookupUtils {
|
|||
return b.booleanValue();
|
||||
}
|
||||
// Even more trickiness:
|
||||
// Note - even if the StringEval represents a number value (for example "1"),
|
||||
// Excel does not resolve it to a boolean.
|
||||
// Note - even if the StringEval represents a number value (for example "1"),
|
||||
// Excel does not resolve it to a boolean.
|
||||
throw EvaluationException.invalidValue();
|
||||
// This is in contrast to the code below,, where NumberEvals values (for
|
||||
// This is in contrast to the code below,, where NumberEvals values (for
|
||||
// example 0.01) *do* resolve to equivalent boolean values.
|
||||
}
|
||||
if (valEval instanceof NumericValueEval) {
|
||||
|
@ -350,7 +431,7 @@ final class LookupUtils {
|
|||
}
|
||||
throw new RuntimeException("Unexpected eval type (" + valEval.getClass().getName() + ")");
|
||||
}
|
||||
|
||||
|
||||
public static int lookupIndexOfValue(ValueEval lookupValue, ValueVector vector, boolean isRangeLookup) throws EvaluationException {
|
||||
LookupValueComparer lookupComparer = createLookupComparer(lookupValue);
|
||||
int result;
|
||||
|
@ -364,13 +445,13 @@ final class LookupUtils {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Finds first (lowest index) exact occurrence of specified value.
|
||||
* @param lookupValue the value to be found in column or row vector
|
||||
* @param vector the values to be searched. For VLOOKUP this is the first column of the
|
||||
* tableArray. For HLOOKUP this is the first row of the tableArray.
|
||||
* @param vector the values to be searched. For VLOOKUP this is the first column of the
|
||||
* tableArray. For HLOOKUP this is the first row of the tableArray.
|
||||
* @return zero based index into the vector, -1 if value cannot be found
|
||||
*/
|
||||
private static int lookupIndexOfExactValue(LookupValueComparer lookupComparer, ValueVector vector) {
|
||||
|
@ -385,10 +466,10 @@ final class LookupUtils {
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Encapsulates some standard binary search functionality so the unusual Excel behaviour can
|
||||
* be clearly distinguished.
|
||||
* be clearly distinguished.
|
||||
*/
|
||||
private static final class BinarySearchIndexes {
|
||||
|
||||
|
@ -427,7 +508,7 @@ final class LookupUtils {
|
|||
}
|
||||
/**
|
||||
* Excel has funny behaviour when the some elements in the search vector are the wrong type.
|
||||
*
|
||||
*
|
||||
*/
|
||||
private static int performBinarySearch(ValueVector vector, LookupValueComparer lookupComparer) {
|
||||
// both low and high indexes point to values assumed too low and too high.
|
||||
|
@ -435,7 +516,7 @@ final class LookupUtils {
|
|||
|
||||
while(true) {
|
||||
int midIx = bsi.getMidIx();
|
||||
|
||||
|
||||
if(midIx < 0) {
|
||||
return bsi.getLowIx();
|
||||
}
|
||||
|
@ -455,17 +536,17 @@ final class LookupUtils {
|
|||
}
|
||||
}
|
||||
/**
|
||||
* Excel seems to handle mismatched types initially by just stepping 'mid' ix forward to the
|
||||
* Excel seems to handle mismatched types initially by just stepping 'mid' ix forward to the
|
||||
* first compatible value.
|
||||
* @param midIx 'mid' index (value which has the wrong type)
|
||||
* @return usually -1, signifying that the BinarySearchIndex has been narrowed to the new mid
|
||||
* @return usually -1, signifying that the BinarySearchIndex has been narrowed to the new mid
|
||||
* index. Zero or greater signifies that an exact match for the lookup value was found
|
||||
*/
|
||||
private static int handleMidValueTypeMismatch(LookupValueComparer lookupComparer, ValueVector vector,
|
||||
BinarySearchIndexes bsi, int midIx) {
|
||||
int newMid = midIx;
|
||||
int highIx = bsi.getHighIx();
|
||||
|
||||
|
||||
while(true) {
|
||||
newMid++;
|
||||
if(newMid == highIx) {
|
||||
|
@ -511,9 +592,9 @@ final class LookupUtils {
|
|||
}
|
||||
|
||||
public static LookupValueComparer createLookupComparer(ValueEval lookupValue) throws EvaluationException {
|
||||
|
||||
|
||||
if (lookupValue instanceof BlankEval) {
|
||||
// blank eval can never be found in a lookup array
|
||||
// blank eval can never be found in a lookup array
|
||||
throw new EvaluationException(ErrorEval.NA);
|
||||
}
|
||||
if (lookupValue instanceof StringEval) {
|
||||
|
|
|
@ -29,18 +29,19 @@ import org.apache.poi.hssf.record.formula.eval.StringEval;
|
|||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||
import org.apache.poi.hssf.record.formula.functions.LookupUtils.CompareResult;
|
||||
import org.apache.poi.hssf.record.formula.functions.LookupUtils.LookupValueComparer;
|
||||
import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* 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.
|
||||
|
@ -50,26 +51,26 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.LookupValueCompa
|
|||
* <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.
|
||||
* 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
|
||||
*/
|
||||
public final class Match implements Function {
|
||||
|
||||
|
||||
|
||||
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
|
||||
|
||||
|
||||
double match_type = 1; // default
|
||||
|
||||
|
||||
switch(args.length) {
|
||||
case 3:
|
||||
try {
|
||||
|
@ -85,15 +86,15 @@ public final class Match implements Function {
|
|||
default:
|
||||
return ErrorEval.VALUE_INVALID;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
ValueEval[] lookupRange = evaluateLookupRange(args[1]);
|
||||
ValueVector lookupRange = evaluateLookupRange(args[1]);
|
||||
int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual);
|
||||
return new NumberEval(index + 1); // +1 to convert to 1-based
|
||||
} catch (EvaluationException e) {
|
||||
|
@ -101,19 +102,40 @@ public final class Match implements Function {
|
|||
}
|
||||
}
|
||||
|
||||
private static ValueEval[] evaluateLookupRange(Eval eval) throws EvaluationException {
|
||||
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(Eval eval) throws EvaluationException {
|
||||
if (eval instanceof RefEval) {
|
||||
RefEval re = (RefEval) eval;
|
||||
return new ValueEval[] { re.getInnerValueEval(), };
|
||||
return new SingleValueVector(re.getInnerValueEval());
|
||||
}
|
||||
if (eval instanceof AreaEval) {
|
||||
AreaEval ae = (AreaEval) eval;
|
||||
if(!ae.isColumn() && !ae.isRow()) {
|
||||
ValueVector result = LookupUtils.createVector((AreaEval)eval);
|
||||
if (result == null) {
|
||||
throw new EvaluationException(ErrorEval.NA);
|
||||
}
|
||||
return ae.getValues();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Error handling for lookup_range arg is also unusual
|
||||
if(eval instanceof NumericValueEval) {
|
||||
throw new EvaluationException(ErrorEval.NA);
|
||||
|
@ -133,7 +155,7 @@ public final class Match implements Function {
|
|||
|
||||
|
||||
|
||||
private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol)
|
||||
private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol)
|
||||
throws EvaluationException {
|
||||
Eval match_type = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
|
||||
|
||||
|
@ -156,28 +178,29 @@ public final class Match implements Function {
|
|||
}
|
||||
throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return zero based index
|
||||
*/
|
||||
private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange,
|
||||
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 < lookupRange.length; i++) {
|
||||
if(lookupComparer.compareTo(lookupRange[i]).isEqual()) {
|
||||
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 = lookupRange.length - 1; i>=0; i--) {
|
||||
CompareResult cmp = lookupComparer.compareTo(lookupRange[i]);
|
||||
for (int i = size - 1; i>=0; i--) {
|
||||
CompareResult cmp = lookupComparer.compareTo(lookupRange.getItem(i));
|
||||
if(cmp.isTypeMismatch()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -187,11 +210,11 @@ public final class Match implements Function {
|
|||
}
|
||||
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<lookupRange.length; i++) {
|
||||
CompareResult cmp = lookupComparer.compareTo(lookupRange[i]);
|
||||
for (int i = 0; i<size; i++) {
|
||||
CompareResult cmp = lookupComparer.compareTo(lookupRange.getItem(i));
|
||||
if(cmp.isEqual()) {
|
||||
return i;
|
||||
}
|
||||
|
@ -212,7 +235,7 @@ public final class Match implements Function {
|
|||
if(isLookupValueWild(stringValue)) {
|
||||
throw new RuntimeException("Wildcard lookup values '" + stringValue + "' not supported yet");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return LookupUtils.createLookupComparer(lookupValue);
|
||||
}
|
||||
|
|
|
@ -42,40 +42,6 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
|
|||
*/
|
||||
public final class Vlookup implements Function {
|
||||
|
||||
private static final class ColumnVector implements ValueVector {
|
||||
|
||||
private final AreaEval _tableArray;
|
||||
private final int _size;
|
||||
private final int _columnAbsoluteIndex;
|
||||
private final int _firstRowAbsoluteIndex;
|
||||
|
||||
public ColumnVector(AreaEval tableArray, int columnIndex) {
|
||||
_columnAbsoluteIndex = tableArray.getFirstColumn() + columnIndex;
|
||||
if(!tableArray.containsColumn((short)_columnAbsoluteIndex)) {
|
||||
int lastColIx = tableArray.getLastColumn() - tableArray.getFirstColumn();
|
||||
throw new IllegalArgumentException("Specified column index (" + columnIndex
|
||||
+ ") is outside the allowed range (0.." + lastColIx + ")");
|
||||
}
|
||||
_tableArray = tableArray;
|
||||
_size = tableArray.getLastRow() - tableArray.getFirstRow() + 1;
|
||||
if(_size < 1) {
|
||||
throw new RuntimeException("bad table array size zero");
|
||||
}
|
||||
_firstRowAbsoluteIndex = tableArray.getFirstRow();
|
||||
}
|
||||
|
||||
public ValueEval getItem(int index) {
|
||||
if(index>_size) {
|
||||
throw new ArrayIndexOutOfBoundsException("Specified index (" + index
|
||||
+ ") is outside the allowed range (0.." + (_size-1) + ")");
|
||||
}
|
||||
return _tableArray.getValueAt(_firstRowAbsoluteIndex + index, (short)_columnAbsoluteIndex);
|
||||
}
|
||||
public int getSize() {
|
||||
return _size;
|
||||
}
|
||||
}
|
||||
|
||||
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
|
||||
Eval arg3 = null;
|
||||
switch(args.length) {
|
||||
|
@ -93,7 +59,7 @@ public final class Vlookup implements Function {
|
|||
ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
|
||||
AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]);
|
||||
boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol);
|
||||
int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, new ColumnVector(tableArray, 0), isRangeLookup);
|
||||
int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createColumnVector(tableArray, 0), isRangeLookup);
|
||||
ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol);
|
||||
int colIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex);
|
||||
ValueVector resultCol = createResultColumnVector(tableArray, colIndex);
|
||||
|
@ -113,11 +79,9 @@ public final class Vlookup implements Function {
|
|||
if(colIndex < 0) {
|
||||
throw EvaluationException.invalidValue();
|
||||
}
|
||||
int nCols = tableArray.getLastColumn() - tableArray.getFirstColumn() + 1;
|
||||
|
||||
if(colIndex >= nCols) {
|
||||
if(colIndex >= tableArray.getWidth()) {
|
||||
throw EvaluationException.invalidRef();
|
||||
}
|
||||
return new ColumnVector(tableArray, colIndex);
|
||||
return LookupUtils.createColumnVector(tableArray, colIndex);
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -32,7 +32,7 @@ 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;
|
||||
import org.apache.poi.hssf.record.formula.functions.Countif.I_MatchPredicate;
|
||||
import org.apache.poi.hssf.record.formula.functions.CountUtils.I_MatchPredicate;
|
||||
import org.apache.poi.hssf.usermodel.HSSFCell;
|
||||
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
|
||||
import org.apache.poi.hssf.usermodel.HSSFRow;
|
||||
|
|
Loading…
Reference in New Issue