Patch from Josh from bug #44371 - support for OFFSET function, and various tweaks to the formula evaluator to support this

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@629821 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2008-02-21 15:08:44 +00:00
parent acd787dcb8
commit 173f63cd0a
13 changed files with 674 additions and 73 deletions

View File

@ -36,6 +36,7 @@
<!-- Don't forget to update status.xml too! --> <!-- Don't forget to update status.xml too! -->
<release version="3.1-beta1" date="2008-??-??"> <release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">44371 - Support for the Offset function</action>
<action dev="POI-DEVELOPERS" type="fix">38921 - Have HSSFPalette.findSimilar() work properly</action> <action dev="POI-DEVELOPERS" type="fix">38921 - Have HSSFPalette.findSimilar() work properly</action>
<action dev="POI-DEVELOPERS" type="fix">44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows</action> <action dev="POI-DEVELOPERS" type="fix">44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows</action>
<action dev="POI-DEVELOPERS" type="fix">44403 - Further support for unusual, but valid, arguments to the Mid function</action> <action dev="POI-DEVELOPERS" type="fix">44403 - Further support for unusual, but valid, arguments to the Mid function</action>

View File

@ -33,6 +33,7 @@
<!-- Don't forget to update changes.xml too! --> <!-- Don't forget to update changes.xml too! -->
<changes> <changes>
<release version="3.1-beta1" date="2008-??-??"> <release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">44371 - Support for the Offset function</action>
<action dev="POI-DEVELOPERS" type="fix">38921 - Have HSSFPalette.findSimilar() work properly</action> <action dev="POI-DEVELOPERS" type="fix">38921 - Have HSSFPalette.findSimilar() work properly</action>
<action dev="POI-DEVELOPERS" type="fix">44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows</action> <action dev="POI-DEVELOPERS" type="fix">44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows</action>
<action dev="POI-DEVELOPERS" type="fix">44403 - Further support for unusual, but valid, arguments to the Mid function</action> <action dev="POI-DEVELOPERS" type="fix">44403 - Further support for unusual, but valid, arguments to the Mid function</action>

View File

