From: PJ Fanning Date: Fri, 21 Jan 2022 19:43:10 +0000 (+0000) Subject: [github-296] cache data of external workbook. Thanks to aspojo. This closes #296 X-Git-Tag: REL_5_2_1~181 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=68ce022531f47497226f42f434cc8a78c000ebbe;p=poi.git [github-296] cache data of external workbook. Thanks to aspojo. This closes #296 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1897311 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/ExternalLinksTable.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/ExternalLinksTable.java index 21568d4897..6aa975ee99 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/model/ExternalLinksTable.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/model/ExternalLinksTable.java @@ -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 +} diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/BaseXSSFFormulaEvaluator.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/BaseXSSFFormulaEvaluator.java index c38dd70438..722d4334e4 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/BaseXSSFFormulaEvaluator.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/BaseXSSFFormulaEvaluator.java @@ -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; diff --git a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java index 458c46d750..0846f9499a 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java @@ -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."); diff --git a/poi/src/main/java9/module-info.class b/poi/src/main/java9/module-info.class index 6274a9f771..68a5947012 100644 Binary files a/poi/src/main/java9/module-info.class and b/poi/src/main/java9/module-info.class differ