]> source.dussan.org Git - poi.git/commitdiff
Partial HSSF support for adding new external workbook formula references for #57184
authorNick Burch <nick@apache.org>
Tue, 4 Nov 2014 21:35:01 +0000 (21:35 +0000)
committerNick Burch <nick@apache.org>
Tue, 4 Nov 2014 21:35:01 +0000 (21:35 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1636742 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/hssf/model/InternalWorkbook.java
src/java/org/apache/poi/hssf/model/LinkTable.java
src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
src/java/org/apache/poi/ss/usermodel/Workbook.java
src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java

index 580d48d025df2a5af45e96d6fb23ceb49dcd7b4a..3c3b91b148efbc783e5fc4b6e71302d40c2d4720 100644 (file)
@@ -94,6 +94,7 @@ import org.apache.poi.ss.formula.ptg.Ptg;
 import org.apache.poi.ss.formula.ptg.Ref3DPtg;
 import org.apache.poi.ss.formula.udf.UDFFinder;
 import org.apache.poi.ss.usermodel.BuiltinFormats;
+import org.apache.poi.ss.usermodel.Workbook;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
@@ -1803,6 +1804,10 @@ public final class InternalWorkbook {
         }
         return linkTable;
     }
+    
+    public int linkExternalWorkbook(String name, Workbook externalWorkbook) {
+        return getOrCreateLinkTable().linkExternalWorkbook(name, externalWorkbook);
+    }
 
     /** 
      * Finds the first sheet name by his extern sheet index
index 7277da2e6c240518bfb71d308342af3fa55ba77b..cc228b3dfd13b5b105140c73301463ffabcf49e2 100644 (file)
@@ -37,6 +37,7 @@ import org.apache.poi.ss.formula.ptg.ErrPtg;
 import org.apache.poi.ss.formula.ptg.NameXPtg;
 import org.apache.poi.ss.formula.ptg.Ptg;
 import org.apache.poi.ss.formula.ptg.Ref3DPtg;
+import org.apache.poi.ss.usermodel.Workbook;
 
 /**
  * Link Table (OOO pdf reference: 4.10.3 ) <p/>
@@ -110,31 +111,39 @@ final class LinkTable {
                        _crnBlocks = new CRNBlock[temp.size()];
                        temp.toArray(_crnBlocks);
                }
-
-        /**
-         * Create a new block for internal references. It is called when constructing a new LinkTable.
-         *
-         * @see org.apache.poi.hssf.model.LinkTable#LinkTable(int, WorkbookRecordList)
-         */
-        public ExternalBookBlock(int numberOfSheets) {
-                       _externalBookRecord = SupBookRecord.createInternalReferences((short)numberOfSheets);
-                       _externalNameRecords = new ExternalNameRecord[0];
-                       _crnBlocks = new CRNBlock[0];
-               }
-
-        /**
-         * Create a new block for registering add-in functions
-         *
-         * @see org.apache.poi.hssf.model.LinkTable#addNameXPtg(String)
-         */
-        public ExternalBookBlock() {
-            _externalBookRecord = SupBookRecord.createAddInFunctions();
-            _externalNameRecords = new ExternalNameRecord[0];
-            _crnBlocks = new CRNBlock[0];
-        }
+               
+       /**
+        * Create a new block for external references.
+        */
+       public ExternalBookBlock(String url, String[] sheetNames) {
+           _externalBookRecord = SupBookRecord.createExternalReferences(url, sheetNames);
+           _crnBlocks = new CRNBlock[0];
+       }
+
+       /**
+        * Create a new block for internal references. It is called when constructing a new LinkTable.
+        *
+        * @see org.apache.poi.hssf.model.LinkTable#LinkTable(int, WorkbookRecordList)
+        */
+       public ExternalBookBlock(int numberOfSheets) {
+           _externalBookRecord = SupBookRecord.createInternalReferences((short)numberOfSheets);
+           _externalNameRecords = new ExternalNameRecord[0];
+           _crnBlocks = new CRNBlock[0];
+       }
+
+       /**
+        * Create a new block for registering add-in functions
+        *
+        * @see org.apache.poi.hssf.model.LinkTable#addNameXPtg(String)
+        */
+       public ExternalBookBlock() {
+           _externalBookRecord = SupBookRecord.createAddInFunctions();
+           _externalNameRecords = new ExternalNameRecord[0];
+           _crnBlocks = new CRNBlock[0];
+       }
 
                public SupBookRecord getExternalBookRecord() {
-                       return _externalBookRecord;
+                   return _externalBookRecord;
                }
 
                public String getNameText(int definedNameIndex) {
@@ -382,31 +391,68 @@ final class LinkTable {
             };
                }
        }