@ -70,7 +70,21 @@ public class Area3DPtg extends Ptg implements AreaI
field_5_last_column = in.readShort(); field_5_last_column = in.readShort();
} }
public String toString() public Area3DPtg(short firstRow, short lastRow, short firstColumn, short lastColumn,
boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative,
short externalSheetIndex) {
setFirstRow(firstRow);
setLastRow(lastRow);
setFirstColumn(firstColumn);
setLastColumn(lastColumn);
setFirstRowRelative(firstRowRelative);
setLastRowRelative(lastRowRelative);
setFirstColRelative(firstColRelative);
setLastColRelative(lastColRelative);
setExternSheetIndex(externalSheetIndex);
}
public String toString()
{ {
StringBuffer buffer = new StringBuffer(); StringBuffer buffer = new StringBuffer();

View File

@ -27,7 +27,7 @@ import org.apache.poi.hssf.record.formula.Ptg;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt; * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
* *
*/ */
public class Area3DEval implements AreaEval { public final class Area3DEval implements AreaEval {
private Area3DPtg delegate; private Area3DPtg delegate;
@ -90,4 +90,8 @@ public class Area3DEval implements AreaEval {
return delegate.getFirstRow() == delegate.getLastRow(); return delegate.getFirstRow() == delegate.getLastRow();
} }
public int getExternSheetIndex() {
return delegate.getExternSheetIndex();
}
} }

View File

@ -20,6 +20,9 @@
*/ */
package org.apache.poi.hssf.record.formula.eval; package org.apache.poi.hssf.record.formula.eval;
import java.util.HashMap;
import java.util.Map;
import org.apache.poi.hssf.record.formula.functions.*; import org.apache.poi.hssf.record.formula.functions.*;
/** /**
@ -27,12 +30,47 @@ import org.apache.poi.hssf.record.formula.functions.*;
* *
*/ */
public abstract class FunctionEval implements OperationEval { public abstract class FunctionEval implements OperationEval {
/**
* Some function IDs that require special treatment
*/
private static final class FunctionID {
/** 78 */
public static final int OFFSET = 78;
/** 148 */
public static final int INDIRECT = 148;
}
// convenient access to namespace
private static final FunctionID ID = null;
protected static Function[] functions = produceFunctions(); protected static Function[] functions = produceFunctions();
private static Map freeRefFunctionsByIdMap;
static {
Map m = new HashMap();
addMapping(m, ID.OFFSET, new Offset());
addMapping(m, ID.INDIRECT, new Indirect());
freeRefFunctionsByIdMap = m;
}
private static void addMapping(Map m, int offset, FreeRefFunction frf) {
m.put(createFRFKey(offset), frf);
}
private static Integer createFRFKey(int functionIndex) {
return new Integer(functionIndex);
}
public Function getFunction() { public Function getFunction() {
short fidx = getFunctionIndex(); short fidx = getFunctionIndex();
return functions[fidx]; return functions[fidx];
} }
public boolean isFreeRefFunction() {
return freeRefFunctionsByIdMap.containsKey(createFRFKey(getFunctionIndex()));
}
public FreeRefFunction getFreeRefFunction() {
return (FreeRefFunction) freeRefFunctionsByIdMap.get(createFRFKey(getFunctionIndex()));
}
public abstract short getFunctionIndex(); public abstract short getFunctionIndex();
@ -115,7 +153,7 @@ public abstract class FunctionEval implements OperationEval {
retval[75] = new Areas(); // AREAS retval[75] = new Areas(); // AREAS
retval[76] = new Rows(); // ROWS retval[76] = new Rows(); // ROWS
retval[77] = new Columns(); // COLUMNS retval[77] = new Columns(); // COLUMNS
retval[78] = new Offset(); // OFFSET retval[ID.OFFSET] = null; // Offset.evaluate has a different signature
retval[79] = new Absref(); // ABSREF retval[79] = new Absref(); // ABSREF
retval[80] = new Relref(); // RELREF retval[80] = new Relref(); // RELREF
retval[81] = new Argument(); // ARGUMENT retval[81] = new Argument(); // ARGUMENT
@ -185,7 +223,7 @@ public abstract class FunctionEval implements OperationEval {
retval[145] = new NotImplementedFunction(); // GETDEF retval[145] = new NotImplementedFunction(); // GETDEF
retval[146] = new Reftext(); // REFTEXT retval[146] = new Reftext(); // REFTEXT
retval[147] = new Textref(); // TEXTREF retval[147] = new Textref(); // TEXTREF
retval[148] = new Indirect(); // INDIRECT retval[ID.INDIRECT] = null; // Indirect.evaluate has different signature
retval[149] = new NotImplementedFunction(); // REGISTER retval[149] = new NotImplementedFunction(); // REGISTER
retval[150] = new Call(); // CALL retval[150] = new Call(); // CALL
retval[151] = new NotImplementedFunction(); // ADDBAR retval[151] = new NotImplementedFunction(); // ADDBAR

View File

@ -27,7 +27,7 @@ import org.apache.poi.hssf.record.formula.Ref3DPtg;
* @author Amol S. Deshmukh * @author Amol S. Deshmukh
* *
*/ */
public class Ref3DEval implements RefEval { public final class Ref3DEval implements RefEval {
private ValueEval value; private ValueEval value;
@ -57,4 +57,8 @@ public class Ref3DEval implements RefEval {
return evaluated; return evaluated;
} }
public int getExternSheetIndex() {
return delegate.getExternSheetIndex();
}
} }

View File

@ -0,0 +1,57 @@
/* ====================================================================
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.Eval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
* For most Excel functions, involving references ((cell, area), (2d, 3d)), the references are
* passed in as arguments, and the exact location remains fixed. However, a select few Excel
* functions have the ability to access cells that were not part of any reference passed as an
* argument.<br/>
* Two important functions with this feature are <b>INDIRECT</b> and <b>OFFSET</b><p/>
*
* In POI, the <tt>HSSFFormulaEvaluator</tt> evaluates every cell in each reference argument before
* calling the function. This means that functions using fixed references do not need access to
* the rest of the workbook to execute. Hence the <tt>evaluate()</tt> method on the common
* interface <tt>Function</tt> does not take a workbook parameter.<p>
*
* This interface recognises the requirement of some functions to freely create and evaluate
* references beyond those passed in as arguments.
*
* @author Josh Micich
*/
public interface FreeRefFunction {
/**
*
* @param args the pre-evaluated arguments for this function. args is never <code>null</code>,
* nor are any of its elements.
* @param srcCellRow zero based row index of the cell containing the currently evaluating formula
* @param srcCellCol zero based column index of the cell containing the currently evaluating formula
* @param workbook is the workbook containing the formula/cell being evaluated
* @param sheet is the sheet containing the formula/cell being evaluated
* @return never <code>null</code>. Possibly an instance of <tt>ErrorEval</tt> in the case of
* a specified Excel error (Exceptions are never thrown to represent Excel errors).
*
*/
ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet);
}

View File

@ -14,12 +14,36 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
public class Indirect extends NotImplementedFunction { 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.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
* Implementation for Excel function INDIRECT<p/>
*
* INDIRECT() returns the cell or area reference denoted by the text argument.<p/>
*
* <b>Syntax</b>:</br>
* <b>INDIRECT</b>(<b>ref_text</b>,isA1Style)<p/>
*
* <b>ref_text</b> a string representation of the desired reference as it would normally be written
* in a cell formula.<br/>
* <b>isA1Style</b> (default TRUE) specifies whether the ref_text should be interpreted as A1-style
* or R1C1-style.
*
*
* @author Josh Micich
*/
public final class Indirect implements FreeRefFunction {
public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
// TODO - implement INDIRECT()
return ErrorEval.FUNCTION_NOT_IMPLEMENTED;
}
} }

View File

@ -14,12 +14,349 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
public class Offset extends NotImplementedFunction { import org.apache.poi.hssf.record.formula.Area3DPtg;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.eval.Area3DEval;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
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.NumericValueEval;
import org.apache.poi.hssf.record.formula.eval.Ref3DEval;
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.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
* Implementation for Excel function OFFSET()<p/>
*
* OFFSET returns an area reference that is a specified number of rows and columns from a
* reference cell or area.<p/>
*
* <b>Syntax</b>:<br/>
* <b>OFFSET</b>(<b>reference</b>, <b>rows</b>, <b>cols</b>, height, width)<p/>
* <b>reference</b> is the base reference.<br/>
* <b>rows</b> is the number of rows up or down from the base reference.<br/>
* <b>cols</b> is the number of columns left or right from the base reference.<br/>
* <b>height</b> (default same height as base reference) is the row count for the returned area reference.<br/>
* <b>width</b> (default same width as base reference) is the column count for the returned area reference.<br/>
*
* @author Josh Micich
*/
public final class Offset implements FreeRefFunction {
// These values are specific to BIFF8
private static final int LAST_VALID_ROW_INDEX = 0xFFFF;
private static final int LAST_VALID_COLUMN_INDEX = 0xFF;
/**
* Exceptions are used within this class to help simplify flow control when error conditions
* are encountered
*/
private static final class EvalEx extends Exception {
private final ErrorEval _error;
public EvalEx(ErrorEval error) {
_error = error;
}
public ErrorEval getError() {
return _error;
}
}
/**
* A one dimensional base + offset. Represents either a row range or a column range.
* Two instances of this class together specify an area range.
*/
/* package */ static final class LinearOffsetRange {
private final int _offset;
private final int _length;
public LinearOffsetRange(int offset, int length) {
if(length == 0) {
// handled that condition much earlier
throw new RuntimeException("length may not be zero");
}
_offset = offset;
_length = length;
}
public short getFirstIndex() {
return (short) _offset;
}
public short getLastIndex() {
return (short) (_offset + _length - 1);
}
/**
* Moves the range by the specified translation amount.<p/>
*
* This method also 'normalises' the range: Excel specifies that the width and height
* parameters (length field here) cannot be negative. However, OFFSET() does produce
* sensible results in these cases. That behavior is replicated here. <p/>
*
* @param translationAmount may be zero negative or positive
*
* @return the equivalent <tt>LinearOffsetRange</tt> with a positive length, moved by the
* specified translationAmount.
*/
public LinearOffsetRange normaliseAndTranslate(int translationAmount) {
if (_length > 0) {
if(translationAmount == 0) {
return this;
}
return new LinearOffsetRange(translationAmount + _offset, _length);
}
return new LinearOffsetRange(translationAmount + _offset + _length + 1, -_length);
}
public boolean isOutOfBounds(int lowValidIx, int highValidIx) {
if(_offset < lowValidIx) {
return true;
}
if(getLastIndex() > highValidIx) {
return true;
}
return false;
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(_offset).append("...").append(getLastIndex());
sb.append("]");
return sb.toString();
}
}
/**
* Encapsulates either an area or cell reference which may be 2d or 3d.
*/
private static final class BaseRef {
private static final int INVALID_SHEET_INDEX = -1;
private final int _firstRowIndex;
private final int _firstColumnIndex;
private final int _width;
private final int _height;
private final int _externalSheetIndex;
public BaseRef(RefEval re) {
_firstRowIndex = re.getRow();
_firstColumnIndex = re.getColumn();
_height = 1;
_width = 1;
if (re instanceof Ref3DEval) {
Ref3DEval r3e = (Ref3DEval) re;
_externalSheetIndex = r3e.getExternSheetIndex();
} else {
_externalSheetIndex = INVALID_SHEET_INDEX;
}
}
public BaseRef(AreaEval ae) {
_firstRowIndex = ae.getFirstRow();
_firstColumnIndex = ae.getFirstColumn();
_height = ae.getLastRow() - ae.getFirstRow() + 1;
_width = ae.getLastColumn() - ae.getFirstColumn() + 1;
if (ae instanceof Area3DEval) {
Area3DEval a3e = (Area3DEval) ae;
_externalSheetIndex = a3e.getExternSheetIndex();
} else {
_externalSheetIndex = INVALID_SHEET_INDEX;
}
}
public int getWidth() {
return _width;
}
public int getHeight() {
return _height;
}
public int getFirstRowIndex() {
return _firstRowIndex;
}
public int getFirstColumnIndex() {
return _firstColumnIndex;
}
public boolean isIs3d() {
return _externalSheetIndex > 0;
}
public short getExternalSheetIndex() {
if(_externalSheetIndex < 0) {
throw new IllegalStateException("external sheet index only available for 3d refs");
}
return (short) _externalSheetIndex;
}
}
public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
if(args.length < 3 || args.length > 5) {
return ErrorEval.VALUE_INVALID;
}
try {
BaseRef baseRef = evaluateBaseRef(args[0]);
int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol);
int columnOffset = evaluateIntArg(args[2], srcCellRow, srcCellCol);
int height = baseRef.getHeight();
int width = baseRef.getWidth();
switch(args.length) {
case 5:
width = evaluateIntArg(args[4], srcCellRow, srcCellCol);
case 4:
height = evaluateIntArg(args[3], srcCellRow, srcCellCol);
}
// Zero height or width raises #REF! error
if(height == 0 || width == 0) {
return ErrorEval.REF_INVALID;
}
LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height);
LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width);
return createOffset(baseRef, rowOffsetRange, colOffsetRange, workbook, sheet);
} catch (EvalEx e) {
return e.getError();
}
}
private static AreaEval createOffset(BaseRef baseRef,
LinearOffsetRange rowOffsetRange, LinearOffsetRange colOffsetRange,
HSSFWorkbook workbook, HSSFSheet sheet) throws EvalEx {
LinearOffsetRange rows = rowOffsetRange.normaliseAndTranslate(baseRef.getFirstRowIndex());
LinearOffsetRange cols = colOffsetRange.normaliseAndTranslate(baseRef.getFirstColumnIndex());
if(rows.isOutOfBounds(0, LAST_VALID_ROW_INDEX)) {
throw new EvalEx(ErrorEval.REF_INVALID);
}
if(cols.isOutOfBounds(0, LAST_VALID_COLUMN_INDEX)) {
throw new EvalEx(ErrorEval.REF_INVALID);
}
if(baseRef.isIs3d()) {
Area3DPtg a3dp = new Area3DPtg(rows.getFirstIndex(), rows.getLastIndex(),
cols.getFirstIndex(), cols.getLastIndex(),
false, false, false, false,
baseRef.getExternalSheetIndex());
return HSSFFormulaEvaluator.evaluateArea3dPtg(workbook, a3dp);
}
AreaPtg ap = new AreaPtg(rows.getFirstIndex(), rows.getLastIndex(),
cols.getFirstIndex(), cols.getLastIndex(),
false, false, false, false);
return HSSFFormulaEvaluator.evaluateAreaPtg(sheet, workbook, ap);
}
private static BaseRef evaluateBaseRef(Eval eval) throws EvalEx {
if(eval instanceof RefEval) {
return new BaseRef((RefEval)eval);
}
if(eval instanceof AreaEval) {
return new BaseRef((AreaEval)eval);
}
if (eval instanceof ErrorEval) {
throw new EvalEx((ErrorEval) eval);
}
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
/**
* OFFSET's numeric arguments (2..5) have similar processing rules
*/
private static int evaluateIntArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
double d = evaluateDoubleArg(eval, srcCellRow, srcCellCol);
return convertDoubleToInt(d);
}
/**
* Fractional values are silently truncated by Excel.
* Truncation is toward negative infinity.
*/
/* package */ static int convertDoubleToInt(double d) {
// Note - the standard java type conversion from double to int truncates toward zero.
// but Math.floor() truncates toward negative infinity
return (int)Math.floor(d);
}
private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
ValueEval ve = evaluateSingleValue(eval, srcCellRow, srcCellCol);
if (ve instanceof NumericValueEval) {
return ((NumericValueEval) ve).getNumberValue();
}
if (ve instanceof StringEval) {
StringEval se = (StringEval) ve;
Double d = parseDouble(se.getStringValue());
if(d == null) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
return d.doubleValue();
}
if (ve instanceof BoolEval) {
// in the context of OFFSET, booleans resolve to 0 and 1.
if(((BoolEval) ve).getBooleanValue()) {
return 1;
}
return 0;
}
throw new RuntimeException("Unexpected eval type (" + ve.getClass().getName() + ")");
}
private static Double parseDouble(String s) {
// TODO - find a home for this method
// TODO - support various number formats: sign char, dollars, commas
// OFFSET and COUNTIF seem to handle these
return Countif.parseDouble(s);
}
private static ValueEval evaluateSingleValue(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
if(eval instanceof RefEval) {
return ((RefEval)eval).getInnerValueEval();
}
if(eval instanceof AreaEval) {
return chooseSingleElementFromArea((AreaEval)eval, srcCellRow, srcCellCol);
}
if (eval instanceof ValueEval) {
return (ValueEval) eval;
}
throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
}
// TODO - this code seems to get repeated a bit
private static ValueEval chooseSingleElementFromArea(AreaEval ae, int srcCellRow, short srcCellCol) throws EvalEx {
if (ae.isColumn()) {
if (ae.isRow()) {
return ae.getValues()[0];
}
if (!ae.containsRow(srcCellRow)) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
return ae.getValueAt(srcCellRow, ae.getFirstColumn());
}
if (!ae.isRow()) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
if (!ae.containsColumn(srcCellCol)) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
return ae.getValueAt(ae.getFirstRow(), srcCellCol);
}
} }

