]> source.dussan.org Git - poi.git/commitdiff
[github-296] cache data of external workbook. Thanks to aspojo. This closes #296
authorPJ Fanning <fanningpj@apache.org>
Fri, 21 Jan 2022 19:43:10 +0000 (19:43 +0000)
committerPJ Fanning <fanningpj@apache.org>
Fri, 21 Jan 2022 19:43:10 +0000 (19:43 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1897311 13f79535-47bb-0310-9956-ffa450edef68

poi-ooxml/src/main/java/org/apache/poi/xssf/model/ExternalLinksTable.java
poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/BaseXSSFFormulaEvaluator.java
poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java
poi/src/main/java9/module-info.class

index 21568d48970be768b0decf8e556539d87aa60b00..6aa975ee994ee1b05e9a45739428778e0428ffbe 100644 (file)
@@ -33,9 +33,15 @@ import org.apache.poi.ss.usermodel.Name;
 import org.apache.poi.util.Internal;
 import org.apache.poi.util.Removal;
 import org.apache.xmlbeans.XmlException;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalBook;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalCell;
 import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalDefinedName;
 import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalLink;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalRow;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalSheetData;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalSheetDataSet;
 import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalSheetName;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalSheetNames;
 import org.openxmlformats.schemas.spreadsheetml.x2006.main.ExternalLinkDocument;
 
 /**
@@ -123,7 +129,7 @@ public class ExternalLinksTable extends POIXMLDocumentPart {
 
         // Have a new one added
         PackageRelationship newRel = getPackagePart().addExternalRelationship(
-                                target, PackageRelationshipTypes.EXTERNAL_LINK_PATH);
+                target, PackageRelationshipTypes.EXTERNAL_LINK_PATH);
         link.getExternalBook().setId(newRel.getId());
     }
 
@@ -148,6 +154,80 @@ public class ExternalLinksTable extends POIXMLDocumentPart {
     }
 
 
+    public void cacheData(String sheetName, long rowR, String cellR, String v) {
+        CTExternalBook externalBook = link.getExternalBook();
+        synchronized (externalBook.monitor()) {
+            CTExternalSheetData sheetData = getSheetData(getSheetNameIndex(sheetName));
+            CTExternalRow row = getRow(sheetData, rowR);
+            CTExternalCell cell = getCell(row, cellR);
+            cell.setV(v);
+        }
+    }
+
+    private int getSheetNameIndex(String sheetName) {
+        CTExternalBook externalBook = link.getExternalBook();
+        CTExternalSheetNames sheetNames = externalBook.getSheetNames();
+        if (sheetNames == null) {
+            sheetNames = externalBook.addNewSheetNames();
+        }
+        int index = -1;
+        for (int i = 0; i < sheetNames.sizeOfSheetNameArray(); i++) {
+            CTExternalSheetName ctExternalSheetName = sheetNames.getSheetNameArray(i);
+            if (ctExternalSheetName.getVal().equals(sheetName)) {
+                index = i;
+                break;
+            }
+        }
+        if (index == -1) {
+            CTExternalSheetName ctExternalSheetName = sheetNames.addNewSheetName();
+            ctExternalSheetName.setVal(sheetName);
+            index = sheetNames.sizeOfSheetNameArray() - 1;
+        }
+        return index;
+
+    }
+
+    private CTExternalSheetData getSheetData(int sheetId) {
+
+        CTExternalSheetDataSet sheetDataSet = link.getExternalBook().getSheetDataSet();
+        if (sheetDataSet == null) {
+            sheetDataSet = link.getExternalBook().addNewSheetDataSet();
+        }
+        CTExternalSheetData ctExternalSheetData = null;
+        for (CTExternalSheetData item : sheetDataSet.getSheetDataArray()) {
+            if (item.getSheetId() == sheetId) {
+                ctExternalSheetData = item;
+                break;
+            }
+        }
+        if (ctExternalSheetData == null) {
+            ctExternalSheetData = sheetDataSet.addNewSheetData();
+            ctExternalSheetData.setSheetId(sheetId);
+        }
+        return ctExternalSheetData;
+    }
+
+    private CTExternalRow getRow(CTExternalSheetData sheetData, long rowR) {
+        for (CTExternalRow ctExternalRow : sheetData.getRowArray()) {
+            if (ctExternalRow.getR() == rowR) {
+                return ctExternalRow;
+            }
+        }
+        CTExternalRow ctExternalRow = sheetData.addNewRow();
+        ctExternalRow.setR(rowR);
+        return ctExternalRow;
+    }
+
+    private CTExternalCell getCell(CTExternalRow row, String cellR) {
+        for (CTExternalCell ctExternalCell : row.getCellArray()) {
+            if (ctExternalCell.getR().equals(cellR)) {
+                return ctExternalCell;
+            }
+        }
+        CTExternalCell ctExternalCell = row.addNewCell();
+        ctExternalCell.setR(cellR);
+        return ctExternalCell;
+    }
     // TODO Last seen data
 
 
@@ -226,4 +306,4 @@ public class ExternalLinksTable extends POIXMLDocumentPart {
             throw new IllegalStateException("Not Supported");
         }
     }
-}
\ No newline at end of file
+}
index c38dd70438e579431115651a98e095ae5fc7f9bb..722d4334e4c8dddab5f8bdba0587ba3a01a9b393 100644 (file)
@@ -26,10 +26,14 @@ import org.apache.poi.ss.formula.eval.ErrorEval;
 import org.apache.poi.ss.formula.eval.NumberEval;
 import org.apache.poi.ss.formula.eval.StringEval;
 import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.ptg.Area3DPxg;
+import org.apache.poi.ss.formula.ptg.Ptg;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.CellType;
 import org.apache.poi.ss.usermodel.CellValue;
 import org.apache.poi.ss.usermodel.RichTextString;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.xssf.model.ExternalLinksTable;
 
 /**
  * Internal POI use only - parent of XSSF and SXSSF formula evaluators
@@ -55,6 +59,7 @@ public abstract class BaseXSSFFormulaEvaluator extends BaseFormulaEvaluator {
     protected CellValue evaluateFormulaCellValue(Cell cell) {
         EvaluationCell evalCell = toEvaluationCell(cell);
         ValueEval eval = _bookEvaluator.evaluate(evalCell);
+        cacheExternalWorkbookCells(evalCell);
         if (eval instanceof NumberEval) {
             NumberEval ne = (NumberEval) eval;
             return new CellValue(ne.getNumberValue());
@@ -73,9 +78,57 @@ public abstract class BaseXSSFFormulaEvaluator extends BaseFormulaEvaluator {
         throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")");
     }
 
+    /**
+     * cache cell value of external workbook
+     *
+     * @param evalCell sourceCell
+     */
+    private void cacheExternalWorkbookCells(EvaluationCell evalCell) {
+        //
+        Ptg[] formulaTokens = getEvaluationWorkbook().getFormulaTokens(evalCell);
+        for (Ptg ptg : formulaTokens) {
+            if (ptg instanceof Area3DPxg) {
+                Area3DPxg area3DPxg = (Area3DPxg) ptg;
+                if (area3DPxg.getExternalWorkbookNumber() > 0) {
+                    EvaluationWorkbook.ExternalSheet externalSheet = getEvaluationWorkbook().getExternalSheet(area3DPxg.getSheetName(), area3DPxg.getLastSheetName(), area3DPxg.getExternalWorkbookNumber());
+
+                    XSSFCell xssfCell = ((XSSFEvaluationCell) evalCell).getXSSFCell();
+                    XSSFWorkbook externalWorkbook = (XSSFWorkbook) xssfCell.getSheet().getWorkbook().getCreationHelper().getReferencedWorkbooks().get(externalSheet.getWorkbookName());
+                    ExternalLinksTable externalLinksTable = xssfCell.getSheet().getWorkbook().getExternalLinksTable().get(area3DPxg.getExternalWorkbookNumber() - 1);
+
+                    int firstSheet = externalWorkbook.getSheetIndex(area3DPxg.getSheetName());
+                    int lastSheet = firstSheet;
+                    if (area3DPxg.getLastSheetName() != null) {
+                        lastSheet = externalWorkbook.getSheetIndex(area3DPxg.getLastSheetName());
+                    }
+
+                    for (int sheetIndex = firstSheet; sheetIndex <= lastSheet; sheetIndex++) {
+                        XSSFSheet sheet = externalWorkbook.getSheetAt(sheetIndex);
+                        int firstRow = area3DPxg.getFirstRow();
+                        int lastRow = area3DPxg.getLastRow();
+                        for (int rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) {
+                            XSSFRow row = sheet.getRow(rowIndex);
+                            int firstColumn = area3DPxg.getFirstColumn();
+                            int lastColumn = area3DPxg.getLastColumn();
+                            for (int cellIndex = firstColumn; cellIndex <= lastColumn; cellIndex++) {
+                                XSSFCell cell = row.getCell(cellIndex);
+                                String cellValue = cell.getRawValue();
+                                String cellR = new CellReference(cell).formatAsString(false);
+                                externalLinksTable.cacheData(sheet.getSheetName(), rowIndex + 1, cellR, cellValue);
+
+                            }
+                        }
+
+                    }
+                }
+
+            }
+        }
+    }
+
     @Override
     protected void setCellType(Cell cell, CellType cellType) {
-        if (cell instanceof  XSSFCell) {
+        if (cell instanceof XSSFCell) {
             EvaluationWorkbook evaluationWorkbook = getEvaluationWorkbook();
             BaseXSSFEvaluationWorkbook xewb = BaseXSSFEvaluationWorkbook.class.isAssignableFrom(evaluationWorkbook.getClass()) ? (BaseXSSFEvaluationWorkbook) evaluationWorkbook : null;
 
index 458c46d750a9d8b3a175d682e7ce6f528a6f8923..0846f9499a89bc4c4e63ba4f9955fe469ec8c7b7 100644 (file)
@@ -61,6 +61,8 @@ import org.apache.poi.xssf.XSSFITestDataProvider;
 import org.apache.poi.xssf.model.StylesTable;
 import org.junit.jupiter.api.Test;
 import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCalcPr;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalLink;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalSheetData;
 import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTPivotCache;
 import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook;
 import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbookPr;
@@ -109,13 +111,13 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook {
         // Check the package contains what we'd expect it to
         try (OPCPackage pkg = OPCPackage.open(file.toString())) {
             PackagePart wbRelPart =
-                pkg.getPart(PackagingURIHelper.createPartName("/xl/_rels/workbook.xml.rels"));
+                    pkg.getPart(PackagingURIHelper.createPartName("/xl/_rels/workbook.xml.rels"));
             assertNotNull(wbRelPart);
             assertTrue(wbRelPart.isRelationshipPart());
             assertEquals(ContentTypes.RELATIONSHIPS_PART, wbRelPart.getContentType());
 
             PackagePart wbPart =
-                pkg.getPart(PackagingURIHelper.createPartName("/xl/workbook.xml"));
+                    pkg.getPart(PackagingURIHelper.createPartName("/xl/workbook.xml"));
             // Links to the three sheets, shared strings and styles
             assertTrue(wbPart.hasRelationships());
             assertEquals(5, wbPart.getRelationships().size());
@@ -294,11 +296,11 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook {
 
             // Add two more styles
             assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 8,
-                st.putNumberFormat("testFORMAT"));
+                    st.putNumberFormat("testFORMAT"));
             assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 8,
-                st.putNumberFormat("testFORMAT"));
+                    st.putNumberFormat("testFORMAT"));
             assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 9,
-                st.putNumberFormat("testFORMAT2"));
+                    st.putNumberFormat("testFORMAT2"));
             assertEquals(10, st.getNumDataFormats());
 
 
@@ -370,7 +372,7 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook {
             assertEquals(1, allPictures.size());
 
             PackagePartName imagePartName = PackagingURIHelper
-                .createPartName("/xl/media/image1.jpeg");
+                    .createPartName("/xl/media/image1.jpeg");
             PackagePart imagePart = workbook.getPackage().getPart(imagePartName);
             assertNotNull(imagePart);
 
@@ -1069,31 +1071,31 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook {
      */
     @Test
     void getTable() throws IOException {
-       XSSFWorkbook wb = openSampleWorkbook("WithTable.xlsx");
-       XSSFTable table1 = wb.getTable("Tabella1");
-       assertNotNull(table1, "Tabella1 was not found in workbook");
-       assertEquals("Tabella1", table1.getName(), "Table name");
-       assertEquals("Foglio1", table1.getSheetName(), "Sheet name");
-
-       // Table lookup should be case-insensitive
-       assertSame(table1, wb.getTable("TABELLA1"), "Case insensitive table name lookup");
-
-       // If workbook does not contain any data tables matching the provided name, getTable should return null
-       assertNull(wb.getTable(null), "Null table name should not throw NPE");
-       assertNull(wb.getTable("Foglio1"), "Should not be able to find non-existent table");
-
-       // If a table is added after getTable is called it should still be reachable by XSSFWorkbook.getTable
-       // This test makes sure that if any caching is done that getTable never uses a stale cache
-       XSSFTable table2 = wb.getSheet("Foglio2").createTable(null);
-       table2.setName("Table2");
-       assertSame(table2, wb.getTable("Table2"), "Did not find Table2");
-
-       // If table name is modified after getTable is called, the table can only be found by its new name
-       // This test makes sure that if any caching is done that getTable never uses a stale cache
-       table1.setName("Table1");
-       assertSame(table1, wb.getTable("TABLE1"), "Did not find Tabella1 renamed to Table1");
-
-       wb.close();
+        XSSFWorkbook wb = openSampleWorkbook("WithTable.xlsx");
+        XSSFTable table1 = wb.getTable("Tabella1");
+        assertNotNull(table1, "Tabella1 was not found in workbook");
+        assertEquals("Tabella1", table1.getName(), "Table name");
+        assertEquals("Foglio1", table1.getSheetName(), "Sheet name");
+
+        // Table lookup should be case-insensitive
+        assertSame(table1, wb.getTable("TABELLA1"), "Case insensitive table name lookup");
+
+        // If workbook does not contain any data tables matching the provided name, getTable should return null
+        assertNull(wb.getTable(null), "Null table name should not throw NPE");
+        assertNull(wb.getTable("Foglio1"), "Should not be able to find non-existent table");
+
+        // If a table is added after getTable is called it should still be reachable by XSSFWorkbook.getTable
+        // This test makes sure that if any caching is done that getTable never uses a stale cache
+        XSSFTable table2 = wb.getSheet("Foglio2").createTable(null);
+        table2.setName("Table2");
+        assertSame(table2, wb.getTable("Table2"), "Did not find Table2");
+
+        // If table name is modified after getTable is called, the table can only be found by its new name
+        // This test makes sure that if any caching is done that getTable never uses a stale cache
+        table1.setName("Table1");
+        assertSame(table1, wb.getTable("TABLE1"), "Did not find Tabella1 renamed to Table1");
+
+        wb.close();
     }
 
     @SuppressWarnings("deprecation")
@@ -1299,6 +1301,46 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook {
         }
     }
 
+    @Test
+    void testCacheExternalWorkbook() throws Exception {
+        String nameA = "cache-external-workbook-a.xlsx";
+
+        try (
+                UnsynchronizedByteArrayOutputStream bosA = new UnsynchronizedByteArrayOutputStream();
+                UnsynchronizedByteArrayOutputStream bosB = new UnsynchronizedByteArrayOutputStream();
+                XSSFWorkbook workbookA = new XSSFWorkbook();
+                XSSFWorkbook workbookB = new XSSFWorkbook()
+        ) {
+            XSSFRow row1 = workbookA.createSheet().createRow(0);
+            double v1 = 10, v2 = 10, sum = v1 + v2;
+            row1.createCell(0).setCellValue(v1);
+            row1.createCell(1).setCellValue(v2);
+
+            XSSFRow row = workbookB.createSheet().createRow(0);
+            XSSFCell cell = row.createCell(0);
+
+            workbookB.linkExternalWorkbook(nameA, workbookA);
+            String formula = String.format(LocaleUtil.getUserLocale(), "SUM('[%s]Sheet0'!A1:B1)", nameA);
+            cell.setCellFormula(formula);
+            XSSFFormulaEvaluator evaluator = workbookB.getCreationHelper().createFormulaEvaluator();
+            evaluator.evaluateFormulaCell(cell);
+
+            assertEquals(sum, cell.getNumericCellValue());
+
+            workbookA.write(bosA);
+            workbookB.write(bosB);
+
+            try(
+                    XSSFWorkbook workbook2 = new XSSFWorkbook(bosB.toInputStream())
+            ) {
+                CTExternalLink link = workbook2.getExternalLinksTable().get(0).getCTExternalLink();
+                CTExternalSheetData sheetData = link.getExternalBook().getSheetDataSet().getSheetDataArray(0);
+                assertEquals(Double.valueOf(sheetData.getRowArray(0).getCellArray(0).getV()), v1);
+                assertEquals(Double.valueOf(sheetData.getRowArray(0).getCellArray(1).getV()), v2);
+            }
+        }
+    }
+
     private static void expectFormattedContent(Cell cell, String value) {
         assertEquals(value, new DataFormatter().formatCellValue(cell),
                 "Cell " + ref(cell) + " has wrong formatted content.");
index 6274a9f771b25e7b571224bf11484302c986ee61..68a594701218fe1d88b59d3cd8c4b924bc331523 100644 (file)
Binary files a/poi/src/main/java9/module-info.class and b/poi/src/main/java9/module-info.class differ