You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by jo...@apache.org on 2008/09/28 04:04:32 UTC

svn commit: r699761 - in /poi/trunk/src: documentation/content/xdocs/ java/org/apache/poi/hssf/model/ java/org/apache/poi/hssf/record/ java/org/apache/poi/hssf/record/formula/ java/org/apache/poi/hssf/usermodel/ java/org/apache/poi/ss/formula/ testcase...

Author: josh
Date: Sat Sep 27 19:04:31 2008
New Revision: 699761

URL: http://svn.apache.org/viewvc?rev=699761&view=rev
Log:
Bug 45865 - modified Formula Parser/Evaluator to handle cross-worksheet formulas

Added:
    poi/trunk/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java
    poi/trunk/src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java
    poi/trunk/src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls   (with props)
    poi/trunk/src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls   (with props)
Modified:
    poi/trunk/src/documentation/content/xdocs/changes.xml
    poi/trunk/src/documentation/content/xdocs/status.xml
    poi/trunk/src/java/org/apache/poi/hssf/model/LinkTable.java
    poi/trunk/src/java/org/apache/poi/hssf/model/Workbook.java
    poi/trunk/src/java/org/apache/poi/hssf/record/ExternSheetRecord.java
    poi/trunk/src/java/org/apache/poi/hssf/record/SupBookRecord.java
    poi/trunk/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
    poi/trunk/src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java
    poi/trunk/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
    poi/trunk/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java
    poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
    poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
    poi/trunk/src/java/org/apache/poi/ss/formula/CellLocation.java
    poi/trunk/src/java/org/apache/poi/ss/formula/EvaluationCache.java
    poi/trunk/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
    poi/trunk/src/java/org/apache/poi/ss/formula/FormulaParser.java
    poi/trunk/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java
    poi/trunk/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java
    poi/trunk/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
    poi/trunk/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
    poi/trunk/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java
    poi/trunk/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java

Modified: poi/trunk/src/documentation/content/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/poi/trunk/src/documentation/content/xdocs/changes.xml?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/documentation/content/xdocs/changes.xml (original)
+++ poi/trunk/src/documentation/content/xdocs/changes.xml Sat Sep 27 19:04:31 2008
@@ -37,6 +37,7 @@
 
 		<!-- Don't forget to update status.xml too! -->
         <release version="3.2-alpha1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="add">45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas</action>
            <action dev="POI-DEVELOPERS" type="add">Optimised the FormulaEvaluator to take cell dependencies into account</action>
            <action dev="POI-DEVELOPERS" type="add">16936 - Initial support for whole-row cell styling</action>
            <action dev="POI-DEVELOPERS" type="add">Update hssf.extractor.ExcelExtractor to optionally output blank cells too</action>

Modified: poi/trunk/src/documentation/content/xdocs/status.xml
URL: http://svn.apache.org/viewvc/poi/trunk/src/documentation/content/xdocs/status.xml?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/documentation/content/xdocs/status.xml (original)
+++ poi/trunk/src/documentation/content/xdocs/status.xml Sat Sep 27 19:04:31 2008
@@ -34,6 +34,7 @@
 	<!-- Don't forget to update changes.xml too! -->
     <changes>
         <release version="3.2-alpha1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="add">45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas</action>
            <action dev="POI-DEVELOPERS" type="add">Optimised the FormulaEvaluator to take cell dependencies into account</action>
            <action dev="POI-DEVELOPERS" type="add">16936 - Initial support for whole-row cell styling</action>
            <action dev="POI-DEVELOPERS" type="add">Update hssf.extractor.ExcelExtractor to optionally output blank cells too</action>

Modified: poi/trunk/src/java/org/apache/poi/hssf/model/LinkTable.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/model/LinkTable.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/model/LinkTable.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/model/LinkTable.java Sat Sep 27 19:04:31 2008
@@ -29,6 +29,7 @@
 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 @@
 			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 @@
 		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 @@
 		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 @@
 		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);
 	}
 
 