View File

@ -25,7 +25,7 @@ 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.RefEval;
/** /**
* Implementation for Excel COLUMNS function. * Implementation for Excel ROWS function.
* *
* @author Josh Micich * @author Josh Micich
*/ */

View File

@ -74,6 +74,7 @@ import org.apache.poi.hssf.record.formula.eval.EqualEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval; 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.Eval;
import org.apache.poi.hssf.record.formula.eval.FuncVarEval; import org.apache.poi.hssf.record.formula.eval.FuncVarEval;
import org.apache.poi.hssf.record.formula.eval.FunctionEval;
import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval; import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval;
import org.apache.poi.hssf.record.formula.eval.GreaterThanEval; import org.apache.poi.hssf.record.formula.eval.GreaterThanEval;
import org.apache.poi.hssf.record.formula.eval.LessEqualEval; import org.apache.poi.hssf.record.formula.eval.LessEqualEval;
@ -91,7 +92,6 @@ import org.apache.poi.hssf.record.formula.eval.SubtractEval;
import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval;
import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFSheet;
/** /**
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt; * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
@ -379,7 +379,7 @@ public class HSSFFormulaEvaluator {
Stack stack = new Stack(); Stack stack = new Stack();
for (int i = 0, iSize = ptgs.length; i < iSize; i++) { for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
// since we dont know how to handle these yet :( // since we don't know how to handle these yet :(
if (ptgs[i] instanceof ControlPtg) { continue; } if (ptgs[i] instanceof ControlPtg) { continue; }
if (ptgs[i] instanceof MemErrPtg) { continue; } if (ptgs[i] instanceof MemErrPtg) { continue; }
if (ptgs[i] instanceof MissingArgPtg) { continue; } if (ptgs[i] instanceof MissingArgPtg) { continue; }
@ -405,7 +405,7 @@ public class HSSFFormulaEvaluator {
Eval p = (Eval) stack.pop(); Eval p = (Eval) stack.pop();
ops[j] = p; ops[j] = p;
} }
Eval opresult = operation.evaluate(ops, srcRowNum, srcColNum); Eval opresult = invokeOperation(operation, ops, srcRowNum, srcColNum, workbook, sheet);
stack.push(opresult); stack.push(opresult);
} }
else if (ptgs[i] instanceof ReferencePtg) { else if (ptgs[i] instanceof ReferencePtg) {
@ -428,56 +428,12 @@ public class HSSFFormulaEvaluator {
} }
else if (ptgs[i] instanceof AreaPtg) { else if (ptgs[i] instanceof AreaPtg) {
AreaPtg ap = (AreaPtg) ptgs[i]; AreaPtg ap = (AreaPtg) ptgs[i];
short row0 = ap.getFirstRow(); AreaEval ae = evaluateAreaPtg(sheet, workbook, ap);
short col0 = ap.getFirstColumn();
short row1 = ap.getLastRow();
short col1 = ap.getLastColumn();
// If the last row is -1, then the
// reference is for the rest of the column
// (eg C:C)
// TODO: Handle whole column ranges properly
if(row1 == -1 && row0 >= 0) {
row1 = (short)sheet.getLastRowNum();
}
ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
for (short x = row0; sheet != null && x < row1 + 1; x++) {
HSSFRow row = sheet.getRow(x);
for (short y = col0; row != null && y < col1 + 1; y++) {
values[(x - row0) * (col1 - col0 + 1) + (y - col0)] =
getEvalForCell(row.getCell(y), row, sheet, workbook);
}
}
AreaEval ae = new Area2DEval(ap, values);
stack.push(ae); stack.push(ae);
} }
else if (ptgs[i] instanceof Area3DPtg) { else if (ptgs[i] instanceof Area3DPtg) {
Area3DPtg a3dp = (Area3DPtg) ptgs[i]; Area3DPtg a3dp = (Area3DPtg) ptgs[i];
short row0 = a3dp.getFirstRow(); AreaEval ae = evaluateArea3dPtg(workbook, a3dp);
short col0 = a3dp.getFirstColumn();
short row1 = a3dp.getLastRow();
short col1 = a3dp.getLastColumn();
Workbook wb = workbook.getWorkbook();
HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(a3dp.getExternSheetIndex()));
// If the last row is -1, then the
// reference is for the rest of the column
// (eg C:C)
// TODO: Handle whole column ranges properly
if(row1 == -1 && row0 >= 0) {
row1 = (short)xsheet.getLastRowNum();
}
ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
for (short x = row0; xsheet != null && x < row1 + 1; x++) {
HSSFRow row = xsheet.getRow(x);
for (short y = col0; row != null && y < col1 + 1; y++) {
values[(x - row0) * (col1 - col0 + 1) + (y - col0)] =
getEvalForCell(row.getCell(y), row, xsheet, workbook);
}
}
AreaEval ae = new Area3DEval(a3dp, values);
stack.push(ae); stack.push(ae);
} }
else { else {
@ -485,23 +441,95 @@ public class HSSFFormulaEvaluator {
stack.push(ptgEval); stack.push(ptgEval);
} }
} }
ValueEval value = ((ValueEval) stack.pop()); ValueEval value = ((ValueEval) stack.pop());
if (value instanceof RefEval) { if (value instanceof RefEval) {
RefEval rv = (RefEval) value; RefEval rv = (RefEval) value;
value = rv.getInnerValueEval(); return rv.getInnerValueEval();
} }
else if (value instanceof AreaEval) { if (value instanceof AreaEval) {
AreaEval ae = (AreaEval) value; AreaEval ae = (AreaEval) value;
if (ae.isRow()) if (ae.isRow()) {
value = ae.getValueAt(ae.getFirstRow(), srcColNum); if(ae.isColumn()) {
else if (ae.isColumn()) return ae.getValues()[0];
value = ae.getValueAt(srcRowNum, ae.getFirstColumn()); }
else return ae.getValueAt(ae.getFirstRow(), srcColNum);
value = ErrorEval.VALUE_INVALID; }
if (ae.isColumn()) {
return ae.getValueAt(srcRowNum, ae.getFirstColumn());
}
return ErrorEval.VALUE_INVALID;
} }
return value; return value;
} }
private static Eval invokeOperation(OperationEval operation, Eval[] ops, int srcRowNum, short srcColNum,
HSSFWorkbook workbook, HSSFSheet sheet) {
if(operation instanceof FunctionEval) {
FunctionEval fe = (FunctionEval) operation;
if(fe.isFreeRefFunction()) {
return fe.getFreeRefFunction().evaluate(ops, srcRowNum, srcColNum, workbook, sheet);
}
}
return operation.evaluate(ops, srcRowNum, srcColNum);
}
public static AreaEval evaluateAreaPtg(HSSFSheet sheet, HSSFWorkbook workbook, AreaPtg ap) {
short row0 = ap.getFirstRow();
short col0 = ap.getFirstColumn();
short row1 = ap.getLastRow();
short col1 = ap.getLastColumn();
// If the last row is -1, then the
// reference is for the rest of the column
// (eg C:C)
// TODO: Handle whole column ranges properly
if(row1 == -1 && row0 >= 0) {
row1 = (short)sheet.getLastRowNum();
}
ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
for (short x = row0; sheet != null && x < row1 + 1; x++) {
HSSFRow row = sheet.getRow(x);
for (short y = col0; row != null && y < col1 + 1; y++) {
values[(x - row0) * (col1 - col0 + 1) + (y - col0)] =
getEvalForCell(row.getCell(y), row, sheet, workbook);
}
}
AreaEval ae = new Area2DEval(ap, values);
return ae;
}
public static AreaEval evaluateArea3dPtg(HSSFWorkbook workbook, Area3DPtg a3dp) {
short row0 = a3dp.getFirstRow();
short col0 = a3dp.getFirstColumn();
short row1 = a3dp.getLastRow();
short col1 = a3dp.getLastColumn();
Workbook wb = workbook.getWorkbook();
HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(a3dp.getExternSheetIndex()));
// If the last row is -1, then the
// reference is for the rest of the column
// (eg C:C)
// TODO: Handle whole column ranges properly
if(row1 == -1 && row0 >= 0) {
row1 = (short)xsheet.getLastRowNum();
}
ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
for (short x = row0; xsheet != null && x < row1 + 1; x++) {
HSSFRow row = xsheet.getRow(x);
for (short y = col0; row != null && y < col1 + 1; y++) {
values[(x - row0) * (col1 - col0 + 1) + (y - col0)] =
getEvalForCell(row.getCell(y), row, xsheet, workbook);
}
}
AreaEval ae = new Area3DEval(a3dp, values);
return ae;
}
/** /**
* returns the OperationEval concrete impl instance corresponding * returns the OperationEval concrete impl instance corresponding
* to the suplied operationPtg * to the suplied operationPtg

View File

@ -38,6 +38,7 @@ public final class AllIndividualFunctionEvaluationTests {
result.addTestSuite(TestMid.class); result.addTestSuite(TestMid.class);
result.addTestSuite(TestMathX.class); result.addTestSuite(TestMathX.class);
result.addTestSuite(TestMatch.class); result.addTestSuite(TestMatch.class);
result.addTestSuite(TestOffset.class);
result.addTestSuite(TestRowCol.class); result.addTestSuite(TestRowCol.class);
result.addTestSuite(TestSumproduct.class); result.addTestSuite(TestSumproduct.class);
result.addTestSuite(TestStatsLib.class); result.addTestSuite(TestStatsLib.class);

View File

@ -0,0 +1,92 @@
/* ====================================================================
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 junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.functions.Offset.LinearOffsetRange;
/**
* Tests for OFFSET function implementation
*
* @author Josh Micich
*/
public final class TestOffset extends TestCase {
private static void confirmDoubleConvert(double doubleVal, int expected) {
assertEquals(expected, Offset.convertDoubleToInt(doubleVal));
}
/**
* Excel's double to int conversion (for function 'OFFSET()') behaves more like Math.floor().
* Note - negative values are not symmetrical
*/
public void testDoubleConversion() {
confirmDoubleConvert(100.09, 100);
confirmDoubleConvert(100.01, 100);
confirmDoubleConvert(100.00, 100);
confirmDoubleConvert(99.99, 99);
confirmDoubleConvert(+2.01, +2);
confirmDoubleConvert(+2.00, +2);
confirmDoubleConvert(+1.99, +1);
confirmDoubleConvert(+1.01, +1);
confirmDoubleConvert(+1.00, +1);
confirmDoubleConvert(+0.99, 0);
confirmDoubleConvert(+0.01, 0);
confirmDoubleConvert( 0.00, 0);
confirmDoubleConvert(-0.01, -1);
confirmDoubleConvert(-0.99, -1);
confirmDoubleConvert(-1.00, -1);
confirmDoubleConvert(-1.01, -2);
confirmDoubleConvert(-1.99, -2);
confirmDoubleConvert(-2.00, -2);
confirmDoubleConvert(-2.01, -3);
}
public void testLinearOffsetRange() {
LinearOffsetRange lor;
lor = new LinearOffsetRange(3, 2);
assertEquals(3, lor.getFirstIndex());
assertEquals(4, lor.getLastIndex());
lor = lor.normaliseAndTranslate(0); // expected no change
assertEquals(3, lor.getFirstIndex());
assertEquals(4, lor.getLastIndex());
lor = lor.normaliseAndTranslate(5);
assertEquals(8, lor.getFirstIndex());
assertEquals(9, lor.getLastIndex());
// negative length
lor = new LinearOffsetRange(6, -4).normaliseAndTranslate(0);
assertEquals(3, lor.getFirstIndex());
assertEquals(6, lor.getLastIndex());
// bounds checking
lor = new LinearOffsetRange(0, 100);
assertFalse(lor.isOutOfBounds(0, 16383));
lor = lor.normaliseAndTranslate(16300);
assertTrue(lor.isOutOfBounds(0, 16383));
assertFalse(lor.isOutOfBounds(0, 65535));
}
}