756 lines
29 KiB
Java
756 lines
29 KiB
Java
/* ====================================================================
|
|
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 java.io.IOException;
|
|
import java.net.URI;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
|
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
|
|
import org.apache.poi.openxml4j.exceptions.PartAlreadyExistsException;
|
|
import org.apache.poi.openxml4j.opc.OPCPackage;
|
|
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.TargetMode;
|
|
import org.apache.poi.util.Internal;
|
|
import org.apache.poi.util.POILogFactory;
|
|
import org.apache.poi.util.POILogger;
|
|
import org.apache.poi.xddf.usermodel.chart.XDDFChart;
|
|
import org.apache.poi.xssf.usermodel.XSSFRelation;
|
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
|
import org.apache.poi.xwpf.usermodel.XWPFRelation;
|
|
|
|
/**
|
|
* Represents an entry of a OOXML package.
|
|
* <p>
|
|
* Each POIXMLDocumentPart keeps a reference to the underlying a {@link org.apache.poi.openxml4j.opc.PackagePart}.
|
|
* </p>
|
|
*/
|
|
public class POIXMLDocumentPart {
|
|
private static final POILogger logger = POILogFactory.getLogger(POIXMLDocumentPart.class);
|
|
|
|
private String coreDocumentRel = PackageRelationshipTypes.CORE_DOCUMENT;
|
|
private PackagePart packagePart;
|
|
private POIXMLDocumentPart parent;
|
|
private Map<String, RelationPart> relations = new LinkedHashMap<>();
|
|
private boolean isCommited = false;
|
|
|
|
/**
|
|
* to check whether embedded part is already committed
|
|
*
|
|
* @return return true if embedded part is committed
|
|
*/
|
|
public boolean isCommited() {
|
|
return isCommited;
|
|
}
|
|
|
|
/**
|
|
* setter method to set embedded part is committed
|
|
*
|
|
* @param isCommited boolean value
|
|
*/
|
|
public void setCommited(boolean isCommited) {
|
|
this.isCommited = isCommited;
|
|
}
|
|
|
|
/**
|
|
* The RelationPart is a cached relationship between the document, which contains the RelationPart,
|
|
* and one of its referenced child document parts.
|
|
* The child document parts may only belong to one parent, but it's often referenced by other
|
|
* parents too, having varying {@link PackageRelationship#getId() relationship ids} pointing to it.
|
|
*/
|
|
public static class RelationPart {
|
|
private final PackageRelationship relationship;
|
|
private final POIXMLDocumentPart documentPart;
|
|
|
|
RelationPart(PackageRelationship relationship, POIXMLDocumentPart documentPart) {
|
|
this.relationship = relationship;
|
|
this.documentPart = documentPart;
|
|
}
|
|
|
|
/**
|
|
* @return the cached relationship, which uniquely identifies this child document part within the parent
|
|
*/
|
|
public PackageRelationship getRelationship() {
|
|
return relationship;
|
|
}
|
|
|
|
/**
|
|
* @param <T> the cast of the caller to a document sub class
|
|
* @return the child document part
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public <T extends POIXMLDocumentPart> T getDocumentPart() {
|
|
return (T) documentPart;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Counter that provides the amount of incoming relations from other parts
|
|
* to this part.
|
|
*/
|
|
private int relationCounter;
|
|
|
|
int incrementRelationCounter() {
|
|
relationCounter++;
|
|
return relationCounter;
|
|
}
|
|
|
|
int decrementRelationCounter() {
|
|
relationCounter--;
|
|
return relationCounter;
|
|
}
|
|
|
|
int getRelationCounter() {
|
|
return relationCounter;
|
|
}
|
|
|
|
/**
|
|
* Construct POIXMLDocumentPart representing a "core document" package part.
|
|
*
|
|
* @param pkg the OPCPackage containing this document
|
|
*/
|
|
public POIXMLDocumentPart(OPCPackage pkg) {
|
|
this(pkg, PackageRelationshipTypes.CORE_DOCUMENT);
|
|
}
|
|
|
|
/**
|
|
* Construct POIXMLDocumentPart representing a custom "core document" package part.
|
|
*
|
|
* @param pkg the OPCPackage containing this document
|
|
* @param coreDocumentRel the relation type of this document
|
|
*/
|
|
public POIXMLDocumentPart(OPCPackage pkg, String coreDocumentRel) {
|
|
this(getPartFromOPCPackage(pkg, coreDocumentRel));
|
|
this.coreDocumentRel = coreDocumentRel;
|
|
}
|
|
|
|
/**
|
|
* Creates new POIXMLDocumentPart - called by client code to create new parts from scratch.
|
|
*
|
|
* @see #createRelationship(POIXMLRelation, POIXMLFactory, int, boolean)
|
|
*/
|
|
public POIXMLDocumentPart() {
|
|
}
|
|
|
|
/**
|
|
* Creates an POIXMLDocumentPart representing the given package part and relationship.
|
|
* Called by {@link #read(POIXMLFactory, java.util.Map)} when reading in an existing file.
|
|
*
|
|
* @param part - The package part that holds xml data representing this sheet.
|
|
* @see #read(POIXMLFactory, java.util.Map)
|
|
* @since POI 3.14-Beta1
|
|
*/
|
|
public POIXMLDocumentPart(PackagePart part) {
|
|
this(null, part);
|
|
}
|
|
|
|
/**
|
|
* Creates an POIXMLDocumentPart representing the given package part, relationship and parent
|
|
* Called by {@link #read(POIXMLFactory, java.util.Map)} when reading in an existing file.
|
|
*
|
|
* @param parent - Parent part
|
|
* @param part - The package part that holds xml data representing this sheet.
|
|
* @see #read(POIXMLFactory, java.util.Map)
|
|
* @since POI 3.14-Beta1
|
|
*/
|
|
public POIXMLDocumentPart(POIXMLDocumentPart parent, PackagePart part) {
|
|
this.packagePart = part;
|
|
this.parent = parent;
|
|
}
|
|
|
|
/**
|
|
* When you open something like a theme, call this to
|
|
* re-base the XML Document onto the core child of the
|
|
* current core document
|
|
*
|
|
* @param pkg the package to be rebased
|
|
* @throws InvalidFormatException if there was an error in the core document relation
|
|
* @throws IllegalStateException if there are more than one core document relations
|
|
*/
|
|
protected final void rebase(OPCPackage pkg) throws InvalidFormatException {
|
|
PackageRelationshipCollection cores =
|
|
packagePart.getRelationshipsByType(coreDocumentRel);
|
|
if (cores.size() != 1) {
|
|
throw new IllegalStateException(
|
|
"Tried to rebase using " + coreDocumentRel +
|
|
" but found " + cores.size() + " parts of the right type"
|
|
);
|
|
}
|
|
packagePart = packagePart.getRelatedPart(cores.getRelationship(0));
|
|
}
|
|
|
|
/**
|
|
* Provides access to the underlying PackagePart
|
|
*
|
|
* @return the underlying PackagePart
|
|
*/
|
|
public final PackagePart getPackagePart() {
|
|
return packagePart;
|
|
}
|
|
|
|
/**
|
|
* Returns the list of child relations for this POIXMLDocumentPart
|
|
*
|
|
* @return child relations
|
|
*/
|
|
public final List<POIXMLDocumentPart> getRelations() {
|
|
List<POIXMLDocumentPart> l = new ArrayList<>();
|
|
for (RelationPart rp : relations.values()) {
|
|
l.add(rp.getDocumentPart());
|
|
}
|
|
return Collections.unmodifiableList(l);
|
|
}
|
|
|
|
/**
|
|
* Returns the list of child relations for this POIXMLDocumentPart
|
|
*
|
|
* @return child relations
|
|
*/
|
|
public final List<RelationPart> getRelationParts() {
|
|
List<RelationPart> l = new ArrayList<>(relations.values());
|
|
return Collections.unmodifiableList(l);
|
|
}
|
|
|
|
/**
|
|
* Returns the target {@link POIXMLDocumentPart}, where a
|
|
* {@link PackageRelationship} is set from the {@link PackagePart} of this
|
|
* {@link POIXMLDocumentPart} to the {@link PackagePart} of the target
|
|
* {@link POIXMLDocumentPart} with a {@link PackageRelationship#getId()}
|
|
* matching the given parameter value.
|
|
*
|
|
* @param id The relation id to look for
|
|
* @return the target part of the relation, or null, if none exists
|
|
*/
|
|
public final POIXMLDocumentPart getRelationById(String id) {
|
|
RelationPart rp = getRelationPartById(id);
|
|
return (rp == null) ? null : rp.getDocumentPart();
|
|
}
|
|
|
|
/**
|
|
* Returns the target {@link RelationPart}, where a
|
|
* {@link PackageRelationship} is set from the {@link PackagePart} of this
|
|
* {@link POIXMLDocumentPart} to the {@link PackagePart} of the target
|
|
* {@link POIXMLDocumentPart} with a {@link PackageRelationship#getId()}
|
|
* matching the given parameter value.
|
|
*
|
|
* @param id The relation id to look for
|
|
* @return the target relation part, or null, if none exists
|
|
* @since 4.0.0
|
|
*/
|
|
public final RelationPart getRelationPartById(String id) {
|
|
return relations.get(id);
|
|
}
|
|
|
|
/**
|
|
* Returns the first {@link PackageRelationship#getId()} of the
|
|
* {@link PackageRelationship}, that sources from the {@link PackagePart} of
|
|
* this {@link POIXMLDocumentPart} to the {@link PackagePart} of the given
|
|
* parameter value.<p>
|
|
* <p>
|
|
* There can be multiple references to the given {@link POIXMLDocumentPart}
|
|
* and only the first in the order of creation is returned.
|
|
*
|
|
* @param part The {@link POIXMLDocumentPart} for which the according
|
|
* relation-id shall be found.
|
|
* @return The value of the {@link PackageRelationship#getId()} or null, if
|
|
* parts are not related.
|
|
*/
|
|
public final String getRelationId(POIXMLDocumentPart part) {
|
|
for (RelationPart rp : relations.values()) {
|
|
if (rp.getDocumentPart() == part) {
|
|
return rp.getRelationship().getId();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Add a new child POIXMLDocumentPart
|
|
*
|
|
* @param relId the preferred relation id, when null the next free relation id will be used
|
|
* @param relationshipType the package relationship type
|
|
* @param part the child to add
|
|
* @return the new RelationPart
|
|
* @since 3.14-Beta1
|
|
*/
|
|
public final RelationPart addRelation(String relId, POIXMLRelation relationshipType, POIXMLDocumentPart part) {
|
|
PackageRelationship pr = this.packagePart.findExistingRelation(part.getPackagePart());
|
|
if (pr == null) {
|
|
PackagePartName ppn = part.getPackagePart().getPartName();
|
|
String relType = relationshipType.getRelation();
|
|
pr = packagePart.addRelationship(ppn, TargetMode.INTERNAL, relType, relId);
|
|
}
|
|
addRelation(pr, part);
|
|
return new RelationPart(pr, part);
|
|
}
|
|
|
|
/**
|
|
* Add a new child POIXMLDocumentPart
|
|
*
|
|
* @param pr the relationship of the child
|
|
* @param part the child to add
|
|
*/
|
|
private void addRelation(PackageRelationship pr, POIXMLDocumentPart part) {
|
|
relations.put(pr.getId(), new RelationPart(pr, part));
|
|
part.incrementRelationCounter();
|
|
|
|
}
|
|
|
|
/**
|
|
* Remove the relation to the specified part in this package and remove the
|
|
* part, if it is no longer needed.<p>
|
|
* <p>
|
|
* If there are multiple relationships to the same part, this will only
|
|
* remove the first relationship in the order of creation. The removal
|
|
* via the part id ({@link #removeRelation(String)} is preferred.
|
|
*
|
|
* @param part the part which relation is to be removed from this document
|
|
*/
|
|
protected final void removeRelation(POIXMLDocumentPart part) {
|
|
removeRelation(part, true);
|
|
}
|
|
|
|
/**
|
|
* Remove the relation to the specified part in this package and remove the
|
|
* part, if it is no longer needed and flag is set to true.<p>
|
|
* <p>
|
|
* If there are multiple relationships to the same part, this will only
|
|
* remove the first relationship in the order of creation. The removal
|
|
* via the part id ({@link #removeRelation(String, boolean)} is preferred.
|
|
*
|
|
* @param part The related part, to which the relation shall be removed.
|
|
* @param removeUnusedParts true, if the part shall be removed from the package if not
|
|
* needed any longer.
|
|
* @return true, if the relation was removed
|
|
*/
|
|
protected final boolean removeRelation(POIXMLDocumentPart part, boolean removeUnusedParts) {
|
|
String id = getRelationId(part);
|
|
return removeRelation(id, removeUnusedParts);
|
|
}
|
|
|
|
/**
|
|
* Remove the relation to the specified part in this package and remove the
|
|
* part, if it is no longer needed.<p>
|
|
* <p>
|
|
* If there are multiple relationships to the same part, this will only
|
|
* remove the first relationship in the order of creation. The removal
|
|
* via the part id ({@link #removeRelation(String)} is preferred.
|
|
*
|
|
* @param partId the part id which relation is to be removed from this document
|
|
* @since 4.0.0
|
|
*/
|
|
protected final void removeRelation(String partId) {
|
|
removeRelation(partId, true);
|
|
}
|
|
|
|
/**
|
|
* Remove the relation to the specified part in this package and remove the
|
|
* part, if it is no longer needed and flag is set to true.<p>
|
|
*
|
|
* @param partId The related part id, to which the relation shall be removed.
|
|
* @param removeUnusedParts true, if the part shall be removed from the package if not
|
|
* needed any longer.
|
|
* @return true, if the relation was removed
|
|
* @since 4.0.0
|
|
*/
|
|
private final boolean removeRelation(String partId, boolean removeUnusedParts) {
|
|
RelationPart rp = relations.get(partId);
|
|
if (rp == null) {
|
|
// part is not related with this POIXMLDocumentPart
|
|
return false;
|
|
}
|
|
POIXMLDocumentPart part = rp.getDocumentPart();
|
|
/* decrement usage counter */
|
|
part.decrementRelationCounter();
|
|
/* remove packagepart relationship */
|
|
getPackagePart().removeRelationship(partId);
|
|
/* remove POIXMLDocument from relations */
|
|
relations.remove(partId);
|
|
|
|
if (removeUnusedParts) {
|
|
/* if last relation to target part was removed, delete according target part */
|
|
if (part.getRelationCounter() == 0) {
|
|
try {
|
|
part.onDocumentRemove();
|
|
} catch (IOException e) {
|
|
throw new POIXMLException(e);
|
|
}
|
|
getPackagePart().getPackage().removePart(part.getPackagePart());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the parent POIXMLDocumentPart. All parts except root have not-null parent.
|
|
*
|
|
* @return the parent POIXMLDocumentPart or <code>null</code> for the root element.
|
|
*/
|
|
public final POIXMLDocumentPart getParent() {
|
|
return parent;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return packagePart == null ? "" : packagePart.toString();
|
|
}
|
|
|
|
/**
|
|
* Save the content in the underlying package part.
|
|
* Default implementation is empty meaning that the package part is left unmodified.
|
|
* <p>
|
|
* Sub-classes should override and add logic to marshal the "model" into Ooxml4J.
|
|
* <p>
|
|
* For example, the code saving a generic XML entry may look as follows:
|
|
* <pre>
|
|
* protected void commit() throws IOException {
|
|
* PackagePart part = getPackagePart();
|
|
* OutputStream out = part.getOutputStream();
|
|
* XmlObject bean = getXmlBean(); //the "model" which holds changes in memory
|
|
* bean.save(out, DEFAULT_XML_OPTIONS);
|
|
* out.close();
|
|
* }
|
|
* </pre>
|
|
*
|
|
* @throws IOException a subclass may throw an IOException if the changes can't be committed
|
|
*/
|
|
protected void commit() throws IOException {
|
|
|
|
}
|
|
|
|
/**
|
|
* Save changes in the underlying OOXML package.
|
|
* Recursively fires {@link #commit()} for each package part
|
|
*
|
|
* @param alreadySaved context set containing already visited nodes
|
|
* @throws IOException a related part may throw an IOException if the changes can't be saved
|
|
*/
|
|
protected final void onSave(Set<PackagePart> alreadySaved) throws IOException {
|
|
//if part is already committed then return
|
|
if (this.isCommited) {
|
|
return;
|
|
}
|
|
|
|
// this usually clears out previous content in the part...
|
|
prepareForCommit();
|
|
|
|
commit();
|
|
alreadySaved.add(this.getPackagePart());
|
|
for (RelationPart rp : relations.values()) {
|
|
POIXMLDocumentPart p = rp.getDocumentPart();
|
|
if (!alreadySaved.contains(p.getPackagePart())) {
|
|
p.onSave(alreadySaved);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure that a memory based package part does not have lingering data from previous
|
|
* commit() calls.
|
|
* <p>
|
|
* Note: This is overwritten for some objects, as *PictureData seem to store the actual content
|
|
* in the part directly without keeping a copy like all others therefore we need to handle them differently.
|
|
*/
|
|
protected void prepareForCommit() {
|
|
PackagePart part = this.getPackagePart();
|
|
if (part != null) {
|
|
part.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new child POIXMLDocumentPart
|
|
*
|
|
* @param descriptor the part descriptor
|
|
* @param factory the factory that will create an instance of the requested relation
|
|
* @return the created child POIXMLDocumentPart
|
|
* @throws PartAlreadyExistsException If rule M1.12 is not verified : Packages shall not contain
|
|
* equivalent part names and package implementers shall neither
|
|
* create nor recognize packages with equivalent part names.
|
|
*/
|
|
public final POIXMLDocumentPart createRelationship(POIXMLRelation descriptor, POIXMLFactory factory) {
|
|
return createRelationship(descriptor, factory, -1, false).getDocumentPart();
|
|
}
|
|
|
|
/**
|
|
* Create a new child POIXMLDocumentPart
|
|
*
|
|
* @param descriptor the part descriptor
|
|
* @param factory the factory that will create an instance of the requested relation
|
|
* @param idx part number
|
|
* @return the created child POIXMLDocumentPart
|
|
* @throws PartAlreadyExistsException If rule M1.12 is not verified : Packages shall not contain
|
|
* equivalent part names and package implementers shall neither
|
|
* create nor recognize packages with equivalent part names.
|
|
*/
|
|
public final POIXMLDocumentPart createRelationship(POIXMLRelation descriptor, POIXMLFactory factory, int idx) {
|
|
return createRelationship(descriptor, factory, idx, false).getDocumentPart();
|
|
}
|
|
|
|
/**
|
|
* Identifies the next available part number for a part of the given type,
|
|
* if possible, otherwise -1 if none are available.
|
|
* The found (valid) index can then be safely given to
|
|
* {@link #createRelationship(POIXMLRelation, POIXMLFactory, int)} or
|
|
* {@link #createRelationship(POIXMLRelation, POIXMLFactory, int, boolean)}
|
|
* without naming clashes.
|
|
* If parts with other types are already claiming a name for this relationship
|
|
* type (eg a {@link XSSFRelation#CHART} using the drawing part namespace
|
|
* normally used by {@link XSSFRelation#DRAWINGS}), those will be considered
|
|
* when finding the next spare number.
|
|
*
|
|
* @param descriptor The relationship type to find the part number for
|
|
* @param minIdx The minimum free index to assign, use -1 for any
|
|
* @return The next free part number, or -1 if none available
|
|
*/
|
|
protected final int getNextPartNumber(POIXMLRelation descriptor, int minIdx) {
|
|
OPCPackage pkg = packagePart.getPackage();
|
|
|
|
try {
|
|
String name = descriptor.getDefaultFileName();
|
|
if (name.equals(descriptor.getFileName(9999))) {
|
|
// Non-index based, check if default is free
|
|
PackagePartName ppName = PackagingURIHelper.createPartName(name);
|
|
if (pkg.containPart(ppName)) {
|
|
// Default name already taken, not index based, nothing free
|
|
return -1;
|
|
} else {
|
|
// Default name free
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Default to searching from 1, unless they asked for 0+
|
|
int idx = (minIdx < 0) ? 1 : minIdx;
|
|
int maxIdx = minIdx + pkg.getParts().size();
|
|
while (idx <= maxIdx) {
|
|
name = descriptor.getFileName(idx);
|
|
PackagePartName ppName = PackagingURIHelper.createPartName(name);
|
|
if (!pkg.containPart(ppName)) {
|
|
return idx;
|
|
}
|
|
idx++;
|
|
}
|
|
} catch (InvalidFormatException e) {
|
|
// Give a general wrapped exception for the problem
|
|
throw new POIXMLException(e);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Create a new child POIXMLDocumentPart
|
|
*
|
|
* @param descriptor the part descriptor
|
|
* @param factory the factory that will create an instance of the requested relation
|
|
* @param idx part number
|
|
* @param noRelation if true, then no relationship is added.
|
|
* @return the created child POIXMLDocumentPart
|
|
* @throws PartAlreadyExistsException If rule M1.12 is not verified : Packages shall not contain
|
|
* equivalent part names and package implementers shall neither
|
|
* create nor recognize packages with equivalent part names.
|
|
*/
|
|
public final RelationPart createRelationship(POIXMLRelation descriptor, POIXMLFactory factory, int idx, boolean noRelation) {
|
|
try {
|
|
PackagePartName ppName = PackagingURIHelper.createPartName(descriptor.getFileName(idx));
|
|
PackageRelationship rel = null;
|
|
PackagePart part = packagePart.getPackage().createPart(ppName, descriptor.getContentType());
|
|
if (!noRelation) {
|
|
/* only add to relations, if according relationship is being created. */
|
|
rel = packagePart.addRelationship(ppName, TargetMode.INTERNAL, descriptor.getRelation());
|
|
}
|
|
POIXMLDocumentPart doc = factory.newDocumentPart(descriptor);
|
|
doc.packagePart = part;
|
|
doc.parent = this;
|
|
if (!noRelation) {
|
|
/* only add to relations, if according relationship is being created. */
|
|
addRelation(rel, doc);
|
|
}
|
|
|
|
return new RelationPart(rel, doc);
|
|
} catch (PartAlreadyExistsException pae) {
|
|
// Return the specific exception so the user knows
|
|
// that the name is already taken
|
|
throw pae;
|
|
} catch (Exception e) {
|
|
// Give a general wrapped exception for the problem
|
|
throw new POIXMLException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Iterate through the underlying PackagePart and create child POIXMLFactory instances
|
|
* using the specified factory
|
|
*
|
|
* @param factory the factory object that creates POIXMLFactory instances
|
|
* @param context context map containing already visited noted keyed by targetURI
|
|
* @throws OpenXML4JException thrown when a related part can't be read
|
|
*/
|
|
protected void read(POIXMLFactory factory, Map<PackagePart, POIXMLDocumentPart> context) throws OpenXML4JException {
|
|
PackagePart pp = getPackagePart();
|
|
|
|
if (pp.getContentType().equals(XWPFRelation.GLOSSARY_DOCUMENT.getContentType())) {
|
|
logger.log(POILogger.WARN,
|
|
"POI does not currently support template.main+xml (glossary) parts. " +
|
|
"Skipping this part for now.");
|
|
return;
|
|
}
|
|
|
|
// add mapping a second time, in case of initial caller hasn't done so
|
|
POIXMLDocumentPart otherChild = context.put(pp, this);
|
|
if (otherChild != null && otherChild != this) {
|
|
throw new POIXMLException("Unique PackagePart-POIXMLDocumentPart relation broken!");
|
|
}
|
|
|
|
if (!pp.hasRelationships()) return;
|
|
|
|
PackageRelationshipCollection rels = packagePart.getRelationships();
|
|
List<POIXMLDocumentPart> readLater = new ArrayList<>();
|
|
|
|
// 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);
|
|
}
|
|
|
|
final PackagePart p = packagePart.getPackage().getPart(relName);
|
|
if (p == null) {
|
|
logger.log(POILogger.ERROR, "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);
|
|
}
|
|
childPart.parent = this;
|
|
// already add child to context, so other children can reference it
|
|
context.put(p, childPart);
|
|
readLater.add(childPart);
|
|
}
|
|
|
|
addRelation(rel, childPart);
|
|
}
|
|
}
|
|
|
|
for (POIXMLDocumentPart childPart : readLater) {
|
|
childPart.read(factory, context);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the PackagePart that is the target of a relationship from this Part.
|
|
*
|
|
* @param rel The relationship
|
|
* @return The target part
|
|
* @throws InvalidFormatException thrown if the related part has is erroneous
|
|
*/
|
|
protected PackagePart getTargetPart(PackageRelationship rel) throws InvalidFormatException {
|
|
return getPackagePart().getRelatedPart(rel);
|
|
}
|
|
|
|
|
|
/**
|
|
* Fired when a new package part is created
|
|
*
|
|
* @throws IOException a subclass may throw an IOException on document creation
|
|
*/
|
|
protected void onDocumentCreate() throws IOException {
|
|
|
|
}
|
|
|
|
/**
|
|
* Fired when a package part is read
|
|
*
|
|
* @throws IOException a subclass may throw an IOException when a document is read
|
|
*/
|
|
protected void onDocumentRead() throws IOException {
|
|
|
|
}
|
|
|
|
/**
|
|
* Fired when a package part is about to be removed from the package
|
|
*
|
|
* @throws IOException a subclass may throw an IOException when a document is removed
|
|
*/
|
|
protected void onDocumentRemove() throws IOException {
|
|
|
|
}
|
|
|
|
/**
|
|
* Internal method, do not use!
|
|
* <p>
|
|
* This method only exists to allow access to protected {@link POIXMLDocumentPart#onDocumentRead()}
|
|
* from {@link org.apache.poi.xwpf.usermodel.XWPFDocument} without reflection. It should be removed.
|
|
*
|
|
* @param part the part which is to be read
|
|
* @throws IOException if the part can't be read
|
|
*/
|
|
@Internal
|
|
@Deprecated
|
|
public static void _invokeOnDocumentRead(POIXMLDocumentPart part) throws IOException {
|
|
part.onDocumentRead();
|
|
}
|
|
|
|
/**
|
|
* Retrieves the core document part
|
|
*
|
|
* @since POI 3.14-Beta1
|
|
*/
|
|
private static PackagePart getPartFromOPCPackage(OPCPackage pkg, String coreDocumentRel) {
|
|
PackageRelationship coreRel = pkg.getRelationshipsByType(coreDocumentRel).getRelationship(0);
|
|
|
|
if (coreRel != null) {
|
|
PackagePart pp = pkg.getPart(coreRel);
|
|
if (pp == null) {
|
|
throw new POIXMLException("OOXML file structure broken/invalid - core document '" + coreRel.getTargetURI() + "' not found.");
|
|
}
|
|
return pp;
|
|
}
|
|
|
|
coreRel = pkg.getRelationshipsByType(PackageRelationshipTypes.STRICT_CORE_DOCUMENT).getRelationship(0);
|
|
if (coreRel != null) {
|
|
throw new POIXMLException("Strict OOXML isn't currently supported, please see bug #57699");
|
|
}
|
|
|
|
throw new POIXMLException("OOXML file structure broken/invalid - no core document found!");
|
|
}
|
|
}
|