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

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

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


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

View File

@ -67,6 +67,7 @@
<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action> <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>
<release version="3.2-alpha1" date="2008-??-??"> <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">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">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> <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> <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>
<release version="3.2-alpha1" date="2008-??-??"> <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">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">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> <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.NameRecord;
import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.SupBookRecord; import org.apache.poi.hssf.record.SupBookRecord;
import org.apache.poi.hssf.record.UnicodeString;
import org.apache.poi.hssf.record.formula.NameXPtg; import org.apache.poi.hssf.record.formula.NameXPtg;
/** /**
@ -109,8 +110,8 @@ final class LinkTable {
temp.toArray(_crnBlocks); temp.toArray(_crnBlocks);
} }
public ExternalBookBlock(short numberOfSheets) { public ExternalBookBlock(int numberOfSheets) {
_externalBookRecord = SupBookRecord.createInternalReferences(numberOfSheets); _externalBookRecord = SupBookRecord.createInternalReferences((short)numberOfSheets);
_externalNameRecords = new ExternalNameRecord[0]; _externalNameRecords = new ExternalNameRecord[0];
_crnBlocks = new CRNBlock[0]; _crnBlocks = new CRNBlock[0];
} }
@ -197,7 +198,7 @@ final class LinkTable {
return ExternSheetRecord.combine(esrs); return ExternSheetRecord.combine(esrs);
} }
public LinkTable(short numberOfSheets, WorkbookRecordList workbookRecordList) { public LinkTable(int numberOfSheets, WorkbookRecordList workbookRecordList) {
_workbookRecordList = workbookRecordList; _workbookRecordList = workbookRecordList;
_definedNames = new ArrayList(); _definedNames = new ArrayList();
_externalBookBlocks = new ExternalBookBlock[] { _externalBookBlocks = new ExternalBookBlock[] {
@ -303,8 +304,62 @@ final class LinkTable {
return lastName.getSheetNumber() == firstName.getSheetNumber(); return lastName.getSheetNumber() == firstName.getSheetNumber();
} }
public String[] getExternalBookAndSheetName(int extRefIndex) {
public int getIndexToSheet(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); return _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex);
} }
@ -315,20 +370,26 @@ final class LinkTable {
return _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex); return _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex);
} }
public int addSheetIndexToExternSheet(int sheetNumber) { public int checkExternSheet(int sheetIndex) {
// TODO - what about the first parameter (extBookIndex)? int thisWbIndex = -1; // this is probably always zero
return _externSheetRecord.addRef(0, sheetNumber, sheetNumber); for (int i=0; i<_externalBookBlocks.length; i++) {
} SupBookRecord ebr = _externalBookBlocks[i].getExternalBookRecord();
if (ebr.isInternalReferences()) {
public short checkExternSheet(int sheetIndex) { thisWbIndex = i;
break;
}
}
if (thisWbIndex < 0) {
throw new RuntimeException("Could not find 'internal references' EXTERNALBOOK");
}
//Trying to find reference to this sheet //Trying to find reference to this sheet
int i = _externSheetRecord.getRefIxForSheet(sheetIndex); int i = _externSheetRecord.getRefIxForSheet(thisWbIndex, sheetIndex);
if (i>=0) { if (i>=0) {
return (short)i; return i;
} }
//We Haven't found reference to this sheet //We haven't found reference to this sheet
return (short)addSheetIndexToExternSheet((short) sheetIndex); 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.*;
import org.apache.poi.hssf.record.formula.NameXPtg; import org.apache.poi.hssf.record.formula.NameXPtg;
import org.apache.poi.hssf.util.HSSFColor; 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.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
@ -328,9 +329,9 @@ public final class Workbook implements Model {
for ( int k = 0; k < nBoundSheets; k++ ) { for ( int k = 0; k < nBoundSheets; k++ ) {
BoundSheetRecord bsr = retval.createBoundSheet(k); BoundSheetRecord bsr = retval.createBoundSheet(k);
records.add(bsr); records.add(bsr);
retval.boundsheets.add(bsr); retval.boundsheets.add(bsr);
retval.records.setBspos(records.size() - 1); retval.records.setBspos(records.size() - 1);
} }
// retval.records.supbookpos = retval.records.bspos + 1; // retval.records.supbookpos = retval.records.bspos + 1;
// retval.records.namepos = retval.records.supbookpos + 2; // 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 * @param hidden 0 for not hidden, 1 for hidden, 2 for very hidden
*/ */
public void setSheetHidden(int sheetnum, int hidden) { public void setSheetHidden(int sheetnum, int hidden) {
BoundSheetRecord bsr = getBoundSheetRec(sheetnum); BoundSheetRecord bsr = getBoundSheetRec(sheetnum);
boolean h = false; boolean h = false;
boolean vh = false; boolean vh = false;
if(hidden == 0) { if(hidden == 0) {
} else if(hidden == 1) { } else if(hidden == 1) {
h = true; h = true;
} else if(hidden == 2) { } else if(hidden == 2) {
vh = true; vh = true;
} else { } else {
throw new IllegalArgumentException("Invalid hidden flag " + hidden + " given, must be 0, 1 or 2"); throw new IllegalArgumentException("Invalid hidden flag " + hidden + " given, must be 0, 1 or 2");
} }
bsr.setHidden(h); bsr.setHidden(h);
bsr.setVeryHidden(vh); bsr.setVeryHidden(vh);
} }
@ -761,23 +762,23 @@ public final class Workbook implements Model {
* have a Style set. * have a Style set.
*/ */
public StyleRecord getStyleRecord(int xfIndex) { public StyleRecord getStyleRecord(int xfIndex) {
// Style records always follow after // Style records always follow after
// the ExtendedFormat records // the ExtendedFormat records
boolean done = false; boolean done = false;
for(int i=records.getXfpos(); i<records.size() && for(int i=records.getXfpos(); i<records.size() &&
!done; i++) { !done; i++) {
Record r = records.get(i); Record r = records.get(i);
if(r instanceof ExtendedFormatRecord) { if(r instanceof ExtendedFormatRecord) {
} else if(r instanceof StyleRecord) { } else if(r instanceof StyleRecord) {
StyleRecord sr = (StyleRecord)r; StyleRecord sr = (StyleRecord)r;
if(sr.getIndex() == xfIndex) { if(sr.getIndex() == xfIndex) {
return sr; return sr;
} }
} else { } else {
done = true; done = true;
} }
} }
return null; return null;
} }
/** /**
* Creates a new StyleRecord, for the given Extended * Creates a new StyleRecord, for the given Extended
@ -785,29 +786,29 @@ public final class Workbook implements Model {
* records collection * records collection
*/ */
public StyleRecord createStyleRecord(int xfIndex) { public StyleRecord createStyleRecord(int xfIndex) {
// Style records always follow after // Style records always follow after
// the ExtendedFormat records // the ExtendedFormat records
StyleRecord newSR = new StyleRecord(); StyleRecord newSR = new StyleRecord();
newSR.setIndex((short)xfIndex); newSR.setIndex((short)xfIndex);
// Find the spot // Find the spot
int addAt = -1; int addAt = -1;
for(int i=records.getXfpos(); i<records.size() && for(int i=records.getXfpos(); i<records.size() &&
addAt == -1; i++) { addAt == -1; i++) {
Record r = records.get(i); Record r = records.get(i);
if(r instanceof ExtendedFormatRecord || if(r instanceof ExtendedFormatRecord ||
r instanceof StyleRecord) { r instanceof StyleRecord) {
// Keep going // Keep going
} else { } else {
addAt = i; addAt = i;
} }
} }
if(addAt == -1) { if(addAt == -1) {
throw new IllegalStateException("No XF Records found!"); throw new IllegalStateException("No XF Records found!");
} }
records.add(addAt, newSR); records.add(addAt, newSR);
return newSR; return newSR;
} }
/** /**
@ -1914,8 +1915,7 @@ public final class Workbook implements Model {
*/ */
public String findSheetNameFromExternSheet(int externSheetIndex){ public String findSheetNameFromExternSheet(int externSheetIndex){
int indexToSheet = linkTable.getIndexToSheet(externSheetIndex); int indexToSheet = linkTable.getIndexToInternalSheet(externSheetIndex);
if (indexToSheet < 0) { if (indexToSheet < 0) {
// TODO - what does '-1' mean here? // TODO - what does '-1' mean here?
//error check, bail out gracefully! //error check, bail out gracefully!
@ -1927,6 +1927,13 @@ public final class Workbook implements Model {
} }
return getSheetName(indexToSheet); 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. * 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 * @return index to extern sheet
*/ */
public short checkExternSheet(int sheetNumber){ 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 /** gets the total number of names
* @return number of names * @return number of names
*/ */

View File

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

View File

@ -221,8 +221,33 @@ public final class SupBookRecord extends Record {
{ {
return sid; return sid;
} }
public UnicodeString getURL() { public String getURL() {
return field_2_encoded_url; 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() { public UnicodeString[] getSheetNames() {
return (UnicodeString[]) field_3_sheet_names.clone(); return (UnicodeString[]) field_3_sheet_names.clone();

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@
package org.apache.poi.hssf.record.formula; package org.apache.poi.hssf.record.formula;
import org.apache.poi.ss.formula.FormulaRenderingWorkbook; import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
/** /**
* @author Josh Micich * @author Josh Micich
@ -29,13 +30,22 @@ final class ExternSheetNameResolver {
} }
public static String prependSheetName(FormulaRenderingWorkbook book, int field_1_index_extern_sheet, String cellRefText) { public static String prependSheetName(FormulaRenderingWorkbook book, int field_1_index_extern_sheet, String cellRefText) {
String sheetName = book.getSheetNameByExternSheet(field_1_index_extern_sheet); ExternalSheet externalSheet = book.getExternalSheet(field_1_index_extern_sheet);
StringBuffer sb = new StringBuffer(sheetName.length() + cellRefText.length() + 4); StringBuffer sb;
if (sheetName.length() < 1) { if (externalSheet != null) {
// What excel does if sheet has been deleted String wbName = externalSheet.getWorkbookName();
sb.append("#REF"); // note - '!' added just once below String sheetName = externalSheet.getSheetName();
sb = new StringBuffer(wbName.length() + sheetName.length() + cellRefText.length() + 4);
SheetNameFormatter.appendFormat(sb, wbName, sheetName);
} else { } 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('!');
sb.append(cellRefText); 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.hssf.record.RecordInputStream;
import org.apache.poi.ss.util.CellReference; 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.FormulaRenderingWorkbook;
import org.apache.poi.ss.formula.WorkbookDependentFormula;
import org.apache.poi.util.LittleEndian; 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) * @author Jason Height (jheight at chariot dot net dot au)
* @version 1.0-pre * @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; public final static byte sid = 0x3a;
private final static int SIZE = 7; // 6 + 1 for Ptg private final static int SIZE = 7; // 6 + 1 for Ptg
@ -75,11 +76,11 @@ public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormu
return SIZE; return SIZE;
} }
public int getExternSheetIndex(){ public int getExternSheetIndex() {
return field_1_index_extern_sheet; return field_1_index_extern_sheet;
} }
public void setExternSheetIndex(int index){ public void setExternSheetIndex(int index) {
field_1_index_extern_sheet = index; field_1_index_extern_sheet = index;
} }

View File

@ -66,6 +66,22 @@ public final class SheetNameFormatter {
out.append(rawSheetName); 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) { private static void appendAndEscape(StringBuffer sb, String rawSheetName) {
int len = rawSheetName.length(); int len = rawSheetName.length();
@ -101,13 +117,27 @@ public final class SheetNameFormatter {
return true; 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; 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 * @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 * 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) { /* package */ static boolean isSpecialChar(char ch) {
// note - Character.isJavaIdentifierPart() would allow dollars '$' // note - Character.isJavaIdentifierPart() would allow dollars '$'
@ -115,7 +145,8 @@ public final class SheetNameFormatter {
return false; return false;
} }
switch(ch) { switch(ch) {
case '_': // underscore is ok case '.': // dot is OK
case '_': // underscore is OK
return false; return false;
case '\n': case '\n':
case '\r': 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.HSSFFormulaParser;
import org.apache.poi.hssf.model.Workbook; 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.NameRecord;
import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg; import org.apache.poi.hssf.record.formula.NameXPtg;
import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ptg;
@ -39,6 +41,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
int sheetIndex = _uBook.getSheetIndex(sheetName); int sheetIndex = _uBook.getSheetIndex(sheetName);
return _iBook.checkExternSheet(sheetIndex); return _iBook.checkExternSheet(sheetIndex);
} }
public int getExternalSheetIndex(String workbookName, String sheetName) {
return _iBook.getExternalSheetIndex(workbookName, sheetName);
}
public EvaluationName getName(int index) { public EvaluationName getName(int index) {
return new Name(_iBook.getNameRecord(index), index); return new Name(_iBook.getNameRecord(index), index);
@ -57,6 +62,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
public int getSheetIndex(Sheet sheet) { public int getSheetIndex(Sheet sheet) {
return _uBook.getSheetIndex(sheet); return _uBook.getSheetIndex(sheet);
} }
public int getSheetIndex(String sheetName) {
return _uBook.getSheetIndex(sheetName);
}
public String getSheetName(int sheetIndex) { public String getSheetName(int sheetIndex) {
return _uBook.getSheetName(sheetIndex); return _uBook.getSheetName(sheetIndex);
@ -75,8 +83,12 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
} }
public int convertFromExternSheetIndex(int externSheetIndex) { public int convertFromExternSheetIndex(int externSheetIndex) {
return _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex); return _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);
} }
public ExternalSheet getExternalSheet(int externSheetIndex) {
return _iBook.getExternalSheet(externSheetIndex);
}
public HSSFWorkbook getWorkbook() { public HSSFWorkbook getWorkbook() {
return _uBook; return _uBook;
} }
@ -96,7 +108,15 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
return new Name(_iBook.getNameRecord(ix), ix); return new Name(_iBook.getNameRecord(ix), ix);
} }
public Ptg[] getFormulaTokens(Cell cell) { 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 { 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.NumberEval;
import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; 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.formula.WorkbookEvaluator;
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellValue; import org.apache.poi.ss.usermodel.CellValue;
@ -56,6 +57,21 @@ public class HSSFFormulaEvaluator /* almost implements FormulaEvaluator */ {
public HSSFFormulaEvaluator(HSSFWorkbook workbook) { public HSSFFormulaEvaluator(HSSFWorkbook workbook) {
_bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(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 * 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. * Creates an area ref from a pair of Cell References.
* Also normalises such that the top-left
*/ */
public AreaReference(CellReference topLeft, CellReference botRight) { public AreaReference(CellReference topLeft, CellReference botRight) {
super(topLeft, botRight); super(topLeft, botRight);

View File

@ -18,22 +18,13 @@
package org.apache.poi.hssf.util; 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 * cell references, and POI usermodel style row=0, column=0
* style references. * style references.
* @author Avik Sengupta * @author Avik Sengupta
* @author Dennis Doubleday (patch to seperateRowColumns()) * @author Dennis Doubleday (patch to seperateRowColumns())
*/ */
public final class CellReference extends org.apache.poi.ss.util.CellReference { 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 * 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. * 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) { public CellReference(int pRow, int pCol) {
super(pRow, pCol, true, true); 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) { public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
super(null, pRow, pCol, pAbsRow, 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) { public CellReference(String pSheetName, int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
super(pSheetName, pRow, pCol, pAbsRow, pAbsCol); super(pSheetName, pRow, pCol, pAbsRow, pAbsCol);
} }
protected void appendCellReference(StringBuffer sb) {
super.appendCellReference(sb);
}
protected static String convertNumToColString(int col) { protected static String convertNumToColString(int col) {
return org.apache.poi.ss.util.CellReference.convertNumToColString(col); return org.apache.poi.ss.util.CellReference.convertNumToColString(col);
} }

View File

@ -17,25 +17,32 @@
package org.apache.poi.ss.formula; package org.apache.poi.ss.formula;
import org.apache.poi.hssf.util.CellReference;
/** /**
* Stores the parameters that identify the evaluation of one cell.<br/> * Stores the parameters that identify the evaluation of one cell.<br/>
*/ */
final class CellLocation { final class CellLocation {
public static final CellLocation[] EMPTY_ARRAY = { }; public static final CellLocation[] EMPTY_ARRAY = { };
private final EvaluationWorkbook _book;
private final int _sheetIndex; private final int _sheetIndex;
private final int _rowIndex; private final int _rowIndex;
private final int _columnIndex; private final int _columnIndex;
private final int _hashCode; 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) { if (sheetIndex < 0) {
throw new IllegalArgumentException("sheetIndex must not be negative"); throw new IllegalArgumentException("sheetIndex must not be negative");
} }
_book = book;
_sheetIndex = sheetIndex; _sheetIndex = sheetIndex;
_rowIndex = rowIndex; _rowIndex = rowIndex;
_columnIndex = columnIndex; _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() { public int getSheetIndex() {
return _sheetIndex; return _sheetIndex;
@ -49,15 +56,18 @@ final class CellLocation {
public boolean equals(Object obj) { public boolean equals(Object obj) {
CellLocation other = (CellLocation) obj; CellLocation other = (CellLocation) obj;
if (getSheetIndex() != other.getSheetIndex()) {
return false;
}
if (getRowIndex() != other.getRowIndex()) { if (getRowIndex() != other.getRowIndex()) {
return false; return false;
} }
if (getColumnIndex() != other.getColumnIndex()) { if (getColumnIndex() != other.getColumnIndex()) {
return false; return false;
} }
if (getSheetIndex() != other.getSheetIndex()) {
return false;
}
if (getBook() != other.getBook()) {
return false;
}
return true; return true;
} }
public int hashCode() { public int hashCode() {
@ -68,7 +78,8 @@ final class CellLocation {
* @return human readable string for debug purposes * @return human readable string for debug purposes
*/ */
public String formatAsString() { 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() { 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()); + cellLoc.formatAsString());
} }
} }
if (_evaluationListener == null) { sortCellLocationsForLogging(usedCells);
// 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);
}
CellCacheEntry entry = getEntry(cellLoc); CellCacheEntry entry = getEntry(cellLoc);
CellLocation[] consumingFormulaCells = entry.getConsumingCells(); CellLocation[] consumingFormulaCells = entry.getConsumingCells();
CellLocation[] prevUsedCells = entry.getUsedCells(); CellLocation[] prevUsedCells = entry.getUsedCells();
@ -110,6 +104,18 @@ final class EvaluationCache {
recurseClearCachedFormulaResults(consumingFormulaCells, 0); 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, private void unlinkConsumingCells(CellLocation[] prevUsedCells, CellLocation[] usedCells,
CellLocation cellLoc) { CellLocation cellLoc) {
if (prevUsedCells == null) { if (prevUsedCells == null) {
@ -149,6 +155,7 @@ final class EvaluationCache {
* @param formulaCells * @param formulaCells
*/ */
private void recurseClearCachedFormulaResults(CellLocation[] formulaCells, int depth) { private void recurseClearCachedFormulaResults(CellLocation[] formulaCells, int depth) {
sortCellLocationsForLogging(formulaCells);
int nextDepth = depth+1; int nextDepth = depth+1;
for (int i = 0; i < formulaCells.length; i++) { for (int i = 0; i < formulaCells.length; i++) {
CellLocation fc = formulaCells[i]; CellLocation fc = formulaCells[i];
@ -196,6 +203,10 @@ final class EvaluationCache {
CellLocation clB = (CellLocation) b; CellLocation clB = (CellLocation) b;
int cmp; int cmp;
cmp = System.identityHashCode(clA.getBook()) - System.identityHashCode(clB.getBook());
if (cmp != 0) {
return cmp;
}
cmp = clA.getSheetIndex() - clB.getSheetIndex(); cmp = clA.getSheetIndex() - clB.getSheetIndex();
if (cmp != 0) { if (cmp != 0) {
return cmp; return cmp;

View File

@ -31,12 +31,36 @@ import org.apache.poi.ss.usermodel.Sheet;
*/ */
public interface EvaluationWorkbook { public interface EvaluationWorkbook {
String getSheetName(int sheetIndex); String getSheetName(int sheetIndex);
/**
* @return -1 if the specified sheet is from a different book
*/
int getSheetIndex(Sheet sheet); int getSheetIndex(Sheet sheet);
int getSheetIndex(String sheetName);
Sheet getSheet(int sheetIndex); 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); int convertFromExternSheetIndex(int externSheetIndex);
EvaluationName getName(NamePtg namePtg); EvaluationName getName(NamePtg namePtg);
String resolveNameXText(NameXPtg ptg); String resolveNameXText(NameXPtg ptg);
Ptg[] getFormulaTokens(Cell cell); 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.PercentPtg;
import org.apache.poi.hssf.record.formula.PowerPtg; import org.apache.poi.hssf.record.formula.PowerPtg;
import org.apache.poi.hssf.record.formula.Ptg; 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.Ref3DPtg;
import org.apache.poi.hssf.record.formula.RefPtg; import org.apache.poi.hssf.record.formula.RefPtg;
import org.apache.poi.hssf.record.formula.StringPtg; 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.usermodel.HSSFErrorConstants;
import org.apache.poi.hssf.util.AreaReference; import org.apache.poi.hssf.util.AreaReference;
import org.apache.poi.hssf.util.CellReference; import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.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. * 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 * @author Josh Micich
*/ */
public final class FormulaParser { 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/> * Specific exception thrown when a supplied formula does not parse properly.<br/>
@ -176,23 +204,23 @@ public final class FormulaParser {
} }
/** Recognize an Alpha Character */ /** Recognize an Alpha Character */
private boolean IsAlpha(char c) { private static boolean IsAlpha(char c) {
return Character.isLetter(c) || c == '$' || c=='_'; return Character.isLetter(c) || c == '$' || c=='_';
} }
/** Recognize a Decimal Digit */ /** Recognize a Decimal Digit */
private boolean IsDigit(char c) { private static boolean IsDigit(char c) {
return Character.isDigit(c); return Character.isDigit(c);
} }
/** Recognize an Alphanumeric */ /** Recognize an Alphanumeric */
private boolean IsAlNum(char c) { private static boolean IsAlNum(char c) {
return (IsAlpha(c) || IsDigit(c)); return IsAlpha(c) || IsDigit(c);
} }
/** Recognize White Space */ /** Recognize White Space */
private boolean IsWhite( char c) { private static boolean IsWhite( char c) {
return (c ==' ' || c== TAB); return c ==' ' || c== TAB;
} }
/** Skip Over Leading White Space */ /** Skip Over Leading White Space */
@ -213,7 +241,13 @@ public final class FormulaParser {
} }
GetChar(); 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/> * 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 * 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 * may return a value like "A1..B2", in which case the caller must convert it to
* an area reference like "A1:B2" * an area reference like "A1:B2"
*/ */
private String parseIdentifier() { private Identifier parseIdentifier() {
StringBuffer Token = new StringBuffer(); StringBuffer sb = new StringBuffer();
if (!IsAlpha(look) && look != '\'') { if (!IsAlpha(look) && look != '\'' && look != '[') {
throw expected("Name"); throw expected("Name");
} }
if(look == '\'') boolean isQuoted = look == '\'';
{ if(isQuoted) {
Match('\''); Match('\'');
boolean done = look == '\''; boolean done = look == '\'';
while(!done) while(!done) {
{ sb.append(look);
Token.append(look);
GetChar(); GetChar();
if(look == '\'') if(look == '\'')
{ {
@ -240,17 +273,15 @@ public final class FormulaParser {
done = look != '\''; done = look != '\'';
} }
} }
} } else {
else
{
// allow for any sequence of dots and identifier chars // allow for any sequence of dots and identifier chars
// special case of two consecutive dots is best treated in the calling code // special case of two consecutive dots is best treated in the calling code
while (IsAlNum(look) || look == '.') { while (IsAlNum(look) || look == '.' || look == '[' || look == ']') {
Token.append(look); sb.append(look);
GetChar(); GetChar();
} }
} }
return Token.toString(); return new Identifier(sb.toString(), isQuoted);
} }
/** Get a Number */ /** Get a Number */
@ -265,72 +296,112 @@ public final class FormulaParser {
} }
private ParseNode parseFunctionReferenceOrName() { private ParseNode parseFunctionReferenceOrName() {
String name = parseIdentifier(); Identifier iden = parseIdentifier();
if (look == '('){ if (look == '('){
//This is a function //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); AreaReference areaRef = parseArea(name);
if (areaRef != null) { if (areaRef != null) {
// will happen if dots are used instead of colon // will happen if dots are used instead of colon
return new AreaPtg(areaRef.formatAsString()); 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 // This can be either a cell ref or a named range
// Try to spot which it is
int nameType = CellReference.classifyCellReference(name); int nameType = CellReference.classifyCellReference(name);
if (nameType == NameType.CELL) { if (nameType == NameType.CELL) {
return new RefPtg(name); 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) { if (nameType != NameType.NAMED_RANGE) {
new FormulaParseException("Name '" + name new FormulaParseException("Name '" + name
+ "' does not look like a cell reference or named range"); + "' 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"); + 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) * @param name an 'identifier' like string (i.e. contains alphanums, and dots)
* @return <code>null</code> if name cannot be split at a dot * @return <code>null</code> if name cannot be split at a dot
@ -585,7 +667,7 @@ public final class FormulaParser {
Match('}'); Match('}');
return arrayNode; return arrayNode;
} }
if (IsAlpha(look) || look == '\''){ if (IsAlpha(look) || look == '\'' || look == '['){
return parseFunctionReferenceOrName(); return parseFunctionReferenceOrName();
} }
// else - assume number // else - assume number
@ -662,7 +744,7 @@ public final class FormulaParser {
} }
private Boolean parseBooleanLiteral() { private Boolean parseBooleanLiteral() {
String iden = parseIdentifier(); String iden = parseUnquotedIdentifier();
if ("TRUE".equalsIgnoreCase(iden)) { if ("TRUE".equalsIgnoreCase(iden)) {
return Boolean.TRUE; return Boolean.TRUE;
} }
@ -720,7 +802,7 @@ public final class FormulaParser {
private int parseErrorLiteral() { private int parseErrorLiteral() {
Match('#'); Match('#');
String part1 = parseIdentifier().toUpperCase(); String part1 = parseUnquotedIdentifier().toUpperCase();
switch(part1.charAt(0)) { switch(part1.charAt(0)) {
case 'V': case 'V':

View File

@ -32,6 +32,16 @@ public interface FormulaParsingWorkbook {
*/ */
EvaluationName getName(String name); EvaluationName getName(String name);
int getExternalSheetIndex(String sheetName);
NameXPtg getNameXPtg(String name); 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.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg; 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/> * 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 { 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 getSheetNameByExternSheet(int externSheetIndex);
String resolveNameXText(NameXPtg nameXPtg); String resolveNameXText(NameXPtg nameXPtg);
String getNameText(NamePtg namePtg); 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.AbstractFunctionPtg;
import org.apache.poi.hssf.record.formula.ControlPtg; 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.ValueOperatorPtg;
import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ptg;
@ -115,6 +116,10 @@ final class OperandClassTransformer {
return; return;
} }
if (children.length > 0) { 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"); 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.PercentPtg;
import org.apache.poi.hssf.record.formula.PowerPtg; import org.apache.poi.hssf.record.formula.PowerPtg;
import org.apache.poi.hssf.record.formula.Ptg; 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.SubtractPtg;
import org.apache.poi.hssf.record.formula.UnaryMinusPtg; import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
import org.apache.poi.hssf.record.formula.UnaryPlusPtg; 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.OperationEval;
import org.apache.poi.hssf.record.formula.eval.PercentEval; 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.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.SubtractEval;
import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval;
import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval;
@ -101,6 +103,7 @@ final class OperationEvaluatorFactory {
add(m, SubtractPtg.class, SubtractEval.instance); add(m, SubtractPtg.class, SubtractEval.instance);
add(m, UnaryMinusPtg.class, UnaryMinusEval.instance); add(m, UnaryMinusPtg.class, UnaryMinusEval.instance);
add(m, UnaryPlusPtg.class, UnaryPlusEval.instance); add(m, UnaryPlusPtg.class, UnaryPlusEval.instance);
add(m, RangePtg.class, RangeEval.instance);
return m; return m;
} }

View File

@ -22,12 +22,18 @@ import java.util.Map;
import java.util.Stack; import java.util.Stack;
import org.apache.poi.hssf.record.formula.Area3DPtg; 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.AreaPtg;
import org.apache.poi.hssf.record.formula.AttrPtg;
import org.apache.poi.hssf.record.formula.BoolPtg; import org.apache.poi.hssf.record.formula.BoolPtg;
import org.apache.poi.hssf.record.formula.ControlPtg; 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.ErrPtg;
import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.IntPtg; import org.apache.poi.hssf.record.formula.IntPtg;
import org.apache.poi.hssf.record.formula.MemErrPtg; 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.MissingArgPtg;
import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg; 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.OperationPtg;
import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.Ref3DPtg; 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.RefPtg;
import org.apache.poi.hssf.record.formula.StringPtg; import org.apache.poi.hssf.record.formula.StringPtg;
import org.apache.poi.hssf.record.formula.UnionPtg; 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.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.util.CellReference; 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.Cell;
import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
@ -68,13 +76,14 @@ import org.apache.poi.ss.usermodel.Sheet;
* *
* @author Josh Micich * @author Josh Micich
*/ */
public class WorkbookEvaluator { public final class WorkbookEvaluator {
private final EvaluationWorkbook _workbook; private final EvaluationWorkbook _workbook;
private final EvaluationCache _cache; private EvaluationCache _cache;
private final IEvaluationListener _evaluationListener; private final IEvaluationListener _evaluationListener;
private final Map _sheetIndexesBySheet; private final Map _sheetIndexesBySheet;
private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
public WorkbookEvaluator(EvaluationWorkbook workbook) { public WorkbookEvaluator(EvaluationWorkbook workbook) {
this (workbook, null); this (workbook, null);
@ -84,6 +93,7 @@ public class WorkbookEvaluator {
_evaluationListener = evaluationListener; _evaluationListener = evaluationListener;
_cache = new EvaluationCache(evaluationListener); _cache = new EvaluationCache(evaluationListener);
_sheetIndexesBySheet = new IdentityHashMap(); _sheetIndexesBySheet = new IdentityHashMap();
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
} }
/** /**
@ -101,7 +111,22 @@ public class WorkbookEvaluator {
System.out.println(s); 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. * 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 * 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"); throw new IllegalArgumentException("value must not be null");
} }
int sheetIndex = getSheetIndex(sheet); 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) { public void notifySetFormula(Sheet sheet, int rowIndex, int columnIndex) {
int sheetIndex = getSheetIndex(sheet); 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) { private int getSheetIndex(Sheet sheet) {
Integer result = (Integer) _sheetIndexesBySheet.get(sheet); Integer result = (Integer) _sheetIndexesBySheet.get(sheet);
if (result == null) { 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); _sheetIndexesBySheet.put(sheet, result);
} }
return result.intValue(); return result.intValue();
@ -146,7 +175,7 @@ public class WorkbookEvaluator {
public ValueEval evaluate(Cell srcCell) { public ValueEval evaluate(Cell srcCell) {
int sheetIndex = getSheetIndex(srcCell.getSheet()); 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)); return internalEvaluate(srcCell, cellLoc, new EvaluationTracker(_cache));
} }
@ -181,10 +210,10 @@ public class WorkbookEvaluator {
isPlainFormulaCell = false; isPlainFormulaCell = false;
Ptg[] ptgs = _workbook.getFormulaTokens(srcCell); Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
if(evalListener == null) { if(evalListener == null) {
result = evaluateCell(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker); result = evaluateFormula(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
} else { } else {
evalListener.onStartEvaluate(sheetIndex, rowIndex, columnIndex, ptgs); 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); evalListener.onEndEvaluate(sheetIndex, rowIndex, columnIndex, result);
} }
} }
@ -225,17 +254,31 @@ public class WorkbookEvaluator {
} }
throw new RuntimeException("Unexpected cell type (" + cellType + ")"); 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(); Stack stack = new Stack();
for (int i = 0, iSize = ptgs.length; i < iSize; i++) { for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
// since we don't know how to handle these yet :( // since we don't know how to handle these yet :(
Ptg ptg = ptgs[i]; 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) { if (ptg instanceof ControlPtg) {
// skip Parentheses, Attr, etc // skip Parentheses, Attr, etc
continue; 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 MemErrPtg) { continue; }
if (ptg instanceof MissingArgPtg) { if (ptg instanceof MissingArgPtg) {
// TODO - might need to push BlankEval or MissingArgEval // 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>, * @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>,
* <tt>BlankEval</tt> or <tt>ErrorEval</tt>. Never <code>null</code>. * <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) { if (evaluationResult instanceof RefEval) {
RefEval rv = (RefEval) evaluationResult; RefEval rv = (RefEval) evaluationResult;
return rv.getInnerValueEval(); return rv.getInnerValueEval();
@ -321,6 +364,20 @@ public class WorkbookEvaluator {
} }
return operation.evaluate(ops, srcRowNum, (short)srcColNum); 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 * returns an appropriate Eval impl instance for the Ptg. The Ptg must be
@ -329,6 +386,8 @@ public class WorkbookEvaluator {
* passed here! * passed here!
*/ */
private Eval getEvalForPtg(Ptg ptg, int sheetIndex, EvaluationTracker tracker) { 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) { if (ptg instanceof NamePtg) {
// named ranges, macro functions // named ranges, macro functions
NamePtg namePtg = (NamePtg) ptg; NamePtg namePtg = (NamePtg) ptg;
@ -361,16 +420,18 @@ public class WorkbookEvaluator {
if (ptg instanceof ErrPtg) { if (ptg instanceof ErrPtg) {
return ErrorEval.valueOf(((ErrPtg) ptg).getErrorCode()); 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) { if (ptg instanceof Ref3DPtg) {
Ref3DPtg refPtg = (Ref3DPtg) ptg; Ref3DPtg refPtg = (Ref3DPtg) ptg;
int otherSheetIndex = _workbook.convertFromExternSheetIndex(refPtg.getExternSheetIndex()); SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, refPtg);
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
return new LazyRefEval(refPtg, sre); return new LazyRefEval(refPtg, sre);
} }
if (ptg instanceof Area3DPtg) { if (ptg instanceof Area3DPtg) {
Area3DPtg aptg = (Area3DPtg) ptg; Area3DPtg aptg = (Area3DPtg) ptg;
int otherSheetIndex = _workbook.convertFromExternSheetIndex(aptg.getExternSheetIndex()); SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, aptg);
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
return new LazyAreaEval(aptg, sre); return new LazyAreaEval(aptg, sre);
} }
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, sheetIndex); SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, sheetIndex);
@ -410,7 +471,7 @@ public class WorkbookEvaluator {
} else { } else {
cell = row.getCell(columnIndex); cell = row.getCell(columnIndex);
} }
CellLocation cellLoc = new CellLocation(sheetIndex, rowIndex, columnIndex); CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex);
tracker.acceptDependency(cellLoc); tracker.acceptDependency(cellLoc);
return internalEvaluate(cell, cellLoc, tracker); return internalEvaluate(cell, cellLoc, tracker);
} }

View File

@ -90,7 +90,7 @@ public class AreaReference {
for(int i=refPart.length()-1; i>=0; i--) { for(int i=refPart.length()-1; i>=0; i--) {
int ch = refPart.charAt(i); int ch = refPart.charAt(i);
if (ch == '$' && i==0) { if (ch == '$' && i==0) {
continue; continue;
} }
if (ch < 'A' || ch > 'Z') { if (ch < 'A' || ch > 'Z') {
return false; return false;
@ -103,8 +103,45 @@ public class AreaReference {
* Creates an area ref from a pair of Cell References. * Creates an area ref from a pair of Cell References.
*/ */
public AreaReference(CellReference topLeft, CellReference botRight) { public AreaReference(CellReference topLeft, CellReference botRight) {
_firstCell = topLeft; boolean swapRows = topLeft.getRow() > botRight.getRow();
_lastCell = botRight; 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; _isSingleCell = false;
} }

View File

@ -34,21 +34,28 @@ public class CellReference {
public static final class NameType { public static final class NameType {
public static final int CELL = 1; public static final int CELL = 1;
public static final int NAMED_RANGE = 2; public static final int NAMED_RANGE = 2;
public static final int COLUMN = 3;
public static final int BAD_CELL_OR_NAMED_RANGE = -1; public static final int BAD_CELL_OR_NAMED_RANGE = -1;
} }
/** The character ($) that signifies a row or column value is absolute instead of relative */ /** The character ($) that signifies a row or column value is absolute instead of relative */
private static final char ABSOLUTE_REFERENCE_MARKER = '$'; private static final char ABSOLUTE_REFERENCE_MARKER = '$';
/** The character (!) that separates sheet names from cell references */ /** The character (!) that separates sheet names from cell references */
private static final char SHEET_NAME_DELIMITER = '!'; private static final char SHEET_NAME_DELIMITER = '!';
/** The character (') used to quote sheet names when they contain special characters */ /** The character (') used to quote sheet names when they contain special characters */
private static final char SPECIAL_NAME_DELIMITER = '\''; 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 * Matches a run of one or more letters followed by a run of one or more digits.
* run of digits is group 2. Each group may optionally be prefixed with a single '$'. * 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]+)"); 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 * Named range names must start with a letter or underscore. Subsequent characters may include
* digits or dot. (They can even end in dot). * digits or dot. (They can even end in dot).
@ -65,96 +72,96 @@ public class CellReference {
private final boolean _isRowAbs; private final boolean _isRowAbs;
private final boolean _isColAbs; private final boolean _isColAbs;
/** /**
* Create an cell ref from a string representation. Sheet names containing special characters should be * 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. * delimited and escaped as per normal syntax rules for formulas.
*/ */
public CellReference(String cellRef) { public CellReference(String cellRef) {
String[] parts = separateRefParts(cellRef); String[] parts = separateRefParts(cellRef);
_sheetName = parts[0]; _sheetName = parts[0];
String colRef = parts[1]; String colRef = parts[1];
if (colRef.length() < 1) { if (colRef.length() < 1) {
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
} }
_isColAbs = colRef.charAt(0) == '$'; _isColAbs = colRef.charAt(0) == '$';
if (_isColAbs) { if (_isColAbs) {
colRef=colRef.substring(1); colRef=colRef.substring(1);
} }
_colIndex = convertColStringToIndex(colRef); _colIndex = convertColStringToIndex(colRef);
String rowRef=parts[2]; String rowRef=parts[2];
if (rowRef.length() < 1) { if (rowRef.length() < 1) {
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
} }
_isRowAbs = rowRef.charAt(0) == '$'; _isRowAbs = rowRef.charAt(0) == '$';
if (_isRowAbs) { if (_isRowAbs) {
rowRef=rowRef.substring(1); rowRef=rowRef.substring(1);
} }
_rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based _rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based
} }
public CellReference(int pRow, int pCol) { public CellReference(int pRow, int pCol) {
this(pRow, pCol & 0xFFFF, false, false); this(pRow, pCol, false, false);
} }
public CellReference(int pRow, short pCol) { public CellReference(int pRow, short pCol) {
this(pRow, (int)pCol, false, false); this(pRow, pCol & 0xFFFF, false, false);
} }
public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) { public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
this(null, pRow, pCol, pAbsRow, pAbsCol); this(null, pRow, pCol, pAbsRow, pAbsCol);
} }
public CellReference(String pSheetName, int pRow, int pCol, boolean pAbsRow, boolean 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. // 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. // so these checks are currently N.Q.R.
if(pRow < -1) { if(pRow < -1) {
throw new IllegalArgumentException("row index may not be negative"); throw new IllegalArgumentException("row index may not be negative");
} }
if(pCol < -1) { if(pCol < -1) {
throw new IllegalArgumentException("column index may not be negative"); throw new IllegalArgumentException("column index may not be negative");
} }
_sheetName = pSheetName; _sheetName = pSheetName;
_rowIndex=pRow; _rowIndex=pRow;
_colIndex=pCol; _colIndex=pCol;
_isRowAbs = pAbsRow; _isRowAbs = pAbsRow;
_isColAbs=pAbsCol; _isColAbs=pAbsCol;
} }
public int getRow(){return _rowIndex;} public int getRow(){return _rowIndex;}
public short getCol(){return (short) _colIndex;} public short getCol(){return (short) _colIndex;}
public boolean isRowAbsolute(){return _isRowAbs;} public boolean isRowAbsolute(){return _isRowAbs;}
public boolean isColAbsolute(){return _isColAbs;} public boolean isColAbsolute(){return _isColAbs;}
/** /**
* @return possibly <code>null</code> if this is a 2D reference. Special characters are not * @return possibly <code>null</code> if this is a 2D reference. Special characters are not
* escaped or delimited * escaped or delimited
*/ */
public String getSheetName(){ public String getSheetName(){
return _sheetName; return _sheetName;
} }
public static boolean isPartAbsolute(String part) { public static boolean isPartAbsolute(String part) {
return part.charAt(0) == ABSOLUTE_REFERENCE_MARKER; return part.charAt(0) == ABSOLUTE_REFERENCE_MARKER;
} }
/**
/** * takes in a column reference portion of a CellRef and converts it from
* takes in a column reference portion of a CellRef and converts it from * ALPHA-26 number format to 0-based base 10.
* ALPHA-26 number format to 0-based base 10. * 'A' -> 0
* 'A' -> 0 * 'Z' -> 25
* 'Z' -> 25 * 'AA' -> 26
* 'AA' -> 26 * 'IV' -> 255
* 'IV' -> 255 * @return zero based column index
* @return zero based column index */
*/ public static int convertColStringToIndex(String ref) {
protected static int convertColStringToIndex(String ref) {
int pos = 0; int pos = 0;
int retval=0; int retval=0;
for (int k = ref.length()-1; k >= 0; k--) { for (int k = ref.length()-1; k >= 0; k--) {
char thechar = ref.charAt(k); char thechar = ref.charAt(k);
if (thechar == ABSOLUTE_REFERENCE_MARKER) { if (thechar == ABSOLUTE_REFERENCE_MARKER) {
if (k != 0) { if (k != 0) {
throw new IllegalArgumentException("Bad col ref format '" + ref + "'"); throw new IllegalArgumentException("Bad col ref format '" + ref + "'");
} }
break; break;
} }
// Character.getNumericValue() returns the values // Character.getNumericValue() returns the values
// 10-35 for the letter A-Z // 10-35 for the letter A-Z
int shift = (int)Math.pow(26, pos); int shift = (int)Math.pow(26, pos);
@ -162,64 +169,70 @@ public class CellReference {
pos++; pos++;
} }
return retval-1; return retval-1;
} }
/** /**
* Classifies an identifier as either a simple (2D) cell reference or a named range name * Classifies an identifier as either a simple (2D) cell reference or a named range name
* @return one of the values from <tt>NameType</tt> * @return one of the values from <tt>NameType</tt>
*/ */
public static int classifyCellReference(String str) { public static int classifyCellReference(String str) {
int len = str.length(); int len = str.length();
if (len < 1) { if (len < 1) {
throw new IllegalArgumentException("Empty string not allowed"); throw new IllegalArgumentException("Empty string not allowed");
} }
char firstChar = str.charAt(0); char firstChar = str.charAt(0);
switch (firstChar) { switch (firstChar) {
case ABSOLUTE_REFERENCE_MARKER: case ABSOLUTE_REFERENCE_MARKER:
case '.': case '.':
case '_': case '_':
break; break;
default: default:
if (!Character.isLetter(firstChar)) { if (!Character.isLetter(firstChar)) {
throw new IllegalArgumentException("Invalid first char (" + firstChar throw new IllegalArgumentException("Invalid first char (" + firstChar
+ ") of cell reference or named range. Letter expected"); + ") of cell reference or named range. Letter expected");
} }
} }
if (!Character.isDigit(str.charAt(len-1))) { if (!Character.isDigit(str.charAt(len-1))) {
// no digits at end of str // no digits at end of str
return validateNamedRangeName(str); return validateNamedRangeName(str);
} }
Matcher cellRefPatternMatcher = CELL_REF_PATTERN.matcher(str); Matcher cellRefPatternMatcher = CELL_REF_PATTERN.matcher(str);
if (!cellRefPatternMatcher.matches()) { if (!cellRefPatternMatcher.matches()) {
return validateNamedRangeName(str); return validateNamedRangeName(str);
} }
String lettersGroup = cellRefPatternMatcher.group(1); String lettersGroup = cellRefPatternMatcher.group(1);
String digitsGroup = cellRefPatternMatcher.group(2); String digitsGroup = cellRefPatternMatcher.group(2);
if (cellReferenceIsWithinRange(lettersGroup, digitsGroup)) { if (cellReferenceIsWithinRange(lettersGroup, digitsGroup)) {
// valid cell reference // valid cell reference
return NameType.CELL; return NameType.CELL;
} }
// If str looks like a cell reference, but is out of (row/col) range, it is a valid // If str looks like a cell reference, but is out of (row/col) range, it is a valid
// named range name // named range name
// This behaviour is a little weird. For example, "IW123" 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 // because the column "IW" is beyond the maximum "IV". Note - this behaviour is version
// dependent. In Excel 2007, "IW123" is not a valid named range name. // dependent. In BIFF12, "IW123" is not a valid named range name, but in BIFF8 it is.
if (str.indexOf(ABSOLUTE_REFERENCE_MARKER) >= 0) { if (str.indexOf(ABSOLUTE_REFERENCE_MARKER) >= 0) {
// Of course, named range names cannot have '$' // Of course, named range names cannot have '$'
return NameType.BAD_CELL_OR_NAMED_RANGE; return NameType.BAD_CELL_OR_NAMED_RANGE;
} }
return NameType.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()) { if (!NAMED_RANGE_NAME_PATTERN.matcher(str).matches()) {
return NameType.BAD_CELL_OR_NAMED_RANGE; return NameType.BAD_CELL_OR_NAMED_RANGE;
} }
return NameType.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 * 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 * 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" * <blockquote><table border="0" cellpadding="1" cellspacing="0"
* summary="Notable cases."> * summary="Notable cases.">
* <tr><th>Input&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th> * <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", "1"</td><td>true</td></tr>
* <tr><td>"a", "111"</td><td>true</td></tr> * <tr><td>"a", "111"</td><td>true</td></tr>
* <tr><td>"A", "65536"</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. * @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) { public static boolean cellReferenceIsWithinRange(String colStr, String rowStr) {
int numberOfLetters = colStr.length(); if (!isColumnWithnRange(colStr)) {
if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) { return false;
// "Sheet1" case etc
return false; // that was easy
} }
int nDigits = rowStr.length(); int nDigits = rowStr.length();
if(nDigits > BIFF8_LAST_ROW_TEXT_LEN) { if(nDigits > BIFF8_LAST_ROW_TEXT_LEN) {
return false; 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) { if(nDigits == BIFF8_LAST_ROW_TEXT_LEN) {
// ASCII comparison is valid if digit count is same // ASCII comparison is valid if digit count is same
@ -288,87 +291,104 @@ public class CellReference {
return true; 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 * 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 * 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. * name still in ALPHA-26 number format. The third element is the row.
*/ */
private static String[] separateRefParts(String reference) { private static String[] separateRefParts(String reference) {
int plingPos = reference.lastIndexOf(SHEET_NAME_DELIMITER); int plingPos = reference.lastIndexOf(SHEET_NAME_DELIMITER);
String sheetName = parseSheetName(reference, plingPos); String sheetName = parseSheetName(reference, plingPos);
int start = plingPos+1; int start = plingPos+1;
int length = reference.length(); int length = reference.length();
int loc = start; int loc = start;
// skip initial dollars // skip initial dollars
if (reference.charAt(loc)==ABSOLUTE_REFERENCE_MARKER) { if (reference.charAt(loc)==ABSOLUTE_REFERENCE_MARKER) {
loc++; loc++;
} }
// step over column name chars until first digit (or dollars) for row number. // step over column name chars until first digit (or dollars) for row number.
for (; loc < length; loc++) { for (; loc < length; loc++) {
char ch = reference.charAt(loc); char ch = reference.charAt(loc);
if (Character.isDigit(ch) || ch == ABSOLUTE_REFERENCE_MARKER) { if (Character.isDigit(ch) || ch == ABSOLUTE_REFERENCE_MARKER) {
break; break;
} }
} }
return new String[] { return new String[] {
sheetName, sheetName,
reference.substring(start,loc), reference.substring(start,loc),
reference.substring(loc), reference.substring(loc),
}; };
} }
private static String parseSheetName(String reference, int indexOfSheetNameDelimiter) { private static String parseSheetName(String reference, int indexOfSheetNameDelimiter) {
if(indexOfSheetNameDelimiter < 0) { if(indexOfSheetNameDelimiter < 0) {
return null; return null;
} }
boolean isQuoted = reference.charAt(0) == SPECIAL_NAME_DELIMITER; boolean isQuoted = reference.charAt(0) == SPECIAL_NAME_DELIMITER;
if(!isQuoted) { if(!isQuoted) {
return reference.substring(0, indexOfSheetNameDelimiter); return reference.substring(0, indexOfSheetNameDelimiter);
} }
int lastQuotePos = indexOfSheetNameDelimiter-1; int lastQuotePos = indexOfSheetNameDelimiter-1;
if(reference.charAt(lastQuotePos) != SPECIAL_NAME_DELIMITER) { if(reference.charAt(lastQuotePos) != SPECIAL_NAME_DELIMITER) {
throw new RuntimeException("Mismatched quotes: (" + reference + ")"); throw new RuntimeException("Mismatched quotes: (" + reference + ")");
} }
// TODO - refactor cell reference parsing logic to one place. // TODO - refactor cell reference parsing logic to one place.
// Current known incarnations: // Current known incarnations:
// FormulaParser.GetName() // FormulaParser.GetName()
// CellReference.parseSheetName() (here) // CellReference.parseSheetName() (here)
// AreaReference.separateAreaRefs() // AreaReference.separateAreaRefs()
// SheetNameFormatter.format() (inverse) // SheetNameFormatter.format() (inverse)
StringBuffer sb = new StringBuffer(indexOfSheetNameDelimiter); StringBuffer sb = new StringBuffer(indexOfSheetNameDelimiter);
for(int i=1; i<lastQuotePos; i++) { // Note boundaries - skip outer quotes for(int i=1; i<lastQuotePos; i++) { // Note boundaries - skip outer quotes
char ch = reference.charAt(i); char ch = reference.charAt(i);
if(ch != SPECIAL_NAME_DELIMITER) { if(ch != SPECIAL_NAME_DELIMITER) {
sb.append(ch); sb.append(ch);
continue; continue;
} }
if(i < lastQuotePos) { if(i < lastQuotePos) {
if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) { if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) {
// two consecutive quotes is the escape sequence for a single one // two consecutive quotes is the escape sequence for a single one
i++; // skip this and keep parsing the special name i++; // skip this and keep parsing the special name
sb.append(ch); sb.append(ch);
continue; continue;
} }
} }
throw new RuntimeException("Bad sheet name quote escaping: (" + reference + ")"); throw new RuntimeException("Bad sheet name quote escaping: (" + reference + ")");
} }
return sb.toString(); return sb.toString();
} }
/** /**
* Takes in a 0-based base-10 column and returns a ALPHA-26 * Takes in a 0-based base-10 column and returns a ALPHA-26
* representation. * representation.
* eg column #3 -> D * eg column #3 -> D
*/ */
protected static String convertNumToColString(int col) { protected static String convertNumToColString(int col) {
// Excel counts column A as the 1st column, we // Excel counts column A as the 1st column, we
// treat it as the 0th one // treat it as the 0th one
int excelColNum = col + 1; int excelColNum = col + 1;
@ -387,36 +407,36 @@ public class CellReference {
} }
return colRef; 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 * Returns the three parts of the cell reference, the
* Sheet name (or null if none supplied), the 1 based * 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. * Appends cell reference with '$' markers for absolute values as required.
* Sheet name is not included. * Sheet name is not included.
*/ */
protected void appendCellReference(StringBuffer sb) { /* package */ void appendCellReference(StringBuffer sb) {
if(_isColAbs) { if(_isColAbs) {
sb.append(ABSOLUTE_REFERENCE_MARKER); sb.append(ABSOLUTE_REFERENCE_MARKER);
} }
sb.append( convertNumToColString(_colIndex)); sb.append( convertNumToColString(_colIndex));
if(_isRowAbs) { if(_isRowAbs) {
sb.append(ABSOLUTE_REFERENCE_MARKER); sb.append(ABSOLUTE_REFERENCE_MARKER);
} }
sb.append(_rowIndex+1); sb.append(_rowIndex+1);
} }
} }

View File

@ -57,10 +57,6 @@ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
return convertToExternalSheetIndex(sheetIndex); return convertToExternalSheetIndex(sheetIndex);
} }
public EvaluationName getName(int index) {
return new Name(_uBook.getNameAt(index), index, this);
}
public EvaluationName getName(String name) { public EvaluationName getName(String name) {
for(int i=0; i < _uBook.getNumberOfNames(); i++) { for(int i=0; i < _uBook.getNumberOfNames(); i++) {
String nameText = _uBook.getNameName(i); String nameText = _uBook.getNameName(i);
@ -88,14 +84,15 @@ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
return _uBook.getSheetAt(sheetIndex); return _uBook.getSheetAt(sheetIndex);
} }
/** public ExternalSheet getExternalSheet(int externSheetIndex) {
* Doesn't do anything - returns the same index // TODO Auto-generated method stub
* TODO - figure out if this is a ole2 specific thing, or return null;
* if we need to do something proper here too! }
*/ public int getExternalSheetIndex(String workbookName, String sheetName) {
public Sheet getSheetByExternSheetIndex(int externSheetIndex) { throw new RuntimeException("not implemented yet");
int sheetIndex = convertFromExternalSheetIndex(externSheetIndex); }
return _uBook.getSheetAt(sheetIndex); public int getSheetIndex(String sheetName) {
return _uBook.getSheetIndex(sheetName);
} }
public Workbook getWorkbook() { 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.record.AllRecordTests;
import org.apache.poi.hssf.usermodel.AllUserModelTests; import org.apache.poi.hssf.usermodel.AllUserModelTests;
import org.apache.poi.hssf.util.AllHSSFUtilTests; 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/> * 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(TestEventRecordFactory.class));
suite.addTest(new TestSuite(TestModelFactory.class)); suite.addTest(new TestSuite(TestModelFactory.class));
suite.addTest(new TestSuite(TestEvaluationCache.class)); suite.addTest(AllSSFormulaTests.suite());
return 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.PercentPtg;
import org.apache.poi.hssf.record.formula.PowerPtg; import org.apache.poi.hssf.record.formula.PowerPtg;
import org.apache.poi.hssf.record.formula.Ptg; 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.RefPtg;
import org.apache.poi.hssf.record.formula.StringPtg; import org.apache.poi.hssf.record.formula.StringPtg;
import org.apache.poi.hssf.record.formula.SubtractPtg; import org.apache.poi.hssf.record.formula.SubtractPtg;
import org.apache.poi.hssf.record.formula.UnaryMinusPtg; import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
import org.apache.poi.hssf.record.formula.UnaryPlusPtg; 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.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFErrorConstants; import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
@ -553,14 +555,14 @@ public final class TestFormulaParser extends TestCase {
Class[] expClss; Class[] expClss;
expClss = new Class[] { expClss = new Class[] {
RefPtg.class, RefPtg.class,
AttrPtg.class, // tAttrIf AttrPtg.class, // tAttrIf
MissingArgPtg.class, MissingArgPtg.class,
AttrPtg.class, // tAttrSkip AttrPtg.class, // tAttrSkip
RefPtg.class, RefPtg.class,
AttrPtg.class, // tAttrSkip AttrPtg.class, // tAttrSkip
FuncVarPtg.class, FuncVarPtg.class,
}; };
confirmTokenClasses("if(A1, ,C1)", expClss); 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 * 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/> * 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 * 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 * 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. * (e.g. COUNTIF) Excel fails to evaluate the formula, giving '#VALUE!' instead.
*/ */
public void testFuncPtgSelection() { public void testFuncPtgSelection() {
@ -777,7 +779,7 @@ public final class TestFormulaParser extends TestCase {
parseFormula("round(3.14;2)"); parseFormula("round(3.14;2)");
throw new AssertionFailedError("Didn't get parse exception as expected"); throw new AssertionFailedError("Didn't get parse exception as expected");
} catch (RuntimeException e) { } catch (RuntimeException e) {
FormulaParserTestHelper.confirmParseException(e, FormulaParserTestHelper.confirmParseException(e,
"Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'"); "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"); parseFormula(" =2+2");
throw new AssertionFailedError("Didn't get parse exception as expected"); throw new AssertionFailedError("Didn't get parse exception as expected");
} catch (RuntimeException e) { } catch (RuntimeException e) {
FormulaParserTestHelper.confirmParseException(e, FormulaParserTestHelper.confirmParseException(e,
"The specified formula ' =2+2' starts with an equals sign which is not allowed."); "The specified formula ' =2+2' starts with an equals sign which is not allowed.");
} }
} }
/** /**
* this function name has a dot in it. * this function name has a dot in it.
*/ */
@ -798,8 +800,8 @@ public final class TestFormulaParser extends TestCase {
Ptg[] ptgs; Ptg[] ptgs;
try { try {
ptgs = parseFormula("error.type(A1)"); ptgs = parseFormula("error.type(A1)");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
if (e.getMessage().equals("Invalid Formula cell reference: 'error'")) { if (e.getMessage().equals("Invalid Formula cell reference: 'error'")) {
throw new AssertionFailedError("Identified bug 45334"); throw new AssertionFailedError("Identified bug 45334");
@ -811,7 +813,7 @@ public final class TestFormulaParser extends TestCase {
FuncPtg funcPtg = (FuncPtg) ptgs[1]; FuncPtg funcPtg = (FuncPtg) ptgs[1];
assertEquals("ERROR.TYPE", funcPtg.getName()); assertEquals("ERROR.TYPE", funcPtg.getName());
} }
public void testNamedRangeThatLooksLikeCell() { public void testNamedRangeThatLooksLikeCell() {
HSSFWorkbook wb = new HSSFWorkbook(); HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("Sheet1"); HSSFSheet sheet = wb.createSheet("Sheet1");
@ -838,35 +840,35 @@ public final class TestFormulaParser extends TestCase {
cell.setCellFormula("count(pf1)"); cell.setCellFormula("count(pf1)");
throw new AssertionFailedError("Expected formula parse execption"); throw new AssertionFailedError("Expected formula parse execption");
} catch (RuntimeException e) { } catch (RuntimeException e) {
FormulaParserTestHelper.confirmParseException(e, FormulaParserTestHelper.confirmParseException(e,
"Specified named range 'pf1' does not exist in the current workbook."); "Specified named range 'pf1' does not exist in the current workbook.");
} }
cell.setCellFormula("count(fp1)"); // plain cell ref, col is in range cell.setCellFormula("count(fp1)"); // plain cell ref, col is in range
} }
public void testParseAreaRefHighRow_bug45358() { public void testParseAreaRefHighRow_bug45358() {
Ptg[] ptgs; Ptg[] ptgs;
AreaI aptg; AreaI aptg;
HSSFWorkbook book = new HSSFWorkbook(); HSSFWorkbook book = new HSSFWorkbook();
book.createSheet("Sheet1"); book.createSheet("Sheet1");
ptgs = HSSFFormulaParser.parse("Sheet1!A10:A40000", book); ptgs = HSSFFormulaParser.parse("Sheet1!A10:A40000", book);
aptg = (AreaI) ptgs[0]; aptg = (AreaI) ptgs[0];
if (aptg.getLastRow() == -25537) { if (aptg.getLastRow() == -25537) {
throw new AssertionFailedError("Identified bug 45358"); throw new AssertionFailedError("Identified bug 45358");
} }
assertEquals(39999, aptg.getLastRow()); assertEquals(39999, aptg.getLastRow());
ptgs = HSSFFormulaParser.parse("Sheet1!A10:A65536", book); ptgs = HSSFFormulaParser.parse("Sheet1!A10:A65536", book);
aptg = (AreaI) ptgs[0]; aptg = (AreaI) ptgs[0];
assertEquals(65535, aptg.getLastRow()); assertEquals(65535, aptg.getLastRow());
// plain area refs should be ok too // plain area refs should be ok too
ptgs = parseFormula("A10:A65536"); ptgs = parseFormula("A10:A65536");
aptg = (AreaI) ptgs[0]; aptg = (AreaI) ptgs[0];
assertEquals(65535, aptg.getLastRow()); assertEquals(65535, aptg.getLastRow());
} }
public void testParseArray() { public void testParseArray() {
Ptg[] ptgs; Ptg[] ptgs;
@ -875,11 +877,71 @@ public final class TestFormulaParser extends TestCase {
Ptg ptg0 = ptgs[0]; Ptg ptg0 = ptgs[0];
assertEquals(ArrayPtg.class, ptg0.getClass()); assertEquals(ArrayPtg.class, ptg0.getClass());
assertEquals("{1.0,2.0,2.0,#REF!;FALSE,3.0,3.0,2.0}", ptg0.toFormulaString()); assertEquals("{1.0,2.0,2.0,#REF!;FALSE,3.0,3.0,2.0}", ptg0.toFormulaString());
ArrayPtg aptg = (ArrayPtg) ptg0; ArrayPtg aptg = (ArrayPtg) ptg0;
Object[][] values = aptg.getTokenArrayValues(); Object[][] values = aptg.getTokenArrayValues();
assertEquals(ErrorConstant.valueOf(HSSFErrorConstants.ERROR_REF), values[0][3]); assertEquals(ErrorConstant.valueOf(HSSFErrorConstants.ERROR_REF), values[0][3]);
assertEquals(Boolean.FALSE, values[1][0]); 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( 34, record.getRecordSize() ); //sid+size+data
assertEquals("testURL", record.getURL().getString()); assertEquals("testURL", record.getURL());
UnicodeString[] sheetNames = record.getSheetNames(); UnicodeString[] sheetNames = record.getSheetNames();
assertEquals(2, sheetNames.length); assertEquals(2, sheetNames.length);
assertEquals("Sheet1", sheetNames[0].getString()); assertEquals("Sheet1", sheetNames[0].getString());

View File

@ -20,16 +20,12 @@ package org.apache.poi.hssf.record.formula;
import junit.framework.TestCase; import junit.framework.TestCase;
/** /**
* Tests for SheetNameFormatter * Tests for {@link SheetNameFormatter}
* *
* @author Josh Micich * @author Josh Micich
*/ */
public final class TestSheetNameFormatter extends TestCase { public final class TestSheetNameFormatter extends TestCase {
public TestSheetNameFormatter(String testName) {
super(testName);
}
private static void confirmFormat(String rawSheetName, String expectedSheetNameEncoding) { private static void confirmFormat(String rawSheetName, String expectedSheetNameEncoding) {
assertEquals(expectedSheetNameEncoding, SheetNameFormatter.format(rawSheetName)); assertEquals(expectedSheetNameEncoding, SheetNameFormatter.format(rawSheetName));
} }
@ -55,6 +51,16 @@ public final class TestSheetNameFormatter extends TestCase {
confirmFormat("TAXRETURN19980415", "TAXRETURN19980415"); 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) { private static void confirmCellNameMatch(String rawSheetName, boolean expected) {
assertEquals(expected, SheetNameFormatter.nameLooksLikePlainCellReference(rawSheetName)); assertEquals(expected, SheetNameFormatter.nameLooksLikePlainCellReference(rawSheetName));
} }

View File

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

View File

@ -146,7 +146,7 @@ public final class TestFormulaBugs extends TestCase {
throw new AssertionFailedError("Identified bug 42448"); 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... // 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());
}
}