Bug 45865 - modified Formula Parser/Evaluator to handle cross-worksheet formulas

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@699761 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-09-28 02:04:31 +00:00
parent 567de4d703
commit c819babd6e
26 changed files with 680 additions and 139 deletions

View File

@ -37,6 +37,7 @@
<!-- Don't forget to update status.xml too! --> <!-- Don't forget to update status.xml too! -->
<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

@ -34,6 +34,7 @@
<!-- Don't forget to update changes.xml too! --> <!-- Don't forget to update changes.xml too! -->
<changes> <changes>
<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) {
int ebIx = _externSheetRecord.getExtbookIndexFromRefIndex(extRefIndex);
SupBookRecord ebr = _externalBookBlocks[ebIx].getExternalBookRecord();
if (!ebr.isExternalReferences()) {
return null;
}
int shIx = _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex);
UnicodeString usSheetName = ebr.getSheetNames()[shIx];
return new String[] {
ebr.getURL(),
usSheetName.getString(),
};
}
public int getIndexToSheet(int extRefIndex) { public int getExternalSheetIndex(String workbookName, String sheetName) {
SupBookRecord ebrTarget = null;
int externalBookIndex = -1;
for (int i=0; i<_externalBookBlocks.length; i++) {
SupBookRecord ebr = _externalBookBlocks[i].getExternalBookRecord();
if (!ebr.isExternalReferences()) {
continue;
}
if (workbookName.equals(ebr.getURL())) { // not sure if 'equals()' works when url has a directory
ebrTarget = ebr;
externalBookIndex = i;
break;
}
}
if (ebrTarget == null) {
throw new RuntimeException("No external workbook with name '" + workbookName + "'");
}
int sheetIndex = getSheetIndex(ebrTarget.getSheetNames(), sheetName);
int result = _externSheetRecord.getRefIxForSheet(externalBookIndex, sheetIndex);
if (result < 0) {
throw new RuntimeException("ExternSheetRecord does not contain combination ("
+ externalBookIndex + ", " + sheetIndex + ")");
}
return result;
}
private static int getSheetIndex(UnicodeString[] sheetNames, String sheetName) {
for (int i = 0; i < sheetNames.length; i++) {
if (sheetNames[i].getString().equals(sheetName)) {
return i;
}
}
throw new RuntimeException("External workbook does not contain sheet '" + sheetName + "'");
}
/**
* @param extRefIndex as from a {@link Ref3DPtg} or {@link Area3DPtg}
* @return -1 if the reference is to an external book
*/
public int getIndexToInternalSheet(int extRefIndex) {
return _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex); 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,9 +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.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.formula.ExternSheetReferenceToken;
import org.apache.poi.ss.formula.WorkbookDependentFormula;
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;
/** /**
@ -32,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
@ -77,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

@ -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.hssf.util.CellReference; import org.apache.poi.hssf.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();

View File

@ -39,6 +39,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 +60,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
public int getSheetIndex(HSSFSheet sheet) { public int getSheetIndex(HSSFSheet 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,7 +81,11 @@ 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;

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;
/** /**
@ -54,6 +55,21 @@ public class HSSFFormulaEvaluator {
_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
* @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell * @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell

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.hssf.usermodel.HSSFSheet;
*/ */
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(HSSFSheet sheet); int getSheetIndex(HSSFSheet sheet);
int getSheetIndex(String sheetName);
HSSFSheet getSheet(int sheetIndex); HSSFSheet 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(HSSFCell cell); Ptg[] getFormulaTokens(HSSFCell 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

@ -257,7 +257,7 @@ public final class FormulaParser {
*/ */
private Identifier parseIdentifier() { private Identifier parseIdentifier() {
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
if (!IsAlpha(look) && look != '\'') { if (!IsAlpha(look) && look != '\'' && look != '[') {
throw expected("Name"); throw expected("Name");
} }
boolean isQuoted = look == '\''; boolean isQuoted = look == '\'';
@ -276,7 +276,7 @@ public final class FormulaParser {
} 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 == ']') {
sb.append(look); sb.append(look);
GetChar(); GetChar();
} }
@ -368,7 +368,7 @@ public final class FormulaParser {
// 3-D ref // 3-D ref
// this code assumes iden is a sheetName // this code assumes iden is a sheetName
// TODO - handle <book name> ! <named range name> // TODO - handle <book name> ! <named range name>
int externIdx = book.getExternalSheetIndex(iden.getName()); int externIdx = getExternalSheetIndex(iden.getName());
String secondIden = parseUnquotedIdentifier(); String secondIden = parseUnquotedIdentifier();
AreaReference areaRef = parseArea(secondIden); AreaReference areaRef = parseArea(secondIden);
if (areaRef == null) { if (areaRef == null) {
@ -418,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
@ -656,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

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

@ -63,6 +63,7 @@ import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.util.CellReference; import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
/** /**
* Evaluates formula cells.<p/> * Evaluates formula cells.<p/>
@ -75,13 +76,14 @@ import org.apache.poi.hssf.util.CellReference;
* *
* @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);
@ -91,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;
} }
/** /**
@ -108,6 +111,21 @@ 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.
@ -130,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);
} }
/** /**
@ -139,13 +157,17 @@ public class WorkbookEvaluator {
*/ */
public void notifySetFormula(HSSFSheet sheet, int rowIndex, int columnIndex) { public void notifySetFormula(HSSFSheet 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(HSSFSheet sheet) { private int getSheetIndex(HSSFSheet 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();
@ -153,7 +175,7 @@ public class WorkbookEvaluator {
public ValueEval evaluate(HSSFCell srcCell) { public ValueEval evaluate(HSSFCell 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));
} }
@ -342,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
@ -350,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;
@ -388,14 +426,12 @@ public class WorkbookEvaluator {
} }
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);
@ -435,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

@ -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;
@ -913,4 +915,33 @@ public final class TestFormulaParser extends TestCase {
assertEquals("'true'!B2", cell.getCellFormula()); 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

@ -19,6 +19,7 @@ package org.apache.poi.ss.formula;
import junit.framework.TestCase; 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.AreaErrPtg;
import org.apache.poi.hssf.record.formula.AttrPtg; import org.apache.poi.hssf.record.formula.AttrPtg;
import org.apache.poi.hssf.record.formula.DeletedArea3DPtg; import org.apache.poi.hssf.record.formula.DeletedArea3DPtg;
@ -29,6 +30,10 @@ 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.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.ValueEval; 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}. * Tests {@link WorkbookEvaluator}.
@ -89,4 +94,61 @@ public class TestWorkbookEvaluator extends TestCase {
} }
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());
}
} }