1698 lines
52 KiB
Java
1698 lines
52 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.openxml4j.opc;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.Closeable;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
|
import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
|
|
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
|
|
import org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException;
|
|
import org.apache.poi.openxml4j.exceptions.PartAlreadyExistsException;
|
|
import org.apache.poi.openxml4j.opc.internal.ContentType;
|
|
import org.apache.poi.openxml4j.opc.internal.ContentTypeManager;
|
|
import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart;
|
|
import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
|
|
import org.apache.poi.openxml4j.opc.internal.PartUnmarshaller;
|
|
import org.apache.poi.openxml4j.opc.internal.ZipContentTypeManager;
|
|
import org.apache.poi.openxml4j.opc.internal.marshallers.DefaultMarshaller;
|
|
import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPackagePropertiesMarshaller;
|
|
import org.apache.poi.openxml4j.opc.internal.unmarshallers.PackagePropertiesUnmarshaller;
|
|
import org.apache.poi.openxml4j.opc.internal.unmarshallers.UnmarshallContext;
|
|
import org.apache.poi.openxml4j.util.Nullable;
|
|
import org.apache.poi.openxml4j.util.ZipEntrySource;
|
|
import org.apache.poi.util.NotImplemented;
|
|
import org.apache.poi.util.POILogFactory;
|
|
import org.apache.poi.util.POILogger;
|
|
|
|
/**
|
|
* Represents a container that can store multiple data objects.
|
|
*/
|
|
public abstract class OPCPackage implements RelationshipSource, Closeable {
|
|
|
|
/**
|
|
* Logger.
|
|
*/
|
|
private static final POILogger logger = POILogFactory.getLogger(OPCPackage.class);
|
|
|
|
/**
|
|
* Default package access.
|
|
*/
|
|
protected static final PackageAccess defaultPackageAccess = PackageAccess.READ_WRITE;
|
|
|
|
/**
|
|
* Package access.
|
|
*/
|
|
private PackageAccess packageAccess;
|
|
|
|
/**
|
|
* Package parts collection.
|
|
*/
|
|
protected PackagePartCollection partList;
|
|
|
|
/**
|
|
* Package relationships.
|
|
*/
|
|
protected PackageRelationshipCollection relationships;
|
|
|
|
/**
|
|
* Part marshallers by content type.
|
|
*/
|
|
protected Map<ContentType, PartMarshaller> partMarshallers;
|
|
|
|
/**
|
|
* Default part marshaller.
|
|
*/
|
|
protected PartMarshaller defaultPartMarshaller;
|
|
|
|
/**
|
|
* Part unmarshallers by content type.
|
|
*/
|
|
protected Map<ContentType, PartUnmarshaller> partUnmarshallers;
|
|
|
|
/**
|
|
* Core package properties.
|
|
*/
|
|
protected PackagePropertiesPart packageProperties;
|
|
|
|
/**
|
|
* Manage parts content types of this package.
|
|
*/
|
|
protected ContentTypeManager contentTypeManager;
|
|
|
|
/**
|
|
* Flag if a modification is done to the document.
|
|
*/
|
|
protected boolean isDirty = false;
|
|
|
|
/**
|
|
* File path of this package.
|
|
*/
|
|
protected String originalPackagePath;
|
|
|
|
/**
|
|
* Output stream for writing this package.
|
|
*/
|
|
protected OutputStream output;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param access
|
|
* Package access.
|
|
*/
|
|
OPCPackage(PackageAccess access) {
|
|
if (getClass() != ZipPackage.class) {
|
|
throw new IllegalArgumentException("PackageBase may not be subclassed");
|
|
}
|
|
init();
|
|
this.packageAccess = access;
|
|
}
|
|
|
|
/**
|
|
* Initialize the package instance.
|
|
*/
|
|
private void init() {
|
|
this.partMarshallers = new HashMap<ContentType, PartMarshaller>(5);
|
|
this.partUnmarshallers = new HashMap<ContentType, PartUnmarshaller>(2);
|
|
|
|
try {
|
|
// Add 'default' unmarshaller
|
|
this.partUnmarshallers.put(new ContentType(
|
|
ContentTypes.CORE_PROPERTIES_PART),
|
|
new PackagePropertiesUnmarshaller());
|
|
|
|
// Add default marshaller
|
|
this.defaultPartMarshaller = new DefaultMarshaller();
|
|
// TODO Delocalize specialized marshallers
|
|
this.partMarshallers.put(new ContentType(
|
|
ContentTypes.CORE_PROPERTIES_PART),
|
|
new ZipPackagePropertiesMarshaller());
|
|
} catch (InvalidFormatException e) {
|
|
// Should never happen
|
|
throw new OpenXML4JRuntimeException(
|
|
"Package.init() : this exception should never happen, " +
|
|
"if you read this message please send a mail to the developers team. : " +
|
|
e.getMessage(),
|
|
e
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Open a package with read/write permission.
|
|
*
|
|
* @param path
|
|
* The document path.
|
|
* @return A Package object, else <b>null</b>.
|
|
* @throws InvalidFormatException
|
|
* If the specified file doesn't exist, and a parsing error
|
|
* occur.
|
|
*/
|
|
public static OPCPackage open(String path) throws InvalidFormatException {
|
|
return open(path, defaultPackageAccess);
|
|
}
|
|
|
|
/**
|
|
* Open a package with read/write permission.
|
|
*
|
|
* @param file
|
|
* The file to open.
|
|
* @return A Package object, else <b>null</b>.
|
|
* @throws InvalidFormatException
|
|
* If the specified file doesn't exist, and a parsing error
|
|
* occur.
|
|
*/
|
|
public static OPCPackage open(File file) throws InvalidFormatException {
|
|
return open(file, defaultPackageAccess);
|
|
}
|
|
|
|
/**
|
|
* Open an user provided {@link ZipEntrySource} with read-only permission.
|
|
* This method can be used to stream data into POI.
|
|
* Opposed to other open variants, the data is read as-is, e.g. there aren't
|
|
* any zip-bomb protection put in place.
|
|
*
|
|
* @param zipEntry the custom source
|
|
* @return A Package object
|
|
* @throws InvalidFormatException if a parsing error occur.
|
|
*/
|
|
public static OPCPackage open(ZipEntrySource zipEntry)
|
|
throws InvalidFormatException {
|
|
OPCPackage pack = new ZipPackage(zipEntry, PackageAccess.READ);
|
|
try {
|
|
if (pack.partList == null) {
|
|
pack.getParts();
|
|
}
|
|
// pack.originalPackagePath = file.getAbsolutePath();
|
|
return pack;
|
|
} catch (InvalidFormatException e) {
|
|
try {
|
|
pack.close();
|
|
} catch (IOException e1) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
throw e;
|
|
} catch (RuntimeException e) {
|
|
try {
|
|
pack.close();
|
|
} catch (IOException e1) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open a package.
|
|
*
|
|
* @param path
|
|
* The document path.
|
|
* @param access
|
|
* PackageBase access.
|
|
* @return A PackageBase object, else <b>null</b>.
|
|
* @throws InvalidFormatException
|
|
* If the specified file doesn't exist, and a parsing error
|
|
* occur.
|
|
* @throws InvalidOperationException
|
|
*/
|
|
public static OPCPackage open(String path, PackageAccess access)
|
|
throws InvalidFormatException, InvalidOperationException {
|
|
if (path == null || "".equals(path.trim())) {
|
|
throw new IllegalArgumentException("'path' must be given");
|
|
}
|
|
|
|
File file = new File(path);
|
|
if (file.exists() && file.isDirectory()) {
|
|
throw new IllegalArgumentException("path must not be a directory");
|
|
}
|
|
|
|
OPCPackage pack = new ZipPackage(path, access);
|
|
boolean success = false;
|
|
if (pack.partList == null && access != PackageAccess.WRITE) {
|
|
try {
|
|
pack.getParts();
|
|
success = true;
|
|
} finally {
|
|
if (! success) {
|
|
try {
|
|
pack.close();
|
|
} catch (final IOException e) {
|
|
throw new InvalidOperationException("Could not close OPCPackage while cleaning up", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pack.originalPackagePath = new File(path).getAbsolutePath();
|
|
return pack;
|
|
}
|
|
|
|
/**
|
|
* Open a package.
|
|
*
|
|
* @param file
|
|
* The file to open.
|
|
* @param access
|
|
* PackageBase access.
|
|
* @return A PackageBase object, else <b>null</b>.
|
|
* @throws InvalidFormatException
|
|
* If the specified file doesn't exist, and a parsing error
|
|
* occur.
|
|
*/
|
|
public static OPCPackage open(File file, PackageAccess access)
|
|
throws InvalidFormatException {
|
|
if (file == null) {
|
|
throw new IllegalArgumentException("'file' must be given");
|
|
}
|
|
if (file.exists() && file.isDirectory()) {
|
|
throw new IllegalArgumentException("file must not be a directory");
|
|
}
|
|
|
|
OPCPackage pack = new ZipPackage(file, access);
|
|
try {
|
|
if (pack.partList == null && access != PackageAccess.WRITE) {
|
|
pack.getParts();
|
|
}
|
|
pack.originalPackagePath = file.getAbsolutePath();
|
|
return pack;
|
|
} catch (InvalidFormatException e) {
|
|
try {
|
|
pack.close();
|
|
} catch (IOException e1) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
throw e;
|
|
} catch (RuntimeException e) {
|
|
try {
|
|
pack.close();
|
|
} catch (IOException e1) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open a package.
|
|
*
|
|
* Note - uses quite a bit more memory than {@link #open(String)}, which
|
|
* doesn't need to hold the whole zip file in memory, and can take advantage
|
|
* of native methods
|
|
*
|
|
* @param in
|
|
* The InputStream to read the package from
|
|
* @return A PackageBase object
|
|
*/
|
|
public static OPCPackage open(InputStream in) throws InvalidFormatException,
|
|
IOException {
|
|
OPCPackage pack = new ZipPackage(in, PackageAccess.READ_WRITE);
|
|
if (pack.partList == null) {
|
|
pack.getParts();
|
|
}
|
|
return pack;
|
|
}
|
|
|
|
/**
|
|
* Opens a package if it exists, else it creates one.
|
|
*
|
|
* @param file
|
|
* The file to open or to create.
|
|
* @return A newly created package if the specified file does not exist,
|
|
* else the package extract from the file.
|
|
* @throws InvalidFormatException
|
|
* Throws if the specified file exist and is not valid.
|
|
*/
|
|
public static OPCPackage openOrCreate(File file) throws InvalidFormatException {
|
|
OPCPackage retPackage = null;
|
|
if (file.exists()) {
|
|
retPackage = open(file.getAbsolutePath());
|
|
} else {
|
|
retPackage = create(file);
|
|
}
|
|
return retPackage;
|
|
}
|
|
|
|
/**
|
|
* Creates a new package.
|
|
*
|
|
* @param path
|
|
* Path of the document.
|
|
* @return A newly created PackageBase ready to use.
|
|
*/
|
|
public static OPCPackage create(String path) {
|
|
return create(new File(path));
|
|
}
|
|
|
|
/**
|
|
* Creates a new package.
|
|
*
|
|
* @param file
|
|
* Path of the document.
|
|
* @return A newly created PackageBase ready to use.
|
|
*/
|
|
public static OPCPackage create(File file) {
|
|
if (file == null || (file.exists() && file.isDirectory())) {
|
|
throw new IllegalArgumentException("file");
|
|
}
|
|
|
|
if (file.exists()) {
|
|
throw new InvalidOperationException(
|
|
"This package (or file) already exists : use the open() method or delete the file.");
|
|
}
|
|
|
|
// Creates a new package
|
|
OPCPackage pkg = new ZipPackage();
|
|
pkg.originalPackagePath = file.getAbsolutePath();
|
|
|
|
configurePackage(pkg);
|
|
return pkg;
|
|
}
|
|
|
|
public static OPCPackage create(OutputStream output) {
|
|
OPCPackage pkg = new ZipPackage();
|
|
pkg.originalPackagePath = null;
|
|
pkg.output = output;
|
|
|
|
configurePackage(pkg);
|
|
return pkg;
|
|
}
|
|
|
|
/**
|
|
* Configure the package.
|
|
*
|
|
* @param pkg
|
|
*/
|
|
private static void configurePackage(OPCPackage pkg) {
|
|
try {
|
|
// Content type manager
|
|
pkg.contentTypeManager = new ZipContentTypeManager(null, pkg);
|
|
|
|
// Add default content types for .xml and .rels
|
|
pkg.contentTypeManager.addContentType(
|
|
PackagingURIHelper.createPartName(
|
|
PackagingURIHelper.PACKAGE_RELATIONSHIPS_ROOT_URI),
|
|
ContentTypes.RELATIONSHIPS_PART);
|
|
pkg.contentTypeManager.addContentType(
|
|
PackagingURIHelper.createPartName("/default.xml"),
|
|
ContentTypes.PLAIN_OLD_XML);
|
|
|
|
// Initialise some PackageBase properties
|
|
pkg.packageProperties = new PackagePropertiesPart(pkg,
|
|
PackagingURIHelper.CORE_PROPERTIES_PART_NAME);
|
|
pkg.packageProperties.setCreatorProperty("Generated by Apache POI OpenXML4J");
|
|
pkg.packageProperties.setCreatedProperty(new Nullable<Date>(new Date()));
|
|
} catch (InvalidFormatException e) {
|
|
// Should never happen
|
|
throw new IllegalStateException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Flush the package : save all.
|
|
*
|
|
* @see #close()
|
|
*/
|
|
public void flush() {
|
|
throwExceptionIfReadOnly();
|
|
|
|
if (this.packageProperties != null) {
|
|
this.packageProperties.flush();
|
|
}
|
|
|
|
this.flushImpl();
|
|
}
|
|
|
|
/**
|
|
* Close the open, writable package and save its content.
|
|
*
|
|
* If your package is open read only, then you should call {@link #revert()}
|
|
* when finished with the package.
|
|
*
|
|
* @throws IOException
|
|
* If an IO exception occur during the saving process.
|
|
*/
|
|
@Override
|
|
public void close() throws IOException {
|
|
if (this.packageAccess == PackageAccess.READ) {
|
|
logger.log(POILogger.WARN,
|
|
"The close() method is intended to SAVE a package. This package is open in READ ONLY mode, use the revert() method instead !");
|
|
revert();
|
|
return;
|
|
}
|
|
if (this.contentTypeManager == null) {
|
|
logger.log(POILogger.WARN,
|
|
"Unable to call close() on a package that hasn't been fully opened yet");
|
|
revert();
|
|
return;
|
|
}
|
|
|
|
// Save the content
|
|
ReentrantReadWriteLock l = new ReentrantReadWriteLock();
|
|
try {
|
|
l.writeLock().lock();
|
|
if (this.originalPackagePath != null
|
|
&& !"".equals(this.originalPackagePath.trim())) {
|
|
File targetFile = new File(this.originalPackagePath);
|
|
if (!targetFile.exists()
|
|
|| !(this.originalPackagePath
|
|
.equalsIgnoreCase(targetFile.getAbsolutePath()))) {
|
|
// Case of a package created from scratch
|
|
save(targetFile);
|
|
} else {
|
|
closeImpl();
|
|
}
|
|
} else if (this.output != null) {
|
|
save(this.output);
|
|
output.close();
|
|
}
|
|
} finally {
|
|
l.writeLock().unlock();
|
|
}
|
|
|
|
// Clear
|
|
this.contentTypeManager.clearAll();
|
|
}
|
|
|
|
/**
|
|
* Close the package WITHOUT saving its content. Reinitialize this package
|
|
* and cancel all changes done to it.
|
|
*/
|
|
public void revert() {
|
|
revertImpl();
|
|
}
|
|
|
|
/**
|
|
* Add a thumbnail to the package. This method is provided to make easier
|
|
* the addition of a thumbnail in a package. You can do the same work by
|
|
* using the traditionnal relationship and part mechanism.
|
|
*
|
|
* @param path The full path to the image file.
|
|
*/
|
|
public void addThumbnail(String path) throws IOException {
|
|
// Check parameter
|
|
if (path == null || path.isEmpty()) {
|
|
throw new IllegalArgumentException("path");
|
|
}
|
|
String name = path.substring(path.lastIndexOf(File.separatorChar) + 1);
|
|
|
|
FileInputStream is = new FileInputStream(path);
|
|
try {
|
|
addThumbnail(name, is);
|
|
} finally {
|
|
is.close();
|
|
}
|
|
}
|
|
/**
|
|
* Add a thumbnail to the package. This method is provided to make easier
|
|
* the addition of a thumbnail in a package. You can do the same work by
|
|
* using the traditionnal relationship and part mechanism.
|
|
*
|
|
* @param filename The full path to the image file.
|
|
* @param data the image data
|
|
*/
|
|
public void addThumbnail(String filename, InputStream data) throws IOException {
|
|
// Check parameter
|
|
if (filename == null || filename.isEmpty()) {
|
|
throw new IllegalArgumentException("filename");
|
|
}
|
|
|
|
// Create the thumbnail part name
|
|
String contentType = ContentTypes
|
|
.getContentTypeFromFileExtension(filename);
|
|
PackagePartName thumbnailPartName;
|
|
try {
|
|
thumbnailPartName = PackagingURIHelper.createPartName("/docProps/"
|
|
+ filename);
|
|
} catch (InvalidFormatException e) {
|
|
String partName = "/docProps/thumbnail" +
|
|
filename.substring(filename.lastIndexOf(".") + 1);
|
|
try {
|
|
thumbnailPartName = PackagingURIHelper.createPartName(partName);
|
|
} catch (InvalidFormatException e2) {
|
|
throw new InvalidOperationException(
|
|
"Can't add a thumbnail file named '" + filename + "'", e2);
|
|
}
|
|
}
|
|
|
|
// Check if part already exist
|
|
if (this.getPart(thumbnailPartName) != null)
|
|
throw new InvalidOperationException(
|
|
"You already add a thumbnail named '" + filename + "'");
|
|
|
|
// Add the thumbnail part to this package.
|
|
PackagePart thumbnailPart = this.createPart(thumbnailPartName,
|
|
contentType, false);
|
|
|
|
// Add the relationship between the package and the thumbnail part
|
|
this.addRelationship(thumbnailPartName, TargetMode.INTERNAL,
|
|
PackageRelationshipTypes.THUMBNAIL);
|
|
|
|
// Copy file data to the newly created part
|
|
StreamHelper.copyStream(data, thumbnailPart.getOutputStream());
|
|
}
|
|
|
|
/**
|
|
* Throws an exception if the package access mode is in read only mode
|
|
* (PackageAccess.Read).
|
|
*
|
|
* @throws InvalidOperationException
|
|
* Throws if a writing operation is done on a read only package.
|
|
* @see org.apache.poi.openxml4j.opc.PackageAccess
|
|
*/
|
|
void throwExceptionIfReadOnly() throws InvalidOperationException {
|
|
if (packageAccess == PackageAccess.READ) {
|
|
throw new InvalidOperationException(
|
|
"Operation not allowed, document open in read only mode!");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Throws an exception if the package access mode is in write only mode
|
|
* (PackageAccess.Write). This method is call when other methods need write
|
|
* right.
|
|
*
|
|
* @throws InvalidOperationException
|
|
* Throws if a read operation is done on a write only package.
|
|
* @see org.apache.poi.openxml4j.opc.PackageAccess
|
|
*/
|
|
void throwExceptionIfWriteOnly() throws InvalidOperationException {
|
|
if (packageAccess == PackageAccess.WRITE) {
|
|
throw new InvalidOperationException(
|
|
"Operation not allowed, document open in write only mode!");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves or creates if none exists, core package property part.
|
|
*
|
|
* @return The PackageProperties part of this package.
|
|
*/
|
|
public PackageProperties getPackageProperties()
|
|
throws InvalidFormatException {
|
|
this.throwExceptionIfWriteOnly();
|
|
// If no properties part has been found then we create one
|
|
if (this.packageProperties == null) {
|
|
this.packageProperties = new PackagePropertiesPart(this,
|
|
PackagingURIHelper.CORE_PROPERTIES_PART_NAME);
|
|
}
|
|
return this.packageProperties;
|
|
}
|
|
|
|
/**
|
|
* Retrieve a part identified by its name.
|
|
*
|
|
* @param partName
|
|
* Part name of the part to retrieve.
|
|
* @return The part with the specified name, else <code>null</code>.
|
|
*/
|
|
public PackagePart getPart(PackagePartName partName) {
|
|
throwExceptionIfWriteOnly();
|
|
|
|
if (partName == null) {
|
|
throw new IllegalArgumentException("partName");
|
|
}
|
|
|
|
// If the partlist is null, then we parse the package.
|
|
if (partList == null) {
|
|
try {
|
|
getParts();
|
|
} catch (InvalidFormatException e) {
|
|
return null;
|
|
}
|
|
}
|
|
return getPartImpl(partName);
|
|
}
|
|
|
|
/**
|
|
* Retrieve parts by content type.
|
|
*
|
|
* @param contentType
|
|
* The content type criteria.
|
|
* @return All part associated to the specified content type.
|
|
*/
|
|
public ArrayList<PackagePart> getPartsByContentType(String contentType) {
|
|
ArrayList<PackagePart> retArr = new ArrayList<PackagePart>();
|
|
for (PackagePart part : partList.values()) {
|
|
if (part.getContentType().equals(contentType)) {
|
|
retArr.add(part);
|
|
}
|
|
}
|
|
Collections.sort(retArr);
|
|
return retArr;
|
|
}
|
|
|
|
/**
|
|
* Retrieve parts by relationship type.
|
|
*
|
|
* @param relationshipType
|
|
* Relationship type.
|
|
* @return All parts which are the target of a relationship with the
|
|
* specified type, if the method can't retrieve relationships from
|
|
* the package, then return <code>null</code>.
|
|
*/
|
|
public ArrayList<PackagePart> getPartsByRelationshipType(
|
|
String relationshipType) {
|
|
if (relationshipType == null) {
|
|
throw new IllegalArgumentException("relationshipType");
|
|
}
|
|
ArrayList<PackagePart> retArr = new ArrayList<PackagePart>();
|
|
for (PackageRelationship rel : getRelationshipsByType(relationshipType)) {
|
|
PackagePart part = getPart(rel);
|
|
if (part != null) {
|
|
retArr.add(part);
|
|
}
|
|
}
|
|
Collections.sort(retArr);
|
|
return retArr;
|
|
}
|
|
|
|
/**
|
|
* Retrieve parts by name
|
|
*
|
|
* @param namePattern
|
|
* The pattern for matching the names
|
|
* @return All parts associated to the specified content type, sorted
|
|
* in alphanumerically by the part-name
|
|
*/
|
|
public List<PackagePart> getPartsByName(final Pattern namePattern) {
|
|
if (namePattern == null) {
|
|
throw new IllegalArgumentException("name pattern must not be null");
|
|
}
|
|
Matcher matcher = namePattern.matcher("");
|
|
ArrayList<PackagePart> result = new ArrayList<PackagePart>();
|
|
for (PackagePart part : partList.values()) {
|
|
PackagePartName partName = part.getPartName();
|
|
if (matcher.reset(partName.getName()).matches()) {
|
|
result.add(part);
|
|
}
|
|
}
|
|
Collections.sort(result);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the target part from the specified relationship.
|
|
*
|
|
* @param partRel
|
|
* The part relationship uses to retrieve the part.
|
|
*/
|
|
public PackagePart getPart(PackageRelationship partRel) {
|
|
PackagePart retPart = null;
|
|
ensureRelationships();
|
|
for (PackageRelationship rel : relationships) {
|
|
if (rel.getRelationshipType().equals(partRel.getRelationshipType())) {
|
|
try {
|
|
retPart = getPart(PackagingURIHelper.createPartName(rel
|
|
.getTargetURI()));
|
|
} catch (InvalidFormatException e) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return retPart;
|
|
}
|
|
|
|
/**
|
|
* Load the parts of the archive if it has not been done yet. The
|
|
* relationships of each part are not loaded.
|
|
*
|
|
* Note - Rule M4.1 states that there may only ever be one Core
|
|
* Properties Part, but Office produced files will sometimes
|
|
* have multiple! As Office ignores all but the first, we relax
|
|
* Compliance with Rule M4.1, and ignore all others silently too.
|
|
*
|
|
* @return All this package's parts.
|
|
*/
|
|
public ArrayList<PackagePart> getParts() throws InvalidFormatException {
|
|
throwExceptionIfWriteOnly();
|
|
|
|
// If the part list is null, we parse the package to retrieve all parts.
|
|
if (partList == null) {
|
|
/* Variables use to validate OPC Compliance */
|
|
|
|
// Check rule M4.1 -> A format consumer shall consider more than
|
|
// one core properties relationship for a package to be an error
|
|
// (We just log it and move on, as real files break this!)
|
|
boolean hasCorePropertiesPart = false;
|
|
boolean needCorePropertiesPart = true;
|
|
|
|
PackagePart[] parts = this.getPartsImpl();
|
|
this.partList = new PackagePartCollection();
|
|
for (PackagePart part : parts) {
|
|
if (partList.containsKey(part._partName)) {
|
|
throw new InvalidFormatException(
|
|
"A part with the name '" +
|
|
part._partName +
|
|
"' already exist : Packages shall not contain equivalent " +
|
|
"part names and package implementers shall neither create " +
|
|
"nor recognize packages with equivalent part names. [M1.12]");
|
|
}
|
|
|
|
// Check OPC compliance rule M4.1
|
|
if (part.getContentType().equals(
|
|
ContentTypes.CORE_PROPERTIES_PART)) {
|
|
if (!hasCorePropertiesPart) {
|
|
hasCorePropertiesPart = true;
|
|
} else {
|
|
logger.log(POILogger.WARN, "OPC Compliance error [M4.1]: " +
|
|
"there is more than one core properties relationship in the package! " +
|
|
"POI will use only the first, but other software may reject this file.");
|
|
}
|
|
}
|
|
|
|
PartUnmarshaller partUnmarshaller = partUnmarshallers.get(part._contentType);
|
|
|
|
if (partUnmarshaller != null) {
|
|
UnmarshallContext context = new UnmarshallContext(this,
|
|
part._partName);
|
|
try {
|
|
PackagePart unmarshallPart = partUnmarshaller
|
|
.unmarshall(context, part.getInputStream());
|
|
partList.put(unmarshallPart._partName, unmarshallPart);
|
|
|
|
// Core properties case-- use first CoreProperties part we come across
|
|
// and ignore any subsequent ones
|
|
if (unmarshallPart instanceof PackagePropertiesPart &&
|
|
hasCorePropertiesPart &&
|
|
needCorePropertiesPart) {
|
|
this.packageProperties = (PackagePropertiesPart) unmarshallPart;
|
|
needCorePropertiesPart = false;
|
|
}
|
|
} catch (IOException ioe) {
|
|
logger.log(POILogger.WARN, "Unmarshall operation : IOException for "
|
|
+ part._partName);
|
|
continue;
|
|
} catch (InvalidOperationException invoe) {
|
|
throw new InvalidFormatException(invoe.getMessage(), invoe);
|
|
}
|
|
} else {
|
|
try {
|
|
partList.put(part._partName, part);
|
|
} catch (InvalidOperationException e) {
|
|
throw new InvalidFormatException(e.getMessage(), e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ArrayList<PackagePart> result = new ArrayList<PackagePart>(partList.values());
|
|
java.util.Collections.sort(result);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Create and add a part, with the specified name and content type, to the
|
|
* package.
|
|
*
|
|
* @param partName
|
|
* Part name.
|
|
* @param contentType
|
|
* Part content type.
|
|
* @return The newly created part.
|
|
* @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.
|
|
* @see #createPartImpl(PackagePartName, String, boolean)
|
|
*/
|
|
public PackagePart createPart(PackagePartName partName, String contentType) {
|
|
return this.createPart(partName, contentType, true);
|
|
}
|
|
|
|
/**
|
|
* Create and add a part, with the specified name and content type, to the
|
|
* package. For general purpose, prefer the overload version of this method
|
|
* without the 'loadRelationships' parameter.
|
|
*
|
|
* @param partName
|
|
* Part name.
|
|
* @param contentType
|
|
* Part content type.
|
|
* @param loadRelationships
|
|
* Specify if the existing relationship part, if any, logically
|
|
* associated to the newly created part will be loaded.
|
|
* @return The newly created part.
|
|
* @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.
|
|
* @see #createPartImpl(PackagePartName, String, boolean)
|
|
*/
|
|
PackagePart createPart(PackagePartName partName, String contentType,
|
|
boolean loadRelationships) {
|
|
throwExceptionIfReadOnly();
|
|
if (partName == null) {
|
|
throw new IllegalArgumentException("partName");
|
|
}
|
|
|
|
if (contentType == null || contentType.equals("")) {
|
|
throw new IllegalArgumentException("contentType");
|
|
}
|
|
|
|
// Check if the specified part name already exists
|
|
if (partList.containsKey(partName)
|
|
&& !partList.get(partName).isDeleted()) {
|
|
throw new PartAlreadyExistsException(
|
|
"A part with the name '" + partName.getName() + "'" +
|
|
" already exists : Packages shall not contain equivalent part names and package" +
|
|
" implementers shall neither create nor recognize packages with equivalent part names. [M1.12]");
|
|
}
|
|
|
|
/* Check OPC compliance */
|
|
|
|
// Rule [M4.1]: The format designer shall specify and the format producer
|
|
// shall create at most one core properties relationship for a package.
|
|
// A format consumer shall consider more than one core properties
|
|
// relationship for a package to be an error. If present, the
|
|
// relationship shall target the Core Properties part.
|
|
// Note - POI will read files with more than one Core Properties, which
|
|
// Office sometimes produces, but is strict on generation
|
|
if (contentType.equals(ContentTypes.CORE_PROPERTIES_PART)) {
|
|
if (this.packageProperties != null) {
|
|
throw new InvalidOperationException(
|
|
"OPC Compliance error [M4.1]: you try to add more than one core properties relationship in the package !");
|
|
}
|
|
}
|
|
|
|
/* End check OPC compliance */
|
|
|
|
PackagePart part = this.createPartImpl(partName, contentType,
|
|
loadRelationships);
|
|
this.contentTypeManager.addContentType(partName, contentType);
|
|
this.partList.put(partName, part);
|
|
this.isDirty = true;
|
|
return part;
|
|
}
|
|
|
|
/**
|
|
* Add a part to the package.
|
|
*
|
|
* @param partName
|
|
* Part name of the part to create.
|
|
* @param contentType
|
|
* type associated with the file
|
|
* @param content
|
|
* the contents to add. In order to have faster operation in
|
|
* document merge, the data are stored in memory not on a hard
|
|
* disk
|
|
*
|
|
* @return The new part.
|
|
* @see #createPart(PackagePartName, String)
|
|
*/
|
|
public PackagePart createPart(PackagePartName partName, String contentType,
|
|
ByteArrayOutputStream content) {
|
|
PackagePart addedPart = this.createPart(partName, contentType);
|
|
if (addedPart == null) {
|
|
return null;
|
|
}
|
|
// Extract the zip entry content to put it in the part content
|
|
if (content != null) {
|
|
try {
|
|
OutputStream partOutput = addedPart.getOutputStream();
|
|
if (partOutput == null) {
|
|
return null;
|
|
}
|
|
|
|
partOutput.write(content.toByteArray(), 0, content.size());
|
|
partOutput.close();
|
|
|
|
} catch (IOException ioe) {
|
|
return null;
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
return addedPart;
|
|
}
|
|
|
|
/**
|
|
* Add the specified part to the package. If a part already exists in the
|
|
* package with the same name as the one specified, then we replace the old
|
|
* part by the specified part.
|
|
*
|
|
* @param part
|
|
* The part to add (or replace).
|
|
* @return The part added to the package, the same as the one specified.
|
|
* @throws InvalidFormatException
|
|
* 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.
|
|
*/
|
|
protected PackagePart addPackagePart(PackagePart part) {
|
|
throwExceptionIfReadOnly();
|
|
if (part == null) {
|
|
throw new IllegalArgumentException("part");
|
|
}
|
|
|
|
if (partList.containsKey(part._partName)) {
|
|
if (!partList.get(part._partName).isDeleted()) {
|
|
throw new InvalidOperationException(
|
|
"A part with the name '"
|
|
+ part._partName.getName()
|
|
+ "' already exists : Packages shall not contain equivalent part names and package implementers shall neither create nor recognize packages with equivalent part names. [M1.12]");
|
|
}
|
|
// If the specified partis flagged as deleted, we make it
|
|
// available
|
|
part.setDeleted(false);
|
|
// and delete the old part to replace it thereafeter
|
|
this.partList.remove(part._partName);
|
|
}
|
|
this.partList.put(part._partName, part);
|
|
this.isDirty = true;
|
|
return part;
|
|
}
|
|
|
|
/**
|
|
* Remove the specified part in this package. If this part is relationship
|
|
* part, then delete all relationships in the source part.
|
|
*
|
|
* @param part
|
|
* The part to remove. If <code>null</code>, skip the action.
|
|
* @see #removePart(PackagePartName)
|
|
*/
|
|
public void removePart(PackagePart part) {
|
|
if (part != null) {
|
|
removePart(part.getPartName());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove a part in this package. If this part is relationship part, then
|
|
* delete all relationships in the source part.
|
|
*
|
|
* @param partName
|
|
* The part name of the part to remove.
|
|
*/
|
|
public void removePart(PackagePartName partName) {
|
|
throwExceptionIfReadOnly();
|
|
if (partName == null || !this.containPart(partName)) {
|
|
throw new IllegalArgumentException("partName");
|
|
}
|
|
|
|
// Delete the specified part from the package.
|
|
if (this.partList.containsKey(partName)) {
|
|
this.partList.get(partName).setDeleted(true);
|
|
this.removePartImpl(partName);
|
|
this.partList.remove(partName);
|
|
} else {
|
|
this.removePartImpl(partName);
|
|
}
|
|
|
|
// Delete content type
|
|
this.contentTypeManager.removeContentType(partName);
|
|
|
|
// If this part is a relationship part, then delete all relationships of
|
|
// the source part.
|
|
if (partName.isRelationshipPartURI()) {
|
|
URI sourceURI = PackagingURIHelper
|
|
.getSourcePartUriFromRelationshipPartUri(partName.getURI());
|
|
PackagePartName sourcePartName;
|
|
try {
|
|
sourcePartName = PackagingURIHelper.createPartName(sourceURI);
|
|
} catch (InvalidFormatException e) {
|
|
logger
|
|
.log(POILogger.ERROR, "Part name URI '"
|
|
+ sourceURI
|
|
+ "' is not valid ! This message is not intended to be displayed !");
|
|
return;
|
|
}
|
|
if (sourcePartName.getURI().equals(
|
|
PackagingURIHelper.PACKAGE_ROOT_URI)) {
|
|
clearRelationships();
|
|
} else if (containPart(sourcePartName)) {
|
|
PackagePart part = getPart(sourcePartName);
|
|
if (part != null) {
|
|
part.clearRelationships();
|
|
}
|
|
}
|
|
}
|
|
|
|
this.isDirty = true;
|
|
}
|
|
|
|
/**
|
|
* Remove a part from this package as well as its relationship part, if one
|
|
* exists, and all parts listed in the relationship part. Be aware that this
|
|
* do not delete relationships which target the specified part.
|
|
*
|
|
* @param partName
|
|
* The name of the part to delete.
|
|
* @throws InvalidFormatException
|
|
* Throws if the associated relationship part of the specified
|
|
* part is not valid.
|
|
*/
|
|
public void removePartRecursive(PackagePartName partName)
|
|
throws InvalidFormatException {
|
|
// Retrieves relationship part, if one exists
|
|
PackagePart relPart = this.partList.get(PackagingURIHelper
|
|
.getRelationshipPartName(partName));
|
|
// Retrieves PackagePart object from the package
|
|
PackagePart partToRemove = this.partList.get(partName);
|
|
|
|
if (relPart != null) {
|
|
PackageRelationshipCollection partRels = new PackageRelationshipCollection(
|
|
partToRemove);
|
|
for (PackageRelationship rel : partRels) {
|
|
PackagePartName partNameToRemove = PackagingURIHelper
|
|
.createPartName(PackagingURIHelper.resolvePartUri(rel
|
|
.getSourceURI(), rel.getTargetURI()));
|
|
removePart(partNameToRemove);
|
|
}
|
|
|
|
// Finally delete its relationship part if one exists
|
|
this.removePart(relPart._partName);
|
|
}
|
|
|
|
// Delete the specified part
|
|
this.removePart(partToRemove._partName);
|
|
}
|
|
|
|
/**
|
|
* Delete the part with the specified name and its associated relationships
|
|
* part if one exists. Prefer the use of this method to delete a part in the
|
|
* package, compare to the remove() methods that don't remove associated
|
|
* relationships part.
|
|
*
|
|
* @param partName
|
|
* Name of the part to delete
|
|
*/
|
|
public void deletePart(PackagePartName partName) {
|
|
if (partName == null) {
|
|
throw new IllegalArgumentException("partName");
|
|
}
|
|
|
|
// Remove the part
|
|
this.removePart(partName);
|
|
// Remove the relationships part
|
|
this.removePart(PackagingURIHelper.getRelationshipPartName(partName));
|
|
}
|
|
|
|
/**
|
|
* Delete the part with the specified name and all part listed in its
|
|
* associated relationships part if one exists. This process is recursively
|
|
* apply to all parts in the relationships part of the specified part.
|
|
* Prefer the use of this method to delete a part in the package, compare to
|
|
* the remove() methods that don't remove associated relationships part.
|
|
*
|
|
* @param partName
|
|
* Name of the part to delete
|
|
*/
|
|
public void deletePartRecursive(PackagePartName partName) {
|
|
if (partName == null || !this.containPart(partName)) {
|
|
throw new IllegalArgumentException("partName");
|
|
}
|
|
|
|
PackagePart partToDelete = this.getPart(partName);
|
|
// Remove the part
|
|
this.removePart(partName);
|
|
// Remove all relationship parts associated
|
|
try {
|
|
for (PackageRelationship relationship : partToDelete
|
|
.getRelationships()) {
|
|
PackagePartName targetPartName = PackagingURIHelper
|
|
.createPartName(PackagingURIHelper.resolvePartUri(
|
|
partName.getURI(), relationship.getTargetURI()));
|
|
this.deletePartRecursive(targetPartName);
|
|
}
|
|
} catch (InvalidFormatException e) {
|
|
logger.log(POILogger.WARN, "An exception occurs while deleting part '"
|
|
+ partName.getName()
|
|
+ "'. Some parts may remain in the package. - "
|
|
+ e.getMessage());
|
|
return;
|
|
}
|
|
// Remove the relationships part
|
|
PackagePartName relationshipPartName = PackagingURIHelper
|
|
.getRelationshipPartName(partName);
|
|
if (relationshipPartName != null && containPart(relationshipPartName)) {
|
|
this.removePart(relationshipPartName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a part already exists in this package from its name.
|
|
*
|
|
* @param partName
|
|
* Part name to check.
|
|
* @return <i>true</i> if the part is logically added to this package, else
|
|
* <i>false</i>.
|
|
*/
|
|
public boolean containPart(PackagePartName partName) {
|
|
return (this.getPart(partName) != null);
|
|
}
|
|
|
|
/**
|
|
* Add a relationship to the package (except relationships part).
|
|
*
|
|
* Check rule M4.1 : The format designer shall specify and the format
|
|
* producer shall create at most one core properties relationship for a
|
|
* package. A format consumer shall consider more than one core properties
|
|
* relationship for a package to be an error. If present, the relationship
|
|
* shall target the Core Properties part.
|
|
*
|
|
* Check rule M1.25: The Relationships part shall not have relationships to
|
|
* any other part. Package implementers shall enforce this requirement upon
|
|
* the attempt to create such a relationship and shall treat any such
|
|
* relationship as invalid.
|
|
*
|
|
* @param targetPartName
|
|
* Target part name.
|
|
* @param targetMode
|
|
* Target mode, either Internal or External.
|
|
* @param relationshipType
|
|
* Relationship type.
|
|
* @param relID
|
|
* ID of the relationship.
|
|
* @see PackageRelationshipTypes
|
|
*/
|
|
@Override
|
|
public PackageRelationship addRelationship(PackagePartName targetPartName,
|
|
TargetMode targetMode, String relationshipType, String relID) {
|
|
/* Check OPC compliance */
|
|
|
|
// Check rule M4.1 : The format designer shall specify and the format
|
|
// producer
|
|
// shall create at most one core properties relationship for a package.
|
|
// A format consumer shall consider more than one core properties
|
|
// relationship for a package to be an error. If present, the
|
|
// relationship shall target the Core Properties part.
|
|
if (relationshipType.equals(PackageRelationshipTypes.CORE_PROPERTIES)
|
|
&& this.packageProperties != null) {
|
|
throw new InvalidOperationException(
|
|
"OPC Compliance error [M4.1]: can't add another core properties part ! Use the built-in package method instead.");
|
|
}
|
|
|
|
/*
|
|
* Check rule M1.25: The Relationships part shall not have relationships
|
|
* to any other part. Package implementers shall enforce this
|
|
* requirement upon the attempt to create such a relationship and shall
|
|
* treat any such relationship as invalid.
|
|
*/
|
|
if (targetPartName.isRelationshipPartURI()) {
|
|
throw new InvalidOperationException(
|
|
"Rule M1.25: The Relationships part shall not have relationships to any other part.");
|
|
}
|
|
|
|
/* End OPC compliance */
|
|
|
|
ensureRelationships();
|
|
PackageRelationship retRel = relationships.addRelationship(
|
|
targetPartName.getURI(), targetMode, relationshipType, relID);
|
|
this.isDirty = true;
|
|
return retRel;
|
|
}
|
|
|
|
/**
|
|
* Add a package relationship.
|
|
*
|
|
* @param targetPartName
|
|
* Target part name.
|
|
* @param targetMode
|
|
* Target mode, either Internal or External.
|
|
* @param relationshipType
|
|
* Relationship type.
|
|
* @see PackageRelationshipTypes
|
|
*/
|
|
@Override
|
|
public PackageRelationship addRelationship(PackagePartName targetPartName,
|
|
TargetMode targetMode, String relationshipType) {
|
|
return this.addRelationship(targetPartName, targetMode,
|
|
relationshipType, null);
|
|
}
|
|
|
|
/**
|
|
* Adds an external relationship to a part (except relationships part).
|
|
*
|
|
* The targets of external relationships are not subject to the same
|
|
* validity checks that internal ones are, as the contents is potentially
|
|
* any file, URL or similar.
|
|
*
|
|
* @param target
|
|
* External target of the relationship
|
|
* @param relationshipType
|
|
* Type of relationship.
|
|
* @return The newly created and added relationship
|
|
* @see org.apache.poi.openxml4j.opc.RelationshipSource#addExternalRelationship(java.lang.String,
|
|
* java.lang.String)
|
|
*/
|
|
@Override
|
|
public PackageRelationship addExternalRelationship(String target,
|
|
String relationshipType) {
|
|
return addExternalRelationship(target, relationshipType, null);
|
|
}
|
|
|
|
/**
|
|
* Adds an external relationship to a part (except relationships part).
|
|
*
|
|
* The targets of external relationships are not subject to the same
|
|
* validity checks that internal ones are, as the contents is potentially
|
|
* any file, URL or similar.
|
|
*
|
|
* @param target
|
|
* External target of the relationship
|
|
* @param relationshipType
|
|
* Type of relationship.
|
|
* @param id
|
|
* Relationship unique id.
|
|
* @return The newly created and added relationship
|
|
* @see org.apache.poi.openxml4j.opc.RelationshipSource#addExternalRelationship(java.lang.String,
|
|
* java.lang.String)
|
|
*/
|
|
@Override
|
|
public PackageRelationship addExternalRelationship(String target,
|
|
String relationshipType, String id) {
|
|
if (target == null) {
|
|
throw new IllegalArgumentException("target");
|
|
}
|
|
if (relationshipType == null) {
|
|
throw new IllegalArgumentException("relationshipType");
|
|
}
|
|
|
|
URI targetURI;
|
|
try {
|
|
targetURI = new URI(target);
|
|
} catch (URISyntaxException e) {
|
|
throw new IllegalArgumentException("Invalid target - " + e);
|
|
}
|
|
|
|
ensureRelationships();
|
|
PackageRelationship retRel = relationships.addRelationship(targetURI,
|
|
TargetMode.EXTERNAL, relationshipType, id);
|
|
this.isDirty = true;
|
|
return retRel;
|
|
}
|
|
|
|
/**
|
|
* Delete a relationship from this package.
|
|
*
|
|
* @param id
|
|
* Id of the relationship to delete.
|
|
*/
|
|
@Override
|
|
public void removeRelationship(String id) {
|
|
if (relationships != null) {
|
|
relationships.removeRelationship(id);
|
|
this.isDirty = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves all package relationships.
|
|
*
|
|
* @return All package relationships of this package.
|
|
* @throws OpenXML4JException
|
|
* @see #getRelationshipsHelper(String)
|
|
*/
|
|
@Override
|
|
public PackageRelationshipCollection getRelationships() {
|
|
return getRelationshipsHelper(null);
|
|
}
|
|
|
|
/**
|
|
* Retrieves all relationships with the specified type.
|
|
*
|
|
* @param relationshipType
|
|
* The filter specifying the relationship type.
|
|
* @return All relationships with the specified relationship type.
|
|
*/
|
|
@Override
|
|
public PackageRelationshipCollection getRelationshipsByType(
|
|
String relationshipType) {
|
|
throwExceptionIfWriteOnly();
|
|
if (relationshipType == null) {
|
|
throw new IllegalArgumentException("relationshipType");
|
|
}
|
|
return getRelationshipsHelper(relationshipType);
|
|
}
|
|
|
|
/**
|
|
* Retrieves all relationships with specified id (normally just ine because
|
|
* a relationship id is supposed to be unique).
|
|
*
|
|
* @param id
|
|
* Id of the wanted relationship.
|
|
*/
|
|
private PackageRelationshipCollection getRelationshipsHelper(String id) {
|
|
throwExceptionIfWriteOnly();
|
|
ensureRelationships();
|
|
return this.relationships.getRelationships(id);
|
|
}
|
|
|
|
/**
|
|
* Clear package relationships.
|
|
*/
|
|
@Override
|
|
public void clearRelationships() {
|
|
if (relationships != null) {
|
|
relationships.clear();
|
|
this.isDirty = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure that the relationships collection is not null.
|
|
*/
|
|
public void ensureRelationships() {
|
|
if (this.relationships == null) {
|
|
try {
|
|
this.relationships = new PackageRelationshipCollection(this);
|
|
} catch (InvalidFormatException e) {
|
|
this.relationships = new PackageRelationshipCollection();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @see org.apache.poi.openxml4j.opc.RelationshipSource#getRelationship(java.lang.String)
|
|
*/
|
|
@Override
|
|
public PackageRelationship getRelationship(String id) {
|
|
return this.relationships.getRelationshipByID(id);
|
|
}
|
|
|
|
/**
|
|
* @see org.apache.poi.openxml4j.opc.RelationshipSource#hasRelationships()
|
|
*/
|
|
@Override
|
|
public boolean hasRelationships() {
|
|
return (relationships.size() > 0);
|
|
}
|
|
|
|
/**
|
|
* @see org.apache.poi.openxml4j.opc.RelationshipSource#isRelationshipExists(org.apache.poi.openxml4j.opc.PackageRelationship)
|
|
*/
|
|
@Override
|
|
public boolean isRelationshipExists(PackageRelationship rel) {
|
|
for (PackageRelationship r : this.getRelationships()) {
|
|
if (r == rel) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Add a marshaller.
|
|
*
|
|
* @param contentType
|
|
* The content type to bind to the specified marshaller.
|
|
* @param marshaller
|
|
* The marshaller to register with the specified content type.
|
|
*/
|
|
public void addMarshaller(String contentType, PartMarshaller marshaller) {
|
|
try {
|
|
partMarshallers.put(new ContentType(contentType), marshaller);
|
|
} catch (InvalidFormatException e) {
|
|
logger.log(POILogger.WARN, "The specified content type is not valid: '"
|
|
+ e.getMessage() + "'. The marshaller will not be added !");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add an unmarshaller.
|
|
*
|
|
* @param contentType
|
|
* The content type to bind to the specified unmarshaller.
|
|
* @param unmarshaller
|
|
* The unmarshaller to register with the specified content type.
|
|
*/
|
|
public void addUnmarshaller(String contentType,
|
|
PartUnmarshaller unmarshaller) {
|
|
try {
|
|
partUnmarshallers.put(new ContentType(contentType), unmarshaller);
|
|
} catch (InvalidFormatException e) {
|
|
logger.log(POILogger.WARN, "The specified content type is not valid: '"
|
|
+ e.getMessage()
|
|
+ "'. The unmarshaller will not be added !");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove a marshaller by its content type.
|
|
*
|
|
* @param contentType
|
|
* The content type associated with the marshaller to remove.
|
|
*/
|
|
public void removeMarshaller(String contentType) {
|
|
try {
|
|
partMarshallers.remove(new ContentType(contentType));
|
|
} catch (InvalidFormatException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove an unmarshaller by its content type.
|
|
*
|
|
* @param contentType
|
|
* The content type associated with the unmarshaller to remove.
|
|
*/
|
|
public void removeUnmarshaller(String contentType) {
|
|
try {
|
|
partUnmarshallers.remove(new ContentType(contentType));
|
|
} catch (InvalidFormatException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
/* Accesseurs */
|
|
|
|
/**
|
|
* Get the package access mode.
|
|
*
|
|
* @return the packageAccess The current package access.
|
|
*/
|
|
public PackageAccess getPackageAccess() {
|
|
return packageAccess;
|
|
}
|
|
|
|
/**
|
|
* Validates the package compliance with the OPC specifications.
|
|
*
|
|
* @return <b>true</b> if the package is valid else <b>false</b>
|
|
*/
|
|
@NotImplemented
|
|
public boolean validatePackage(OPCPackage pkg) throws InvalidFormatException {
|
|
throw new InvalidOperationException("Not implemented yet !!!");
|
|
}
|
|
|
|
/**
|
|
* Save the document in the specified file.
|
|
*
|
|
* @param targetFile
|
|
* Destination file.
|
|
* @throws IOException
|
|
* Throws if an IO exception occur.
|
|
* @see #save(OutputStream)
|
|
*/
|
|
public void save(File targetFile) throws IOException {
|
|
if (targetFile == null) {
|
|
throw new IllegalArgumentException("targetFile");
|
|
}
|
|
|
|
this.throwExceptionIfReadOnly();
|
|
|
|
// You shouldn't save the the same file, do a close instead
|
|
if(targetFile.exists() &&
|
|
targetFile.getAbsolutePath().equals(this.originalPackagePath)) {
|
|
throw new InvalidOperationException(
|
|
"You can't call save(File) to save to the currently open " +
|
|
"file. To save to the current file, please just call close()"
|
|
);
|
|
}
|
|
|
|
// Do the save
|
|
FileOutputStream fos = null;
|
|
try {
|
|
fos = new FileOutputStream(targetFile);
|
|
this.save(fos);
|
|
} finally {
|
|
if (fos != null) fos.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save the document in the specified output stream.
|
|
*
|
|
* @param outputStream
|
|
* The stream to save the package.
|
|
* @see #saveImpl(OutputStream)
|
|
*/
|
|
public void save(OutputStream outputStream) throws IOException {
|
|
throwExceptionIfReadOnly();
|
|
this.saveImpl(outputStream);
|
|
}
|
|
|
|
/**
|
|
* Core method to create a package part. This method must be implemented by
|
|
* the subclass.
|
|
*
|
|
* @param partName
|
|
* URI of the part to create.
|
|
* @param contentType
|
|
* Content type of the part to create.
|
|
* @return The newly created package part.
|
|
*/
|
|
protected abstract PackagePart createPartImpl(PackagePartName partName,
|
|
String contentType, boolean loadRelationships);
|
|
|
|
/**
|
|
* Core method to delete a package part. This method must be implemented by
|
|
* the subclass.
|
|
*
|
|
* @param partName
|
|
* The URI of the part to delete.
|
|
*/
|
|
protected abstract void removePartImpl(PackagePartName partName);
|
|
|
|
/**
|
|
* Flush the package but not save.
|
|
*/
|
|
protected abstract void flushImpl();
|
|
|
|
/**
|
|
* Close the package and cause a save of the package.
|
|
*
|
|
*/
|
|
protected abstract void closeImpl() throws IOException;
|
|
|
|
/**
|
|
* Close the package without saving the document. Discard all changes made
|
|
* to this package.
|
|
*/
|
|
protected abstract void revertImpl();
|
|
|
|
/**
|
|
* Save the package into the specified output stream.
|
|
*
|
|
* @param outputStream
|
|
* The output stream use to save this package.
|
|
*/
|
|
protected abstract void saveImpl(OutputStream outputStream)
|
|
throws IOException;
|
|
|
|
/**
|
|
* Get the package part mapped to the specified URI.
|
|
*
|
|
* @param partName
|
|
* The URI of the part to retrieve.
|
|
* @return The package part located by the specified URI, else <b>null</b>.
|
|
*/
|
|
protected abstract PackagePart getPartImpl(PackagePartName partName);
|
|
|
|
/**
|
|
* Get all parts link to the package.
|
|
*
|
|
* @return A list of the part owned by the package.
|
|
*/
|
|
protected abstract PackagePart[] getPartsImpl()
|
|
throws InvalidFormatException;
|
|
|
|
/**
|
|
* Replace a content type in this package.
|
|
*
|
|
* <p>
|
|
* A typical scneario to call this method is to rename a template file to the main format, e.g.
|
|
* ".dotx" to ".docx"
|
|
* ".dotm" to ".docm"
|
|
* ".xltx" to ".xlsx"
|
|
* ".xltm" to ".xlsm"
|
|
* ".potx" to ".pptx"
|
|
* ".potm" to ".pptm"
|
|
* </p>
|
|
* For example, a code converting a .xlsm macro workbook to .xlsx would look as follows:
|
|
* <p>
|
|
* <pre><code>
|
|
*
|
|
* OPCPackage pkg = OPCPackage.open(new FileInputStream("macro-workbook.xlsm"));
|
|
* pkg.replaceContentType(
|
|
* "application/vnd.ms-excel.sheet.macroEnabled.main+xml",
|
|
* "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml");
|
|
*
|
|
* FileOutputStream out = new FileOutputStream("workbook.xlsx");
|
|
* pkg.save(out);
|
|
* out.close();
|
|
*
|
|
* </code></pre>
|
|
* </p>
|
|
*
|
|
* @param oldContentType the content type to be replaced
|
|
* @param newContentType the replacement
|
|
* @return whether replacement was succesfull
|
|
* @since POI-3.8
|
|
*/
|
|
public boolean replaceContentType(String oldContentType, String newContentType){
|
|
boolean success = false;
|
|
ArrayList<PackagePart> list = getPartsByContentType(oldContentType);
|
|
for (PackagePart packagePart : list) {
|
|
if (packagePart.getContentType().equals(oldContentType)) {
|
|
PackagePartName partName = packagePart.getPartName();
|
|
contentTypeManager.addContentType(partName, newContentType);
|
|
success = true;
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* Add the specified part, and register its content type with the content
|
|
* type manager.
|
|
*
|
|
* @param part
|
|
* The part to add.
|
|
*/
|
|
public void registerPartAndContentType(PackagePart part) {
|
|
addPackagePart(part);
|
|
this.contentTypeManager.addContentType(part.getPartName(), part.getContentType());
|
|
this.isDirty = true;
|
|
}
|
|
|
|
/**
|
|
* Remove the specified part, and clear its content type from the content
|
|
* type manager.
|
|
*
|
|
* @param partName
|
|
* The part name of the part to remove.
|
|
*/
|
|
public void unregisterPartAndContentType(PackagePartName partName) {
|
|
removePart(partName);
|
|
this.contentTypeManager.removeContentType(partName);
|
|
this.isDirty = true;
|
|
}
|
|
|
|
}
|