Merged revisions 699178,699487,699489,699761 via svnmerge from

https://svn.apache.org/repos/asf/poi/trunk

........
  r699178 | josh | 2008-09-25 21:49:20 -0700 (Thu, 25 Sep 2008) | 1 line
  
  Changed HSSFEvaluationWorkbook to avoid re-parsing cell formulas during execution. (working towards fix for bug 45865)
........
  r699487 | josh | 2008-09-26 13:25:45 -0700 (Fri, 26 Sep 2008) | 1 line
  
  Fix formula parser to properly support the range operator. Small fixes to parsing of sheet names and full column references.
........
  r699489 | josh | 2008-09-26 13:32:06 -0700 (Fri, 26 Sep 2008) | 1 line
  
  Code cleanup in junit
........
  r699761 | josh | 2008-09-27 19:04:31 -0700 (Sat, 27 Sep 2008) | 1 line
  
  Bug 45865 - modified Formula Parser/Evaluator to handle cross-worksheet formulas
........


git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@700234 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-09-29 20:09:09 +00:00
parent 0d50343293
commit 96cb7321ba
44 changed files with 2133 additions and 1281 deletions

View File

@ -67,6 +67,7 @@
<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
</release>
<release version="3.2-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas</action>
<action dev="POI-DEVELOPERS" type="add">Optimised the FormulaEvaluator to take cell dependencies into account</action>
<action dev="POI-DEVELOPERS" type="add">16936 - Initial support for whole-row cell styling</action>
<action dev="POI-DEVELOPERS" type="add">Update hssf.extractor.ExcelExtractor to optionally output blank cells too</action>

View File

@ -64,6 +64,7 @@
<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
</release>
<release version="3.2-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas</action>
<action dev="POI-DEVELOPERS" type="add">Optimised the FormulaEvaluator to take cell dependencies into account</action>
<action dev="POI-DEVELOPERS" type="add">16936 - Initial support for whole-row cell styling</action>
<action dev="POI-DEVELOPERS" type="add">Update hssf.extractor.ExcelExtractor to optionally output blank cells too</action>

View File

