aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org
diff options
context:
space:
mode:
authorJosh Micich <josh@apache.org>2008-09-29 20:09:09 +0000
committerJosh Micich <josh@apache.org>2008-09-29 20:09:09 +0000
commit8bfd1494141e2fac61069bb3431189565f8c6a39 (patch)
treec1e4032f4be206d4167e5828f35e3593f04d97b6 /src/java/org
parent7c8d6be5bce576e94054a6808da509e3db3c1ebc (diff)
downloadpoi-8bfd1494141e2fac61069bb3431189565f8c6a39.tar.gz
poi-8bfd1494141e2fac61069bb3431189565f8c6a39.zip
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
Diffstat (limited to 'src/java/org')
-rwxr-xr-xsrc/java/org/apache/poi/hssf/model/LinkTable.java91
-rw-r--r--src/java/org/apache/poi/hssf/model/Workbook.java130
-rw-r--r--src/java/org/apache/poi/hssf/record/ExternSheetRecord.java5
-rw-r--r--src/java/org/apache/poi/hssf/record/SupBookRecord.java29
-rw-r--r--src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java9
-rw-r--r--src/java/org/apache/poi/hssf/record/formula/AreaI.java9
-rw-r--r--src/java/org/apache/poi/hssf/record/formula/AreaPtgBase.java464
-rw-r--r--src/java/org/apache/poi/hssf/record/formula/AttrPtg.java34
-rw-r--r--src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java22
-rw-r--r--src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java9
-rwxr-xr-xsrc/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java35
-rw-r--r--src/java/org/apache/poi/hssf/record/formula/eval/RangeEval.java71
-rw-r--r--src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java24
-rw-r--r--src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java16
-rw-r--r--src/java/org/apache/poi/hssf/util/AreaReference.java1
-rw-r--r--src/java/org/apache/poi/hssf/util/CellReference.java18
-rw-r--r--src/java/org/apache/poi/ss/formula/CellLocation.java23
-rw-r--r--src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java155
-rw-r--r--src/java/org/apache/poi/ss/formula/EvaluationCache.java25
-rw-r--r--src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java24
-rw-r--r--src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java29
-rw-r--r--src/java/org/apache/poi/ss/formula/FormulaParser.java226
-rw-r--r--src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java12
-rw-r--r--src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java5
-rw-r--r--src/java/org/apache/poi/ss/formula/OperandClassTransformer.java5
-rwxr-xr-xsrc/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java3
-rw-r--r--src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java91
-rw-r--r--src/java/org/apache/poi/ss/util/AreaReference.java43
-rw-r--r--src/java/org/apache/poi/ss/util/CellReference.java574
29 files changed, 1444 insertions, 738 deletions
diff --git a/src/java/org/apache/poi/hssf/model/LinkTable.java b/src/java/org/apache/poi/hssf/model/LinkTable.java
index 2caab0fb56..998712e5e8 100755
--- a/src/java/org/apache/poi/hssf/model/LinkTable.java
+++ b/src/java/org/apache/poi/hssf/model/LinkTable.java
@@ -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);
}
diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java
index dbf7ecf7e1..0728d0e29f 100644
--- a/src/java/org/apache/poi/hssf/model/Workbook.java
+++ b/src/java/org/apache/poi/hssf/model/Workbook.java
@@ -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
*/
diff --git a/src/java/org/apache/poi/hssf/record/ExternSheetRecord.java b/src/java/org/apache/poi/hssf/record/ExternSheetRecord.java
index c4f00581b2..64f86c3f12 100644
--- a/src/java/org/apache/poi/hssf/record/ExternSheetRecord.java
+++ b/src/java/org/apache/poi/hssf/record/ExternSheetRecord.java
@@ -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;
}
diff --git a/src/java/org/apache/poi/hssf/record/SupBookRecord.java b/src/java/org/apache/poi/hssf/record/SupBookRecord.java
index c75e2db894..b4e46c6fdc 100644
--- a/src/java/org/apache/poi/hssf/record/SupBookRecord.java
+++ b/src/java/org/apache/poi/hssf/record/SupBookRecord.java
@@ -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();
diff --git a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
index 9a538cf70d..58cdc5b275 100644
--- a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
@@ -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) {
diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaI.java b/src/java/org/apache/poi/hssf/record/formula/AreaI.java
index 477d32f677..319a9819c9 100644
--- a/src/java/org/apache/poi/hssf/record/formula/AreaI.java
+++ b/src/java/org/apache/poi/hssf/record/formula/AreaI.java
@@ -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;
}
}
-
} \ No newline at end of file
diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaPtgBase.java b/src/java/org/apache/poi/hssf/record/formula/AreaPtgBase.java
index 7bfefc5526..a97ecd4f03 100644
--- a/src/java/org/apache/poi/hssf/record/formula/AreaPtgBase.java
+++ b/src/java/org/apache/poi/hssf/record/formula/AreaPtgBase.java
@@ -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;
+ }
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java b/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java
index 34558ebdf8..701a7aee74 100644
--- a/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java
@@ -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)";
diff --git a/src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java b/src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java
index 412c110d3a..dadbcb8702 100644
--- a/src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java
+++ b/src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java
@@ -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);
diff --git a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
index 261e98ce8b..053057925f 100644
--- a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
@@ -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;
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java
index ace857da1e..fd822c3986 100755
--- a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java
+++ b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java
@@ -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':
diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/RangeEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/RangeEval.java
new file mode 100644
index 0000000000..9398108fdc
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/formula/eval/RangeEval.java
@@ -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");
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
index d82a9d1144..0b6a954499 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
@@ -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 {
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
index d7a3207383..2f30d15c81 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
@@ -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
diff --git a/src/java/org/apache/poi/hssf/util/AreaReference.java b/src/java/org/apache/poi/hssf/util/AreaReference.java
index d6575642ee..6a0b0e2d3a 100644
--- a/src/java/org/apache/poi/hssf/util/AreaReference.java
+++ b/src/java/org/apache/poi/hssf/util/AreaReference.java
@@ -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);
diff --git a/src/java/org/apache/poi/hssf/util/CellReference.java b/src/java/org/apache/poi/hssf/util/CellReference.java
index 0ddb0764ee..2e31b2a215 100644
--- a/src/java/org/apache/poi/hssf/util/CellReference.java
+++ b/src/java/org/apache/poi/hssf/util/CellReference.java
@@ -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);
}
diff --git a/src/java/org/apache/poi/ss/formula/CellLocation.java b/src/java/org/apache/poi/ss/formula/CellLocation.java
index d6bb9b7bfb..6857c4bc1b 100644
--- a/src/java/org/apache/poi/ss/formula/CellLocation.java
+++ b/src/java/org/apache/poi/ss/formula/CellLocation.java
@@ -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() {
diff --git a/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java b/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java
new file mode 100644
index 0000000000..c62d2f182d
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java
@@ -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;
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/EvaluationCache.java b/src/java/org/apache/poi/ss/formula/EvaluationCache.java
index fdc933f6fa..b0c34fd78a 100644
--- a/src/java/org/apache/poi/ss/formula/EvaluationCache.java
+++ b/src/java/org/apache/poi/ss/formula/EvaluationCache.java
@@ -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;
diff --git a/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
index 02917d82a4..6cf938fe9f 100644
--- a/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
+++ b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
@@ -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;
+ }
+ }
}
diff --git a/src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java b/src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java
new file mode 100644
index 0000000000..09262a1320
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java
@@ -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();
+}
diff --git a/src/java/org/apache/poi/ss/formula/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java
index df2f8c282f..1398399ff4 100644
--- a/src/java/org/apache/poi/ss/formula/FormulaParser.java
+++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java
@@ -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));
- }
-
- private Ptg parseNameOrReference(String name) {
-
- AreaReference areaRef = parseArea(name);
- if (areaRef != null) {
- // will happen if dots are used instead of colon
- return new AreaPtg(areaRef.formatAsString());
+ if (!iden.isQuoted()) {
+ String name = iden.getName();
+ if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
+ return new ParseNode(new BoolPtg(name.toUpperCase()));
+ }
}
+ return parseRangeExpression(iden);
+ }
- if (look == ':' || look == '.') { // this is a AreaReference
+ private ParseNode parseRangeExpression(Identifier iden) {
+ Ptg ptgA = parseNameOrCellRef(iden);
+ if (look == ':') {
GetChar();
-
- while (look == '.') { // formulas can have . or .. or ... instead of :
- 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;
- String first = name;
- String second = parseIdentifier();
- return new AreaPtg(first+":"+second);
+ 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 == '!') {
- 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);
+ 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);
}
- return new Ref3DPtg(first, externIdx);
- }
- if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
- return new BoolPtg(name.toUpperCase());
+ // 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());
+ }
// 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':
diff --git a/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java
index 69431c2c22..e9be7d1d39 100644
--- a/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java
+++ b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java
@@ -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);
}
diff --git a/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java b/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java
index ac95f4da0f..c9b95f6b1c 100644
--- a/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java
+++ b/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java
@@ -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);
diff --git a/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java b/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java
index 79087e4d6d..91226bd53b 100644
--- a/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java
+++ b/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java
@@ -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");
}
diff --git a/src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java b/src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java
index eaa57c114f..09c93723f9 100755
--- a/src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java
+++ b/src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java
@@ -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;
}
diff --git a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
index b41e2c2c3f..9c02ca9f9e 100644
--- a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
+++ b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
@@ -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);
}
diff --git a/src/java/org/apache/poi/ss/util/AreaReference.java b/src/java/org/apache/poi/ss/util/AreaReference.java
index ae2c9192f0..30d92ebcf4 100644
--- a/src/java/org/apache/poi/ss/util/AreaReference.java
+++ b/src/java/org/apache/poi/ss/util/AreaReference.java
@@ -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;
}
diff --git a/src/java/org/apache/poi/ss/util/CellReference.java b/src/java/org/apache/poi/ss/util/CellReference.java
index e5005db5d4..23b4c806e8 100644
--- a/src/java/org/apache/poi/ss/util/CellReference.java
+++ b/src/java/org/apache/poi/ss/util/CellReference.java
@@ -34,22 +34,29 @@ 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
- }
-
- public CellReference(int pRow, int pCol) {
- this(pRow, pCol & 0xFFFF, false, false);
- }
- public CellReference(int pRow, short pCol) {
- this(pRow, (int)pCol, false, false);
- }
+ /**
+ * 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, 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) {
+ this(pRow, pCol, false, false);
+ }
+ public CellReference(int pRow, short pCol) {
+ this(pRow, pCol & 0xFFFF, false, false);
+ }
- 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;
- }
+ 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;
+ }
- /**
- * takes in a column reference portion of a CellRef and converts it from
- * ALPHA-26 number format to 0-based base 10.
- * 'A' -> 0
- * 'Z' -> 25
- * 'AA' -> 26
- * 'IV' -> 255
- * @return zero based column index
- */
- protected static int convertColStringToIndex(String ref) {
- int pos = 0;
- int retval=0;
+ public int getRow(){return _rowIndex;}
+ public short getCol(){return (short) _colIndex;}
+ public boolean isRowAbsolute(){return _isRowAbs;}
+ public boolean isColAbsolute(){return _isColAbs;}
+ /**
+ * @return possibly <code>null</code> if this is a 2D reference. Special characters are not
+ * escaped or delimited
+ */
+ public String getSheetName(){
+ return _sheetName;
+ }
+
+ public static boolean isPartAbsolute(String part) {
+ return part.charAt(0) == ABSOLUTE_REFERENCE_MARKER;
+ }
+ /**
+ * takes in a column reference portion of a CellRef and converts it from
+ * ALPHA-26 number format to 0-based base 10.
+ * 'A' -> 0
+ * 'Z' -> 25
+ * 'AA' -> 26
+ * 'IV' -> 255
+ * @return zero based column index
+ */
+ public static int convertColStringToIndex(String ref) {
+
+ int pos = 0;
+ int retval=0;
for (int k = ref.length()-1; k >= 0; k--) {
char thechar = ref.charAt(k);
- if (thechar == ABSOLUTE_REFERENCE_MARKER) {
- if (k != 0) {
- throw new IllegalArgumentException("Bad col ref format '" + ref + "'");
- }
- break;
- }
+ if (thechar == ABSOLUTE_REFERENCE_MARKER) {
+ if (k != 0) {
+ throw new IllegalArgumentException("Bad col ref format '" + ref + "'");
+ }
+ break;
+ }
// Character.getNumericValue() returns the values
// 10-35 for the letter A-Z
int shift = (int)Math.pow(26, pos);
@@ -162,64 +169,70 @@ public class CellReference {
pos++;
}
return retval-1;
- }
+ }
- /**
- * Classifies an identifier as either a simple (2D) cell reference or a named range name
- * @return one of the values from <tt>NameType</tt>
- */
- public static int classifyCellReference(String str) {
- int len = str.length();
- if (len < 1) {
- throw new IllegalArgumentException("Empty string not allowed");
- }
- char firstChar = str.charAt(0);
- switch (firstChar) {
- case ABSOLUTE_REFERENCE_MARKER:
- case '.':
- case '_':
- break;
- default:
- if (!Character.isLetter(firstChar)) {
- throw new IllegalArgumentException("Invalid first char (" + firstChar
- + ") of cell reference or named range. Letter expected");
- }
- }
- if (!Character.isDigit(str.charAt(len-1))) {
- // no digits at end of str
- return validateNamedRangeName(str);
- }
- Matcher cellRefPatternMatcher = CELL_REF_PATTERN.matcher(str);
- if (!cellRefPatternMatcher.matches()) {
- return validateNamedRangeName(str);
- }
- String lettersGroup = cellRefPatternMatcher.group(1);
- String digitsGroup = cellRefPatternMatcher.group(2);
- if (cellReferenceIsWithinRange(lettersGroup, digitsGroup)) {
- // valid cell reference
- return NameType.CELL;
- }
- // If str looks like a cell reference, but is out of (row/col) range, it is a valid
- // named range name
- // This behaviour is a little weird. For example, "IW123" is a valid named range name
- // because the column "IW" is beyond the maximum "IV". Note - this behaviour is version
- // dependent. In Excel 2007, "IW123" is not a valid named range name.
- if (str.indexOf(ABSOLUTE_REFERENCE_MARKER) >= 0) {
- // Of course, named range names cannot have '$'
- return NameType.BAD_CELL_OR_NAMED_RANGE;
- }
- return NameType.NAMED_RANGE;
- }
+ /**
+ * Classifies an identifier as either a simple (2D) cell reference or a named range name
+ * @return one of the values from <tt>NameType</tt>
+ */
+ public static int classifyCellReference(String str) {
+ int len = str.length();
+ if (len < 1) {
+ throw new IllegalArgumentException("Empty string not allowed");
+ }
+ char firstChar = str.charAt(0);
+ switch (firstChar) {
+ case ABSOLUTE_REFERENCE_MARKER:
+ case '.':
+ case '_':
+ break;
+ default:
+ if (!Character.isLetter(firstChar)) {
+ throw new IllegalArgumentException("Invalid first char (" + firstChar
+ + ") of cell reference or named range. Letter expected");
+ }
+ }
+ if (!Character.isDigit(str.charAt(len-1))) {
+ // no digits at end of str
+ return validateNamedRangeName(str);
+ }
+ Matcher cellRefPatternMatcher = CELL_REF_PATTERN.matcher(str);
+ if (!cellRefPatternMatcher.matches()) {
+ return validateNamedRangeName(str);
+ }
+ String lettersGroup = cellRefPatternMatcher.group(1);
+ String digitsGroup = cellRefPatternMatcher.group(2);
+ if (cellReferenceIsWithinRange(lettersGroup, digitsGroup)) {
+ // valid cell reference
+ return NameType.CELL;
+ }
+ // If str looks like a cell reference, but is out of (row/col) range, it is a valid
+ // named range name
+ // This behaviour is a little weird. For example, "IW123" is a valid named range name
+ // because the column "IW" is beyond the maximum "IV". Note - this behaviour is version
+ // dependent. In BIFF12, "IW123" is not a valid named range name, but in BIFF8 it is.
+ if (str.indexOf(ABSOLUTE_REFERENCE_MARKER) >= 0) {
+ // Of course, named range names cannot have '$'
+ return NameType.BAD_CELL_OR_NAMED_RANGE;
+ }
+ return NameType.NAMED_RANGE;
+ }
- private static int validateNamedRangeName(String str) {
+ private static int validateNamedRangeName(String str) {
+ Matcher colMatcher = COLUMN_REF_PATTERN.matcher(str);
+ if (colMatcher.matches()) {
+ String colStr = colMatcher.group(1);
+ if (isColumnWithnRange(colStr)) {
+ return NameType.COLUMN;
+ }
+ }
if (!NAMED_RANGE_NAME_PATTERN.matcher(str).matches()) {
return NameType.BAD_CELL_OR_NAMED_RANGE;
}
return NameType.NAMED_RANGE;
-
}
-
-
+
+
/**
* Used to decide whether a name of the form "[A-Z]*[0-9]*" that appears in a formula can be
* interpreted as a cell reference. Names of that form can be also used for sheets and/or
@@ -240,7 +253,7 @@ public class CellReference {
* <blockquote><table border="0" cellpadding="1" cellspacing="0"
* summary="Notable cases.">
* <tr><th>Input&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>
- * <th>Result&nbsp;</th></tr>
+ * <th>Result&nbsp;</th></tr>
* <tr><td>"A", "1"</td><td>true</td></tr>
* <tr><td>"a", "111"</td><td>true</td></tr>
* <tr><td>"A", "65536"</td><td>true</td></tr>
@@ -257,23 +270,13 @@ public class CellReference {
* @return <code>true</code> if the row and col parameters are within range of a BIFF8 spreadsheet.
*/
public static boolean cellReferenceIsWithinRange(String colStr, String rowStr) {
- int numberOfLetters = colStr.length();
- if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) {
- // "Sheet1" case etc
- return false; // that was easy
+ if (!isColumnWithnRange(colStr)) {
+ return false;
}
int nDigits = rowStr.length();
if(nDigits > BIFF8_LAST_ROW_TEXT_LEN) {
return false;
}
- if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) {
- if(colStr.toUpperCase().compareTo(BIFF8_LAST_COLUMN) > 0) {
- return false;
- }
- } else {
- // apparent column name has less chars than max
- // no need to check range
- }
if(nDigits == BIFF8_LAST_ROW_TEXT_LEN) {
// ASCII comparison is valid if digit count is same
@@ -288,87 +291,104 @@ public class CellReference {
return true;
}
+ private static boolean isColumnWithnRange(String colStr) {
+ int numberOfLetters = colStr.length();
+ if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) {
+ // "Sheet1" case etc
+ return false; // that was easy
+ }
+ if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) {
+ if(colStr.toUpperCase().compareTo(BIFF8_LAST_COLUMN) > 0) {
+ return false;
+ }
+ } else {
+ // apparent column name has less chars than max
+ // no need to check range
+ }
+ return true;
+ }
+
/**
- * Separates the row from the columns and returns an array of three Strings. The first element
- * is the sheet name. Only the first element may be null. The second element in is the column
- * name still in ALPHA-26 number format. The third element is the row.
- */
- private static String[] separateRefParts(String reference) {
-
- int plingPos = reference.lastIndexOf(SHEET_NAME_DELIMITER);
- String sheetName = parseSheetName(reference, plingPos);
- int start = plingPos+1;
+ * Separates the row from the columns and returns an array of three Strings. The first element
+ * is the sheet name. Only the first element may be null. The second element in is the column
+ * name still in ALPHA-26 number format. The third element is the row.
+ */
+ private static String[] separateRefParts(String reference) {
+
+ int plingPos = reference.lastIndexOf(SHEET_NAME_DELIMITER);
+ String sheetName = parseSheetName(reference, plingPos);
+ int start = plingPos+1;
- int length = reference.length();
+ int length = reference.length();
- int loc = start;
- // skip initial dollars
- if (reference.charAt(loc)==ABSOLUTE_REFERENCE_MARKER) {
- loc++;
- }
- // step over column name chars until first digit (or dollars) for row number.
- for (; loc < length; loc++) {
- char ch = reference.charAt(loc);
- if (Character.isDigit(ch) || ch == ABSOLUTE_REFERENCE_MARKER) {
- break;
- }
- }
- return new String[] {
- sheetName,
- reference.substring(start,loc),
- reference.substring(loc),
- };
- }
+ int loc = start;
+ // skip initial dollars
+ if (reference.charAt(loc)==ABSOLUTE_REFERENCE_MARKER) {
+ loc++;
+ }
+ // step over column name chars until first digit (or dollars) for row number.
+ for (; loc < length; loc++) {
+ char ch = reference.charAt(loc);
+ if (Character.isDigit(ch) || ch == ABSOLUTE_REFERENCE_MARKER) {
+ break;
+ }
+ }
+ return new String[] {
+ sheetName,
+ reference.substring(start,loc),
+ reference.substring(loc),
+ };
+ }
- private static String parseSheetName(String reference, int indexOfSheetNameDelimiter) {
- if(indexOfSheetNameDelimiter < 0) {
- return null;
- }
-
- boolean isQuoted = reference.charAt(0) == SPECIAL_NAME_DELIMITER;
- if(!isQuoted) {
- return reference.substring(0, indexOfSheetNameDelimiter);
- }
- int lastQuotePos = indexOfSheetNameDelimiter-1;
- if(reference.charAt(lastQuotePos) != SPECIAL_NAME_DELIMITER) {
- throw new RuntimeException("Mismatched quotes: (" + reference + ")");
- }
+ private static String parseSheetName(String reference, int indexOfSheetNameDelimiter) {
+ if(indexOfSheetNameDelimiter < 0) {
+ return null;
+ }
+
+ boolean isQuoted = reference.charAt(0) == SPECIAL_NAME_DELIMITER;
+ if(!isQuoted) {
+ return reference.substring(0, indexOfSheetNameDelimiter);
+ }
+ int lastQuotePos = indexOfSheetNameDelimiter-1;
+ if(reference.charAt(lastQuotePos) != SPECIAL_NAME_DELIMITER) {
+ throw new RuntimeException("Mismatched quotes: (" + reference + ")");
+ }
- // TODO - refactor cell reference parsing logic to one place.
- // Current known incarnations:
- // FormulaParser.GetName()
- // CellReference.parseSheetName() (here)
- // AreaReference.separateAreaRefs()
- // SheetNameFormatter.format() (inverse)
-
- StringBuffer sb = new StringBuffer(indexOfSheetNameDelimiter);
-
- for(int i=1; i<lastQuotePos; i++) { // Note boundaries - skip outer quotes
- char ch = reference.charAt(i);
- if(ch != SPECIAL_NAME_DELIMITER) {
- sb.append(ch);
- continue;
- }
- if(i < lastQuotePos) {
- if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) {
- // two consecutive quotes is the escape sequence for a single one
- i++; // skip this and keep parsing the special name
- sb.append(ch);
- continue;
- }
- }
- throw new RuntimeException("Bad sheet name quote escaping: (" + reference + ")");
- }
- return sb.toString();
- }
+ // TODO - refactor cell reference parsing logic to one place.
+ // Current known incarnations:
+ // FormulaParser.GetName()
+ // CellReference.parseSheetName() (here)
+ // AreaReference.separateAreaRefs()
+ // SheetNameFormatter.format() (inverse)
+
+ StringBuffer sb = new StringBuffer(indexOfSheetNameDelimiter);
+
+ for(int i=1; i<lastQuotePos; i++) { // Note boundaries - skip outer quotes
+ char ch = reference.charAt(i);
+ if(ch != SPECIAL_NAME_DELIMITER) {
+ sb.append(ch);
+ continue;
+ }
+ if(i < lastQuotePos) {
+ if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) {
+ // two consecutive quotes is the escape sequence for a single one
+ i++; // skip this and keep parsing the special name
+ sb.append(ch);
+ continue;
+ }
+ }
+ throw new RuntimeException("Bad sheet name quote escaping: (" + reference + ")");
+ }
+ return sb.toString();
+ }
- /**
- * Takes in a 0-based base-10 column and returns a ALPHA-26
- * representation.
- * eg column #3 -> D
- */
- protected static String convertNumToColString(int col) {
+ /**
+ * Takes in a 0-based base-10 column and returns a ALPHA-26
+ * representation.
+ * eg column #3 -> D
+ */
+ protected static String convertNumToColString(int col) {
// Excel counts column A as the 1st column, we
// treat it as the 0th one
int excelColNum = col + 1;
@@ -387,36 +407,36 @@ public class CellReference {
}
return colRef;
- }
+ }
+
+ /**
+ * Example return values:
+ * <table border="0" cellpadding="1" cellspacing="0" summary="Example return values">
+ * <tr><th align='left'>Result</th><th align='left'>Comment</th></tr>
+ * <tr><td>A1</td><td>Cell reference without sheet</td></tr>
+ * <tr><td>Sheet1!A1</td><td>Standard sheet name</td></tr>
+ * <tr><td>'O''Brien''s Sales'!A1'&nbsp;</td><td>Sheet name with special characters</td></tr>
+ * </table>
+ * @return the text representation of this cell reference as it would appear in a formula.
+ */
+ public String formatAsString() {
+ StringBuffer sb = new StringBuffer(32);
+ if(_sheetName != null) {
+ SheetNameFormatter.appendFormat(sb, _sheetName);
+ sb.append(SHEET_NAME_DELIMITER);
+ }
+ appendCellReference(sb);
+ return sb.toString();
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(formatAsString());
+ sb.append("]");
+ return sb.toString();
+ }
- /**
- * Example return values:
- * <table border="0" cellpadding="1" cellspacing="0" summary="Example return values">
- * <tr><th align='left'>Result</th><th align='left'>Comment</th></tr>
- * <tr><td>A1</td><td>Cell reference without sheet</td></tr>
- * <tr><td>Sheet1!A1</td><td>Standard sheet name</td></tr>
- * <tr><td>'O''Brien''s Sales'!A1'&nbsp;</td><td>Sheet name with special characters</td></tr>
- * </table>
- * @return the text representation of this cell reference as it would appear in a formula.
- */
- public String formatAsString() {
- StringBuffer sb = new StringBuffer(32);
- if(_sheetName != null) {
- SheetNameFormatter.appendFormat(sb, _sheetName);
- sb.append(SHEET_NAME_DELIMITER);
- }
- appendCellReference(sb);
- return sb.toString();
- }
-
- public String toString() {
- StringBuffer sb = new StringBuffer(64);
- sb.append(getClass().getName()).append(" [");
- sb.append(formatAsString());
- sb.append("]");
- return sb.toString();
- }
-
/**
* Returns the three parts of the cell reference, the
* Sheet name (or null if none supplied), the 1 based
@@ -433,18 +453,18 @@ public class CellReference {
};
}
- /**
- * Appends cell reference with '$' markers for absolute values as required.
- * Sheet name is not included.
- */
- protected void appendCellReference(StringBuffer sb) {
- if(_isColAbs) {
- sb.append(ABSOLUTE_REFERENCE_MARKER);
- }
- sb.append( convertNumToColString(_colIndex));
- if(_isRowAbs) {
- sb.append(ABSOLUTE_REFERENCE_MARKER);
- }
- sb.append(_rowIndex+1);
- }
+ /**
+ * Appends cell reference with '$' markers for absolute values as required.
+ * Sheet name is not included.
+ */
+ /* package */ void appendCellReference(StringBuffer sb) {
+ if(_isColAbs) {
+ sb.append(ABSOLUTE_REFERENCE_MARKER);
+ }
+ sb.append( convertNumToColString(_colIndex));
+ if(_isRowAbs) {
+ sb.append(ABSOLUTE_REFERENCE_MARKER);
+ }
+ sb.append(_rowIndex+1);
+ }
}