From: Nick Burch Date: Thu, 21 Feb 2008 15:08:44 +0000 (+0000) Subject: Patch from Josh from bug #44371 - support for OFFSET function, and various tweaks... X-Git-Tag: REL_3_0_3_BETA1~123 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=b5dfd29cfc7b3caaf942012b002cc03c680331ee;p=poi.git 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 --- diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index eea475fd2c..3b4364fef7 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -36,6 +36,7 @@ + 44371 - Support for the Offset function 38921 - Have HSSFPalette.findSimilar() work properly 44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows 44403 - Further support for unusual, but valid, arguments to the Mid function diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 2d176896f5..43839ae8b0 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -33,6 +33,7 @@ + 44371 - Support for the Offset function 38921 - Have HSSFPalette.findSimilar() work properly 44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows 44403 - Further support for unusual, but valid, arguments to the Mid function diff --git a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java index 91417994e4..c3de3758f5 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java @@ -70,7 +70,21 @@ public class Area3DPtg extends Ptg implements AreaI 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(); diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area3DEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area3DEval.java index d371a09855..b997b95c6d 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area3DEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area3DEval.java @@ -27,7 +27,7 @@ import org.apache.poi.hssf.record.formula.Ptg; * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * */ -public class Area3DEval implements AreaEval { +public final class Area3DEval implements AreaEval { private Area3DPtg delegate; @@ -90,4 +90,8 @@ public class Area3DEval implements AreaEval { return delegate.getFirstRow() == delegate.getLastRow(); } + public int getExternSheetIndex() { + return delegate.getExternSheetIndex(); + } + } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java index d1420b2e85..d1c17b9f54 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java @@ -20,6 +20,9 @@ */ package org.apache.poi.hssf.record.formula.eval; +import java.util.HashMap; +import java.util.Map; + 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 { + /** + * 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(); + 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() { short fidx = getFunctionIndex(); return functions[fidx]; } + public boolean isFreeRefFunction() { + return freeRefFunctionsByIdMap.containsKey(createFRFKey(getFunctionIndex())); + } + public FreeRefFunction getFreeRefFunction() { + return (FreeRefFunction) freeRefFunctionsByIdMap.get(createFRFKey(getFunctionIndex())); + } public abstract short getFunctionIndex(); @@ -115,7 +153,7 @@ public abstract class FunctionEval implements OperationEval { retval[75] = new Areas(); // AREAS retval[76] = new Rows(); // ROWS 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[80] = new Relref(); // RELREF retval[81] = new Argument(); // ARGUMENT @@ -185,7 +223,7 @@ public abstract class FunctionEval implements OperationEval { retval[145] = new NotImplementedFunction(); // GETDEF retval[146] = new Reftext(); // REFTEXT 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[150] = new Call(); // CALL retval[151] = new NotImplementedFunction(); // ADDBAR diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java index acedbe766d..dcf8f0f712 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java @@ -27,7 +27,7 @@ import org.apache.poi.hssf.record.formula.Ref3DPtg; * @author Amol S. Deshmukh * */ -public class Ref3DEval implements RefEval { +public final class Ref3DEval implements RefEval { private ValueEval value; @@ -57,4 +57,8 @@ public class Ref3DEval implements RefEval { return evaluated; } + public int getExternSheetIndex() { + return delegate.getExternSheetIndex(); + } + } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java new file mode 100755 index 0000000000..56d285543e --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java @@ -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.
+ * Two important functions with this feature are INDIRECT and OFFSET

+ * + * In POI, the HSSFFormulaEvaluator 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 evaluate() method on the common + * interface Function does not take a workbook parameter.

+ * + * 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 null, + * 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 null. Possibly an instance of ErrorEval 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); +} diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java index c7464ffed8..935e7cdbbd 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java @@ -14,12 +14,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + 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

+ * + * INDIRECT() returns the cell or area reference denoted by the text argument.

+ * + * Syntax:
+ * INDIRECT(ref_text,isA1Style)