Modified: poi/trunk/src/java/org/apache/poi/hssf/model/Workbook.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/model/Workbook.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/model/Workbook.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/model/Workbook.java Sat Sep 27 19:04:31 2008
@@ -26,6 +26,7 @@
 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 @@
         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 @@
      * @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 @@
      *  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 @@
      *  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 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 @@
         }
         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 @@
      * @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
      */

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/ExternSheetRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/ExternSheetRecord.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/ExternSheetRecord.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/ExternSheetRecord.java Sat Sep 27 19:04:31 2008
@@ -250,10 +250,13 @@
 		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;
 			}

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/SupBookRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/SupBookRecord.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/SupBookRecord.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/SupBookRecord.java Sat Sep 27 19:04:31 2008
@@ -221,8 +221,33 @@
     {
         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();

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java Sat Sep 27 19:04:31 2008
@@ -18,9 +18,9 @@
 package org.apache.poi.hssf.record.formula;
 
 import org.apache.poi.hssf.record.RecordInputStream;
-import org.apache.poi.hssf.usermodel.HSSFWorkbook;
-import org.apache.poi.ss.formula.WorkbookDependentFormula;
+import org.apache.poi.ss.formula.ExternSheetReferenceToken;
 import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
+import org.apache.poi.ss.formula.WorkbookDependentFormula;
 import org.apache.poi.util.LittleEndian;
 
 /**
@@ -32,7 +32,7 @@
  * @author Jason Height (jheight at chariot dot net dot au)
  * @version 1.0-pre
  */
-public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFormula {
+public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFormula, ExternSheetReferenceToken {
 	public final static byte sid = 0x3b;
 	private final static int SIZE = 11; // 10 + 1 for Ptg
 	
@@ -77,8 +77,8 @@
 		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) {

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java Sat Sep 27 19:04:31 2008
@@ -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 @@
 	}
 
 	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);

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java Sat Sep 27 19:04:31 2008
@@ -19,8 +19,9 @@
 
 import org.apache.poi.hssf.record.RecordInputStream;
 import org.apache.poi.hssf.util.CellReference;
-import org.apache.poi.ss.formula.WorkbookDependentFormula;
+import org.apache.poi.ss.formula.ExternSheetReferenceToken;
 import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
+import org.apache.poi.ss.formula.WorkbookDependentFormula;
 import org.apache.poi.util.LittleEndian;
 
 /**
@@ -31,7 +32,7 @@
  * @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 @@
         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;
     }
 

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java Sat Sep 27 19:04:31 2008
@@ -66,6 +66,22 @@
 			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();

Modified: poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java Sat Sep 27 19:04:31 2008
@@ -39,6 +39,9 @@
 		int sheetIndex = _uBook.getSheetIndex(sheetName);
 		return _iBook.checkExternSheet(sheetIndex);
 	}
+	public int getExternalSheetIndex(String workbookName, String sheetName) {
+		return _iBook.getExternalSheetIndex(workbookName, sheetName);
+	}
 
 	public EvaluationName getName(int index) {
 		return new Name(_iBook.getNameRecord(index), index);
@@ -57,6 +60,9 @@
 	public int getSheetIndex(HSSFSheet sheet) {
 		return _uBook.getSheetIndex(sheet);
 	}
+	public int getSheetIndex(String sheetName) {
+		return _uBook.getSheetIndex(sheetName);
+	}
 
 	public String getSheetName(int sheetIndex) {
 		return _uBook.getSheetName(sheetIndex);
@@ -75,8 +81,12 @@
 	}
 	public int convertFromExternSheetIndex(int externSheetIndex) {
 		return _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);
-}
+	}
 
+	public ExternalSheet getExternalSheet(int externSheetIndex) {
+		return _iBook.getExternalSheet(externSheetIndex);
+	}
+	
 	public HSSFWorkbook getWorkbook() {
 		return _uBook;
 	}

Modified: poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java Sat Sep 27 19:04:31 2008
@@ -25,6 +25,7 @@
 import org.apache.poi.hssf.record.formula.eval.NumberEval;
 import org.apache.poi.hssf.record.formula.eval.StringEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment;
 import org.apache.poi.ss.formula.WorkbookEvaluator;
 
 /**
@@ -53,6 +54,21 @@
 	public 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

Modified: poi/trunk/src/java/org/apache/poi/ss/formula/CellLocation.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/formula/CellLocation.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/formula/CellLocation.java (original)
+++ poi/trunk/src/java/org/apache/poi/ss/formula/CellLocation.java Sat Sep 27 19:04:31 2008
@@ -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 @@
 
 	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 @@
 	 * @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() {

Added: poi/trunk/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java?rev=699761&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java Sat Sep 27 19:04:31 2008
@@ -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;
+	}
+}

Modified: poi/trunk/src/java/org/apache/poi/ss/formula/EvaluationCache.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/formula/EvaluationCache.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/formula/EvaluationCache.java (original)
+++ poi/trunk/src/java/org/apache/poi/ss/formula/EvaluationCache.java Sat Sep 27 19:04:31 2008
@@ -81,13 +81,7 @@
 						+ 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 @@
 		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 @@
 	 * @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 @@
 			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;

Modified: poi/trunk/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java (original)
+++ poi/trunk/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java Sat Sep 27 19:04:31 2008
@@ -31,12 +31,36 @@
  */
 public interface EvaluationWorkbook {
 	String getSheetName(int sheetIndex);
+	/**
+	 * @return -1 if the specified sheet is from a different book
+	 */
 	int getSheetIndex(HSSFSheet sheet);
+	int getSheetIndex(String sheetName);
 
 	HSSFSheet getSheet(int sheetIndex);
 
+	/**
+	 * @return <code>null</code> if externSheetIndex refers to a sheet inside the current workbook
+	 */
+	ExternalSheet getExternalSheet(int externSheetIndex);
 	int convertFromExternSheetIndex(int externSheetIndex);
 	EvaluationName getName(NamePtg namePtg);
 	String resolveNameXText(NameXPtg ptg);
 	Ptg[] getFormulaTokens(HSSFCell cell);
+	
+	class ExternalSheet {
+		private final String _workbookName;
+		private final String _sheetName;
+
+		public ExternalSheet(String workbookName, String sheetName) {
+			_workbookName = workbookName;
+			_sheetName = sheetName;
+		}
+		public String getWorkbookName() {
+			return _workbookName;
+		}
+		public String getSheetName() {
+			return _sheetName;
+		}
+	}
 }

Added: poi/trunk/src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java?rev=699761&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java (added)
+++ poi/trunk/src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java Sat Sep 27 19:04:31 2008
@@ -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();
+}

