]> source.dussan.org Git - poi.git/commitdiff
Bug 45865 - modified Formula Parser/Evaluator to handle cross-worksheet formulas
authorJosh Micich <josh@apache.org>
Sun, 28 Sep 2008 02:04:31 +0000 (02:04 +0000)
committerJosh Micich <josh@apache.org>
Sun, 28 Sep 2008 02:04:31 +0000 (02:04 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@699761 13f79535-47bb-0310-9956-ffa450edef68

26 files changed:
src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/model/LinkTable.java
src/java/org/apache/poi/hssf/model/Workbook.java
src/java/org/apache/poi/hssf/record/ExternSheetRecord.java
src/java/org/apache/poi/hssf/record/SupBookRecord.java
src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
src/java/org/apache/poi/hssf/record/formula/ExternSheetNameResolver.java
src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java
src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
src/java/org/apache/poi/ss/formula/CellLocation.java
src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/EvaluationCache.java
src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/FormulaParser.java
src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java
src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java
src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java
src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java

index c054f65be1cd6788634d0354a9bc6b6025c15ed9..ecf151967eab0872ff8bf69d003ae796c51746ab 100644 (file)
@@ -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>
index b416d344faf3e16abeeba41eca43f2e347e59d1f..e3c96986007d6650350a5c2992508196ccbcf595 100644 (file)
@@ -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>
index 2caab0fb561543e1653df91f4a74aef7f3e2c7cd..998712e5e8875b43bf6ce7aac07279a410b4374d 100755 (executable)
@@ -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);
        }
 
 
index dbf7ecf7e148e35e213279ff409dc2fc74e902ba..0728d0e29ffdeb37d72cedcac65e7fa15ff494f3 100644 (file)
@@ -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
      */
index c4f00581b2e13eb5be9373a2555a8da8ea42d3c7..64f86c3f124f0ca97407d2990107714fc0e1b17e 100644 (file)
@@ -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;
                        }
index c75e2db8944fef9f3231c29b5ecf1cf1ae3d1129..b4e46c6fdc8356024ea2499b94e8d2445d515afe 100644 (file)
@@ -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();
index 46ff3382fec2ff8aefad2656f8ecd251697da765..58cdc5b275bfc21aff88643fe9cc5d23bf8aee52 100644 (file)
@@ -18,9 +18,9 @@
 package org.apache.poi.hssf.record.formula;
 
 import org.apache.poi.hssf.record.RecordInputStream;
-import org.apache.poi.hssf.usermodel.HSSFWorkbook;
-import org.apache.poi.ss.formula.WorkbookDependentFormula;
+import org.apache.poi.ss.formula.ExternSheetReferenceToken;
 import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
+import org.apache.poi.ss.formula.WorkbookDependentFormula;
 import org.apache.poi.util.LittleEndian;
 
 /**
@@ -32,7 +32,7 @@ import org.apache.poi.util.LittleEndian;
  * @author Jason Height (jheight at chariot dot net dot au)
  * @version 1.0-pre
  */
