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 7c8d6be5bc
commit 8bfd149414
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 int getIndexToSheet(int extRefIndex) {
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 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 short checkExternSheet(int sheetIndex) {
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");
}
//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;
@ -328,9 +329,9 @@ public final class Workbook implements Model {
for ( int k = 0; k < nBoundSheets; k++ ) {
BoundSheetRecord bsr = retval.createBoundSheet(k);
records.add(bsr);
retval.boundsheets.add(bsr);
retval.records.setBspos(records.size() - 1);
records.add(bsr);
retval.boundsheets.add(bsr);
retval.records.setBspos(records.size() - 1);
}
// retval.records.supbookpos = retval.records.bspos + 1;
// retval.records.namepos = retval.records.supbookpos + 2;
@ -586,19 +587,19 @@ public final class Workbook implements Model {
* @param hidden 0 for not hidden, 1 for hidden, 2 for very hidden
*/
public void setSheetHidden(int sheetnum, int hidden) {
BoundSheetRecord bsr = getBoundSheetRec(sheetnum);
boolean h = false;
boolean vh = false;
if(hidden == 0) {
} else if(hidden == 1) {
h = true;
} else if(hidden == 2) {
vh = true;
} else {
throw new IllegalArgumentException("Invalid hidden flag " + hidden + " given, must be 0, 1 or 2");
}
bsr.setHidden(h);
bsr.setVeryHidden(vh);
BoundSheetRecord bsr = getBoundSheetRec(sheetnum);
boolean h = false;
boolean vh = false;
if(hidden == 0) {
} else if(hidden == 1) {
h = true;
} else if(hidden == 2) {
vh = true;
} else {
throw new IllegalArgumentException("Invalid hidden flag " + hidden + " given, must be 0, 1 or 2");
}
bsr.setHidden(h);
bsr.setVeryHidden(vh);
}
@ -761,23 +762,23 @@ public final class Workbook implements Model {
* have a Style set.
*/
public StyleRecord getStyleRecord(int xfIndex) {
// Style records always follow after
// the ExtendedFormat records
boolean done = false;
for(int i=records.getXfpos(); i<records.size() &&
!done; i++) {
Record r = records.get(i);
if(r instanceof ExtendedFormatRecord) {
} else if(r instanceof StyleRecord) {
StyleRecord sr = (StyleRecord)r;
if(sr.getIndex() == xfIndex) {
return sr;
}
} else {
done = true;
}
}
return null;
// Style records always follow after
// the ExtendedFormat records
boolean done = false;
for(int i=records.getXfpos(); i<records.size() &&
!done; i++) {
Record r = records.get(i);
if(r instanceof ExtendedFormatRecord) {
} else if(r instanceof StyleRecord) {
StyleRecord sr = (StyleRecord)r;
if(sr.getIndex() == xfIndex) {
return sr;
}
} else {
done = true;
}
}
return null;
}
/**
* Creates a new StyleRecord, for the given Extended
@ -785,29 +786,29 @@ public final class Workbook implements Model {
* records collection
*/
public StyleRecord createStyleRecord(int xfIndex) {
// Style records always follow after
// the ExtendedFormat records
StyleRecord newSR = new StyleRecord();
newSR.setIndex((short)xfIndex);
// Find the spot
int addAt = -1;
for(int i=records.getXfpos(); i<records.size() &&
addAt == -1; i++) {
Record r = records.get(i);
if(r instanceof ExtendedFormatRecord ||
r instanceof StyleRecord) {
// Keep going
} else {
addAt = i;
}
}
if(addAt == -1) {
throw new IllegalStateException("No XF Records found!");
}
records.add(addAt, newSR);
return newSR;
// Style records always follow after
// the ExtendedFormat records
StyleRecord newSR = new StyleRecord();
newSR.setIndex((short)xfIndex);
// Find the spot
int addAt = -1;
for(int i=records.getXfpos(); i<records.size() &&
addAt == -1; i++) {
Record r = records.get(i);
if(r instanceof ExtendedFormatRecord ||
r instanceof StyleRecord) {
// Keep going
} else {
addAt = i;
}
}
if(addAt == -1) {
throw new IllegalStateException("No XF Records found!");
}
records.add(addAt, newSR);
return newSR;
}
/**
@ -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

@ -30,248 +30,264 @@ import org.apache.poi.util.LittleEndian;
* @author Jason Height (jheight at chariot dot net dot au)
*/
public abstract class AreaPtgBase extends OperandPtg implements AreaI {
/**
* TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas.
* see similar comment in ReferencePtg
*/
protected final RuntimeException notImplemented() {
return new RuntimeException("Coding Error: This method should never be called. This ptg should be converted");
}
/**
* TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas.
* see similar comment in ReferencePtg
*/
protected final RuntimeException notImplemented() {
return new RuntimeException("Coding Error: This method should never be called. This ptg should be converted");
}
/** zero based, unsigned 16 bit */
private int field_1_first_row;
/** zero based, unsigned 16 bit */
private int field_2_last_row;
/** zero based, unsigned 8 bit */
private int field_3_first_column;
/** zero based, unsigned 8 bit */
private int field_4_last_column;
private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000);
private final static BitField colRelative = BitFieldFactory.getInstance(0x4000);
private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF);
/** zero based, unsigned 16 bit */
private int field_1_first_row;
/** zero based, unsigned 16 bit */
private int field_2_last_row;
/** zero based, unsigned 8 bit */
private int field_3_first_column;
/** zero based, unsigned 8 bit */
private int field_4_last_column;
private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000);
private final static BitField colRelative = BitFieldFactory.getInstance(0x4000);
private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF);
protected AreaPtgBase() {
// do nothing
}
protected AreaPtgBase(String arearef) {
AreaReference ar = new AreaReference(arearef);
CellReference firstCell = ar.getFirstCell();
CellReference lastCell = ar.getLastCell();
setFirstRow(firstCell.getRow());
setFirstColumn(firstCell.getCol());
setLastRow(lastCell.getRow());
setLastColumn(lastCell.getCol());
setFirstColRelative(!firstCell.isColAbsolute());
setLastColRelative(!lastCell.isColAbsolute());
setFirstRowRelative(!firstCell.isRowAbsolute());
setLastRowRelative(!lastCell.isRowAbsolute());
}
protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn,
boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
checkColumnBounds(firstColumn);
checkColumnBounds(lastColumn);
checkRowBounds(firstRow);
checkRowBounds(lastRow);
setFirstRow(firstRow);
setLastRow(lastRow);
setFirstColumn(firstColumn);
setLastColumn(lastColumn);
setFirstRowRelative(firstRowRelative);
setLastRowRelative(lastRowRelative);
setFirstColRelative(firstColRelative);
setLastColRelative(lastColRelative);
}
protected AreaPtgBase() {
// do nothing
}
protected AreaPtgBase(String arearef) {
AreaReference ar = new AreaReference(arearef);
CellReference firstCell = ar.getFirstCell();
CellReference lastCell = ar.getLastCell();
setFirstRow(firstCell.getRow());
setFirstColumn(firstCell.getCol());
setLastRow(lastCell.getRow());
setLastColumn(lastCell.getCol());
setFirstColRelative(!firstCell.isColAbsolute());
setLastColRelative(!lastCell.isColAbsolute());
setFirstRowRelative(!firstCell.isRowAbsolute());
setLastRowRelative(!lastCell.isRowAbsolute());
}
protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn,
boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
checkColumnBounds(firstColumn);
checkColumnBounds(lastColumn);
checkRowBounds(firstRow);
checkRowBounds(lastRow);
if (lastRow > firstRow) {
setFirstRow(firstRow);
setLastRow(lastRow);
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) {
if((colIx & 0x0FF) != colIx) {
throw new IllegalArgumentException("colIx (" + colIx + ") is out of range");
}
}
private static void checkRowBounds(int rowIx) {
if((rowIx & 0x0FFFF) != rowIx) {
throw new IllegalArgumentException("rowIx (" + rowIx + ") is out of range");
}
}
private static void checkColumnBounds(int colIx) {
if((colIx & 0x0FF) != colIx) {
throw new IllegalArgumentException("colIx (" + colIx + ") is out of range");
}
}
private static void checkRowBounds(int rowIx) {
if((rowIx & 0x0FFFF) != rowIx) {
throw new IllegalArgumentException("rowIx (" + rowIx + ") is out of range");
}
}
protected final void readCoordinates(RecordInputStream in) {
field_1_first_row = in.readUShort();
field_2_last_row = in.readUShort();
field_3_first_column = in.readUShort();
field_4_last_column = in.readUShort();
}
protected final void writeCoordinates(byte[] array, int offset) {
LittleEndian.putUShort(array, offset + 0, field_1_first_row);
LittleEndian.putUShort(array, offset + 2, field_2_last_row);
LittleEndian.putUShort(array, offset + 4, field_3_first_column);
LittleEndian.putUShort(array, offset + 6, field_4_last_column);
}
protected final void readCoordinates(RecordInputStream in) {
field_1_first_row = in.readUShort();
field_2_last_row = in.readUShort();
field_3_first_column = in.readUShort();
field_4_last_column = in.readUShort();
}
protected final void writeCoordinates(byte[] array, int offset) {
LittleEndian.putUShort(array, offset + 0, field_1_first_row);
LittleEndian.putUShort(array, offset + 2, field_2_last_row);
LittleEndian.putUShort(array, offset + 4, field_3_first_column);
LittleEndian.putUShort(array, offset + 6, field_4_last_column);
}
/**
* @return the first row in the area
*/
public final int getFirstRow() {
return field_1_first_row;
}
/**
* @return the first row in the area
*/
public final int getFirstRow() {
return field_1_first_row;
}
/**
* sets the first row
* @param rowIx number (0-based)
*/
public final void setFirstRow(int rowIx) {
checkRowBounds(rowIx);
field_1_first_row = rowIx;
}
/**
* sets the first row
* @param rowIx number (0-based)
*/
public final void setFirstRow(int rowIx) {
checkRowBounds(rowIx);
field_1_first_row = rowIx;
}
/**
* @return last row in the range (x2 in x1,y1-x2,y2)
*/
public final int getLastRow() {
return field_2_last_row;
}
/**
* @return last row in the range (x2 in x1,y1-x2,y2)
*/
public final int getLastRow() {
return field_2_last_row;
}
/**
* @param rowIx last row number in the area
*/
public final void setLastRow(int rowIx) {
checkRowBounds(rowIx);
field_2_last_row = rowIx;
}
/**
* @param rowIx last row number in the area
*/
public final void setLastRow(int rowIx) {
checkRowBounds(rowIx);
field_2_last_row = rowIx;
}
/**
* @return the first column number in the area.
*/
public final int getFirstColumn() {
return columnMask.getValue(field_3_first_column);
}
/**
* @return the first column number in the area.
*/
public final int getFirstColumn() {
return columnMask.getValue(field_3_first_column);
}
/**
* @return the first column number + the options bit settings unstripped
*/
public final short getFirstColumnRaw() {
return (short) field_3_first_column; // TODO
}
/**
* @return the first column number + the options bit settings unstripped
*/
public final short getFirstColumnRaw() {
return (short) field_3_first_column; // TODO
}
/**
* @return whether or not the first row is a relative reference or not.
*/
public final boolean isFirstRowRelative() {
return rowRelative.isSet(field_3_first_column);
}
/**
* sets the first row to relative or not
* @param rel is relative or not.
*/
public final void setFirstRowRelative(boolean rel) {
field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel);
}
/**
* @return whether or not the first row is a relative reference or not.
*/
public final boolean isFirstRowRelative() {
return rowRelative.isSet(field_3_first_column);
}
/**
* sets the first row to relative or not
* @param rel is relative or not.
*/
public final void setFirstRowRelative(boolean rel) {
field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel);
}
/**
* @return isrelative first column to relative or not
*/
public final boolean isFirstColRelative() {
return colRelative.isSet(field_3_first_column);
}
/**
* set whether the first column is relative
*/
public final void setFirstColRelative(boolean rel) {
field_3_first_column=colRelative.setBoolean(field_3_first_column,rel);
}
/**
* @return isrelative first column to relative or not
*/
public final boolean isFirstColRelative() {
return colRelative.isSet(field_3_first_column);
}
/**
* set whether the first column is relative
*/
public final void setFirstColRelative(boolean rel) {
field_3_first_column=colRelative.setBoolean(field_3_first_column,rel);
}
/**
* set the first column in the area
*/
public final void setFirstColumn(int colIx) {
checkColumnBounds(colIx);
field_3_first_column=columnMask.setValue(field_3_first_column, colIx);
}
/**
* set the first column in the area
*/
public final void setFirstColumn(int colIx) {
checkColumnBounds(colIx);
field_3_first_column=columnMask.setValue(field_3_first_column, colIx);
}
/**
* set the first column irrespective of the bitmasks
*/
public final void setFirstColumnRaw(int column) {
field_3_first_column = column;
}
/**
* set the first column irrespective of the bitmasks
*/
public final void setFirstColumnRaw(int column) {
field_3_first_column = column;
}
/**
* @return lastcolumn in the area
*/
public final int getLastColumn() {
return columnMask.getValue(field_4_last_column);
}
/**
* @return lastcolumn in the area
*/
public final int getLastColumn() {
return columnMask.getValue(field_4_last_column);
}
/**
* @return last column and bitmask (the raw field)
*/
public final short getLastColumnRaw() {
return (short) field_4_last_column;
}
/**
* @return last column and bitmask (the raw field)
*/
public final short getLastColumnRaw() {
return (short) field_4_last_column;
}
/**
* @return last row relative or not
*/
public final boolean isLastRowRelative() {
return rowRelative.isSet(field_4_last_column);
}
/**
* set whether the last row is relative or not
* @param rel <code>true</code> if the last row relative, else
* <code>false</code>
*/
public final void setLastRowRelative(boolean rel) {
field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel);
}
/**
* @return last row relative or not
*/
public final boolean isLastRowRelative() {
return rowRelative.isSet(field_4_last_column);
}
/**
* set whether the last row is relative or not
* @param rel <code>true</code> if the last row relative, else
* <code>false</code>
*/
public final void setLastRowRelative(boolean rel) {
field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel);
}
/**
* @return lastcol relative or not
*/
public final boolean isLastColRelative() {
return colRelative.isSet(field_4_last_column);
}
/**
* set whether the last column should be relative or not
*/
public final void setLastColRelative(boolean rel) {
field_4_last_column=colRelative.setBoolean(field_4_last_column,rel);
}
/**
* set the last column in the area
*/
public final void setLastColumn(int colIx) {
checkColumnBounds(colIx);
field_4_last_column=columnMask.setValue(field_4_last_column, colIx);
}
/**
* @return lastcol relative or not
*/
public final boolean isLastColRelative() {
return colRelative.isSet(field_4_last_column);
}
/**
* set whether the last column should be relative or not
*/
public final void setLastColRelative(boolean rel) {
field_4_last_column=colRelative.setBoolean(field_4_last_column,rel);
}
/**
* set the last column in the area
*/
public final void setLastColumn(int colIx) {
checkColumnBounds(colIx);
field_4_last_column=columnMask.setValue(field_4_last_column, colIx);
}
/**
* set the last column irrespective of the bitmasks
*/
public final void setLastColumnRaw(short column) {
field_4_last_column = column;
}
protected final String formatReferenceAsString() {
CellReference topLeft = new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative());
CellReference botRight = new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative());
if(AreaReference.isWholeColumnReference(topLeft, botRight)) {
return (new AreaReference(topLeft, botRight)).formatAsString();
}
return topLeft.formatAsString() + ":" + botRight.formatAsString();
}
public String toFormulaString() {
return formatReferenceAsString();
}
/**
* set the last column irrespective of the bitmasks
*/
public final void setLastColumnRaw(short column) {
field_4_last_column = column;
}
protected final String formatReferenceAsString() {
CellReference topLeft = new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative());
CellReference botRight = new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative());
if(AreaReference.isWholeColumnReference(topLeft, botRight)) {
return (new AreaReference(topLeft, botRight)).formatAsString();
}
return topLeft.formatAsString() + ":" + botRight.formatAsString();
}
public String toFormulaString() {
return formatReferenceAsString();
}
public byte getDefaultOperandClass() {
return Ptg.CLASS_REF;
}
public byte getDefaultOperandClass() {
return Ptg.CLASS_REF;
}
}