@ -29,6 +29,7 @@ import org.apache.poi.hssf.record.ExternalNameRecord;
import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.SupBookRecord;
import org.apache.poi.hssf.record.UnicodeString;
import org.apache.poi.hssf.record.formula.NameXPtg;
/**
@ -109,8 +110,8 @@ final class LinkTable {
temp.toArray(_crnBlocks);
}
public ExternalBookBlock(short numberOfSheets) {
_externalBookRecord = SupBookRecord.createInternalReferences(numberOfSheets);
public ExternalBookBlock(int numberOfSheets) {
_externalBookRecord = SupBookRecord.createInternalReferences((short)numberOfSheets);
_externalNameRecords = new ExternalNameRecord[0];
_crnBlocks = new CRNBlock[0];
}
@ -197,7 +198,7 @@ final class LinkTable {
return ExternSheetRecord.combine(esrs);
}
public LinkTable(short numberOfSheets, WorkbookRecordList workbookRecordList) {
public LinkTable(int numberOfSheets, WorkbookRecordList workbookRecordList) {
_workbookRecordList = workbookRecordList;
_definedNames = new ArrayList();
_externalBookBlocks = new ExternalBookBlock[] {
@ -303,8 +304,62 @@ final class LinkTable {
return lastName.getSheetNumber() == firstName.getSheetNumber();
}
public String[] getExternalBookAndSheetName(int extRefIndex) {
int ebIx = _externSheetRecord.getExtbookIndexFromRefIndex(extRefIndex);
SupBookRecord ebr = _externalBookBlocks[ebIx].getExternalBookRecord();
if (!ebr.isExternalReferences()) {
return null;
}
int shIx = _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex);
UnicodeString usSheetName = ebr.getSheetNames()[shIx];
return new String[] {
ebr.getURL(),
usSheetName.getString(),
};
}
public int getIndexToSheet(int extRefIndex) {
public int getExternalSheetIndex(String workbookName, String sheetName) {
SupBookRecord ebrTarget = null;
int externalBookIndex = -1;
for (int i=0; i<_externalBookBlocks.length; i++) {
SupBookRecord ebr = _externalBookBlocks[i].getExternalBookRecord();
if (!ebr.isExternalReferences()) {
continue;
}
if (workbookName.equals(ebr.getURL())) { // not sure if 'equals()' works when url has a directory
ebrTarget = ebr;
externalBookIndex = i;
break;
}
}
if (ebrTarget == null) {
throw new RuntimeException("No external workbook with name '" + workbookName + "'");
}
int sheetIndex = getSheetIndex(ebrTarget.getSheetNames(), sheetName);
int result = _externSheetRecord.getRefIxForSheet(externalBookIndex, sheetIndex);
if (result < 0) {
throw new RuntimeException("ExternSheetRecord does not contain combination ("
+ externalBookIndex + ", " + sheetIndex + ")");
}
return result;
}
private static int getSheetIndex(UnicodeString[] sheetNames, String sheetName) {
for (int i = 0; i < sheetNames.length; i++) {
if (sheetNames[i].getString().equals(sheetName)) {
return i;
}
}
throw new RuntimeException("External workbook does not contain sheet '" + sheetName + "'");
}
/**
* @param extRefIndex as from a {@link Ref3DPtg} or {@link Area3DPtg}
* @return -1 if the reference is to an external book
*/
public int getIndexToInternalSheet(int extRefIndex) {
return _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex);
}
@ -315,20 +370,26 @@ final class LinkTable {
return _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex);
}
public int addSheetIndexToExternSheet(int sheetNumber) {
// TODO - what about the first parameter (extBookIndex)?
return _externSheetRecord.addRef(0, sheetNumber, sheetNumber);
public int checkExternSheet(int sheetIndex) {
int thisWbIndex = -1; // this is probably always zero
for (int i=0; i<_externalBookBlocks.length; i++) {
SupBookRecord ebr = _externalBookBlocks[i].getExternalBookRecord();
if (ebr.isInternalReferences()) {
thisWbIndex = i;
break;
}
}
if (thisWbIndex < 0) {
throw new RuntimeException("Could not find 'internal references' EXTERNALBOOK");
}
public short checkExternSheet(int sheetIndex) {
//Trying to find reference to this sheet
int i = _externSheetRecord.getRefIxForSheet(sheetIndex);
int i = _externSheetRecord.getRefIxForSheet(thisWbIndex, sheetIndex);
if (i>=0) {
return (short)i;
return i;
}
//We Haven't found reference to this sheet
return (short)addSheetIndexToExternSheet((short) sheetIndex);
//We haven't found reference to this sheet
return _externSheetRecord.addRef(thisWbIndex, sheetIndex, sheetIndex);
}

View File

@ -26,6 +26,7 @@ import org.apache.poi.ddf.*;
import org.apache.poi.hssf.record.*;
import org.apache.poi.hssf.record.formula.NameXPtg;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
@ -1914,8 +1915,7 @@ public final class Workbook implements Model {
*/
public String findSheetNameFromExternSheet(int externSheetIndex){
int indexToSheet = linkTable.getIndexToSheet(externSheetIndex);
int indexToSheet = linkTable.getIndexToInternalSheet(externSheetIndex);
if (indexToSheet < 0) {
// TODO - what does '-1' mean here?
//error check, bail out gracefully!
@ -1927,6 +1927,13 @@ public final class Workbook implements Model {
}
return getSheetName(indexToSheet);
}
public ExternalSheet getExternalSheet(int externSheetIndex) {
String[] extNames = linkTable.getExternalBookAndSheetName(externSheetIndex);
if (extNames == null) {
return null;
}
return new ExternalSheet(extNames[0], extNames[1]);
}
/**
* Finds the sheet index for a particular external sheet number.
@ -1944,9 +1951,14 @@ public final class Workbook implements Model {
* @return index to extern sheet
*/
public short checkExternSheet(int sheetNumber){
return getOrCreateLinkTable().checkExternSheet(sheetNumber);
return (short)getOrCreateLinkTable().checkExternSheet(sheetNumber);
}
public int getExternalSheetIndex(String workbookName, String sheetName) {
return getOrCreateLinkTable().getExternalSheetIndex(workbookName, sheetName);
}
/** gets the total number of names
* @return number of names
*/

View File

@ -250,10 +250,13 @@ public class ExternSheetRecord extends Record {
return _list.size() - 1;
}
public int getRefIxForSheet(int sheetIndex) {
public int getRefIxForSheet(int externalBookIndex, int sheetIndex) {
int nItems = _list.size();
for (int i = 0; i < nItems; i++) {
RefSubRecord ref = getRef(i);
if (ref.getExtBookIndex() != externalBookIndex) {
continue;
}
if (ref.getFirstSheetIndex() == sheetIndex && ref.getLastSheetIndex() == sheetIndex) {
return i;
}

View File

@ -221,8 +221,33 @@ public final class SupBookRecord extends Record {
{
return sid;
}
public UnicodeString getURL() {
return field_2_encoded_url;
public String getURL() {
String encodedUrl = field_2_encoded_url.getString();
switch(encodedUrl.charAt(0)) {
case 0: // Reference to an empty workbook name
return encodedUrl.substring(1); // will this just be empty string?
case 1: // encoded file name
return decodeFileName(encodedUrl);
case 2: // Self-referential external reference
return encodedUrl.substring(1);
}
return encodedUrl;
}
private static String decodeFileName(String encodedUrl) {
return encodedUrl.substring(1);
// TODO the following special characters may appear in the rest of the string, and need to get interpreted
/* see "MICROSOFT OFFICE EXCEL 97-2007 BINARY FILE FORMAT SPECIFICATION"
chVolume 1
chSameVolume 2
chDownDir 3
chUpDir 4
chLongVolume 5
chStartupDir 6
chAltStartupDir 7
chLibDir 8
*/
}
public UnicodeString[] getSheetNames() {
return (UnicodeString[]) field_3_sheet_names.clone();

View File

@ -18,8 +18,9 @@
package org.apache.poi.hssf.record.formula;
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.ss.formula.WorkbookDependentFormula;
import org.apache.poi.ss.formula.ExternSheetReferenceToken;
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
import org.apache.poi.ss.formula.WorkbookDependentFormula;
import org.apache.poi.util.LittleEndian;
/**
@ -31,7 +32,7 @@ import org.apache.poi.util.LittleEndian;
* @author Jason Height (jheight at chariot dot net dot au)
* @version 1.0-pre
*/
public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFormula {
public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFormula, ExternSheetReferenceToken {
public final static byte sid = 0x3b;
private final static int SIZE = 11; // 10 + 1 for Ptg
@ -76,8 +77,8 @@ public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFor
return SIZE;
}
public short getExternSheetIndex() {
return (short)field_1_index_extern_sheet;
public int getExternSheetIndex() {
return field_1_index_extern_sheet;
}
public void setExternSheetIndex(int index) {

View File

@ -50,10 +50,10 @@ public interface AreaI {
public OffsetArea(int baseRow, int baseColumn, int relFirstRowIx, int relLastRowIx,
int relFirstColIx, int relLastColIx) {
_firstRow = baseRow + relFirstRowIx;
_lastRow = baseRow + relLastRowIx;
_firstColumn = baseColumn + relFirstColIx;
_lastColumn = baseColumn + relLastColIx;
_firstRow = baseRow + Math.min(relFirstRowIx, relLastRowIx);
_lastRow = baseRow + Math.max(relFirstRowIx, relLastRowIx);
_firstColumn = baseColumn + Math.min(relFirstColIx, relLastColIx);
_lastColumn = baseColumn + Math.max(relFirstColIx, relLastColIx);
}
public int getFirstColumn() {
@ -72,5 +72,4 @@ public interface AreaI {
return _lastRow;
}
}
}

View File

@ -76,14 +76,30 @@ public abstract class AreaPtgBase extends OperandPtg implements AreaI {
checkColumnBounds(lastColumn);
checkRowBounds(firstRow);
checkRowBounds(lastRow);
if (lastRow > firstRow) {
setFirstRow(firstRow);
setLastRow(lastRow);
setFirstColumn(firstColumn);
setLastColumn(lastColumn);
setFirstRowRelative(firstRowRelative);
setLastRowRelative(lastRowRelative);
} else {
setFirstRow(lastRow);
setLastRow(firstRow);
setFirstRowRelative(lastRowRelative);
setLastRowRelative(firstRowRelative);
}
if (lastColumn > firstColumn) {
setFirstColumn(firstColumn);
setLastColumn(lastColumn);
setFirstColRelative(firstColRelative);
setLastColRelative(lastColRelative);
} else {
setFirstColumn(lastColumn);
setLastColumn(firstColumn);
setFirstColRelative(lastColRelative);
setLastColRelative(firstColRelative);
}
}
private static void checkColumnBounds(int colIx) {

View File

@ -50,6 +50,8 @@ public final class AttrPtg extends ControlPtg {
private static final BitField baxcel = BitFieldFactory.getInstance(0x20); // 'assignment-style formula in a macro sheet'
private static final BitField space = BitFieldFactory.getInstance(0x40);
public static final AttrPtg SUM = new AttrPtg(0x0010, 0, null, -1);
public static final class SpaceType {
private SpaceType() {
// no instances of this class

View File

@ -18,6 +18,7 @@
package org.apache.poi.hssf.record.formula;
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
/**
* @author Josh Micich
@ -29,14 +30,23 @@ final class ExternSheetNameResolver {
}
public static String prependSheetName(FormulaRenderingWorkbook book, int field_1_index_extern_sheet, String cellRefText) {
ExternalSheet externalSheet = book.getExternalSheet(field_1_index_extern_sheet);
StringBuffer sb;
if (externalSheet != null) {
String wbName = externalSheet.getWorkbookName();
String sheetName = externalSheet.getSheetName();
sb = new StringBuffer(wbName.length() + sheetName.length() + cellRefText.length() + 4);
SheetNameFormatter.appendFormat(sb, wbName, sheetName);
} else {
String sheetName = book.getSheetNameByExternSheet(field_1_index_extern_sheet);
StringBuffer sb = new StringBuffer(sheetName.length() + cellRefText.length() + 4);
sb = new StringBuffer(sheetName.length() + cellRefText.length() + 4);
if (sheetName.length() < 1) {
// What excel does if sheet has been deleted
sb.append("#REF"); // note - '!' added just once below
} else {
SheetNameFormatter.appendFormat(sb, sheetName);
}
}
sb.append('!');
sb.append(cellRefText);
return sb.toString();

View File

@ -19,8 +19,9 @@ package org.apache.poi.hssf.record.formula;
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.formula.WorkbookDependentFormula;
import org.apache.poi.ss.formula.ExternSheetReferenceToken;
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
import org.apache.poi.ss.formula.WorkbookDependentFormula;
import org.apache.poi.util.LittleEndian;
/**
@ -31,7 +32,7 @@ import org.apache.poi.util.LittleEndian;
* @author Jason Height (jheight at chariot dot net dot au)
* @version 1.0-pre
*/
public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormula {
public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormula, ExternSheetReferenceToken {
public final static byte sid = 0x3a;
private final static int SIZE = 7; // 6 + 1 for Ptg

View File

@ -66,6 +66,22 @@ public final class SheetNameFormatter {
out.append(rawSheetName);
}
}
public static void appendFormat(StringBuffer out, String workbookName, String rawSheetName) {
boolean needsQuotes = needsDelimiting(workbookName) || needsDelimiting(rawSheetName);
if(needsQuotes) {
out.append(DELIMITER);
out.append('[');
appendAndEscape(out, workbookName.replace('[', '(').replace(']', ')'));
out.append(']');
appendAndEscape(out, rawSheetName);
out.append(DELIMITER);
} else {
out.append('[');
out.append(workbookName);
out.append(']');
out.append(rawSheetName);
}
}
private static void appendAndEscape(StringBuffer sb, String rawSheetName) {
int len = rawSheetName.length();
@ -101,13 +117,27 @@ public final class SheetNameFormatter {
return true;
}
}
if (nameLooksLikeBooleanLiteral(rawSheetName)) {
return true;
}
// Error constant literals all contain '#' and other special characters
// so they don't get this far
return false;
}
private static boolean nameLooksLikeBooleanLiteral(String rawSheetName) {
switch(rawSheetName.charAt(0)) {
case 'T': case 't':
return "TRUE".equalsIgnoreCase(rawSheetName);
case 'F': case 'f':
return "FALSE".equalsIgnoreCase(rawSheetName);
}
return false;
}
/**
* @return <code>true</code> if the presence of the specified character in a sheet name would
* require the sheet name to be delimited in formulas. This includes every non-alphanumeric
* character besides underscore '_'.
* character besides underscore '_' and dot '.'.
*/
/* package */ static boolean isSpecialChar(char ch) {
// note - Character.isJavaIdentifierPart() would allow dollars '$'
@ -115,7 +145,8 @@ public final class SheetNameFormatter {
return false;
}
switch(ch) {
case '_': // underscore is ok
case '.': // dot is OK
case '_': // underscore is OK
return false;
case '\n':
case '\r':

View File

@ -0,0 +1,71 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.eval;
/**
*
* @author Josh Micich
*/
public final class RangeEval implements OperationEval {
public static final OperationEval instance = new RangeEval();
private RangeEval() {
}
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
if(args.length != 2) {
return ErrorEval.VALUE_INVALID;
}
try {
RefEval reA = evaluateRef(args[0]);
RefEval reB = evaluateRef(args[1]);
return resolveRange(reA, reB);
} catch (EvaluationException e) {
return e.getErrorEval();
}
}
private static AreaEval resolveRange(RefEval reA, RefEval reB) {
int height = reB.getRow() - reA.getRow();
int width = reB.getColumn() - reA.getColumn();
return reA.offset(0, height, 0, width);
}
private static RefEval evaluateRef(Eval arg) throws EvaluationException {
if (arg instanceof RefEval) {
return (RefEval) arg;
}
if (arg instanceof ErrorEval) {
throw new EvaluationException((ErrorEval)arg);
}
throw new IllegalArgumentException("Unexpected ref arg class (" + arg.getClass().getName() + ")");
}
public int getNumberOfOperands() {
return 2;
}
public int getType() {
throw new RuntimeException("obsolete code should not be called");
}
}

View File

@ -2,7 +2,9 @@ package org.apache.poi.hssf.usermodel;
import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg;
import org.apache.poi.hssf.record.formula.Ptg;
@ -39,6 +41,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
int sheetIndex = _uBook.getSheetIndex(sheetName);
return _iBook.checkExternSheet(sheetIndex);
}
public int getExternalSheetIndex(String workbookName, String sheetName) {
return _iBook.getExternalSheetIndex(workbookName, sheetName);
}
public EvaluationName getName(int index) {
return new Name(_iBook.getNameRecord(index), index);
@ -57,6 +62,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
public int getSheetIndex(Sheet sheet) {
return _uBook.getSheetIndex(sheet);
}
public int getSheetIndex(String sheetName) {
return _uBook.getSheetIndex(sheetName);
}
public String getSheetName(int sheetIndex) {
return _uBook.getSheetName(sheetIndex);
@ -77,6 +85,10 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
return _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);
}
public ExternalSheet getExternalSheet(int externSheetIndex) {
return _iBook.getExternalSheet(externSheetIndex);
}
public HSSFWorkbook getWorkbook() {
return _uBook;
}
@ -96,8 +108,16 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
return new Name(_iBook.getNameRecord(ix), ix);
}
public Ptg[] getFormulaTokens(Cell cell) {
if (false) {
// re-parsing the formula text also works, but is a waste of time
// It is useful from time to time to run all unit tests with this code
// to make sure that all formulas POI can evaluate can also be parsed.
return HSSFFormulaParser.parse(cell.getCellFormula(), _uBook);
}
HSSFCell hCell = (HSSFCell) cell;
FormulaRecord fr = ((FormulaRecordAggregate) hCell.getCellValueRecord()).getFormulaRecord();
return fr.getParsedExpression();
}
private static final class Name implements EvaluationName {

View File

@ -25,6 +25,7 @@ import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment;
import org.apache.poi.ss.formula.WorkbookEvaluator;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellValue;
@ -57,6 +58,21 @@ public class HSSFFormulaEvaluator /* almost implements FormulaEvaluator */ {
_bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook));
}
/**
* Coordinates several formula evaluators together so that formulas that involve external
* references can be evaluated.
* @param workbookNames the simple file names used to identify the workbooks in formulas
* with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1")
* @param evaluators all evaluators for the full set of workbooks required by the formulas.
*/
public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) {
WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length];
for (int i = 0; i < wbEvals.length; i++) {
wbEvals[i] = evaluators[i]._bookEvaluator;
}
CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals);
}
/**
* Does nothing
* @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell

View File

@ -30,6 +30,7 @@ public final class AreaReference extends org.apache.poi.ss.util.AreaReference {
/**
* Creates an area ref from a pair of Cell References.
* Also normalises such that the top-left
*/
public AreaReference(CellReference topLeft, CellReference botRight) {
super(topLeft, botRight);

View File

@ -18,22 +18,13 @@
package org.apache.poi.hssf.util;
/**
* Common convertion functions between Excel style A1, C27 style
* Common conversion functions between Excel style A1, C27 style
* cell references, and POI usermodel style row=0, column=0
* style references.
* @author Avik Sengupta
* @author Dennis Doubleday (patch to seperateRowColumns())
*/
public final class CellReference extends org.apache.poi.ss.util.CellReference {
/**
* Used to classify identifiers found in formulas as cell references or not.
*/
public static final class NameType {
public static final int CELL = 1;
public static final int NAMED_RANGE = 2;
public static final int BAD_CELL_OR_NAMED_RANGE = -1;
}
/**
* Create an cell ref from a string representation. Sheet names containing special characters should be
* delimited and escaped as per normal syntax rules for formulas.
@ -45,9 +36,6 @@ public final class CellReference extends org.apache.poi.ss.util.CellReference {
public CellReference(int pRow, int pCol) {
super(pRow, pCol, true, true);
}
public CellReference(int pRow, short pCol) {
super(pRow, (int)pCol, true, true);
}
public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
super(null, pRow, pCol, pAbsRow, pAbsCol);
@ -55,10 +43,6 @@ public final class CellReference extends org.apache.poi.ss.util.CellReference {
public CellReference(String pSheetName, int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
super(pSheetName, pRow, pCol, pAbsRow, pAbsCol);
}
protected void appendCellReference(StringBuffer sb) {
super.appendCellReference(sb);
}
protected static String convertNumToColString(int col) {
return org.apache.poi.ss.util.CellReference.convertNumToColString(col);
}

View File

@ -17,25 +17,32 @@
package org.apache.poi.ss.formula;
import org.apache.poi.hssf.util.CellReference;
/**
* Stores the parameters that identify the evaluation of one cell.<br/>
*/
final class CellLocation {
public static final CellLocation[] EMPTY_ARRAY = { };
private final EvaluationWorkbook _book;
private final int _sheetIndex;
private final int _rowIndex;
private final int _columnIndex;
private final int _hashCode;
public CellLocation(int sheetIndex, int rowIndex, int columnIndex) {
public CellLocation(EvaluationWorkbook book, int sheetIndex, int rowIndex, int columnIndex) {
if (sheetIndex < 0) {
throw new IllegalArgumentException("sheetIndex must not be negative");
}
_book = book;
_sheetIndex = sheetIndex;
_rowIndex = rowIndex;
_columnIndex = columnIndex;
_hashCode = sheetIndex + 17 * (rowIndex + 17 * columnIndex);
_hashCode = System.identityHashCode(book) + sheetIndex + 17 * (rowIndex + 17 * columnIndex);
}
public Object getBook() {
return _book;
}
public int getSheetIndex() {
return _sheetIndex;
@ -49,15 +56,18 @@ final class CellLocation {
public boolean equals(Object obj) {
CellLocation other = (CellLocation) obj;
if (getSheetIndex() != other.getSheetIndex()) {
return false;
}
if (getRowIndex() != other.getRowIndex()) {
return false;
}
if (getColumnIndex() != other.getColumnIndex()) {
return false;
}
if (getSheetIndex() != other.getSheetIndex()) {
return false;
}
if (getBook() != other.getBook()) {
return false;
}
return true;
}
public int hashCode() {
@ -68,7 +78,8 @@ final class CellLocation {
* @return human readable string for debug purposes
*/
public String formatAsString() {
return "ShIx=" + getSheetIndex() + " R=" + getRowIndex() + " C=" + getColumnIndex();
CellReference cr = new CellReference(_rowIndex, _columnIndex, false, false);
return "ShIx=" + getSheetIndex() + " " + cr.formatAsString();
}
public String toString() {

View File

@ -0,0 +1,155 @@
/* ====================================================================
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.ss.formula;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Manages a collection of {@link WorkbookEvaluator}s, in order to support evaluation of formulas
* across spreadsheets.<p/>
*
* For POI internal use only
*
* @author Josh Micich
*/
public final class CollaboratingWorkbooksEnvironment {
public static final CollaboratingWorkbooksEnvironment EMPTY = new CollaboratingWorkbooksEnvironment();
private final Map _evaluatorsByName;
private final WorkbookEvaluator[] _evaluators;
private boolean _unhooked;
private CollaboratingWorkbooksEnvironment() {
_evaluatorsByName = Collections.EMPTY_MAP;
_evaluators = new WorkbookEvaluator[0];
}
public static void setup(String[] workbookNames, WorkbookEvaluator[] evaluators) {
int nItems = workbookNames.length;
if (evaluators.length != nItems) {
throw new IllegalArgumentException("Number of workbook names is " + nItems
+ " but number of evaluators is " + evaluators.length);
}
if (nItems < 1) {
throw new IllegalArgumentException("Must provide at least one collaborating worbook");
}
new CollaboratingWorkbooksEnvironment(workbookNames, evaluators, nItems);
}
private CollaboratingWorkbooksEnvironment(String[] workbookNames, WorkbookEvaluator[] evaluators, int nItems) {
Map m = new HashMap(nItems * 3 / 2);
IdentityHashMap uniqueEvals = new IdentityHashMap(nItems * 3 / 2);
for(int i=0; i<nItems; i++) {
String wbName = workbookNames[i];
WorkbookEvaluator wbEval = evaluators[i];
if (m.containsKey(wbName)) {
throw new IllegalArgumentException("Duplicate workbook name '" + wbName + "'");
}
if (uniqueEvals.containsKey(wbEval)) {
String msg = "Attempted to register same workbook under names '"
+ uniqueEvals.get(wbEval) + "' and '" + wbName + "'";
throw new IllegalArgumentException(msg);
}
uniqueEvals.put(wbEval, wbName);
m.put(wbName, wbEval);
}
unhookOldEnvironments(evaluators);
hookNewEnvironment(evaluators, this);
_unhooked = false;
_evaluators = evaluators;
_evaluatorsByName = m;
}
private static void hookNewEnvironment(WorkbookEvaluator[] evaluators, CollaboratingWorkbooksEnvironment env) {
// All evaluators will need to share the same cache.
// but the cache takes an optional evaluation listener.
int nItems = evaluators.length;
IEvaluationListener evalListener = evaluators[0].getEvaluationListener();
// make sure that all evaluators have the same listener
for(int i=0; i<nItems; i++) {
if(evalListener != evaluators[i].getEvaluationListener()) {
// This would be very complex to support
throw new RuntimeException("Workbook evaluators must all have the same evaluation listener");
}
}
EvaluationCache cache = new EvaluationCache(evalListener);
for(int i=0; i<nItems; i++) {
evaluators[i].attachToEnvironment(env, cache);
}
}
private void unhookOldEnvironments(WorkbookEvaluator[] evaluators) {
Set oldEnvs = new HashSet();
for(int i=0; i<evaluators.length; i++) {
oldEnvs.add(evaluators[i].getEnvironment());
}
CollaboratingWorkbooksEnvironment[] oldCWEs = new CollaboratingWorkbooksEnvironment[oldEnvs.size()];
oldEnvs.toArray(oldCWEs);
for (int i = 0; i < oldCWEs.length; i++) {
oldCWEs[i].unhook();
}
}
/**
*
*/
private void unhook() {
if (_evaluators.length < 1) {
return;
}
for (int i = 0; i < _evaluators.length; i++) {
_evaluators[i].detachFromEnvironment();
}
_unhooked = true;
}
public WorkbookEvaluator getWorkbookEvaluator(String workbookName) {
if (_unhooked) {
throw new IllegalStateException("This environment has been unhooked");
}
WorkbookEvaluator result = (WorkbookEvaluator) _evaluatorsByName.get(workbookName);
if (result == null) {
StringBuffer sb = new StringBuffer(256);
sb.append("Could not resolve external workbook name '").append(workbookName).append("'.");
if (_evaluators.length < 1) {
sb.append(" Workbook environment has not been set up.");
} else {
sb.append(" The following workbook names are valid: (");
Iterator i = _evaluatorsByName.keySet().iterator();
int count=0;
while(i.hasNext()) {
if (count++>0) {
sb.append(", ");
}
sb.append("'").append(i.next()).append("'");
}
sb.append(")");
}
throw new RuntimeException(sb.toString());
}
return result;
}
}

View File

@ -81,13 +81,7 @@ final class EvaluationCache {
+ cellLoc.formatAsString());
}
}
if (_evaluationListener == null) {
// optimisation - don't bother sorting if there is no listener.
} else {
// for testing
// make order of callbacks to listener more deterministic
Arrays.sort(usedCells, CellLocationComparator);
}
sortCellLocationsForLogging(usedCells);
CellCacheEntry entry = getEntry(cellLoc);
CellLocation[] consumingFormulaCells = entry.getConsumingCells();
CellLocation[] prevUsedCells = entry.getUsedCells();
@ -110,6 +104,18 @@ final class EvaluationCache {
recurseClearCachedFormulaResults(consumingFormulaCells, 0);
}
/**
* This method sorts the supplied cellLocs so that the order of call-backs to the evaluation
* listener is more deterministic
*/
private void sortCellLocationsForLogging(CellLocation[] cellLocs) {
if (_evaluationListener == null) {
// optimisation - don't bother sorting if there is no listener.
} else {
Arrays.sort(cellLocs, CellLocationComparator);
}
}
private void unlinkConsumingCells(CellLocation[] prevUsedCells, CellLocation[] usedCells,
CellLocation cellLoc) {
if (prevUsedCells == null) {
@ -149,6 +155,7 @@ final class EvaluationCache {
* @param formulaCells
*/
private void recurseClearCachedFormulaResults(CellLocation[] formulaCells, int depth) {
sortCellLocationsForLogging(formulaCells);
int nextDepth = depth+1;
for (int i = 0; i < formulaCells.length; i++) {
CellLocation fc = formulaCells[i];
@ -196,6 +203,10 @@ final class EvaluationCache {
CellLocation clB = (CellLocation) b;
int cmp;
cmp = System.identityHashCode(clA.getBook()) - System.identityHashCode(clB.getBook());
if (cmp != 0) {
return cmp;
}
cmp = clA.getSheetIndex() - clB.getSheetIndex();
if (cmp != 0) {
return cmp;

View File

@ -31,12 +31,36 @@ import org.apache.poi.ss.usermodel.Sheet;
*/
public interface EvaluationWorkbook {
String getSheetName(int sheetIndex);
/**
* @return -1 if the specified sheet is from a different book
*/
int getSheetIndex(Sheet sheet);
int getSheetIndex(String sheetName);
Sheet getSheet(int sheetIndex);
/**
* @return <code>null</code> if externSheetIndex refers to a sheet inside the current workbook
*/
ExternalSheet getExternalSheet(int externSheetIndex);
int convertFromExternSheetIndex(int externSheetIndex);
EvaluationName getName(NamePtg namePtg);
String resolveNameXText(NameXPtg ptg);
Ptg[] getFormulaTokens(Cell cell);
class ExternalSheet {
private final String _workbookName;
private final String _sheetName;
public ExternalSheet(String workbookName, String sheetName) {
_workbookName = workbookName;
_sheetName = sheetName;
}
public String getWorkbookName() {
return _workbookName;
}
public String getSheetName() {
return _sheetName;
}
}
}

View File

@ -0,0 +1,29 @@
/* ====================================================================
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.ss.formula;
/**
* Should be implemented by any {@link Ptg} subclass that needs has an extern sheet index <br/>
*
* For POI internal use only
*
* @author Josh Micich
*/
public interface ExternSheetReferenceToken {
int getExternSheetIndex();
}

View File

@ -49,6 +49,7 @@ import org.apache.poi.hssf.record.formula.ParenthesisPtg;
import org.apache.poi.hssf.record.formula.PercentPtg;
import org.apache.poi.hssf.record.formula.PowerPtg;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.RangePtg;
import org.apache.poi.hssf.record.formula.Ref3DPtg;
import org.apache.poi.hssf.record.formula.RefPtg;
import org.apache.poi.hssf.record.formula.StringPtg;
@ -60,7 +61,7 @@ import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry;
import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
import org.apache.poi.hssf.util.AreaReference;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.hssf.util.CellReference.NameType;
import org.apache.poi.ss.util.CellReference.NameType;
/**
* This class parses a formula string into a List of tokens in RPN order.
@ -81,6 +82,33 @@ import org.apache.poi.hssf.util.CellReference.NameType;
* @author Josh Micich
*/
public final class FormulaParser {
private static final class Identifier {
private final String _name;
private final boolean _isQuoted;
public Identifier(String name, boolean isQuoted) {
_name = name;
_isQuoted = isQuoted;
}
public String getName() {
return _name;
}
public boolean isQuoted() {
return _isQuoted;
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName());
sb.append(" [");
if (_isQuoted) {
sb.append("'").append(_name).append("'");
} else {
sb.append(_name);
}
sb.append("]");
return sb.toString();
}
}
/**
* Specific exception thrown when a supplied formula does not parse properly.<br/>
@ -176,23 +204,23 @@ public final class FormulaParser {
}
/** Recognize an Alpha Character */
private boolean IsAlpha(char c) {
private static boolean IsAlpha(char c) {
return Character.isLetter(c) || c == '$' || c=='_';
}
/** Recognize a Decimal Digit */
private boolean IsDigit(char c) {
private static boolean IsDigit(char c) {
return Character.isDigit(c);
}
/** Recognize an Alphanumeric */
private boolean IsAlNum(char c) {
return (IsAlpha(c) || IsDigit(c));
private static boolean IsAlNum(char c) {
return IsAlpha(c) || IsDigit(c);
}
/** Recognize White Space */
private boolean IsWhite( char c) {
return (c ==' ' || c== TAB);
private static boolean IsWhite( char c) {
return c ==' ' || c== TAB;
}
/** Skip Over Leading White Space */
@ -213,7 +241,13 @@ public final class FormulaParser {
}
GetChar();
}
private String parseUnquotedIdentifier() {
Identifier iden = parseIdentifier();
if (iden.isQuoted()) {
throw expected("unquoted identifier");
}
return iden.getName();
}
/**
* Parses a sheet name, named range name, or simple cell reference.<br/>
* Note - identifiers in Excel can contain dots, so this method may return a String
@ -221,18 +255,17 @@ public final class FormulaParser {
* may return a value like "A1..B2", in which case the caller must convert it to
* an area reference like "A1:B2"
*/
private String parseIdentifier() {
StringBuffer Token = new StringBuffer();
if (!IsAlpha(look) && look != '\'') {
private Identifier parseIdentifier() {
StringBuffer sb = new StringBuffer();
if (!IsAlpha(look) && look != '\'' && look != '[') {
throw expected("Name");
}
if(look == '\'')
{
boolean isQuoted = look == '\'';
if(isQuoted) {
Match('\'');
boolean done = look == '\'';
while(!done)
{
Token.append(look);
while(!done) {
sb.append(look);
GetChar();
if(look == '\'')
{
@ -240,17 +273,15 @@ public final class FormulaParser {
done = look != '\'';
}
}
}
else
{
} else {
// allow for any sequence of dots and identifier chars
// special case of two consecutive dots is best treated in the calling code
while (IsAlNum(look) || look == '.') {
Token.append(look);
while (IsAlNum(look) || look == '.' || look == '[' || look == ']') {
sb.append(look);
GetChar();
}
}
return Token.toString();
return new Identifier(sb.toString(), isQuoted);
}
/** Get a Number */
@ -265,72 +296,112 @@ public final class FormulaParser {
}
private ParseNode parseFunctionReferenceOrName() {
String name = parseIdentifier();
Identifier iden = parseIdentifier();
if (look == '('){
//This is a function
return function(name);
return function(iden.getName());
}
return new ParseNode(parseNameOrReference(name));
if (!iden.isQuoted()) {
String name = iden.getName();
if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
return new ParseNode(new BoolPtg(name.toUpperCase()));
}
}
return parseRangeExpression(iden);
}
private Ptg parseNameOrReference(String name) {
private ParseNode parseRangeExpression(Identifier iden) {
Ptg ptgA = parseNameOrCellRef(iden);
if (look == ':') {
GetChar();
Identifier iden2 = parseIdentifier();
Ptg ptgB = parseNameOrCellRef(iden2);
Ptg simplified = reduceRangeExpression(ptgA, ptgB);
if (simplified == null) {
ParseNode[] children = {
new ParseNode(ptgA),
new ParseNode(ptgB),
};
return new ParseNode(RangePtg.instance, children);
}
return new ParseNode(simplified);
}
return new ParseNode(ptgA);
}
/**
*
* "A1", "B3" -> "A1:B3"
* "sheet1!A1", "B3" -> "sheet1!A1:B3"
*
* @return <code>null</code> if the range expression cannot / shouldn't be reduced.
*/
private static Ptg reduceRangeExpression(Ptg ptgA, Ptg ptgB) {
if (!(ptgB instanceof RefPtg)) {
// only when second ref is simple 2-D ref can the range
// expression be converted to an area ref
return null;
}
RefPtg refB = (RefPtg) ptgB;
if (ptgA instanceof RefPtg) {
RefPtg refA = (RefPtg) ptgA;
return new AreaPtg(refA.getRow(), refB.getRow(), refA.getColumn(), refB.getColumn(),
refA.isRowRelative(), refB.isRowRelative(), refA.isColRelative(), refB.isColRelative());
}
if (ptgA instanceof Ref3DPtg) {
Ref3DPtg refA = (Ref3DPtg) ptgA;
return new Area3DPtg(refA.getRow(), refB.getRow(), refA.getColumn(), refB.getColumn(),
refA.isRowRelative(), refB.isRowRelative(), refA.isColRelative(), refB.isColRelative(),
refA.getExternSheetIndex());
}
// Note - other operand types (like AreaPtg) which probably can't evaluate
// do not cause validation errors at parse time
return null;
}
private Ptg parseNameOrCellRef(Identifier iden) {
if (look == '!') {
GetChar();
// 3-D ref
// this code assumes iden is a sheetName
// TODO - handle <book name> ! <named range name>
int externIdx = getExternalSheetIndex(iden.getName());
String secondIden = parseUnquotedIdentifier();
AreaReference areaRef = parseArea(secondIden);
if (areaRef == null) {
return new Ref3DPtg(secondIden, externIdx);
}
// will happen if dots are used instead of colon
return new Area3DPtg(areaRef.formatAsString(), externIdx);
}
String name = iden.getName();
AreaReference areaRef = parseArea(name);
if (areaRef != null) {
// will happen if dots are used instead of colon
return new AreaPtg(areaRef.formatAsString());
}
if (look == ':' || look == '.') { // this is a AreaReference
GetChar();
while (look == '.') { // formulas can have . or .. or ... instead of :
GetChar();
}
String first = name;
String second = parseIdentifier();
return new AreaPtg(first+":"+second);
}
if (look == '!') {
Match('!');
String sheetName = name;
String first = parseIdentifier();
int externIdx = book.getExternalSheetIndex(sheetName);
areaRef = parseArea(name);
if (areaRef != null) {
// will happen if dots are used instead of colon
return new Area3DPtg(areaRef.formatAsString(), externIdx);
}
if (look == ':') {
Match(':');
String second=parseIdentifier();
if (look == '!') {
//The sheet name was included in both of the areas. Only really
//need it once
Match('!');
String third=parseIdentifier();
if (!sheetName.equals(second))
throw new RuntimeException("Unhandled double sheet reference.");
return new Area3DPtg(first+":"+third,externIdx);
}
return new Area3DPtg(first+":"+second,externIdx);
}
return new Ref3DPtg(first, externIdx);
}
if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
return new BoolPtg(name.toUpperCase());
}
// This can be either a cell ref or a named range
// Try to spot which it is
int nameType = CellReference.classifyCellReference(name);
if (nameType == NameType.CELL) {
return new RefPtg(name);
}
if (look == ':') {
if (nameType == NameType.COLUMN) {
GetChar();
String secondIden = parseUnquotedIdentifier();
if (CellReference.classifyCellReference(secondIden) != NameType.COLUMN) {
throw new FormulaParseException("Expected full column after '" + name
+ ":' but got '" + secondIden + "'");
}
return new AreaPtg(name + ":" + secondIden);
}
}
if (nameType != NameType.NAMED_RANGE) {
new FormulaParseException("Name '" + name
+ "' does not look like a cell reference or named range");
@ -347,6 +418,17 @@ public final class FormulaParser {
+ name + "' is not a range as expected");
}
private int getExternalSheetIndex(String name) {
if (name.charAt(0) == '[') {
// we have a sheet name qualified with workbook name e.g. '[MyData.xls]Sheet1'
int pos = name.lastIndexOf(']'); // safe because sheet names never have ']'
String wbName = name.substring(1, pos);
String sheetName = name.substring(pos+1);
return book.getExternalSheetIndex(wbName, sheetName);
}
return book.getExternalSheetIndex(name);
}
/**
* @param name an 'identifier' like string (i.e. contains alphanums, and dots)
* @return <code>null</code> if name cannot be split at a dot
@ -585,7 +667,7 @@ public final class FormulaParser {
Match('}');
return arrayNode;
}
if (IsAlpha(look) || look == '\''){
if (IsAlpha(look) || look == '\'' || look == '['){
return parseFunctionReferenceOrName();
}
// else - assume number
@ -662,7 +744,7 @@ public final class FormulaParser {
}
private Boolean parseBooleanLiteral() {
String iden = parseIdentifier();
String iden = parseUnquotedIdentifier();
if ("TRUE".equalsIgnoreCase(iden)) {
return Boolean.TRUE;
}
@ -720,7 +802,7 @@ public final class FormulaParser {
private int parseErrorLiteral() {
Match('#');
String part1 = parseIdentifier().toUpperCase();
String part1 = parseUnquotedIdentifier().toUpperCase();
switch(part1.charAt(0)) {
case 'V':

View File

@ -32,6 +32,16 @@ public interface FormulaParsingWorkbook {
*/
EvaluationName getName(String name);
int getExternalSheetIndex(String sheetName);
NameXPtg getNameXPtg(String name);
/**
* gets the externSheet index for a sheet from this workbook
*/
int getExternalSheetIndex(String sheetName);
/**
* gets the externSheet index for a sheet from an external workbook
* @param workbookName e.g. "Budget.xls"
* @param sheetName a name of a sheet in that workbook
*/
int getExternalSheetIndex(String workbookName, String sheetName);
}

View File

@ -19,6 +19,7 @@ package org.apache.poi.ss.formula;
import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
/**
* Abstracts a workbook for the purpose of converting formula to text.<br/>
@ -29,6 +30,10 @@ import org.apache.poi.hssf.record.formula.NameXPtg;
*/
public interface FormulaRenderingWorkbook {
/**
* @return <code>null</code> if externSheetIndex refers to a sheet inside the current workbook
*/
ExternalSheet getExternalSheet(int externSheetIndex);
String getSheetNameByExternSheet(int externSheetIndex);
String resolveNameXText(NameXPtg nameXPtg);
String getNameText(NamePtg namePtg);

View File

@ -19,6 +19,7 @@ package org.apache.poi.ss.formula;
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
import org.apache.poi.hssf.record.formula.ControlPtg;
import org.apache.poi.hssf.record.formula.RangePtg;
import org.apache.poi.hssf.record.formula.ValueOperatorPtg;
import org.apache.poi.hssf.record.formula.Ptg;
@ -115,6 +116,10 @@ final class OperandClassTransformer {
return;
}
if (children.length > 0) {
if (token == RangePtg.instance) {
// TODO is any token transformation required under the various ref operators?
return;
}
throw new IllegalStateException("Node should not have any children");
}

View File

@ -40,6 +40,7 @@ import org.apache.poi.hssf.record.formula.OperationPtg;
import org.apache.poi.hssf.record.formula.PercentPtg;
import org.apache.poi.hssf.record.formula.PowerPtg;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.RangePtg;
import org.apache.poi.hssf.record.formula.SubtractPtg;
import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
@ -57,6 +58,7 @@ import org.apache.poi.hssf.record.formula.eval.NotEqualEval;
import org.apache.poi.hssf.record.formula.eval.OperationEval;
import org.apache.poi.hssf.record.formula.eval.PercentEval;
import org.apache.poi.hssf.record.formula.eval.PowerEval;
import org.apache.poi.hssf.record.formula.eval.RangeEval;
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;
@ -101,6 +103,7 @@ final class OperationEvaluatorFactory {
add(m, SubtractPtg.class, SubtractEval.instance);
add(m, UnaryMinusPtg.class, UnaryMinusEval.instance);
add(m, UnaryPlusPtg.class, UnaryPlusEval.instance);
add(m, RangePtg.class, RangeEval.instance);
return m;
}

View File

@ -22,12 +22,18 @@ import java.util.Map;
import java.util.Stack;
import org.apache.poi.hssf.record.formula.Area3DPtg;
import org.apache.poi.hssf.record.formula.AreaErrPtg;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.AttrPtg;
import org.apache.poi.hssf.record.formula.BoolPtg;
import org.apache.poi.hssf.record.formula.ControlPtg;
import org.apache.poi.hssf.record.formula.DeletedArea3DPtg;
import org.apache.poi.hssf.record.formula.DeletedRef3DPtg;
import org.apache.poi.hssf.record.formula.ErrPtg;
import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.IntPtg;
import org.apache.poi.hssf.record.formula.MemErrPtg;
import org.apache.poi.hssf.record.formula.MemFuncPtg;
import org.apache.poi.hssf.record.formula.MissingArgPtg;
import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg;
@ -35,6 +41,7 @@ import org.apache.poi.hssf.record.formula.NumberPtg;
import org.apache.poi.hssf.record.formula.OperationPtg;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.Ref3DPtg;
import org.apache.poi.hssf.record.formula.RefErrorPtg;
import org.apache.poi.hssf.record.formula.RefPtg;
import org.apache.poi.hssf.record.formula.StringPtg;
import org.apache.poi.hssf.record.formula.UnionPtg;
@ -53,6 +60,7 @@ 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.util.CellReference;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
@ -68,13 +76,14 @@ import org.apache.poi.ss.usermodel.Sheet;
*
* @author Josh Micich
*/
public class WorkbookEvaluator {
public final class WorkbookEvaluator {
private final EvaluationWorkbook _workbook;
private final EvaluationCache _cache;
private EvaluationCache _cache;
private final IEvaluationListener _evaluationListener;
private final Map _sheetIndexesBySheet;
private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
public WorkbookEvaluator(EvaluationWorkbook workbook) {
this (workbook, null);
@ -84,6 +93,7 @@ public class WorkbookEvaluator {
_evaluationListener = evaluationListener;
_cache = new EvaluationCache(evaluationListener);
_sheetIndexesBySheet = new IdentityHashMap();
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
}
/**
@ -101,6 +111,21 @@ public class WorkbookEvaluator {
System.out.println(s);
}
}
/* package */ void attachToEnvironment(CollaboratingWorkbooksEnvironment collaboratingWorkbooksEnvironment, EvaluationCache cache) {
_collaboratingWorkbookEnvironment = collaboratingWorkbooksEnvironment;
_cache = cache;
}
/* package */ CollaboratingWorkbooksEnvironment getEnvironment() {
return _collaboratingWorkbookEnvironment;
}
/* package */ void detachFromEnvironment() {
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
_cache = new EvaluationCache(_evaluationListener);
}
/* package */ IEvaluationListener getEvaluationListener() {
return _evaluationListener;
}
/**
* Should be called whenever there are changes to input cells in the evaluated workbook.
@ -123,7 +148,7 @@ public class WorkbookEvaluator {
throw new IllegalArgumentException("value must not be null");
}
int sheetIndex = getSheetIndex(sheet);
_cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
_cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
}
/**
@ -132,13 +157,17 @@ public class WorkbookEvaluator {
*/
public void notifySetFormula(Sheet sheet, int rowIndex, int columnIndex) {
int sheetIndex = getSheetIndex(sheet);
_cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
_cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
}
private int getSheetIndex(Sheet sheet) {
Integer result = (Integer) _sheetIndexesBySheet.get(sheet);
if (result == null) {
result = new Integer(_workbook.getSheetIndex(sheet));
int sheetIndex = _workbook.getSheetIndex(sheet);
if (sheetIndex < 0) {
throw new RuntimeException("Specified sheet from a different book");
}
result = new Integer(sheetIndex);
_sheetIndexesBySheet.put(sheet, result);
}
return result.intValue();
@ -146,7 +175,7 @@ public class WorkbookEvaluator {
public ValueEval evaluate(Cell srcCell) {
int sheetIndex = getSheetIndex(srcCell.getSheet());
CellLocation cellLoc = new CellLocation(sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
return internalEvaluate(srcCell, cellLoc, new EvaluationTracker(_cache));
}
@ -181,10 +210,10 @@ public class WorkbookEvaluator {
isPlainFormulaCell = false;
Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
if(evalListener == null) {
result = evaluateCell(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
result = evaluateFormula(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
} else {
evalListener.onStartEvaluate(sheetIndex, rowIndex, columnIndex, ptgs);
result = evaluateCell(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
result = evaluateFormula(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
evalListener.onEndEvaluate(sheetIndex, rowIndex, columnIndex, result);
}
}
@ -225,17 +254,31 @@ public class WorkbookEvaluator {
}
throw new RuntimeException("Unexpected cell type (" + cellType + ")");
}
private ValueEval evaluateCell(int sheetIndex, int srcRowNum, short srcColNum, Ptg[] ptgs, EvaluationTracker tracker) {
// visibility raised for testing
/* package */ ValueEval evaluateFormula(int sheetIndex, int srcRowNum, int srcColNum, Ptg[] ptgs, EvaluationTracker tracker) {
Stack stack = new Stack();
for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
// since we don't know how to handle these yet :(
Ptg ptg = ptgs[i];
if (ptg instanceof AttrPtg) {
AttrPtg attrPtg = (AttrPtg) ptg;
if (attrPtg.isSum()) {
// Excel prefers to encode 'SUM()' as a tAttr token, but this evaluator
// expects the equivalent function token
byte nArgs = 1; // tAttrSum always has 1 parameter
ptg = new FuncVarPtg("SUM", nArgs);
}
}
if (ptg instanceof ControlPtg) {
// skip Parentheses, Attr, etc
continue;
}
if (ptg instanceof MemFuncPtg) {
// can ignore, rest of tokens for this expression are in OK RPN order
continue;
}
if (ptg instanceof MemErrPtg) { continue; }
if (ptg instanceof MissingArgPtg) {
// TODO - might need to push BlankEval or MissingArgEval
@ -289,7 +332,7 @@ public class WorkbookEvaluator {
* @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>,
* <tt>BlankEval</tt> or <tt>ErrorEval</tt>. Never <code>null</code>.
*/
private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) {
private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, int srcColNum) {
if (evaluationResult instanceof RefEval) {
RefEval rv = (RefEval) evaluationResult;
return rv.getInnerValueEval();
@ -321,6 +364,20 @@ public class WorkbookEvaluator {
}
return operation.evaluate(ops, srcRowNum, (short)srcColNum);
}
private SheetRefEvaluator createExternSheetRefEvaluator(EvaluationTracker tracker,
ExternSheetReferenceToken ptg) {
int externSheetIndex = ptg.getExternSheetIndex();
ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
if (externalSheet != null) {
WorkbookEvaluator otherEvaluator = _collaboratingWorkbookEnvironment.getWorkbookEvaluator(externalSheet.getWorkbookName());
EvaluationWorkbook otherBook = otherEvaluator._workbook;
int otherSheetIndex = otherBook.getSheetIndex(externalSheet.getSheetName());
return new SheetRefEvaluator(otherEvaluator, tracker, otherBook, otherSheetIndex);
}
int otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex);
return new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
}
/**
* returns an appropriate Eval impl instance for the Ptg. The Ptg must be
@ -329,6 +386,8 @@ public class WorkbookEvaluator {
* passed here!
*/
private Eval getEvalForPtg(Ptg ptg, int sheetIndex, EvaluationTracker tracker) {
// consider converting all these (ptg instanceof XxxPtg) expressions to (ptg.getClass() == XxxPtg.class)
if (ptg instanceof NamePtg) {
// named ranges, macro functions
NamePtg namePtg = (NamePtg) ptg;
@ -361,16 +420,18 @@ public class WorkbookEvaluator {
if (ptg instanceof ErrPtg) {
return ErrorEval.valueOf(((ErrPtg) ptg).getErrorCode());
}
if (ptg instanceof AreaErrPtg ||ptg instanceof RefErrorPtg
|| ptg instanceof DeletedArea3DPtg || ptg instanceof DeletedRef3DPtg) {
return ErrorEval.REF_INVALID;
}
if (ptg instanceof Ref3DPtg) {
Ref3DPtg refPtg = (Ref3DPtg) ptg;
int otherSheetIndex = _workbook.convertFromExternSheetIndex(refPtg.getExternSheetIndex());
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, refPtg);
return new LazyRefEval(refPtg, sre);
}
if (ptg instanceof Area3DPtg) {
Area3DPtg aptg = (Area3DPtg) ptg;
int otherSheetIndex = _workbook.convertFromExternSheetIndex(aptg.getExternSheetIndex());
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, aptg);
return new LazyAreaEval(aptg, sre);
}
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, sheetIndex);
@ -410,7 +471,7 @@ public class WorkbookEvaluator {
} else {
cell = row.getCell(columnIndex);
}
CellLocation cellLoc = new CellLocation(sheetIndex, rowIndex, columnIndex);
CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex);
tracker.acceptDependency(cellLoc);
return internalEvaluate(cell, cellLoc, tracker);
}

View File

@ -103,8 +103,45 @@ public class AreaReference {
* Creates an area ref from a pair of Cell References.
*/
public AreaReference(CellReference topLeft, CellReference botRight) {
boolean swapRows = topLeft.getRow() > botRight.getRow();
boolean swapCols = topLeft.getCol() > botRight.getCol();
if (swapRows || swapCols) {
int firstRow;
int lastRow;
int firstColumn;
int lastColumn;
boolean firstRowAbs;
boolean lastRowAbs;
boolean firstColAbs;
boolean lastColAbs;
if (swapRows) {
firstRow = botRight.getRow();
firstRowAbs = botRight.isRowAbsolute();
lastRow = topLeft.getRow();
lastRowAbs = topLeft.isRowAbsolute();
} else {
firstRow = topLeft.getRow();
firstRowAbs = topLeft.isRowAbsolute();
lastRow = botRight.getRow();
lastRowAbs = botRight.isRowAbsolute();
}
if (swapCols) {
firstColumn = botRight.getCol();
firstColAbs = botRight.isColAbsolute();
lastColumn = topLeft.getCol();
lastColAbs = topLeft.isColAbsolute();
} else {
firstColumn = topLeft.getCol();
firstColAbs = topLeft.isColAbsolute();
lastColumn = botRight.getCol();
lastColAbs = botRight.isColAbsolute();
}
_firstCell = new CellReference(firstRow, firstColumn, firstRowAbs, firstColAbs);
_lastCell = new CellReference(lastRow, lastColumn, lastRowAbs, lastColAbs);
} else {
_firstCell = topLeft;
_lastCell = botRight;
}
_isSingleCell = false;
}

View File

@ -34,6 +34,7 @@ public class CellReference {
public static final class NameType {
public static final int CELL = 1;
public static final int NAMED_RANGE = 2;
public static final int COLUMN = 3;
public static final int BAD_CELL_OR_NAMED_RANGE = -1;
}
@ -45,10 +46,16 @@ public class CellReference {
private static final char SPECIAL_NAME_DELIMITER = '\'';
/**
* Matches a run of letters followed by a run of digits. The run of letters is group 1 and the
* run of digits is group 2. Each group may optionally be prefixed with a single '$'.
* Matches a run of one or more letters followed by a run of one or more digits.
* The run of letters is group 1 and the run of digits is group 2.
* Each group may optionally be prefixed with a single '$'.
*/
private static final Pattern CELL_REF_PATTERN = Pattern.compile("\\$?([A-Za-z]+)\\$?([0-9]+)");
/**
* Matches a run of one or more letters. The run of letters is group 1.
* The text may optionally be prefixed with a single '$'.
*/
private static final Pattern COLUMN_REF_PATTERN = Pattern.compile("\\$?([A-Za-z]+)");
/**
* Named range names must start with a letter or underscore. Subsequent characters may include
* digits or dot. (They can even end in dot).
@ -94,10 +101,10 @@ public class CellReference {
}
public CellReference(int pRow, int pCol) {
this(pRow, pCol & 0xFFFF, false, false);
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) {
@ -134,7 +141,6 @@ public class CellReference {
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.
@ -144,7 +150,8 @@ public class CellReference {
* 'IV' -> 255
* @return zero based column index
*/
protected static int convertColStringToIndex(String ref) {
public static int convertColStringToIndex(String ref) {
int pos = 0;
int retval=0;
for (int k = ref.length()-1; k >= 0; k--) {
@ -203,7 +210,7 @@ public class CellReference {
// named range name
// This behaviour is a little weird. For example, "IW123" is a valid named range name
// because the column "IW" is beyond the maximum "IV". Note - this behaviour is version
// dependent. In Excel 2007, "IW123" is not a valid named range name.
// dependent. In BIFF12, "IW123" is not a valid named range name, but in BIFF8 it is.
if (str.indexOf(ABSOLUTE_REFERENCE_MARKER) >= 0) {
// Of course, named range names cannot have '$'
return NameType.BAD_CELL_OR_NAMED_RANGE;
@ -212,11 +219,17 @@ public class CellReference {
}
private static int validateNamedRangeName(String str) {
Matcher colMatcher = COLUMN_REF_PATTERN.matcher(str);
if (colMatcher.matches()) {
String colStr = colMatcher.group(1);
if (isColumnWithnRange(colStr)) {
return NameType.COLUMN;
}
}
if (!NAMED_RANGE_NAME_PATTERN.matcher(str).matches()) {
return NameType.BAD_CELL_OR_NAMED_RANGE;
}
return NameType.NAMED_RANGE;
}
@ -257,23 +270,13 @@ public class CellReference {
* @return <code>true</code> if the row and col parameters are within range of a BIFF8 spreadsheet.
*/
public static boolean cellReferenceIsWithinRange(String colStr, String rowStr) {
int numberOfLetters = colStr.length();
if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) {
// "Sheet1" case etc
return false; // that was easy
if (!isColumnWithnRange(colStr)) {
return false;
}
int nDigits = rowStr.length();
if(nDigits > BIFF8_LAST_ROW_TEXT_LEN) {
return false;
}
if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) {
if(colStr.toUpperCase().compareTo(BIFF8_LAST_COLUMN) > 0) {
return false;
}
} else {
// apparent column name has less chars than max
// no need to check range
}
if(nDigits == BIFF8_LAST_ROW_TEXT_LEN) {
// ASCII comparison is valid if digit count is same
@ -288,6 +291,23 @@ public class CellReference {
return true;
}
private static boolean isColumnWithnRange(String colStr) {
int numberOfLetters = colStr.length();
if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) {
// "Sheet1" case etc
return false; // that was easy
}
if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) {
if(colStr.toUpperCase().compareTo(BIFF8_LAST_COLUMN) > 0) {
return false;
}
} else {
// apparent column name has less chars than max
// no need to check range
}
return true;
}
/**
* Separates the row from the columns and returns an array of three Strings. The first element
* is the sheet name. Only the first element may be null. The second element in is the column
@ -437,7 +457,7 @@ public class CellReference {
* Appends cell reference with '$' markers for absolute values as required.
* Sheet name is not included.
*/
protected void appendCellReference(StringBuffer sb) {
/* package */ void appendCellReference(StringBuffer sb) {
if(_isColAbs) {
sb.append(ABSOLUTE_REFERENCE_MARKER);
}

View File

@ -57,10 +57,6 @@ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
return convertToExternalSheetIndex(sheetIndex);
}
public EvaluationName getName(int index) {
return new Name(_uBook.getNameAt(index), index, this);
}
public EvaluationName getName(String name) {
for(int i=0; i < _uBook.getNumberOfNames(); i++) {
String nameText = _uBook.getNameName(i);
@ -88,14 +84,15 @@ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
return _uBook.getSheetAt(sheetIndex);
}
/**
* Doesn't do anything - returns the same index
* TODO - figure out if this is a ole2 specific thing, or
* if we need to do something proper here too!
*/
public Sheet getSheetByExternSheetIndex(int externSheetIndex) {
int sheetIndex = convertFromExternalSheetIndex(externSheetIndex);
return _uBook.getSheetAt(sheetIndex);
public ExternalSheet getExternalSheet(int externSheetIndex) {
// TODO Auto-generated method stub
return null;
}
public int getExternalSheetIndex(String workbookName, String sheetName) {
throw new RuntimeException("not implemented yet");
}
public int getSheetIndex(String sheetName) {
return _uBook.getSheetIndex(sheetName);
}
public Workbook getWorkbook() {

View File

@ -28,7 +28,7 @@ import org.apache.poi.hssf.model.AllModelTests;
import org.apache.poi.hssf.record.AllRecordTests;
import org.apache.poi.hssf.usermodel.AllUserModelTests;
import org.apache.poi.hssf.util.AllHSSFUtilTests;
import org.apache.poi.ss.formula.TestEvaluationCache;
import org.apache.poi.ss.formula.AllSSFormulaTests;
/**
* Test Suite for all sub-packages of org.apache.poi.hssf<br/>
@ -53,7 +53,7 @@ public final class HSSFTests {
}
suite.addTest(new TestSuite(TestEventRecordFactory.class));
suite.addTest(new TestSuite(TestModelFactory.class));
suite.addTest(new TestSuite(TestEvaluationCache.class));
suite.addTest(AllSSFormulaTests.suite());
return suite;
}
}

View File

@ -43,11 +43,13 @@ import org.apache.poi.hssf.record.formula.NumberPtg;
import org.apache.poi.hssf.record.formula.PercentPtg;
import org.apache.poi.hssf.record.formula.PowerPtg;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.Ref3DPtg;
import org.apache.poi.hssf.record.formula.RefPtg;
import org.apache.poi.hssf.record.formula.StringPtg;
import org.apache.poi.hssf.record.formula.SubtractPtg;
import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
import org.apache.poi.hssf.usermodel.FormulaExtractor;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
@ -880,6 +882,66 @@ public final class TestFormulaParser extends TestCase {
Object[][] values = aptg.getTokenArrayValues();
assertEquals(ErrorConstant.valueOf(HSSFErrorConstants.ERROR_REF), values[0][3]);
assertEquals(Boolean.FALSE, values[1][0]);
}
public void testRangeOperator() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet();
HSSFCell cell = sheet.createRow(0).createCell(0);
wb.setSheetName(0, "Sheet1");
cell.setCellFormula("Sheet1!B$4:Sheet1!$C1"); // explicit range ':' operator
assertEquals("Sheet1!B$4:Sheet1!$C1", cell.getCellFormula());
cell.setCellFormula("Sheet1!B$4:$C1"); // plain area ref
assertEquals("Sheet1!B1:$C$4", cell.getCellFormula()); // note - area ref is normalised
cell.setCellFormula("Sheet1!$C1...B$4"); // different syntax for plain area ref
assertEquals("Sheet1!B1:$C$4", cell.getCellFormula());
// with funny sheet name
wb.setSheetName(0, "A1...A2");
cell.setCellFormula("A1...A2!B1");
assertEquals("A1...A2!B1", cell.getCellFormula());
}
public void testBooleanNamedSheet() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("true");
HSSFCell cell = sheet.createRow(0).createCell(0);
cell.setCellFormula("'true'!B2");
assertEquals("'true'!B2", cell.getCellFormula());
}
public void testParseExternalWorkbookReference() {
HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls");
HSSFCell cell = wbA.getSheetAt(0).getRow(0).getCell(0);
// make sure formula in sample is as expected
assertEquals("[multibookFormulaB.xls]BSheet1!B1", cell.getCellFormula());
Ptg[] expectedPtgs = FormulaExtractor.getPtgs(cell);
confirmSingle3DRef(expectedPtgs, 1);
// now try (re-)parsing the formula
Ptg[] actualPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]BSheet1!B1", wbA);
confirmSingle3DRef(actualPtgs, 1); // externalSheetIndex 1 -> BSheet1
// try parsing a formula pointing to a different external sheet
Ptg[] otherPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]AnotherSheet!B1", wbA);
confirmSingle3DRef(otherPtgs, 0); // externalSheetIndex 0 -> AnotherSheet
// try setting the same formula in a cell
cell.setCellFormula("[multibookFormulaB.xls]AnotherSheet!B1");
assertEquals("[multibookFormulaB.xls]AnotherSheet!B1", cell.getCellFormula());
}
private static void confirmSingle3DRef(Ptg[] ptgs, int expectedExternSheetIndex) {
assertEquals(1, ptgs.length);
Ptg ptg0 = ptgs[0];
assertEquals(Ref3DPtg.class, ptg0.getClass());
assertEquals(expectedExternSheetIndex, ((Ref3DPtg)ptg0).getExternSheetIndex());
}
}
}

View File

@ -78,7 +78,7 @@ public final class TestSupBookRecord extends TestCase {
assertEquals( 34, record.getRecordSize() ); //sid+size+data
assertEquals("testURL", record.getURL().getString());
assertEquals("testURL", record.getURL());
UnicodeString[] sheetNames = record.getSheetNames();
assertEquals(2, sheetNames.length);
assertEquals("Sheet1", sheetNames[0].getString());

View File

@ -20,16 +20,12 @@ package org.apache.poi.hssf.record.formula;
import junit.framework.TestCase;
/**
* Tests for SheetNameFormatter
* Tests for {@link SheetNameFormatter}
*
* @author Josh Micich
*/
public final class TestSheetNameFormatter extends TestCase {
public TestSheetNameFormatter(String testName) {
super(testName);
}
private static void confirmFormat(String rawSheetName, String expectedSheetNameEncoding) {
assertEquals(expectedSheetNameEncoding, SheetNameFormatter.format(rawSheetName));
}
@ -55,6 +51,16 @@ public final class TestSheetNameFormatter extends TestCase {
confirmFormat("TAXRETURN19980415", "TAXRETURN19980415");
}
public void testBooleanLiterals() {
confirmFormat("TRUE", "'TRUE'");
confirmFormat("FALSE", "'FALSE'");
confirmFormat("True", "'True'");
confirmFormat("fAlse", "'fAlse'");
confirmFormat("Yes", "Yes");
confirmFormat("No", "No");
}
private static void confirmCellNameMatch(String rawSheetName, boolean expected) {
assertEquals(expected, SheetNameFormatter.nameLooksLikePlainCellReference(rawSheetName));
}

View File

@ -37,6 +37,7 @@ public class AllFormulaEvalTests {
result.addTestSuite(TestFormulaBugs.class);
result.addTestSuite(TestFormulasFromSpreadsheet.class);
result.addTestSuite(TestPercentEval.class);
result.addTestSuite(TestRangeEval.class);
result.addTestSuite(TestUnaryPlusEval.class);
return result;
}

View File

@ -146,7 +146,7 @@ public final class TestFormulaBugs extends TestCase {
throw new AssertionFailedError("Identified bug 42448");
}
assertEquals("SUMPRODUCT(A!C7:C67,B8:B68)/B69", cell.getCellFormula());
assertEquals("SUMPRODUCT(A!C7:A!C67,B8:B68)/B69", cell.getCellFormula());
// might as well evaluate the sucker...

View File

@ -0,0 +1,95 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.AreaI;
import org.apache.poi.hssf.record.formula.AreaI.OffsetArea;
import org.apache.poi.hssf.util.AreaReference;
import org.apache.poi.hssf.util.CellReference;
import junit.framework.TestCase;
/**
* Test for unary plus operator evaluator.
*
* @author Josh Micich
*/
public final class TestRangeEval extends TestCase {
public void testPermutations() {
confirm("B3", "D7", "B3:D7");
confirm("B1", "B1", "B1:B1");
confirm("B7", "D3", "B3:D7");
confirm("D3", "B7", "B3:D7");
confirm("D7", "B3", "B3:D7");
}
private static void confirm(String refA, String refB, String expectedAreaRef) {
Eval[] args = {
createRefEval(refA),
createRefEval(refB),
};
AreaReference ar = new AreaReference(expectedAreaRef);
Eval result = RangeEval.instance.evaluate(args, 0, (short)0);
assertTrue(result instanceof AreaEval);
AreaEval ae = (AreaEval) result;
assertEquals(ar.getFirstCell().getRow(), ae.getFirstRow());
assertEquals(ar.getLastCell().getRow(), ae.getLastRow());
assertEquals(ar.getFirstCell().getCol(), ae.getFirstColumn());
assertEquals(ar.getLastCell().getCol(), ae.getLastColumn());
}
private static Eval createRefEval(String refStr) {
CellReference cr = new CellReference(refStr);
return new MockRefEval(cr.getRow(), cr.getCol());
}
private static final class MockRefEval extends RefEvalBase {
public MockRefEval(int rowIndex, int columnIndex) {
super(rowIndex, columnIndex);
}
public ValueEval getInnerValueEval() {
throw new RuntimeException("not expected to be called during this test");
}
public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx,
int relLastColIx) {
AreaI area = new OffsetArea(getRow(), getColumn(),
relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);
return new MockAreaEval(area);
}
}
private static final class MockAreaEval extends AreaEvalBase {
public MockAreaEval(AreaI ptg) {
super(ptg);
}
public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) {
throw new RuntimeException("not expected to be called during this test");
}
public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx,
int relLastColIx) {
throw new RuntimeException("not expected to be called during this test");
}
}
}

View File

@ -18,7 +18,6 @@
package org.apache.poi.hssf.usermodel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
@ -42,12 +41,8 @@ public final class TestFormulas extends TestCase {
/**
* Add 1+1 -- WHoohoo!
*/
public void testBasicAddIntegers() {
public void testBasicAddIntegers()
throws Exception {
File file = TempFile.createTempFile("testFormula",".xls");
FileOutputStream out = new FileOutputStream(file);
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet();
HSSFRow r = null;
@ -58,95 +53,78 @@ public final class TestFormulas extends TestCase {
c = r.createCell(1);
c.setCellFormula(1 + "+" + 1);
wb.write(out);
out.close();
FileInputStream in = new FileInputStream(file);
wb = new HSSFWorkbook(in);
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
s = wb.getSheetAt(0);
r = s.getRow(1);
c = r.getCell(1);
assertTrue("Formula is as expected",("1+1".equals(c.getCellFormula())));
in.close();
}
/**
* Add various integers
*/
public void testAddIntegers()
throws Exception {
public void testAddIntegers() {
binomialOperator("+");
}
/**
* Multiply various integers
*/
public void testMultplyIntegers()
throws Exception {
public void testMultplyIntegers() {
binomialOperator("*");
}
/**
* Subtract various integers
*/
public void testSubtractIntegers()
throws Exception {
public void testSubtractIntegers() {
binomialOperator("-");
}
/**
* Subtract various integers
*/
public void testDivideIntegers()
throws Exception {
public void testDivideIntegers() {
binomialOperator("/");
}
/**
* Exponentialize various integers;
*/
public void testPowerIntegers()
throws Exception {
public void testPowerIntegers() {
binomialOperator("^");
}
/**
* Concatinate two numbers 1&2 = 12
* Concatenate two numbers 1&2 = 12
*/
public void testConcatIntegers()
throws Exception {
public void testConcatIntegers() {
binomialOperator("&");
}
/**
* tests 1*2+3*4
*/
public void testOrderOfOperationsMultiply()
throws Exception {
public void testOrderOfOperationsMultiply() {
orderTest("1*2+3*4");
}
/**
* tests 1*2+3^4
*/
public void testOrderOfOperationsPower()
throws Exception {
public void testOrderOfOperationsPower() {
orderTest("1*2+3^4");
}
/**
* Tests that parenthesis are obeyed
*/
public void testParenthesis()
throws Exception {
public void testParenthesis() {
orderTest("(1*3)+2+(1+2)*(3^4)^5");
}
public void testReferencesOpr()
throws Exception {
public void testReferencesOpr() {
String[] operation = new String[] {
"+", "-", "*", "/", "^", "&"
};
@ -159,16 +137,12 @@ public final class TestFormulas extends TestCase {
* Tests creating a file with floating point in a formula.
*
*/
public void testFloat()
throws Exception {
public void testFloat() {
floatTest("*");
floatTest("/");
}
private void floatTest(String operator)
throws Exception {
File file = TempFile.createTempFile("testFormulaFloat",".xls");
FileOutputStream out = new FileOutputStream(file);
private static void floatTest(String operator) {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet();
HSSFRow r = null;
@ -180,10 +154,10 @@ public final class TestFormulas extends TestCase {
c = r.createCell(1);
c.setCellFormula(""+Float.MIN_VALUE + operator + Float.MIN_VALUE);
for (short x = 1; x < Short.MAX_VALUE && x > 0; x=(short)(x*2) ) {
for (int x = 1; x < Short.MAX_VALUE && x > 0; x=(short)(x*2) ) {
r = s.createRow(x);
for (short y = 1; y < 256 && y > 0; y= (short) (y +2)) {
for (int y = 1; y < 256 && y > 0; y= (short) (y +2)) {
c = r.createCell(y);
c.setCellFormula("" + x+"."+y + operator + y +"."+x);
@ -196,69 +170,48 @@ public final class TestFormulas extends TestCase {
c = r.createCell(0);
c.setCellFormula("" + Float.MAX_VALUE + operator + Float.MAX_VALUE);
}
wb.write(out);
out.close();
assertTrue("file exists",file.exists());
out=null;wb=null; //otherwise we get out of memory error!
floatVerify(operator,file);
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
floatVerify(operator, wb);
}
private void floatVerify(String operator, File file)
throws Exception {
private static void floatVerify(String operator, HSSFWorkbook wb) {
FileInputStream in = new FileInputStream(file);
HSSFWorkbook wb = new HSSFWorkbook(in);
HSSFSheet s = wb.getSheetAt(0);
HSSFRow r = null;
HSSFCell c = null;
// dont know how to check correct result .. for the moment, we just verify that the file can be read.
// don't know how to check correct result .. for the moment, we just verify that the file can be read.
for (short x = 1; x < Short.MAX_VALUE && x > 0; x=(short)(x*2)) {
r = s.getRow(x);
for (int x = 1; x < Short.MAX_VALUE && x > 0; x=(short)(x*2)) {
HSSFRow r = s.getRow(x);
for (short y = 1; y < 256 && y > 0; y=(short)(y+2)) {
for (int y = 1; y < 256 && y > 0; y=(short)(y+2)) {
c = r.getCell(y);
HSSFCell c = r.getCell(y);
assertTrue("got a formula",c.getCellFormula()!=null);
assertTrue("loop Formula is as expected "+x+"."+y+operator+y+"."+x+"!="+c.getCellFormula(),(
(""+x+"."+y+operator+y+"."+x).equals(c.getCellFormula()) ));
}
}
}
in.close();
assertTrue("file exists",file.exists());
}
public void testAreaSum()
throws Exception {
public void testAreaSum() {
areaFunctionTest("SUM");
}
public void testAreaAverage()
throws Exception {
public void testAreaAverage() {
areaFunctionTest("AVERAGE");
}
public void testRefArraySum()
throws Exception {
public void testRefArraySum() {
refArrayFunctionTest("SUM");
}
public void testAreaArraySum()
throws Exception {
public void testAreaArraySum() {
refAreaArrayFunctionTest("SUM");
}
private void operationRefTest(String operator)
throws Exception {
File file = TempFile.createTempFile("testFormula",".xls");
FileOutputStream out = new FileOutputStream(file);
private static void operationRefTest(String operator) {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet();
HSSFRow r = null;
@ -317,21 +270,16 @@ public final class TestFormulas extends TestCase {
c.setCellFormula("" + "B1" + operator + "IV255");
}
wb.write(out);
out.close();
assertTrue("file exists",file.exists());
operationalRefVerify(operator,file);
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
operationalRefVerify(operator, wb);
}
/**
* Opens the sheet we wrote out by binomialOperator and makes sure the formulas
* all match what we expect (x operator y)
*/
private void operationalRefVerify(String operator, File file)
throws Exception {
private static void operationalRefVerify(String operator, HSSFWorkbook wb) {
FileInputStream in = new FileInputStream(file);
HSSFWorkbook wb = new HSSFWorkbook(in);
HSSFSheet s = wb.getSheetAt(0);
HSSFRow r = null;
HSSFCell c = null;
@ -345,46 +293,43 @@ public final class TestFormulas extends TestCase {
));
for (short x = 1; x < Short.MAX_VALUE && x > 0; x=(short)(x*2)) {
for (int x = 1; x < Short.MAX_VALUE && x > 0; x=(short)(x*2)) {
r = s.getRow(x);
for (short y = 1; y < 256 && y > 0; y++) {
for (int y = 1; y < 256 && y > 0; y++) {
String ref=null;
String ref2=null;
short refx1=0;
short refy1=0;
short refx2=0;
short refy2=0;
int refx1;
int refy1;
int refx2;
int refy2;
if (x +50 < Short.MAX_VALUE) {
refx1=(short)(x+50);
refx2=(short)(x+46);
refx1=x+50;
refx2=x+46;
} else {
refx1=(short)(x-4);
refx2=(short)(x-3);
refx1=x-4;
refx2=x-3;
}
if (y+50 < 255) {
refy1=(short)(y+50);
refy2=(short)(y+49);
refy1=y+50;
refy2=y+49;
} else {
refy1=(short)(y-4);
refy2=(short)(y-3);
refy1=y-4;
refy2=y-3;
}
c = r.getCell(y);
CellReference cr= new CellReference(refx1, refy1, false, false);
String ref=cr.formatAsString();
ref=cr.formatAsString();
cr=new CellReference(refx2,refy2, false, false);
ref2=cr.formatAsString();
String ref2=cr.formatAsString();
assertTrue("loop Formula is as expected "+ref+operator+ref2+"!="+c.getCellFormula(),(
(""+ref+operator+ref2).equals(c.getCellFormula())
)
);
}
}
@ -392,13 +337,7 @@ public final class TestFormulas extends TestCase {
r = s.getRow(0);
c = r.getCell(0);
assertTrue("maxval Formula is as expected",(
("B1"+operator+"IV255").equals(c.getCellFormula())
)
);
in.close();
assertTrue("file exists",file.exists());
assertEquals("B1"+operator+"IV255", c.getCellFormula());
}
@ -406,10 +345,7 @@ public final class TestFormulas extends TestCase {
/**
* tests order wrting out == order writing in for a given formula
*/
private void orderTest(String formula)
throws Exception {
File file = TempFile.createTempFile("testFormula",".xls");
FileOutputStream out = new FileOutputStream(file);
private static void orderTest(String formula) {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet();
HSSFRow r = null;
@ -420,12 +356,7 @@ public final class TestFormulas extends TestCase {
c = r.createCell(1);
c.setCellFormula(formula);
wb.write(out);
out.close();
assertTrue("file exists",file.exists());
FileInputStream in = new FileInputStream(file);
wb = new HSSFWorkbook(in);
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
s = wb.getSheetAt(0);
//get our minimum values
@ -434,8 +365,6 @@ public final class TestFormulas extends TestCase {
assertTrue("minval Formula is as expected",
formula.equals(c.getCellFormula())
);
in.close();
}
/**
@ -443,10 +372,7 @@ public final class TestFormulas extends TestCase {
* huge set of x operator y formulas. Next we call binomialVerify and verify
* that they are all how we expect.
*/
private void binomialOperator(String operator)
throws Exception {
File file = TempFile.createTempFile("testFormula",".xls");
FileOutputStream out = new FileOutputStream(file);
private static void binomialOperator(String operator) {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet();
HSSFRow r = null;
@ -474,23 +400,15 @@ public final class TestFormulas extends TestCase {
c = r.createCell(0);
c.setCellFormula("" + Short.MAX_VALUE + operator + Short.MAX_VALUE);
}
wb.write(out);
out.close();
assertTrue("file exists",file.exists());
binomialVerify(operator,file);
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
binomialVerify(operator, wb);
}
/**
* Opens the sheet we wrote out by binomialOperator and makes sure the formulas
* all match what we expect (x operator y)
*/
private void binomialVerify(String operator, File file)
throws Exception {
FileInputStream in = new FileInputStream(file);
HSSFWorkbook wb = new HSSFWorkbook(in);
private static void binomialVerify(String operator, HSSFWorkbook wb) {
HSSFSheet s = wb.getSheetAt(0);
HSSFRow r = null;
HSSFCell c = null;
@ -502,10 +420,10 @@ public final class TestFormulas extends TestCase {
( ("1"+operator+"1").equals(c.getCellFormula())
));
for (short x = 1; x < Short.MAX_VALUE && x > 0; x=(short)(x*2)) {
for (int x = 1; x < Short.MAX_VALUE && x > 0; x=(short)(x*2)) {
r = s.getRow(x);
for (short y = 1; y < 256 && y > 0; y++) {
for (int y = 1; y < 256 && y > 0; y++) {
c = r.getCell(y);
@ -513,8 +431,6 @@ public final class TestFormulas extends TestCase {
(""+x+operator+y).equals(c.getCellFormula())
)
);
}
}
@ -522,27 +438,17 @@ public final class TestFormulas extends TestCase {
r = s.getRow(0);
c = r.getCell(0);
assertTrue("maxval Formula is as expected",(
(""+Short.MAX_VALUE+operator+Short.MAX_VALUE).equals(c.getCellFormula())
)
);
in.close();
assertTrue("file exists",file.exists());
}
/**
* Writes a function then tests to see if its correct
*
*/
public void areaFunctionTest(String function)
throws Exception {
public static void areaFunctionTest(String function) {
File file = TempFile.createTempFile("testFormulaAreaFunction"+function,".xls");
FileOutputStream out = new FileOutputStream(file);
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet();
HSSFRow r = null;
@ -554,13 +460,7 @@ public final class TestFormulas extends TestCase {
c = r.createCell(0);
c.setCellFormula(function+"(A2:A3)");
wb.write(out);
out.close();
assertTrue("file exists",file.exists());
FileInputStream in = new FileInputStream(file);
wb = new HSSFWorkbook(in);
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
s = wb.getSheetAt(0);
r = s.getRow(0);
c = r.getCell(0);
@ -568,18 +468,13 @@ public final class TestFormulas extends TestCase {
assertTrue("function ="+function+"(A2:A3)",
( (function+"(A2:A3)").equals((function+"(A2:A3)")) )
);
in.close();
}
/**
* Writes a function then tests to see if its correct
*
*/
public void refArrayFunctionTest(String function)
throws Exception {
public void refArrayFunctionTest(String function) {
File file = TempFile.createTempFile("testFormulaArrayFunction"+function,".xls");
FileOutputStream out = new FileOutputStream(file);
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet();
HSSFRow r = null;
@ -591,13 +486,7 @@ public final class TestFormulas extends TestCase {
c = r.createCell(0);
c.setCellFormula(function+"(A2,A3)");
wb.write(out);
out.close();
assertTrue("file exists",file.exists());
FileInputStream in = new FileInputStream(file);
wb = new HSSFWorkbook(in);
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
s = wb.getSheetAt(0);
r = s.getRow(0);
c = r.getCell(0);
@ -605,7 +494,6 @@ public final class TestFormulas extends TestCase {
assertTrue("function ="+function+"(A2,A3)",
( (function+"(A2,A3)").equals(c.getCellFormula()) )
);
in.close();
}
@ -613,11 +501,8 @@ public final class TestFormulas extends TestCase {
* Writes a function then tests to see if its correct
*
*/
public void refAreaArrayFunctionTest(String function)
throws Exception {
public void refAreaArrayFunctionTest(String function) {
File file = TempFile.createTempFile("testFormulaAreaArrayFunction"+function,".xls");
FileOutputStream out = new FileOutputStream(file);
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet();
HSSFRow r = null;
@ -631,12 +516,7 @@ public final class TestFormulas extends TestCase {
c=r.createCell(1);
c.setCellFormula(function+"($A$2:$A4,B$2:B4)");
wb.write(out);
out.close();
assertTrue("file exists",file.exists());
FileInputStream in = new FileInputStream(file);
wb = new HSSFWorkbook(in);
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
s = wb.getSheetAt(0);
r = s.getRow(0);
c = r.getCell(0);
@ -649,22 +529,17 @@ public final class TestFormulas extends TestCase {
assertTrue("function ="+function+"($A$2:$A4,B$2:B4)",
( (function+"($A$2:$A4,B$2:B4)").equals(c.getCellFormula()) )
);
in.close();
}
public void testAbsRefs() throws Exception {
File file = TempFile.createTempFile("testFormulaAbsRef",".xls");
FileOutputStream out = new FileOutputStream(file);
public void testAbsRefs() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet();
HSSFRow r = null;
HSSFCell c = null;
HSSFRow r;
HSSFCell c;
r = s.createRow(0);
c = r.createCell(0);
c.setCellFormula("A3+A2");
c=r.createCell(1);
@ -676,12 +551,7 @@ public final class TestFormulas extends TestCase {
c=r.createCell(4);
c.setCellFormula("SUM($A$3,$A$2)");
wb.write(out);
out.close();
assertTrue("file exists",file.exists());
FileInputStream in = new FileInputStream(file);
wb = new HSSFWorkbook(in);
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
s = wb.getSheetAt(0);
r = s.getRow(0);
c = r.getCell(0);
@ -694,14 +564,9 @@ public final class TestFormulas extends TestCase {
assertTrue("$A$3+$A$2", ("$A$3+$A$2").equals(c.getCellFormula()));
c = r.getCell(4);
assertTrue("SUM($A$3,$A$2)", ("SUM($A$3,$A$2)").equals(c.getCellFormula()));
in.close();
}
public void testSheetFunctions()
throws IOException
{
File file = TempFile.createTempFile("testSheetFormula",".xls");
FileOutputStream out = new FileOutputStream(file);
public void testSheetFunctions() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet("A");
HSSFRow r = null;
@ -715,20 +580,15 @@ public final class TestFormulas extends TestCase {
c=r.createCell(0); c.setCellFormula("AVERAGE(A!A1:B1)");
c=r.createCell(1); c.setCellFormula("A!A1+A!B1");
c=r.createCell(2); c.setCellFormula("A!$A$1+A!$B1");
wb.write(out);
out.close();
assertTrue("file exists",file.exists());
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
FileInputStream in = new FileInputStream(file);
wb = new HSSFWorkbook(in);
s = wb.getSheet("B");
r = s.getRow(0);
c = r.getCell(0);
assertTrue("expected: AVERAGE(A!A1:B1) got: "+c.getCellFormula(), ("AVERAGE(A!A1:B1)").equals(c.getCellFormula()));
c = r.getCell(1);
assertTrue("expected: A!A1+A!B1 got: "+c.getCellFormula(), ("A!A1+A!B1").equals(c.getCellFormula()));
in.close();
}
public void testRVAoperands() throws Exception {
@ -769,11 +629,7 @@ public final class TestFormulas extends TestCase {
assertTrue("file exists",file.exists());
}
public void testStringFormulas()
throws IOException
{
File file = TempFile.createTempFile("testStringFormula",".xls");
FileOutputStream out = new FileOutputStream(file);
public void testStringFormulas() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet("A");
HSSFRow r = null;
@ -783,26 +639,17 @@ public final class TestFormulas extends TestCase {
c=r.createCell(2); c.setCellFormula("LOWER(\"ABC\")");
c=r.createCell(3); c.setCellFormula("CONCATENATE(\" my \",\" name \")");
wb.write(out);
out.close();
HSSFTestDataSamples.writeOutAndReadBack(wb);
wb = openSample("StringFormulas.xls");
s = wb.getSheetAt(0);
r = s.getRow(0);
c = r.getCell(0);
assertTrue("expected: UPPER(\"xyz\") got "+c.getCellFormula(), ("UPPER(\"xyz\")").equals(c.getCellFormula()));
//c = r.getCell((short)1);
//assertTrue("expected: A!A1+A!B1 got: "+c.getCellFormula(), ("A!A1+A!B1").equals(c.getCellFormula()));
assertEquals("UPPER(\"xyz\")", c.getCellFormula());
}
public void testLogicalFormulas() {
public void testLogicalFormulas()
throws IOException
{
File file = TempFile.createTempFile("testLogicalFormula",".xls");
FileOutputStream out = new FileOutputStream(file);
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet("A");
HSSFRow r = null;
@ -810,26 +657,14 @@ public final class TestFormulas extends TestCase {
r = s.createRow(0);
c=r.createCell(1); c.setCellFormula("IF(A1<A2,B1,B2)");
wb.write(out);
out.close();
assertTrue("file exists",file.exists());
FileInputStream in = new FileInputStream(file);
wb = new HSSFWorkbook(in);
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
s = wb.getSheetAt(0);
r = s.getRow(0);
c = r.getCell(1);
assertEquals("Formula in cell 1 ","IF(A1<A2,B1,B2)",c.getCellFormula());
in.close();
}
public void testDateFormulas()
throws IOException
{
File file = TempFile.createTempFile("testDateFormula",".xls");
FileOutputStream out = new FileOutputStream(file);
public void testDateFormulas() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet("testSheet1");
HSSFRow r = null;
@ -853,19 +688,11 @@ public final class TestFormulas extends TestCase {
c.setCellStyle(cellStyle);
}
wb.write(out);
out.close();
assertTrue("file exists",file.exists());
HSSFTestDataSamples.writeOutAndReadBack(wb);
}
public void testIfFormulas()
throws IOException
{
File file = TempFile.createTempFile("testIfFormula",".xls");
FileOutputStream out = new FileOutputStream(file);
public void testIfFormulas() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet s = wb.createSheet("testSheet1");
HSSFRow r = null;
@ -876,19 +703,12 @@ public final class TestFormulas extends TestCase {
c=r.createCell(3); c.setCellFormula("MAX(A1:B1)");
c=r.createCell(4); c.setCellFormula("IF(A1=D1,\"A1\",\"B1\")");
wb.write(out);
out.close();
assertTrue("file exists",file.exists());
FileInputStream in = new FileInputStream(file);
wb = new HSSFWorkbook(in);
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
s = wb.getSheetAt(0);
r = s.getRow(0);
c = r.getCell(4);
assertTrue("expected: IF(A1=D1,\"A1\",\"B1\") got "+c.getCellFormula(), ("IF(A1=D1,\"A1\",\"B1\")").equals(c.getCellFormula()));
in.close();
wb = openSample("IfFormulaTest.xls");
s = wb.getSheetAt(0);
@ -897,10 +717,8 @@ public final class TestFormulas extends TestCase {
assertTrue("expected: IF(A3=A1,\"A1\",\"A2\") got "+c.getCellFormula(), ("IF(A3=A1,\"A1\",\"A2\")").equals(c.getCellFormula()));
//c = r.getCell((short)1);
//assertTrue("expected: A!A1+A!B1 got: "+c.getCellFormula(), ("A!A1+A!B1").equals(c.getCellFormula()));
in.close();
File simpleIf = TempFile.createTempFile("testSimpleIfFormulaWrite",".xls");
out = new FileOutputStream(simpleIf);
wb = new HSSFWorkbook();
s = wb.createSheet("testSheet1");
r = null;
@ -908,14 +726,8 @@ public final class TestFormulas extends TestCase {
r = s.createRow(0);
c=r.createCell(0); c.setCellFormula("IF(1=1,0,1)");
wb.write(out);
out.close();
assertTrue("file exists", simpleIf.exists());
HSSFTestDataSamples.writeOutAndReadBack(wb);
assertTrue("length of simpleIf file is zero", (simpleIf.length()>0));
File nestedIf = TempFile.createTempFile("testNestedIfFormula",".xls");
out = new FileOutputStream(nestedIf);
wb = new HSSFWorkbook();
s = wb.createSheet("testSheet1");
r = null;
@ -939,17 +751,10 @@ public final class TestFormulas extends TestCase {
formulaCell.setCellFormula("IF(A1=B1,AVERAGE(A1:B1),AVERAGE(A2:B2))");
wb.write(out);
out.close();
assertTrue("file exists", nestedIf.exists());
assertTrue("length of nestedIf file is zero", (nestedIf.length()>0));
HSSFTestDataSamples.writeOutAndReadBack(wb);
}
public void testSumIf()
throws IOException
{
public void testSumIf() {
String function ="SUMIF(A1:A5,\">4000\",B1:B5)";
HSSFWorkbook wb = openSample("sumifformula.xls");
@ -960,40 +765,34 @@ public final class TestFormulas extends TestCase {
assertEquals(function, c.getCellFormula());
File file = TempFile.createTempFile("testSumIfFormula",".xls");
FileOutputStream out = new FileOutputStream(file);
wb = new HSSFWorkbook();
s = wb.createSheet();
r = s.createRow(0);
c=r.createCell(0); c.setCellValue((double)1000);
c=r.createCell(1); c.setCellValue((double)1);
c=r.createCell(0); c.setCellValue(1000);
c=r.createCell(1); c.setCellValue(1);
r = s.createRow(1);
c=r.createCell(0); c.setCellValue((double)2000);
c=r.createCell(1); c.setCellValue((double)2);
c=r.createCell(0); c.setCellValue(2000);
c=r.createCell(1); c.setCellValue(2);
r = s.createRow(2);
c=r.createCell(0); c.setCellValue((double)3000);
c=r.createCell(1); c.setCellValue((double)3);
c=r.createCell(0); c.setCellValue(3000);
c=r.createCell(1); c.setCellValue(3);
r = s.createRow(3);
c=r.createCell(0); c.setCellValue((double)4000);
c=r.createCell(1); c.setCellValue((double)4);
c=r.createCell(0); c.setCellValue(4000);
c=r.createCell(1); c.setCellValue(4);
r = s.createRow(4);
c=r.createCell(0); c.setCellValue((double)5000);
c=r.createCell(1); c.setCellValue((double)5);
c=r.createCell(0); c.setCellValue(5000);
c=r.createCell(1); c.setCellValue(5);
r = s.getRow(0);
c=r.createCell(2); c.setCellFormula(function);
wb.write(out);
out.close();
assertTrue("sumif file doesnt exists", (file.exists()));
assertTrue("sumif == 0 bytes", file.length() > 0);
HSSFTestDataSamples.writeOutAndReadBack(wb);
}
public void testSquareMacro() {
@ -1064,7 +863,7 @@ public final class TestFormulas extends TestCase {
/** Unknown Ptg 3D*/
public void test27272_2() throws Exception {
HSSFWorkbook wb = openSample("27272_2.xls");
assertEquals("Reference for named range ", "'LOAD.POD_HISTORIES'!#REF!",wb.getNameAt(0).getReference());
assertEquals("Reference for named range ", "LOAD.POD_HISTORIES!#REF!",wb.getNameAt(0).getReference());
File outF = File.createTempFile("bug27272_2",".xls");
wb.write(new FileOutputStream(outF));
System.out.println("Open "+outF.getAbsolutePath()+" in Excel");
@ -1085,8 +884,4 @@ public final class TestFormulas extends TestCase {
assertEquals("DZ2*2", wb.getSheetAt(0).getRow(1).getCell(128).toString());
assertEquals("B32770*2", wb.getSheetAt(0).getRow(32768).getCell(1).toString());
}
public static void main(String [] args) {
junit.textui.TestRunner.run(TestFormulas.class);
}
}

View File

@ -0,0 +1,34 @@
/* ====================================================================
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.ss.formula;
import junit.framework.Test;
import junit.framework.TestSuite;
/**
* Test suite for org.apache.poi.ss.formula
*
* @author Josh Micich
*/
public final class AllSSFormulaTests {
public static Test suite() {
TestSuite result = new TestSuite(AllSSFormulaTests.class.getName());
result.addTestSuite(TestEvaluationCache.class);
result.addTestSuite(TestWorkbookEvaluator.class);
return result;
}
}

View File

@ -0,0 +1,154 @@
/* ====================================================================
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.ss.formula;
import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.record.formula.AreaErrPtg;
import org.apache.poi.hssf.record.formula.AttrPtg;
import org.apache.poi.hssf.record.formula.DeletedArea3DPtg;
import org.apache.poi.hssf.record.formula.DeletedRef3DPtg;
import org.apache.poi.hssf.record.formula.IntPtg;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.RefErrorPtg;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
* Tests {@link WorkbookEvaluator}.
*
* @author Josh Micich
*/
public class TestWorkbookEvaluator extends TestCase {
/**
* Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing
* the whole formula which converts tAttrSum to tFuncVar("SUM") )
*/
public void testAttrSum() {
Ptg[] ptgs = {
new IntPtg(42),
AttrPtg.SUM,
};
ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
}
/**
* Make sure that the evaluator can directly handle (deleted) ref error tokens
* (instead of relying on re-parsing the whole formula which converts these
* to the error constant #REF! )
*/
public void testRefErr() {
confirmRefErr(new RefErrorPtg());
confirmRefErr(new AreaErrPtg());
confirmRefErr(new DeletedRef3DPtg(0));
confirmRefErr(new DeletedArea3DPtg(0));
}
private static void confirmRefErr(Ptg ptg) {
Ptg[] ptgs = {
ptg,
};
ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
assertEquals(ErrorEval.REF_INVALID, result);
}
/**
* Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing
* the whole formula which converts tAttrSum to tFuncVar("SUM") )
*/
public void testMemFunc() {
Ptg[] ptgs = {
new IntPtg(42),
AttrPtg.SUM,
};
ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
}
public void testEvaluateMultipleWorkbooks() {
HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls");
HSSFWorkbook wbB = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaB.xls");
HSSFFormulaEvaluator evaluatorA = new HSSFFormulaEvaluator(wbA);
HSSFFormulaEvaluator evaluatorB = new HSSFFormulaEvaluator(wbB);
// Hook up the workbook evaluators to enable evaluation of formulas across books
String[] bookNames = { "multibookFormulaA.xls", "multibookFormulaB.xls", };
HSSFFormulaEvaluator[] evaluators = { evaluatorA, evaluatorB, };
HSSFFormulaEvaluator.setupEnvironment(bookNames, evaluators);
HSSFCell cell;
HSSFSheet aSheet1 = wbA.getSheetAt(0);
HSSFSheet bSheet1 = wbB.getSheetAt(0);
// Simple case - single link from wbA to wbB
confirmFormula(wbA, 0, 0, 0, "[multibookFormulaB.xls]BSheet1!B1");
cell = aSheet1.getRow(0).getCell(0);
confirmEvaluation(35, evaluatorA, cell);
// more complex case - back link into wbA
// [wbA]ASheet1!A2 references (among other things) [wbB]BSheet1!B2
confirmFormula(wbA, 0, 1, 0, "[multibookFormulaB.xls]BSheet1!$B$2+2*A3");
// [wbB]BSheet1!B2 references (among other things) [wbA]AnotherSheet!A1:B2
confirmFormula(wbB, 0, 1, 1, "SUM([multibookFormulaA.xls]AnotherSheet!$A$1:$B$2)+B3");
cell = aSheet1.getRow(1).getCell(0);
confirmEvaluation(264, evaluatorA, cell);
// change [wbB]BSheet1!B3 (from 50 to 60)
bSheet1.getRow(2).getCell(1).setCellValue(60);
evaluatorB.setCachedPlainValue(bSheet1, 2, 1, new NumberEval(60));
confirmEvaluation(274, evaluatorA, cell);
// change [wbA]ASheet1!A3 (from 100 to 80)
aSheet1.getRow(2).getCell(0).setCellValue(80);
evaluatorA.setCachedPlainValue(aSheet1, 2, 0, new NumberEval(80));
confirmEvaluation(234, evaluatorA, cell);
// change [wbA]AnotherSheet!A1 (from 2 to 3)
wbA.getSheetAt(1).getRow(0).getCell(0).setCellValue(3);
evaluatorA.setCachedPlainValue(wbA.getSheetAt(1), 0, 0, new NumberEval(3));
confirmEvaluation(235, evaluatorA, cell);
}
private static void confirmEvaluation(double expectedValue, HSSFFormulaEvaluator fe, HSSFCell cell) {
assertEquals(expectedValue, fe.evaluate(cell).getNumberValue(), 0.0);
}
private static void confirmFormula(HSSFWorkbook wb, int sheetIndex, int rowIndex, int columnIndex,
String expectedFormula) {
HSSFCell cell = wb.getSheetAt(sheetIndex).getRow(rowIndex).getCell(columnIndex);
assertEquals(expectedFormula, cell.getCellFormula());
}
}