Merged revisions 699178,699487,699489,699761 via svnmerge from
https://svn.apache.org/repos/asf/poi/trunk ........ r699178 | josh | 2008-09-25 21:49:20 -0700 (Thu, 25 Sep 2008) | 1 line Changed HSSFEvaluationWorkbook to avoid re-parsing cell formulas during execution. (working towards fix for bug 45865) ........ r699487 | josh | 2008-09-26 13:25:45 -0700 (Fri, 26 Sep 2008) | 1 line Fix formula parser to properly support the range operator. Small fixes to parsing of sheet names and full column references. ........ r699489 | josh | 2008-09-26 13:32:06 -0700 (Fri, 26 Sep 2008) | 1 line Code cleanup in junit ........ r699761 | josh | 2008-09-27 19:04:31 -0700 (Sat, 27 Sep 2008) | 1 line Bug 45865 - modified Formula Parser/Evaluator to handle cross-worksheet formulas ........ git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@700234 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
0d50343293
commit
96cb7321ba
@ -67,6 +67,7 @@
|
||||
<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
|
||||
</release>
|
||||
<release version="3.2-alpha1" date="2008-??-??">
|
||||
<action dev="POI-DEVELOPERS" type="add">45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">Optimised the FormulaEvaluator to take cell dependencies into account</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">16936 - Initial support for whole-row cell styling</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">Update hssf.extractor.ExcelExtractor to optionally output blank cells too</action>
|
||||
|
@ -64,6 +64,7 @@
|
||||
<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
|
||||
</release>
|
||||
<release version="3.2-alpha1" date="2008-??-??">
|
||||
<action dev="POI-DEVELOPERS" type="add">45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">Optimised the FormulaEvaluator to take cell dependencies into account</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">16936 - Initial support for whole-row cell styling</action>
|
||||
<action dev="POI-DEVELOPERS" type="add">Update hssf.extractor.ExcelExtractor to optionally output blank cells too</action>
|
||||
|
@ -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,8 +18,9 @@
|
||||
package org.apache.poi.hssf.record.formula;
|
||||
|
||||
import org.apache.poi.hssf.record.RecordInputStream;
|
||||
import org.apache.poi.ss.formula.WorkbookDependentFormula;
|
||||
import org.apache.poi.ss.formula.ExternSheetReferenceToken;
|
||||
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
|
||||
import org.apache.poi.ss.formula.WorkbookDependentFormula;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
|
||||
/**
|
||||
@ -31,7 +32,7 @@ import org.apache.poi.util.LittleEndian;
|
||||
* @author Jason Height (jheight at chariot dot net dot au)
|
||||
* @version 1.0-pre
|
||||
*/
|
||||
public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFormula {
|
||||
public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFormula, ExternSheetReferenceToken {
|
||||
public final static byte sid = 0x3b;
|
||||
private final static int SIZE = 11; // 10 + 1 for Ptg
|
||||
|
||||
@ -76,8 +77,8 @@ public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFor
|
||||
return SIZE;
|
||||
}
|
||||
|
||||
public short getExternSheetIndex() {
|
||||
return (short)field_1_index_extern_sheet;
|
||||
public int getExternSheetIndex() {
|
||||
return field_1_index_extern_sheet;
|
||||
}
|
||||
|
||||
public void setExternSheetIndex(int index) {
|
||||
|
@ -50,10 +50,10 @@ public interface AreaI {
|
||||
|
||||
public OffsetArea(int baseRow, int baseColumn, int relFirstRowIx, int relLastRowIx,
|
||||
int relFirstColIx, int relLastColIx) {
|
||||
_firstRow = baseRow + relFirstRowIx;
|
||||
_lastRow = baseRow + relLastRowIx;
|
||||
_firstColumn = baseColumn + relFirstColIx;
|
||||
_lastColumn = baseColumn + relLastColIx;
|
||||
_firstRow = baseRow + Math.min(relFirstRowIx, relLastRowIx);
|
||||
_lastRow = baseRow + Math.max(relFirstRowIx, relLastRowIx);
|
||||
_firstColumn = baseColumn + Math.min(relFirstColIx, relLastColIx);
|
||||
_lastColumn = baseColumn + Math.max(relFirstColIx, relLastColIx);
|
||||
}
|
||||
|
||||
public int getFirstColumn() {
|
||||
@ -72,5 +72,4 @@ public interface AreaI {
|
||||
return _lastRow;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -30,248 +30,264 @@ import org.apache.poi.util.LittleEndian;
|
||||
* @author Jason Height (jheight at chariot dot net dot au)
|
||||
*/
|
||||
public abstract class AreaPtgBase extends OperandPtg implements AreaI {
|
||||
/**
|
||||
* TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas.
|
||||
* see similar comment in ReferencePtg
|
||||
*/
|
||||
protected final RuntimeException notImplemented() {
|
||||
return new RuntimeException("Coding Error: This method should never be called. This ptg should be converted");
|
||||
}
|
||||
/**
|
||||
* TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas.
|
||||
* see similar comment in ReferencePtg
|
||||
*/
|
||||
protected final RuntimeException notImplemented() {
|
||||
return new RuntimeException("Coding Error: This method should never be called. This ptg should be converted");
|
||||
}
|
||||
|
||||
/** zero based, unsigned 16 bit */
|
||||
private int field_1_first_row;
|
||||
/** zero based, unsigned 16 bit */
|
||||
private int field_2_last_row;
|
||||
/** zero based, unsigned 8 bit */
|
||||
private int field_3_first_column;
|
||||
/** zero based, unsigned 8 bit */
|
||||
private int field_4_last_column;
|
||||
|
||||
private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000);
|
||||
private final static BitField colRelative = BitFieldFactory.getInstance(0x4000);
|
||||
private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF);
|
||||
/** zero based, unsigned 16 bit */
|
||||
private int field_1_first_row;
|
||||
/** zero based, unsigned 16 bit */
|
||||
private int field_2_last_row;
|
||||
/** zero based, unsigned 8 bit */
|
||||
private int field_3_first_column;
|
||||
/** zero based, unsigned 8 bit */
|
||||
private int field_4_last_column;
|
||||
|
||||
private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000);
|
||||
private final static BitField colRelative = BitFieldFactory.getInstance(0x4000);
|
||||
private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF);
|
||||
|
||||
protected AreaPtgBase() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
protected AreaPtgBase(String arearef) {
|
||||
AreaReference ar = new AreaReference(arearef);
|
||||
CellReference firstCell = ar.getFirstCell();
|
||||
CellReference lastCell = ar.getLastCell();
|
||||
setFirstRow(firstCell.getRow());
|
||||
setFirstColumn(firstCell.getCol());
|
||||
setLastRow(lastCell.getRow());
|
||||
setLastColumn(lastCell.getCol());
|
||||
setFirstColRelative(!firstCell.isColAbsolute());
|
||||
setLastColRelative(!lastCell.isColAbsolute());
|
||||
setFirstRowRelative(!firstCell.isRowAbsolute());
|
||||
setLastRowRelative(!lastCell.isRowAbsolute());
|
||||
}
|
||||
|
||||
protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn,
|
||||
boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
|
||||
|
||||
checkColumnBounds(firstColumn);
|
||||
checkColumnBounds(lastColumn);
|
||||
checkRowBounds(firstRow);
|
||||
checkRowBounds(lastRow);
|
||||
setFirstRow(firstRow);
|
||||
setLastRow(lastRow);
|
||||
setFirstColumn(firstColumn);
|
||||
setLastColumn(lastColumn);
|
||||
setFirstRowRelative(firstRowRelative);
|
||||
setLastRowRelative(lastRowRelative);
|
||||
setFirstColRelative(firstColRelative);
|
||||
setLastColRelative(lastColRelative);
|
||||
}
|
||||
protected AreaPtgBase() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
protected AreaPtgBase(String arearef) {
|
||||
AreaReference ar = new AreaReference(arearef);
|
||||
CellReference firstCell = ar.getFirstCell();
|
||||
CellReference lastCell = ar.getLastCell();
|
||||
setFirstRow(firstCell.getRow());
|
||||
setFirstColumn(firstCell.getCol());
|
||||
setLastRow(lastCell.getRow());
|
||||
setLastColumn(lastCell.getCol());
|
||||
setFirstColRelative(!firstCell.isColAbsolute());
|
||||
setLastColRelative(!lastCell.isColAbsolute());
|
||||
setFirstRowRelative(!firstCell.isRowAbsolute());
|
||||
setLastRowRelative(!lastCell.isRowAbsolute());
|
||||
}
|
||||
|
||||
protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn,
|
||||
boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
|
||||
|
||||
checkColumnBounds(firstColumn);
|
||||
checkColumnBounds(lastColumn);
|
||||
checkRowBounds(firstRow);
|
||||
checkRowBounds(lastRow);
|
||||
|
||||
if (lastRow > firstRow) {
|
||||
setFirstRow(firstRow);
|
||||
setLastRow(lastRow);
|
||||
setFirstRowRelative(firstRowRelative);
|
||||
setLastRowRelative(lastRowRelative);
|
||||
} else {
|
||||
setFirstRow(lastRow);
|
||||
setLastRow(firstRow);
|
||||
setFirstRowRelative(lastRowRelative);
|
||||
setLastRowRelative(firstRowRelative);
|
||||
}
|
||||
|
||||
if (lastColumn > firstColumn) {
|
||||
setFirstColumn(firstColumn);
|
||||
setLastColumn(lastColumn);
|
||||
setFirstColRelative(firstColRelative);
|
||||
setLastColRelative(lastColRelative);
|
||||
} else {
|
||||
setFirstColumn(lastColumn);
|
||||
setLastColumn(firstColumn);
|
||||
setFirstColRelative(lastColRelative);
|
||||
setLastColRelative(firstColRelative);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkColumnBounds(int colIx) {
|
||||
if((colIx & 0x0FF) != colIx) {
|
||||
throw new IllegalArgumentException("colIx (" + colIx + ") is out of range");
|
||||
}
|
||||
}
|
||||
private static void checkRowBounds(int rowIx) {
|
||||
if((rowIx & 0x0FFFF) != rowIx) {
|
||||
throw new IllegalArgumentException("rowIx (" + rowIx + ") is out of range");
|
||||
}
|
||||
}
|
||||
private static void checkColumnBounds(int colIx) {
|
||||
if((colIx & 0x0FF) != colIx) {
|
||||
throw new IllegalArgumentException("colIx (" + colIx + ") is out of range");
|
||||
}
|
||||
}
|
||||
private static void checkRowBounds(int rowIx) {
|
||||
if((rowIx & 0x0FFFF) != rowIx) {
|
||||
throw new IllegalArgumentException("rowIx (" + rowIx + ") is out of range");
|
||||
}
|
||||
}
|
||||
|
||||
protected final void readCoordinates(RecordInputStream in) {
|
||||
field_1_first_row = in.readUShort();
|
||||
field_2_last_row = in.readUShort();
|
||||
field_3_first_column = in.readUShort();
|
||||
field_4_last_column = in.readUShort();
|
||||
}
|
||||
protected final void writeCoordinates(byte[] array, int offset) {
|
||||
LittleEndian.putUShort(array, offset + 0, field_1_first_row);
|
||||
LittleEndian.putUShort(array, offset + 2, field_2_last_row);
|
||||
LittleEndian.putUShort(array, offset + 4, field_3_first_column);
|
||||
LittleEndian.putUShort(array, offset + 6, field_4_last_column);
|
||||
}
|
||||
protected final void readCoordinates(RecordInputStream in) {
|
||||
field_1_first_row = in.readUShort();
|
||||
field_2_last_row = in.readUShort();
|
||||
field_3_first_column = in.readUShort();
|
||||
field_4_last_column = in.readUShort();
|
||||
}
|
||||
protected final void writeCoordinates(byte[] array, int offset) {
|
||||
LittleEndian.putUShort(array, offset + 0, field_1_first_row);
|
||||
LittleEndian.putUShort(array, offset + 2, field_2_last_row);
|
||||
LittleEndian.putUShort(array, offset + 4, field_3_first_column);
|
||||
LittleEndian.putUShort(array, offset + 6, field_4_last_column);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first row in the area
|
||||
*/
|
||||
public final int getFirstRow() {
|
||||
return field_1_first_row;
|
||||
}
|
||||
/**
|
||||
* @return the first row in the area
|
||||
*/
|
||||
public final int getFirstRow() {
|
||||
return field_1_first_row;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the first row
|
||||
* @param rowIx number (0-based)
|
||||
*/
|
||||
public final void setFirstRow(int rowIx) {
|
||||
checkRowBounds(rowIx);
|
||||
field_1_first_row = rowIx;
|
||||
}
|
||||
/**
|
||||
* sets the first row
|
||||
* @param rowIx number (0-based)
|
||||
*/
|
||||
public final void setFirstRow(int rowIx) {
|
||||
checkRowBounds(rowIx);
|
||||
field_1_first_row = rowIx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return last row in the range (x2 in x1,y1-x2,y2)
|
||||
*/
|
||||
public final int getLastRow() {
|
||||
return field_2_last_row;
|
||||
}
|
||||
/**
|
||||
* @return last row in the range (x2 in x1,y1-x2,y2)
|
||||
*/
|
||||
public final int getLastRow() {
|
||||
return field_2_last_row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rowIx last row number in the area
|
||||
*/
|
||||
public final void setLastRow(int rowIx) {
|
||||
checkRowBounds(rowIx);
|
||||
field_2_last_row = rowIx;
|
||||
}
|
||||
/**
|
||||
* @param rowIx last row number in the area
|
||||
*/
|
||||
public final void setLastRow(int rowIx) {
|
||||
checkRowBounds(rowIx);
|
||||
field_2_last_row = rowIx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first column number in the area.
|
||||
*/
|
||||
public final int getFirstColumn() {
|
||||
return columnMask.getValue(field_3_first_column);
|
||||
}
|
||||
/**
|
||||
* @return the first column number in the area.
|
||||
*/
|
||||
public final int getFirstColumn() {
|
||||
return columnMask.getValue(field_3_first_column);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first column number + the options bit settings unstripped
|
||||
*/
|
||||
public final short getFirstColumnRaw() {
|
||||
return (short) field_3_first_column; // TODO
|
||||
}
|
||||
/**
|
||||
* @return the first column number + the options bit settings unstripped
|
||||
*/
|
||||
public final short getFirstColumnRaw() {
|
||||
return (short) field_3_first_column; // TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether or not the first row is a relative reference or not.
|
||||
*/
|
||||
public final boolean isFirstRowRelative() {
|
||||
return rowRelative.isSet(field_3_first_column);
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the first row to relative or not
|
||||
* @param rel is relative or not.
|
||||
*/
|
||||
public final void setFirstRowRelative(boolean rel) {
|
||||
field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel);
|
||||
}
|
||||
/**
|
||||
* @return whether or not the first row is a relative reference or not.
|
||||
*/
|
||||
public final boolean isFirstRowRelative() {
|
||||
return rowRelative.isSet(field_3_first_column);
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the first row to relative or not
|
||||
* @param rel is relative or not.
|
||||
*/
|
||||
public final void setFirstRowRelative(boolean rel) {
|
||||
field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return isrelative first column to relative or not
|
||||
*/
|
||||
public final boolean isFirstColRelative() {
|
||||
return colRelative.isSet(field_3_first_column);
|
||||
}
|
||||
|
||||
/**
|
||||
* set whether the first column is relative
|
||||
*/
|
||||
public final void setFirstColRelative(boolean rel) {
|
||||
field_3_first_column=colRelative.setBoolean(field_3_first_column,rel);
|
||||
}
|
||||
/**
|
||||
* @return isrelative first column to relative or not
|
||||
*/
|
||||
public final boolean isFirstColRelative() {
|
||||
return colRelative.isSet(field_3_first_column);
|
||||
}
|
||||
|
||||
/**
|
||||
* set whether the first column is relative
|
||||
*/
|
||||
public final void setFirstColRelative(boolean rel) {
|
||||
field_3_first_column=colRelative.setBoolean(field_3_first_column,rel);
|
||||
}
|
||||
|
||||
/**
|
||||
* set the first column in the area
|
||||
*/
|
||||
public final void setFirstColumn(int colIx) {
|
||||
checkColumnBounds(colIx);
|
||||
field_3_first_column=columnMask.setValue(field_3_first_column, colIx);
|
||||
}
|
||||
/**
|
||||
* set the first column in the area
|
||||
*/
|
||||
public final void setFirstColumn(int colIx) {
|
||||
checkColumnBounds(colIx);
|
||||
field_3_first_column=columnMask.setValue(field_3_first_column, colIx);
|
||||
}
|
||||
|
||||
/**
|
||||
* set the first column irrespective of the bitmasks
|
||||
*/
|
||||
public final void setFirstColumnRaw(int column) {
|
||||
field_3_first_column = column;
|
||||
}
|
||||
/**
|
||||
* set the first column irrespective of the bitmasks
|
||||
*/
|
||||
public final void setFirstColumnRaw(int column) {
|
||||
field_3_first_column = column;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return lastcolumn in the area
|
||||
*/
|
||||
public final int getLastColumn() {
|
||||
return columnMask.getValue(field_4_last_column);
|
||||
}
|
||||
/**
|
||||
* @return lastcolumn in the area
|
||||
*/
|
||||
public final int getLastColumn() {
|
||||
return columnMask.getValue(field_4_last_column);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return last column and bitmask (the raw field)
|
||||
*/
|
||||
public final short getLastColumnRaw() {
|
||||
return (short) field_4_last_column;
|
||||
}
|
||||
/**
|
||||
* @return last column and bitmask (the raw field)
|
||||
*/
|
||||
public final short getLastColumnRaw() {
|
||||
return (short) field_4_last_column;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return last row relative or not
|
||||
*/
|
||||
public final boolean isLastRowRelative() {
|
||||
return rowRelative.isSet(field_4_last_column);
|
||||
}
|
||||
|
||||
/**
|
||||
* set whether the last row is relative or not
|
||||
* @param rel <code>true</code> if the last row relative, else
|
||||
* <code>false</code>
|
||||
*/
|
||||
public final void setLastRowRelative(boolean rel) {
|
||||
field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel);
|
||||
}
|
||||
/**
|
||||
* @return last row relative or not
|
||||
*/
|
||||
public final boolean isLastRowRelative() {
|
||||
return rowRelative.isSet(field_4_last_column);
|
||||
}
|
||||
|
||||
/**
|
||||
* set whether the last row is relative or not
|
||||
* @param rel <code>true</code> if the last row relative, else
|
||||
* <code>false</code>
|
||||
*/
|
||||
public final void setLastRowRelative(boolean rel) {
|
||||
field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return lastcol relative or not
|
||||
*/
|
||||
public final boolean isLastColRelative() {
|
||||
return colRelative.isSet(field_4_last_column);
|
||||
}
|
||||
|
||||
/**
|
||||
* set whether the last column should be relative or not
|
||||
*/
|
||||
public final void setLastColRelative(boolean rel) {
|
||||
field_4_last_column=colRelative.setBoolean(field_4_last_column,rel);
|
||||
}
|
||||
|
||||
/**
|
||||
* set the last column in the area
|
||||
*/
|
||||
public final void setLastColumn(int colIx) {
|
||||
checkColumnBounds(colIx);
|
||||
field_4_last_column=columnMask.setValue(field_4_last_column, colIx);
|
||||
}
|
||||
/**
|
||||
* @return lastcol relative or not
|
||||
*/
|
||||
public final boolean isLastColRelative() {
|
||||
return colRelative.isSet(field_4_last_column);
|
||||
}
|
||||
|
||||
/**
|
||||
* set whether the last column should be relative or not
|
||||
*/
|
||||
public final void setLastColRelative(boolean rel) {
|
||||
field_4_last_column=colRelative.setBoolean(field_4_last_column,rel);
|
||||
}
|
||||
|
||||
/**
|
||||
* set the last column in the area
|
||||
*/
|
||||
public final void setLastColumn(int colIx) {
|
||||
checkColumnBounds(colIx);
|
||||
field_4_last_column=columnMask.setValue(field_4_last_column, colIx);
|
||||
}
|
||||
|
||||
/**
|
||||
* set the last column irrespective of the bitmasks
|
||||
*/
|
||||
public final void setLastColumnRaw(short column) {
|
||||
field_4_last_column = column;
|
||||
}
|
||||
protected final String formatReferenceAsString() {
|
||||
CellReference topLeft = new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative());
|
||||
CellReference botRight = new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative());
|
||||
|
||||
if(AreaReference.isWholeColumnReference(topLeft, botRight)) {
|
||||
return (new AreaReference(topLeft, botRight)).formatAsString();
|
||||
}
|
||||
return topLeft.formatAsString() + ":" + botRight.formatAsString();
|
||||
}
|
||||
|
||||
public String toFormulaString() {
|
||||
return formatReferenceAsString();
|
||||
}
|
||||
/**
|
||||
* set the last column irrespective of the bitmasks
|
||||
*/
|
||||
public final void setLastColumnRaw(short column) {
|
||||
field_4_last_column = column;
|
||||
}
|
||||
protected final String formatReferenceAsString() {
|
||||
CellReference topLeft = new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative());
|
||||
CellReference botRight = new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative());
|
||||
|
||||
if(AreaReference.isWholeColumnReference(topLeft, botRight)) {
|
||||
return (new AreaReference(topLeft, botRight)).formatAsString();
|
||||
}
|
||||
return topLeft.formatAsString() + ":" + botRight.formatAsString();
|
||||
}
|
||||
|
||||
public String toFormulaString() {
|
||||
return formatReferenceAsString();
|
||||
}
|
||||
|
||||
public byte getDefaultOperandClass() {
|
||||
return Ptg.CLASS_REF;
|
||||
}
|
||||
public byte getDefaultOperandClass() {
|
||||
return Ptg.CLASS_REF;
|
||||
}
|
||||
}
|
||||
|
@ -34,13 +34,13 @@ public final class AttrPtg extends ControlPtg {
|
||||
private final static int SIZE = 4;
|
||||
private byte field_1_options;
|
||||
private short field_2_data;
|
||||
|
||||
|
||||
/** only used for tAttrChoose: table of offsets to starts of args */
|
||||
private final int[] _jumpTable;
|
||||
/** only used for tAttrChoose: offset to the tFuncVar for CHOOSE() */
|
||||
private final int _chooseFuncOffset;
|
||||
|
||||
// flags 'volatile' and 'space', can be combined.
|
||||
|
||||
// flags 'volatile' and 'space', can be combined.
|
||||
// OOO spec says other combinations are theoretically possible but not likely to occur.
|
||||
private static final BitField semiVolatile = BitFieldFactory.getInstance(0x01);
|
||||
private static final BitField optiIf = BitFieldFactory.getInstance(0x02);
|
||||
@ -49,12 +49,14 @@ public final class AttrPtg extends ControlPtg {
|
||||
private static final BitField sum = BitFieldFactory.getInstance(0x10);
|
||||
private static final BitField baxcel = BitFieldFactory.getInstance(0x20); // 'assignment-style formula in a macro sheet'
|
||||
private static final BitField space = BitFieldFactory.getInstance(0x40);
|
||||
|
||||
|
||||
public static final AttrPtg SUM = new AttrPtg(0x0010, 0, null, -1);
|
||||
|
||||
public static final class SpaceType {
|
||||
private SpaceType() {
|
||||
// no instances of this class
|
||||
}
|
||||
|
||||
|
||||
/** 00H = Spaces before the next token (not allowed before tParen token) */
|
||||
public static final int SPACE_BEFORE = 0x00;
|
||||
/** 01H = Carriage returns before the next token (not allowed before tParen token) */
|
||||
@ -75,7 +77,7 @@ public final class AttrPtg extends ControlPtg {
|
||||
_jumpTable = null;
|
||||
_chooseFuncOffset = -1;
|
||||
}
|
||||
|
||||
|
||||
public AttrPtg(RecordInputStream in)
|
||||
{
|
||||
field_1_options = in.readByte();
|
||||
@ -92,7 +94,7 @@ public final class AttrPtg extends ControlPtg {
|
||||
_jumpTable = null;
|
||||
_chooseFuncOffset = -1;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
private AttrPtg(int options, int data, int[] jt, int chooseFuncOffset) {
|
||||
field_1_options = (byte) options;
|
||||
@ -100,7 +102,7 @@ public final class AttrPtg extends ControlPtg {
|
||||
_jumpTable = jt;
|
||||
_chooseFuncOffset = chooseFuncOffset;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param type a constant from <tt>SpaceType</tt>
|
||||
* @param count the number of space characters
|
||||
@ -145,7 +147,7 @@ public final class AttrPtg extends ControlPtg {
|
||||
{
|
||||
return sum.isSet(getOptions());
|
||||
}
|
||||
|
||||
|
||||
public void setSum(boolean bsum) {
|
||||
field_1_options=sum.setByteBoolean(field_1_options,bsum);
|
||||
}
|
||||
@ -155,13 +157,13 @@ public final class AttrPtg extends ControlPtg {
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags this ptg as a goto/jump
|
||||
* Flags this ptg as a goto/jump
|
||||
* @param isGoto
|
||||
*/
|
||||
public void setGoto(boolean isGoto) {
|
||||
field_1_options=optGoto.setByteBoolean(field_1_options, isGoto);
|
||||
}
|
||||
|
||||
|
||||
// lets hope no one uses this anymore
|
||||
public boolean isBaxcel()
|
||||
{
|
||||
@ -201,7 +203,7 @@ public final class AttrPtg extends ControlPtg {
|
||||
} else if(isOptimizedChoose()) {
|
||||
sb.append("choose nCases=").append(getData());
|
||||
} else if(isGoto()) {
|
||||
sb.append("skip dist=").append(getData());
|
||||
sb.append("skip dist=").append(getData());
|
||||
} else if(isSum()) {
|
||||
sb.append("sum ");
|
||||
} else if(isBaxcel()) {
|
||||
@ -218,7 +220,7 @@ public final class AttrPtg extends ControlPtg {
|
||||
LittleEndian.putShort(array,offset+2, field_2_data);
|
||||
int[] jt = _jumpTable;
|
||||
if (jt != null) {
|
||||
int joff = offset+4;
|
||||
int joff = offset+4;
|
||||
LittleEndian.putUShort(array, joff, _chooseFuncOffset);
|
||||
joff+=2;
|
||||
for (int i = 0; i < jt.length; i++) {
|
||||
@ -227,7 +229,7 @@ public final class AttrPtg extends ControlPtg {
|
||||
}
|
||||
LittleEndian.putUShort(array, joff, _chooseFuncOffset);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public int getSize()
|
||||
@ -249,7 +251,7 @@ public final class AttrPtg extends ControlPtg {
|
||||
return toFormulaString() + "(" + operands[ 0 ] + ")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public int getNumberOfOperands()
|
||||
{
|
||||
@ -260,7 +262,7 @@ public final class AttrPtg extends ControlPtg {
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
public String toFormulaString() {
|
||||
if(semiVolatile.isSet(field_1_options)) {
|
||||
return "ATTR(semiVolatile)";
|
||||
|
@ -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.ss.util.CellReference;
|
||||
import org.apache.poi.ss.formula.WorkbookDependentFormula;
|
||||
import org.apache.poi.ss.formula.ExternSheetReferenceToken;
|
||||
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
|
||||
import org.apache.poi.ss.formula.WorkbookDependentFormula;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
|
||||
/**
|
||||
@ -31,7 +32,7 @@ import org.apache.poi.util.LittleEndian;
|
||||
* @author Jason Height (jheight at chariot dot net dot au)
|
||||
* @version 1.0-pre
|
||||
*/
|
||||
public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormula {
|
||||
public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormula, ExternSheetReferenceToken {
|
||||
public final static byte sid = 0x3a;
|
||||
|
||||
private final static int SIZE = 7; // 6 + 1 for Ptg
|
||||
@ -75,11 +76,11 @@ public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormu
|
||||
return SIZE;
|
||||
}
|
||||
|
||||
public int getExternSheetIndex(){
|
||||
public int getExternSheetIndex() {
|
||||
return field_1_index_extern_sheet;
|
||||
}
|
||||
|
||||
public void setExternSheetIndex(int index){
|
||||
public void setExternSheetIndex(int index) {
|
||||
field_1_index_extern_sheet = index;
|
||||
}
|
||||
|
||||
|
@ -66,6 +66,22 @@ public final class SheetNameFormatter {
|
||||
out.append(rawSheetName);
|
||||
}
|
||||
}
|
||||
public static void appendFormat(StringBuffer out, String workbookName, String rawSheetName) {
|
||||
boolean needsQuotes = needsDelimiting(workbookName) || needsDelimiting(rawSheetName);
|
||||
if(needsQuotes) {
|
||||
out.append(DELIMITER);
|
||||
out.append('[');
|
||||
appendAndEscape(out, workbookName.replace('[', '(').replace(']', ')'));
|
||||
out.append(']');
|
||||
appendAndEscape(out, rawSheetName);
|
||||
out.append(DELIMITER);
|
||||
} else {
|
||||
out.append('[');
|
||||
out.append(workbookName);
|
||||
out.append(']');
|
||||
out.append(rawSheetName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendAndEscape(StringBuffer sb, String rawSheetName) {
|
||||
int len = rawSheetName.length();
|
||||
@ -101,13 +117,27 @@ public final class SheetNameFormatter {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (nameLooksLikeBooleanLiteral(rawSheetName)) {
|
||||
return true;
|
||||
}
|
||||
// Error constant literals all contain '#' and other special characters
|
||||
// so they don't get this far
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean nameLooksLikeBooleanLiteral(String rawSheetName) {
|
||||
switch(rawSheetName.charAt(0)) {
|
||||
case 'T': case 't':
|
||||
return "TRUE".equalsIgnoreCase(rawSheetName);
|
||||
case 'F': case 'f':
|
||||
return "FALSE".equalsIgnoreCase(rawSheetName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @return <code>true</code> if the presence of the specified character in a sheet name would
|
||||
* require the sheet name to be delimited in formulas. This includes every non-alphanumeric
|
||||
* character besides underscore '_'.
|
||||
* character besides underscore '_' and dot '.'.
|
||||
*/
|
||||
/* package */ static boolean isSpecialChar(char ch) {
|
||||
// note - Character.isJavaIdentifierPart() would allow dollars '$'
|
||||
@ -115,7 +145,8 @@ public final class SheetNameFormatter {
|
||||
return false;
|
||||
}
|
||||
switch(ch) {
|
||||
case '_': // underscore is ok
|
||||
case '.': // dot is OK
|
||||
case '_': // underscore is OK
|
||||
return false;
|
||||
case '\n':
|
||||
case '\r':
|
||||
|
@ -0,0 +1,71 @@
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hssf.record.formula.eval;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Josh Micich
|
||||
*/
|
||||
public final class RangeEval implements OperationEval {
|
||||
|
||||
public static final OperationEval instance = new RangeEval();
|
||||
|
||||
private RangeEval() {
|
||||
}
|
||||
|
||||
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
|
||||
if(args.length != 2) {
|
||||
return ErrorEval.VALUE_INVALID;
|
||||
}
|
||||
|
||||
try {
|
||||
RefEval reA = evaluateRef(args[0]);
|
||||
RefEval reB = evaluateRef(args[1]);
|
||||
return resolveRange(reA, reB);
|
||||
} catch (EvaluationException e) {
|
||||
return e.getErrorEval();
|
||||
}
|
||||
}
|
||||
|
||||
private static AreaEval resolveRange(RefEval reA, RefEval reB) {
|
||||
|
||||
int height = reB.getRow() - reA.getRow();
|
||||
int width = reB.getColumn() - reA.getColumn();
|
||||
|
||||
return reA.offset(0, height, 0, width);
|
||||
}
|
||||
|
||||
private static RefEval evaluateRef(Eval arg) throws EvaluationException {
|
||||
if (arg instanceof RefEval) {
|
||||
return (RefEval) arg;
|
||||
}
|
||||
if (arg instanceof ErrorEval) {
|
||||
throw new EvaluationException((ErrorEval)arg);
|
||||
}
|
||||
throw new IllegalArgumentException("Unexpected ref arg class (" + arg.getClass().getName() + ")");
|
||||
}
|
||||
|
||||
public int getNumberOfOperands() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
throw new RuntimeException("obsolete code should not be called");
|
||||
}
|
||||
}
|
@ -2,7 +2,9 @@ package org.apache.poi.hssf.usermodel;
|
||||
|
||||
import org.apache.poi.hssf.model.HSSFFormulaParser;
|
||||
import org.apache.poi.hssf.model.Workbook;
|
||||
import org.apache.poi.hssf.record.FormulaRecord;
|
||||
import org.apache.poi.hssf.record.NameRecord;
|
||||
import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
|
||||
import org.apache.poi.hssf.record.formula.NamePtg;
|
||||
import org.apache.poi.hssf.record.formula.NameXPtg;
|
||||
import org.apache.poi.hssf.record.formula.Ptg;
|
||||
@ -39,6 +41,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
|
||||
int sheetIndex = _uBook.getSheetIndex(sheetName);
|
||||
return _iBook.checkExternSheet(sheetIndex);
|
||||
}
|
||||
public int getExternalSheetIndex(String workbookName, String sheetName) {
|
||||
return _iBook.getExternalSheetIndex(workbookName, sheetName);
|
||||
}
|
||||
|
||||
public EvaluationName getName(int index) {
|
||||
return new Name(_iBook.getNameRecord(index), index);
|
||||
@ -57,6 +62,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
|
||||
public int getSheetIndex(Sheet sheet) {
|
||||
return _uBook.getSheetIndex(sheet);
|
||||
}
|
||||
public int getSheetIndex(String sheetName) {
|
||||
return _uBook.getSheetIndex(sheetName);
|
||||
}
|
||||
|
||||
public String getSheetName(int sheetIndex) {
|
||||
return _uBook.getSheetName(sheetIndex);
|
||||
@ -75,8 +83,12 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
|
||||
}
|
||||
public int convertFromExternSheetIndex(int externSheetIndex) {
|
||||
return _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public ExternalSheet getExternalSheet(int externSheetIndex) {
|
||||
return _iBook.getExternalSheet(externSheetIndex);
|
||||
}
|
||||
|
||||
public HSSFWorkbook getWorkbook() {
|
||||
return _uBook;
|
||||
}
|
||||
@ -96,7 +108,15 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
|
||||
return new Name(_iBook.getNameRecord(ix), ix);
|
||||
}
|
||||
public Ptg[] getFormulaTokens(Cell cell) {
|
||||
return HSSFFormulaParser.parse(cell.getCellFormula(), _uBook);
|
||||
if (false) {
|
||||
// re-parsing the formula text also works, but is a waste of time
|
||||
// It is useful from time to time to run all unit tests with this code
|
||||
// to make sure that all formulas POI can evaluate can also be parsed.
|
||||
return HSSFFormulaParser.parse(cell.getCellFormula(), _uBook);
|
||||
}
|
||||
HSSFCell hCell = (HSSFCell) cell;
|
||||
FormulaRecord fr = ((FormulaRecordAggregate) hCell.getCellValueRecord()).getFormulaRecord();
|
||||
return fr.getParsedExpression();
|
||||
}
|
||||
|
||||
private static final class Name implements EvaluationName {
|
||||
|
@ -25,6 +25,7 @@ import org.apache.poi.hssf.record.formula.eval.ErrorEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.NumberEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment;
|
||||
import org.apache.poi.ss.formula.WorkbookEvaluator;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellValue;
|
||||
@ -56,6 +57,21 @@ public class HSSFFormulaEvaluator /* almost implements FormulaEvaluator */ {
|
||||
public HSSFFormulaEvaluator(HSSFWorkbook workbook) {
|
||||
_bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook));
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinates several formula evaluators together so that formulas that involve external
|
||||
* references can be evaluated.
|
||||
* @param workbookNames the simple file names used to identify the workbooks in formulas
|
||||
* with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1")
|
||||
* @param evaluators all evaluators for the full set of workbooks required by the formulas.
|
||||
*/
|
||||
public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) {
|
||||
WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length];
|
||||
for (int i = 0; i < wbEvals.length; i++) {
|
||||
wbEvals[i] = evaluators[i]._bookEvaluator;
|
||||
}
|
||||
CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing
|
||||
|
@ -30,6 +30,7 @@ public final class AreaReference extends org.apache.poi.ss.util.AreaReference {
|
||||
|
||||
/**
|
||||
* Creates an area ref from a pair of Cell References.
|
||||
* Also normalises such that the top-left
|
||||
*/
|
||||
public AreaReference(CellReference topLeft, CellReference botRight) {
|
||||
super(topLeft, botRight);
|
||||
|
@ -18,22 +18,13 @@
|
||||
package org.apache.poi.hssf.util;
|
||||
|
||||
/**
|
||||
* Common convertion functions between Excel style A1, C27 style
|
||||
* Common conversion functions between Excel style A1, C27 style
|
||||
* cell references, and POI usermodel style row=0, column=0
|
||||
* style references.
|
||||
* @author Avik Sengupta
|
||||
* @author Dennis Doubleday (patch to seperateRowColumns())
|
||||
*/
|
||||
public final class CellReference extends org.apache.poi.ss.util.CellReference {
|
||||
/**
|
||||
* Used to classify identifiers found in formulas as cell references or not.
|
||||
*/
|
||||
public static final class NameType {
|
||||
public static final int CELL = 1;
|
||||
public static final int NAMED_RANGE = 2;
|
||||
public static final int BAD_CELL_OR_NAMED_RANGE = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an cell ref from a string representation. Sheet names containing special characters should be
|
||||
* delimited and escaped as per normal syntax rules for formulas.
|
||||
@ -45,9 +36,6 @@ public final class CellReference extends org.apache.poi.ss.util.CellReference {
|
||||
public CellReference(int pRow, int pCol) {
|
||||
super(pRow, pCol, true, true);
|
||||
}
|
||||
public CellReference(int pRow, short pCol) {
|
||||
super(pRow, (int)pCol, true, true);
|
||||
}
|
||||
|
||||
public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
|
||||
super(null, pRow, pCol, pAbsRow, pAbsCol);
|
||||
@ -55,10 +43,6 @@ public final class CellReference extends org.apache.poi.ss.util.CellReference {
|
||||
public CellReference(String pSheetName, int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
|
||||
super(pSheetName, pRow, pCol, pAbsRow, pAbsCol);
|
||||
}
|
||||
|
||||
protected void appendCellReference(StringBuffer sb) {
|
||||
super.appendCellReference(sb);
|
||||
}
|
||||
protected static String convertNumToColString(int col) {
|
||||
return org.apache.poi.ss.util.CellReference.convertNumToColString(col);
|
||||
}
|
||||
|
@ -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.ss.usermodel.Sheet;
|
||||
*/
|
||||
public interface EvaluationWorkbook {
|
||||
String getSheetName(int sheetIndex);
|
||||
/**
|
||||
* @return -1 if the specified sheet is from a different book
|
||||
*/
|
||||
int getSheetIndex(Sheet sheet);
|
||||
int getSheetIndex(String sheetName);
|
||||
|
||||
Sheet getSheet(int sheetIndex);
|
||||
|
||||
/**
|
||||
* @return <code>null</code> if externSheetIndex refers to a sheet inside the current workbook
|
||||
*/
|
||||
ExternalSheet getExternalSheet(int externSheetIndex);
|
||||
int convertFromExternSheetIndex(int externSheetIndex);
|
||||
EvaluationName getName(NamePtg namePtg);
|
||||
String resolveNameXText(NameXPtg ptg);
|
||||
Ptg[] getFormulaTokens(Cell cell);
|
||||
|
||||
class ExternalSheet {
|
||||
private final String _workbookName;
|
||||
private final String _sheetName;
|
||||
|
||||
public ExternalSheet(String workbookName, String sheetName) {
|
||||
_workbookName = workbookName;
|
||||
_sheetName = sheetName;
|
||||
}
|
||||
public String getWorkbookName() {
|
||||
return _workbookName;
|
||||
}
|
||||
public String getSheetName() {
|
||||
return _sheetName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -49,6 +49,7 @@ import org.apache.poi.hssf.record.formula.ParenthesisPtg;
|
||||
import org.apache.poi.hssf.record.formula.PercentPtg;
|
||||
import org.apache.poi.hssf.record.formula.PowerPtg;
|
||||
import org.apache.poi.hssf.record.formula.Ptg;
|
||||
import org.apache.poi.hssf.record.formula.RangePtg;
|
||||
import org.apache.poi.hssf.record.formula.Ref3DPtg;
|
||||
import org.apache.poi.hssf.record.formula.RefPtg;
|
||||
import org.apache.poi.hssf.record.formula.StringPtg;
|
||||
@ -60,7 +61,7 @@ import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry;
|
||||
import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
|
||||
import org.apache.poi.hssf.util.AreaReference;
|
||||
import org.apache.poi.hssf.util.CellReference;
|
||||
import org.apache.poi.hssf.util.CellReference.NameType;
|
||||
import org.apache.poi.ss.util.CellReference.NameType;
|
||||
|
||||
/**
|
||||
* This class parses a formula string into a List of tokens in RPN order.
|
||||
@ -81,6 +82,33 @@ import org.apache.poi.hssf.util.CellReference.NameType;
|
||||
* @author Josh Micich
|
||||
*/
|
||||
public final class FormulaParser {
|
||||
private static final class Identifier {
|
||||
private final String _name;
|
||||
private final boolean _isQuoted;
|
||||
|
||||
public Identifier(String name, boolean isQuoted) {
|
||||
_name = name;
|
||||
_isQuoted = isQuoted;
|
||||
}
|
||||
public String getName() {
|
||||
return _name;
|
||||
}
|
||||
public boolean isQuoted() {
|
||||
return _isQuoted;
|
||||
}
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer(64);
|
||||
sb.append(getClass().getName());
|
||||
sb.append(" [");
|
||||
if (_isQuoted) {
|
||||
sb.append("'").append(_name).append("'");
|
||||
} else {
|
||||
sb.append(_name);
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific exception thrown when a supplied formula does not parse properly.<br/>
|
||||
@ -176,23 +204,23 @@ public final class FormulaParser {
|
||||
}
|
||||
|
||||
/** Recognize an Alpha Character */
|
||||
private boolean IsAlpha(char c) {
|
||||
private static boolean IsAlpha(char c) {
|
||||
return Character.isLetter(c) || c == '$' || c=='_';
|
||||
}
|
||||
|
||||
/** Recognize a Decimal Digit */
|
||||
private boolean IsDigit(char c) {
|
||||
private static boolean IsDigit(char c) {
|
||||
return Character.isDigit(c);
|
||||
}
|
||||
|
||||
/** Recognize an Alphanumeric */
|
||||
private boolean IsAlNum(char c) {
|
||||
return (IsAlpha(c) || IsDigit(c));
|
||||
private static boolean IsAlNum(char c) {
|
||||
return IsAlpha(c) || IsDigit(c);
|
||||
}
|
||||
|
||||
/** Recognize White Space */
|
||||
private boolean IsWhite( char c) {
|
||||
return (c ==' ' || c== TAB);
|
||||
private static boolean IsWhite( char c) {
|
||||
return c ==' ' || c== TAB;
|
||||
}
|
||||
|
||||
/** Skip Over Leading White Space */
|
||||
@ -213,7 +241,13 @@ public final class FormulaParser {
|
||||
}
|
||||
GetChar();
|
||||
}
|
||||
|
||||
private String parseUnquotedIdentifier() {
|
||||
Identifier iden = parseIdentifier();
|
||||
if (iden.isQuoted()) {
|
||||
throw expected("unquoted identifier");
|
||||
}
|
||||
return iden.getName();
|
||||
}
|
||||
/**
|
||||
* Parses a sheet name, named range name, or simple cell reference.<br/>
|
||||
* Note - identifiers in Excel can contain dots, so this method may return a String
|
||||
@ -221,18 +255,17 @@ public final class FormulaParser {
|
||||
* may return a value like "A1..B2", in which case the caller must convert it to
|
||||
* an area reference like "A1:B2"
|
||||
*/
|
||||
private String parseIdentifier() {
|
||||
StringBuffer Token = new StringBuffer();
|
||||
if (!IsAlpha(look) && look != '\'') {
|
||||
private Identifier parseIdentifier() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (!IsAlpha(look) && look != '\'' && look != '[') {
|
||||
throw expected("Name");
|
||||
}
|
||||
if(look == '\'')
|
||||
{
|
||||
boolean isQuoted = look == '\'';
|
||||
if(isQuoted) {
|
||||
Match('\'');
|
||||
boolean done = look == '\'';
|
||||
while(!done)
|
||||
{
|
||||
Token.append(look);
|
||||
while(!done) {
|
||||
sb.append(look);
|
||||
GetChar();
|
||||
if(look == '\'')
|
||||
{
|
||||
@ -240,17 +273,15 @@ public final class FormulaParser {
|
||||
done = look != '\'';
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// allow for any sequence of dots and identifier chars
|
||||
// special case of two consecutive dots is best treated in the calling code
|
||||
while (IsAlNum(look) || look == '.') {
|
||||
Token.append(look);
|
||||
while (IsAlNum(look) || look == '.' || look == '[' || look == ']') {
|
||||
sb.append(look);
|
||||
GetChar();
|
||||
}
|
||||
}
|
||||
return Token.toString();
|
||||
return new Identifier(sb.toString(), isQuoted);
|
||||
}
|
||||
|
||||
/** Get a Number */
|
||||
@ -265,72 +296,112 @@ public final class FormulaParser {
|
||||
}
|
||||
|
||||
private ParseNode parseFunctionReferenceOrName() {
|
||||
String name = parseIdentifier();
|
||||
Identifier iden = parseIdentifier();
|
||||
if (look == '('){
|
||||
//This is a function
|
||||
return function(name);
|
||||
return function(iden.getName());
|
||||
}
|
||||
return new ParseNode(parseNameOrReference(name));
|
||||
if (!iden.isQuoted()) {
|
||||
String name = iden.getName();
|
||||
if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
|
||||
return new ParseNode(new BoolPtg(name.toUpperCase()));
|
||||
}
|
||||
}
|
||||
return parseRangeExpression(iden);
|
||||
}
|
||||
|
||||
private Ptg parseNameOrReference(String name) {
|
||||
private ParseNode parseRangeExpression(Identifier iden) {
|
||||
Ptg ptgA = parseNameOrCellRef(iden);
|
||||
if (look == ':') {
|
||||
GetChar();
|
||||
Identifier iden2 = parseIdentifier();
|
||||
Ptg ptgB = parseNameOrCellRef(iden2);
|
||||
Ptg simplified = reduceRangeExpression(ptgA, ptgB);
|
||||
|
||||
if (simplified == null) {
|
||||
ParseNode[] children = {
|
||||
new ParseNode(ptgA),
|
||||
new ParseNode(ptgB),
|
||||
};
|
||||
return new ParseNode(RangePtg.instance, children);
|
||||
}
|
||||
return new ParseNode(simplified);
|
||||
}
|
||||
return new ParseNode(ptgA);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* "A1", "B3" -> "A1:B3"
|
||||
* "sheet1!A1", "B3" -> "sheet1!A1:B3"
|
||||
*
|
||||
* @return <code>null</code> if the range expression cannot / shouldn't be reduced.
|
||||
*/
|
||||
private static Ptg reduceRangeExpression(Ptg ptgA, Ptg ptgB) {
|
||||
if (!(ptgB instanceof RefPtg)) {
|
||||
// only when second ref is simple 2-D ref can the range
|
||||
// expression be converted to an area ref
|
||||
return null;
|
||||
}
|
||||
RefPtg refB = (RefPtg) ptgB;
|
||||
|
||||
if (ptgA instanceof RefPtg) {
|
||||
RefPtg refA = (RefPtg) ptgA;
|
||||
return new AreaPtg(refA.getRow(), refB.getRow(), refA.getColumn(), refB.getColumn(),
|
||||
refA.isRowRelative(), refB.isRowRelative(), refA.isColRelative(), refB.isColRelative());
|
||||
}
|
||||
if (ptgA instanceof Ref3DPtg) {
|
||||
Ref3DPtg refA = (Ref3DPtg) ptgA;
|
||||
return new Area3DPtg(refA.getRow(), refB.getRow(), refA.getColumn(), refB.getColumn(),
|
||||
refA.isRowRelative(), refB.isRowRelative(), refA.isColRelative(), refB.isColRelative(),
|
||||
refA.getExternSheetIndex());
|
||||
}
|
||||
// Note - other operand types (like AreaPtg) which probably can't evaluate
|
||||
// do not cause validation errors at parse time
|
||||
return null;
|
||||
}
|
||||
|
||||
private Ptg parseNameOrCellRef(Identifier iden) {
|
||||
|
||||
if (look == '!') {
|
||||
GetChar();
|
||||
// 3-D ref
|
||||
// this code assumes iden is a sheetName
|
||||
// TODO - handle <book name> ! <named range name>
|
||||
int externIdx = getExternalSheetIndex(iden.getName());
|
||||
String secondIden = parseUnquotedIdentifier();
|
||||
AreaReference areaRef = parseArea(secondIden);
|
||||
if (areaRef == null) {
|
||||
return new Ref3DPtg(secondIden, externIdx);
|
||||
}
|
||||
// will happen if dots are used instead of colon
|
||||
return new Area3DPtg(areaRef.formatAsString(), externIdx);
|
||||
}
|
||||
|
||||
String name = iden.getName();
|
||||
AreaReference areaRef = parseArea(name);
|
||||
if (areaRef != null) {
|
||||
// will happen if dots are used instead of colon
|
||||
return new AreaPtg(areaRef.formatAsString());
|
||||
}
|
||||
|
||||
if (look == ':' || look == '.') { // this is a AreaReference
|
||||
GetChar();
|
||||
|
||||
while (look == '.') { // formulas can have . or .. or ... instead of :
|
||||
GetChar();
|
||||
}
|
||||
|
||||
String first = name;
|
||||
String second = parseIdentifier();
|
||||
return new AreaPtg(first+":"+second);
|
||||
}
|
||||
|
||||
if (look == '!') {
|
||||
Match('!');
|
||||
String sheetName = name;
|
||||
String first = parseIdentifier();
|
||||
int externIdx = book.getExternalSheetIndex(sheetName);
|
||||
areaRef = parseArea(name);
|
||||
if (areaRef != null) {
|
||||
// will happen if dots are used instead of colon
|
||||
return new Area3DPtg(areaRef.formatAsString(), externIdx);
|
||||
}
|
||||
if (look == ':') {
|
||||
Match(':');
|
||||
String second=parseIdentifier();
|
||||
if (look == '!') {
|
||||
//The sheet name was included in both of the areas. Only really
|
||||
//need it once
|
||||
Match('!');
|
||||
String third=parseIdentifier();
|
||||
|
||||
if (!sheetName.equals(second))
|
||||
throw new RuntimeException("Unhandled double sheet reference.");
|
||||
|
||||
return new Area3DPtg(first+":"+third,externIdx);
|
||||
}
|
||||
return new Area3DPtg(first+":"+second,externIdx);
|
||||
}
|
||||
return new Ref3DPtg(first, externIdx);
|
||||
}
|
||||
if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
|
||||
return new BoolPtg(name.toUpperCase());
|
||||
}
|
||||
|
||||
// This can be either a cell ref or a named range
|
||||
// Try to spot which it is
|
||||
|
||||
|
||||
int nameType = CellReference.classifyCellReference(name);
|
||||
if (nameType == NameType.CELL) {
|
||||
return new RefPtg(name);
|
||||
}
|
||||
if (look == ':') {
|
||||
if (nameType == NameType.COLUMN) {
|
||||
GetChar();
|
||||
String secondIden = parseUnquotedIdentifier();
|
||||
if (CellReference.classifyCellReference(secondIden) != NameType.COLUMN) {
|
||||
throw new FormulaParseException("Expected full column after '" + name
|
||||
+ ":' but got '" + secondIden + "'");
|
||||
}
|
||||
return new AreaPtg(name + ":" + secondIden);
|
||||
}
|
||||
}
|
||||
if (nameType != NameType.NAMED_RANGE) {
|
||||
new FormulaParseException("Name '" + name
|
||||
+ "' does not look like a cell reference or named range");
|
||||
@ -347,6 +418,17 @@ public final class FormulaParser {
|
||||
+ name + "' is not a range as expected");
|
||||
}
|
||||
|
||||
private int getExternalSheetIndex(String name) {
|
||||
if (name.charAt(0) == '[') {
|
||||
// we have a sheet name qualified with workbook name e.g. '[MyData.xls]Sheet1'
|
||||
int pos = name.lastIndexOf(']'); // safe because sheet names never have ']'
|
||||
String wbName = name.substring(1, pos);
|
||||
String sheetName = name.substring(pos+1);
|
||||
return book.getExternalSheetIndex(wbName, sheetName);
|
||||
}
|
||||
return book.getExternalSheetIndex(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name an 'identifier' like string (i.e. contains alphanums, and dots)
|
||||
* @return <code>null</code> if name cannot be split at a dot
|
||||
@ -585,7 +667,7 @@ public final class FormulaParser {
|
||||
Match('}');
|
||||
return arrayNode;
|
||||
}
|
||||
if (IsAlpha(look) || look == '\''){
|
||||
if (IsAlpha(look) || look == '\'' || look == '['){
|
||||
return parseFunctionReferenceOrName();
|
||||
}
|
||||
// else - assume number
|
||||
@ -662,7 +744,7 @@ public final class FormulaParser {
|
||||
}
|
||||
|
||||
private Boolean parseBooleanLiteral() {
|
||||
String iden = parseIdentifier();
|
||||
String iden = parseUnquotedIdentifier();
|
||||
if ("TRUE".equalsIgnoreCase(iden)) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
@ -720,7 +802,7 @@ public final class FormulaParser {
|
||||
|
||||
private int parseErrorLiteral() {
|
||||
Match('#');
|
||||
String part1 = parseIdentifier().toUpperCase();
|
||||
String part1 = parseUnquotedIdentifier().toUpperCase();
|
||||
|
||||
switch(part1.charAt(0)) {
|
||||
case 'V':
|
||||
|
@ -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);
|
||||
|
@ -19,6 +19,7 @@ package org.apache.poi.ss.formula;
|
||||
|
||||
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
|
||||
import org.apache.poi.hssf.record.formula.ControlPtg;
|
||||
import org.apache.poi.hssf.record.formula.RangePtg;
|
||||
import org.apache.poi.hssf.record.formula.ValueOperatorPtg;
|
||||
import org.apache.poi.hssf.record.formula.Ptg;
|
||||
|
||||
@ -115,6 +116,10 @@ final class OperandClassTransformer {
|
||||
return;
|
||||
}
|
||||
if (children.length > 0) {
|
||||
if (token == RangePtg.instance) {
|
||||
// TODO is any token transformation required under the various ref operators?
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException("Node should not have any children");
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ import org.apache.poi.hssf.record.formula.OperationPtg;
|
||||
import org.apache.poi.hssf.record.formula.PercentPtg;
|
||||
import org.apache.poi.hssf.record.formula.PowerPtg;
|
||||
import org.apache.poi.hssf.record.formula.Ptg;
|
||||
import org.apache.poi.hssf.record.formula.RangePtg;
|
||||
import org.apache.poi.hssf.record.formula.SubtractPtg;
|
||||
import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
|
||||
import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
|
||||
@ -57,6 +58,7 @@ import org.apache.poi.hssf.record.formula.eval.NotEqualEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.OperationEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.PercentEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.PowerEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.RangeEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.SubtractEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval;
|
||||
@ -101,6 +103,7 @@ final class OperationEvaluatorFactory {
|
||||
add(m, SubtractPtg.class, SubtractEval.instance);
|
||||
add(m, UnaryMinusPtg.class, UnaryMinusEval.instance);
|
||||
add(m, UnaryPlusPtg.class, UnaryPlusEval.instance);
|
||||
add(m, RangePtg.class, RangeEval.instance);
|
||||
return m;
|
||||
}
|
||||
|
||||
|
@ -22,12 +22,18 @@ import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.apache.poi.hssf.record.formula.Area3DPtg;
|
||||
import org.apache.poi.hssf.record.formula.AreaErrPtg;
|
||||
import org.apache.poi.hssf.record.formula.AreaPtg;
|
||||
import org.apache.poi.hssf.record.formula.AttrPtg;
|
||||
import org.apache.poi.hssf.record.formula.BoolPtg;
|
||||
import org.apache.poi.hssf.record.formula.ControlPtg;
|
||||
import org.apache.poi.hssf.record.formula.DeletedArea3DPtg;
|
||||
import org.apache.poi.hssf.record.formula.DeletedRef3DPtg;
|
||||
import org.apache.poi.hssf.record.formula.ErrPtg;
|
||||
import org.apache.poi.hssf.record.formula.FuncVarPtg;
|
||||
import org.apache.poi.hssf.record.formula.IntPtg;
|
||||
import org.apache.poi.hssf.record.formula.MemErrPtg;
|
||||
import org.apache.poi.hssf.record.formula.MemFuncPtg;
|
||||
import org.apache.poi.hssf.record.formula.MissingArgPtg;
|
||||
import org.apache.poi.hssf.record.formula.NamePtg;
|
||||
import org.apache.poi.hssf.record.formula.NameXPtg;
|
||||
@ -35,6 +41,7 @@ import org.apache.poi.hssf.record.formula.NumberPtg;
|
||||
import org.apache.poi.hssf.record.formula.OperationPtg;
|
||||
import org.apache.poi.hssf.record.formula.Ptg;
|
||||
import org.apache.poi.hssf.record.formula.Ref3DPtg;
|
||||
import org.apache.poi.hssf.record.formula.RefErrorPtg;
|
||||
import org.apache.poi.hssf.record.formula.RefPtg;
|
||||
import org.apache.poi.hssf.record.formula.StringPtg;
|
||||
import org.apache.poi.hssf.record.formula.UnionPtg;
|
||||
@ -53,6 +60,7 @@ import org.apache.poi.hssf.record.formula.eval.RefEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||
import org.apache.poi.hssf.util.CellReference;
|
||||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
@ -68,13 +76,14 @@ import org.apache.poi.ss.usermodel.Sheet;
|
||||
*
|
||||
* @author Josh Micich
|
||||
*/
|
||||
public class WorkbookEvaluator {
|
||||
public final class WorkbookEvaluator {
|
||||
|
||||
private final EvaluationWorkbook _workbook;
|
||||
private final EvaluationCache _cache;
|
||||
private EvaluationCache _cache;
|
||||
|
||||
private final IEvaluationListener _evaluationListener;
|
||||
private final Map _sheetIndexesBySheet;
|
||||
private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
|
||||
|
||||
public WorkbookEvaluator(EvaluationWorkbook workbook) {
|
||||
this (workbook, null);
|
||||
@ -84,6 +93,7 @@ public class WorkbookEvaluator {
|
||||
_evaluationListener = evaluationListener;
|
||||
_cache = new EvaluationCache(evaluationListener);
|
||||
_sheetIndexesBySheet = new IdentityHashMap();
|
||||
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,7 +111,22 @@ public class WorkbookEvaluator {
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
/* package */ void attachToEnvironment(CollaboratingWorkbooksEnvironment collaboratingWorkbooksEnvironment, EvaluationCache cache) {
|
||||
_collaboratingWorkbookEnvironment = collaboratingWorkbooksEnvironment;
|
||||
_cache = cache;
|
||||
}
|
||||
/* package */ CollaboratingWorkbooksEnvironment getEnvironment() {
|
||||
return _collaboratingWorkbookEnvironment;
|
||||
}
|
||||
|
||||
/* package */ void detachFromEnvironment() {
|
||||
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
|
||||
_cache = new EvaluationCache(_evaluationListener);
|
||||
}
|
||||
/* package */ IEvaluationListener getEvaluationListener() {
|
||||
return _evaluationListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called whenever there are changes to input cells in the evaluated workbook.
|
||||
* Failure to call this method after changing cell values will cause incorrect behaviour
|
||||
@ -123,7 +148,7 @@ public class WorkbookEvaluator {
|
||||
throw new IllegalArgumentException("value must not be null");
|
||||
}
|
||||
int sheetIndex = getSheetIndex(sheet);
|
||||
_cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
|
||||
_cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
|
||||
|
||||
}
|
||||
/**
|
||||
@ -132,13 +157,17 @@ public class WorkbookEvaluator {
|
||||
*/
|
||||
public void notifySetFormula(Sheet sheet, int rowIndex, int columnIndex) {
|
||||
int sheetIndex = getSheetIndex(sheet);
|
||||
_cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
|
||||
_cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
|
||||
|
||||
}
|
||||
private int getSheetIndex(Sheet sheet) {
|
||||
Integer result = (Integer) _sheetIndexesBySheet.get(sheet);
|
||||
if (result == null) {
|
||||
result = new Integer(_workbook.getSheetIndex(sheet));
|
||||
int sheetIndex = _workbook.getSheetIndex(sheet);
|
||||
if (sheetIndex < 0) {
|
||||
throw new RuntimeException("Specified sheet from a different book");
|
||||
}
|
||||
result = new Integer(sheetIndex);
|
||||
_sheetIndexesBySheet.put(sheet, result);
|
||||
}
|
||||
return result.intValue();
|
||||
@ -146,7 +175,7 @@ public class WorkbookEvaluator {
|
||||
|
||||
public ValueEval evaluate(Cell srcCell) {
|
||||
int sheetIndex = getSheetIndex(srcCell.getSheet());
|
||||
CellLocation cellLoc = new CellLocation(sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
|
||||
CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
|
||||
return internalEvaluate(srcCell, cellLoc, new EvaluationTracker(_cache));
|
||||
}
|
||||
|
||||
@ -181,10 +210,10 @@ public class WorkbookEvaluator {
|
||||
isPlainFormulaCell = false;
|
||||
Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
|
||||
if(evalListener == null) {
|
||||
result = evaluateCell(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
|
||||
result = evaluateFormula(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
|
||||
} else {
|
||||
evalListener.onStartEvaluate(sheetIndex, rowIndex, columnIndex, ptgs);
|
||||
result = evaluateCell(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
|
||||
result = evaluateFormula(sheetIndex, rowIndex, (short)columnIndex, ptgs, tracker);
|
||||
evalListener.onEndEvaluate(sheetIndex, rowIndex, columnIndex, result);
|
||||
}
|
||||
}
|
||||
@ -225,17 +254,31 @@ public class WorkbookEvaluator {
|
||||
}
|
||||
throw new RuntimeException("Unexpected cell type (" + cellType + ")");
|
||||
}
|
||||
private ValueEval evaluateCell(int sheetIndex, int srcRowNum, short srcColNum, Ptg[] ptgs, EvaluationTracker tracker) {
|
||||
// visibility raised for testing
|
||||
/* package */ ValueEval evaluateFormula(int sheetIndex, int srcRowNum, int srcColNum, Ptg[] ptgs, EvaluationTracker tracker) {
|
||||
|
||||
Stack stack = new Stack();
|
||||
for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
|
||||
|
||||
// since we don't know how to handle these yet :(
|
||||
Ptg ptg = ptgs[i];
|
||||
if (ptg instanceof AttrPtg) {
|
||||
AttrPtg attrPtg = (AttrPtg) ptg;
|
||||
if (attrPtg.isSum()) {
|
||||
// Excel prefers to encode 'SUM()' as a tAttr token, but this evaluator
|
||||
// expects the equivalent function token
|
||||
byte nArgs = 1; // tAttrSum always has 1 parameter
|
||||
ptg = new FuncVarPtg("SUM", nArgs);
|
||||
}
|
||||
}
|
||||
if (ptg instanceof ControlPtg) {
|
||||
// skip Parentheses, Attr, etc
|
||||
continue;
|
||||
}
|
||||
if (ptg instanceof MemFuncPtg) {
|
||||
// can ignore, rest of tokens for this expression are in OK RPN order
|
||||
continue;
|
||||
}
|
||||
if (ptg instanceof MemErrPtg) { continue; }
|
||||
if (ptg instanceof MissingArgPtg) {
|
||||
// TODO - might need to push BlankEval or MissingArgEval
|
||||
@ -289,7 +332,7 @@ public class WorkbookEvaluator {
|
||||
* @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>,
|
||||
* <tt>BlankEval</tt> or <tt>ErrorEval</tt>. Never <code>null</code>.
|
||||
*/
|
||||
private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) {
|
||||
private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, int srcColNum) {
|
||||
if (evaluationResult instanceof RefEval) {
|
||||
RefEval rv = (RefEval) evaluationResult;
|
||||
return rv.getInnerValueEval();
|
||||
@ -321,6 +364,20 @@ public class WorkbookEvaluator {
|
||||
}
|
||||
return operation.evaluate(ops, srcRowNum, (short)srcColNum);
|
||||
}
|
||||
private SheetRefEvaluator createExternSheetRefEvaluator(EvaluationTracker tracker,
|
||||
ExternSheetReferenceToken ptg) {
|
||||
int externSheetIndex = ptg.getExternSheetIndex();
|
||||
ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
|
||||
if (externalSheet != null) {
|
||||
WorkbookEvaluator otherEvaluator = _collaboratingWorkbookEnvironment.getWorkbookEvaluator(externalSheet.getWorkbookName());
|
||||
EvaluationWorkbook otherBook = otherEvaluator._workbook;
|
||||
int otherSheetIndex = otherBook.getSheetIndex(externalSheet.getSheetName());
|
||||
return new SheetRefEvaluator(otherEvaluator, tracker, otherBook, otherSheetIndex);
|
||||
}
|
||||
int otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex);
|
||||
return new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an appropriate Eval impl instance for the Ptg. The Ptg must be
|
||||
@ -329,6 +386,8 @@ public class WorkbookEvaluator {
|
||||
* passed here!
|
||||
*/
|
||||
private Eval getEvalForPtg(Ptg ptg, int sheetIndex, EvaluationTracker tracker) {
|
||||
// consider converting all these (ptg instanceof XxxPtg) expressions to (ptg.getClass() == XxxPtg.class)
|
||||
|
||||
if (ptg instanceof NamePtg) {
|
||||
// named ranges, macro functions
|
||||
NamePtg namePtg = (NamePtg) ptg;
|
||||
@ -361,16 +420,18 @@ public class WorkbookEvaluator {
|
||||
if (ptg instanceof ErrPtg) {
|
||||
return ErrorEval.valueOf(((ErrPtg) ptg).getErrorCode());
|
||||
}
|
||||
if (ptg instanceof AreaErrPtg ||ptg instanceof RefErrorPtg
|
||||
|| ptg instanceof DeletedArea3DPtg || ptg instanceof DeletedRef3DPtg) {
|
||||
return ErrorEval.REF_INVALID;
|
||||
}
|
||||
if (ptg instanceof Ref3DPtg) {
|
||||
Ref3DPtg refPtg = (Ref3DPtg) ptg;
|
||||
int otherSheetIndex = _workbook.convertFromExternSheetIndex(refPtg.getExternSheetIndex());
|
||||
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
|
||||
SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, refPtg);
|
||||
return new LazyRefEval(refPtg, sre);
|
||||
}
|
||||
if (ptg instanceof Area3DPtg) {
|
||||
Area3DPtg aptg = (Area3DPtg) ptg;
|
||||
int otherSheetIndex = _workbook.convertFromExternSheetIndex(aptg.getExternSheetIndex());
|
||||
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
|
||||
SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, aptg);
|
||||
return new LazyAreaEval(aptg, sre);
|
||||
}
|
||||
SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, sheetIndex);
|
||||
@ -410,7 +471,7 @@ public class WorkbookEvaluator {
|
||||
} else {
|
||||
cell = row.getCell(columnIndex);
|
||||
}
|
||||
CellLocation cellLoc = new CellLocation(sheetIndex, rowIndex, columnIndex);
|
||||
CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex);
|
||||
tracker.acceptDependency(cellLoc);
|
||||
return internalEvaluate(cell, cellLoc, tracker);
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ public class AreaReference {
|
||||
for(int i=refPart.length()-1; i>=0; i--) {
|
||||
int ch = refPart.charAt(i);
|
||||
if (ch == '$' && i==0) {
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
if (ch < 'A' || ch > 'Z') {
|
||||
return false;
|
||||
@ -103,8 +103,45 @@ public class AreaReference {
|
||||
* Creates an area ref from a pair of Cell References.
|
||||
*/
|
||||
public AreaReference(CellReference topLeft, CellReference botRight) {
|
||||
_firstCell = topLeft;
|
||||
_lastCell = botRight;
|
||||
boolean swapRows = topLeft.getRow() > botRight.getRow();
|
||||
boolean swapCols = topLeft.getCol() > botRight.getCol();
|
||||
if (swapRows || swapCols) {
|
||||
int firstRow;
|
||||
int lastRow;
|
||||
int firstColumn;
|
||||
int lastColumn;
|
||||
boolean firstRowAbs;
|
||||
boolean lastRowAbs;
|
||||
boolean firstColAbs;
|
||||
boolean lastColAbs;
|
||||
if (swapRows) {
|
||||
firstRow = botRight.getRow();
|
||||
firstRowAbs = botRight.isRowAbsolute();
|
||||
lastRow = topLeft.getRow();
|
||||
lastRowAbs = topLeft.isRowAbsolute();
|
||||
} else {
|
||||
firstRow = topLeft.getRow();
|
||||
firstRowAbs = topLeft.isRowAbsolute();
|
||||
lastRow = botRight.getRow();
|
||||
lastRowAbs = botRight.isRowAbsolute();
|
||||
}
|
||||
if (swapCols) {
|
||||
firstColumn = botRight.getCol();
|
||||
firstColAbs = botRight.isColAbsolute();
|
||||
lastColumn = topLeft.getCol();
|
||||
lastColAbs = topLeft.isColAbsolute();
|
||||
} else {
|
||||
firstColumn = topLeft.getCol();
|
||||
firstColAbs = topLeft.isColAbsolute();
|
||||
lastColumn = botRight.getCol();
|
||||
lastColAbs = botRight.isColAbsolute();
|
||||
}
|
||||
_firstCell = new CellReference(firstRow, firstColumn, firstRowAbs, firstColAbs);
|
||||
_lastCell = new CellReference(lastRow, lastColumn, lastRowAbs, lastColAbs);
|
||||
} else {
|
||||
_firstCell = topLeft;
|
||||
_lastCell = botRight;
|
||||
}
|
||||
_isSingleCell = false;
|
||||
}
|
||||
|
||||
|
@ -34,21 +34,28 @@ public class CellReference {
|
||||
public static final class NameType {
|
||||
public static final int CELL = 1;
|
||||
public static final int NAMED_RANGE = 2;
|
||||
public static final int COLUMN = 3;
|
||||
public static final int BAD_CELL_OR_NAMED_RANGE = -1;
|
||||
}
|
||||
|
||||
/** The character ($) that signifies a row or column value is absolute instead of relative */
|
||||
private static final char ABSOLUTE_REFERENCE_MARKER = '$';
|
||||
/** The character (!) that separates sheet names from cell references */
|
||||
private static final char SHEET_NAME_DELIMITER = '!';
|
||||
/** The character (') used to quote sheet names when they contain special characters */
|
||||
private static final char SPECIAL_NAME_DELIMITER = '\'';
|
||||
|
||||
/**
|
||||
* Matches a run of letters followed by a run of digits. The run of letters is group 1 and the
|
||||
* run of digits is group 2. Each group may optionally be prefixed with a single '$'.
|
||||
*/
|
||||
/** The character ($) that signifies a row or column value is absolute instead of relative */
|
||||
private static final char ABSOLUTE_REFERENCE_MARKER = '$';
|
||||
/** The character (!) that separates sheet names from cell references */
|
||||
private static final char SHEET_NAME_DELIMITER = '!';
|
||||
/** The character (') used to quote sheet names when they contain special characters */
|
||||
private static final char SPECIAL_NAME_DELIMITER = '\'';
|
||||
|
||||
/**
|
||||
* Matches a run of one or more letters followed by a run of one or more digits.
|
||||
* The run of letters is group 1 and the run of digits is group 2.
|
||||
* Each group may optionally be prefixed with a single '$'.
|
||||
*/
|
||||
private static final Pattern CELL_REF_PATTERN = Pattern.compile("\\$?([A-Za-z]+)\\$?([0-9]+)");
|
||||
/**
|
||||
* Matches a run of one or more letters. The run of letters is group 1.
|
||||
* The text may optionally be prefixed with a single '$'.
|
||||
*/
|
||||
private static final Pattern COLUMN_REF_PATTERN = Pattern.compile("\\$?([A-Za-z]+)");
|
||||
/**
|
||||
* Named range names must start with a letter or underscore. Subsequent characters may include
|
||||
* digits or dot. (They can even end in dot).
|
||||
@ -65,96 +72,96 @@ public class CellReference {
|
||||
private final boolean _isRowAbs;
|
||||
private final boolean _isColAbs;
|
||||
|
||||
/**
|
||||
* Create an cell ref from a string representation. Sheet names containing special characters should be
|
||||
* delimited and escaped as per normal syntax rules for formulas.
|
||||
*/
|
||||
public CellReference(String cellRef) {
|
||||
String[] parts = separateRefParts(cellRef);
|
||||
_sheetName = parts[0];
|
||||
String colRef = parts[1];
|
||||
if (colRef.length() < 1) {
|
||||
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
|
||||
}
|
||||
_isColAbs = colRef.charAt(0) == '$';
|
||||
if (_isColAbs) {
|
||||
colRef=colRef.substring(1);
|
||||
}
|
||||
_colIndex = convertColStringToIndex(colRef);
|
||||
|
||||
String rowRef=parts[2];
|
||||
if (rowRef.length() < 1) {
|
||||
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
|
||||
}
|
||||
_isRowAbs = rowRef.charAt(0) == '$';
|
||||
if (_isRowAbs) {
|
||||
rowRef=rowRef.substring(1);
|
||||
}
|
||||
_rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based
|
||||
}
|
||||
/**
|
||||
* Create an cell ref from a string representation. Sheet names containing special characters should be
|
||||
* delimited and escaped as per normal syntax rules for formulas.
|
||||
*/
|
||||
public CellReference(String cellRef) {
|
||||
String[] parts = separateRefParts(cellRef);
|
||||
_sheetName = parts[0];
|
||||
String colRef = parts[1];
|
||||
if (colRef.length() < 1) {
|
||||
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
|
||||
}
|
||||
_isColAbs = colRef.charAt(0) == '$';
|
||||
if (_isColAbs) {
|
||||
colRef=colRef.substring(1);
|
||||
}
|
||||
_colIndex = convertColStringToIndex(colRef);
|
||||
|
||||
String rowRef=parts[2];
|
||||
if (rowRef.length() < 1) {
|
||||
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
|
||||
}
|
||||
_isRowAbs = rowRef.charAt(0) == '$';
|
||||
if (_isRowAbs) {
|
||||
rowRef=rowRef.substring(1);
|
||||
}
|
||||
_rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based
|
||||
}
|
||||
|
||||
public CellReference(int pRow, int pCol) {
|
||||
this(pRow, pCol & 0xFFFF, false, false);
|
||||
}
|
||||
public CellReference(int pRow, short pCol) {
|
||||
this(pRow, (int)pCol, false, false);
|
||||
}
|
||||
public CellReference(int pRow, int pCol) {
|
||||
this(pRow, pCol, false, false);
|
||||
}
|
||||
public CellReference(int pRow, short pCol) {
|
||||
this(pRow, pCol & 0xFFFF, false, false);
|
||||
}
|
||||
|
||||
public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
|
||||
this(null, pRow, pCol, pAbsRow, pAbsCol);
|
||||
}
|
||||
public CellReference(String pSheetName, int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
|
||||
// TODO - "-1" is a special value being temporarily used for whole row and whole column area references.
|
||||
// so these checks are currently N.Q.R.
|
||||
if(pRow < -1) {
|
||||
throw new IllegalArgumentException("row index may not be negative");
|
||||
}
|
||||
if(pCol < -1) {
|
||||
throw new IllegalArgumentException("column index may not be negative");
|
||||
}
|
||||
_sheetName = pSheetName;
|
||||
_rowIndex=pRow;
|
||||
_colIndex=pCol;
|
||||
_isRowAbs = pAbsRow;
|
||||
_isColAbs=pAbsCol;
|
||||
}
|
||||
public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
|
||||
this(null, pRow, pCol, pAbsRow, pAbsCol);
|
||||
}
|
||||
public CellReference(String pSheetName, int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
|
||||
// TODO - "-1" is a special value being temporarily used for whole row and whole column area references.
|
||||
// so these checks are currently N.Q.R.
|
||||
if(pRow < -1) {
|
||||
throw new IllegalArgumentException("row index may not be negative");
|
||||
}
|
||||
if(pCol < -1) {
|
||||
throw new IllegalArgumentException("column index may not be negative");
|
||||
}
|
||||
_sheetName = pSheetName;
|
||||
_rowIndex=pRow;
|
||||
_colIndex=pCol;
|
||||
_isRowAbs = pAbsRow;
|
||||
_isColAbs=pAbsCol;
|
||||
}
|
||||
|
||||
public int getRow(){return _rowIndex;}
|
||||
public short getCol(){return (short) _colIndex;}
|
||||
public boolean isRowAbsolute(){return _isRowAbs;}
|
||||
public boolean isColAbsolute(){return _isColAbs;}
|
||||
/**
|
||||
* @return possibly <code>null</code> if this is a 2D reference. Special characters are not
|
||||
* escaped or delimited
|
||||
*/
|
||||
public String getSheetName(){
|
||||
return _sheetName;
|
||||
}
|
||||
|
||||
public static boolean isPartAbsolute(String part) {
|
||||
return part.charAt(0) == ABSOLUTE_REFERENCE_MARKER;
|
||||
}
|
||||
|
||||
/**
|
||||
* takes in a column reference portion of a CellRef and converts it from
|
||||
* ALPHA-26 number format to 0-based base 10.
|
||||
* 'A' -> 0
|
||||
* 'Z' -> 25
|
||||
* 'AA' -> 26
|
||||
* 'IV' -> 255
|
||||
* @return zero based column index
|
||||
*/
|
||||
protected static int convertColStringToIndex(String ref) {
|
||||
int pos = 0;
|
||||
int retval=0;
|
||||
public int getRow(){return _rowIndex;}
|
||||
public short getCol(){return (short) _colIndex;}
|
||||
public boolean isRowAbsolute(){return _isRowAbs;}
|
||||
public boolean isColAbsolute(){return _isColAbs;}
|
||||
/**
|
||||
* @return possibly <code>null</code> if this is a 2D reference. Special characters are not
|
||||
* escaped or delimited
|
||||
*/
|
||||
public String getSheetName(){
|
||||
return _sheetName;
|
||||
}
|
||||
|
||||
public static boolean isPartAbsolute(String part) {
|
||||
return part.charAt(0) == ABSOLUTE_REFERENCE_MARKER;
|
||||
}
|
||||
/**
|
||||
* takes in a column reference portion of a CellRef and converts it from
|
||||
* ALPHA-26 number format to 0-based base 10.
|
||||
* 'A' -> 0
|
||||
* 'Z' -> 25
|
||||
* 'AA' -> 26
|
||||
* 'IV' -> 255
|
||||
* @return zero based column index
|
||||
*/
|
||||
public static int convertColStringToIndex(String ref) {
|
||||
|
||||
int pos = 0;
|
||||
int retval=0;
|
||||
for (int k = ref.length()-1; k >= 0; k--) {
|
||||
char thechar = ref.charAt(k);
|
||||
if (thechar == ABSOLUTE_REFERENCE_MARKER) {
|
||||
if (k != 0) {
|
||||
throw new IllegalArgumentException("Bad col ref format '" + ref + "'");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (thechar == ABSOLUTE_REFERENCE_MARKER) {
|
||||
if (k != 0) {
|
||||
throw new IllegalArgumentException("Bad col ref format '" + ref + "'");
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Character.getNumericValue() returns the values
|
||||
// 10-35 for the letter A-Z
|
||||
int shift = (int)Math.pow(26, pos);
|
||||
@ -162,64 +169,70 @@ public class CellReference {
|
||||
pos++;
|
||||
}
|
||||
return retval-1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies an identifier as either a simple (2D) cell reference or a named range name
|
||||
* @return one of the values from <tt>NameType</tt>
|
||||
*/
|
||||
public static int classifyCellReference(String str) {
|
||||
int len = str.length();
|
||||
if (len < 1) {
|
||||
throw new IllegalArgumentException("Empty string not allowed");
|
||||
}
|
||||
char firstChar = str.charAt(0);
|
||||
switch (firstChar) {
|
||||
case ABSOLUTE_REFERENCE_MARKER:
|
||||
case '.':
|
||||
case '_':
|
||||
break;
|
||||
default:
|
||||
if (!Character.isLetter(firstChar)) {
|
||||
throw new IllegalArgumentException("Invalid first char (" + firstChar
|
||||
+ ") of cell reference or named range. Letter expected");
|
||||
}
|
||||
}
|
||||
if (!Character.isDigit(str.charAt(len-1))) {
|
||||
// no digits at end of str
|
||||
return validateNamedRangeName(str);
|
||||
}
|
||||
Matcher cellRefPatternMatcher = CELL_REF_PATTERN.matcher(str);
|
||||
if (!cellRefPatternMatcher.matches()) {
|
||||
return validateNamedRangeName(str);
|
||||
}
|
||||
String lettersGroup = cellRefPatternMatcher.group(1);
|
||||
String digitsGroup = cellRefPatternMatcher.group(2);
|
||||
if (cellReferenceIsWithinRange(lettersGroup, digitsGroup)) {
|
||||
// valid cell reference
|
||||
return NameType.CELL;
|
||||
}
|
||||
// If str looks like a cell reference, but is out of (row/col) range, it is a valid
|
||||
// named range name
|
||||
// This behaviour is a little weird. For example, "IW123" is a valid named range name
|
||||
// because the column "IW" is beyond the maximum "IV". Note - this behaviour is version
|
||||
// dependent. In Excel 2007, "IW123" is not a valid named range name.
|
||||
if (str.indexOf(ABSOLUTE_REFERENCE_MARKER) >= 0) {
|
||||
// Of course, named range names cannot have '$'
|
||||
return NameType.BAD_CELL_OR_NAMED_RANGE;
|
||||
}
|
||||
return NameType.NAMED_RANGE;
|
||||
}
|
||||
/**
|
||||
* Classifies an identifier as either a simple (2D) cell reference or a named range name
|
||||
* @return one of the values from <tt>NameType</tt>
|
||||
*/
|
||||
public static int classifyCellReference(String str) {
|
||||
int len = str.length();
|
||||
if (len < 1) {
|
||||
throw new IllegalArgumentException("Empty string not allowed");
|
||||
}
|
||||
char firstChar = str.charAt(0);
|
||||
switch (firstChar) {
|
||||
case ABSOLUTE_REFERENCE_MARKER:
|
||||
case '.':
|
||||
case '_':
|
||||
break;
|
||||
default:
|
||||
if (!Character.isLetter(firstChar)) {
|
||||
throw new IllegalArgumentException("Invalid first char (" + firstChar
|
||||
+ ") of cell reference or named range. Letter expected");
|
||||
}
|
||||
}
|
||||
if (!Character.isDigit(str.charAt(len-1))) {
|
||||
// no digits at end of str
|
||||
return validateNamedRangeName(str);
|
||||
}
|
||||
Matcher cellRefPatternMatcher = CELL_REF_PATTERN.matcher(str);
|
||||
if (!cellRefPatternMatcher.matches()) {
|
||||
return validateNamedRangeName(str);
|
||||
}
|
||||
String lettersGroup = cellRefPatternMatcher.group(1);
|
||||
String digitsGroup = cellRefPatternMatcher.group(2);
|
||||
if (cellReferenceIsWithinRange(lettersGroup, digitsGroup)) {
|
||||
// valid cell reference
|
||||
return NameType.CELL;
|
||||
}
|
||||
// If str looks like a cell reference, but is out of (row/col) range, it is a valid
|
||||
// named range name
|
||||
// This behaviour is a little weird. For example, "IW123" is a valid named range name
|
||||
// because the column "IW" is beyond the maximum "IV". Note - this behaviour is version
|
||||
// dependent. In BIFF12, "IW123" is not a valid named range name, but in BIFF8 it is.
|
||||
if (str.indexOf(ABSOLUTE_REFERENCE_MARKER) >= 0) {
|
||||
// Of course, named range names cannot have '$'
|
||||
return NameType.BAD_CELL_OR_NAMED_RANGE;
|
||||
}
|
||||
return NameType.NAMED_RANGE;
|
||||
}
|
||||
|
||||
private static int validateNamedRangeName(String str) {
|
||||
private static int validateNamedRangeName(String str) {
|
||||
Matcher colMatcher = COLUMN_REF_PATTERN.matcher(str);
|
||||
if (colMatcher.matches()) {
|
||||
String colStr = colMatcher.group(1);
|
||||
if (isColumnWithnRange(colStr)) {
|
||||
return NameType.COLUMN;
|
||||
}
|
||||
}
|
||||
if (!NAMED_RANGE_NAME_PATTERN.matcher(str).matches()) {
|
||||
return NameType.BAD_CELL_OR_NAMED_RANGE;
|
||||
}
|
||||
return NameType.NAMED_RANGE;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Used to decide whether a name of the form "[A-Z]*[0-9]*" that appears in a formula can be
|
||||
* interpreted as a cell reference. Names of that form can be also used for sheets and/or
|
||||
@ -240,7 +253,7 @@ public class CellReference {
|
||||
* <blockquote><table border="0" cellpadding="1" cellspacing="0"
|
||||
* summary="Notable cases.">
|
||||
* <tr><th>Input </th>
|
||||
* <th>Result </th></tr>
|
||||
* <th>Result </th></tr>
|
||||
* <tr><td>"A", "1"</td><td>true</td></tr>
|
||||
* <tr><td>"a", "111"</td><td>true</td></tr>
|
||||
* <tr><td>"A", "65536"</td><td>true</td></tr>
|
||||
@ -257,23 +270,13 @@ public class CellReference {
|
||||
* @return <code>true</code> if the row and col parameters are within range of a BIFF8 spreadsheet.
|
||||
*/
|
||||
public static boolean cellReferenceIsWithinRange(String colStr, String rowStr) {
|
||||
int numberOfLetters = colStr.length();
|
||||
if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) {
|
||||
// "Sheet1" case etc
|
||||
return false; // that was easy
|
||||
if (!isColumnWithnRange(colStr)) {
|
||||
return false;
|
||||
}
|
||||
int nDigits = rowStr.length();
|
||||
if(nDigits > BIFF8_LAST_ROW_TEXT_LEN) {
|
||||
return false;
|
||||
}
|
||||
if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) {
|
||||
if(colStr.toUpperCase().compareTo(BIFF8_LAST_COLUMN) > 0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// apparent column name has less chars than max
|
||||
// no need to check range
|
||||
}
|
||||
|
||||
if(nDigits == BIFF8_LAST_ROW_TEXT_LEN) {
|
||||
// ASCII comparison is valid if digit count is same
|
||||
@ -288,87 +291,104 @@ public class CellReference {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isColumnWithnRange(String colStr) {
|
||||
int numberOfLetters = colStr.length();
|
||||
if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) {
|
||||
// "Sheet1" case etc
|
||||
return false; // that was easy
|
||||
}
|
||||
if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) {
|
||||
if(colStr.toUpperCase().compareTo(BIFF8_LAST_COLUMN) > 0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// apparent column name has less chars than max
|
||||
// no need to check range
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Separates the row from the columns and returns an array of three Strings. The first element
|
||||
* is the sheet name. Only the first element may be null. The second element in is the column
|
||||
* name still in ALPHA-26 number format. The third element is the row.
|
||||
*/
|
||||
private static String[] separateRefParts(String reference) {
|
||||
|
||||
int plingPos = reference.lastIndexOf(SHEET_NAME_DELIMITER);
|
||||
String sheetName = parseSheetName(reference, plingPos);
|
||||
int start = plingPos+1;
|
||||
* Separates the row from the columns and returns an array of three Strings. The first element
|
||||
* is the sheet name. Only the first element may be null. The second element in is the column
|
||||
* name still in ALPHA-26 number format. The third element is the row.
|
||||
*/
|
||||
private static String[] separateRefParts(String reference) {
|
||||
|
||||
int plingPos = reference.lastIndexOf(SHEET_NAME_DELIMITER);
|
||||
String sheetName = parseSheetName(reference, plingPos);
|
||||
int start = plingPos+1;
|
||||
|
||||
int length = reference.length();
|
||||
int length = reference.length();
|
||||
|
||||
|
||||
int loc = start;
|
||||
// skip initial dollars
|
||||
if (reference.charAt(loc)==ABSOLUTE_REFERENCE_MARKER) {
|
||||
loc++;
|
||||
}
|
||||
// step over column name chars until first digit (or dollars) for row number.
|
||||
for (; loc < length; loc++) {
|
||||
char ch = reference.charAt(loc);
|
||||
if (Character.isDigit(ch) || ch == ABSOLUTE_REFERENCE_MARKER) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new String[] {
|
||||
sheetName,
|
||||
reference.substring(start,loc),
|
||||
reference.substring(loc),
|
||||
};
|
||||
}
|
||||
int loc = start;
|
||||
// skip initial dollars
|
||||
if (reference.charAt(loc)==ABSOLUTE_REFERENCE_MARKER) {
|
||||
loc++;
|
||||
}
|
||||
// step over column name chars until first digit (or dollars) for row number.
|
||||
for (; loc < length; loc++) {
|
||||
char ch = reference.charAt(loc);
|
||||
if (Character.isDigit(ch) || ch == ABSOLUTE_REFERENCE_MARKER) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new String[] {
|
||||
sheetName,
|
||||
reference.substring(start,loc),
|
||||
reference.substring(loc),
|
||||
};
|
||||
}
|
||||
|
||||
private static String parseSheetName(String reference, int indexOfSheetNameDelimiter) {
|
||||
if(indexOfSheetNameDelimiter < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean isQuoted = reference.charAt(0) == SPECIAL_NAME_DELIMITER;
|
||||
if(!isQuoted) {
|
||||
return reference.substring(0, indexOfSheetNameDelimiter);
|
||||
}
|
||||
int lastQuotePos = indexOfSheetNameDelimiter-1;
|
||||
if(reference.charAt(lastQuotePos) != SPECIAL_NAME_DELIMITER) {
|
||||
throw new RuntimeException("Mismatched quotes: (" + reference + ")");
|
||||
}
|
||||
private static String parseSheetName(String reference, int indexOfSheetNameDelimiter) {
|
||||
if(indexOfSheetNameDelimiter < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean isQuoted = reference.charAt(0) == SPECIAL_NAME_DELIMITER;
|
||||
if(!isQuoted) {
|
||||
return reference.substring(0, indexOfSheetNameDelimiter);
|
||||
}
|
||||
int lastQuotePos = indexOfSheetNameDelimiter-1;
|
||||
if(reference.charAt(lastQuotePos) != SPECIAL_NAME_DELIMITER) {
|
||||
throw new RuntimeException("Mismatched quotes: (" + reference + ")");
|
||||
}
|
||||
|
||||
// TODO - refactor cell reference parsing logic to one place.
|
||||
// Current known incarnations:
|
||||
// FormulaParser.GetName()
|
||||
// CellReference.parseSheetName() (here)
|
||||
// AreaReference.separateAreaRefs()
|
||||
// SheetNameFormatter.format() (inverse)
|
||||
|
||||
StringBuffer sb = new StringBuffer(indexOfSheetNameDelimiter);
|
||||
|
||||
for(int i=1; i<lastQuotePos; i++) { // Note boundaries - skip outer quotes
|
||||
char ch = reference.charAt(i);
|
||||
if(ch != SPECIAL_NAME_DELIMITER) {
|
||||
sb.append(ch);
|
||||
continue;
|
||||
}
|
||||
if(i < lastQuotePos) {
|
||||
if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) {
|
||||
// two consecutive quotes is the escape sequence for a single one
|
||||
i++; // skip this and keep parsing the special name
|
||||
sb.append(ch);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Bad sheet name quote escaping: (" + reference + ")");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
// TODO - refactor cell reference parsing logic to one place.
|
||||
// Current known incarnations:
|
||||
// FormulaParser.GetName()
|
||||
// CellReference.parseSheetName() (here)
|
||||
// AreaReference.separateAreaRefs()
|
||||
// SheetNameFormatter.format() (inverse)
|
||||
|
||||
StringBuffer sb = new StringBuffer(indexOfSheetNameDelimiter);
|
||||
|
||||
for(int i=1; i<lastQuotePos; i++) { // Note boundaries - skip outer quotes
|
||||
char ch = reference.charAt(i);
|
||||
if(ch != SPECIAL_NAME_DELIMITER) {
|
||||
sb.append(ch);
|
||||
continue;
|
||||
}
|
||||
if(i < lastQuotePos) {
|
||||
if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) {
|
||||
// two consecutive quotes is the escape sequence for a single one
|
||||
i++; // skip this and keep parsing the special name
|
||||
sb.append(ch);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Bad sheet name quote escaping: (" + reference + ")");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in a 0-based base-10 column and returns a ALPHA-26
|
||||
* representation.
|
||||
* eg column #3 -> D
|
||||
*/
|
||||
protected static String convertNumToColString(int col) {
|
||||
/**
|
||||
* Takes in a 0-based base-10 column and returns a ALPHA-26
|
||||
* representation.
|
||||
* eg column #3 -> D
|
||||
*/
|
||||
protected static String convertNumToColString(int col) {
|
||||
// Excel counts column A as the 1st column, we
|
||||
// treat it as the 0th one
|
||||
int excelColNum = col + 1;
|
||||
@ -387,36 +407,36 @@ public class CellReference {
|
||||
}
|
||||
|
||||
return colRef;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example return values:
|
||||
* <table border="0" cellpadding="1" cellspacing="0" summary="Example return values">
|
||||
* <tr><th align='left'>Result</th><th align='left'>Comment</th></tr>
|
||||
* <tr><td>A1</td><td>Cell reference without sheet</td></tr>
|
||||
* <tr><td>Sheet1!A1</td><td>Standard sheet name</td></tr>
|
||||
* <tr><td>'O''Brien''s Sales'!A1' </td><td>Sheet name with special characters</td></tr>
|
||||
* </table>
|
||||
* @return the text representation of this cell reference as it would appear in a formula.
|
||||
*/
|
||||
public String formatAsString() {
|
||||
StringBuffer sb = new StringBuffer(32);
|
||||
if(_sheetName != null) {
|
||||
SheetNameFormatter.appendFormat(sb, _sheetName);
|
||||
sb.append(SHEET_NAME_DELIMITER);
|
||||
}
|
||||
appendCellReference(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer(64);
|
||||
sb.append(getClass().getName()).append(" [");
|
||||
sb.append(formatAsString());
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Example return values:
|
||||
* <table border="0" cellpadding="1" cellspacing="0" summary="Example return values">
|
||||
* <tr><th align='left'>Result</th><th align='left'>Comment</th></tr>
|
||||
* <tr><td>A1</td><td>Cell reference without sheet</td></tr>
|
||||
* <tr><td>Sheet1!A1</td><td>Standard sheet name</td></tr>
|
||||
* <tr><td>'O''Brien''s Sales'!A1' </td><td>Sheet name with special characters</td></tr>
|
||||
* </table>
|
||||
* @return the text representation of this cell reference as it would appear in a formula.
|
||||
*/
|
||||
public String formatAsString() {
|
||||
StringBuffer sb = new StringBuffer(32);
|
||||
if(_sheetName != null) {
|
||||
SheetNameFormatter.appendFormat(sb, _sheetName);
|
||||
sb.append(SHEET_NAME_DELIMITER);
|
||||
}
|
||||
appendCellReference(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer(64);
|
||||
sb.append(getClass().getName()).append(" [");
|
||||
sb.append(formatAsString());
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the three parts of the cell reference, the
|
||||
* Sheet name (or null if none supplied), the 1 based
|
||||
@ -433,18 +453,18 @@ public class CellReference {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends cell reference with '$' markers for absolute values as required.
|
||||
* Sheet name is not included.
|
||||
*/
|
||||
protected void appendCellReference(StringBuffer sb) {
|
||||
if(_isColAbs) {
|
||||
sb.append(ABSOLUTE_REFERENCE_MARKER);
|
||||
}
|
||||
sb.append( convertNumToColString(_colIndex));
|
||||
if(_isRowAbs) {
|
||||
sb.append(ABSOLUTE_REFERENCE_MARKER);
|
||||
}
|
||||
sb.append(_rowIndex+1);
|
||||
}
|
||||
/**
|
||||
* Appends cell reference with '$' markers for absolute values as required.
|
||||
* Sheet name is not included.
|
||||
*/
|
||||
/* package */ void appendCellReference(StringBuffer sb) {
|
||||
if(_isColAbs) {
|
||||
sb.append(ABSOLUTE_REFERENCE_MARKER);
|
||||
}
|
||||
sb.append( convertNumToColString(_colIndex));
|
||||
if(_isRowAbs) {
|
||||
sb.append(ABSOLUTE_REFERENCE_MARKER);
|
||||
}
|
||||
sb.append(_rowIndex+1);
|
||||
}
|
||||
}
|
||||
|
@ -57,10 +57,6 @@ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
|
||||
return convertToExternalSheetIndex(sheetIndex);
|
||||
}
|
||||
|
||||
public EvaluationName getName(int index) {
|
||||
return new Name(_uBook.getNameAt(index), index, this);
|
||||
}
|
||||
|
||||
public EvaluationName getName(String name) {
|
||||
for(int i=0; i < _uBook.getNumberOfNames(); i++) {
|
||||
String nameText = _uBook.getNameName(i);
|
||||
@ -88,14 +84,15 @@ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
|
||||
return _uBook.getSheetAt(sheetIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Doesn't do anything - returns the same index
|
||||
* TODO - figure out if this is a ole2 specific thing, or
|
||||
* if we need to do something proper here too!
|
||||
*/
|
||||
public Sheet getSheetByExternSheetIndex(int externSheetIndex) {
|
||||
int sheetIndex = convertFromExternalSheetIndex(externSheetIndex);
|
||||
return _uBook.getSheetAt(sheetIndex);
|
||||
public ExternalSheet getExternalSheet(int externSheetIndex) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
public int getExternalSheetIndex(String workbookName, String sheetName) {
|
||||
throw new RuntimeException("not implemented yet");
|
||||
}
|
||||
public int getSheetIndex(String sheetName) {
|
||||
return _uBook.getSheetIndex(sheetName);
|
||||
}
|
||||
|
||||
public Workbook getWorkbook() {
|
||||
|
@ -28,7 +28,7 @@ import org.apache.poi.hssf.model.AllModelTests;
|
||||
import org.apache.poi.hssf.record.AllRecordTests;
|
||||
import org.apache.poi.hssf.usermodel.AllUserModelTests;
|
||||
import org.apache.poi.hssf.util.AllHSSFUtilTests;
|
||||
import org.apache.poi.ss.formula.TestEvaluationCache;
|
||||
import org.apache.poi.ss.formula.AllSSFormulaTests;
|
||||
|
||||
/**
|
||||
* Test Suite for all sub-packages of org.apache.poi.hssf<br/>
|
||||
@ -53,7 +53,7 @@ public final class HSSFTests {
|
||||
}
|
||||
suite.addTest(new TestSuite(TestEventRecordFactory.class));
|
||||
suite.addTest(new TestSuite(TestModelFactory.class));
|
||||
suite.addTest(new TestSuite(TestEvaluationCache.class));
|
||||
suite.addTest(AllSSFormulaTests.suite());
|
||||
return suite;
|
||||
}
|
||||
}
|
||||
|
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;
|
||||
@ -553,14 +555,14 @@ public final class TestFormulaParser extends TestCase {
|
||||
|
||||
Class[] expClss;
|
||||
|
||||
expClss = new Class[] {
|
||||
RefPtg.class,
|
||||
expClss = new Class[] {
|
||||
RefPtg.class,
|
||||
AttrPtg.class, // tAttrIf
|
||||
MissingArgPtg.class,
|
||||
MissingArgPtg.class,
|
||||
AttrPtg.class, // tAttrSkip
|
||||
RefPtg.class,
|
||||
AttrPtg.class, // tAttrSkip
|
||||
FuncVarPtg.class,
|
||||
FuncVarPtg.class,
|
||||
};
|
||||
|
||||
confirmTokenClasses("if(A1, ,C1)", expClss);
|
||||
@ -735,10 +737,10 @@ public final class TestFormulaParser extends TestCase {
|
||||
/**
|
||||
* Make sure that POI uses the right Func Ptg when encoding formulas. Functions with variable
|
||||
* number of args should get FuncVarPtg, functions with fixed args should get FuncPtg.<p/>
|
||||
*
|
||||
*
|
||||
* Prior to the fix for bug 44675 POI would encode FuncVarPtg for all functions. In many cases
|
||||
* Excel tolerates the wrong Ptg and evaluates the formula OK (e.g. SIN), but in some cases
|
||||
* (e.g. COUNTIF) Excel fails to evaluate the formula, giving '#VALUE!' instead.
|
||||
* Excel tolerates the wrong Ptg and evaluates the formula OK (e.g. SIN), but in some cases
|
||||
* (e.g. COUNTIF) Excel fails to evaluate the formula, giving '#VALUE!' instead.
|
||||
*/
|
||||
public void testFuncPtgSelection() {
|
||||
|
||||
@ -777,7 +779,7 @@ public final class TestFormulaParser extends TestCase {
|
||||
parseFormula("round(3.14;2)");
|
||||
throw new AssertionFailedError("Didn't get parse exception as expected");
|
||||
} catch (RuntimeException e) {
|
||||
FormulaParserTestHelper.confirmParseException(e,
|
||||
FormulaParserTestHelper.confirmParseException(e,
|
||||
"Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'");
|
||||
}
|
||||
|
||||
@ -785,11 +787,11 @@ public final class TestFormulaParser extends TestCase {
|
||||
parseFormula(" =2+2");
|
||||
throw new AssertionFailedError("Didn't get parse exception as expected");
|
||||
} catch (RuntimeException e) {
|
||||
FormulaParserTestHelper.confirmParseException(e,
|
||||
FormulaParserTestHelper.confirmParseException(e,
|
||||
"The specified formula ' =2+2' starts with an equals sign which is not allowed.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* this function name has a dot in it.
|
||||
*/
|
||||
@ -798,8 +800,8 @@ public final class TestFormulaParser extends TestCase {
|
||||
Ptg[] ptgs;
|
||||
try {
|
||||
ptgs = parseFormula("error.type(A1)");
|
||||
|
||||
|
||||
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (e.getMessage().equals("Invalid Formula cell reference: 'error'")) {
|
||||
throw new AssertionFailedError("Identified bug 45334");
|
||||
@ -811,7 +813,7 @@ public final class TestFormulaParser extends TestCase {
|
||||
FuncPtg funcPtg = (FuncPtg) ptgs[1];
|
||||
assertEquals("ERROR.TYPE", funcPtg.getName());
|
||||
}
|
||||
|
||||
|
||||
public void testNamedRangeThatLooksLikeCell() {
|
||||
HSSFWorkbook wb = new HSSFWorkbook();
|
||||
HSSFSheet sheet = wb.createSheet("Sheet1");
|
||||
@ -838,35 +840,35 @@ public final class TestFormulaParser extends TestCase {
|
||||
cell.setCellFormula("count(pf1)");
|
||||
throw new AssertionFailedError("Expected formula parse execption");
|
||||
} catch (RuntimeException e) {
|
||||
FormulaParserTestHelper.confirmParseException(e,
|
||||
FormulaParserTestHelper.confirmParseException(e,
|
||||
"Specified named range 'pf1' does not exist in the current workbook.");
|
||||
}
|
||||
cell.setCellFormula("count(fp1)"); // plain cell ref, col is in range
|
||||
}
|
||||
|
||||
|
||||
public void testParseAreaRefHighRow_bug45358() {
|
||||
Ptg[] ptgs;
|
||||
AreaI aptg;
|
||||
|
||||
|
||||
HSSFWorkbook book = new HSSFWorkbook();
|
||||
book.createSheet("Sheet1");
|
||||
|
||||
|
||||
ptgs = HSSFFormulaParser.parse("Sheet1!A10:A40000", book);
|
||||
aptg = (AreaI) ptgs[0];
|
||||
if (aptg.getLastRow() == -25537) {
|
||||
throw new AssertionFailedError("Identified bug 45358");
|
||||
}
|
||||
assertEquals(39999, aptg.getLastRow());
|
||||
|
||||
|
||||
ptgs = HSSFFormulaParser.parse("Sheet1!A10:A65536", book);
|
||||
aptg = (AreaI) ptgs[0];
|
||||
assertEquals(65535, aptg.getLastRow());
|
||||
|
||||
|
||||
// plain area refs should be ok too
|
||||
ptgs = parseFormula("A10:A65536");
|
||||
aptg = (AreaI) ptgs[0];
|
||||
assertEquals(65535, aptg.getLastRow());
|
||||
|
||||
|
||||
}
|
||||
public void testParseArray() {
|
||||
Ptg[] ptgs;
|
||||
@ -875,11 +877,71 @@ public final class TestFormulaParser extends TestCase {
|
||||
Ptg ptg0 = ptgs[0];
|
||||
assertEquals(ArrayPtg.class, ptg0.getClass());
|
||||
assertEquals("{1.0,2.0,2.0,#REF!;FALSE,3.0,3.0,2.0}", ptg0.toFormulaString());
|
||||
|
||||
|
||||
ArrayPtg aptg = (ArrayPtg) ptg0;
|
||||
Object[][] values = aptg.getTokenArrayValues();
|
||||
assertEquals(ErrorConstant.valueOf(HSSFErrorConstants.ERROR_REF), values[0][3]);
|
||||
assertEquals(Boolean.FALSE, values[1][0]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void testRangeOperator() {
|
||||
|
||||
HSSFWorkbook wb = new HSSFWorkbook();
|
||||
HSSFSheet sheet = wb.createSheet();
|
||||
HSSFCell cell = sheet.createRow(0).createCell(0);
|
||||
|
||||
wb.setSheetName(0, "Sheet1");
|
||||
cell.setCellFormula("Sheet1!B$4:Sheet1!$C1"); // explicit range ':' operator
|
||||
assertEquals("Sheet1!B$4:Sheet1!$C1", cell.getCellFormula());
|
||||
|
||||
cell.setCellFormula("Sheet1!B$4:$C1"); // plain area ref
|
||||
assertEquals("Sheet1!B1:$C$4", cell.getCellFormula()); // note - area ref is normalised
|
||||
|
||||
cell.setCellFormula("Sheet1!$C1...B$4"); // different syntax for plain area ref
|
||||
assertEquals("Sheet1!B1:$C$4", cell.getCellFormula());
|
||||
|
||||
// with funny sheet name
|
||||
wb.setSheetName(0, "A1...A2");
|
||||
cell.setCellFormula("A1...A2!B1");
|
||||
assertEquals("A1...A2!B1", cell.getCellFormula());
|
||||
}
|
||||
|
||||
public void testBooleanNamedSheet() {
|
||||
|
||||
HSSFWorkbook wb = new HSSFWorkbook();
|
||||
HSSFSheet sheet = wb.createSheet("true");
|
||||
HSSFCell cell = sheet.createRow(0).createCell(0);
|
||||
cell.setCellFormula("'true'!B2");
|
||||
|
||||
assertEquals("'true'!B2", cell.getCellFormula());
|
||||
}
|
||||
|
||||
public void testParseExternalWorkbookReference() {
|
||||
HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls");
|
||||
HSSFCell cell = wbA.getSheetAt(0).getRow(0).getCell(0);
|
||||
|
||||
// make sure formula in sample is as expected
|
||||
assertEquals("[multibookFormulaB.xls]BSheet1!B1", cell.getCellFormula());
|
||||
Ptg[] expectedPtgs = FormulaExtractor.getPtgs(cell);
|
||||
confirmSingle3DRef(expectedPtgs, 1);
|
||||
|
||||
// now try (re-)parsing the formula
|
||||
Ptg[] actualPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]BSheet1!B1", wbA);
|
||||
confirmSingle3DRef(actualPtgs, 1); // externalSheetIndex 1 -> BSheet1
|
||||
|
||||
// try parsing a formula pointing to a different external sheet
|
||||
Ptg[] otherPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]AnotherSheet!B1", wbA);
|
||||
confirmSingle3DRef(otherPtgs, 0); // externalSheetIndex 0 -> AnotherSheet
|
||||
|
||||
// try setting the same formula in a cell
|
||||
cell.setCellFormula("[multibookFormulaB.xls]AnotherSheet!B1");
|
||||
assertEquals("[multibookFormulaB.xls]AnotherSheet!B1", cell.getCellFormula());
|
||||
}
|
||||
private static void confirmSingle3DRef(Ptg[] ptgs, int expectedExternSheetIndex) {
|
||||
assertEquals(1, ptgs.length);
|
||||
Ptg ptg0 = ptgs[0];
|
||||
assertEquals(Ref3DPtg.class, ptg0.getClass());
|
||||
assertEquals(expectedExternSheetIndex, ((Ref3DPtg)ptg0).getExternSheetIndex());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -20,16 +20,12 @@ package org.apache.poi.hssf.record.formula;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Tests for SheetNameFormatter
|
||||
* Tests for {@link SheetNameFormatter}
|
||||
*
|
||||
* @author Josh Micich
|
||||
*/
|
||||
public final class TestSheetNameFormatter extends TestCase {
|
||||
|
||||
public TestSheetNameFormatter(String testName) {
|
||||
super(testName);
|
||||
}
|
||||
|
||||
private static void confirmFormat(String rawSheetName, String expectedSheetNameEncoding) {
|
||||
assertEquals(expectedSheetNameEncoding, SheetNameFormatter.format(rawSheetName));
|
||||
}
|
||||
@ -55,6 +51,16 @@ public final class TestSheetNameFormatter extends TestCase {
|
||||
confirmFormat("TAXRETURN19980415", "TAXRETURN19980415");
|
||||
}
|
||||
|
||||
public void testBooleanLiterals() {
|
||||
confirmFormat("TRUE", "'TRUE'");
|
||||
confirmFormat("FALSE", "'FALSE'");
|
||||
confirmFormat("True", "'True'");
|
||||
confirmFormat("fAlse", "'fAlse'");
|
||||
|
||||
confirmFormat("Yes", "Yes");
|
||||
confirmFormat("No", "No");
|
||||
}
|
||||
|
||||
private static void confirmCellNameMatch(String rawSheetName, boolean expected) {
|
||||
assertEquals(expected, SheetNameFormatter.nameLooksLikePlainCellReference(rawSheetName));
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ public class AllFormulaEvalTests {
|
||||
result.addTestSuite(TestFormulaBugs.class);
|
||||
result.addTestSuite(TestFormulasFromSpreadsheet.class);
|
||||
result.addTestSuite(TestPercentEval.class);
|
||||
result.addTestSuite(TestRangeEval.class);
|
||||
result.addTestSuite(TestUnaryPlusEval.class);
|
||||
return result;
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ public final class TestFormulaBugs extends TestCase {
|
||||
throw new AssertionFailedError("Identified bug 42448");
|
||||
}
|
||||
|
||||
assertEquals("SUMPRODUCT(A!C7:C67,B8:B68)/B69", cell.getCellFormula());
|
||||
assertEquals("SUMPRODUCT(A!C7:A!C67,B8:B68)/B69", cell.getCellFormula());
|
||||
|
||||
// might as well evaluate the sucker...
|
||||
|
||||
|
@ -0,0 +1,95 @@
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hssf.record.formula.eval;
|
||||
|
||||
import org.apache.poi.hssf.record.formula.AreaI;
|
||||
import org.apache.poi.hssf.record.formula.AreaI.OffsetArea;
|
||||
import org.apache.poi.hssf.util.AreaReference;
|
||||
import org.apache.poi.hssf.util.CellReference;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Test for unary plus operator evaluator.
|
||||
*
|
||||
* @author Josh Micich
|
||||
*/
|
||||
public final class TestRangeEval extends TestCase {
|
||||
|
||||
public void testPermutations() {
|
||||
|
||||
confirm("B3", "D7", "B3:D7");
|
||||
confirm("B1", "B1", "B1:B1");
|
||||
|
||||
confirm("B7", "D3", "B3:D7");
|
||||
confirm("D3", "B7", "B3:D7");
|
||||
confirm("D7", "B3", "B3:D7");
|
||||
}
|
||||
|
||||
private static void confirm(String refA, String refB, String expectedAreaRef) {
|
||||
|
||||
Eval[] args = {
|
||||
createRefEval(refA),
|
||||
createRefEval(refB),
|
||||
};
|
||||
AreaReference ar = new AreaReference(expectedAreaRef);
|
||||
Eval result = RangeEval.instance.evaluate(args, 0, (short)0);
|
||||
assertTrue(result instanceof AreaEval);
|
||||
AreaEval ae = (AreaEval) result;
|
||||
assertEquals(ar.getFirstCell().getRow(), ae.getFirstRow());
|
||||
assertEquals(ar.getLastCell().getRow(), ae.getLastRow());
|
||||
assertEquals(ar.getFirstCell().getCol(), ae.getFirstColumn());
|
||||
assertEquals(ar.getLastCell().getCol(), ae.getLastColumn());
|
||||
}
|
||||
|
||||
private static Eval createRefEval(String refStr) {
|
||||
CellReference cr = new CellReference(refStr);
|
||||
return new MockRefEval(cr.getRow(), cr.getCol());
|
||||
|
||||
}
|
||||
|
||||
private static final class MockRefEval extends RefEvalBase {
|
||||
|
||||
public MockRefEval(int rowIndex, int columnIndex) {
|
||||
super(rowIndex, columnIndex);
|
||||
}
|
||||
public ValueEval getInnerValueEval() {
|
||||
throw new RuntimeException("not expected to be called during this test");
|
||||
}
|
||||
public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx,
|
||||
int relLastColIx) {
|
||||
AreaI area = new OffsetArea(getRow(), getColumn(),
|
||||
relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);
|
||||
return new MockAreaEval(area);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MockAreaEval extends AreaEvalBase {
|
||||
|
||||
public MockAreaEval(AreaI ptg) {
|
||||
super(ptg);
|
||||
}
|
||||
public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) {
|
||||
throw new RuntimeException("not expected to be called during this test");
|
||||
}
|
||||
public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx,
|
||||
int relLastColIx) {
|
||||
throw new RuntimeException("not expected to be called during this test");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,34 @@
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.ss.formula;
|
||||
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
/**
|
||||
* Test suite for org.apache.poi.ss.formula
|
||||
*
|
||||
* @author Josh Micich
|
||||
*/
|
||||
public final class AllSSFormulaTests {
|
||||
public static Test suite() {
|
||||
TestSuite result = new TestSuite(AllSSFormulaTests.class.getName());
|
||||
result.addTestSuite(TestEvaluationCache.class);
|
||||
result.addTestSuite(TestWorkbookEvaluator.class);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.ss.formula;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.poi.hssf.HSSFTestDataSamples;
|
||||
import org.apache.poi.hssf.record.formula.AreaErrPtg;
|
||||
import org.apache.poi.hssf.record.formula.AttrPtg;
|
||||
import org.apache.poi.hssf.record.formula.DeletedArea3DPtg;
|
||||
import org.apache.poi.hssf.record.formula.DeletedRef3DPtg;
|
||||
import org.apache.poi.hssf.record.formula.IntPtg;
|
||||
import org.apache.poi.hssf.record.formula.Ptg;
|
||||
import org.apache.poi.hssf.record.formula.RefErrorPtg;
|
||||
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.NumberEval;
|
||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||
import org.apache.poi.hssf.usermodel.HSSFCell;
|
||||
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
|
||||
import org.apache.poi.hssf.usermodel.HSSFSheet;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
|
||||
/**
|
||||
* Tests {@link WorkbookEvaluator}.
|
||||
*
|
||||
* @author Josh Micich
|
||||
*/
|
||||
public class TestWorkbookEvaluator extends TestCase {
|
||||
|
||||
/**
|
||||
* Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing
|
||||
* the whole formula which converts tAttrSum to tFuncVar("SUM") )
|
||||
*/
|
||||
public void testAttrSum() {
|
||||
|
||||
Ptg[] ptgs = {
|
||||
new IntPtg(42),
|
||||
AttrPtg.SUM,
|
||||
};
|
||||
|
||||
ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
|
||||
assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the evaluator can directly handle (deleted) ref error tokens
|
||||
* (instead of relying on re-parsing the whole formula which converts these
|
||||
* to the error constant #REF! )
|
||||
*/
|
||||
public void testRefErr() {
|
||||
|
||||
confirmRefErr(new RefErrorPtg());
|
||||
confirmRefErr(new AreaErrPtg());
|
||||
confirmRefErr(new DeletedRef3DPtg(0));
|
||||
confirmRefErr(new DeletedArea3DPtg(0));
|
||||
}
|
||||
private static void confirmRefErr(Ptg ptg) {
|
||||
Ptg[] ptgs = {
|
||||
ptg,
|
||||
};
|
||||
|
||||
ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
|
||||
assertEquals(ErrorEval.REF_INVALID, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing
|
||||
* the whole formula which converts tAttrSum to tFuncVar("SUM") )
|
||||
*/
|
||||
public void testMemFunc() {
|
||||
|
||||
Ptg[] ptgs = {
|
||||
new IntPtg(42),
|
||||
AttrPtg.SUM,
|
||||
};
|
||||
|
||||
ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
|
||||
assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
|
||||
}
|
||||
|
||||
|
||||
public void testEvaluateMultipleWorkbooks() {
|
||||
HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls");
|
||||
HSSFWorkbook wbB = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaB.xls");
|
||||
|
||||
HSSFFormulaEvaluator evaluatorA = new HSSFFormulaEvaluator(wbA);
|
||||
HSSFFormulaEvaluator evaluatorB = new HSSFFormulaEvaluator(wbB);
|
||||
|
||||
// Hook up the workbook evaluators to enable evaluation of formulas across books
|
||||
String[] bookNames = { "multibookFormulaA.xls", "multibookFormulaB.xls", };
|
||||
HSSFFormulaEvaluator[] evaluators = { evaluatorA, evaluatorB, };
|
||||
HSSFFormulaEvaluator.setupEnvironment(bookNames, evaluators);
|
||||
|
||||
HSSFCell cell;
|
||||
|
||||
HSSFSheet aSheet1 = wbA.getSheetAt(0);
|
||||
HSSFSheet bSheet1 = wbB.getSheetAt(0);
|
||||
|
||||
// Simple case - single link from wbA to wbB
|
||||
confirmFormula(wbA, 0, 0, 0, "[multibookFormulaB.xls]BSheet1!B1");
|
||||
cell = aSheet1.getRow(0).getCell(0);
|
||||
confirmEvaluation(35, evaluatorA, cell);
|
||||
|
||||
|
||||
// more complex case - back link into wbA
|
||||
// [wbA]ASheet1!A2 references (among other things) [wbB]BSheet1!B2
|
||||
confirmFormula(wbA, 0, 1, 0, "[multibookFormulaB.xls]BSheet1!$B$2+2*A3");
|
||||
// [wbB]BSheet1!B2 references (among other things) [wbA]AnotherSheet!A1:B2
|
||||
confirmFormula(wbB, 0, 1, 1, "SUM([multibookFormulaA.xls]AnotherSheet!$A$1:$B$2)+B3");
|
||||
|
||||
cell = aSheet1.getRow(1).getCell(0);
|
||||
confirmEvaluation(264, evaluatorA, cell);
|
||||
|
||||
// change [wbB]BSheet1!B3 (from 50 to 60)
|
||||
bSheet1.getRow(2).getCell(1).setCellValue(60);
|
||||
evaluatorB.setCachedPlainValue(bSheet1, 2, 1, new NumberEval(60));
|
||||
confirmEvaluation(274, evaluatorA, cell);
|
||||
|
||||
// change [wbA]ASheet1!A3 (from 100 to 80)
|
||||
aSheet1.getRow(2).getCell(0).setCellValue(80);
|
||||
evaluatorA.setCachedPlainValue(aSheet1, 2, 0, new NumberEval(80));
|
||||
confirmEvaluation(234, evaluatorA, cell);
|
||||
|
||||
// change [wbA]AnotherSheet!A1 (from 2 to 3)
|
||||
wbA.getSheetAt(1).getRow(0).getCell(0).setCellValue(3);
|
||||
evaluatorA.setCachedPlainValue(wbA.getSheetAt(1), 0, 0, new NumberEval(3));
|
||||
confirmEvaluation(235, evaluatorA, cell);
|
||||
}
|
||||
|
||||
private static void confirmEvaluation(double expectedValue, HSSFFormulaEvaluator fe, HSSFCell cell) {
|
||||
assertEquals(expectedValue, fe.evaluate(cell).getNumberValue(), 0.0);
|
||||
}
|
||||
|
||||
private static void confirmFormula(HSSFWorkbook wb, int sheetIndex, int rowIndex, int columnIndex,
|
||||
String expectedFormula) {
|
||||
HSSFCell cell = wb.getSheetAt(sheetIndex).getRow(rowIndex).getCell(columnIndex);
|
||||
assertEquals(expectedFormula, cell.getCellFormula());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user