Modified: poi/trunk/src/java/org/apache/poi/ss/formula/FormulaParser.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/formula/FormulaParser.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/formula/FormulaParser.java (original)
+++ poi/trunk/src/java/org/apache/poi/ss/formula/FormulaParser.java Sat Sep 27 19:04:31 2008
@@ -257,7 +257,7 @@
      */
     private Identifier parseIdentifier() {
         StringBuffer sb = new StringBuffer();
-        if (!IsAlpha(look) && look != '\'') {
+        if (!IsAlpha(look) && look != '\'' && look != '[') {
             throw expected("Name");
         }
         boolean isQuoted = look == '\''; 
@@ -276,7 +276,7 @@
         } else {
             // allow for any sequence of dots and identifier chars
             // special case of two consecutive dots is best treated in the calling code
-            while (IsAlNum(look) || look == '.') {
+            while (IsAlNum(look) || look == '.' || look == '[' || look == ']') {
                 sb.append(look);
                 GetChar();
             }
@@ -368,7 +368,7 @@
             // 3-D ref
             // this code assumes iden is a sheetName
             // TODO - handle <book name> ! <named range name>
-            int externIdx = book.getExternalSheetIndex(iden.getName());
+            int externIdx = getExternalSheetIndex(iden.getName());
             String secondIden = parseUnquotedIdentifier();
             AreaReference areaRef = parseArea(secondIden);
             if (areaRef == null) {
@@ -418,6 +418,17 @@
                     + name + "' is not a range as expected");
     }
 
+    private int getExternalSheetIndex(String name) {
+        if (name.charAt(0) == '[') {
+            // we have a sheet name qualified with workbook name e.g. '[MyData.xls]Sheet1'
+            int pos = name.lastIndexOf(']'); // safe because sheet names never have ']'
+            String wbName = name.substring(1, pos);
+            String sheetName = name.substring(pos+1);
+            return book.getExternalSheetIndex(wbName, sheetName);
+        }
+        return book.getExternalSheetIndex(name);
+    }
+
     /**
      * @param name an 'identifier' like string (i.e. contains alphanums, and dots)
      * @return <code>null</code> if name cannot be split at a dot
@@ -656,7 +667,7 @@
                 Match('}');
                 return arrayNode;
         }
-        if (IsAlpha(look) || look == '\''){
+        if (IsAlpha(look) || look == '\'' || look == '['){
             return parseFunctionReferenceOrName();
         }
         // else - assume number

Modified: poi/trunk/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java (original)
+++ poi/trunk/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java Sat Sep 27 19:04:31 2008
@@ -32,6 +32,16 @@
 	 */
 	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);
 }

