--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.ooxml;\r
+\r
+import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;\r
+\r
+import java.net.URI;\r
+\r
+/**\r
+ * Represents a hyperlink relationship.\r
+ *\r
+ * @since POI 5.2.6\r
+ */\r
+public class HyperlinkRelationship extends ReferenceRelationship {\r
+ /**\r
+ * Initializes a new instance of the HyperlinkRelationship.\r
+ *\r
+ * @param hyperlinkUri The target uri of the hyperlink relationship.\r
+ * @param isExternal Is the URI external.\r
+ * @param id The relationship ID.\r
+ */\r
+ protected HyperlinkRelationship(POIXMLDocumentPart container, URI hyperlinkUri, boolean isExternal, String id) {\r
+ super(container, hyperlinkUri, isExternal, PackageRelationshipTypes.HYPERLINK_PART, id);\r
+ }\r
+\r
+ @Override\r
+ public String getRelationshipType() {\r
+ return PackageRelationshipTypes.HYPERLINK_PART;\r
+ }\r
+}\r
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;
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;
private PackagePart packagePart;
private POIXMLDocumentPart parent;
private final Map<String, RelationPart> relations = new LinkedHashMap<>();
+ private final Map<String, ReferenceRelationship> referenceRelationships = new LinkedHashMap<>();
private boolean isCommitted = false;
/**
// 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);
+ }
}
}
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<ReferenceRelationship> getReferenceRelationships() {
+ List<ReferenceRelationship> list = new ArrayList<>(referenceRelationships.values());
+ return Collections.unmodifiableList(list);
+ }
}
--- /dev/null
+/* ====================================================================\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.ooxml;\r
+\r
+import org.apache.poi.openxml4j.opc.PackageRelationship;\r
+import org.apache.poi.openxml4j.opc.TargetMode;\r
+\r
+import java.net.URI;\r
+\r
+/**\r
+ * Defines a reference relationship. A reference relationship can be internal or external.\r
+ *\r
+ * @since POI 5.2.6\r
+ */\r
+public abstract class ReferenceRelationship {\r
+ private POIXMLDocumentPart container;\r
+ private final String relationshipType;\r
+ private final boolean external;\r
+ private final String id;\r
+ private final URI uri;\r
+\r
+ protected ReferenceRelationship(POIXMLDocumentPart container, PackageRelationship packageRelationship) {\r
+ if (packageRelationship == null) {\r
+ throw new IllegalArgumentException("packageRelationship");\r
+ }\r
+\r
+ this.container = container;\r
+ this.relationshipType = packageRelationship.getRelationshipType();\r
+ this.uri = packageRelationship.getTargetURI();\r
+ this.external = packageRelationship.getTargetMode() == TargetMode.EXTERNAL;\r
+ this.id = packageRelationship.getId();\r
+ }\r
+\r
+ protected ReferenceRelationship(POIXMLDocumentPart container, URI targetUri, boolean isExternal, String relationshipType, String id) {\r
+ if (targetUri == null) {\r
+ throw new IllegalArgumentException("targetUri");\r
+ }\r
+\r
+ this.container = container;\r
+ this.relationshipType = relationshipType;\r
+ this.uri = targetUri;\r
+ this.id = id;\r
+ this.external = isExternal;\r
+ }\r
+\r
+ public POIXMLDocumentPart getContainer() {\r
+ return container;\r
+ }\r
+\r
+ public String getRelationshipType() {\r
+ return relationshipType;\r
+ }\r
+\r
+ public boolean isExternal() {\r
+ return external;\r
+ }\r
+\r
+ public String getId() {\r
+ return id;\r
+ }\r
+\r
+ public URI getUri() {\r
+ return uri;\r
+ }\r
+}\r
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
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;
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;
// 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();
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;
addRelation(rp, clonedSheet);
}
+ // copy sheet's reference relations;
+ List<ReferenceRelationship> 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) {
addRelation(rp, clonedDg);
}
}
+
+ // copy sheet's reference relations;
+ List<ReferenceRelationship> srcRefs = drawingPatriarch.getReferenceRelationships();
+ for (ReferenceRelationship ref : srcRefs) {
+ if (ref instanceof HyperlinkRelationship) {
+ clonedDg.createHyperlink(ref.getUri(), ref.isExternal(), ref.getId());
+ }
+ }
}
}
XSSFSheet.cloneTables(clonedSheet);
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);
}
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());
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;
// Check the relations from this
Collection<RelationPart> rels = slide.getRelationParts();
+ Collection<ReferenceRelationship> 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) {
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;
}
}
+ @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());
+ }
+ }
}
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;
assertEquals(1, wb1.getNumberOfSheets());
XSSFSheet sh = wb1.getSheetAt(0);
XSSFDrawing drawing = sh.createDrawingPatriarch();
- List<RelationPart> rels = drawing.getRelationParts();
- assertEquals(1, rels.size());
- assertEquals("Sheet1!A1", rels.get(0).getRelationship().getTargetURI().getFragment());
+ List<ReferenceRelationship> 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());
}
}
}