Fix for bug 46948 - Range operator can now take area-refs for operands
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@761023 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
fba33cf5a4
commit
0e7da90f77
@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
<!-- Don't forget to update status.xml too! -->
|
<!-- Don't forget to update status.xml too! -->
|
||||||
<release version="3.5-beta6" date="2009-??-??">
|
<release version="3.5-beta6" date="2009-??-??">
|
||||||
|
<action dev="POI-DEVELOPERS" type="fix">46948 - Fixed evaluation of range operator to allow for area-ref operands</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">46918 - Fixed ExtendedPivotTableViewFieldsRecord(SXVDEX) to allow shorter format</action>
|
<action dev="POI-DEVELOPERS" type="fix">46918 - Fixed ExtendedPivotTableViewFieldsRecord(SXVDEX) to allow shorter format</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">46898 - Fixed formula evaluator to not cache intermediate circular-reference error results</action>
|
<action dev="POI-DEVELOPERS" type="fix">46898 - Fixed formula evaluator to not cache intermediate circular-reference error results</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">46917 - Fixed PageItemRecord(SXPI) to allow multiple field infos</action>
|
<action dev="POI-DEVELOPERS" type="fix">46917 - Fixed PageItemRecord(SXPI) to allow multiple field infos</action>
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
<!-- Don't forget to update changes.xml too! -->
|
<!-- Don't forget to update changes.xml too! -->
|
||||||
<changes>
|
<changes>
|
||||||
<release version="3.5-beta6" date="2009-??-??">
|
<release version="3.5-beta6" date="2009-??-??">
|
||||||
|
<action dev="POI-DEVELOPERS" type="fix">46948 - Fixed evaluation of range operator to allow for area-ref operands</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">46918 - Fixed ExtendedPivotTableViewFieldsRecord(SXVDEX) to allow shorter format</action>
|
<action dev="POI-DEVELOPERS" type="fix">46918 - Fixed ExtendedPivotTableViewFieldsRecord(SXVDEX) to allow shorter format</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">46898 - Fixed formula evaluator to not cache intermediate circular-reference error results</action>
|
<action dev="POI-DEVELOPERS" type="fix">46898 - Fixed formula evaluator to not cache intermediate circular-reference error results</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">46917 - Fixed PageItemRecord(SXPI) to allow multiple field infos</action>
|
<action dev="POI-DEVELOPERS" type="fix">46917 - Fixed PageItemRecord(SXPI) to allow multiple field infos</action>
|
||||||
|
@ -36,25 +36,36 @@ public final class RangeEval implements OperationEval {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
RefEval reA = evaluateRef(args[0]);
|
AreaEval reA = evaluateRef(args[0]);
|
||||||
RefEval reB = evaluateRef(args[1]);
|
AreaEval reB = evaluateRef(args[1]);
|
||||||
return resolveRange(reA, reB);
|
return resolveRange(reA, reB);
|
||||||
} catch (EvaluationException e) {
|
} catch (EvaluationException e) {
|
||||||
return e.getErrorEval();
|
return e.getErrorEval();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AreaEval resolveRange(RefEval reA, RefEval reB) {
|
/**
|
||||||
|
* @return simple rectangular {@link AreaEval} which fully encloses both areas
|
||||||
|
* <tt>aeA</tt> and <tt>aeB</tt>
|
||||||
|
*/
|
||||||
|
private static AreaEval resolveRange(AreaEval aeA, AreaEval aeB) {
|
||||||
|
int aeAfr = aeA.getFirstRow();
|
||||||
|
int aeAfc = aeA.getFirstColumn();
|
||||||
|
|
||||||
int height = reB.getRow() - reA.getRow();
|
int top = Math.min(aeAfr, aeB.getFirstRow());
|
||||||
int width = reB.getColumn() - reA.getColumn();
|
int bottom = Math.max(aeA.getLastRow(), aeB.getLastRow());
|
||||||
|
int left = Math.min(aeAfc, aeB.getFirstColumn());
|
||||||
|
int right = Math.max(aeA.getLastColumn(), aeB.getLastColumn());
|
||||||
|
|
||||||
return reA.offset(0, height, 0, width);
|
return aeA.offset(top-aeAfr, bottom-aeAfr, left-aeAfc, right-aeAfc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RefEval evaluateRef(Eval arg) throws EvaluationException {
|
private static AreaEval evaluateRef(Eval arg) throws EvaluationException {
|
||||||
|
if (arg instanceof AreaEval) {
|
||||||
|
return (AreaEval) arg;
|
||||||
|
}
|
||||||
if (arg instanceof RefEval) {
|
if (arg instanceof RefEval) {
|
||||||
return (RefEval) arg;
|
return ((RefEval) arg).offset(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
if (arg instanceof ErrorEval) {
|
if (arg instanceof ErrorEval) {
|
||||||
throw new EvaluationException((ErrorEval)arg);
|
throw new EvaluationException((ErrorEval)arg);
|
||||||
|
@ -17,12 +17,28 @@
|
|||||||
|
|
||||||
package org.apache.poi.hssf.record.formula.eval;
|
package org.apache.poi.hssf.record.formula.eval;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
import junit.framework.AssertionFailedError;
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
|
||||||
import org.apache.poi.hssf.record.formula.AreaI;
|
import org.apache.poi.hssf.record.formula.AreaI;
|
||||||
|
import org.apache.poi.hssf.record.formula.AttrPtg;
|
||||||
|
import org.apache.poi.hssf.record.formula.FuncVarPtg;
|
||||||
|
import org.apache.poi.hssf.record.formula.IntPtg;
|
||||||
|
import org.apache.poi.hssf.record.formula.Ptg;
|
||||||
|
import org.apache.poi.hssf.record.formula.RangePtg;
|
||||||
|
import org.apache.poi.hssf.record.formula.RefPtg;
|
||||||
import org.apache.poi.hssf.record.formula.AreaI.OffsetArea;
|
import org.apache.poi.hssf.record.formula.AreaI.OffsetArea;
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFCell;
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFRow;
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||||
import org.apache.poi.hssf.util.AreaReference;
|
import org.apache.poi.hssf.util.AreaReference;
|
||||||
import org.apache.poi.hssf.util.CellReference;
|
import org.apache.poi.hssf.util.CellReference;
|
||||||
|
import org.apache.poi.ss.formula.FormulaParser;
|
||||||
import junit.framework.TestCase;
|
import org.apache.poi.ss.usermodel.CellValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for unary plus operator evaluator.
|
* Test for unary plus operator evaluator.
|
||||||
@ -30,22 +46,22 @@ import junit.framework.TestCase;
|
|||||||
* @author Josh Micich
|
* @author Josh Micich
|
||||||
*/
|
*/
|
||||||
public final class TestRangeEval extends TestCase {
|
public final class TestRangeEval extends TestCase {
|
||||||
|
|
||||||
public void testPermutations() {
|
public void testPermutations() {
|
||||||
|
|
||||||
confirm("B3", "D7", "B3:D7");
|
confirm("B3", "D7", "B3:D7");
|
||||||
confirm("B1", "B1", "B1:B1");
|
confirm("B1", "B1", "B1:B1");
|
||||||
|
|
||||||
confirm("B7", "D3", "B3:D7");
|
confirm("B7", "D3", "B3:D7");
|
||||||
confirm("D3", "B7", "B3:D7");
|
confirm("D3", "B7", "B3:D7");
|
||||||
confirm("D7", "B3", "B3:D7");
|
confirm("D7", "B3", "B3:D7");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void confirm(String refA, String refB, String expectedAreaRef) {
|
private static void confirm(String refA, String refB, String expectedAreaRef) {
|
||||||
|
|
||||||
Eval[] args = {
|
Eval[] args = {
|
||||||
createRefEval(refA),
|
createRefEval(refA),
|
||||||
createRefEval(refB),
|
createRefEval(refB),
|
||||||
};
|
};
|
||||||
AreaReference ar = new AreaReference(expectedAreaRef);
|
AreaReference ar = new AreaReference(expectedAreaRef);
|
||||||
Eval result = RangeEval.instance.evaluate(args, 0, (short)0);
|
Eval result = RangeEval.instance.evaluate(args, 0, (short)0);
|
||||||
@ -60,7 +76,7 @@ public final class TestRangeEval extends TestCase {
|
|||||||
private static Eval createRefEval(String refStr) {
|
private static Eval createRefEval(String refStr) {
|
||||||
CellReference cr = new CellReference(refStr);
|
CellReference cr = new CellReference(refStr);
|
||||||
return new MockRefEval(cr.getRow(), cr.getCol());
|
return new MockRefEval(cr.getRow(), cr.getCol());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class MockRefEval extends RefEvalBase {
|
private static final class MockRefEval extends RefEvalBase {
|
||||||
@ -89,7 +105,91 @@ public final class TestRangeEval extends TestCase {
|
|||||||
}
|
}
|
||||||
public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx,
|
public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx,
|
||||||
int relLastColIx) {
|
int relLastColIx) {
|
||||||
throw new RuntimeException("not expected to be called during this test");
|
AreaI area = new OffsetArea(getFirstRow(), getFirstColumn(),
|
||||||
|
relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);
|
||||||
|
|
||||||
|
return new MockAreaEval(area);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testRangeUsingOffsetFunc_bug46948() {
|
||||||
|
HSSFWorkbook wb = new HSSFWorkbook();
|
||||||
|
HSSFRow row = wb.createSheet("Sheet1").createRow(0);
|
||||||
|
HSSFCell cellA1 = row.createCell(0);
|
||||||
|
HSSFCell cellB1 = row.createCell(1);
|
||||||
|
row.createCell(2).setCellValue(5.0); // C1
|
||||||
|
row.createCell(3).setCellValue(7.0); // D1
|
||||||
|
row.createCell(4).setCellValue(9.0); // E1
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
cellA1.setCellFormula("SUM(C1:OFFSET(C1,0,B1))");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// TODO fix formula parser to handle ':' as a proper operator
|
||||||
|
if (!e.getClass().getName().startsWith(FormulaParser.class.getName())) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
// FormulaParseException is expected until the parser is fixed up
|
||||||
|
// Poke the formula in directly:
|
||||||
|
pokeInOffsetFormula(cellA1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cellB1.setCellValue(1.0); // range will be C1:D1
|
||||||
|
|
||||||
|
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
|
||||||
|
CellValue cv;
|
||||||
|
try {
|
||||||
|
cv = fe.evaluate(cellA1);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
if (e.getMessage().equals("Unexpected ref arg class (org.apache.poi.ss.formula.LazyAreaEval)")) {
|
||||||
|
throw new AssertionFailedError("Identified bug 46948");
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(12.0, cv.getNumberValue(), 0.0);
|
||||||
|
|
||||||
|
cellB1.setCellValue(2.0); // range will be C1:E1
|
||||||
|
fe.notifyUpdateCell(cellB1);
|
||||||
|
cv = fe.evaluate(cellA1);
|
||||||
|
assertEquals(21.0, cv.getNumberValue(), 0.0);
|
||||||
|
|
||||||
|
cellB1.setCellValue(0.0); // range will be C1:C1
|
||||||
|
fe.notifyUpdateCell(cellB1);
|
||||||
|
cv = fe.evaluate(cellA1);
|
||||||
|
assertEquals(5.0, cv.getNumberValue(), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directly sets the formula "SUM(C1:OFFSET(C1,0,B1))" in the specified cell.
|
||||||
|
* This hack can be removed when the formula parser can handle functions as
|
||||||
|
* operands to the range (:) operator.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static void pokeInOffsetFormula(HSSFCell cell) {
|
||||||
|
cell.setCellFormula("1");
|
||||||
|
FormulaRecordAggregate fr;
|
||||||
|
try {
|
||||||
|
Field field = HSSFCell.class.getDeclaredField("_record");
|
||||||
|
field.setAccessible(true);
|
||||||
|
fr = (FormulaRecordAggregate) field.get(cell);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
Ptg[] ptgs = {
|
||||||
|
new RefPtg("C1"),
|
||||||
|
new RefPtg("C1"),
|
||||||
|
new IntPtg(0),
|
||||||
|
new RefPtg("B1"),
|
||||||
|
new FuncVarPtg("OFFSET", (byte)3),
|
||||||
|
RangePtg.instance,
|
||||||
|
AttrPtg.SUM,
|
||||||
|
};
|
||||||
|
fr.setParsedExpression(ptgs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user