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:
parent
6a851a43b2
commit
19aa351cdc
@ -37,6 +37,7 @@
|
||||
|
||||
<!-- Don't forget to update status.xml too! -->
|
||||
<release version="3.2-alpha1" date="2008-??-??">
|
||||
<action dev="POI-DEVELOPERS" type="add">45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">Optimised the FormulaEvaluator to take cell dependencies into account</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">16936 - Initial support for whole-row cell styling</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">Update hssf.extractor.ExcelExtractor to optionally output blank cells too</action>
|
||||
|
@ -34,6 +34,7 @@
|
||||
<!-- Don't forget to update changes.xml too! -->
|
||||
<changes>
|
||||
<release version="3.2-alpha1" date="2008-??-??">
|
||||
<action dev="POI-DEVELOPERS" type="add">45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">Optimised the FormulaEvaluator to take cell dependencies into account</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">16936 - Initial support for whole-row cell styling</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">Update hssf.extractor.ExcelExtractor to optionally output blank cells too</action>
|
||||
|
@ -29,6 +29,7 @@ import org.apache.poi.hssf.record.ExternalNameRecord;
|
||||
import org.apache.poi.hssf.record.NameRecord;
|
||||
import org.apache.poi.hssf.record.Record;
|
||||
import org.apache.poi.hssf.record.SupBookRecord;
|
||||
import org.apache.poi.hssf.record.UnicodeString;
|
||||
import org.apache.poi.hssf.record.formula.NameXPtg;
|
||||
|
||||
/**
|
||||
@ -109,8 +110,8 @@ final class LinkTable {
|
||||
temp.toArray(_crnBlocks);
|
||||
}
|
||||
|
||||
public ExternalBookBlock(short numberOfSheets) {
|
||||
_externalBookRecord = SupBookRecord.createInternalReferences(numberOfSheets);
|
||||
public ExternalBookBlock(int numberOfSheets) {
|
||||
_externalBookRecord = SupBookRecord.createInternalReferences((short)numberOfSheets);
|
||||
_externalNameRecords = new ExternalNameRecord[0];
|
||||
_crnBlocks = new CRNBlock[0];
|
||||
}
|
||||
@ -197,7 +198,7 @@ final class LinkTable {
|
||||
return ExternSheetRecord.combine(esrs);
|
||||
}
|
||||
|
||||
public LinkTable(short numberOfSheets, WorkbookRecordList workbookRecordList) {
|
||||
public LinkTable(int numberOfSheets, WorkbookRecordList workbookRecordList) {
|
||||
_workbookRecordList = workbookRecordList;
|
||||
_definedNames = new ArrayList();
|
||||
_externalBookBlocks = new ExternalBookBlock[] {
|
||||
@ -303,8 +304,62 @@ final class LinkTable {
|
||||
return lastName.getSheetNumber() == firstName.getSheetNumber();
|
||||
}
|
||||
|
||||
|
||||
public int getIndexToSheet(int extRefIndex) {
|
||||
public String[] getExternalBookAndSheetName(int extRefIndex) {
|
||||
int ebIx = _externSheetRecord.getExtbookIndexFromRefIndex(extRefIndex);
|
||||
SupBookRecord ebr = _externalBookBlocks[ebIx].getExternalBookRecord();
|
||||
if (!ebr.isExternalReferences()) {
|
||||
return null;
|
||||
}
|
||||
int shIx = _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex);
|
||||
UnicodeString usSheetName = ebr.getSheetNames()[shIx];
|
||||
return new String[] {
|
||||
ebr.getURL(),
|
||||
usSheetName.getString(),
|
||||
};
|
||||
}
|
||||
|
||||
public int getExternalSheetIndex(String workbookName, String sheetName) {
|
||||
SupBookRecord ebrTarget = null;
|
||||
int externalBookIndex = -1;
|
||||
for (int i=0; i<_externalBookBlocks.length; i++) {
|
||||
SupBookRecord ebr = _externalBookBlocks[i].getExternalBookRecord();
|
||||
if (!ebr.isExternalReferences()) {
|
||||
continue;
|
||||
}
|
||||
if (workbookName.equals(ebr.getURL())) { // not sure if 'equals()' works when url has a directory
|
||||
ebrTarget = ebr;
|
||||
externalBookIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ebrTarget == null) {
|
||||
throw new RuntimeException("No external workbook with name '" + workbookName + "'");
|
||||
}
|
||||
int sheetIndex = getSheetIndex(ebrTarget.getSheetNames(), sheetName);
|
||||
|
||||
int result = _externSheetRecord.getRefIxForSheet(externalBookIndex, sheetIndex);
|
||||
if (result < 0) {
|
||||
throw new RuntimeException("ExternSheetRecord does not contain combination ("
|
||||
+ externalBookIndex + ", " + sheetIndex + ")");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int getSheetIndex(UnicodeString[] sheetNames, String sheetName) {
|
||||
for (int i = 0; i < sheetNames.length; i++) {
|
||||
if (sheetNames[i].getString().equals(sheetName)) {
|
||||
return i;
|
||||
}
|
||||
|
||||
}
|
||||
throw new RuntimeException("External workbook does not contain sheet '" + sheetName + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param extRefIndex as from a {@link Ref3DPtg} or {@link Area3DPtg}
|
||||
* @return -1 if the reference is to an external book
|
||||
*/
|
||||
public int getIndexToInternalSheet(int extRefIndex) {
|
||||
return _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex);
|
||||
}
|
||||
|
||||
@ -315,20 +370,26 @@ final class LinkTable {
|
||||
return _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex);
|
||||
}
|
||||
|
||||
public int addSheetIndexToExternSheet(int sheetNumber) {
|
||||
// TODO - what about the first parameter (extBookIndex)?
|
||||
return _externSheetRecord.addRef(0, sheetNumber, sheetNumber);
|
||||
}
|
||||
|
||||
public short checkExternSheet(int sheetIndex) {
|
||||
public int checkExternSheet(int sheetIndex) {
|
||||
int thisWbIndex = -1; // this is probably always zero
|
||||
for (int i=0; i<_externalBookBlocks.length; i++) {
|
||||
SupBookRecord ebr = _externalBookBlocks[i].getExternalBookRecord();
|
||||
if (ebr.isInternalReferences()) {
|
||||
thisWbIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (thisWbIndex < 0) {
|
||||
throw new RuntimeException("Could not find 'internal references' EXTERNALBOOK");
|
||||
}
|
||||
|
||||
//Trying to find reference to this sheet
|
||||
int i = _externSheetRecord.getRefIxForSheet(sheetIndex);
|
||||
int i = _externSheetRecord.getRefIxForSheet(thisWbIndex, sheetIndex);
|
||||
if (i>=0) {
|
||||
return (short)i;
|
||||
return i;
|
||||
}
|
||||
//We Haven't found reference to this sheet
|
||||
return (short)addSheetIndexToExternSheet((short) sheetIndex);
|
||||
//We haven't found reference to this sheet
|
||||
return _externSheetRecord.addRef(thisWbIndex, sheetIndex, sheetIndex);
|
||||
}
|
||||
|
||||
|
||||
|
@ -26,6 +26,7 @@ import org.apache.poi.ddf.*;
|
||||
import org.apache.poi.hssf.record.*;
|
||||
import org.apache.poi.hssf.record.formula.NameXPtg;
|
||||
import org.apache.poi.hssf.util.HSSFColor;
|
||||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
|
||||
import org.apache.poi.util.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
|
||||
@ -328,9 +329,9 @@ public final class Workbook implements Model {
|
||||
for ( int k = 0; k < nBoundSheets; k++ ) {
|
||||
BoundSheetRecord bsr = retval.createBoundSheet(k);
|
||||
|
||||
records.add(bsr);
|
||||
retval.boundsheets.add(bsr);
|
||||
retval.records.setBspos(records.size() - 1);
|
||||
records.add(bsr);
|
||||
retval.boundsheets.add(bsr);
|
||||
retval.records.setBspos(records.size() - 1);
|
||||
}
|
||||
// retval.records.supbookpos = retval.records.bspos + 1;
|
||||
// retval.records.namepos = retval.records.supbookpos + 2;
|
||||
@ -586,19 +587,19 @@ public final class Workbook implements Model {
|
||||
* @param hidden 0 for not hidden, 1 for hidden, 2 for very hidden
|
||||
*/
|
||||
public void setSheetHidden(int sheetnum, int hidden) {
|
||||
BoundSheetRecord bsr = getBoundSheetRec(sheetnum);
|
||||
boolean h = false;
|
||||
boolean vh = false;
|
||||
if(hidden == 0) {
|
||||
} else if(hidden == 1) {
|
||||
h = true;
|
||||
} else if(hidden == 2) {
|
||||
vh = true;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid hidden flag " + hidden + " given, must be 0, 1 or 2");
|
||||
}
|
||||
bsr.setHidden(h);
|
||||
bsr.setVeryHidden(vh);
|
||||
BoundSheetRecord bsr = getBoundSheetRec(sheetnum);
|
||||
boolean h = false;
|
||||
boolean vh = false;
|
||||
if(hidden == 0) {
|
||||
} else if(hidden == 1) {
|
||||
h = true;
|
||||
} else if(hidden == 2) {
|
||||
vh = true;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid hidden flag " + hidden + " given, must be 0, 1 or 2");
|
||||
}
|
||||
bsr.setHidden(h);
|
||||
bsr.setVeryHidden(vh);
|
||||
}
|
||||
|
||||
|
||||
@ -761,23 +762,23 @@ public final class Workbook implements Model {
|
||||
* have a Style set.
|
||||
*/
|
||||
public StyleRecord getStyleRecord(int xfIndex) {
|
||||
// Style records always follow after
|
||||
// the ExtendedFormat records
|
||||
boolean done = false;
|
||||
for(int i=records.getXfpos(); i<records.size() &&
|
||||
!done; i++) {
|
||||
Record r = records.get(i);
|
||||
if(r instanceof ExtendedFormatRecord) {
|
||||
} else if(r instanceof StyleRecord) {
|
||||
StyleRecord sr = (StyleRecord)r;
|
||||
if(sr.getIndex() == xfIndex) {
|
||||
return sr;
|
||||
}
|
||||
} else {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
// Style records always follow after
|
||||
// the ExtendedFormat records
|
||||
boolean done = false;
|
||||
for(int i=records.getXfpos(); i<records.size() &&
|
||||
!done; i++) {
|
||||
Record r = records.get(i);
|
||||
if(r instanceof ExtendedFormatRecord) {
|
||||
} else if(r instanceof StyleRecord) {
|
||||
StyleRecord sr = (StyleRecord)r;
|
||||
if(sr.getIndex() == xfIndex) {
|
||||
return sr;
|
||||
}
|
||||
} else {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Creates a new StyleRecord, for the given Extended
|
||||
@ -785,29 +786,29 @@ public final class Workbook implements Model {
|
||||
* records collection
|
||||
*/
|
||||
public StyleRecord createStyleRecord(int xfIndex) {
|
||||
// Style records always follow after
|
||||
// the ExtendedFormat records
|
||||
StyleRecord newSR = new StyleRecord();
|
||||
newSR.setIndex((short)xfIndex);
|
||||
|
||||
// Find the spot
|
||||
int addAt = -1;
|
||||
for(int i=records.getXfpos(); i<records.size() &&
|
||||
addAt == -1; i++) {
|
||||
Record r = records.get(i);
|
||||
if(r instanceof ExtendedFormatRecord ||
|
||||
r instanceof StyleRecord) {
|
||||
// Keep going
|
||||
} else {
|
||||
addAt = i;
|
||||
}
|
||||
}
|
||||
if(addAt == -1) {
|
||||
throw new IllegalStateException("No XF Records found!");
|
||||
}
|
||||
records.add(addAt, newSR);
|
||||
|
||||
return newSR;
|
||||
// Style records always follow after
|
||||
// the ExtendedFormat records
|
||||
StyleRecord newSR = new StyleRecord();
|
||||
newSR.setIndex((short)xfIndex);
|
||||
|
||||
// Find the spot
|
||||
int addAt = -1;
|
||||
for(int i=records.getXfpos(); i<records.size() &&
|
||||
addAt == -1; i++) {
|
||||
Record r = records.get(i);
|
||||
if(r instanceof ExtendedFormatRecord ||
|
||||
r instanceof StyleRecord) {
|
||||
// Keep going
|
||||
} else {
|
||||
addAt = i;
|
||||
}
|
||||
}
|
||||
if(addAt == -1) {
|
||||
throw new IllegalStateException("No XF Records found!");
|
||||
}
|
||||
records.add(addAt, newSR);
|
||||
|
||||
return newSR;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1914,8 +1915,7 @@ public final class Workbook implements Model {
|
||||
*/
|
||||
public String findSheetNameFromExternSheet(int externSheetIndex){
|
||||
|
||||
int indexToSheet = linkTable.getIndexToSheet(externSheetIndex);
|
||||
|
||||
int indexToSheet = linkTable.getIndexToInternalSheet(externSheetIndex);
|
||||
if (indexToSheet < 0) {
|
||||
// TODO - what does '-1' mean here?
|
||||
//error check, bail out gracefully!
|
||||
@ -1927,6 +1927,13 @@ public final class Workbook implements Model {
|
||||
}
|
||||
return getSheetName(indexToSheet);
|
||||
}
|
||||
public ExternalSheet getExternalSheet(int externSheetIndex) {
|
||||
String[] extNames = linkTable.getExternalBookAndSheetName(externSheetIndex);
|
||||
if (extNames == null) {
|
||||
return null;
|
||||
}
|
||||
return new ExternalSheet(extNames[0], extNames[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the sheet index for a particular external sheet number.
|
||||
@ -1944,9 +1951,14 @@ public final class Workbook implements Model {
|
||||
* @return index to extern sheet
|
||||
*/
|
||||
public short checkExternSheet(int sheetNumber){
|
||||
return getOrCreateLinkTable().checkExternSheet(sheetNumber);
|
||||
return (short)getOrCreateLinkTable().checkExternSheet(sheetNumber);
|
||||
}
|
||||
|
||||
public int getExternalSheetIndex(String workbookName, String sheetName) {
|
||||
return getOrCreateLinkTable().getExternalSheetIndex(workbookName, sheetName);
|
||||
}
|
||||
|
||||
|
||||
/** gets the total number of names
|
||||
* @return number of names
|
||||
*/
|
||||
|
@ -250,10 +250,13 @@ public class ExternSheetRecord extends Record {
|
||||
return _list.size() - 1;
|
||||
}
|
||||
|
||||
public int getRefIxForSheet(int sheetIndex) {
|
||||
public int getRefIxForSheet(int externalBookIndex, int sheetIndex) {
|
||||
int nItems = _list.size();
|
||||
for (int i = 0; i < nItems; i++) {
|
||||
RefSubRecord ref = getRef(i);
|
||||
if (ref.getExtBookIndex() != externalBookIndex) {
|
||||
continue;
|
||||
}
|
||||
if (ref.getFirstSheetIndex() == sheetIndex && ref.getLastSheetIndex() == sheetIndex) {
|
||||
return i;
|
||||
}
|
||||
|
@ -221,8 +221,33 @@ public final class SupBookRecord extends Record {
|
||||
{
|
||||
return sid;
|
||||
}
|
||||
public UnicodeString getURL() {
|
||||
return field_2_encoded_url;
|
||||
public String getURL() {
|
||||
String encodedUrl = field_2_encoded_url.getString();
|
||||
switch(encodedUrl.charAt(0)) {
|
||||
case 0: // Reference to an empty workbook name
|
||||
return encodedUrl.substring(1); // will this just be empty string?
|
||||
case 1: // encoded file name
|
||||
return decodeFileName(encodedUrl);
|
||||
case 2: // Self-referential external reference
|
||||
return encodedUrl.substring(1);
|
||||
|
||||
}
|
||||
return encodedUrl;
|
||||
}
|
||||
private static String decodeFileName(String encodedUrl) {
|
||||
return encodedUrl.substring(1);
|
||||
// TODO the following special characters may appear in the rest of the string, and need to get interpreted
|
||||
/* see "MICROSOFT OFFICE EXCEL 97-2007 BINARY FILE FORMAT SPECIFICATION"
|
||||
chVolume 1
|
||||
chSameVolume 2
|
||||
chDownDir 3
|
||||
chUpDir 4
|
||||
chLongVolume 5
|
||||
chStartupDir 6
|
||||
chAltStartupDir 7
|
||||
chLibDir 8
|
||||
|
||||
*/
|
||||
}
|
||||
public UnicodeString[] getSheetNames() {
|
||||
return (UnicodeString[]) field_3_sheet_names.clone();
|
||||
|
@ -18,9 +18,9 @@
|
||||
package org.apache.poi.hssf.record.formula;
|
||||
|
||||
import org.apache.poi.hssf.record.RecordInputStream;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.apache.poi.ss.formula.WorkbookDependentFormula;
|
||||
import org.apache.poi.ss.formula.ExternSheetReferenceToken;
|
||||
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
|
||||
import org.apache.poi.ss.formula.WorkbookDependentFormula;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
|
||||
/**
|
||||
@ -32,7 +32,7 @@ import org.apache.poi.util.LittleEndian;
|
||||
* @author Jason Height (jheight at chariot dot net dot au)
|
||||
* @version 1.0-pre
|
||||
*/
|
||||
public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFormula {
|
||||
public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFormula, ExternSheetReferenceToken {
|
||||
public final static byte sid = 0x3b;
|
||||
private final static int SIZE = 11; // 10 + 1 for Ptg
|
||||
|
||||
@ -77,8 +77,8 @@ public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFor
|
||||
return SIZE;
|
||||
}
|
||||
|
||||
public short getExternSheetIndex() {
|
||||
return (short)field_1_index_extern_sheet;
|
||||
public int getExternSheetIndex() {
|
||||
return field_1_index_extern_sheet;
|
||||
}
|
||||
|
||||
public void setExternSheetIndex(int index) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
package org.apache.poi.hssf.record.formula;
|
||||
|
||||
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
|
||||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
|
||||
|
||||
/**
|
||||
* @author Josh Micich
|
||||
@ -29,13 +30,22 @@ final class ExternSheetNameResolver {
|
||||
}
|
||||
|
||||
public static String prependSheetName(FormulaRenderingWorkbook book, int field_1_index_extern_sheet, String cellRefText) {
|
||||
String sheetName = book.getSheetNameByExternSheet(field_1_index_extern_sheet);
|
||||
StringBuffer sb = new StringBuffer(sheetName.length() + cellRefText.length() + 4);
|
||||
if (sheetName.length() < 1) {
|
||||
// What excel does if sheet has been deleted
|
||||
sb.append("#REF"); // note - '!' added just once below
|
||||
ExternalSheet externalSheet = book.getExternalSheet(field_1_index_extern_sheet);
|
||||
StringBuffer sb;
|
||||
if (externalSheet != null) {
|
||||
String wbName = externalSheet.getWorkbookName();
|
||||
String sheetName = externalSheet.getSheetName();
|
||||
sb = new StringBuffer(wbName.length() + sheetName.length() + cellRefText.length() + 4);
|
||||
SheetNameFormatter.appendFormat(sb, wbName, sheetName);
|
||||
} else {
|
||||
SheetNameFormatter.appendFormat(sb, sheetName);
|
||||
String sheetName = book.getSheetNameByExternSheet(field_1_index_extern_sheet);
|
||||
sb = new StringBuffer(sheetName.length() + cellRefText.length() + 4);
|
||||
if (sheetName.length() < 1) {
|
||||
// What excel does if sheet has been deleted
|
||||
sb.append("#REF"); // note - '!' added just once below
|
||||
} else {
|
||||
SheetNameFormatter.appendFormat(sb, sheetName);
|
||||
}
|
||||
}
|
||||
sb.append('!');
|
||||
sb.append(cellRefText);
|
||||
|
@ -19,8 +19,9 @@ package org.apache.poi.hssf.record.formula;
|
||||
|
||||
import org.apache.poi.hssf.record.RecordInputStream;
|
||||
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.WorkbookDependentFormula;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
|
||||
/**
|
||||
@ -31,7 +32,7 @@ import org.apache.poi.util.LittleEndian;
|
||||
* @author Jason Height (jheight at chariot dot net dot au)
|
||||
* @version 1.0-pre
|
||||
*/
|
||||
public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormula {
|
||||
public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormula, ExternSheetReferenceToken {
|
||||
public final static byte sid = 0x3a;
|
||||
|
||||
private final static int SIZE = 7; // 6 + 1 for Ptg
|
||||
@ -75,11 +76,11 @@ public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormu
|
||||
return SIZE;
|
||||
}
|
||||
|
||||
public int getExternSheetIndex(){
|
||||
public int getExternSheetIndex() {
|
||||
return field_1_index_extern_sheet;
|
||||
}
|
||||
|
||||
public void setExternSheetIndex(int index){
|
||||
public void setExternSheetIndex(int index) {
|
||||
field_1_index_extern_sheet = index;
|
||||
}
|
||||
|
||||
|
@ -66,6 +66,22 @@ public final class SheetNameFormatter {
|
||||
out.append(rawSheetName);
|
||||
}
|
||||
}
|
||||
public static void appendFormat(StringBuffer out, String workbookName, String rawSheetName) {
|
||||
boolean needsQuotes = needsDelimiting(workbookName) || needsDelimiting(rawSheetName);
|
||||
if(needsQuotes) {
|
||||
out.append(DELIMITER);
|
||||
out.append('[');
|
||||
appendAndEscape(out, workbookName.replace('[', '(').replace(']', ')'));
|
||||
out.append(']');
|
||||
appendAndEscape(out, rawSheetName);
|
||||
out.append(DELIMITER);
|
||||
} else {
|
||||
out.append('[');
|
||||
out.append(workbookName);
|
||||
out.append(']');
|
||||
out.append(rawSheetName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendAndEscape(StringBuffer sb, String rawSheetName) {
|
||||
int len = rawSheetName.length();
|
||||
|
@ -39,6 +39,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
|
||||
int sheetIndex = _uBook.getSheetIndex(sheetName);
|
||||
return _iBook.checkExternSheet(sheetIndex);
|
||||
}
|
||||
public int getExternalSheetIndex(String workbookName, String sheetName) {
|
||||
return _iBook.getExternalSheetIndex(workbookName, sheetName);
|
||||
}
|
||||
|
||||
public EvaluationName getName(int index) {
|
||||
return new Name(_iBook.getNameRecord(index), index);
|
||||
@ -57,6 +60,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
|
||||
public int getSheetIndex(HSSFSheet sheet) {
|
||||
return _uBook.getSheetIndex(sheet);
|
||||
}
|
||||
public int getSheetIndex(String sheetName) {
|
||||
return _uBook.getSheetIndex(sheetName);
|
||||
}
|
||||
|
||||
public String getSheetName(int sheetIndex) {
|
||||
return _uBook.getSheetName(sheetIndex);
|
||||
@ -75,8 +81,12 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
|
||||
}
|
||||
public int convertFromExternSheetIndex(int externSheetIndex) {
|
||||
return _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public ExternalSheet getExternalSheet(int externSheetIndex) {
|
||||
return _iBook.getExternalSheet(externSheetIndex);
|
||||
}
|
||||
|
||||
public HSSFWorkbook getWorkbook() {
|
||||
return _uBook;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import org.apache.poi.hssf.record.formula.eval.ErrorEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.NumberEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment;
|
||||
import org.apache.poi.ss.formula.WorkbookEvaluator;
|
||||
|
||||
/**
|
||||
@ -53,6 +54,21 @@ public class HSSFFormulaEvaluator {
|
||||
public HSSFFormulaEvaluator(HSSFWorkbook workbook) {
|
||||
_bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook));
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinates several formula evaluators together so that formulas that involve external
|
||||
* references can be evaluated.
|
||||
* @param workbookNames the simple file names used to identify the workbooks in formulas
|
||||
* with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1")
|
||||
* @param evaluators all evaluators for the full set of workbooks required by the formulas.
|
||||
*/
|
||||
public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) {
|
||||
WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length];
|
||||
for (int i = 0; i < wbEvals.length; i++) {
|
||||
wbEvals[i] = evaluators[i]._bookEvaluator;
|
||||
}
|
||||
CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing
|
||||
|
@ -17,25 +17,32 @@
|
||||
|
||||
package org.apache.poi.ss.formula;
|
||||
|
||||
import org.apache.poi.hssf.util.CellReference;
|
||||
|
||||
/**
|
||||
* Stores the parameters that identify the evaluation of one cell.<br/>
|
||||
*/
|
||||
final class CellLocation {
|
||||
public static final CellLocation[] EMPTY_ARRAY = { };
|
||||
|
||||
private final EvaluationWorkbook _book;
|
||||
private final int _sheetIndex;
|
||||
private final int _rowIndex;
|
||||
private final int _columnIndex;
|
||||
private final int _hashCode;
|
||||
|
||||
public CellLocation(int sheetIndex, int rowIndex, int columnIndex) {
|
||||
public CellLocation(EvaluationWorkbook book, int sheetIndex, int rowIndex, int columnIndex) {
|
||||
if (sheetIndex < 0) {
|
||||
throw new IllegalArgumentException("sheetIndex must not be negative");
|
||||
}
|
||||
_book = book;
|
||||
_sheetIndex = sheetIndex;
|
||||
_rowIndex = rowIndex;
|
||||
_columnIndex = columnIndex;
|
||||
_hashCode = sheetIndex + 17 * (rowIndex + 17 * columnIndex);
|
||||
_hashCode = System.identityHashCode(book) + sheetIndex + 17 * (rowIndex + 17 * columnIndex);
|
||||
}
|
||||
public Object getBook() {
|
||||
return _book;
|
||||
}
|
||||
public int getSheetIndex() {
|
||||
return _sheetIndex;
|
||||
@ -49,15 +56,18 @@ final class CellLocation {
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
CellLocation other = (CellLocation) obj;
|
||||
if (getSheetIndex() != other.getSheetIndex()) {
|
||||
return false;
|
||||
}
|
||||
if (getRowIndex() != other.getRowIndex()) {
|
||||
return false;
|
||||
}
|
||||
if (getColumnIndex() != other.getColumnIndex()) {
|
||||
return false;
|
||||
}
|
||||
if (getSheetIndex() != other.getSheetIndex()) {
|
||||
return false;
|
||||
}
|
||||
if (getBook() != other.getBook()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public int hashCode() {
|
||||
@ -68,7 +78,8 @@ final class CellLocation {
|
||||
* @return human readable string for debug purposes
|
||||
*/
|
||||
public String formatAsString() {
|
||||
return "ShIx=" + getSheetIndex() + " R=" + getRowIndex() + " C=" + getColumnIndex();
|
||||
CellReference cr = new CellReference(_rowIndex, _columnIndex, false, false);
|
||||
return "ShIx=" + getSheetIndex() + " " + cr.formatAsString();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -81,13 +81,7 @@ final class EvaluationCache {
|
||||
+ cellLoc.formatAsString());
|
||||
}
|
||||
}
|
||||
if (_evaluationListener == null) {
|
||||
// optimisation - don't bother sorting if there is no listener.
|
||||
} else {
|
||||
// for testing
|
||||
// make order of callbacks to listener more deterministic
|
||||
Arrays.sort(usedCells, CellLocationComparator);
|
||||
}
|
||||
sortCellLocationsForLogging(usedCells);
|
||||
CellCacheEntry entry = getEntry(cellLoc);
|
||||
CellLocation[] consumingFormulaCells = entry.getConsumingCells();
|
||||
CellLocation[] prevUsedCells = entry.getUsedCells();
|
||||
@ -110,6 +104,18 @@ final class EvaluationCache {
|
||||
recurseClearCachedFormulaResults(consumingFormulaCells, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sorts the supplied cellLocs so that the order of call-backs to the evaluation
|
||||
* listener is more deterministic
|
||||
*/
|
||||
private void sortCellLocationsForLogging(CellLocation[] cellLocs) {
|
||||
if (_evaluationListener == null) {
|
||||
// optimisation - don't bother sorting if there is no listener.
|
||||
} else {
|
||||
Arrays.sort(cellLocs, CellLocationComparator);
|
||||
}
|
||||
}
|
||||
|
||||
private void unlinkConsumingCells(CellLocation[] prevUsedCells, CellLocation[] usedCells,
|
||||
CellLocation cellLoc) {
|
||||
if (prevUsedCells == null) {
|
||||
@ -149,6 +155,7 @@ final class EvaluationCache {
|
||||
* @param formulaCells
|
||||
*/
|
||||
private void recurseClearCachedFormulaResults(CellLocation[] formulaCells, int depth) {
|
||||
sortCellLocationsForLogging(formulaCells);
|
||||
int nextDepth = depth+1;
|
||||
for (int i = 0; i < formulaCells.length; i++) {
|
||||
CellLocation fc = formulaCells[i];
|
||||
@ -196,6 +203,10 @@ final class EvaluationCache {
|
||||
CellLocation clB = (CellLocation) b;
|
||||
|
||||
int cmp;
|
||||
cmp = System.identityHashCode(clA.getBook()) - System.identityHashCode(clB.getBook());
|
||||
if (cmp != 0) {
|
||||
return cmp;
|
||||
}
|
||||
cmp = clA.getSheetIndex() - clB.getSheetIndex();
|
||||
if (cmp != 0) {
|
||||
return cmp;
|
||||
|
@ -31,12 +31,36 @@ import org.apache.poi.hssf.usermodel.HSSFSheet;
|
||||
*/
|
||||
public interface EvaluationWorkbook {
|
||||
String getSheetName(int sheetIndex);
|
||||
/**
|
||||
* @return -1 if the specified sheet is from a different book
|
||||
*/
|
||||
int getSheetIndex(HSSFSheet sheet);
|
||||
int getSheetIndex(String sheetName);
|
||||
|
||||
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);
|
||||
EvaluationName getName(NamePtg namePtg);
|
||||
String resolveNameXText(NameXPtg ptg);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -257,7 +257,7 @@ public final class FormulaParser {
|
||||
*/
|
||||
private Identifier parseIdentifier() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (!IsAlpha(look) && look != '\'') {
|
||||
if (!IsAlpha(look) && look != '\'' && look != '[') {
|
||||
throw expected("Name");
|
||||
}
|
||||
boolean isQuoted = look == '\'';
|
||||
@ -276,7 +276,7 @@ public final class FormulaParser {
|
||||
} else {
|
||||
// allow for any sequence of dots and identifier chars
|
||||
// special case of two consecutive dots is best treated in the calling code
|
||||
while (IsAlNum(look) || look == '.') {
|
||||
while (IsAlNum(look) || look == '.' || look == '[' || look == ']') {
|
||||
sb.append(look);
|
||||
GetChar();
|
||||
}
|
||||
@ -368,7 +368,7 @@ public final class FormulaParser {
|
||||
// 3-D ref
|
||||
// this code assumes iden is a sheetName
|
||||
// TODO - handle <book name> ! <named range name>
|
||||
int externIdx = book.getExternalSheetIndex(iden.getName());
|
||||
int externIdx = getExternalSheetIndex(iden.getName());
|
||||
String secondIden = parseUnquotedIdentifier();
|
||||
AreaReference areaRef = parseArea(secondIden);
|
||||
if (areaRef == null) {
|
||||
@ -418,6 +418,17 @@ public final class FormulaParser {
|
||||
+ name + "' is not a range as expected");
|
||||
}
|
||||
|
||||
private int getExternalSheetIndex(String name) {
|
||||
if (name.charAt(0) == '[') {
|
||||
// we have a sheet name qualified with workbook name e.g. '[MyData.xls]Sheet1'
|
||||
int pos = name.lastIndexOf(']'); // safe because sheet names never have ']'
|
||||
String wbName = name.substring(1, pos);
|
||||
String sheetName = name.substring(pos+1);
|
||||
return book.getExternalSheetIndex(wbName, sheetName);
|
||||
}
|
||||
return book.getExternalSheetIndex(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name an 'identifier' like string (i.e. contains alphanums, and dots)
|
||||
* @return <code>null</code> if name cannot be split at a dot
|
||||
@ -656,7 +667,7 @@ public final class FormulaParser {
|
||||
Match('}');
|
||||
return arrayNode;
|
||||
}
|
||||
if (IsAlpha(look) || look == '\''){
|
||||
if (IsAlpha(look) || look == '\'' || look == '['){
|
||||
return parseFunctionReferenceOrName();
|
||||
}
|
||||
// else - assume number
|
||||
|
@ -32,6 +32,16 @@ public interface FormulaParsingWorkbook {
|
||||
*/
|
||||
EvaluationName getName(String name);
|
||||
|
||||
int getExternalSheetIndex(String sheetName);
|
||||
NameXPtg getNameXPtg(String name);
|
||||
|
||||
/**
|
||||
* gets the externSheet index for a sheet from this workbook
|
||||
*/
|
||||
int getExternalSheetIndex(String sheetName);
|
||||
/**
|
||||
* gets the externSheet index for a sheet from an external workbook
|
||||
* @param workbookName e.g. "Budget.xls"
|
||||
* @param sheetName a name of a sheet in that workbook
|
||||
*/
|
||||
int getExternalSheetIndex(String workbookName, String sheetName);
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package org.apache.poi.ss.formula;
|
||||
|
||||
import org.apache.poi.hssf.record.formula.NamePtg;
|
||||
import org.apache.poi.hssf.record.formula.NameXPtg;
|
||||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
|
||||
|
||||
/**
|
||||
* Abstracts a workbook for the purpose of converting formula to text.<br/>
|
||||
@ -29,6 +30,10 @@ import org.apache.poi.hssf.record.formula.NameXPtg;
|
||||
*/
|
||||
public interface FormulaRenderingWorkbook {
|
||||
|
||||
/**
|
||||
* @return <code>null</code> if externSheetIndex refers to a sheet inside the current workbook
|
||||
*/
|
||||
ExternalSheet getExternalSheet(int externSheetIndex);
|
||||
String getSheetNameByExternSheet(int externSheetIndex);
|
||||
String resolveNameXText(NameXPtg nameXPtg);
|
||||
String getNameText(NamePtg namePtg);
|
||||
|
@ -63,6 +63,7 @@ import org.apache.poi.hssf.usermodel.HSSFCell;
|
||||
import org.apache.poi.hssf.usermodel.HSSFRow;
|
||||
import org.apache.poi.hssf.usermodel.HSSFSheet;
|
||||
import org.apache.poi.hssf.util.CellReference;
|
||||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
|
||||
|
||||
/**
|
||||
* Evaluates formula cells.<p/>
|
||||
@ -75,13 +76,14 @@ import org.apache.poi.hssf.util.CellReference;
|
||||
*
|
||||
* @author Josh Micich
|
||||
*/
|
||||
public class WorkbookEvaluator {
|
||||
public final class WorkbookEvaluator {
|
||||
|
||||
private final EvaluationWorkbook _workbook;
|
||||
private final EvaluationCache _cache;
|
||||
private EvaluationCache _cache;
|
||||
|
||||
private final IEvaluationListener _evaluationListener;
|
||||
private final Map _sheetIndexesBySheet;
|
||||
private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
|
||||
|
||||
public WorkbookEvaluator(EvaluationWorkbook workbook) {
|
||||
this (workbook, null);
|
||||
@ -91,6 +93,7 @@ public class WorkbookEvaluator {
|
||||
_evaluationListener = evaluationListener;
|
||||
_cache = new EvaluationCache(evaluationListener);
|
||||
_sheetIndexesBySheet = new IdentityHashMap();
|
||||
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,7 +111,22 @@ public class WorkbookEvaluator {
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
/* package */ void attachToEnvironment(CollaboratingWorkbooksEnvironment collaboratingWorkbooksEnvironment, EvaluationCache cache) {
|
||||
_collaboratingWorkbookEnvironment = collaboratingWorkbooksEnvironment;
|
||||
_cache = cache;
|
||||
}
|
||||
/* package */ CollaboratingWorkbooksEnvironment getEnvironment() {
|
||||
return _collaboratingWorkbookEnvironment;
|
||||
}
|
||||
|
||||
/* package */ void detachFromEnvironment() {
|
||||
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
|
||||
_cache = new EvaluationCache(_evaluationListener);
|
||||
}
|
||||
/* package */ IEvaluationListener getEvaluationListener() {
|
||||
return _evaluationListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called whenever there are changes to input cells in the evaluated workbook.
|
||||
* Failure to call this method after changing cell values will cause incorrect behaviour
|
||||
@ -130,7 +148,7 @@ public class WorkbookEvaluator {
|
||||
throw new IllegalArgumentException("value must not be null");
|
||||
}
|
||||
int sheetIndex = getSheetIndex(sheet);
|
||||
_cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
|
||||
_cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
|
||||
|
||||
}
|
||||
/**
|
||||
@ -139,13 +157,17 @@ public class WorkbookEvaluator {
|
||||
*/
|
||||
public void notifySetFormula(HSSFSheet sheet, int rowIndex, int columnIndex) {
|
||||
int sheetIndex = getSheetIndex(sheet);
|
||||
_cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
|
||||
_cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
|
||||
|
||||
}
|
||||
private int getSheetIndex(HSSFSheet sheet) {
|
||||
Integer result = (Integer) _sheetIndexesBySheet.get(sheet);
|
||||
if (result == null) {
|
||||
result = new Integer(_workbook.getSheetIndex(sheet));
|
||||
int sheetIndex = _workbook.getSheetIndex(sheet);
|
||||
if (sheetIndex < 0) {
|
||||
throw new RuntimeException("Specified sheet from a different book");
|
||||
}
|
||||
result = new Integer(sheetIndex);
|
||||
_sheetIndexesBySheet.put(sheet, result);
|
||||
}
|
||||
return result.intValue();
|
||||
@ -153,7 +175,7 @@ public class WorkbookEvaluator {
|
||||
|
||||
public ValueEval evaluate(HSSFCell srcCell) {
|
||||
int sheetIndex = getSheetIndex(srcCell.getSheet());
|
||||
CellLocation cellLoc = new CellLocation(sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
|
||||
CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
|
||||
return internalEvaluate(srcCell, cellLoc, new EvaluationTracker(_cache));
|
||||
}
|
||||
|
||||
@ -342,6 +364,20 @@ public class WorkbookEvaluator {
|
||||
}
|
||||
return operation.evaluate(ops, srcRowNum, (short)srcColNum);
|
||||
}
|
||||
private SheetRefEvaluator createExternSheetRefEvaluator(EvaluationTracker tracker,
|
||||
ExternSheetReferenceToken ptg) {
|
||||
int externSheetIndex = ptg.getExternSheetIndex();
|
||||
ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
|
||||
if (externalSheet != null) {
|
||||
WorkbookEvaluator otherEvaluator = _collaboratingWorkbookEnvironment.getWorkbookEvaluator(externalSheet.getWorkbookName());
|
||||
EvaluationWorkbook otherBook = otherEvaluator._workbook;
|
||||
int otherSheetIndex = otherBook.getSheetIndex(externalSheet.getSheetName());
|
||||
return new SheetRefEvaluator(otherEvaluator, tracker, otherBook, otherSheetIndex);
|
||||
}
|
||||
int otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex);
|
||||
return new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an appropriate Eval impl instance for the Ptg. The Ptg must be
|
||||
@ -350,6 +386,8 @@ public class WorkbookEvaluator {
|
||||
* passed here!
|
||||
*/
|
||||
private Eval getEvalForPtg(Ptg ptg, int sheetIndex, EvaluationTracker tracker) {
|
||||
// consider converting all these (ptg instanceof XxxPtg) expressions to (ptg.getClass() == XxxPtg.class)
|
||||
|
||||
if (ptg instanceof NamePtg) {
|
||||
// named ranges, macro functions
|
||||
NamePtg namePtg = (NamePtg) ptg;
|
||||
@ -388,14 +426,12 @@ public class WorkbookEvaluator {
|
||||
}
|
||||
if (ptg instanceof Ref3DPtg) {
|
||||
Ref3DPtg refPtg = (Ref3DPtg) ptg;
|
||||
int otherSheetIndex = _workbook.convertFromExternSheetIndex(refPtg.getExternSheetIndex());
|
||||
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
|
||||
SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, refPtg);
|
||||
return new LazyRefEval(refPtg, sre);
|
||||
}
|
||||
if (ptg instanceof Area3DPtg) {
|
||||
Area3DPtg aptg = (Area3DPtg) ptg;
|
||||
int otherSheetIndex = _workbook.convertFromExternSheetIndex(aptg.getExternSheetIndex());
|
||||
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
|
||||
SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, aptg);
|
||||
return new LazyAreaEval(aptg, sre);
|
||||
}
|
||||
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, sheetIndex);
|
||||
@ -435,7 +471,7 @@ public class WorkbookEvaluator {
|
||||
} else {
|
||||
cell = row.getCell(columnIndex);
|
||||
}
|
||||
CellLocation cellLoc = new CellLocation(sheetIndex, rowIndex, columnIndex);
|
||||
CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex);
|
||||
tracker.acceptDependency(cellLoc);
|
||||
return internalEvaluate(cell, cellLoc, tracker);
|
||||
}
|
||||
|
BIN
src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls
Normal file
BIN
src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls
Normal file
Binary file not shown.
BIN
src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls
Normal file
BIN
src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls
Normal file
Binary file not shown.
@ -43,11 +43,13 @@ import org.apache.poi.hssf.record.formula.NumberPtg;
|
||||
import org.apache.poi.hssf.record.formula.PercentPtg;
|
||||
import org.apache.poi.hssf.record.formula.PowerPtg;
|
||||
import org.apache.poi.hssf.record.formula.Ptg;
|
||||
import org.apache.poi.hssf.record.formula.Ref3DPtg;
|
||||
import org.apache.poi.hssf.record.formula.RefPtg;
|
||||
import org.apache.poi.hssf.record.formula.StringPtg;
|
||||
import org.apache.poi.hssf.record.formula.SubtractPtg;
|
||||
import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
|
||||
import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
|
||||
import org.apache.poi.hssf.usermodel.FormulaExtractor;
|
||||
import org.apache.poi.hssf.usermodel.HSSFCell;
|
||||
import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
|
||||
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
|
||||
@ -913,4 +915,33 @@ public final class TestFormulaParser extends TestCase {
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ public final class TestSupBookRecord extends TestCase {
|
||||
|
||||
assertEquals( 34, record.getRecordSize() ); //sid+size+data
|
||||
|
||||
assertEquals("testURL", record.getURL().getString());
|
||||
assertEquals("testURL", record.getURL());
|
||||
UnicodeString[] sheetNames = record.getSheetNames();
|
||||
assertEquals(2, sheetNames.length);
|
||||
assertEquals("Sheet1", sheetNames[0].getString());
|
||||
|
@ -19,6 +19,7 @@ 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;
|
||||
@ -29,36 +30,40 @@ 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}.
|
||||
* 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") )
|
||||
* 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! )
|
||||
* (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));
|
||||
@ -68,25 +73,82 @@ public class TestWorkbookEvaluator extends TestCase {
|
||||
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") )
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user