Modified: poi/trunk/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java (original)
+++ poi/trunk/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java Sat Sep 27 19:04:31 2008
@@ -19,6 +19,7 @@
 
 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 @@
  */
 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);

Modified: poi/trunk/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java (original)
+++ poi/trunk/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java Sat Sep 27 19:04:31 2008
@@ -63,6 +63,7 @@
 import org.apache.poi.hssf.usermodel.HSSFRow;
 import org.apache.poi.hssf.usermodel.HSSFSheet;
 import org.apache.poi.hssf.util.CellReference;
+import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
 
 /**
  * Evaluates formula cells.<p/>
@@ -75,13 +76,14 @@
  *
  * @author Josh Micich
  */
-public class WorkbookEvaluator {
+public final class WorkbookEvaluator {
 
 	private final EvaluationWorkbook _workbook;
-	private final EvaluationCache _cache;
+	private EvaluationCache _cache;
 
 	private final IEvaluationListener _evaluationListener;
 	private final Map _sheetIndexesBySheet;
+	private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
 
 	public WorkbookEvaluator(EvaluationWorkbook workbook) {
 		this (workbook, null);
@@ -91,6 +93,7 @@
 		_evaluationListener = evaluationListener;
 		_cache = new EvaluationCache(evaluationListener);
 		_sheetIndexesBySheet = new IdentityHashMap();
+		_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
 	}
 
 	/**
@@ -108,7 +111,22 @@
 			System.out.println(s);
 		}
 	}
+	/* package */ void attachToEnvironment(CollaboratingWorkbooksEnvironment collaboratingWorkbooksEnvironment, EvaluationCache cache) {
+		_collaboratingWorkbookEnvironment = collaboratingWorkbooksEnvironment;
+		_cache = cache;
+	}
+	/* package */ CollaboratingWorkbooksEnvironment getEnvironment() {
+		return _collaboratingWorkbookEnvironment;
+	}
 
+	/* package */ void detachFromEnvironment() {
+		_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
+		_cache = new EvaluationCache(_evaluationListener);
+	}
+	/* package */ IEvaluationListener getEvaluationListener() {
+		return _evaluationListener;
+	}
+	 
 	/**
 	 * Should be called whenever there are changes to input cells in the evaluated workbook.
 	 * Failure to call this method after changing cell values will cause incorrect behaviour
@@ -130,7 +148,7 @@
 			throw new IllegalArgumentException("value must not be null");
 		}
 		int sheetIndex = getSheetIndex(sheet);
-		_cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
+		_cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
 
 	}
 	/**
@@ -139,13 +157,17 @@
 	 */
 	public void notifySetFormula(HSSFSheet sheet, int rowIndex, int columnIndex) {
 		int sheetIndex = getSheetIndex(sheet);
-		_cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
+		_cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
 
 	}
 	private int getSheetIndex(HSSFSheet sheet) {
 		Integer result = (Integer) _sheetIndexesBySheet.get(sheet);
 		if (result == null) {
-			result = new Integer(_workbook.getSheetIndex(sheet));
+			int sheetIndex = _workbook.getSheetIndex(sheet);
+			if (sheetIndex < 0) {
+				throw new RuntimeException("Specified sheet from a different book");
+			}
+			result = new Integer(sheetIndex);
 			_sheetIndexesBySheet.put(sheet, result);
 		}
 		return result.intValue();
@@ -153,7 +175,7 @@
 
 	public ValueEval evaluate(HSSFCell srcCell) {
 		int sheetIndex = getSheetIndex(srcCell.getSheet());
-		CellLocation cellLoc = new CellLocation(sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
+		CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
 		return internalEvaluate(srcCell, cellLoc, new EvaluationTracker(_cache));
 	}
 
@@ -342,6 +364,20 @@
 		}
 		return operation.evaluate(ops, srcRowNum, (short)srcColNum);
 	}
+	private SheetRefEvaluator createExternSheetRefEvaluator(EvaluationTracker tracker,
+			ExternSheetReferenceToken ptg) {
+		int externSheetIndex = ptg.getExternSheetIndex();
+		ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
+		if (externalSheet != null) {
+			WorkbookEvaluator otherEvaluator = _collaboratingWorkbookEnvironment.getWorkbookEvaluator(externalSheet.getWorkbookName());
+			EvaluationWorkbook otherBook = otherEvaluator._workbook;
+			int otherSheetIndex = otherBook.getSheetIndex(externalSheet.getSheetName());
+			return new SheetRefEvaluator(otherEvaluator, tracker, otherBook, otherSheetIndex);
+		}
+		int otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex);
+		return new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
+		
+	}
 
 	/**
 	 * returns an appropriate Eval impl instance for the Ptg. The Ptg must be
@@ -350,6 +386,8 @@
 	 * passed here!
 	 */
 	private Eval getEvalForPtg(Ptg ptg, int sheetIndex, EvaluationTracker tracker) {
+		//  consider converting all these (ptg instanceof XxxPtg) expressions to (ptg.getClass() == XxxPtg.class)
+
 		if (ptg instanceof NamePtg) {
 			// named ranges, macro functions
 			NamePtg namePtg = (NamePtg) ptg;
@@ -388,14 +426,12 @@
 		}
 		if (ptg instanceof Ref3DPtg) {
 			Ref3DPtg refPtg = (Ref3DPtg) ptg;
-			int otherSheetIndex = _workbook.convertFromExternSheetIndex(refPtg.getExternSheetIndex());
-			SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
+			SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, refPtg);
 			return new LazyRefEval(refPtg, sre);
 		}
 		if (ptg instanceof Area3DPtg) {
 			Area3DPtg aptg = (Area3DPtg) ptg;
-			int otherSheetIndex = _workbook.convertFromExternSheetIndex(aptg.getExternSheetIndex());
-			SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
+			SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, aptg);
 			return new LazyAreaEval(aptg, sre);
 		}
 		SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, sheetIndex);