+ * + * ref_text a string representation of the desired reference as it would normally be written + * in a cell formula.
+ * isA1Style (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; + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Offset.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Offset.java index 80539d15f9..9497a5f21a 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Offset.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Offset.java @@ -14,12 +14,349 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * Created on May 15, 2005 - * - */ + + 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()

+ * + * OFFSET returns an area reference that is a specified number of rows and columns from a + * reference cell or area.

+ * + * Syntax:
+ * OFFSET(reference, rows, cols, height, width)

+ * reference is the base reference.
+ * rows is the number of rows up or down from the base reference.
+ * cols is the number of columns left or right from the base reference.
+ * height (default same height as base reference) is the row count for the returned area reference.
+ * width (default same width as base reference) is the column count for the returned area reference.
+ * + * @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.

+ * + * 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.

+ * + * @param translationAmount may be zero negative or positive + * + * @return the equivalent LinearOffsetRange 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); + } } diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java index 6a4eb8edb7..aabffab2f6 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java @@ -25,7 +25,7 @@ import org.apache.poi.hssf.record.formula.eval.NumberEval; import org.apache.poi.hssf.record.formula.eval.RefEval; /** - * Implementation for Excel COLUMNS function. + * Implementation for Excel ROWS function. * * @author Josh Micich */ diff --git a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java index 7cad38f102..acbca05e62 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java +++ b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java @@ -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.Eval; 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.GreaterThanEval; 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.UnaryPlusEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; -import org.apache.poi.hssf.usermodel.HSSFSheet; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > @@ -379,7 +379,7 @@ public class HSSFFormulaEvaluator { Stack stack = new Stack(); 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 MemErrPtg) { continue; } if (ptgs[i] instanceof MissingArgPtg) { continue; } @@ -405,7 +405,7 @@ public class HSSFFormulaEvaluator { Eval p = (Eval) stack.pop(); ops[j] = p; } - Eval opresult = operation.evaluate(ops, srcRowNum, srcColNum); + Eval opresult = invokeOperation(operation, ops, srcRowNum, srcColNum, workbook, sheet); stack.push(opresult); } else if (ptgs[i] instanceof ReferencePtg) { @@ -428,56 +428,12 @@ public class HSSFFormulaEvaluator { } else if (ptgs[i] instanceof AreaPtg) { AreaPtg ap = (AreaPtg) ptgs[i]; - 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); + AreaEval ae = evaluateAreaPtg(sheet, workbook, ap); stack.push(ae); } else if (ptgs[i] instanceof Area3DPtg) { Area3DPtg a3dp = (Area3DPtg) ptgs[i]; - 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); + AreaEval ae = evaluateArea3dPtg(workbook, a3dp); stack.push(ae); } else { @@ -485,23 +441,95 @@ public class HSSFFormulaEvaluator { stack.push(ptgEval); } } + ValueEval value = ((ValueEval) stack.pop()); if (value instanceof RefEval) { RefEval rv = (RefEval) value; - value = rv.getInnerValueEval(); + return rv.getInnerValueEval(); } - else if (value instanceof AreaEval) { + if (value instanceof AreaEval) { AreaEval ae = (AreaEval) value; - if (ae.isRow()) - value = ae.getValueAt(ae.getFirstRow(), srcColNum); - else if (ae.isColumn()) - value = ae.getValueAt(srcRowNum, ae.getFirstColumn()); - else - value = ErrorEval.VALUE_INVALID; + if (ae.isRow()) { + if(ae.isColumn()) { + return ae.getValues()[0]; + } + return ae.getValueAt(ae.getFirstRow(), srcColNum); + } + if (ae.isColumn()) { + return ae.getValueAt(srcRowNum, ae.getFirstColumn()); + } + return ErrorEval.VALUE_INVALID; } 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 * to the suplied operationPtg diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java index 1987a06376..8d21956edb 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java @@ -38,6 +38,7 @@ public final class AllIndividualFunctionEvaluationTests { result.addTestSuite(TestMid.class); result.addTestSuite(TestMathX.class); result.addTestSuite(TestMatch.class); + result.addTestSuite(TestOffset.class); result.addTestSuite(TestRowCol.class); result.addTestSuite(TestSumproduct.class); result.addTestSuite(TestStatsLib.class); diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java new file mode 100755 index 0000000000..f114662985 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java @@ -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)); + } + +}