+       
+       private int getExternalWorkbookIndex(String workbookName) {
+           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
+                   return i;
+               }
+           } 
+           return -1;
+       }
+       
+       public int linkExternalWorkbook(String name, Workbook externalWorkbook) {
+        int extBookIndex = getExternalWorkbookIndex(name);
+        if (extBookIndex != -1) {
+            // Already linked!
+            return extBookIndex;
+        }
+        
+        // Create a new SupBookRecord
+        String[] sheetNames = new String[externalWorkbook.getNumberOfSheets()];
+        for (int sn=0; sn<sheetNames.length; sn++) {
+            sheetNames[sn] = externalWorkbook.getSheetName(sn);
+        }
+        String url = "\000" + name;
+        ExternalBookBlock block = new ExternalBookBlock(url, sheetNames);
+        
+        // Add it into the list + records
+        extBookIndex = extendExternalBookBlocks(block);
+
+        // add the created SupBookRecord before ExternSheetRecord
+        int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
+        if (idx == -1) {
+            idx = _workbookRecordList.size();
+        }
+        _workbookRecordList.add(idx, block.getExternalBookRecord());
+        
+        // Setup links for the sheets
+        for (int sn=0; sn<sheetNames.length; sn++) {
+            _externSheetRecord.addRef(extBookIndex, sn, sn);
+        }
+        
+        // Report where it went
+        return extBookIndex;
+       }
 
        public int getExternalSheetIndex(String workbookName, String firstSheetName, String lastSheetName) {
-               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 externalBookIndex = getExternalWorkbookIndex(workbookName);
+        if (externalBookIndex == -1) {
+            throw new RuntimeException("No external workbook with name '" + workbookName + "'");
+        }
+        SupBookRecord ebrTarget = _externalBookBlocks[externalBookIndex].getExternalBookRecord();
+
                int firstSheetIndex = getSheetIndex(ebrTarget.getSheetNames(), firstSheetName);
         int lastSheetIndex = getSheetIndex(ebrTarget.getSheetNames(), lastSheetName);
 
+        // Find or add the external sheet record definition for this
                int result = _externSheetRecord.getRefIxForSheet(externalBookIndex, firstSheetIndex, lastSheetIndex);
                if (result < 0) {
-                       throw new RuntimeException("ExternSheetRecord does not contain combination ("
-                                       + externalBookIndex + ", " + firstSheetIndex + ", " + lastSheetIndex + ")");
+                   result = _externSheetRecord.addRef(externalBookIndex, firstSheetIndex, lastSheetIndex);
                }
                return result;
        }