@@ -435,7 +471,7 @@
 		} 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);
 	}

Added: poi/trunk/src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls?rev=699761&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: poi/trunk/src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls?rev=699761&view=auto
==============================================================================
Binary file - no diff available.

Propchange: poi/trunk/src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Modified: poi/trunk/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java (original)
+++ poi/trunk/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java Sat Sep 27 19:04:31 2008
@@ -43,11 +43,13 @@
 import org.apache.poi.hssf.record.formula.PercentPtg;
 import org.apache.poi.hssf.record.formula.PowerPtg;
 import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.Ref3DPtg;
 import org.apache.poi.hssf.record.formula.RefPtg;
 import org.apache.poi.hssf.record.formula.StringPtg;
 import org.apache.poi.hssf.record.formula.SubtractPtg;
 import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
 import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
+import org.apache.poi.hssf.usermodel.FormulaExtractor;
 import org.apache.poi.hssf.usermodel.HSSFCell;
 import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
 import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
@@ -913,4 +915,33 @@
 
 		assertEquals("'true'!B2", cell.getCellFormula());
 	}
+	
+	public void testParseExternalWorkbookReference() {
+		HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls");
+		HSSFCell cell = wbA.getSheetAt(0).getRow(0).getCell(0);
+
+		// make sure formula in sample is as expected
+		assertEquals("[multibookFormulaB.xls]BSheet1!B1", cell.getCellFormula());
+		Ptg[] expectedPtgs = FormulaExtractor.getPtgs(cell);
+		confirmSingle3DRef(expectedPtgs, 1);
+		
+		// now try (re-)parsing the formula
+		Ptg[] actualPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]BSheet1!B1", wbA);
+		confirmSingle3DRef(actualPtgs, 1); // externalSheetIndex 1 -> BSheet1
+		
+		// try parsing a formula pointing to a different external sheet
+		Ptg[] otherPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]AnotherSheet!B1", wbA);
+		confirmSingle3DRef(otherPtgs, 0); // externalSheetIndex 0 -> AnotherSheet
+		
+		// try setting the same formula in a cell
+		cell.setCellFormula("[multibookFormulaB.xls]AnotherSheet!B1");
+		assertEquals("[multibookFormulaB.xls]AnotherSheet!B1", cell.getCellFormula());
+	}
+	private static void confirmSingle3DRef(Ptg[] ptgs, int expectedExternSheetIndex) {
+		assertEquals(1, ptgs.length);
+		Ptg ptg0 = ptgs[0];
+		assertEquals(Ref3DPtg.class, ptg0.getClass());
+		assertEquals(expectedExternSheetIndex, ((Ref3DPtg)ptg0).getExternSheetIndex());
+	}
+	
 }

