2009-03-18 14:54:01 -04:00
|
|
|
/* ====================================================================
|
|
|
|
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;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
2009-07-12 03:18:42 -04:00
|
|
|
import java.net.URI;
|
2013-12-26 12:55:36 -05:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.LinkedHashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Set;
|
2009-03-18 14:54:01 -04:00
|
|
|
|
2010-06-29 07:07:27 -04:00
|
|
|
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
2009-03-18 14:54:01 -04:00
|
|
|
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
|
2011-09-22 06:37:54 -04:00
|
|
|
import org.apache.poi.openxml4j.exceptions.PartAlreadyExistsException;
|
2011-06-01 08:07:16 -04:00
|
|
|
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;
|
2015-11-11 02:41:54 -05:00
|
|
|
import org.apache.poi.util.Internal;
|
2011-06-01 08:07:16 -04:00
|
|
|
import org.apache.poi.util.POILogFactory;
|
|
|
|
import org.apache.poi.util.POILogger;
|
2016-10-14 06:44:03 -04:00
|
|
|
import org.apache.poi.xssf.usermodel.XSSFRelation;
|
2009-03-18 14:54:01 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 {
|
2010-10-28 09:02:43 -04:00
|
|
|
private static final POILogger logger = POILogFactory.getLogger(POIXMLDocumentPart.class);
|
2009-03-18 14:54:01 -04:00
|
|
|
|
2015-07-19 08:11:19 -04:00
|
|
|
private String coreDocumentRel = PackageRelationshipTypes.CORE_DOCUMENT;
|
2009-03-18 14:54:01 -04:00
|
|
|
private PackagePart packagePart;
|
|
|
|
private POIXMLDocumentPart parent;
|
2016-01-10 15:44:17 -05:00
|
|
|
private Map<String,RelationPart> relations = new LinkedHashMap<String,RelationPart>();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-06-12 15:54:35 -04:00
|
|
|
* @param <T> the cast of the caller to a document sub class
|
|
|
|
*
|
2016-01-10 15:44:17 -05:00
|
|
|
* @return the child document part
|
|
|
|
*/
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
public <T extends POIXMLDocumentPart> T getDocumentPart() {
|
|
|
|
return (T)documentPart;
|
|
|
|
}
|
|
|
|
}
|
2011-06-01 08:07:16 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Counter that provides the amount of incoming relations from other parts
|
|
|
|
* to this part.
|
|
|
|
*/
|
|
|
|
private int relationCounter = 0;
|
|
|
|
|
|
|
|
int incrementRelationCounter() {
|
|
|
|
relationCounter++;
|
|
|
|
return relationCounter;
|
|
|
|
}
|
|
|
|
|
|
|
|
int decrementRelationCounter() {
|
|
|
|
relationCounter--;
|
|
|
|
return relationCounter;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getRelationCounter() {
|
|
|
|
return relationCounter;
|
|
|
|
}
|
2009-03-18 14:54:01 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct POIXMLDocumentPart representing a "core document" package part.
|
2016-06-12 15:54:35 -04:00
|
|
|
*
|
|
|
|
* @param pkg the OPCPackage containing this document
|
2009-03-18 14:54:01 -04:00
|
|
|
*/
|
|
|
|
public POIXMLDocumentPart(OPCPackage pkg) {
|
2015-07-19 08:11:19 -04:00
|
|
|
this(pkg, PackageRelationshipTypes.CORE_DOCUMENT);
|
|
|
|
}
|
2016-01-10 15:44:17 -05:00
|
|
|
|
2015-07-19 08:11:19 -04:00
|
|
|
/**
|
|
|
|
* Construct POIXMLDocumentPart representing a custom "core document" package part.
|
2016-06-12 15:54:35 -04:00
|
|
|
*
|
|
|
|
* @param pkg the OPCPackage containing this document
|
|
|
|
* @param coreDocumentRel the relation type of this document
|
2015-07-19 08:11:19 -04:00
|
|
|
*/
|
|
|
|
public POIXMLDocumentPart(OPCPackage pkg, String coreDocumentRel) {
|
2016-01-10 15:44:17 -05:00
|
|
|
this(getPartFromOPCPackage(pkg, coreDocumentRel));
|
2015-07-19 08:11:19 -04:00
|
|
|
this.coreDocumentRel = coreDocumentRel;
|
2009-03-18 14:54:01 -04:00
|
|
|
}
|
2016-01-10 15:44:17 -05:00
|
|
|
|
2009-03-18 14:54:01 -04:00
|
|
|
/**
|
|
|
|
* Creates new POIXMLDocumentPart - called by client code to create new parts from scratch.
|
|
|
|
*
|
|
|
|
* @see #createRelationship(POIXMLRelation, POIXMLFactory, int, boolean)
|
|
|
|
*/
|
2016-01-10 15:44:17 -05:00
|
|
|
public POIXMLDocumentPart() {
|
2009-03-18 14:54:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an POIXMLDocumentPart representing the given package part and relationship.
|
2016-01-10 15:44:17 -05:00
|
|
|
* Called by {@link #read(POIXMLFactory, java.util.Map)} when reading in an existing file.
|
2009-03-18 14:54:01 -04:00
|
|
|
*
|
2016-01-10 15:44:17 -05:00
|
|
|
* @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;
|
|
|
|
}
|
|
|
|
|
2010-06-29 07:07:27 -04:00
|
|
|
/**
|
|
|
|
* When you open something like a theme, call this to
|
|
|
|
* re-base the XML Document onto the core child of the
|
2016-01-10 15:44:17 -05:00
|
|
|
* current core document
|
2016-06-12 15:54:35 -04:00
|
|
|
*
|
|
|
|
* @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
|
2010-06-29 07:07:27 -04:00
|
|
|
*/
|
|
|
|
protected final void rebase(OPCPackage pkg) throws InvalidFormatException {
|
2011-06-01 08:07:16 -04:00
|
|
|
PackageRelationshipCollection cores =
|
2015-07-19 08:11:19 -04:00
|
|
|
packagePart.getRelationshipsByType(coreDocumentRel);
|
2011-06-01 08:07:16 -04:00
|
|
|
if(cores.size() != 1) {
|
|
|
|
throw new IllegalStateException(
|
2015-07-19 08:11:19 -04:00
|
|
|
"Tried to rebase using " + coreDocumentRel +
|
2010-06-29 07:07:27 -04:00
|
|
|
" but found " + cores.size() + " parts of the right type"
|
2011-06-01 08:07:16 -04:00
|
|
|
);
|
|
|
|
}
|
2016-01-10 15:44:17 -05:00
|
|
|
packagePart = packagePart.getRelatedPart(cores.getRelationship(0));
|
2010-06-29 07:07:27 -04:00
|
|
|
}
|
2009-03-18 14:54:01 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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(){
|
2016-01-10 15:44:17 -05:00
|
|
|
List<POIXMLDocumentPart> l = new ArrayList<POIXMLDocumentPart>();
|
|
|
|
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<RelationPart>(relations.values());
|
|
|
|
return Collections.unmodifiableList(l);
|
2011-06-01 08:07:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2016-01-10 15:44:17 -05:00
|
|
|
*
|
2011-06-01 08:07:16 -04:00
|
|
|
* @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) {
|
2016-01-10 15:44:17 -05:00
|
|
|
RelationPart rp = relations.get(id);
|
|
|
|
return (rp == null) ? null : rp.getDocumentPart();
|
2011-06-01 08:07:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the {@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.
|
2016-01-10 15:44:17 -05:00
|
|
|
*
|
2011-06-01 08:07:16 -04:00
|
|
|
* @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) {
|
2016-01-10 15:44:17 -05:00
|
|
|
for (RelationPart rp : relations.values()) {
|
|
|
|
if (rp.getDocumentPart() == part) {
|
|
|
|
return rp.getRelationship().getId();
|
2011-06-01 08:07:16 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
2009-03-18 14:54:01 -04:00
|
|
|
}
|
|
|
|
|
2016-01-10 15:44:17 -05:00
|
|
|
/**
|
|
|
|
* 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
|
2016-06-12 15:54:35 -04:00
|
|
|
*
|
|
|
|
* @return the new RelationPart
|
2016-01-10 15:44:17 -05:00
|
|
|
*
|
|
|
|
* @since 3.14-Beta1
|
|
|
|
*/
|
|
|
|
public final RelationPart addRelation(String relId, POIXMLRelation relationshipType, POIXMLDocumentPart part) {
|
|
|
|
PackageRelationship pr = findExistingRelation(part);
|
|
|
|
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
|
2009-03-18 14:54:01 -04:00
|
|
|
*/
|
2016-01-10 15:44:17 -05:00
|
|
|
private void addRelation(PackageRelationship pr, POIXMLDocumentPart part) {
|
|
|
|
relations.put(pr.getId(), new RelationPart(pr,part));
|
2011-06-01 08:07:16 -04:00
|
|
|
part.incrementRelationCounter();
|
2016-01-10 15:44:17 -05:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the new part was already added before via PackagePart.addRelationship()
|
|
|
|
*
|
2016-02-18 06:16:11 -05:00
|
|
|
* @param part to find the relationship for
|
|
|
|
* @return The existing relationship, or null if there isn't yet one
|
2016-01-10 15:44:17 -05:00
|
|
|
*/
|
|
|
|
private PackageRelationship findExistingRelation(POIXMLDocumentPart part) {
|
|
|
|
String ppn = part.getPackagePart().getPartName().getName();
|
|
|
|
try {
|
|
|
|
for (PackageRelationship pr : packagePart.getRelationships()) {
|
2016-05-08 09:28:11 -04:00
|
|
|
if (pr.getTargetMode() == TargetMode.EXTERNAL) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-01-10 15:44:17 -05:00
|
|
|
PackagePart pp = packagePart.getRelatedPart(pr);
|
|
|
|
if (ppn.equals(pp.getPartName().getName())) {
|
|
|
|
return pr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (InvalidFormatException e) {
|
|
|
|
throw new POIXMLException("invalid package relationships", e);
|
|
|
|
}
|
|
|
|
return null;
|
2009-03-18 14:54:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-06-01 08:07:16 -04:00
|
|
|
* Remove the relation to the specified part in this package and remove the
|
|
|
|
* part, if it is no longer needed.
|
2016-06-12 15:54:35 -04:00
|
|
|
*
|
|
|
|
* @param part the part which relation is to be removed from this document
|
2009-03-18 14:54:01 -04:00
|
|
|
*/
|
2011-06-01 08:07:16 -04:00
|
|
|
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.
|
2016-01-10 15:44:17 -05:00
|
|
|
*
|
2011-06-01 08:07:16 -04:00
|
|
|
* @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.
|
2016-06-13 19:08:13 -04:00
|
|
|
* @return true, if the relation was removed
|
2011-06-01 08:07:16 -04:00
|
|
|
*/
|
|
|
|
protected final boolean removeRelation(POIXMLDocumentPart part, boolean removeUnusedParts){
|
|
|
|
String id = getRelationId(part);
|
|
|
|
if (id == null) {
|
|
|
|
// part is not related with this POIXMLDocumentPart
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
/* decrement usage counter */
|
|
|
|
part.decrementRelationCounter();
|
|
|
|
/* remove packagepart relationship */
|
|
|
|
getPackagePart().removeRelationship(id);
|
|
|
|
/* remove POIXMLDocument from relations */
|
|
|
|
relations.remove(id);
|
|
|
|
|
|
|
|
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;
|
2009-03-18 14:54:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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(){
|
2014-02-16 14:35:16 -05:00
|
|
|
return packagePart == null ? "" : packagePart.toString();
|
2009-03-18 14:54:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save the content in the underlying package part.
|
|
|
|
* Default implementation is empty meaning that the package part is left unmodified.
|
|
|
|
*
|
|
|
|
* Sub-classes should override and add logic to marshal the "model" into Ooxml4J.
|
|
|
|
*
|
|
|
|
* For example, the code saving a generic XML entry may look as follows:
|
2016-06-13 19:08:13 -04:00
|
|
|
* <pre>
|
2009-03-18 14:54:01 -04:00
|
|
|
* 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();
|
|
|
|
* }
|
2016-06-13 19:08:13 -04:00
|
|
|
* </pre>
|
|
|
|
*
|
|
|
|
* @throws IOException a subclass may throw an IOException if the changes can't be committed
|
2009-03-18 14:54:01 -04:00
|
|
|
*/
|
|
|
|
protected void commit() throws IOException {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save changes in the underlying OOXML package.
|
|
|
|
* Recursively fires {@link #commit()} for each package part
|
2009-08-14 13:25:22 -04:00
|
|
|
*
|
|
|
|
* @param alreadySaved context set containing already visited nodes
|
2016-06-13 19:08:13 -04:00
|
|
|
*
|
|
|
|
* @throws IOException a related part may throw an IOException if the changes can't be saved
|
2009-03-18 14:54:01 -04:00
|
|
|
*/
|
2009-08-14 13:25:22 -04:00
|
|
|
protected final void onSave(Set<PackagePart> alreadySaved) throws IOException{
|
2013-12-26 12:55:36 -05:00
|
|
|
// this usually clears out previous content in the part...
|
|
|
|
prepareForCommit();
|
|
|
|
|
2011-06-01 08:07:16 -04:00
|
|
|
commit();
|
|
|
|
alreadySaved.add(this.getPackagePart());
|
2016-01-10 15:44:17 -05:00
|
|
|
for(RelationPart rp : relations.values()){
|
|
|
|
POIXMLDocumentPart p = rp.getDocumentPart();
|
2009-08-14 13:25:22 -04:00
|
|
|
if (!alreadySaved.contains(p.getPackagePart())) {
|
2011-06-01 08:07:16 -04:00
|
|
|
p.onSave(alreadySaved);
|
|
|
|
}
|
|
|
|
}
|
2009-03-18 14:54:01 -04:00
|
|
|
}
|
|
|
|
|
2013-12-26 12:55:36 -05:00
|
|
|
/**
|
2016-01-10 15:44:17 -05:00
|
|
|
* Ensure that a memory based package part does not have lingering data from previous
|
|
|
|
* commit() calls.
|
|
|
|
*
|
|
|
|
* Note: This is overwritten for some objects, as *PictureData seem to store the actual content
|
2013-12-26 12:55:36 -05:00
|
|
|
* 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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-18 14:54:01 -04:00
|
|
|
/**
|
|
|
|
* 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
|
2015-03-01 14:56:56 -05:00
|
|
|
* @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.
|
2009-03-18 14:54:01 -04:00
|
|
|
*/
|
2009-08-18 10:08:09 -04:00
|
|
|
public final POIXMLDocumentPart createRelationship(POIXMLRelation descriptor, POIXMLFactory factory){
|
2016-01-10 15:44:17 -05:00
|
|
|
return createRelationship(descriptor, factory, -1, false).getDocumentPart();
|
2009-03-18 14:54:01 -04:00
|
|
|
}
|
|
|
|
|
2015-03-01 14:56:56 -05:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2009-08-18 10:08:09 -04:00
|
|
|
public final POIXMLDocumentPart createRelationship(POIXMLRelation descriptor, POIXMLFactory factory, int idx){
|
2016-01-10 15:44:17 -05:00
|
|
|
return createRelationship(descriptor, factory, idx, false).getDocumentPart();
|
2009-03-18 14:54:01 -04:00
|
|
|
}
|
2016-10-14 06:44:03 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 {
|
2016-10-19 02:44:09 -04:00
|
|
|
String name = descriptor.getDefaultFileName();
|
|
|
|
if (name.equals(descriptor.getFileName(9999))) {
|
2016-10-14 06:44:03 -04:00
|
|
|
// Non-index based, check if default is free
|
2016-10-19 02:44:09 -04:00
|
|
|
PackagePartName ppName = PackagingURIHelper.createPartName(name);
|
2016-10-14 06:44:03 -04:00
|
|
|
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+
|
2016-11-11 18:22:43 -05:00
|
|
|
int idx = (minIdx < 0) ? 1 : minIdx;
|
2016-10-19 03:47:07 -04:00
|
|
|
int maxIdx = minIdx + pkg.getParts().size();
|
|
|
|
while (idx <= maxIdx) {
|
2016-10-19 02:44:09 -04:00
|
|
|
name = descriptor.getFileName(idx);
|
|
|
|
PackagePartName ppName = PackagingURIHelper.createPartName(name);
|
|
|
|
if (!pkg.containPart(ppName)) {
|
2016-10-14 06:44:03 -04:00
|
|
|
return idx;
|
|
|
|
}
|
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
} catch (InvalidFormatException e) {
|
|
|
|
// Give a general wrapped exception for the problem
|
|
|
|
throw new POIXMLException(e);
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
2009-03-18 14:54:01 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2015-03-01 14:56:56 -05:00
|
|
|
* @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.
|
2009-03-18 14:54:01 -04:00
|
|
|
*/
|
2016-01-10 15:44:17 -05:00
|
|
|
protected final RelationPart createRelationship(POIXMLRelation descriptor, POIXMLFactory factory, int idx, boolean noRelation){
|
2009-03-18 14:54:01 -04:00
|
|
|
try {
|
|
|
|
PackagePartName ppName = PackagingURIHelper.createPartName(descriptor.getFileName(idx));
|
|
|
|
PackageRelationship rel = null;
|
2011-06-01 08:07:16 -04:00
|
|
|
PackagePart part = packagePart.getPackage().createPart(ppName, descriptor.getContentType());
|
2010-09-20 11:43:07 -04:00
|
|
|
if(!noRelation) {
|
2011-06-01 08:07:16 -04:00
|
|
|
/* only add to relations, if according relationship is being created. */
|
|
|
|
rel = packagePart.addRelationship(ppName, TargetMode.INTERNAL, descriptor.getRelation());
|
2010-09-20 11:43:07 -04:00
|
|
|
}
|
2009-03-18 14:54:01 -04:00
|
|
|
POIXMLDocumentPart doc = factory.newDocumentPart(descriptor);
|
|
|
|
doc.packagePart = part;
|
|
|
|
doc.parent = this;
|
2016-01-10 15:44:17 -05:00
|
|
|
if (!noRelation) {
|
2011-06-01 08:07:16 -04:00
|
|
|
/* only add to relations, if according relationship is being created. */
|
2016-01-10 15:44:17 -05:00
|
|
|
addRelation(rel,doc);
|
2011-06-01 08:07:16 -04:00
|
|
|
}
|
2016-01-10 15:44:17 -05:00
|
|
|
|
|
|
|
return new RelationPart(rel,doc);
|
2011-09-22 06:37:54 -04:00
|
|
|
} catch (PartAlreadyExistsException pae) {
|
|
|
|
// Return the specific exception so the user knows
|
|
|
|
// that the name is already taken
|
|
|
|
throw pae;
|
2009-03-18 14:54:01 -04:00
|
|
|
} catch (Exception e){
|
2011-09-22 06:37:54 -04:00
|
|
|
// Give a general wrapped exception for the problem
|
|
|
|
throw new POIXMLException(e);
|
2009-03-18 14:54:01 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterate through the underlying PackagePart and create child POIXMLFactory instances
|
|
|
|
* using the specified factory
|
|
|
|
*
|
|
|
|
* @param factory the factory object that creates POIXMLFactory instances
|
2009-08-14 13:25:22 -04:00
|
|
|
* @param context context map containing already visited noted keyed by targetURI
|
2016-06-13 19:08:13 -04:00
|
|
|
*
|
|
|
|
* @throws OpenXML4JException thrown when a related part can't be read
|
2009-03-18 14:54:01 -04:00
|
|
|
*/
|
2009-08-14 13:25:22 -04:00
|
|
|
protected void read(POIXMLFactory factory, Map<PackagePart, POIXMLDocumentPart> context) throws OpenXML4JException {
|
2016-01-10 15:44:17 -05:00
|
|
|
PackagePart pp = getPackagePart();
|
|
|
|
// 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;
|
|
|
|
|
2011-06-01 08:07:16 -04:00
|
|
|
PackageRelationshipCollection rels = packagePart.getRelationships();
|
2016-01-10 15:44:17 -05:00
|
|
|
List<POIXMLDocumentPart> readLater = new ArrayList<POIXMLDocumentPart>();
|
|
|
|
|
|
|
|
// scan breadth-first, so parent-relations are hopefully the shallowest element
|
2011-06-01 08:07:16 -04:00
|
|
|
for (PackageRelationship rel : rels) {
|
|
|
|
if(rel.getTargetMode() == TargetMode.INTERNAL){
|
|
|
|
URI uri = rel.getTargetURI();
|
|
|
|
|
2016-01-10 15:44:17 -05:00
|
|
|
// check for internal references (e.g. '#Sheet1!A1')
|
|
|
|
PackagePartName relName;
|
2011-06-01 08:07:16 -04:00
|
|
|
if(uri.getRawFragment() != null) {
|
2016-01-10 15:44:17 -05:00
|
|
|
relName = PackagingURIHelper.createPartName(uri.getPath());
|
2011-06-01 08:07:16 -04:00
|
|
|
} else {
|
2016-01-10 15:44:17 -05:00
|
|
|
relName = PackagingURIHelper.createPartName(uri);
|
2011-06-01 08:07:16 -04:00
|
|
|
}
|
|
|
|
|
2016-01-10 15:44:17 -05:00
|
|
|
final PackagePart p = packagePart.getPackage().getPart(relName);
|
|
|
|
if (p == null) {
|
|
|
|
logger.log(POILogger.ERROR, "Skipped invalid entry " + rel.getTargetURI());
|
|
|
|
continue;
|
2011-06-01 08:07:16 -04:00
|
|
|
}
|
2016-01-10 15:44:17 -05:00
|
|
|
|
|
|
|
POIXMLDocumentPart childPart = context.get(p);
|
|
|
|
if (childPart == null) {
|
|
|
|
childPart = factory.createDocumentPart(this, p);
|
|
|
|
childPart.parent = this;
|
|
|
|
// already add child to context, so other children can reference it
|
|
|
|
context.put(p, childPart);
|
|
|
|
readLater.add(childPart);
|
2011-06-01 08:07:16 -04:00
|
|
|
}
|
2016-01-10 15:44:17 -05:00
|
|
|
|
|
|
|
addRelation(rel,childPart);
|
2011-06-01 08:07:16 -04:00
|
|
|
}
|
|
|
|
}
|
2016-01-10 15:44:17 -05:00
|
|
|
|
|
|
|
for (POIXMLDocumentPart childPart : readLater) {
|
|
|
|
childPart.read(factory, context);
|
|
|
|
}
|
2009-03-18 14:54:01 -04:00
|
|
|
}
|
2016-01-10 15:44:17 -05:00
|
|
|
|
2011-09-19 17:40:39 -04:00
|
|
|
/**
|
|
|
|
* Get the PackagePart that is the target of a relationship from this Part.
|
|
|
|
*
|
|
|
|
* @param rel The relationship
|
|
|
|
* @return The target part
|
2016-06-13 19:08:13 -04:00
|
|
|
* @throws InvalidFormatException thrown if the related part has is erroneous
|
2011-09-19 17:40:39 -04:00
|
|
|
*/
|
|
|
|
protected PackagePart getTargetPart(PackageRelationship rel) throws InvalidFormatException {
|
|
|
|
return getPackagePart().getRelatedPart(rel);
|
|
|
|
}
|
|
|
|
|
2009-03-18 14:54:01 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Fired when a new package part is created
|
2016-06-13 19:08:13 -04:00
|
|
|
*
|
|
|
|
* @throws IOException a subclass may throw an IOException on document creation
|
2009-03-18 14:54:01 -04:00
|
|
|
*/
|
|
|
|
protected void onDocumentCreate() throws IOException {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fired when a package part is read
|
2016-06-13 19:08:13 -04:00
|
|
|
*
|
|
|
|
* @throws IOException a subclass may throw an IOException when a document is read
|
2009-03-18 14:54:01 -04:00
|
|
|
*/
|
2011-06-01 08:07:16 -04:00
|
|
|
protected void onDocumentRead() throws IOException {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fired when a package part is about to be removed from the package
|
2016-06-13 19:08:13 -04:00
|
|
|
*
|
|
|
|
* @throws IOException a subclass may throw an IOException when a document is removed
|
2011-06-01 08:07:16 -04:00
|
|
|
*/
|
|
|
|
protected void onDocumentRemove() throws IOException {
|
2009-03-18 14:54:01 -04:00
|
|
|
|
|
|
|
}
|
2015-11-11 02:41:54 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2016-06-13 19:08:13 -04:00
|
|
|
*
|
|
|
|
* @param part the part which is to be read
|
|
|
|
*
|
|
|
|
* @throws IOException if the part can't be read
|
2015-11-11 02:41:54 -05:00
|
|
|
*/
|
|
|
|
@Internal @Deprecated
|
|
|
|
public static void _invokeOnDocumentRead(POIXMLDocumentPart part) throws IOException {
|
|
|
|
part.onDocumentRead();
|
|
|
|
}
|
2016-01-10 15:44:17 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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!");
|
|
|
|
}
|
2009-03-18 14:54:01 -04:00
|
|
|
}
|