From 0fdd7a1dbc55557f24ab8f3957286dac1add2d70 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Tue, 4 Nov 2014 21:35:01 +0000 Subject: [PATCH] Partial HSSF support for adding new external workbook formula references for #57184 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1636742 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/hssf/model/InternalWorkbook.java | 5 + .../org/apache/poi/hssf/model/LinkTable.java | 144 ++++++++++++------ .../poi/hssf/usermodel/HSSFWorkbook.java | 14 ++ .../org/apache/poi/ss/usermodel/Workbook.java | 16 ++ .../poi/xssf/streaming/SXSSFWorkbook.java | 14 ++ .../poi/xssf/usermodel/XSSFWorkbook.java | 12 ++ .../usermodel/TestHSSFFormulaEvaluator.java | 62 ++++++-- 7 files changed, 209 insertions(+), 58 deletions(-) diff --git a/src/java/org/apache/poi/hssf/model/InternalWorkbook.java b/src/java/org/apache/poi/hssf/model/InternalWorkbook.java index 580d48d025..3c3b91b148 100644 --- a/src/java/org/apache/poi/hssf/model/InternalWorkbook.java +++ b/src/java/org/apache/poi/hssf/model/InternalWorkbook.java @@ -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 diff --git a/src/java/org/apache/poi/hssf/model/LinkTable.java b/src/java/org/apache/poi/hssf/model/LinkTable.java index 7277da2e6c..cc228b3dfd 100644 --- a/src/java/org/apache/poi/hssf/model/LinkTable.java +++ b/src/java/org/apache/poi/hssf/model/LinkTable.java @@ -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 )

@@ -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; snIn 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. + *

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 *

diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java index 112c1ff814..ce56b6a6d6 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java @@ -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. * diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index a51b40675f..7164534d3b 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -1704,6 +1704,18 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java index 22e7ca5493..2c2fe81da4 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java @@ -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(){ -- 2.39.5