Modified: poi/trunk/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java (original)
+++ poi/trunk/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java Sat Sep 27 19:04:31 2008
@@ -78,7 +78,7 @@
 
         assertEquals( 34, record.getRecordSize() );  //sid+size+data
         
-        assertEquals("testURL", record.getURL().getString());
+        assertEquals("testURL", record.getURL());
         UnicodeString[] sheetNames = record.getSheetNames();
         assertEquals(2, sheetNames.length);
         assertEquals("Sheet1", sheetNames[0].getString());

Modified: poi/trunk/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java?rev=699761&r1=699760&r2=699761&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java (original)
+++ poi/trunk/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java Sat Sep 27 19:04:31 2008
@@ -19,6 +19,7 @@
 
 import junit.framework.TestCase;
 
+import org.apache.poi.hssf.HSSFTestDataSamples;
 import org.apache.poi.hssf.record.formula.AreaErrPtg;
 import org.apache.poi.hssf.record.formula.AttrPtg;
 import org.apache.poi.hssf.record.formula.DeletedArea3DPtg;
@@ -29,36 +30,40 @@
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
 import org.apache.poi.hssf.record.formula.eval.NumberEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 
 /**
- * Tests {@link WorkbookEvaluator}. 
+ * Tests {@link WorkbookEvaluator}.
  *
  * @author Josh Micich
  */
 public class TestWorkbookEvaluator extends TestCase {
-	
+
 	/**
 	 * Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing
-	 * the whole formula which converts tAttrSum to tFuncVar("SUM") ) 
+	 * the whole formula which converts tAttrSum to tFuncVar("SUM") )
 	 */
 	public void testAttrSum() {
-		
+
 		Ptg[] ptgs = {
 			new IntPtg(42),
 			AttrPtg.SUM,
 		};
-		
+
 		ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
 		assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
 	}
-	
+
 	/**
 	 * Make sure that the evaluator can directly handle (deleted) ref error tokens
-	 * (instead of relying on re-parsing the whole formula which converts these 
-	 * to the error constant #REF! ) 
+	 * (instead of relying on re-parsing the whole formula which converts these
+	 * to the error constant #REF! )
 	 */
 	public void testRefErr() {
-		
+
 		confirmRefErr(new RefErrorPtg());
 		confirmRefErr(new AreaErrPtg());
 		confirmRefErr(new DeletedRef3DPtg(0));
@@ -68,25 +73,82 @@
 		Ptg[] ptgs = {
 			ptg,
 		};
-		
+
 		ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
 		assertEquals(ErrorEval.REF_INVALID, result);
 	}
-	
+
 	/**
 	 * Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing
-	 * the whole formula which converts tAttrSum to tFuncVar("SUM") ) 
+	 * the whole formula which converts tAttrSum to tFuncVar("SUM") )
 	 */
 	public void testMemFunc() {
-		
+
 		Ptg[] ptgs = {
 			new IntPtg(42),
 			AttrPtg.SUM,
 		};
-		
+
 		ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
 		assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
 	}
-	
-	
+
+
+	public void testEvaluateMultipleWorkbooks() {
+		HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls");
+		HSSFWorkbook wbB = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaB.xls");
+
+		HSSFFormulaEvaluator evaluatorA = new HSSFFormulaEvaluator(wbA);
+		HSSFFormulaEvaluator evaluatorB = new HSSFFormulaEvaluator(wbB);
+
+		// Hook up the workbook evaluators to enable evaluation of formulas across books
+		String[] bookNames = { "multibookFormulaA.xls", "multibookFormulaB.xls", };
+		HSSFFormulaEvaluator[] evaluators = { evaluatorA, evaluatorB, };
+		HSSFFormulaEvaluator.setupEnvironment(bookNames, evaluators);
+
+		HSSFCell cell;
+
+		HSSFSheet aSheet1 = wbA.getSheetAt(0);
+		HSSFSheet bSheet1 = wbB.getSheetAt(0);
+
+		// Simple case - single link from wbA to wbB
+		confirmFormula(wbA, 0, 0, 0, "[multibookFormulaB.xls]BSheet1!B1");
+		cell = aSheet1.getRow(0).getCell(0);
+		confirmEvaluation(35, evaluatorA, cell);
+
+
+		// more complex case - back link into wbA
+		// [wbA]ASheet1!A2 references (among other things) [wbB]BSheet1!B2
+		confirmFormula(wbA, 0, 1, 0, "[multibookFormulaB.xls]BSheet1!$B$2+2*A3");
+		// [wbB]BSheet1!B2 references (among other things) [wbA]AnotherSheet!A1:B2
+		confirmFormula(wbB, 0, 1, 1, "SUM([multibookFormulaA.xls]AnotherSheet!$A$1:$B$2)+B3");
+
+		cell = aSheet1.getRow(1).getCell(0);
+		confirmEvaluation(264, evaluatorA, cell);
+
+		// change [wbB]BSheet1!B3 (from 50 to 60)
+		bSheet1.getRow(2).getCell(1).setCellValue(60);
+		evaluatorB.setCachedPlainValue(bSheet1, 2, 1, new NumberEval(60));
+		confirmEvaluation(274, evaluatorA, cell);
+
+		// change [wbA]ASheet1!A3 (from 100 to 80)
+		aSheet1.getRow(2).getCell(0).setCellValue(80);
+		evaluatorA.setCachedPlainValue(aSheet1, 2, 0, new NumberEval(80));
+		confirmEvaluation(234, evaluatorA, cell);
+
+		// change [wbA]AnotherSheet!A1 (from 2 to 3)
+		wbA.getSheetAt(1).getRow(0).getCell(0).setCellValue(3);
+		evaluatorA.setCachedPlainValue(wbA.getSheetAt(1), 0, 0, new NumberEval(3));
+		confirmEvaluation(235, evaluatorA, cell);
+	}
+
+	private static void confirmEvaluation(double expectedValue, HSSFFormulaEvaluator fe, HSSFCell cell) {
+		assertEquals(expectedValue, fe.evaluate(cell).getNumberValue(), 0.0);
+	}
+
+	private static void confirmFormula(HSSFWorkbook wb, int sheetIndex, int rowIndex, int columnIndex,
+			String expectedFormula) {
+		HSSFCell cell = wb.getSheetAt(sheetIndex).getRow(rowIndex).getCell(columnIndex);
+		assertEquals(expectedFormula, cell.getCellFormula());
+	}
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org