View File

@ -34,13 +34,13 @@ public final class AttrPtg extends ControlPtg {
private final static int SIZE = 4;
private byte field_1_options;
private short field_2_data;
/** only used for tAttrChoose: table of offsets to starts of args */
private final int[] _jumpTable;
/** only used for tAttrChoose: offset to the tFuncVar for CHOOSE() */
private final int _chooseFuncOffset;
// flags 'volatile' and 'space', can be combined.
// flags 'volatile' and 'space', can be combined.
// OOO spec says other combinations are theoretically possible but not likely to occur.
private static final BitField semiVolatile = BitFieldFactory.getInstance(0x01);
private static final BitField optiIf = BitFieldFactory.getInstance(0x02);
@ -49,12 +49,14 @@ public final class AttrPtg extends ControlPtg {
private static final BitField sum = BitFieldFactory.getInstance(0x10);
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
}
/** 00H = Spaces before the next token (not allowed before tParen token) */
public static final int SPACE_BEFORE = 0x00;
/** 01H = Carriage returns before the next token (not allowed before tParen token) */
@ -75,7 +77,7 @@ public final class AttrPtg extends ControlPtg {
_jumpTable = null;
_chooseFuncOffset = -1;
}
public AttrPtg(RecordInputStream in)
{
field_1_options = in.readByte();
@ -92,7 +94,7 @@ public final class AttrPtg extends ControlPtg {
_jumpTable = null;
_chooseFuncOffset = -1;
}
}
private AttrPtg(int options, int data, int[] jt, int chooseFuncOffset) {
field_1_options = (byte) options;
@ -100,7 +102,7 @@ public final class AttrPtg extends ControlPtg {
_jumpTable = jt;
_chooseFuncOffset = chooseFuncOffset;
}
/**
* @param type a constant from <tt>SpaceType</tt>
* @param count the number of space characters
@ -145,7 +147,7 @@ public final class AttrPtg extends ControlPtg {
{
return sum.isSet(getOptions());
}
public void setSum(boolean bsum) {
field_1_options=sum.setByteBoolean(field_1_options,bsum);
}
@ -155,13 +157,13 @@ public final class AttrPtg extends ControlPtg {
}
/**
* Flags this ptg as a goto/jump
* Flags this ptg as a goto/jump
* @param isGoto
*/
public void setGoto(boolean isGoto) {
field_1_options=optGoto.setByteBoolean(field_1_options, isGoto);
}
// lets hope no one uses this anymore
public boolean isBaxcel()
{
@ -201,7 +203,7 @@ public final class AttrPtg extends ControlPtg {
} else if(isOptimizedChoose()) {
sb.append("choose nCases=").append(getData());
} else if(isGoto()) {
sb.append("skip dist=").append(getData());
sb.append("skip dist=").append(getData());
} else if(isSum()) {
sb.append("sum ");
} else if(isBaxcel()) {
@ -218,7 +220,7 @@ public final class AttrPtg extends ControlPtg {
LittleEndian.putShort(array,offset+2, field_2_data);
int[] jt = _jumpTable;
if (jt != null) {
int joff = offset+4;
int joff = offset+4;
LittleEndian.putUShort(array, joff, _chooseFuncOffset);
joff+=2;
for (int i = 0; i < jt.length; i++) {
@ -227,7 +229,7 @@ public final class AttrPtg extends ControlPtg {
}
LittleEndian.putUShort(array, joff, _chooseFuncOffset);
}
}
public int getSize()
@ -249,7 +251,7 @@ public final class AttrPtg extends ControlPtg {
return toFormulaString() + "(" + operands[ 0 ] + ")";
}
}
public int getNumberOfOperands()
{
@ -260,7 +262,7 @@ public final class AttrPtg extends ControlPtg {
{
return -1;
}
public String toFormulaString() {
if(semiVolatile.isSet(field_1_options)) {
return "ATTR(semiVolatile)";

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,13 +30,22 @@ final class ExternSheetNameResolver {
}
public static String prependSheetName(FormulaRenderingWorkbook book, int field_1_index_extern_sheet, String cellRefText) {
String sheetName = book.getSheetNameByExternSheet(field_1_index_extern_sheet);
StringBuffer 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
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 {
SheetNameFormatter.appendFormat(sb, sheetName);
String sheetName = book.getSheetNameByExternSheet(field_1_index_extern_sheet);
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);

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
@ -75,11 +76,11 @@ public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormu
return SIZE;
}
public int getExternSheetIndex(){
public int getExternSheetIndex() {
return field_1_index_extern_sheet;
}
public void setExternSheetIndex(int index){
public void setExternSheetIndex(int index) {
field_1_index_extern_sheet = index;
}

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);
@ -75,8 +83,12 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
}
public int convertFromExternSheetIndex(int externSheetIndex) {
return _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);
}
}
public ExternalSheet getExternalSheet(int externSheetIndex) {
return _iBook.getExternalSheet(externSheetIndex);
}
public HSSFWorkbook getWorkbook() {
return _uBook;
}
@ -96,7 +108,15 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
return new Name(_iBook.getNameRecord(ix), ix);
}
public Ptg[] getFormulaTokens(Cell cell) {
return HSSFFormulaParser.parse(cell.getCellFormula(), _uBook);
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;
@ -56,6 +57,21 @@ public class HSSFFormulaEvaluator /* almost implements FormulaEvaluator */ {
public HSSFFormulaEvaluator(HSSFWorkbook workbook) {
_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

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,7 +111,22 @@ 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.
* Failure to call this method after changing cell values will cause incorrect behaviour
@ -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

@ -90,7 +90,7 @@ public class AreaReference {
for(int i=refPart.length()-1; i>=0; i--) {
int ch = refPart.charAt(i);
if (ch == '$' && i==0) {
continue;
continue;
}
if (ch < 'A' || ch > 'Z') {
return false;
@ -103,8 +103,45 @@ public class AreaReference {
* Creates an area ref from a pair of Cell References.
*/
public AreaReference(CellReference topLeft, CellReference botRight) {
_firstCell = topLeft;
_lastCell = 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,21 +34,28 @@ 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;
}
/** The character ($) that signifies a row or column value is absolute instead of relative */
private static final char ABSOLUTE_REFERENCE_MARKER = '$';
/** The character (!) that separates sheet names from cell references */
private static final char SHEET_NAME_DELIMITER = '!';
/** The character (') used to quote sheet names when they contain special characters */
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 '$'.
*/
/** The character ($) that signifies a row or column value is absolute instead of relative */
private static final char ABSOLUTE_REFERENCE_MARKER = '$';
/** The character (!) that separates sheet names from cell references */
private static final char SHEET_NAME_DELIMITER = '!';
/** The character (') used to quote sheet names when they contain special characters */
private static final char SPECIAL_NAME_DELIMITER = '\'';
/**
* 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).
@ -65,96 +72,96 @@ public class CellReference {
private final boolean _isRowAbs;
private final boolean _isColAbs;
/**
* 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.
*/
public CellReference(String cellRef) {
String[] parts = separateRefParts(cellRef);
_sheetName = parts[0];
String colRef = parts[1];
if (colRef.length() < 1) {
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
}
_isColAbs = colRef.charAt(0) == '$';
if (_isColAbs) {
colRef=colRef.substring(1);
}
_colIndex = convertColStringToIndex(colRef);
String rowRef=parts[2];
if (rowRef.length() < 1) {
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
}
_isRowAbs = rowRef.charAt(0) == '$';
if (_isRowAbs) {
rowRef=rowRef.substring(1);
}
_rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based
}
/**
* 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.
*/
public CellReference(String cellRef) {
String[] parts = separateRefParts(cellRef);
_sheetName = parts[0];
String colRef = parts[1];
if (colRef.length() < 1) {
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
}
_isColAbs = colRef.charAt(0) == '$';
if (_isColAbs) {
colRef=colRef.substring(1);
}
_colIndex = convertColStringToIndex(colRef);
String rowRef=parts[2];
if (rowRef.length() < 1) {
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
}
_isRowAbs = rowRef.charAt(0) == '$';
if (_isRowAbs) {
rowRef=rowRef.substring(1);
}
_rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based
}
public CellReference(int pRow, int pCol) {
this(pRow, pCol & 0xFFFF, false, false);
}
public CellReference(int pRow, short pCol) {
this(pRow, (int)pCol, false, false);
}
public CellReference(int pRow, int pCol) {
this(pRow, pCol, false, false);
}
public CellReference(int pRow, short pCol) {
this(pRow, pCol & 0xFFFF, false, false);
}
public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
this(null, pRow, pCol, pAbsRow, pAbsCol);
}
public CellReference(String pSheetName, int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
// TODO - "-1" is a special value being temporarily used for whole row and whole column area references.
// so these checks are currently N.Q.R.
if(pRow < -1) {
throw new IllegalArgumentException("row index may not be negative");
}
if(pCol < -1) {
throw new IllegalArgumentException("column index may not be negative");
}
_sheetName = pSheetName;
_rowIndex=pRow;
_colIndex=pCol;
_isRowAbs = pAbsRow;
_isColAbs=pAbsCol;
}
public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
this(null, pRow, pCol, pAbsRow, pAbsCol);
}
public CellReference(String pSheetName, int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
// TODO - "-1" is a special value being temporarily used for whole row and whole column area references.
// so these checks are currently N.Q.R.
if(pRow < -1) {
throw new IllegalArgumentException("row index may not be negative");
}
if(pCol < -1) {
throw new IllegalArgumentException("column index may not be negative");
}
_sheetName = pSheetName;
_rowIndex=pRow;
_colIndex=pCol;
_isRowAbs = pAbsRow;
_isColAbs=pAbsCol;
}
public int getRow(){return _rowIndex;}
public short getCol(){return (short) _colIndex;}
public boolean isRowAbsolute(){return _isRowAbs;}
public boolean isColAbsolute(){return _isColAbs;}
/**
* @return possibly <code>null</code> if this is a 2D reference. Special characters are not
* escaped or delimited
*/
public String getSheetName(){
return _sheetName;
}
public static boolean isPartAbsolute(String part) {
return part.charAt(0) == ABSOLUTE_REFERENCE_MARKER;
}
/**
* takes in a column reference portion of a CellRef and converts it from
* ALPHA-26 number format to 0-based base 10.
* 'A' -> 0
* 'Z' -> 25
* 'AA' -> 26
* 'IV' -> 255
* @return zero based column index
*/
protected static int convertColStringToIndex(String ref) {
int pos = 0;
int retval=0;
public int getRow(){return _rowIndex;}
public short getCol(){return (short) _colIndex;}
public boolean isRowAbsolute(){return _isRowAbs;}
public boolean isColAbsolute(){return _isColAbs;}
/**
* @return possibly <code>null</code> if this is a 2D reference. Special characters are not
* escaped or delimited
*/
public String getSheetName(){
return _sheetName;
}
public static boolean isPartAbsolute(String part) {
return part.charAt(0) == ABSOLUTE_REFERENCE_MARKER;
}
/**
* takes in a column reference portion of a CellRef and converts it from
* ALPHA-26 number format to 0-based base 10.
* 'A' -> 0
* 'Z' -> 25
* 'AA' -> 26
* 'IV' -> 255
* @return zero based column index
*/
public static int convertColStringToIndex(String ref) {
int pos = 0;
int retval=0;
for (int k = ref.length()-1; k >= 0; k--) {
char thechar = ref.charAt(k);
if (thechar == ABSOLUTE_REFERENCE_MARKER) {
if (k != 0) {
throw new IllegalArgumentException("Bad col ref format '" + ref + "'");
}
break;
}
if (thechar == ABSOLUTE_REFERENCE_MARKER) {
if (k != 0) {
throw new IllegalArgumentException("Bad col ref format '" + ref + "'");
}
break;
}
// Character.getNumericValue() returns the values
// 10-35 for the letter A-Z
int shift = (int)Math.pow(26, pos);
@ -162,64 +169,70 @@ public class CellReference {
pos++;
}
return retval-1;
}
}
/**
* Classifies an identifier as either a simple (2D) cell reference or a named range name
* @return one of the values from <tt>NameType</tt>
*/
public static int classifyCellReference(String str) {
int len = str.length();
if (len < 1) {
throw new IllegalArgumentException("Empty string not allowed");
}
char firstChar = str.charAt(0);
switch (firstChar) {
case ABSOLUTE_REFERENCE_MARKER:
case '.':
case '_':
break;
default:
if (!Character.isLetter(firstChar)) {
throw new IllegalArgumentException("Invalid first char (" + firstChar
+ ") of cell reference or named range. Letter expected");
}
}
if (!Character.isDigit(str.charAt(len-1))) {
// no digits at end of str
return validateNamedRangeName(str);
}
Matcher cellRefPatternMatcher = CELL_REF_PATTERN.matcher(str);
if (!cellRefPatternMatcher.matches()) {
return validateNamedRangeName(str);
}
String lettersGroup = cellRefPatternMatcher.group(1);
String digitsGroup = cellRefPatternMatcher.group(2);
if (cellReferenceIsWithinRange(lettersGroup, digitsGroup)) {
// valid cell reference
return NameType.CELL;
}
// If str looks like a cell reference, but is out of (row/col) range, it is a valid
// 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.
if (str.indexOf(ABSOLUTE_REFERENCE_MARKER) >= 0) {
// Of course, named range names cannot have '$'
return NameType.BAD_CELL_OR_NAMED_RANGE;
}
return NameType.NAMED_RANGE;
}
/**
* Classifies an identifier as either a simple (2D) cell reference or a named range name
* @return one of the values from <tt>NameType</tt>
*/
public static int classifyCellReference(String str) {
int len = str.length();
if (len < 1) {
throw new IllegalArgumentException("Empty string not allowed");
}
char firstChar = str.charAt(0);
switch (firstChar) {
case ABSOLUTE_REFERENCE_MARKER:
case '.':
case '_':
break;
default:
if (!Character.isLetter(firstChar)) {
throw new IllegalArgumentException("Invalid first char (" + firstChar
+ ") of cell reference or named range. Letter expected");
}
}
if (!Character.isDigit(str.charAt(len-1))) {
// no digits at end of str
return validateNamedRangeName(str);
}
Matcher cellRefPatternMatcher = CELL_REF_PATTERN.matcher(str);
if (!cellRefPatternMatcher.matches()) {
return validateNamedRangeName(str);
}
String lettersGroup = cellRefPatternMatcher.group(1);
String digitsGroup = cellRefPatternMatcher.group(2);
if (cellReferenceIsWithinRange(lettersGroup, digitsGroup)) {
// valid cell reference
return NameType.CELL;
}
// If str looks like a cell reference, but is out of (row/col) range, it is a valid
// 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 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;
}
return NameType.NAMED_RANGE;
}
private static int validateNamedRangeName(String str) {
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;
}
/**
* Used to decide whether a name of the form "[A-Z]*[0-9]*" that appears in a formula can be
* interpreted as a cell reference. Names of that form can be also used for sheets and/or
@ -240,7 +253,7 @@ public class CellReference {
* <blockquote><table border="0" cellpadding="1" cellspacing="0"
* summary="Notable cases.">
* <tr><th>Input&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>
* <th>Result&nbsp;</th></tr>
* <th>Result&nbsp;</th></tr>
* <tr><td>"A", "1"</td><td>true</td></tr>
* <tr><td>"a", "111"</td><td>true</td></tr>
* <tr><td>"A", "65536"</td><td>true</td></tr>
@ -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,87 +291,104 @@ 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
* name still in ALPHA-26 number format. The third element is the row.
*/
private static String[] separateRefParts(String reference) {
int plingPos = reference.lastIndexOf(SHEET_NAME_DELIMITER);
String sheetName = parseSheetName(reference, plingPos);
int start = plingPos+1;
* 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
* name still in ALPHA-26 number format. The third element is the row.
*/
private static String[] separateRefParts(String reference) {
int plingPos = reference.lastIndexOf(SHEET_NAME_DELIMITER);
String sheetName = parseSheetName(reference, plingPos);
int start = plingPos+1;
int length = reference.length();
int length = reference.length();
int loc = start;
// skip initial dollars
if (reference.charAt(loc)==ABSOLUTE_REFERENCE_MARKER) {
loc++;
}
// step over column name chars until first digit (or dollars) for row number.
for (; loc < length; loc++) {
char ch = reference.charAt(loc);
if (Character.isDigit(ch) || ch == ABSOLUTE_REFERENCE_MARKER) {
break;
}
}
return new String[] {
sheetName,
reference.substring(start,loc),
reference.substring(loc),
};
}
int loc = start;
// skip initial dollars
if (reference.charAt(loc)==ABSOLUTE_REFERENCE_MARKER) {
loc++;
}
// step over column name chars until first digit (or dollars) for row number.
for (; loc < length; loc++) {
char ch = reference.charAt(loc);
if (Character.isDigit(ch) || ch == ABSOLUTE_REFERENCE_MARKER) {
break;
}
}
return new String[] {
sheetName,
reference.substring(start,loc),
reference.substring(loc),
};
}
private static String parseSheetName(String reference, int indexOfSheetNameDelimiter) {
if(indexOfSheetNameDelimiter < 0) {
return null;
}
boolean isQuoted = reference.charAt(0) == SPECIAL_NAME_DELIMITER;
if(!isQuoted) {
return reference.substring(0, indexOfSheetNameDelimiter);
}
int lastQuotePos = indexOfSheetNameDelimiter-1;
if(reference.charAt(lastQuotePos) != SPECIAL_NAME_DELIMITER) {
throw new RuntimeException("Mismatched quotes: (" + reference + ")");
}
private static String parseSheetName(String reference, int indexOfSheetNameDelimiter) {
if(indexOfSheetNameDelimiter < 0) {
return null;
}
boolean isQuoted = reference.charAt(0) == SPECIAL_NAME_DELIMITER;
if(!isQuoted) {
return reference.substring(0, indexOfSheetNameDelimiter);
}
int lastQuotePos = indexOfSheetNameDelimiter-1;
if(reference.charAt(lastQuotePos) != SPECIAL_NAME_DELIMITER) {
throw new RuntimeException("Mismatched quotes: (" + reference + ")");
}
// TODO - refactor cell reference parsing logic to one place.
// Current known incarnations:
// FormulaParser.GetName()
// CellReference.parseSheetName() (here)
// AreaReference.separateAreaRefs()
// SheetNameFormatter.format() (inverse)
StringBuffer sb = new StringBuffer(indexOfSheetNameDelimiter);
for(int i=1; i<lastQuotePos; i++) { // Note boundaries - skip outer quotes
char ch = reference.charAt(i);
if(ch != SPECIAL_NAME_DELIMITER) {
sb.append(ch);
continue;
}
if(i < lastQuotePos) {
if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) {
// two consecutive quotes is the escape sequence for a single one
i++; // skip this and keep parsing the special name
sb.append(ch);
continue;
}
}
throw new RuntimeException("Bad sheet name quote escaping: (" + reference + ")");
}
return sb.toString();
}
// TODO - refactor cell reference parsing logic to one place.
// Current known incarnations:
// FormulaParser.GetName()
// CellReference.parseSheetName() (here)
// AreaReference.separateAreaRefs()
// SheetNameFormatter.format() (inverse)
StringBuffer sb = new StringBuffer(indexOfSheetNameDelimiter);
for(int i=1; i<lastQuotePos; i++) { // Note boundaries - skip outer quotes
char ch = reference.charAt(i);
if(ch != SPECIAL_NAME_DELIMITER) {
sb.append(ch);
continue;
}
if(i < lastQuotePos) {
if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) {
// two consecutive quotes is the escape sequence for a single one
i++; // skip this and keep parsing the special name
sb.append(ch);
continue;
}
}
throw new RuntimeException("Bad sheet name quote escaping: (" + reference + ")");
}
return sb.toString();
}
/**
* Takes in a 0-based base-10 column and returns a ALPHA-26
* representation.
* eg column #3 -> D
*/
protected static String convertNumToColString(int col) {
/**
* Takes in a 0-based base-10 column and returns a ALPHA-26
* representation.
* eg column #3 -> D
*/
protected static String convertNumToColString(int col) {
// Excel counts column A as the 1st column, we
// treat it as the 0th one
int excelColNum = col + 1;
@ -387,36 +407,36 @@ public class CellReference {
}
return colRef;
}
}
/**
* Example return values:
* <table border="0" cellpadding="1" cellspacing="0" summary="Example return values">
* <tr><th align='left'>Result</th><th align='left'>Comment</th></tr>
* <tr><td>A1</td><td>Cell reference without sheet</td></tr>
* <tr><td>Sheet1!A1</td><td>Standard sheet name</td></tr>
* <tr><td>'O''Brien''s Sales'!A1'&nbsp;</td><td>Sheet name with special characters</td></tr>
* </table>
* @return the text representation of this cell reference as it would appear in a formula.
*/
public String formatAsString() {
StringBuffer sb = new StringBuffer(32);
if(_sheetName != null) {
SheetNameFormatter.appendFormat(sb, _sheetName);
sb.append(SHEET_NAME_DELIMITER);
}
appendCellReference(sb);
return sb.toString();
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(formatAsString());
sb.append("]");
return sb.toString();
}
/**
* Example return values:
* <table border="0" cellpadding="1" cellspacing="0" summary="Example return values">
* <tr><th align='left'>Result</th><th align='left'>Comment</th></tr>
* <tr><td>A1</td><td>Cell reference without sheet</td></tr>
* <tr><td>Sheet1!A1</td><td>Standard sheet name</td></tr>
* <tr><td>'O''Brien''s Sales'!A1'&nbsp;</td><td>Sheet name with special characters</td></tr>
* </table>
* @return the text representation of this cell reference as it would appear in a formula.
*/
public String formatAsString() {
StringBuffer sb = new StringBuffer(32);
if(_sheetName != null) {
SheetNameFormatter.appendFormat(sb, _sheetName);
sb.append(SHEET_NAME_DELIMITER);
}
appendCellReference(sb);
return sb.toString();
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(formatAsString());
sb.append("]");
return sb.toString();
}
/**
* Returns the three parts of the cell reference, the
* Sheet name (or null if none supplied), the 1 based
@ -433,18 +453,18 @@ public class CellReference {
};
}
/**
* Appends cell reference with '$' markers for absolute values as required.
* Sheet name is not included.
*/
protected void appendCellReference(StringBuffer sb) {
if(_isColAbs) {
sb.append(ABSOLUTE_REFERENCE_MARKER);
}
sb.append( convertNumToColString(_colIndex));
if(_isRowAbs) {
sb.append(ABSOLUTE_REFERENCE_MARKER);
}
sb.append(_rowIndex+1);
}
/**
* Appends cell reference with '$' markers for absolute values as required.
* Sheet name is not included.
*/
/* package */ void appendCellReference(StringBuffer sb) {
if(_isColAbs) {
sb.append(ABSOLUTE_REFERENCE_MARKER);
}
sb.append( convertNumToColString(_colIndex));
if(_isRowAbs) {
sb.append(ABSOLUTE_REFERENCE_MARKER);
}
sb.append(_rowIndex+1);
}
}

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;
@ -553,14 +555,14 @@ public final class TestFormulaParser extends TestCase {
Class[] expClss;
expClss = new Class[] {
RefPtg.class,
expClss = new Class[] {
RefPtg.class,
AttrPtg.class, // tAttrIf
MissingArgPtg.class,
MissingArgPtg.class,
AttrPtg.class, // tAttrSkip
RefPtg.class,
AttrPtg.class, // tAttrSkip
FuncVarPtg.class,
FuncVarPtg.class,
};
confirmTokenClasses("if(A1, ,C1)", expClss);
@ -735,10 +737,10 @@ public final class TestFormulaParser extends TestCase {
/**
* Make sure that POI uses the right Func Ptg when encoding formulas. Functions with variable
* number of args should get FuncVarPtg, functions with fixed args should get FuncPtg.<p/>
*
*
* Prior to the fix for bug 44675 POI would encode FuncVarPtg for all functions. In many cases
* Excel tolerates the wrong Ptg and evaluates the formula OK (e.g. SIN), but in some cases
* (e.g. COUNTIF) Excel fails to evaluate the formula, giving '#VALUE!' instead.
* Excel tolerates the wrong Ptg and evaluates the formula OK (e.g. SIN), but in some cases
* (e.g. COUNTIF) Excel fails to evaluate the formula, giving '#VALUE!' instead.
*/
public void testFuncPtgSelection() {
@ -777,7 +779,7 @@ public final class TestFormulaParser extends TestCase {
parseFormula("round(3.14;2)");
throw new AssertionFailedError("Didn't get parse exception as expected");
} catch (RuntimeException e) {
FormulaParserTestHelper.confirmParseException(e,
FormulaParserTestHelper.confirmParseException(e,
"Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'");
}
@ -785,11 +787,11 @@ public final class TestFormulaParser extends TestCase {
parseFormula(" =2+2");
throw new AssertionFailedError("Didn't get parse exception as expected");
} catch (RuntimeException e) {
FormulaParserTestHelper.confirmParseException(e,
FormulaParserTestHelper.confirmParseException(e,
"The specified formula ' =2+2' starts with an equals sign which is not allowed.");
}
}
/**
* this function name has a dot in it.
*/
@ -798,8 +800,8 @@ public final class TestFormulaParser extends TestCase {
Ptg[] ptgs;
try {
ptgs = parseFormula("error.type(A1)");
} catch (IllegalArgumentException e) {
if (e.getMessage().equals("Invalid Formula cell reference: 'error'")) {
throw new AssertionFailedError("Identified bug 45334");
@ -811,7 +813,7 @@ public final class TestFormulaParser extends TestCase {
FuncPtg funcPtg = (FuncPtg) ptgs[1];
assertEquals("ERROR.TYPE", funcPtg.getName());
}
public void testNamedRangeThatLooksLikeCell() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("Sheet1");
@ -838,35 +840,35 @@ public final class TestFormulaParser extends TestCase {
cell.setCellFormula("count(pf1)");
throw new AssertionFailedError("Expected formula parse execption");
} catch (RuntimeException e) {
FormulaParserTestHelper.confirmParseException(e,
FormulaParserTestHelper.confirmParseException(e,
"Specified named range 'pf1' does not exist in the current workbook.");
}
cell.setCellFormula("count(fp1)"); // plain cell ref, col is in range
}
public void testParseAreaRefHighRow_bug45358() {
Ptg[] ptgs;
AreaI aptg;
HSSFWorkbook book = new HSSFWorkbook();
book.createSheet("Sheet1");
ptgs = HSSFFormulaParser.parse("Sheet1!A10:A40000", book);
aptg = (AreaI) ptgs[0];
if (aptg.getLastRow() == -25537) {
throw new AssertionFailedError("Identified bug 45358");
}
assertEquals(39999, aptg.getLastRow());
ptgs = HSSFFormulaParser.parse("Sheet1!A10:A65536", book);
aptg = (AreaI) ptgs[0];
assertEquals(65535, aptg.getLastRow());
// plain area refs should be ok too
ptgs = parseFormula("A10:A65536");
aptg = (AreaI) ptgs[0];
assertEquals(65535, aptg.getLastRow());
}
public void testParseArray() {
Ptg[] ptgs;
@ -875,11 +877,71 @@ public final class TestFormulaParser extends TestCase {
Ptg ptg0 = ptgs[0];
assertEquals(ArrayPtg.class, ptg0.getClass());
assertEquals("{1.0,2.0,2.0,#REF!;FALSE,3.0,3.0,2.0}", ptg0.toFormulaString());
ArrayPtg aptg = (ArrayPtg) ptg0;
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");
}
}
}

File diff suppressed because it is too large Load Diff

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());
}
}