-public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFormula {
+public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFormula, ExternSheetReferenceToken {
        public final static byte sid = 0x3b;
        private final static int SIZE = 11; // 10 + 1 for Ptg
        
@@ -77,8 +77,8 @@ public final class Area3DPtg extends AreaPtgBase implements WorkbookDependentFor
                return SIZE;
        }
 
-       public short getExternSheetIndex() {
-               return (short)field_1_index_extern_sheet;
+       public int getExternSheetIndex() {
+               return field_1_index_extern_sheet;
        }
 
        public void setExternSheetIndex(int index) {
index 412c110d3a14382ff4ed473664c3afe6c8ed25c5..dadbcb8702fb1374ce5374db4ed2611923c56e33 100644 (file)
@@ -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);
index 7e780b823b883c71fae59ac9f3460542d103067f..217990d73372a2dec46d6a1709eca9511ffef76f 100644 (file)
@@ -19,8 +19,9 @@ package org.apache.poi.hssf.record.formula;
 
 import org.apache.poi.hssf.record.RecordInputStream;
 import org.apache.poi.hssf.util.CellReference;
-import org.apache.poi.ss.formula.WorkbookDependentFormula;
+import org.apache.poi.ss.formula.ExternSheetReferenceToken;
 import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
+import org.apache.poi.ss.formula.WorkbookDependentFormula;
 import org.apache.poi.util.LittleEndian;
 
 /**
@@ -31,7 +32,7 @@ import org.apache.poi.util.LittleEndian;
  * @author Jason Height (jheight at chariot dot net dot au)
  * @version 1.0-pre
  */
-public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormula {
+public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormula, ExternSheetReferenceToken {
     public final static byte sid  = 0x3a;
 
     private final static int  SIZE = 7; // 6 + 1 for Ptg
@@ -75,11 +76,11 @@ public final class Ref3DPtg extends RefPtgBase implements WorkbookDependentFormu
         return SIZE;
     }
 
-    public int getExternSheetIndex(){
+    public int getExternSheetIndex() {
         return field_1_index_extern_sheet;
     }
 
-    public void setExternSheetIndex(int index){
+    public void setExternSheetIndex(int index) {
         field_1_index_extern_sheet = index;
     }
 
index 361dc1b6a95bb13e32182e4a3cc7c155498dda9c..fd822c39863ad872e6b0a0f98cd132dc96d4c459 100755 (executable)
@@ -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();
index 6811e49be20f086d4022bf52012dbcdef5218812..5d67606d3df2fc2e3f7325b75fc891c1dc3c67e7 100644 (file)
@@ -39,6 +39,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
                int sheetIndex = _uBook.getSheetIndex(sheetName);\r
                return _iBook.checkExternSheet(sheetIndex);\r
        }\r
+       public int getExternalSheetIndex(String workbookName, String sheetName) {\r
+               return _iBook.getExternalSheetIndex(workbookName, sheetName);\r
+       }\r
 \r
        public EvaluationName getName(int index) {\r
                return new Name(_iBook.getNameRecord(index), index);\r
@@ -57,6 +60,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
        public int getSheetIndex(HSSFSheet sheet) {\r
                return _uBook.getSheetIndex(sheet);\r
        }\r
+       public int getSheetIndex(String sheetName) {\r
+               return _uBook.getSheetIndex(sheetName);\r
+       }\r
 \r
        public String getSheetName(int sheetIndex) {\r
                return _uBook.getSheetName(sheetIndex);\r
@@ -75,8 +81,12 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
        }\r
        public int convertFromExternSheetIndex(int externSheetIndex) {\r
                return _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);\r
-}\r
+       }\r
 \r
+       public ExternalSheet getExternalSheet(int externSheetIndex) {\r
+               return _iBook.getExternalSheet(externSheetIndex);\r
+       }\r
+       \r
        public HSSFWorkbook getWorkbook() {\r
                return _uBook;\r
        }\r
index ab75f8ba88be195b3ac4526debad6b325cbeaad5..a44999f0ead845dc61156c3528572d085c2d1d08 100644 (file)
@@ -25,6 +25,7 @@ import org.apache.poi.hssf.record.formula.eval.ErrorEval;
 import org.apache.poi.hssf.record.formula.eval.NumberEval;\r
 import org.apache.poi.hssf.record.formula.eval.StringEval;\r
 import org.apache.poi.hssf.record.formula.eval.ValueEval;\r
+import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment;\r
 import org.apache.poi.ss.formula.WorkbookEvaluator;\r
 \r
 /**\r
@@ -53,6 +54,21 @@ public class HSSFFormulaEvaluator {
        public HSSFFormulaEvaluator(HSSFWorkbook workbook) {\r
                _bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook));\r
        }\r
+       \r
+       /**\r
+        * Coordinates several formula evaluators together so that formulas that involve external\r
+        * references can be evaluated.\r
+        * @param workbookNames the simple file names used to identify the workbooks in formulas\r
+        * with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1")\r
+        * @param evaluators all evaluators for the full set of workbooks required by the formulas. \r
+        */\r
+       public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) {\r
+               WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length];\r
+               for (int i = 0; i < wbEvals.length; i++) {\r
+                       wbEvals[i] = evaluators[i]._bookEvaluator;\r
+               }\r
+               CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals);\r
+       }\r
 \r
        /**\r
         * Does nothing\r
index d6bb9b7bfbc05afae1fb622d1b72220550b6f2c8..6857c4bc1bbee17017745239aa5a619c5913c2f1 100644 (file)
 
 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 (file)
index 0000000..c62d2f1
--- /dev/null
@@ -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;
+       }
+}
index fdc933f6fa684ef0b0a5e66fe642d6c958b09cb5..b0c34fd78a01ff87e3669fa1b6c795a4d5443ce9 100644 (file)
@@ -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;
index 664df2c6abeb7dc7bfcb05834589d01e98c31689..41678f19200c2c0270fe4227950a76b7b3b1dad0 100644 (file)
@@ -31,12 +31,36 @@ import org.apache.poi.hssf.usermodel.HSSFSheet;
  */\r
 public interface EvaluationWorkbook {\r
        String getSheetName(int sheetIndex);\r
+       /**\r
+        * @return -1 if the specified sheet is from a different book\r
+        */\r
        int getSheetIndex(HSSFSheet sheet);\r
+       int getSheetIndex(String sheetName);\r
 \r
        HSSFSheet getSheet(int sheetIndex);\r
 \r
+       /**\r
+        * @return <code>null</code> if externSheetIndex refers to a sheet inside the current workbook\r
+        */\r
+       ExternalSheet getExternalSheet(int externSheetIndex);\r
        int convertFromExternSheetIndex(int externSheetIndex);\r
        EvaluationName getName(NamePtg namePtg);\r
        String resolveNameXText(NameXPtg ptg);\r
        Ptg[] getFormulaTokens(HSSFCell cell);\r
+       \r
+       class ExternalSheet {\r
+               private final String _workbookName;\r
+               private final String _sheetName;\r
+\r
+               public ExternalSheet(String workbookName, String sheetName) {\r
+                       _workbookName = workbookName;\r
+                       _sheetName = sheetName;\r
+               }\r
+               public String getWorkbookName() {\r
+                       return _workbookName;\r
+               }\r
+               public String getSheetName() {\r
+                       return _sheetName;\r
+               }\r
+       }\r
 }\r
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 (file)
index 0000000..09262a1
--- /dev/null
@@ -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();
+}
index dd7325a07899237141b320f985e3f97d1fee324c..0b4b435dffa1c1d76b13dfc5f924cd045e3759bc 100644 (file)
@@ -257,7 +257,7 @@ public final class FormulaParser {
      */
     private Identifier parseIdentifier() {
         StringBuffer sb = new StringBuffer();
-        if (!IsAlpha(look) && look != '\'') {
+        if (!IsAlpha(look) && look != '\'' && look != '[') {
             throw expected("Name");
         }
         boolean isQuoted = look == '\''; 
@@ -276,7 +276,7 @@ public final class FormulaParser {
         } else {
             // allow for any sequence of dots and identifier chars
             // special case of two consecutive dots is best treated in the calling code
-            while (IsAlNum(look) || look == '.') {
+            while (IsAlNum(look) || look == '.' || look == '[' || look == ']') {
                 sb.append(look);
                 GetChar();
             }
@@ -368,7 +368,7 @@ public final class FormulaParser {
             // 3-D ref
             // this code assumes iden is a sheetName
             // TODO - handle <book name> ! <named range name>
-            int externIdx = book.getExternalSheetIndex(iden.getName());
+            int externIdx = getExternalSheetIndex(iden.getName());
             String secondIden = parseUnquotedIdentifier();
             AreaReference areaRef = parseArea(secondIden);
             if (areaRef == null) {
@@ -418,6 +418,17 @@ public final class FormulaParser {
                     + name + "' is not a range as expected");
     }
 
+    private int getExternalSheetIndex(String name) {
+        if (name.charAt(0) == '[') {
+            // we have a sheet name qualified with workbook name e.g. '[MyData.xls]Sheet1'
+            int pos = name.lastIndexOf(']'); // safe because sheet names never have ']'
+            String wbName = name.substring(1, pos);
+            String sheetName = name.substring(pos+1);
+            return book.getExternalSheetIndex(wbName, sheetName);
+        }
+        return book.getExternalSheetIndex(name);
+    }
+
     /**
      * @param name an 'identifier' like string (i.e. contains alphanums, and dots)
      * @return <code>null</code> if name cannot be split at a dot
@@ -656,7 +667,7 @@ public final class FormulaParser {
                 Match('}');
                 return arrayNode;
         }
-        if (IsAlpha(look) || look == '\''){
+        if (IsAlpha(look) || look == '\'' || look == '['){
             return parseFunctionReferenceOrName();
         }
         // else - assume number
index 69431c2c221809a30d7cd53540df9acf0234caa4..e9be7d1d39dac4ed9b6a1d92d4d474640e025a34 100644 (file)
@@ -32,6 +32,16 @@ public interface FormulaParsingWorkbook {
         */\r
        EvaluationName getName(String name);\r
 \r
-       int getExternalSheetIndex(String sheetName);\r
        NameXPtg getNameXPtg(String name);\r
+\r
+       /**\r
+        * gets the externSheet index for a sheet from this workbook\r
+        */\r
+       int getExternalSheetIndex(String sheetName);\r
+       /**\r
+        * gets the externSheet index for a sheet from an external workbook\r
+        * @param workbookName e.g. "Budget.xls"\r
+        * @param sheetName a name of a sheet in that workbook\r
+        */\r
+       int getExternalSheetIndex(String workbookName, String sheetName);\r
 }\r
index ac95f4da0f04e3e3e165401eb0920187de2af881..c9b95f6b1c306ffd354a69da3f4a37c90089f779 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.poi.ss.formula;
 \r
 import org.apache.poi.hssf.record.formula.NamePtg;\r
 import org.apache.poi.hssf.record.formula.NameXPtg;\r
+import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;\r
 \r
 /**\r
  * Abstracts a workbook for the purpose of converting formula to text.<br/>\r
@@ -29,6 +30,10 @@ import org.apache.poi.hssf.record.formula.NameXPtg;
  */\r
 public interface FormulaRenderingWorkbook {\r
 \r
+       /**\r
+        * @return <code>null</code> if externSheetIndex refers to a sheet inside the current workbook\r
+        */\r
+       ExternalSheet getExternalSheet(int externSheetIndex);\r
        String getSheetNameByExternSheet(int externSheetIndex);\r
        String resolveNameXText(NameXPtg nameXPtg);\r
        String getNameText(NamePtg namePtg);\r
index e958649695036deaac0d7c35e372a012b0718c2b..724cf2cf2bbfd26ca7d281a2cb59c8a4888631a2 100644 (file)
@@ -63,6 +63,7 @@ import org.apache.poi.hssf.usermodel.HSSFCell;
 import org.apache.poi.hssf.usermodel.HSSFRow;
 import org.apache.poi.hssf.usermodel.HSSFSheet;
 import org.apache.poi.hssf.util.CellReference;
+import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
 
 /**
  * Evaluates formula cells.<p/>
@@ -75,13 +76,14 @@ import org.apache.poi.hssf.util.CellReference;
  *
  * @author Josh Micich
  */
-public class WorkbookEvaluator {
+public final class WorkbookEvaluator {
 
        private final EvaluationWorkbook _workbook;
-       private final EvaluationCache _cache;
+       private EvaluationCache _cache;
 
        private final IEvaluationListener _evaluationListener;
        private final Map _sheetIndexesBySheet;
+       private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
 
        public WorkbookEvaluator(EvaluationWorkbook workbook) {
                this (workbook, null);
@@ -91,6 +93,7 @@ public class WorkbookEvaluator {
                _evaluationListener = evaluationListener;
                _cache = new EvaluationCache(evaluationListener);
                _sheetIndexesBySheet = new IdentityHashMap();
+               _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
        }
 
        /**
@@ -108,7 +111,22 @@ public class WorkbookEvaluator {
                        System.out.println(s);
                }
        }
+       /* package */ void attachToEnvironment(CollaboratingWorkbooksEnvironment collaboratingWorkbooksEnvironment, EvaluationCache cache) {
+               _collaboratingWorkbookEnvironment = collaboratingWorkbooksEnvironment;
+               _cache = cache;
+       }
+       /* package */ CollaboratingWorkbooksEnvironment getEnvironment() {
+               return _collaboratingWorkbookEnvironment;
+       }
 
+       /* package */ void detachFromEnvironment() {
+               _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
+               _cache = new EvaluationCache(_evaluationListener);
+       }
+       /* package */ IEvaluationListener getEvaluationListener() {
+               return _evaluationListener;
+       }
+        
        /**
         * Should be called whenever there are changes to input cells in the evaluated workbook.
         * Failure to call this method after changing cell values will cause incorrect behaviour
@@ -130,7 +148,7 @@ public class WorkbookEvaluator {
                        throw new IllegalArgumentException("value must not be null");
                }
                int sheetIndex = getSheetIndex(sheet);
-               _cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
+               _cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value);
 
        }
        /**
@@ -139,13 +157,17 @@ public class WorkbookEvaluator {
         */
        public void notifySetFormula(HSSFSheet sheet, int rowIndex, int columnIndex) {
                int sheetIndex = getSheetIndex(sheet);
-               _cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
+               _cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null);
 
        }
        private int getSheetIndex(HSSFSheet sheet) {
                Integer result = (Integer) _sheetIndexesBySheet.get(sheet);
                if (result == null) {
-                       result = new Integer(_workbook.getSheetIndex(sheet));
+                       int sheetIndex = _workbook.getSheetIndex(sheet);
+                       if (sheetIndex < 0) {
+                               throw new RuntimeException("Specified sheet from a different book");
+                       }
+                       result = new Integer(sheetIndex);
                        _sheetIndexesBySheet.put(sheet, result);
                }
                return result.intValue();
@@ -153,7 +175,7 @@ public class WorkbookEvaluator {
 
        public ValueEval evaluate(HSSFCell srcCell) {
                int sheetIndex = getSheetIndex(srcCell.getSheet());
-               CellLocation cellLoc = new CellLocation(sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
+               CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum());
                return internalEvaluate(srcCell, cellLoc, new EvaluationTracker(_cache));
        }
 
@@ -342,6 +364,20 @@ public class WorkbookEvaluator {
                }
                return operation.evaluate(ops, srcRowNum, (short)srcColNum);
        }
+       private SheetRefEvaluator createExternSheetRefEvaluator(EvaluationTracker tracker,
+                       ExternSheetReferenceToken ptg) {
+               int externSheetIndex = ptg.getExternSheetIndex();
+               ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
+               if (externalSheet != null) {
+                       WorkbookEvaluator otherEvaluator = _collaboratingWorkbookEnvironment.getWorkbookEvaluator(externalSheet.getWorkbookName());
+                       EvaluationWorkbook otherBook = otherEvaluator._workbook;
+                       int otherSheetIndex = otherBook.getSheetIndex(externalSheet.getSheetName());
+                       return new SheetRefEvaluator(otherEvaluator, tracker, otherBook, otherSheetIndex);
+               }
+               int otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex);
+               return new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
+               
+       }
 
        /**
         * returns an appropriate Eval impl instance for the Ptg. The Ptg must be
@@ -350,6 +386,8 @@ public class WorkbookEvaluator {
         * passed here!
         */
        private Eval getEvalForPtg(Ptg ptg, int sheetIndex, EvaluationTracker tracker) {
+               //  consider converting all these (ptg instanceof XxxPtg) expressions to (ptg.getClass() == XxxPtg.class)
+
                if (ptg instanceof NamePtg) {
                        // named ranges, macro functions
                        NamePtg namePtg = (NamePtg) ptg;
@@ -388,14 +426,12 @@ public class WorkbookEvaluator {
                }
                if (ptg instanceof Ref3DPtg) {
                        Ref3DPtg refPtg = (Ref3DPtg) ptg;
-                       int otherSheetIndex = _workbook.convertFromExternSheetIndex(refPtg.getExternSheetIndex());
-                       SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
+                       SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, refPtg);
                        return new LazyRefEval(refPtg, sre);
                }
                if (ptg instanceof Area3DPtg) {
                        Area3DPtg aptg = (Area3DPtg) ptg;
-                       int otherSheetIndex = _workbook.convertFromExternSheetIndex(aptg.getExternSheetIndex());
-                       SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
+                       SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, aptg);
                        return new LazyAreaEval(aptg, sre);
                }
                SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, sheetIndex);
@@ -435,7 +471,7 @@ public class WorkbookEvaluator {
                } else {
                        cell = row.getCell(columnIndex);
                }
-               CellLocation cellLoc = new CellLocation(sheetIndex, rowIndex, columnIndex);
+               CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex);
                tracker.acceptDependency(cellLoc);
                return internalEvaluate(cell, cellLoc, tracker);
        }
diff --git a/src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls b/src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls
new file mode 100644 (file)
index 0000000..b844fc1
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls b/src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls
new file mode 100644 (file)
index 0000000..3e4708d
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls differ
index 29de45405f536e3f06854f2ec23348336adc1705..53061a228b5db183cca96c9a02cc977f92c82404 100644 (file)
@@ -43,11 +43,13 @@ import org.apache.poi.hssf.record.formula.NumberPtg;
 import org.apache.poi.hssf.record.formula.PercentPtg;
 import org.apache.poi.hssf.record.formula.PowerPtg;
 import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.Ref3DPtg;
 import org.apache.poi.hssf.record.formula.RefPtg;
 import org.apache.poi.hssf.record.formula.StringPtg;
 import org.apache.poi.hssf.record.formula.SubtractPtg;
 import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
 import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
+import org.apache.poi.hssf.usermodel.FormulaExtractor;
 import org.apache.poi.hssf.usermodel.HSSFCell;
 import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
 import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
@@ -913,4 +915,33 @@ public final class TestFormulaParser extends TestCase {
 
                assertEquals("'true'!B2", cell.getCellFormula());
        }
+       
+       public void testParseExternalWorkbookReference() {
+               HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls");
+               HSSFCell cell = wbA.getSheetAt(0).getRow(0).getCell(0);
+
+               // make sure formula in sample is as expected
+               assertEquals("[multibookFormulaB.xls]BSheet1!B1", cell.getCellFormula());
+               Ptg[] expectedPtgs = FormulaExtractor.getPtgs(cell);
+               confirmSingle3DRef(expectedPtgs, 1);
+               
+               // now try (re-)parsing the formula
+               Ptg[] actualPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]BSheet1!B1", wbA);
+               confirmSingle3DRef(actualPtgs, 1); // externalSheetIndex 1 -> BSheet1
+               
+               // try parsing a formula pointing to a different external sheet
+               Ptg[] otherPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]AnotherSheet!B1", wbA);
+               confirmSingle3DRef(otherPtgs, 0); // externalSheetIndex 0 -> AnotherSheet
+               
+               // try setting the same formula in a cell
+               cell.setCellFormula("[multibookFormulaB.xls]AnotherSheet!B1");
+               assertEquals("[multibookFormulaB.xls]AnotherSheet!B1", cell.getCellFormula());
+       }
+       private static void confirmSingle3DRef(Ptg[] ptgs, int expectedExternSheetIndex) {
+               assertEquals(1, ptgs.length);
+               Ptg ptg0 = ptgs[0];
+               assertEquals(Ref3DPtg.class, ptg0.getClass());
+               assertEquals(expectedExternSheetIndex, ((Ref3DPtg)ptg0).getExternSheetIndex());
+       }
+       
 }
index 3727989efb1670ca3004d78d72d16dedf1a2b513..e2a61dde79b32adc19fef584b5f2526903483adc 100644 (file)
@@ -78,7 +78,7 @@ public final class TestSupBookRecord extends TestCase {
 
         assertEquals( 34, record.getRecordSize() );  //sid+size+data
         
-        assertEquals("testURL", record.getURL().getString());
+        assertEquals("testURL", record.getURL());
         UnicodeString[] sheetNames = record.getSheetNames();
         assertEquals(2, sheetNames.length);
         assertEquals("Sheet1", sheetNames[0].getString());
index 20cc9c1787afce1ced4016f2abab5ca39a79590f..0d3e93ee7a129e095769bd024e51370a0f072016 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.poi.ss.formula;
 
 import junit.framework.TestCase;
 
+import org.apache.poi.hssf.HSSFTestDataSamples;
 import org.apache.poi.hssf.record.formula.AreaErrPtg;
 import org.apache.poi.hssf.record.formula.AttrPtg;
 import org.apache.poi.hssf.record.formula.DeletedArea3DPtg;
@@ -29,36 +30,40 @@ import org.apache.poi.hssf.record.formula.RefErrorPtg;
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
 import org.apache.poi.hssf.record.formula.eval.NumberEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 
 /**
- * Tests {@link WorkbookEvaluator}. 
+ * Tests {@link WorkbookEvaluator}.
  *
  * @author Josh Micich
  */
 public class TestWorkbookEvaluator extends TestCase {
-       
+
        /**
         * Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing
-        * the whole formula which converts tAttrSum to tFuncVar("SUM") ) 
+        * the whole formula which converts tAttrSum to tFuncVar("SUM") )
         */
        public void testAttrSum() {
-               
+
                Ptg[] ptgs = {
                        new IntPtg(42),
                        AttrPtg.SUM,
                };
-               
+
                ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
                assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
        }
-       
+
        /**
         * Make sure that the evaluator can directly handle (deleted) ref error tokens
-        * (instead of relying on re-parsing the whole formula which converts these 
-        * to the error constant #REF! ) 
+        * (instead of relying on re-parsing the whole formula which converts these
+        * to the error constant #REF! )
         */
        public void testRefErr() {
-               
+
                confirmRefErr(new RefErrorPtg());
                confirmRefErr(new AreaErrPtg());
                confirmRefErr(new DeletedRef3DPtg(0));
@@ -68,25 +73,82 @@ public class TestWorkbookEvaluator extends TestCase {
                Ptg[] ptgs = {
                        ptg,
                };
-               
+
                ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
                assertEquals(ErrorEval.REF_INVALID, result);
        }
-       
+
        /**
         * Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing
-        * the whole formula which converts tAttrSum to tFuncVar("SUM") ) 
+        * the whole formula which converts tAttrSum to tFuncVar("SUM") )
         */
        public void testMemFunc() {
-               
+
                Ptg[] ptgs = {
                        new IntPtg(42),
                        AttrPtg.SUM,
                };
-               
+
                ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null);
                assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
        }
-       
-       
+
+
+       public void testEvaluateMultipleWorkbooks() {
+               HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls");
+               HSSFWorkbook wbB = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaB.xls");
+
+               HSSFFormulaEvaluator evaluatorA = new HSSFFormulaEvaluator(wbA);
+               HSSFFormulaEvaluator evaluatorB = new HSSFFormulaEvaluator(wbB);
+
+               // Hook up the workbook evaluators to enable evaluation of formulas across books
+               String[] bookNames = { "multibookFormulaA.xls", "multibookFormulaB.xls", };
+               HSSFFormulaEvaluator[] evaluators = { evaluatorA, evaluatorB, };
+               HSSFFormulaEvaluator.setupEnvironment(bookNames, evaluators);
+
+               HSSFCell cell;
+
+               HSSFSheet aSheet1 = wbA.getSheetAt(0);
+               HSSFSheet bSheet1 = wbB.getSheetAt(0);
+
+               // Simple case - single link from wbA to wbB
+               confirmFormula(wbA, 0, 0, 0, "[multibookFormulaB.xls]BSheet1!B1");
+               cell = aSheet1.getRow(0).getCell(0);
+               confirmEvaluation(35, evaluatorA, cell);
+
+
+               // more complex case - back link into wbA
+               // [wbA]ASheet1!A2 references (among other things) [wbB]BSheet1!B2
+               confirmFormula(wbA, 0, 1, 0, "[multibookFormulaB.xls]BSheet1!$B$2+2*A3");
+               // [wbB]BSheet1!B2 references (among other things) [wbA]AnotherSheet!A1:B2
+               confirmFormula(wbB, 0, 1, 1, "SUM([multibookFormulaA.xls]AnotherSheet!$A$1:$B$2)+B3");
+
+               cell = aSheet1.getRow(1).getCell(0);
+               confirmEvaluation(264, evaluatorA, cell);
+
+               // change [wbB]BSheet1!B3 (from 50 to 60)
+               bSheet1.getRow(2).getCell(1).setCellValue(60);
+               evaluatorB.setCachedPlainValue(bSheet1, 2, 1, new NumberEval(60));
+               confirmEvaluation(274, evaluatorA, cell);
+
+               // change [wbA]ASheet1!A3 (from 100 to 80)
+               aSheet1.getRow(2).getCell(0).setCellValue(80);
+               evaluatorA.setCachedPlainValue(aSheet1, 2, 0, new NumberEval(80));
+               confirmEvaluation(234, evaluatorA, cell);
+
+               // change [wbA]AnotherSheet!A1 (from 2 to 3)
+               wbA.getSheetAt(1).getRow(0).getCell(0).setCellValue(3);
+               evaluatorA.setCachedPlainValue(wbA.getSheetAt(1), 0, 0, new NumberEval(3));
+               confirmEvaluation(235, evaluatorA, cell);
+       }
+
+       private static void confirmEvaluation(double expectedValue, HSSFFormulaEvaluator fe, HSSFCell cell) {
+               assertEquals(expectedValue, fe.evaluate(cell).getNumberValue(), 0.0);
+       }
+
+       private static void confirmFormula(HSSFWorkbook wb, int sheetIndex, int rowIndex, int columnIndex,
+                       String expectedFormula) {
+               HSSFCell cell = wb.getSheetAt(sheetIndex).getRow(rowIndex).getCell(columnIndex);
+               assertEquals(expectedFormula, cell.getCellFormula());
+       }
 }