From bd1ea63abe44de41d9cb90f4457bc1b939a7f1c1 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Wed, 29 Sep 2021 16:16:32 +0000 Subject: [PATCH] [github-254] Implement XSSFWorkbook linkExternalWorkbook. Thanks to @aspojo. This closes #254 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1893728 13f79535-47bb-0310-9956-ffa450edef68 --- poi-integration/build.gradle | 3 +- .../xssf/usermodel/XSSFCreationHelper.java | 22 +++++++- .../poi/xssf/usermodel/XSSFWorkbook.java | 49 +++++++++++++++--- .../poi/xssf/usermodel/TestXSSFWorkbook.java | 41 +++++++++++++-- .../spreadsheet/link-external-workbook-a.xlsx | Bin 0 -> 3274 bytes .../spreadsheet/link-external-workbook-b.xlsx | Bin 0 -> 4080 bytes 6 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 test-data/spreadsheet/link-external-workbook-a.xlsx create mode 100644 test-data/spreadsheet/link-external-workbook-b.xlsx diff --git a/poi-integration/build.gradle b/poi-integration/build.gradle index 3bc58132ef..1c01901e59 100644 --- a/poi-integration/build.gradle +++ b/poi-integration/build.gradle @@ -44,7 +44,8 @@ dependencies { testImplementation 'org.apache.ant:ant:1.10.11' testImplementation 'org.apache.commons:commons-collections4:4.4' testImplementation 'com.google.guava:guava:31.0.1-jre' - testRuntimeOnly 'org.slf4j:slf4j-api:1.7.32' + testRuntimeOnly "org.apache.logging.log4j:log4j-core:${log4jVersion}" + testRuntimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:${log4jVersion}" misc(project(':poi-ooxml')) { capabilities { diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCreationHelper.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCreationHelper.java index fedbc8d3dd..3adb47b8b1 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCreationHelper.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCreationHelper.java @@ -18,14 +18,20 @@ package org.apache.poi.xssf.usermodel; import org.apache.poi.common.usermodel.HyperlinkType; import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.FormulaEvaluator; import org.apache.poi.ss.usermodel.Hyperlink; +import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.AreaReference; import org.apache.poi.ss.util.CellReference; import org.apache.poi.util.Internal; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTColor; +import java.util.HashMap; +import java.util.Map; + public class XSSFCreationHelper implements CreationHelper { private final XSSFWorkbook workbook; + private final Map referencedWorkbooks; /** * Should only be called by {@link XSSFWorkbook#getCreationHelper()} @@ -35,6 +41,7 @@ public class XSSFCreationHelper implements CreationHelper { @Internal public XSSFCreationHelper(XSSFWorkbook wb) { workbook = wb; + referencedWorkbooks = new HashMap<>(); } /** @@ -74,7 +81,12 @@ public class XSSFCreationHelper implements CreationHelper { */ @Override public XSSFFormulaEvaluator createFormulaEvaluator() { - return new XSSFFormulaEvaluator(workbook); + XSSFFormulaEvaluator evaluator = new XSSFFormulaEvaluator(workbook); + Map evaluatorMap = new HashMap<>(); + evaluatorMap.put("", evaluator); + this.referencedWorkbooks.forEach((name,otherWorkbook)->evaluatorMap.put(name,otherWorkbook.getCreationHelper().createFormulaEvaluator())); + evaluator.setupReferencedWorkbooks(evaluatorMap); + return evaluator; } /** @@ -104,4 +116,12 @@ public class XSSFCreationHelper implements CreationHelper { public AreaReference createAreaReference(CellReference topLeft, CellReference bottomRight) { return new AreaReference(topLeft, bottomRight, workbook.getSpreadsheetVersion()); } + + protected Map getReferencedWorkbooks() { + return referencedWorkbooks; + } + + protected void addExternalWorkbook(String name, Workbook workbook) { + this.referencedWorkbooks.put(name,workbook); + } } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index 38daa8d85f..fef5bece7b 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -468,6 +468,8 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Date1904Su namedRangesByName = new ArrayListValuedHashMap<>(); sheets = new ArrayList<>(); pivotTables = new ArrayList<>(); + + externalLinks = new ArrayList<>(); } private void setBookViewsIfMissing() { @@ -1971,18 +1973,53 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Date1904Su * 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 linked / referenced. - * - * Note: this is not implemented and thus currently throws an Exception stating this. + *

+ * This support is still regarded as in beta and may change + *