@@ -580,13 +626,7 @@ final class LinkTable {
         // An ExternalBlock for Add-In functions was not found. Create a new one.
         if (extBlock == null) {
             extBlock = new ExternalBookBlock();
-
-            ExternalBookBlock[] tmp = new ExternalBookBlock[_externalBookBlocks.length + 1];
-            System.arraycopy(_externalBookBlocks, 0, tmp, 0, _externalBookBlocks.length);
-            tmp[tmp.length - 1] = extBlock;
-            _externalBookBlocks = tmp;
-
-            extBlockIndex = _externalBookBlocks.length - 1;
+            extBlockIndex = extendExternalBookBlocks(extBlock);
 
             // add the created SupBookRecord before ExternSheetRecord
             int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
@@ -620,6 +660,14 @@ final class LinkTable {
         int ix = _externSheetRecord.getRefIxForSheet(extBlockIndex, fakeSheetIdx, fakeSheetIdx);
         return new NameXPtg(ix, nameIndex);
     }
+    private int extendExternalBookBlocks(ExternalBookBlock newBlock) {
+        ExternalBookBlock[] tmp = new ExternalBookBlock[_externalBookBlocks.length + 1];
+        System.arraycopy(_externalBookBlocks, 0, tmp, 0, _externalBookBlocks.length);
+        tmp[tmp.length - 1] = newBlock;
+        _externalBookBlocks = tmp;
+
+        return (_externalBookBlocks.length - 1);
+    }
 
     private int findRefIndexFromExtBookIndex(int extBookIndex) {
                return _externSheetRecord.findRefIndexFromExtBookIndex(extBookIndex);
index 3f3bedfd01a94d40907bcefc1261fcf34c05d34e..da741d49440a890926a6313c04109f4b5c35a08f 100644 (file)
@@ -79,6 +79,7 @@ import org.apache.poi.ss.formula.udf.AggregatingUDFFinder;
 import org.apache.poi.ss.formula.udf.IndexedUDFFinder;
 import org.apache.poi.ss.formula.udf.UDFFinder;
 import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
+import org.apache.poi.ss.usermodel.Workbook;
 import org.apache.poi.ss.util.CellRangeAddress;
 import org.apache.poi.ss.util.WorkbookUtil;
 import org.apache.poi.util.Configurator;
@@ -1850,6 +1851,19 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
        return storageId;
     }
     
+    /**
+     * Adds the LinkTable records required to allow formulas referencing
+     *  the specified external workbook to be added to this one. Allows
+     *  formulas such as "[MyOtherWorkbook]Sheet3!$A$5" to be added to the 
+     *  file, for workbooks not already referenced.
+     *
+     * @param name The name the workbook will be referenced as in formulas
+     * @param workbook The open workbook to fetch the link required information from
+     */
+    public int linkExternalWorkbook(String name, Workbook workbook) {
+        return this.workbook.linkExternalWorkbook(name, workbook);
+    }
+    
     /**
      * Is the workbook protected with a password (not encrypted)?
      */
index 0ed1a6605d73dd14594170f5a02ce7c5c7404f4b..9d0566c13f67da659707b440a73af3e2179abd4e 100644 (file)
@@ -407,6 +407,22 @@ public interface Workbook extends Closeable {
      */
     void removeName(String name);
 
+    /**
+     * Adds the linking required to allow formulas referencing
+     *  the specified external workbook to be added to this one.
+     * <p>In order for formulas such as "[MyOtherWorkbook]Sheet3!$A$5"
+     *  to be added to the file, some linking information must first
+     *  be recorded. Once a given external workbook has been linked,
+     *  then formulas using it can added. Each workbook needs linking
+     *  only once. 
+     * <p>This linking only applies for writing formulas. To link things
+     *  for evaluation, see {@link FormulaEvaluator#setupReferencedWorkbooks(java.util.Map)}
+     *
+     * @param name The name the workbook will be referenced as in formulas
+     * @param workbook The open workbook to fetch the link required information from
+     */
+    int linkExternalWorkbook(String name, Workbook workbook);
+    
      /**
      * Sets the printarea for the sheet provided
      * <p>
index 112c1ff8144f833b380d4ad78967b7a9be0abf87..ce56b6a6d65235f5de08be2589be68d647098b2e 100644 (file)
@@ -1174,6 +1174,20 @@ public class SXSSFWorkbook implements Workbook
     {
         _wb.setSheetHidden(sheetIx,hidden);
     }
+    
+    /**
+     * Adds the LinkTable records required to allow formulas referencing
+     *  the specified external workbook to be added to this one. Allows
+     *  formulas such as "[MyOtherWorkbook]Sheet3!$A$5" to be added to the 
+     *  file, for workbooks not already referenced.
+     *
+     * @param name The name the workbook will be referenced as in formulas
+     * @param workbook The open workbook to fetch the link required information from
+     */
+    public int linkExternalWorkbook(String name, Workbook workbook) {
+        throw new RuntimeException("NotImplemented");
+    }
+    
     /**
      * Register a new toolpack in this workbook.
      *
index a51b40675f064740211e58e7b2e25a344baccff6..7164534d3b206f02e4f0fc899490b9f4b5867a34 100644 (file)
@@ -1704,6 +1704,18 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X
        return mapInfo;
     }
 
+    /**
+     * Adds the LinkTable records required to allow formulas referencing
+     *  the specified external workbook to be added to this one. Allows
+     *  formulas such as "[MyOtherWorkbook.xlsx]Sheet3!$A$5" to be added to the 
+     *  file, for workbooks not already referenced.
+     *
+     * @param name The name the workbook will be referenced as in formulas
+     * @param workbook The open workbook to fetch the link required information from
+     */
+    public int linkExternalWorkbook(String name, Workbook workbook) {
+        throw new RuntimeException("NotImplemented");
+    }
 
        /**
         * Specifies a boolean value that indicates whether structure of workbook is locked. <br/>
index 22e7ca5493ecbad5afc2df9f1e72a88d96b9d997..2c2fe81da406f52415e48ba1350737a724c59d30 100644 (file)
@@ -231,14 +231,35 @@ public final class TestHSSFFormulaEvaluator extends BaseTestFormulaEvaluator {
       assertEquals(Cell.CELL_TYPE_NUMERIC, cell.getCachedFormulaResultType());
       assertEquals(36.90, cell.getNumericCellValue(), 0.0001);
       
-/*      
-      // Now add a formula that refers to yet another (different) workbook
-      cell = wb.getSheetAt(0).getRow(1).createCell(42);
-      cell.setCellFormula("[alt.xls]Sheet1!$A$1");
       
-      // Check it - TODO Is this correct? Or should it become [2]Sheet1!$A$1 ?
-      assertEquals("[alt.xls]Sheet1!$A$1", cell.getCellFormula());
+      // Add a formula that refers to one of the existing external workbooks
+      cell = wb.getSheetAt(0).getRow(1).createCell(40);
+      cell.setCellFormula("Cost*[XRefCalcData.xls]MarkupSheet!$B$1");
       
+      // Check is was stored correctly
+      assertEquals("Cost*[XRefCalcData.xls]MarkupSheet!$B$1", cell.getCellFormula());
+      
+      // Check it evaluates correctly
+      eval.evaluateFormulaCell(cell);
+      assertEquals(24.60*1.8, cell.getNumericCellValue());
+      
+      
+      // Try to add a formula for a new external workbook, won't be allowed to start
+      try {
+          cell = wb.getSheetAt(0).getRow(1).createCell(42);
+          cell.setCellFormula("[alt.xls]Sheet0!$A$1");
+          fail("New workbook not linked, shouldn't be able to add");
+      } catch(Exception e) {}
+      
+      // Link our new workbook
+      HSSFWorkbook alt = new HSSFWorkbook();
+      alt.createSheet().createRow(0).createCell(0).setCellValue("In another workbook");
+      wb.linkExternalWorkbook("alt.xls", alt);
+      
+      // Now add a formula that refers to our new workbook
+      cell.setCellFormula("[alt.xls]Sheet0!$A$1");
+      assertEquals("[alt.xls]Sheet0!$A$1", cell.getCellFormula());
+
       // Evaluate it, without a link to that workbook
       try {
           eval.evaluate(cell);
@@ -246,8 +267,6 @@ public final class TestHSSFFormulaEvaluator extends BaseTestFormulaEvaluator {
       } catch(Exception e) {}
       
       // Add a link, check it does
-      HSSFWorkbook alt = new HSSFWorkbook();
-      alt.createSheet().createRow(0).createCell(0).setCellValue("In another workbook");
       HSSFFormulaEvaluator.setupEnvironment(
               new String[] { "XRefCalc.xls", "XRefCalcData.xls", "alt.xls" },
               new HSSFFormulaEvaluator[] {
@@ -256,10 +275,33 @@ public final class TestHSSFFormulaEvaluator extends BaseTestFormulaEvaluator {
                     new HSSFFormulaEvaluator(alt)
               }
       );
+      eval.evaluateFormulaCell(cell);
+      assertEquals("In another workbook", cell.getStringCellValue());
+      
+      
+      // Save and re-load
+      wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
+      eval = new HSSFFormulaEvaluator(wb);
+      HSSFFormulaEvaluator.setupEnvironment(
+              new String[] { "XRefCalc.xls", "XRefCalcData.xls", "alt.xls" },
+              new HSSFFormulaEvaluator[] {
+                    eval,
+                    new HSSFFormulaEvaluator(wbData),
+                    new HSSFFormulaEvaluator(alt)
+              }
+      );
+      
+      // Check the one referring to the previously existing workbook behaves
+      cell = wb.getSheetAt(0).getRow(1).getCell(40);
+      assertEquals("Cost*[XRefCalcData.xls]MarkupSheet!$B$1", cell.getCellFormula());
+      eval.evaluateFormulaCell(cell);
+      assertEquals(24.60*1.8, cell.getNumericCellValue());
       
-      eval.evaluate(cell);
+      // Now check the newly added reference
+      cell = wb.getSheetAt(0).getRow(1).getCell(42);
+      assertEquals("[alt.xls]Sheet0!$A$1", cell.getCellFormula());
+      eval.evaluateFormulaCell(cell);
       assertEquals("In another workbook", cell.getStringCellValue());
-*/      
    }
 
     public void testSharedFormulas(){