--- /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.eval;
+
+import org.apache.poi.hssf.record.formula.functions.Function;
+
+/**
+ * @author Josh Micich
+ */
+public final class IntersectionEval implements Function {
+
+ public static final Function instance = new IntersectionEval();
+
+ private IntersectionEval() {
+ // enforces singleton
+ }
+
+ public ValueEval evaluate(ValueEval[] args, int srcRow, short srcCol) {
+ if(args.length != 2) {
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ try {
+ AreaEval reA = evaluateRef(args[0]);
+ AreaEval reB = evaluateRef(args[1]);
+ AreaEval result = resolveRange(reA, reB);
+ if (result == null) {
+ return ErrorEval.NULL_INTERSECTION;
+ }
+ return result;
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ }
+
+ /**
+ * @return simple rectangular {@link AreaEval} which represents the intersection of areas
+ * <tt>aeA</tt> and <tt>aeB</tt>. If the two areas do not intersect, the result is <code>null</code>.
+ */
+ private static AreaEval resolveRange(AreaEval aeA, AreaEval aeB) {
+
+ int aeAfr = aeA.getFirstRow();
+ int aeAfc = aeA.getFirstColumn();
+ int aeBlc = aeB.getLastColumn();
+ if (aeAfc > aeBlc) {
+ return null;
+ }
+ int aeBfc = aeB.getFirstColumn();
+ if (aeBfc > aeA.getLastColumn()) {
+ return null;
+ }
+ int aeBlr = aeB.getLastRow();
+ if (aeAfr > aeBlr) {
+ return null;
+ }
+ int aeBfr = aeB.getFirstRow();
+ int aeAlr = aeA.getLastRow();
+ if (aeBfr > aeAlr) {
+ return null;
+ }
+
+
+ int top = Math.max(aeAfr, aeBfr);
+ int bottom = Math.min(aeAlr, aeBlr);
+ int left = Math.max(aeAfc, aeBfc);
+ int right = Math.min(aeA.getLastColumn(), aeBlc);
+
+ return aeA.offset(top-aeAfr, bottom-aeAfr, left-aeAfc, right-aeAfc);
+ }
+
+ private static AreaEval evaluateRef(ValueEval arg) throws EvaluationException {
+ if (arg instanceof AreaEval) {
+ return (AreaEval) arg;
+ }
+ if (arg instanceof RefEval) {
+ return ((RefEval) arg).offset(0, 0, 0, 0);
+ }
+ if (arg instanceof ErrorEval) {
+ throw new EvaluationException((ErrorEval)arg);
+ }
+ throw new IllegalArgumentException("Unexpected ref arg class (" + arg.getClass().getName() + ")");
+ }
+}
import org.apache.poi.hssf.record.formula.EqualPtg;
import org.apache.poi.hssf.record.formula.GreaterEqualPtg;
import org.apache.poi.hssf.record.formula.GreaterThanPtg;
+import org.apache.poi.hssf.record.formula.IntersectionPtg;
import org.apache.poi.hssf.record.formula.LessEqualPtg;
import org.apache.poi.hssf.record.formula.LessThanPtg;
import org.apache.poi.hssf.record.formula.MultiplyPtg;
import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
import org.apache.poi.hssf.record.formula.eval.ConcatEval;
import org.apache.poi.hssf.record.formula.eval.FunctionEval;
+import org.apache.poi.hssf.record.formula.eval.IntersectionEval;
import org.apache.poi.hssf.record.formula.eval.OperationEval;
import org.apache.poi.hssf.record.formula.eval.PercentEval;
import org.apache.poi.hssf.record.formula.eval.RangeEval;
put(m, 1, UnaryMinusPtg.instance, UnaryMinusEval.instance);
put(m, 1, UnaryPlusPtg.instance, UnaryPlusEval.instance);
put(m, 2, RangePtg.instance, RangeEval.instance);
+ put(m, 2, IntersectionPtg.instance, IntersectionEval.instance);
return m;
}
* This class does not test implementors of <tt>Function</tt> and <tt>OperationEval</tt> in
* isolation. Much of the evaluation engine (i.e. <tt>HSSFFormulaEvaluator</tt>, ...) gets
* exercised as well. Tests for bug fixes and specific/tricky behaviour can be found in the
- * corresponding test class (<tt>TestXxxx</tt>) of the target (<tt>Xxxx</tt>) implementor,
+ * corresponding test class (<tt>TestXxxx</tt>) of the target (<tt>Xxxx</tt>) implementor,
* where execution can be observed more easily.
- *
+ *
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*/
public final class TestFormulasFromSpreadsheet extends TestCase {
-
+
private static final class Result {
public static final int SOME_EVALUATIONS_FAILED = -1;
public static final int ALL_EVALUATIONS_SUCCEEDED = +1;
public static final int NO_EVALUATIONS_FOUND = 0;
}
- /**
+ /**
* This class defines constants for navigating around the test data spreadsheet used for these tests.
*/
private static final class SS {
-
+
/**
* Name of the test spreadsheet (found in the standard test data folder)
*/
/**
* Row (zero-based) in the test spreadsheet where the function examples start.
*/
- public static final int START_FUNCTIONS_ROW_INDEX = 87; // Row '88'
- /**
+ public static final int START_FUNCTIONS_ROW_INDEX = 95; // Row '96'
+ /**
* Index of the column that contains the function names
*/
public static final int COLUMN_INDEX_FUNCTION_NAME = 1; // Column 'B'
-
+
/**
* Used to indicate when there are no more functions left
*/
public static final String FUNCTION_NAMES_END_SENTINEL = "<END-OF-FUNCTIONS>";
-
+
/**
* Index of the column where the test values start (for each function)
*/
public static final short COLUMN_INDEX_FIRST_TEST_VALUE = 3; // Column 'D'
-
+
/**
- * Each function takes 4 rows in the test spreadsheet
+ * Each function takes 4 rows in the test spreadsheet
*/
public static final int NUMBER_OF_ROWS_PER_FUNCTION = 4;
}
private HSSFWorkbook workbook;
private Sheet sheet;
- // Note - multiple failures are aggregated before ending.
+ // Note - multiple failures are aggregated before ending.
// If one or more functions fail, a single AssertionFailedError is thrown at the end
private int _functionFailureCount;
private int _functionSuccessCount;
if(actual == null) {
throw new AssertionFailedError(msg + " - actual value was null");
}
-
+
switch (expected.getCellType()) {
case Cell.CELL_TYPE_BLANK:
assertEquals(msg, Cell.CELL_TYPE_BLANK, actual.getCellType());
_evaluationFailureCount = 0;
_evaluationSuccessCount = 0;
}
-
+
public void testFunctionsFromTestSpreadsheet() {
-
+
processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, null);
processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, null);
// example for debugging individual functions/operators:
// processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, "ConcatEval");
// processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, "AVERAGE");
-
+
// confirm results
- String successMsg = "There were "
+ String successMsg = "There were "
+ _evaluationSuccessCount + " successful evaluation(s) and "
+ _functionSuccessCount + " function(s) without error";
if(_functionFailureCount > 0) {
}
/**
- * @param startRowIndex row index in the spreadsheet where the first function/operator is found
- * @param testFocusFunctionName name of a single function/operator to test alone.
+ * @param startRowIndex row index in the spreadsheet where the first function/operator is found
+ * @param testFocusFunctionName name of a single function/operator to test alone.
* Typically pass <code>null</code> to test all functions
*/
private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) {
Row r = sheet.getRow(rowIndex);
String targetFunctionName = getTargetFunctionName(r);
if(targetFunctionName == null) {
- throw new AssertionFailedError("Test spreadsheet cell empty on row ("
+ throw new AssertionFailedError("Test spreadsheet cell empty on row ("
+ (rowIndex+1) + "). Expected function name or '"
+ SS.FUNCTION_NAMES_END_SENTINEL + "'");
}
break;
}
if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) {
-
+
// expected results are on the row below
Row expectedValuesRow = sheet.getRow(rowIndex + 1);
if(expectedValuesRow == null) {
int missingRowNum = rowIndex + 2; //+1 for 1-based, +1 for next row
- throw new AssertionFailedError("Missing expected values row for function '"
- + targetFunctionName + " (row " + missingRowNum + ")");
+ throw new AssertionFailedError("Missing expected values row for function '"
+ + targetFunctionName + " (row " + missingRowNum + ")");
}
switch(processFunctionRow(evaluator, targetFunctionName, r, expectedValuesRow)) {
case Result.ALL_EVALUATIONS_SUCCEEDED: _functionSuccessCount++; break;
}
/**
- *
+ *
* @return a constant from the local Result class denoting whether there were any evaluation
* cases, and whether they all succeeded.
*/
- private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName,
+ private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName,
Row formulasRow, Row expectedValuesRow) {
-
+
int result = Result.NO_EVALUATIONS_FOUND; // so far
short endcolnum = formulasRow.getLastCellNum();
*/
private static void printShortStackTrace(PrintStream ps, AssertionFailedError e) {
StackTraceElement[] stes = e.getStackTrace();
-
+
int startIx = 0;
// skip any top frames inside junit.framework.Assert
while(startIx<stes.length) {
if(cell.getCellType() == Cell.CELL_TYPE_STRING) {
return cell.getRichStringCellValue().getString();
}
-
+
throw new AssertionFailedError("Bad cell type for 'function name' column: ("
+ cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")");
}