From eebb3717e04341aa041be14f8322ac4dd835c286 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Fri, 19 Apr 2024 09:23:54 +0000 Subject: [PATCH] [bug-63189] support hyperlink relationships. Thanks to Ohyoung Kwon. This closes #617 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1917134 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/ooxml/HyperlinkRelationship.java | 44 +++++++++ .../apache/poi/ooxml/POIXMLDocumentPart.java | 90 ++++++++++++------ .../poi/ooxml/ReferenceRelationship.java | 79 +++++++++++++++ .../openxml4j/opc/PackageRelationship.java | 6 ++ .../marshallers/ZipPartMarshaller.java | 11 ++- .../poi/xssf/usermodel/XSSFWorkbook.java | 18 ++++ .../apache/poi/openxml4j/opc/TestPackage.java | 2 +- .../poi/openxml4j/opc/TestRelationships.java | 9 +- .../org/apache/poi/xslf/TestXSLFBugs.java | 18 +++- .../apache/poi/xssf/TestXSSFCloneSheet.java | 82 ++++++++++++++++ .../poi/xssf/usermodel/TestXSSFBugs.java | 13 +-- test-data/spreadsheet/bug63189.xlsx | Bin 0 -> 11027 bytes 12 files changed, 327 insertions(+), 45 deletions(-) create mode 100644 poi-ooxml/src/main/java/org/apache/poi/ooxml/HyperlinkRelationship.java create mode 100644 poi-ooxml/src/main/java/org/apache/poi/ooxml/ReferenceRelationship.java create mode 100644 test-data/spreadsheet/bug63189.xlsx diff --git a/poi-ooxml/src/main/java/org/apache/poi/ooxml/HyperlinkRelationship.java b/poi-ooxml/src/main/java/org/apache/poi/ooxml/HyperlinkRelationship.java new file mode 100644 index 0000000000..e9649b3d52 --- /dev/null +++ b/poi-ooxml/src/main/java/org/apache/poi/ooxml/HyperlinkRelationship.java @@ -0,0 +1,44 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.ooxml; + +import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; + +import java.net.URI; + +/** + * Represents a hyperlink relationship. + * + * @since POI 5.2.6 + */ +public class HyperlinkRelationship extends ReferenceRelationship { + /** + * Initializes a new instance of the HyperlinkRelationship. + * + * @param hyperlinkUri The target uri of the hyperlink relationship. + * @param isExternal Is the URI external. + * @param id The relationship ID. + */ + protected HyperlinkRelationship(POIXMLDocumentPart container, URI hyperlinkUri, boolean isExternal, String id) { + super(container, hyperlinkUri, isExternal, PackageRelationshipTypes.HYPERLINK_PART, id); + } + + @Override + public String getRelationshipType() { + return PackageRelationshipTypes.HYPERLINK_PART; + } +} diff --git a/poi-ooxml/src/main/java/org/apache/poi/ooxml/POIXMLDocumentPart.java b/poi-ooxml/src/main/java/org/apache/poi/ooxml/POIXMLDocumentPart.java index c83ccfcaaa..c7c920f4eb 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/ooxml/POIXMLDocumentPart.java +++ b/poi-ooxml/src/main/java/org/apache/poi/ooxml/POIXMLDocumentPart.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.apache.logging.log4j.LogManager; @@ -38,7 +39,6 @@ import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; import org.apache.poi.openxml4j.opc.PackagingURIHelper; import org.apache.poi.openxml4j.opc.TargetMode; -import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; import org.apache.poi.xddf.usermodel.chart.XDDFChart; import org.apache.poi.xssf.usermodel.XSSFRelation; @@ -59,6 +59,7 @@ public class POIXMLDocumentPart { private PackagePart packagePart; private POIXMLDocumentPart parent; private final Map relations = new LinkedHashMap<>(); + private final Map referenceRelationships = new LinkedHashMap<>(); private boolean isCommitted = false; /** @@ -640,38 +641,42 @@ public class POIXMLDocumentPart { // scan breadth-first, so parent-relations are hopefully the shallowest element for (PackageRelationship rel : rels) { - if (rel.getTargetMode() == TargetMode.INTERNAL) { - URI uri = rel.getTargetURI(); - - // check for internal references (e.g. '#Sheet1!A1') - PackagePartName relName; - if (uri.getRawFragment() != null) { - relName = PackagingURIHelper.createPartName(uri.getPath()); - } else { - relName = PackagingURIHelper.createPartName(uri); - } + if (Objects.equals(rel.getRelationshipType(), HyperlinkRelationship.HYPERLINK_REL_TYPE)) { + referenceRelationships.put(rel.getId(), new HyperlinkRelationship(this, rel.getTargetURI(), rel.getTargetMode() == TargetMode.EXTERNAL, rel.getId())); + } else { + if (rel.getTargetMode() == TargetMode.INTERNAL) { + URI uri = rel.getTargetURI(); + + // check for internal references (e.g. '#Sheet1!A1') + PackagePartName relName; + if (uri.getRawFragment() != null) { + relName = PackagingURIHelper.createPartName(uri.getPath()); + } else { + relName = PackagingURIHelper.createPartName(uri); + } - final PackagePart p = packagePart.getPackage().getPart(relName); - if (p == null) { - LOG.atError().log("Skipped invalid entry {}", rel.getTargetURI()); - continue; - } + final PackagePart p = packagePart.getPackage().getPart(relName); + if (p == null) { + LOG.atError().log("Skipped invalid entry {}", rel.getTargetURI()); + continue; + } - POIXMLDocumentPart childPart = context.get(p); - if (childPart == null) { - childPart = factory.createDocumentPart(this, p); - //here we are checking if part if embedded and excel then set it to chart class - //so that at the time to writing we can also write updated embedded part - if (this instanceof XDDFChart && childPart instanceof XSSFWorkbook) { - ((XDDFChart) this).setWorkbook((XSSFWorkbook) childPart); + POIXMLDocumentPart childPart = context.get(p); + if (childPart == null) { + childPart = factory.createDocumentPart(this, p); + //here we are checking if part if embedded and excel then set it to chart class + //so that at the time to writing we can also write updated embedded part + if (this instanceof XDDFChart && childPart instanceof XSSFWorkbook) { + ((XDDFChart) this).setWorkbook((XSSFWorkbook) childPart); + } + childPart.parent = this; + // already add child to context, so other children can reference it + context.put(p, childPart); + readLater.add(childPart); } - childPart.parent = this; - // already add child to context, so other children can reference it - context.put(p, childPart); - readLater.add(childPart); - } - addRelation(rel, childPart); + addRelation(rel, childPart); + } } } @@ -767,4 +772,31 @@ public class POIXMLDocumentPart { throw new POIXMLException("OOXML file structure broken/invalid", e); } } + + public boolean removeReferenceRelationship(String relId) { + ReferenceRelationship existing = referenceRelationships.remove(relId); + if (existing != null) { + packagePart.removeRelationship(relId); + return true; + } + + return false; + } + + public ReferenceRelationship getReferenceRelationship(String relId) { + return referenceRelationships.get(relId); + } + + public HyperlinkRelationship createHyperlink(URI uri, boolean isExternal, String relId) { + PackageRelationship pr = packagePart.addRelationship(uri, isExternal ? TargetMode.EXTERNAL : TargetMode.INTERNAL, + HyperlinkRelationship.HYPERLINK_REL_TYPE, relId); + HyperlinkRelationship hyperlink = new HyperlinkRelationship(this, uri, isExternal, relId); + referenceRelationships.put(relId, hyperlink); + return hyperlink; + } + + public List getReferenceRelationships() { + List list = new ArrayList<>(referenceRelationships.values()); + return Collections.unmodifiableList(list); + } } diff --git a/poi-ooxml/src/main/java/org/apache/poi/ooxml/ReferenceRelationship.java b/poi-ooxml/src/main/java/org/apache/poi/ooxml/ReferenceRelationship.java new file mode 100644 index 0000000000..12dce49502 --- /dev/null +++ b/poi-ooxml/src/main/java/org/apache/poi/ooxml/ReferenceRelationship.java @@ -0,0 +1,79 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.ooxml; + +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.TargetMode; + +import java.net.URI; + +/** + * Defines a reference relationship. A reference relationship can be internal or external. + * + * @since POI 5.2.6 + */ +public abstract class ReferenceRelationship { + private POIXMLDocumentPart container; + private final String relationshipType; + private final boolean external; + private final String id; + private final URI uri; + + protected ReferenceRelationship(POIXMLDocumentPart container, PackageRelationship packageRelationship) { + if (packageRelationship == null) { + throw new IllegalArgumentException("packageRelationship"); + } + + this.container = container; + this.relationshipType = packageRelationship.getRelationshipType(); + this.uri = packageRelationship.getTargetURI(); + this.external = packageRelationship.getTargetMode() == TargetMode.EXTERNAL; + this.id = packageRelationship.getId(); + } + + protected ReferenceRelationship(POIXMLDocumentPart container, URI targetUri, boolean isExternal, String relationshipType, String id) { + if (targetUri == null) { + throw new IllegalArgumentException("targetUri"); + } + + this.container = container; + this.relationshipType = relationshipType; + this.uri = targetUri; + this.id = id; + this.external = isExternal; + } + + public POIXMLDocumentPart getContainer() { + return container; + } + + public String getRelationshipType() { + return relationshipType; + } + + public boolean isExternal() { + return external; + } + + public String getId() { + return id; + } + + public URI getUri() { + return uri; + } +} diff --git a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/PackageRelationship.java b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/PackageRelationship.java index bb7717492e..417f3899d9 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/PackageRelationship.java +++ b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/PackageRelationship.java @@ -186,6 +186,12 @@ public final class PackageRelationship { return targetUri; } + // If it's an internal hyperlink target, we don't + // need to apply our normal validation rules + if (PackageRelationshipTypes.HYPERLINK_PART.equals(relationshipType)) { + return targetUri; + } + // Internal target // If it isn't absolute, resolve it relative // to ourselves diff --git a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/marshallers/ZipPartMarshaller.java b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/marshallers/ZipPartMarshaller.java index 9519af2d35..acfe0ff2f8 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/marshallers/ZipPartMarshaller.java +++ b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/marshallers/ZipPartMarshaller.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; +import java.util.Objects; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; @@ -32,6 +33,7 @@ import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackagePartName; import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; +import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; import org.apache.poi.openxml4j.opc.PackagingURIHelper; import org.apache.poi.openxml4j.opc.StreamHelper; import org.apache.poi.openxml4j.opc.TargetMode; @@ -154,7 +156,14 @@ public final class ZipPartMarshaller implements PartMarshaller { // the relationship Target String targetValue; URI uri = rel.getTargetURI(); - if (rel.getTargetMode() == TargetMode.EXTERNAL) { + if (Objects.equals(rel.getRelationshipType(), PackageRelationshipTypes.HYPERLINK_PART)) { + // Save the target as-is - we don't need to validate it, + targetValue = uri.toString(); + if (rel.getTargetMode() == TargetMode.EXTERNAL) { + // add TargetMode attribute (as it is external link external) + relElem.setAttribute(PackageRelationship.TARGET_MODE_ATTRIBUTE_NAME, "External"); + } + } else if (rel.getTargetMode() == TargetMode.EXTERNAL) { // Save the target as-is - we don't need to validate it, // alter it etc targetValue = uri.toString(); 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 e43bba7321..7c03b27fa4 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 @@ -47,10 +47,12 @@ import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.poi.hpsf.ClassIDPredefined; +import org.apache.poi.ooxml.HyperlinkRelationship; import org.apache.poi.ooxml.POIXMLDocument; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.ooxml.POIXMLException; import org.apache.poi.ooxml.POIXMLProperties; +import org.apache.poi.ooxml.ReferenceRelationship; import org.apache.poi.ooxml.util.PackageHelper; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; @@ -683,6 +685,14 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Date1904Su addRelation(rp, clonedSheet); } + // copy sheet's reference relations; + List referenceRelationships = srcSheet.getReferenceRelationships(); + for (ReferenceRelationship ref : referenceRelationships) { + if (ref instanceof HyperlinkRelationship) { + createHyperlink(ref.getUri(), ref.isExternal(), ref.getId()); + } + } + try { for(PackageRelationship pr : srcSheet.getPackagePart().getRelationships()) { if (pr.getTargetMode() == TargetMode.EXTERNAL) { @@ -742,6 +752,14 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Date1904Su addRelation(rp, clonedDg); } } + + // copy sheet's reference relations; + List srcRefs = drawingPatriarch.getReferenceRelationships(); + for (ReferenceRelationship ref : srcRefs) { + if (ref instanceof HyperlinkRelationship) { + clonedDg.createHyperlink(ref.getUri(), ref.isExternal(), ref.getId()); + } + } } } XSSFSheet.cloneTables(clonedSheet); diff --git a/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/TestPackage.java b/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/TestPackage.java index d0d0514a80..6973c0cbc5 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/TestPackage.java +++ b/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/TestPackage.java @@ -284,7 +284,7 @@ public final class TestPackage { assertEquals(1, rels.size()); PackageRelationship rel = rels.getRelationship(0); assertNotNull(rel); - assertEquals("Sheet1!A1", rel.getTargetURI().getRawFragment()); + assertEquals("#Sheet1!A1", rel.getTargetURI().toString()); assertMSCompatibility(pkg); } diff --git a/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/TestRelationships.java b/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/TestRelationships.java index 6af8b2c1f3..fe370a6fe5 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/TestRelationships.java +++ b/poi-ooxml/src/test/java/org/apache/poi/openxml4j/opc/TestRelationships.java @@ -323,10 +323,11 @@ class TestRelationships { PackageRelationship rId1 = drawingPart.getRelationship("rId1"); URI parent = drawingPart.getPartName().getURI(); - URI rel1 = parent.relativize(rId1.getTargetURI()); - URI rel11 = PackagingURIHelper.relativizeURI(drawingPart.getPartName().getURI(), rId1.getTargetURI()); - assertEquals("'Another Sheet'!A1", rel1.getFragment()); - assertEquals("'Another Sheet'!A1", rel11.getFragment()); + // Hyperlink is not a target of relativize() because it is not resolved based on sourceURI in getTargetURI() +// URI rel1 = parent.relativize(rId1.getTargetURI()); +// URI rel11 = PackagingURIHelper.relativizeURI(drawingPart.getPartName().getURI(), rId1.getTargetURI()); +// assertEquals("'Another Sheet'!A1", rel1.getFragment()); +// assertEquals("'Another Sheet'!A1", rel11.getFragment()); PackageRelationship rId2 = drawingPart.getRelationship("rId2"); URI rel2 = PackagingURIHelper.relativizeURI(drawingPart.getPartName().getURI(), rId2.getTargetURI()); diff --git a/poi-ooxml/src/test/java/org/apache/poi/xslf/TestXSLFBugs.java b/poi-ooxml/src/test/java/org/apache/poi/xslf/TestXSLFBugs.java index 8d7a37dd7a..1b0f468416 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xslf/TestXSLFBugs.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xslf/TestXSLFBugs.java @@ -53,8 +53,10 @@ import org.apache.commons.io.output.NullPrintStream; import org.apache.poi.POIDataSamples; import org.apache.poi.common.usermodel.HyperlinkType; import org.apache.poi.extractor.ExtractorFactory; +import org.apache.poi.ooxml.HyperlinkRelationship; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.ooxml.POIXMLDocumentPart.RelationPart; +import org.apache.poi.ooxml.ReferenceRelationship; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackagePartName; @@ -384,23 +386,31 @@ class TestXSLFBugs { // Check the relations from this Collection rels = slide.getRelationParts(); + Collection referenceRelationships = slide.getReferenceRelationships(); // Should have 6 relations: // 1 external hyperlink (skipped from list) // 4 internal hyperlinks // 1 slide layout - assertEquals(5, rels.size()); + assertEquals(1, rels.size()); + assertEquals(5, referenceRelationships.size()); int layouts = 0; int hyperlinks = 0; + int extHyperLinks = 0; for (RelationPart p : rels) { - if (p.getRelationship().getRelationshipType().equals(XSLFRelation.HYPERLINK.getRelation())) { - hyperlinks++; - } else if (p.getDocumentPart() instanceof XSLFSlideLayout) { + if (p.getDocumentPart() instanceof XSLFSlideLayout) { layouts++; } } + for (ReferenceRelationship ref : referenceRelationships) { + if (ref instanceof HyperlinkRelationship) { + if (ref.isExternal()) extHyperLinks++; + else hyperlinks++; + } + } assertEquals(1, layouts); assertEquals(4, hyperlinks); + assertEquals(1, extHyperLinks); // Hyperlinks should all be to #_ftn1 or #ftnref1 for (RelationPart p : rels) { diff --git a/poi-ooxml/src/test/java/org/apache/poi/xssf/TestXSSFCloneSheet.java b/poi-ooxml/src/test/java/org/apache/poi/xssf/TestXSSFCloneSheet.java index 72fc676c6e..e966520bed 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xssf/TestXSSFCloneSheet.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xssf/TestXSSFCloneSheet.java @@ -18,19 +18,31 @@ package org.apache.poi.xssf; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.apache.poi.ooxml.ReferenceRelationship; +import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.ss.usermodel.BaseTestCloneSheet; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xddf.usermodel.chart.XDDFDataSource; import org.apache.poi.xssf.usermodel.XSSFDrawing; +import org.apache.poi.xssf.usermodel.XSSFPicture; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.openxmlformats.schemas.drawingml.x2006.main.CTBlip; +import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTHyperlink; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualPictureProperties; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTPicture; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTPictureNonVisual; import java.io.IOException; @@ -127,4 +139,74 @@ class TestXSSFCloneSheet extends BaseTestCloneSheet { } } + @Test + void testBug63189() throws IOException { + try (XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("bug63189.xlsx")) { + // given + String linkRelationType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"; + String linkTargetUrl = "#Sheet3!A1"; + String imageRelationType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"; + String imageTargetUrl = "/xl/media/image1.png"; + + XSSFSheet srcSheet = workbook.getSheetAt(0); + assertEquals("CloneMe", srcSheet.getSheetName()); + XSSFDrawing drawing = srcSheet.getDrawingPatriarch(); + assertNotNull(drawing); + assertEquals(1, drawing.getShapes().size()); + assertInstanceOf(XSSFPicture.class, drawing.getShapes().get(0)); + XSSFPicture lPic = (XSSFPicture)drawing.getShapes().get(0); + CTPicture pic = lPic.getCTPicture(); + CTPictureNonVisual nvPicPr = pic.getNvPicPr(); + CTNonVisualDrawingProps cNvPr = nvPicPr.getCNvPr(); + assertTrue(cNvPr.isSetHlinkClick()); + CTHyperlink hlinkClick = cNvPr.getHlinkClick(); + String linkRelId = hlinkClick.getId(); + + ReferenceRelationship linkRel = drawing.getReferenceRelationship(linkRelId); + assertEquals(linkRelationType, linkRel.getRelationshipType()); + assertEquals(linkTargetUrl, linkRel.getUri().toString()); + + CTNonVisualPictureProperties cNvPicPr = nvPicPr.getCNvPicPr(); + assertTrue(cNvPicPr.getPicLocks().getNoChangeAspect()); + + CTBlipFillProperties blipFill = pic.getBlipFill(); + CTBlip blip = blipFill.getBlip(); + String imageRelId = blip.getEmbed(); + + PackageRelationship imageRel = drawing.getRelationPartById(imageRelId).getRelationship(); + assertEquals(imageRelationType, imageRel.getRelationshipType()); + assertEquals(imageTargetUrl, imageRel.getTargetURI().toString()); + + // when + XSSFSheet clonedSheet = workbook.cloneSheet(0); + + // then + XSSFDrawing drawing2 = clonedSheet.getDrawingPatriarch(); + assertNotNull(drawing2); + assertEquals(1, drawing2.getShapes().size()); + assertInstanceOf(XSSFPicture.class, drawing2.getShapes().get(0)); + XSSFPicture lPic2 = (XSSFPicture)drawing2.getShapes().get(0); + CTPicture pic2 = lPic2.getCTPicture(); + CTPictureNonVisual nvPicPr2 = pic2.getNvPicPr(); + CTNonVisualDrawingProps cNvPr2 = nvPicPr2.getCNvPr(); + assertTrue(cNvPr2.isSetHlinkClick()); + CTHyperlink hlinkClick2 = cNvPr2.getHlinkClick(); + String linkRelId2 = hlinkClick2.getId(); + + ReferenceRelationship linkRel2 = drawing2.getReferenceRelationship(linkRelId2); + assertEquals(linkRelationType, linkRel2.getRelationshipType()); + assertEquals(linkTargetUrl, linkRel2.getUri().toString()); + + CTNonVisualPictureProperties cNvPicPr2 = nvPicPr2.getCNvPicPr(); + assertTrue(cNvPicPr2.getPicLocks().getNoChangeAspect()); + + CTBlipFillProperties blipFill2 = pic2.getBlipFill(); + CTBlip blip2 = blipFill2.getBlip(); + String imageRelId2 = blip2.getEmbed(); + + PackageRelationship imageRel2 = drawing2.getRelationPartById(imageRelId2).getRelationship(); + assertEquals(imageRelationType, imageRel2.getRelationshipType()); + assertEquals(imageTargetUrl, imageRel2.getTargetURI().toString()); + } + } } diff --git a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFBugs.java b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFBugs.java index 37013d73be..09d2120c76 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFBugs.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFBugs.java @@ -60,6 +60,7 @@ import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.ooxml.POIXMLDocumentPart.RelationPart; import org.apache.poi.ooxml.POIXMLException; import org.apache.poi.ooxml.POIXMLProperties; +import org.apache.poi.ooxml.ReferenceRelationship; import org.apache.poi.ooxml.util.DocumentHelper; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.InvalidOperationException; @@ -234,18 +235,18 @@ public final class TestXSSFBugs extends BaseTestBugzillaIssues { assertEquals(1, wb1.getNumberOfSheets()); XSSFSheet sh = wb1.getSheetAt(0); XSSFDrawing drawing = sh.createDrawingPatriarch(); - List rels = drawing.getRelationParts(); - assertEquals(1, rels.size()); - assertEquals("Sheet1!A1", rels.get(0).getRelationship().getTargetURI().getFragment()); + List referenceRelationships = drawing.getReferenceRelationships(); + assertEquals(1, referenceRelationships.size()); + assertEquals("#Sheet1!A1", referenceRelationships.get(0).getUri().toString()); // And again, just to be sure try (XSSFWorkbook wb2 = writeOutAndReadBack(wb1)) { assertEquals(1, wb2.getNumberOfSheets()); sh = wb2.getSheetAt(0); drawing = sh.createDrawingPatriarch(); - rels = drawing.getRelationParts(); - assertEquals(1, rels.size()); - assertEquals("Sheet1!A1", rels.get(0).getRelationship().getTargetURI().getFragment()); + referenceRelationships = drawing.getReferenceRelationships(); + assertEquals(1, referenceRelationships.size()); + assertEquals("#Sheet1!A1", referenceRelationships.get(0).getUri().toString()); } } } diff --git a/test-data/spreadsheet/bug63189.xlsx b/test-data/spreadsheet/bug63189.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..cea94b126edc20e7f85232f958369f51673722fc GIT binary patch literal 11027 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