-/*
-* 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;
/**
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);
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);
}
-/*
- * 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;
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() {
-/*
-* 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();
-/*
-* 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
* 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;
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;
- }
- temp += countValue(val);
-
- }
- return temp;
- }
+ public boolean matches(Eval valueEval) {
- 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 NumberEval) {
+ // only numbers are counted
+ return true;
+ }
- if(valueEval instanceof ErrorEval) {
- // note - error values not counted
- return 0;
+ // error values and string values not counted
+ return false;
}
-
- if(valueEval instanceof NumberEval)
- return 1;
-
- return 0;
- }
+ };
}
\ No newline at end of file
--- /dev/null
+/* ====================================================================
+ 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;
+ }
+}
-/*
-* 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.
}
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;
- }
- temp += countValue(val);
-
- }
- return temp;
- }
+ 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
- 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;
+ if(valueEval == BlankEval.INSTANCE) {
+ return false;
+ }
+ // Note - everything but BlankEval counts
+ return true;
}
- // also empty strings and zeros are counted too
-
- return 1;
- }
+ };
}
-/*
-* 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.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/>
}
}
- /**
- * Common interface for the matching criteria.
- */
- /* package */ interface I_MatchPredicate {
- boolean matches(Eval x);
- }
private static final class NumberMatcher implements I_MatchPredicate {
* @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() + ")");
}
*/
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) {
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);
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);
}
}
* @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) {
}
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");
}
}
/**
* 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>.
*/
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.
*/
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;
}
return EQUAL;
}
-
+
public boolean isTypeMismatch() {
return _isTypeMismatch;
}
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) {
return CompareResult.TYPE_MISMATCH;
}
if (_targetClass == StringEval.class) {
-
+
}
return compareSameType(other);
}
/** used only for debug purposes */
protected abstract String getValueAsString();
}
-
+
private static final class StringLookupComparer extends LookupValueComparerBase {
private String _value;
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">
* <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;
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
// 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.
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();
}
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
return false;
}
if(valEval instanceof BoolEval) {
- // Happy day flow
+ // Happy day flow
BoolEval boolEval = (BoolEval) valEval;
return boolEval.getBooleanValue();
}
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
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) {
}
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;
}
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) {
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 {
}
/**
* 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.
while(true) {
int midIx = bsi.getMidIx();
-
+
if(midIx < 0) {
return bsi.getLowIx();
}
}
}
/**
- * 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) {
}
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) {
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.
* <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 {
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) {
}
}
- 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);
- 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);
}
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;
}
}
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;
}
if(isLookupValueWild(stringValue)) {
throw new RuntimeException("Wildcard lookup values '" + stringValue + "' not supported yet");
}
-
+
}
return LookupUtils.createLookupComparer(lookupValue);
}
*/
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) {
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);
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);
}
}
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;