+ * see https://bz.apache.org/bugzilla/show_bug.cgi?id=57184 * * @param name The name the workbook will be referenced as in formulas * @param workbook The open workbook to fetch the link required information from - * - * @throws RuntimeException stating that this method is not implemented yet. + * @return index position for external workbook + * @since POI 5.1.0 */ + @Beta @Override - @NotImplemented public int linkExternalWorkbook(String name, Workbook workbook) { - throw new RuntimeException("Not Implemented - see bug #57184"); + int externalLinkIdx=-1; + if (!getCreationHelper().getReferencedWorkbooks().containsKey(name)){ + externalLinkIdx = this.getNextPartNumber(XSSFRelation.EXTERNAL_LINKS, + this.getPackagePart().getPackage().getPartsByContentType(XSSFRelation.EXTERNAL_LINKS.getContentType()).size()); + POIXMLDocumentPart.RelationPart rp = this.createRelationship(XSSFRelation.EXTERNAL_LINKS, XSSFFactory.getInstance(), externalLinkIdx, false); + ExternalLinksTable linksTable = rp.getDocumentPart(); + linksTable.setLinkedFileName(name); + this.getExternalLinksTable().add(linksTable); + + CTExternalReference ctExternalReference = this.getCTWorkbook().addNewExternalReferences().addNewExternalReference(); + ctExternalReference.setId(rp.getRelationship().getId()); + + } else { + List relationParts = getRelationParts(); + for (RelationPart relationPart : relationParts) { + if (relationPart.getDocumentPart() instanceof ExternalLinksTable) { + ExternalLinksTable linksTable = relationPart.getDocumentPart(); + String linkedFileName = linksTable.getLinkedFileName(); + if(linkedFileName.equals(name)){ + String s = relationPart.getRelationship().getTargetURI().toString(); + String s2 = XSSFRelation.EXTERNAL_LINKS.getDefaultFileName(); + String numStr = s.substring(s2.indexOf('#'), s2.indexOf('.')); + externalLinkIdx = Integer.parseInt(numStr); + break; + } + } + } + } + + XSSFCreationHelper creationHelper = getCreationHelper(); + creationHelper.addExternalWorkbook(name,workbook); + + return externalLinkIdx; + } /** 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 bef3a36519..e8c7e5ad14 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 @@ -19,9 +19,7 @@ package org.apache.poi.xssf.usermodel; import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM; import static org.apache.poi.hssf.HSSFTestDataSamples.openSampleFileStream; -import static org.apache.poi.xssf.XSSFTestDataSamples.openSampleWorkbook; -import static org.apache.poi.xssf.XSSFTestDataSamples.writeOut; -import static org.apache.poi.xssf.XSSFTestDataSamples.writeOutAndReadBack; +import static org.apache.poi.xssf.XSSFTestDataSamples.*; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -36,6 +34,7 @@ import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.function.Consumer; import java.util.zip.CRC32; import org.apache.poi.POIDataSamples; @@ -66,6 +65,7 @@ import org.apache.poi.util.TempFile; import org.apache.poi.xddf.usermodel.chart.XDDFBarChartData; import org.apache.poi.xddf.usermodel.chart.XDDFChartData; import org.apache.poi.xssf.XSSFITestDataProvider; +import org.apache.poi.xssf.XSSFTestDataSamples; import org.apache.poi.xssf.model.StylesTable; import org.junit.jupiter.api.Test; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCalcPr; @@ -1215,4 +1215,39 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook { private static String ref(Cell cell) { return new CellReference(cell).formatAsString(); } + + @Test + void testLinkExternalWorkbook() throws Exception { + String nameA = "link-external-workbook-a.xlsx"; + + try ( + XSSFWorkbook a = new XSSFWorkbook(); + XSSFWorkbook b = new XSSFWorkbook() + ) { + XSSFRow row1 = a.createSheet().createRow(0); + row1.createCell(0).setCellValue(10); + row1.createCell(1).setCellValue(20); + + XSSFRow row2 = b.createSheet().createRow(0); + XSSFCell cell = row2.createCell(0); + + b.linkExternalWorkbook(nameA, a); + String formula = String.format("SUM('[%s]Sheet0'!A1:B1)", nameA); + cell.setCellFormula(formula); + XSSFFormulaEvaluator evaluator = b.getCreationHelper().createFormulaEvaluator(); + evaluator.evaluateFormulaCell(cell); + double cellValue = cell.getNumericCellValue(); + assertEquals(cellValue,30.0); + /* + try (FileOutputStream out = new FileOutputStream(getSampleFile(nameA))) { + a.write(out); + } + String nameB = "link-external-workbook-b.xlsx"; + try (FileOutputStream out = new FileOutputStream(getSampleFile(nameB))) { + b.write(out); + } + */ + } + } + } diff --git a/test-data/spreadsheet/link-external-workbook-a.xlsx b/test-data/spreadsheet/link-external-workbook-a.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..fc983ccb08613967cf0325976fc47379f935f615 GIT binary patch literal 3274 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 literal 0 HcmV?d00001 diff --git a/test-data/spreadsheet/link-external-workbook-b.xlsx b/test-data/spreadsheet/link-external-workbook-b.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..30d08f310c34106881673a8a7d57800318eee4a1 GIT binary patch literal 4080 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 MS|BlMnT>q+FLDGj-T(jq literal 0 HcmV?d00001