Modified formula evaluator to handle whole column refs
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@692538 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
d2fd5808e6
commit
69e25acaf8
@ -487,7 +487,7 @@ public final class NameRecord extends Record {
|
||||
}
|
||||
|
||||
private static Ptg createNewPtg(){
|
||||
return new Area3DPtg("A1", 0); // TODO - change to not be partially initialised
|
||||
return new Area3DPtg("A1:A1", 0); // TODO - change to not be partially initialised
|
||||
}
|
||||
|
||||
/** gets the reference , the area only (range)
|
||||
|
@ -60,8 +60,8 @@ public final class Hlookup implements Function {
|
||||
AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]);
|
||||
boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol);
|
||||
int colIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createRowVector(tableArray, 0), isRangeLookup);
|
||||
ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol);
|
||||
int rowIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex);
|
||||
ValueEval veRowIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol);
|
||||
int rowIndex = LookupUtils.resolveRowOrColIndexArg(veRowIndex);
|
||||
ValueVector resultCol = createResultColumnVector(tableArray, rowIndex);
|
||||
return resultCol.getItem(colIndex);
|
||||
} catch (EvaluationException e) {
|
||||
@ -75,13 +75,13 @@ public final class Hlookup implements Function {
|
||||
*
|
||||
* @throws EvaluationException (#VALUE!) if colIndex is negative, (#REF!) if colIndex is too high
|
||||
*/
|
||||
private ValueVector createResultColumnVector(AreaEval tableArray, int colIndex) throws EvaluationException {
|
||||
if(colIndex < 0) {
|
||||
private ValueVector createResultColumnVector(AreaEval tableArray, int rowIndex) throws EvaluationException {
|
||||
if(rowIndex < 0) {
|
||||
throw EvaluationException.invalidValue();
|
||||
}
|
||||
if(colIndex >= tableArray.getWidth()) {
|
||||
if(rowIndex >= tableArray.getHeight()) {
|
||||
throw EvaluationException.invalidRef();
|
||||
}
|
||||
return LookupUtils.createRowVector(tableArray, colIndex);
|
||||
return LookupUtils.createRowVector(tableArray, rowIndex);
|
||||
}
|
||||
}
|
||||
|
@ -90,9 +90,19 @@ public final class Index implements Function {
|
||||
}
|
||||
}
|
||||
|
||||
private static ValueEval getValueFromArea(AreaEval ae, int rowIx, int columnIx) throws EvaluationException {
|
||||
private static ValueEval getValueFromArea(AreaEval ae, int pRowIx, int pColumnIx) throws EvaluationException {
|
||||
int width = ae.getWidth();
|
||||
int height = ae.getHeight();
|
||||
int rowIx;
|
||||
int columnIx;
|
||||
if (ae.isRow() && pColumnIx == 0 && pRowIx > 0) {
|
||||
// TODO - explore all these special cases
|
||||
rowIx = 0;
|
||||
columnIx = pRowIx;
|
||||
} else {
|
||||
rowIx = pRowIx;
|
||||
columnIx = pColumnIx;
|
||||
}
|
||||
|
||||
// Slightly irregular logic for bounds checking errors
|
||||
if (rowIx >= height || columnIx >= width) {
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
package org.apache.poi.hssf.record.formula.functions;
|
||||
|
||||
import org.apache.poi.hssf.record.NumberRecord;
|
||||
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.BoolEval;
|
||||
@ -587,7 +588,9 @@ final class LookupUtils {
|
||||
|
||||
if (lookupValue instanceof BlankEval) {
|
||||
// blank eval can never be found in a lookup array
|
||||
throw new EvaluationException(ErrorEval.NA);
|
||||
//throw new EvaluationException(ErrorEval.NA);
|
||||
// TODO - investigate this
|
||||
return new NumberLookupComparer(NumberEval.ZERO);
|
||||
}
|
||||
if (lookupValue instanceof StringEval) {
|
||||
return new StringLookupComparer((StringEval) lookupValue);
|
||||
|
@ -60,7 +60,16 @@ public final class Vlookup implements Function {
|
||||
AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]);
|
||||
boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol);
|
||||
int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createColumnVector(tableArray, 0), isRangeLookup);
|
||||
ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol);
|
||||
ValueEval veColIndex;
|
||||
try {
|
||||
veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol);
|
||||
} catch (EvaluationException e) {
|
||||
// weird translation of errors for the third arg - needs investigation
|
||||
if (e.getErrorEval() == ErrorEval.NA) {
|
||||
return ErrorEval.REF_INVALID;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
int colIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex);
|
||||
ValueVector resultCol = createResultColumnVector(tableArray, colIndex);
|
||||
return resultCol.getItem(rowIndex);
|
||||
|
@ -46,27 +46,57 @@ public final class AreaReference {
|
||||
}
|
||||
|
||||
String[] parts = separateAreaRefs(reference);
|
||||
String part0 = parts[0];
|
||||
if (parts.length == 1) {
|
||||
// TODO - probably shouldn't initialize area ref when text is really a cell ref
|
||||
// Need to fix some named range stuff to get rid of this
|
||||
_firstCell = new CellReference(part0);
|
||||
|
||||
// Special handling for whole-column references
|
||||
if(parts.length == 2 && parts[0].length() == 1 &&
|
||||
parts[1].length() == 1 &&
|
||||
parts[0].charAt(0) >= 'A' && parts[0].charAt(0) <= 'Z' &&
|
||||
parts[1].charAt(0) >= 'A' && parts[1].charAt(0) <= 'Z') {
|
||||
// Represented internally as x$1 to x$65536
|
||||
// which is the maximum range of rows
|
||||
parts[0] = parts[0] + "$1";
|
||||
parts[1] = parts[1] + "$65536";
|
||||
}
|
||||
|
||||
_firstCell = new CellReference(parts[0]);
|
||||
|
||||
if(parts.length == 2) {
|
||||
_lastCell = new CellReference(parts[1]);
|
||||
_isSingleCell = false;
|
||||
} else {
|
||||
_lastCell = _firstCell;
|
||||
_isSingleCell = true;
|
||||
return;
|
||||
}
|
||||
if (parts.length != 2) {
|
||||
throw new IllegalArgumentException("Bad area ref '" + reference + "'");
|
||||
}
|
||||
|
||||
String part1 = parts[1];
|
||||
if (isPlainColumn(part0)) {
|
||||
if (!isPlainColumn(part1)) {
|
||||
throw new RuntimeException("Bad area ref '" + reference + "'");
|
||||
}
|
||||
// Special handling for whole-column references
|
||||
// Represented internally as x$1 to x$65536
|
||||
// which is the maximum range of rows
|
||||
|
||||
boolean firstIsAbs = CellReference.isPartAbsolute(part0);
|
||||
boolean lastIsAbs = CellReference.isPartAbsolute(part1);
|
||||
|
||||
int col0 = CellReference.convertColStringToIndex(part0);
|
||||
int col1 = CellReference.convertColStringToIndex(part1);
|
||||
|
||||
_firstCell = new CellReference(0, col0, true, firstIsAbs);
|
||||
_lastCell = new CellReference(0xFFFF, col1, true, lastIsAbs);
|
||||
_isSingleCell = false;
|
||||
// TODO - whole row refs
|
||||
} else {
|
||||
_firstCell = new CellReference(part0);
|
||||
_lastCell = new CellReference(part1);
|
||||
_isSingleCell = part0.equals(part1);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPlainColumn(String refPart) {
|
||||
for(int i=refPart.length()-1; i>=0; i--) {
|
||||
int ch = refPart.charAt(i);
|
||||
if (ch == '$' && i==0) {
|
||||
continue;
|
||||
}
|
||||
if (ch < 'A' || ch > 'Z') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,7 +79,7 @@ public final class CellReference {
|
||||
if (_isColAbs) {
|
||||
colRef=colRef.substring(1);
|
||||
}
|
||||
_colIndex = convertColStringToNum(colRef);
|
||||
_colIndex = convertColStringToIndex(colRef);
|
||||
|
||||
String rowRef=parts[2];
|
||||
if (rowRef.length() < 1) {
|
||||
@ -96,7 +96,7 @@ public final class CellReference {
|
||||
this(pRow, pCol, false, false);
|
||||
}
|
||||
public CellReference(int pRow, short pCol) {
|
||||
this(pRow, (int)pCol, false, false);
|
||||
this(pRow, pCol & 0xFFFF, false, false);
|
||||
}
|
||||
|
||||
public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
|
||||
@ -130,17 +130,31 @@ public final class CellReference {
|
||||
return _sheetName;
|
||||
}
|
||||
|
||||
public static boolean isPartAbsolute(String part) {
|
||||
return part.charAt(0) == ABSOLUTE_REFERENCE_MARKER;
|
||||
}
|
||||
/**
|
||||
* takes in a column reference portion of a CellRef and converts it from
|
||||
* ALPHA-26 number format to 0-based base 10.
|
||||
* 'A' -> 0
|
||||
* 'Z' -> 25
|
||||
* 'AA' -> 26
|
||||
* 'IV' -> 255
|
||||
* @return zero based column index
|
||||
*/
|
||||
private int convertColStringToNum(String ref) {
|
||||
int lastIx = ref.length()-1;
|
||||
int retval=0;
|
||||
int pos = 0;
|
||||
public static int convertColStringToIndex(String ref) {
|
||||
|
||||
for (int k = lastIx; k > -1; k--) {
|
||||
int pos = 0;
|
||||
int retval=0;
|
||||
for (int k = ref.length()-1; k >= 0; k--) {
|
||||
char thechar = ref.charAt(k);
|
||||
if (thechar == ABSOLUTE_REFERENCE_MARKER) {
|
||||
if (k != 0) {
|
||||
throw new IllegalArgumentException("Bad col ref format '"
|
||||
+ ref + "'");
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Character.getNumericValue() returns the values
|
||||
// 10-35 for the letter A-Z
|
||||
int shift = (int)Math.pow(26, pos);
|
||||
|
@ -40,4 +40,43 @@ public final class TestHSSFFormulaEvaluator extends TestCase {
|
||||
assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType());
|
||||
assertEquals(3.72, cv.getNumberValue(), 0.0);
|
||||
}
|
||||
|
||||
public void testFullColumnRefs() {
|
||||
HSSFWorkbook wb = new HSSFWorkbook();
|
||||
HSSFSheet sheet = wb.createSheet("Sheet1");
|
||||
HSSFRow row = sheet.createRow(0);
|
||||
HSSFCell cell0 = row.createCell(0);
|
||||
cell0.setCellFormula("sum(D:D)");
|
||||
HSSFCell cell1 = row.createCell(1);
|
||||
cell1.setCellFormula("sum(D:E)");
|
||||
|
||||
// some values in column D
|
||||
setValue(sheet, 1, 3, 5.0);
|
||||
setValue(sheet, 2, 3, 6.0);
|
||||
setValue(sheet, 5, 3, 7.0);
|
||||
setValue(sheet, 50, 3, 8.0);
|
||||
|
||||
// some values in column E
|
||||
setValue(sheet, 1, 4, 9.0);
|
||||
setValue(sheet, 2, 4, 10.0);
|
||||
setValue(sheet, 30000, 4, 11.0);
|
||||
|
||||
// some other values
|
||||
setValue(sheet, 1, 2, 100.0);
|
||||
setValue(sheet, 2, 5, 100.0);
|
||||
setValue(sheet, 3, 6, 100.0);
|
||||
|
||||
|
||||
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb);
|
||||
assertEquals(26.0, fe.evaluate(cell0).getNumberValue(), 0.0);
|
||||
assertEquals(56.0, fe.evaluate(cell1).getNumberValue(), 0.0);
|
||||
}
|
||||
|
||||
private static void setValue(HSSFSheet sheet, int rowIndex, int colIndex, double value) {
|
||||
HSSFRow row = sheet.getRow(rowIndex);
|
||||
if (row == null) {
|
||||
row = sheet.createRow(rowIndex);
|
||||
}
|
||||
row.createCell(colIndex).setCellValue(value);
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ public final class TestAreaReference extends TestCase {
|
||||
public void testReferenceWithSheet() {
|
||||
AreaReference ar;
|
||||
|
||||
ar = new AreaReference("Tabelle1!B5");
|
||||
ar = new AreaReference("Tabelle1!B5:B5");
|
||||
assertTrue(ar.isSingleCell());
|
||||
TestCellReference.confirmCell(ar.getFirstCell(), "Tabelle1", 4, 1, false, false, "Tabelle1!B5");
|
||||
|
||||
@ -113,11 +113,11 @@ public final class TestAreaReference extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testContiguousReferences() throws Exception {
|
||||
String refSimple = "$C$10";
|
||||
public void testContiguousReferences() {
|
||||
String refSimple = "$C$10:$C$10";
|
||||
String ref2D = "$C$10:$D$11";
|
||||
String refDCSimple = "$C$10,$D$12,$E$14";
|
||||
String refDC2D = "$C$10:$C$11,$D$12,$E$14:$E$20";
|
||||
String refDCSimple = "$C$10:$C$10,$D$12:$D$12,$E$14:$E$14";
|
||||
String refDC2D = "$C$10:$C$11,$D$12:$D$12,$E$14:$E$20";
|
||||
String refDC3D = "Tabelle1!$C$10:$C$14,Tabelle1!$D$10:$D$12";
|
||||
|
||||
// Check that we detect as contiguous properly
|
||||
@ -243,16 +243,16 @@ public final class TestAreaReference extends TestCase {
|
||||
private static void confirmResolveCellRef(HSSFWorkbook wb, CellReference cref) {
|
||||
HSSFSheet s = wb.getSheet(cref.getSheetName());
|
||||
HSSFRow r = s.getRow(cref.getRow());
|
||||
HSSFCell c = r.getCell(cref.getCol());
|
||||
HSSFCell c = r.getCell((int)cref.getCol());
|
||||
assertNotNull(c);
|
||||
}
|
||||
|
||||
public void testSpecialSheetNames() {
|
||||
AreaReference ar;
|
||||
ar = new AreaReference("'Sheet A'!A1");
|
||||
ar = new AreaReference("'Sheet A'!A1:A1");
|
||||
confirmAreaSheetName(ar, "Sheet A", "'Sheet A'!A1");
|
||||
|
||||
ar = new AreaReference("'Hey! Look Here!'!A1");
|
||||
ar = new AreaReference("'Hey! Look Here!'!A1:A1");
|
||||
confirmAreaSheetName(ar, "Hey! Look Here!", "'Hey! Look Here!'!A1");
|
||||
|
||||
ar = new AreaReference("'O''Toole'!A1:B2");
|
||||
@ -268,7 +268,24 @@ public final class TestAreaReference extends TestCase {
|
||||
assertEquals(expectedFullText, ar.formatAsString());
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
junit.textui.TestRunner.run(TestAreaReference.class);
|
||||
public void testWholeColumnRefs() {
|
||||
confirmWholeColumnRef("A:A", 0, 0, false, false);
|
||||
confirmWholeColumnRef("$C:D", 2, 3, true, false);
|
||||
confirmWholeColumnRef("AD:$AE", 29, 30, false, true);
|
||||
|
||||
}
|
||||
|
||||
private static void confirmWholeColumnRef(String ref, int firstCol, int lastCol, boolean firstIsAbs, boolean lastIsAbs) {
|
||||
AreaReference ar = new AreaReference(ref);
|
||||
confirmCell(ar.getFirstCell(), 0, firstCol, true, firstIsAbs);
|
||||
confirmCell(ar.getLastCell(), 0xFFFF, lastCol, true, lastIsAbs);
|
||||
}
|
||||
|
||||
private static void confirmCell(CellReference cell, int row, int col, boolean isRowAbs,
|
||||
boolean isColAbs) {
|
||||
assertEquals(row, cell.getRow());
|
||||
assertEquals(col, cell.getCol());
|
||||
assertEquals(isRowAbs, cell.isRowAbsolute());
|
||||
assertEquals(isColAbs, cell.isColAbsolute());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user