OpenXML4J is the POI Project's pure Java implementation of the Open Packaging Conventions (OPC) defined in + ECMA-376.
+Every OpenXML file comprises a collection of byte streams called parts, combined into a container called a package. + POI OpenXML4J provides a physical implementation of the OPC that uses the Zip file format.
+OpenXML4J was originally developed by http://openxml4j.org/ and contributed to POI in 2008. + Thanks to the support and guidance of Julien Chable
+- * Each POIXMLDocumentPart keeps a reference to the underlying a {@link org.openxml4j.opc.PackagePart}. + * Each POIXMLDocumentPart keeps a reference to the underlying a {@link org.apache.poi.openxml4j.opc.PackagePart}. *
* * @author Yegor Kozlov diff --git a/src/ooxml/java/org/apache/poi/POIXMLFactory.java b/src/ooxml/java/org/apache/poi/POIXMLFactory.java index 6341b2084..f10cf0ca0 100755 --- a/src/ooxml/java/org/apache/poi/POIXMLFactory.java +++ b/src/ooxml/java/org/apache/poi/POIXMLFactory.java @@ -16,8 +16,8 @@ ==================================================================== */ package org.apache.poi; -import org.openxml4j.opc.PackageRelationship; -import org.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.PackagePart; /** diff --git a/src/ooxml/java/org/apache/poi/POIXMLProperties.java b/src/ooxml/java/org/apache/poi/POIXMLProperties.java index 894f2f800..3d862be88 100644 --- a/src/ooxml/java/org/apache/poi/POIXMLProperties.java +++ b/src/ooxml/java/org/apache/poi/POIXMLProperties.java @@ -19,10 +19,10 @@ package org.apache.poi; import java.io.IOException; import org.apache.xmlbeans.XmlException; -import org.openxml4j.exceptions.OpenXML4JException; -import org.openxml4j.opc.Package; -import org.openxml4j.opc.PackageRelationshipCollection; -import org.openxml4j.opc.internal.PackagePropertiesPart; +import org.apache.poi.openxml4j.exceptions.OpenXML4JException; +import org.apache.poi.openxml4j.opc.Package; +import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; +import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart; /** * Wrapper around the two different kinds of OOXML properties diff --git a/src/ooxml/java/org/apache/poi/POIXMLPropertiesTextExtractor.java b/src/ooxml/java/org/apache/poi/POIXMLPropertiesTextExtractor.java index 320c6e143..97c9e2d69 100644 --- a/src/ooxml/java/org/apache/poi/POIXMLPropertiesTextExtractor.java +++ b/src/ooxml/java/org/apache/poi/POIXMLPropertiesTextExtractor.java @@ -19,8 +19,8 @@ package org.apache.poi; import java.io.IOException; import org.apache.xmlbeans.XmlException; -import org.openxml4j.exceptions.OpenXML4JException; -import org.openxml4j.opc.internal.PackagePropertiesPart; +import org.apache.poi.openxml4j.exceptions.OpenXML4JException; +import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart; import org.openxmlformats.schemas.officeDocument.x2006.customProperties.CTProperty; /** diff --git a/src/ooxml/java/org/apache/poi/POIXMLTextExtractor.java b/src/ooxml/java/org/apache/poi/POIXMLTextExtractor.java index c6a99436b..25310fff3 100644 --- a/src/ooxml/java/org/apache/poi/POIXMLTextExtractor.java +++ b/src/ooxml/java/org/apache/poi/POIXMLTextExtractor.java @@ -20,7 +20,7 @@ import java.io.IOException; import org.apache.poi.POIXMLProperties.*; import org.apache.xmlbeans.XmlException; -import org.openxml4j.exceptions.OpenXML4JException; +import org.apache.poi.openxml4j.exceptions.OpenXML4JException; public abstract class POIXMLTextExtractor extends POITextExtractor { /** The POIXMLDocument that's open */ diff --git a/src/ooxml/java/org/apache/poi/dev/OOXMLLister.java b/src/ooxml/java/org/apache/poi/dev/OOXMLLister.java index 7084b38f3..a55bc2632 100644 --- a/src/ooxml/java/org/apache/poi/dev/OOXMLLister.java +++ b/src/ooxml/java/org/apache/poi/dev/OOXMLLister.java @@ -22,11 +22,11 @@ import java.io.InputStream; import java.io.PrintStream; import java.util.ArrayList; -import org.openxml4j.opc.Package; -import org.openxml4j.opc.PackageAccess; -import org.openxml4j.opc.PackagePart; -import org.openxml4j.opc.PackageRelationship; -import org.openxml4j.opc.PackageRelationshipCollection; +import org.apache.poi.openxml4j.opc.Package; +import org.apache.poi.openxml4j.opc.PackageAccess; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; /** * Prints out the contents of a OOXML container. diff --git a/src/ooxml/java/org/apache/poi/extractor/ExtractorFactory.java b/src/ooxml/java/org/apache/poi/extractor/ExtractorFactory.java index 406a3145e..7640db845 100644 --- a/src/ooxml/java/org/apache/poi/extractor/ExtractorFactory.java +++ b/src/ooxml/java/org/apache/poi/extractor/ExtractorFactory.java @@ -41,15 +41,14 @@ import org.apache.poi.xslf.XSLFSlideShow; import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor; import org.apache.poi.xssf.extractor.XSSFExcelExtractor; import org.apache.poi.xssf.usermodel.XSSFRelation; -import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFRelation; import org.apache.poi.xwpf.extractor.XWPFWordExtractor; import org.apache.xmlbeans.XmlException; -import org.openxml4j.exceptions.InvalidFormatException; -import org.openxml4j.exceptions.OpenXML4JException; -import org.openxml4j.opc.Package; -import org.openxml4j.opc.PackagePart; -import org.openxml4j.opc.PackageRelationshipCollection; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.openxml4j.exceptions.OpenXML4JException; +import org.apache.poi.openxml4j.opc.Package; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; /** * Figures out the correct POITextExtractor for your supplied diff --git a/src/ooxml/java/org/apache/poi/openxml4j/exceptions/InvalidFormatException.java b/src/ooxml/java/org/apache/poi/openxml4j/exceptions/InvalidFormatException.java new file mode 100755 index 000000000..689f8f33d --- /dev/null +++ b/src/ooxml/java/org/apache/poi/openxml4j/exceptions/InvalidFormatException.java @@ -0,0 +1,27 @@ +/* ==================================================================== + 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.exceptions; + +@SuppressWarnings("serial") +public class InvalidFormatException extends OpenXML4JException{ + + public InvalidFormatException(String message){ + super(message); + } +} diff --git a/src/ooxml/java/org/apache/poi/openxml4j/exceptions/InvalidOperationException.java b/src/ooxml/java/org/apache/poi/openxml4j/exceptions/InvalidOperationException.java new file mode 100755 index 000000000..9e45ccf4b --- /dev/null +++ b/src/ooxml/java/org/apache/poi/openxml4j/exceptions/InvalidOperationException.java @@ -0,0 +1,32 @@ +/* ==================================================================== + 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.exceptions; + +/** + * Throw when an invalid operation is done. + * + * @author Julien Chable + * @version 1.0 + */ +@SuppressWarnings("serial") +public class InvalidOperationException extends OpenXML4JRuntimeException{ + + public InvalidOperationException(String message){ + super(message); + } +} diff --git a/src/ooxml/java/org/apache/poi/openxml4j/exceptions/OpenXML4JException.java b/src/ooxml/java/org/apache/poi/openxml4j/exceptions/OpenXML4JException.java new file mode 100755 index 000000000..d2fa0e144 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/openxml4j/exceptions/OpenXML4JException.java @@ -0,0 +1,34 @@ +/* ==================================================================== + 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.exceptions; + +/** + * Global exception throws when a critical error occurs. (this exception is not + * set as Runtime in order to force user to manage the exception in a + * try/catch). + * + * @author CDubettier, Julien Chable + * @version 1.0 + */ +@SuppressWarnings("serial") +public class OpenXML4JException extends Exception { + + public OpenXML4JException(String msg) { + super(msg); + } +} diff --git a/src/ooxml/java/org/apache/poi/openxml4j/exceptions/OpenXML4JRuntimeException.java b/src/ooxml/java/org/apache/poi/openxml4j/exceptions/OpenXML4JRuntimeException.java new file mode 100755 index 000000000..c68d85ecb --- /dev/null +++ b/src/ooxml/java/org/apache/poi/openxml4j/exceptions/OpenXML4JRuntimeException.java @@ -0,0 +1,34 @@ +/* ==================================================================== + 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.exceptions; + +/** + * Global exception throws when a critical error occurs (this exception is + * set as Runtime in order not to force the user to manage the exception in a + * try/catch). + * + * @author Julien Chable + * @version 1.0 + */ +@SuppressWarnings("serial") +public class OpenXML4JRuntimeException extends RuntimeException { + + public OpenXML4JRuntimeException(String msg) { + super(msg); + } +} diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/CertificateEmbeddingOption.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/CertificateEmbeddingOption.java new file mode 100755 index 000000000..8b946e6d0 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/CertificateEmbeddingOption.java @@ -0,0 +1,33 @@ +/* ==================================================================== + 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; + +/** + * Specifies the location where the X.509 certificate that is used in signing is stored. + * + * @author Julien Chable + * @version 1.0 + */ +public enum CertificateEmbeddingOption { + /** The certificate is embedded in its own PackagePart. */ + IN_CERTIFICATE_PART, + /** The certificate is embedded in the SignaturePart that is created for the signature being added. */ + IN_SIGNATURE_PART, + /** The certificate in not embedded in the package. */ + NOT_EMBEDDED +} diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/CompressionOption.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/CompressionOption.java new file mode 100755 index 000000000..b6c5b30f7 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/CompressionOption.java @@ -0,0 +1,47 @@ +/* ==================================================================== + 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.util.zip.Deflater; + +/** + * Specifies the compression level for content that is stored in a PackagePart. + * + * @author Julien Chable + * @version 1.0 + */ +public enum CompressionOption { + /** Compression is optimized for performance. */ + FAST(Deflater.BEST_SPEED), + /** Compression is optimized for size. */ + MAXIMUM(Deflater.BEST_COMPRESSION), + /** Compression is optimized for a balance between size and performance. */ + NORMAL(Deflater.DEFAULT_COMPRESSION), + /** Compression is turned off. */ + NOT_COMPRESSED(Deflater.NO_COMPRESSION); + + private final int value; + + CompressionOption(int value) { + this.value = value; + } + + public int value() { + return this.value; + } +} diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/Configuration.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/Configuration.java new file mode 100755 index 000000000..72241b38d --- /dev/null +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/Configuration.java @@ -0,0 +1,43 @@ +/* ==================================================================== + 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.File; + +/** + * Storage class for configuration storage parameters. + * TODO xml syntax checking is no longer done with DOM4j parser -> remove the schema or do it ? + * + * @author CDubettier, Julen Chable + * @version 1.0 + */ +public class Configuration { + // TODO configuration by default. should be clearly stated that it should be + // changed to match installation path + // as schemas dir is needed in runtime + static private String pathForXmlSchema = System.getProperty("user.dir") + + File.separator + "src" + File.separator + "schemas"; + + public static String getPathForXmlSchema() { + return pathForXmlSchema; + } + + public static void setPathForXmlSchema(String pathForXmlSchema) { + Configuration.pathForXmlSchema = pathForXmlSchema; + } +} diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/ContentTypes.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/ContentTypes.java new file mode 100755 index 000000000..b05d7903e --- /dev/null +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/ContentTypes.java @@ -0,0 +1,129 @@ +/* ==================================================================== + 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; + +/** + * Open Packaging Convention content types (see Annex F : Standard Namespaces + * and Content Types). + * + * @author CDubettier define some constants, Julien Chable + * @version 0.1 + */ +public class ContentTypes { + + /* + * Open Packaging Convention (Annex F : Standard Namespaces and Content + * Types) + */ + + /** + * Core Properties part. + */ + public static final String CORE_PROPERTIES_PART = "application/vnd.openxmlformats-package.core-properties+xml"; + + /** + * Digital Signature Certificate part. + */ + public static final String DIGITAL_SIGNATURE_CERTIFICATE_PART = "application/vnd.openxmlformats-package.digital-signature-certificate"; + + /** + * Digital Signature Origin part. + */ + public static final String DIGITAL_SIGNATURE_ORIGIN_PART = "application/vnd.openxmlformats-package.digital-signature-origin"; + + /** + * Digital Signature XML Signature part. + */ + public static final String DIGITAL_SIGNATURE_XML_SIGNATURE_PART = "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"; + + /** + * Relationships part. + */ + public static final String RELATIONSHIPS_PART = "application/vnd.openxmlformats-package.relationships+xml"; + + /** + * Custom XML part. + */ + public static final String CUSTOM_XML_PART = "application/vnd.openxmlformats-officedocument.customXmlProperties+xml"; + + /** + * Plain old xml. Note - OOXML uses application/xml, and not text/xml! + */ + public static final String PLAIN_OLD_XML = "application/xml"; + + public static final String IMAGE_JPEG = "image/jpeg"; + + public static final String EXTENSION_JPG_1 = "jpg"; + + public static final String EXTENSION_JPG_2 = "jpeg"; + + // image/png ISO/IEC 15948:2003 http://www.libpng.org/pub/png/spec/ + public static final String IMAGE_PNG = "image/png"; + + public static final String EXTENSION_PNG = "png"; + + // image/gif http://www.w3.org/Graphics/GIF/spec-gif89a.txt + public static final String IMAGE_GIF = "image/gif"; + + public static final String EXTENSION_GIF = "gif"; + + /** + * TIFF image format. + * + * @see http://partners.adobe.com/public/developer/tiff/index.html#spec + */ + public static final String IMAGE_TIFF = "image/tiff"; + + public static final String EXTENSION_TIFF = "tiff"; + + /** + * Pict image format. + * + * @see http://developer.apple.com/documentation/mac/QuickDraw/QuickDraw-2.html + */ + public static final String IMAGE_PICT = "image/pict"; + + public static final String EXTENSION_PICT = "tiff"; + + /** + * XML file. + */ + public static final String XML = "text/xml"; + + public static final String EXTENSION_XML = "xml"; + + public static String getContentTypeFromFileExtension(String filename) { + String extension = filename.substring(filename.lastIndexOf(".") + 1) + .toLowerCase(); + if (extension.equals(EXTENSION_JPG_1) + || extension.equals(EXTENSION_JPG_2)) + return IMAGE_JPEG; + else if (extension.equals(EXTENSION_GIF)) + return IMAGE_GIF; + else if (extension.equals(EXTENSION_PICT)) + return IMAGE_PICT; + else if (extension.equals(EXTENSION_PNG)) + return IMAGE_PNG; + else if (extension.equals(EXTENSION_TIFF)) + return IMAGE_TIFF; + else if (extension.equals(EXTENSION_XML)) + return XML; + else + return null; + } +} diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/EncryptionOption.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/EncryptionOption.java new file mode 100755 index 000000000..0e15332f3 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/EncryptionOption.java @@ -0,0 +1,29 @@ +/* ==================================================================== + 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; + +/** + * Specifies the encryption option for parts in a Package. + * + * @author Julien Chable + * @version 0.1 + */ +public enum EncryptionOption { + /** No encryption. */ + NONE +} diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/Package.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/Package.java new file mode 100755 index 000000000..663ab266f --- /dev/null +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/Package.java @@ -0,0 +1,1396 @@ +/* ==================================================================== + 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.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +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.Date; +import java.util.Hashtable; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.log4j.Logger; +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.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; + +/** + * Represents a container that can store multiple data objects. + * + * @author Julien Chable, CDubet + * @version 0.1 + */ +public abstract class Package implements RelationshipSource { + + /** + * Logger. + */ + protected static Logger logger = Logger.getLogger("org.openxml4j.opc"); + + /** + * 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 Hashtablenull
.
+ */
+ 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 ArrayListnull
.
+ */
+ public ArrayListnull
, 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
+ .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.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 true if the part is logically added to this package, else
+ * false.
+ */
+ 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
+ */
+ 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
+ */
+ 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)
+ */
+ 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)
+ */
+ 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.
+ */
+ 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 {@link #getRelationshipsHelper(String)}
+ */
+ public PackageRelationshipCollection getRelationships()
+ throws OpenXML4JException {
+ return getRelationshipsHelper(null);
+ }
+
+ /**
+ * Retrives all relationships with the specified type.
+ *
+ * @param relationshipType
+ * The filter specifying the relationship type.
+ * @return All relationships with the specified relationship type.
+ * @throws OpenXML4JException
+ */
+ public PackageRelationshipCollection getRelationshipsByType(
+ String relationshipType) throws IllegalArgumentException,
+ OpenXML4JException {
+ 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.
+ * @throws OpenXML4JException
+ */
+ private PackageRelationshipCollection getRelationshipsHelper(String id)
+ throws OpenXML4JException {
+ throwExceptionIfWriteOnly();
+ ensureRelationships();
+ return this.relationships.getRelationships(id);
+ }
+
+ /**
+ * Clear package relationships.
+ */
+ 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)
+ */
+ public PackageRelationship getRelationship(String id) {
+ return this.relationships.getRelationshipByID(id);
+ }
+
+ /**
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#hasRelationships()
+ */
+ public boolean hasRelationships() {
+ return (relationships.size() > 0);
+ }
+
+ /**
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#isRelationshipExists(org.apache.poi.openxml4j.opc.PackageRelationship)
+ */
+ @SuppressWarnings("finally")
+ public boolean isRelationshipExists(PackageRelationship rel) {
+ try {
+ for (PackageRelationship r : this.getRelationships()) {
+ if (r == rel)
+ return true;
+ }
+ } finally {
+ 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.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.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) {
+ partMarshallers.remove(contentType);
+ }
+
+ /**
+ * Remove an unmarshaller by its content type.
+ *
+ * @param contentType
+ * The content type associated with the unmarshaller to remove.
+ */
+ public void removeUnmarshaller(String contentType) {
+ partUnmarshallers.remove(contentType);
+ }
+
+ /* 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 true if the package is valid else false
+ */
+ public boolean validatePackage(Package 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();
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(targetFile);
+ } catch (FileNotFoundException e) {
+ throw new IOException(e.getLocalizedMessage());
+ }
+ this.save(fos);
+ }
+
+ /**
+ * 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 null.
+ */
+ 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;
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageAccess.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageAccess.java
new file mode 100755
index 000000000..a40f4ac43
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageAccess.java
@@ -0,0 +1,33 @@
+/* ====================================================================
+ 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;
+
+/**
+ * Specifies package access.
+ *
+ * @author Julien Chable
+ * @version 1.0
+ */
+public enum PackageAccess {
+ /** Read only. Write not authorized. */
+ READ,
+ /** Write only. Read not authorized. */
+ WRITE,
+ /** Read and Write mode. */
+ READ_WRITE
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageNamespaces.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageNamespaces.java
new file mode 100755
index 000000000..5f53781eb
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageNamespaces.java
@@ -0,0 +1,52 @@
+/* ====================================================================
+ 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;
+
+/**
+ * Open Packaging Convention namespaces URI.
+ *
+ * @author Julien Chable
+ * @version 1.0
+ */
+public interface PackageNamespaces {
+
+ /**
+ * Content Types.
+ */
+ public static final String CONTENT_TYPES = "http://schemas.openxmlformats.org/package/2006/content-types";
+
+ /**
+ * Core Properties.
+ */
+ public static final String CORE_PROPERTIES = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
+
+ /**
+ * Digital Signatures.
+ */
+ public static final String DIGITAL_SIGNATURE = "http://schemas.openxmlformats.org/package/2006/digital-signature";
+
+ /**
+ * Relationships.
+ */
+ public static final String RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006/relationships";
+
+ /**
+ * Markup Compatibility.
+ */
+ public static final String MARKUP_COMPATIBILITY = "http://schemas.openxmlformats.org/markup-compatibility/2006";
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackagePart.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackagePart.java
new file mode 100755
index 000000000..34f26821f
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackagePart.java
@@ -0,0 +1,654 @@
+/* ====================================================================
+ 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+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.opc.internal.ContentType;
+
+/**
+ * Provides a base class for parts stored in a Package.
+ *
+ * @author Julien Chable
+ * @version 0.9
+ */
+public abstract class PackagePart implements RelationshipSource {
+
+ /**
+ * This part's container.
+ */
+ protected Package container;
+
+ /**
+ * The part name. (required by the specification [M1.1])
+ */
+ protected PackagePartName partName;
+
+ /**
+ * The type of content of this part. (required by the specification [M1.2])
+ */
+ protected ContentType contentType;
+
+ /**
+ * Flag to know if this part is a relationship.
+ */
+ private boolean isRelationshipPart;
+
+ /**
+ * Flag to know if this part has been logically deleted.
+ */
+ private boolean isDeleted;
+
+ /**
+ * This part's relationships.
+ */
+ private PackageRelationshipCollection relationships;
+
+ /**
+ * Constructor.
+ *
+ * @param pack
+ * Parent package.
+ * @param partName
+ * The part name, relative to the parent Package root.
+ * @param contentType
+ * The content type.
+ * @throws InvalidFormatException
+ * If the specified URI is not valid.
+ */
+ protected PackagePart(Package pack, PackagePartName partName,
+ ContentType contentType) throws InvalidFormatException {
+ this(pack, partName, contentType, true);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param pack
+ * Parent package.
+ * @param partName
+ * The part name, relative to the parent Package root.
+ * @param contentType
+ * The content type.
+ * @param loadRelationships
+ * Specify if the relationships will be loaded
+ * @throws InvalidFormatException
+ * If the specified URI is not valid.
+ */
+ protected PackagePart(Package pack, PackagePartName partName,
+ ContentType contentType, boolean loadRelationships)
+ throws InvalidFormatException {
+ this.partName = partName;
+ this.contentType = contentType;
+ this.container = (ZipPackage) pack;
+
+ // Check if this part is a relationship part
+ isRelationshipPart = this.partName.isRelationshipPartURI();
+
+ // Load relationships if any
+ if (loadRelationships)
+ loadRelationships();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param pack
+ * Parent package.
+ * @param partName
+ * The part name, relative to the parent Package root.
+ * @param contentType
+ * The Multipurpose Internet Mail Extensions (MIME) content type
+ * of the part's data stream.
+ */
+ public PackagePart(Package pack, PackagePartName partName,
+ String contentType) throws InvalidFormatException {
+ this(pack, partName, new ContentType(contentType));
+ }
+
+ /**
+ * 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)
+ */
+ 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)
+ */
+ public PackageRelationship addExternalRelationship(String target,
+ String relationshipType, String id) {
+ if (target == null) {
+ throw new IllegalArgumentException("target");
+ }
+ if (relationshipType == null) {
+ throw new IllegalArgumentException("relationshipType");
+ }
+
+ if (relationships == null) {
+ relationships = new PackageRelationshipCollection();
+ }
+
+ URI targetURI;
+ try {
+ targetURI = new URI(target);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Invalid target - " + e);
+ }
+
+ return relationships.addRelationship(targetURI, TargetMode.EXTERNAL,
+ relationshipType, id);
+ }
+
+ /**
+ * Add a relationship to a part (except relationships part).
+ *
+ * @param targetPartName
+ * Name of the target part. This one must be relative to the
+ * source root directory of the part.
+ * @param targetMode
+ * Mode [Internal|External].
+ * @param relationshipType
+ * Type of relationship.
+ * @return The newly created and added relationship
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#addRelationship(org.apache.poi.openxml4j.opc.PackagePartName,
+ * org.apache.poi.openxml4j.opc.TargetMode, java.lang.String)
+ */
+ public PackageRelationship addRelationship(PackagePartName targetPartName,
+ TargetMode targetMode, String relationshipType) {
+ return addRelationship(targetPartName, targetMode, relationshipType,
+ null);
+ }
+
+ /**
+ * Add a relationship to a part (except relationships 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
+ * Name of the target part. This one must be relative to the
+ * source root directory of the part.
+ * @param targetMode
+ * Mode [Internal|External].
+ * @param relationshipType
+ * Type of relationship.
+ * @param id
+ * Relationship unique id.
+ * @return The newly created and added relationship
+ *
+ * @throws InvalidFormatException
+ * If the URI point to a relationship part URI.
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#addRelationship(org.apache.poi.openxml4j.opc.PackagePartName,
+ * org.apache.poi.openxml4j.opc.TargetMode, java.lang.String, java.lang.String)
+ */
+ public PackageRelationship addRelationship(PackagePartName targetPartName,
+ TargetMode targetMode, String relationshipType, String id) {
+ container.throwExceptionIfReadOnly();
+
+ if (targetPartName == null) {
+ throw new IllegalArgumentException("targetPartName");
+ }
+ if (targetMode == null) {
+ throw new IllegalArgumentException("targetMode");
+ }
+ if (relationshipType == null) {
+ throw new IllegalArgumentException("relationshipType");
+ }
+
+ if (this.isRelationshipPart || targetPartName.isRelationshipPartURI()) {
+ throw new InvalidOperationException(
+ "Rule M1.25: The Relationships part shall not have relationships to any other part.");
+ }
+
+ if (relationships == null) {
+ relationships = new PackageRelationshipCollection();
+ }
+
+ return relationships.addRelationship(targetPartName.getURI(),
+ targetMode, relationshipType, id);
+ }
+
+ /**
+ * Add a relationship to a part (except relationships part).
+ *
+ * @param targetURI
+ * URI the target part. Must be relative to the source root
+ * directory of the part.
+ * @param targetMode
+ * Mode [Internal|External].
+ * @param relationshipType
+ * Type of relationship.
+ * @return The newly created and added relationship
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#addRelationship(org.apache.poi.openxml4j.opc.PackagePartName,
+ * org.apache.poi.openxml4j.opc.TargetMode, java.lang.String)
+ */
+ public PackageRelationship addRelationship(URI targetURI,
+ TargetMode targetMode, String relationshipType) {
+ return addRelationship(targetURI, targetMode, relationshipType, null);
+ }
+
+ /**
+ * Add a relationship to a part (except relationships 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 targetURI
+ * URI of the target part. Must be relative to the source root
+ * directory of the part.
+ * @param targetMode
+ * Mode [Internal|External].
+ * @param relationshipType
+ * Type of relationship.
+ * @param id
+ * Relationship unique id.
+ * @return The newly created and added relationship
+ *
+ * @throws InvalidFormatException
+ * If the URI point to a relationship part URI.
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#addRelationship(org.apache.poi.openxml4j.opc.PackagePartName,
+ * org.apache.poi.openxml4j.opc.TargetMode, java.lang.String, java.lang.String)
+ */
+ public PackageRelationship addRelationship(URI targetURI,
+ TargetMode targetMode, String relationshipType, String id) {
+ container.throwExceptionIfReadOnly();
+
+ if (targetURI == null) {
+ throw new IllegalArgumentException("targetPartName");
+ }
+ if (targetMode == null) {
+ throw new IllegalArgumentException("targetMode");
+ }
+ if (relationshipType == null) {
+ throw new IllegalArgumentException("relationshipType");
+ }
+
+ // Try to retrieve the target part
+
+ if (this.isRelationshipPart
+ || PackagingURIHelper.isRelationshipPartURI(targetURI)) {
+ throw new InvalidOperationException(
+ "Rule M1.25: The Relationships part shall not have relationships to any other part.");
+ }
+
+ if (relationships == null) {
+ relationships = new PackageRelationshipCollection();
+ }
+
+ return relationships.addRelationship(targetURI,
+ targetMode, relationshipType, id);
+ }
+
+ /**
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#clearRelationships()
+ */
+ public void clearRelationships() {
+ if (relationships != null) {
+ relationships.clear();
+ }
+ }
+
+ /**
+ * Delete the relationship specified by its id.
+ *
+ * @param id
+ * The ID identified the part to delete.
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#removeRelationship(java.lang.String)
+ */
+ public void removeRelationship(String id) {
+ this.container.throwExceptionIfReadOnly();
+ if (this.relationships != null)
+ this.relationships.removeRelationship(id);
+ }
+
+ /**
+ * Retrieve all the relationships attached to this part.
+ *
+ * @return This part's relationships.
+ * @throws OpenXML4JException
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#getRelationships()
+ */
+ public PackageRelationshipCollection getRelationships()
+ throws InvalidFormatException {
+ return getRelationshipsCore(null);
+ }
+
+ /**
+ * Retrieves a package relationship from its id.
+ *
+ * @param id
+ * ID of the package relationship to retrieve.
+ * @return The package relationship
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#getRelationship(java.lang.String)
+ */
+ public PackageRelationship getRelationship(String id) {
+ return this.relationships.getRelationshipByID(id);
+ }
+
+ /**
+ * Retrieve all relationships attached to this part which have the specified
+ * type.
+ *
+ * @param relationshipType
+ * Relationship type filter.
+ * @return All relationships from this part that have the specified type.
+ * @throws InvalidFormatException
+ * If an error occurs while parsing the part.
+ * @throws InvalidOperationException
+ * If the package is open in write only mode.
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#getRelationshipsByType(java.lang.String)
+ */
+ public PackageRelationshipCollection getRelationshipsByType(
+ String relationshipType) throws InvalidFormatException {
+ container.throwExceptionIfWriteOnly();
+
+ return getRelationshipsCore(relationshipType);
+ }
+
+ /**
+ * Implementation of the getRelationships method().
+ *
+ * @param filter
+ * Relationship type filter. If null then the filter is
+ * disabled and return all the relationships.
+ * @return All relationships from this part that have the specified type.
+ * @throws InvalidFormatException
+ * Throws if an error occurs during parsing the relationships
+ * part.
+ * @throws InvalidOperationException
+ * Throws if the package is open en write only mode.
+ * @see #getRelationshipsByType(String)
+ */
+ private PackageRelationshipCollection getRelationshipsCore(String filter)
+ throws InvalidFormatException {
+ this.container.throwExceptionIfWriteOnly();
+ if (relationships == null) {
+ this.throwExceptionIfRelationship();
+ relationships = new PackageRelationshipCollection(this);
+ }
+ return new PackageRelationshipCollection(relationships, filter);
+ }
+
+ /**
+ * Knows if the part have any relationships.
+ *
+ * @return true if the part have at least one relationship else
+ * false.
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#hasRelationships()
+ */
+ public boolean hasRelationships() {
+ return (!this.isRelationshipPart && (relationships != null && relationships
+ .size() > 0));
+ }
+
+ /**
+ * Checks if the specified relationship is part of this package part.
+ *
+ * @param rel
+ * The relationship to check.
+ * @return true if the specified relationship exists in this part,
+ * else returns false
+ * @see org.apache.poi.openxml4j.opc.RelationshipSource#isRelationshipExists(org.apache.poi.openxml4j.opc.PackageRelationship)
+ */
+ @SuppressWarnings("finally")
+ public boolean isRelationshipExists(PackageRelationship rel) {
+ try {
+ for (PackageRelationship r : this.getRelationships()) {
+ if (r == rel)
+ return true;
+ }
+ } finally {
+ return false;
+ }
+ }
+
+ /**
+ * Get the input stream of this part to read its content.
+ *
+ * @return The input stream of the content of this part, else
+ * null
.
+ */
+ public InputStream getInputStream() throws IOException {
+ InputStream inStream = this.getInputStreamImpl();
+ if (inStream == null) {
+ throw new IOException("Can't obtain the input stream from "
+ + partName.getName());
+ } else
+ return inStream;
+ }
+
+ /**
+ * Get the output stream of this part. If the part is originally embedded in
+ * Zip package, it'll be transform intot a MemoryPackagePart in
+ * order to write inside (the standard Java API doesn't allow to write in
+ * the file)
+ *
+ * @see org.apache.poi.openxml4j.opc.internal.MemoryPackagePart
+ */
+ public OutputStream getOutputStream() {
+ OutputStream outStream;
+ // If this part is a zip package part (read only by design) we convert
+ // this part into a MemoryPackagePart instance for write purpose.
+ if (this instanceof ZipPackagePart) {
+ // Delete logically this part
+ this.container.removePart(this.partName);
+
+ // Create a memory part
+ PackagePart part = container.createPart(this.partName,
+ this.contentType.toString(), false);
+ part.relationships = this.relationships;
+ if (part == null) {
+ throw new InvalidOperationException(
+ "Can't create a temporary part !");
+ }
+ outStream = part.getOutputStreamImpl();
+ } else {
+ outStream = this.getOutputStreamImpl();
+ }
+ return outStream;
+ }
+
+ /**
+ * Throws an exception if this package part is a relationship part.
+ *
+ * @throws InvalidOperationException
+ * If this part is a relationship part.
+ */
+ private void throwExceptionIfRelationship()
+ throws InvalidOperationException {
+ if (this.isRelationshipPart)
+ throw new InvalidOperationException(
+ "Can do this operation on a relationship part !");
+ }
+
+ /**
+ * Ensure the package relationships collection instance is built.
+ *
+ * @throws InvalidFormatException
+ * Throws if
+ */
+ private void loadRelationships() throws InvalidFormatException {
+ if (this.relationships == null && !this.isRelationshipPart) {
+ this.throwExceptionIfRelationship();
+ relationships = new PackageRelationshipCollection(this);
+ }
+ }
+
+ /*
+ * Accessors
+ */
+
+ /**
+ * @return the uri
+ */
+ public PackagePartName getPartName() {
+ return partName;
+ }
+
+ /**
+ * @return the contentType
+ */
+ public String getContentType() {
+ return contentType.toString();
+ }
+
+ /**
+ * Set the content type.
+ *
+ * @param contentType
+ * the contentType to set
+ *
+ * @throws InvalidFormatException
+ * Throws if the content type is not valid.
+ * @throws InvalidOperationException
+ * Throws if you try to change the content type whereas this
+ * part is already attached to a package.
+ */
+ public void setContentType(String contentType)
+ throws InvalidFormatException {
+ if (container == null)
+ this.contentType = new ContentType(contentType);
+ else
+ throw new InvalidOperationException(
+ "You can't change the content type of a part.");
+ }
+
+ public Package getPackage() {
+ return container;
+ }
+
+ /**
+ * @return
+ */
+ public boolean isRelationshipPart() {
+ return this.isRelationshipPart;
+ }
+
+ /**
+ * @return
+ */
+ public boolean isDeleted() {
+ return isDeleted;
+ }
+
+ /**
+ * @param isDeleted
+ * the isDeleted to set
+ */
+ public void setDeleted(boolean isDeleted) {
+ this.isDeleted = isDeleted;
+ }
+
+ @Override
+ public String toString() {
+ return "Name: " + this.partName + " - Content Type: "
+ + this.contentType.toString();
+ }
+
+ /*-------------- Abstract methods ------------- */
+
+ /**
+ * Abtract method that get the input stream of this part.
+ *
+ * @exception IOException
+ * Throws if an IO Exception occur in the implementation
+ * method.
+ */
+ protected abstract InputStream getInputStreamImpl() throws IOException;
+
+ /**
+ * Abstract method that get the output stream of this part.
+ */
+ protected abstract OutputStream getOutputStreamImpl();
+
+ /**
+ * Save the content of this part and the associated relationships part (if
+ * this part own at least one relationship) into the specified output
+ * stream.
+ *
+ * @param zos
+ * Output stream to save this part.
+ * @throws OpenXML4JException
+ * If any exception occur.
+ */
+ public abstract boolean save(OutputStream zos) throws OpenXML4JException;
+
+ /**
+ * Load the content of this part.
+ *
+ * @param ios
+ * The input stream of the content to load.
+ * @return true if the content has been successfully loaded, else
+ * false.
+ * @throws InvalidFormatException
+ * Throws if the content format is invalid.
+ */
+ public abstract boolean load(InputStream ios) throws InvalidFormatException;
+
+ /**
+ * Close this part : flush this part, close the input stream and output
+ * stream. After this method call, the part must be available for packaging.
+ */
+ public abstract void close();
+
+ /**
+ * Flush the content of this part. If the input stream and/or output stream
+ * as in a waiting state to read or write, the must to empty their
+ * respective buffer.
+ */
+ public abstract void flush();
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackagePartCollection.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackagePartCollection.java
new file mode 100755
index 000000000..54021302b
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackagePartCollection.java
@@ -0,0 +1,81 @@
+/* ====================================================================
+ 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.util.ArrayList;
+import java.util.TreeMap;
+
+import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
+
+/**
+ * A package part collection.
+ *
+ * @author Julien Chable
+ * @version 0.1
+ */
+public final class PackagePartCollection extends
+ TreeMaptrue
except for
+ * special URI like '/' which is needed for internal use by
+ * OpenXML4J but is not valid.
+ * @throws InvalidFormatException
+ * Throw if the specified part name is not conform to Open
+ * Packaging Convention specifications.
+ * @see java.net.URI
+ */
+ PackagePartName(URI uri, boolean checkConformance)
+ throws InvalidFormatException {
+ if (checkConformance) {
+ throwExceptionIfInvalidPartUri(uri);
+ } else {
+ if (!PackagingURIHelper.PACKAGE_ROOT_URI.equals(uri)) {
+ throw new OpenXML4JRuntimeException(
+ "OCP conformance must be check for ALL part name except special cases : ['/']");
+ }
+ }
+ this.partNameURI = uri;
+ this.isRelationship = isRelationshipPartURI(this.partNameURI);
+ }
+
+ /**
+ * Constructor. Makes a ValidPartName object from a String part name.
+ *
+ * @param partName
+ * Part name to valid and to create.
+ * @param checkConformance
+ * Flag to specify if the contructor have to validate the OPC
+ * conformance. Must be always true
except for
+ * special URI like '/' which is needed for internal use by
+ * OpenXML4J but is not valid.
+ * @throws InvalidFormatException
+ * Throw if the specified part name is not conform to Open
+ * Packaging Convention specifications.
+ */
+ PackagePartName(String partName, boolean checkConformance)
+ throws InvalidFormatException {
+ URI partURI;
+ try {
+ partURI = new URI(partName);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(
+ "partName argmument is not a valid OPC part name !");
+ }
+
+ if (checkConformance) {
+ throwExceptionIfInvalidPartUri(partURI);
+ } else {
+ if (!PackagingURIHelper.PACKAGE_ROOT_URI.equals(partURI)) {
+ throw new OpenXML4JRuntimeException(
+ "OCP conformance must be check for ALL part name except special cases : ['/']");
+ }
+ }
+ this.partNameURI = partURI;
+ this.isRelationship = isRelationshipPartURI(this.partNameURI);
+ }
+
+ /**
+ * Check if the specified part name is a relationship part name.
+ *
+ * @param partUri
+ * The URI to check.
+ * @return true
if this part name respect the relationship
+ * part naming convention else false
.
+ */
+ private boolean isRelationshipPartURI(URI partUri) {
+ if (partUri == null)
+ throw new IllegalArgumentException("partUri");
+
+ return partUri.getPath().matches(
+ "^.*/" + PackagingURIHelper.RELATIONSHIP_PART_SEGMENT_NAME + "/.*\\"
+ + PackagingURIHelper.RELATIONSHIP_PART_EXTENSION_NAME
+ + "$");
+ }
+
+ /**
+ * Know if this part name is a relationship part name.
+ *
+ * @return true
if this part name respect the relationship
+ * part naming convention else false
.
+ */
+ public boolean isRelationshipPartURI() {
+ return this.isRelationship;
+ }
+
+ /**
+ * Throws an exception (of any kind) if the specified part name does not
+ * follow the Open Packaging Convention specifications naming rules.
+ *
+ * @param partUri
+ * The part name to check.
+ * @throws Exception
+ * Throws if the part name is invalid.
+ */
+ private static void throwExceptionIfInvalidPartUri(URI partUri)
+ throws InvalidFormatException {
+ if (partUri == null)
+ throw new IllegalArgumentException("partUri");
+ // Check if the part name URI is empty [M1.1]
+ throwExceptionIfEmptyURI(partUri);
+
+ // Check if the part name URI is absolute
+ throwExceptionIfAbsoluteUri(partUri);
+
+ // Check if the part name URI starts with a forward slash [M1.4]
+ throwExceptionIfPartNameNotStartsWithForwardSlashChar(partUri);
+
+ // Check if the part name URI ends with a forward slash [M1.5]
+ throwExceptionIfPartNameEndsWithForwardSlashChar(partUri);
+
+ // Check if the part name does not have empty segments. [M1.3]
+ // Check if a segment ends with a dot ('.') character. [M1.9]
+ throwExceptionIfPartNameHaveInvalidSegments(partUri);
+ }
+
+ /**
+ * Throws an exception if the specified URI is empty. [M1.1]
+ *
+ * @param partURI
+ * Part URI to check.
+ * @throws InvalidFormatException
+ * If the specified URI is empty.
+ */
+ private static void throwExceptionIfEmptyURI(URI partURI)
+ throws InvalidFormatException {
+ if (partURI == null)
+ throw new IllegalArgumentException("partURI");
+
+ String uriPath = partURI.getPath();
+ if (uriPath.length() == 0
+ || ((uriPath.length() == 1) && (uriPath.charAt(0) == PackagingURIHelper.FORWARD_SLASH_CHAR)))
+ throw new InvalidFormatException(
+ "A part name shall not be empty [M1.1]: "
+ + partURI.getPath());
+ }
+
+ /**
+ * Throws an exception if the part name has empty segments. [M1.3]
+ *
+ * Throws an exception if a segment any characters other than pchar
+ * characters. [M1.6]
+ *
+ * Throws an exception if a segment contain percent-encoded forward slash
+ * ('/'), or backward slash ('\') characters. [M1.7]
+ *
+ * Throws an exception if a segment contain percent-encoded unreserved
+ * characters. [M1.8]
+ *
+ * Throws an exception if the specified part name's segments end with a dot
+ * ('.') character. [M1.9]
+ *
+ * Throws an exception if a segment doesn't include at least one non-dot
+ * character. [M1.10]
+ *
+ * @param partUri
+ * The part name to check.
+ * @throws InvalidFormatException
+ * if the specified URI contain an empty segments or if one the
+ * segments contained in the part name, ends with a dot ('.')
+ * character.
+ */
+ private static void throwExceptionIfPartNameHaveInvalidSegments(URI partUri)
+ throws InvalidFormatException {
+ if (partUri == null || "".equals(partUri)) {
+ throw new IllegalArgumentException("partUri");
+ }
+
+ // Split the URI into several part and analyze each
+ String[] segments = partUri.toASCIIString().split("/");
+ if (segments.length <= 1 || !segments[0].equals(""))
+ throw new InvalidFormatException(
+ "A part name shall not have empty segments [M1.3]: "
+ + partUri.getPath());
+
+ for (int i = 1; i < segments.length; ++i) {
+ String seg = segments[i];
+ if (seg == null || "".equals(seg)) {
+ throw new InvalidFormatException(
+ "A part name shall not have empty segments [M1.3]: "
+ + partUri.getPath());
+ }
+
+ if (seg.endsWith(".")) {
+ throw new InvalidFormatException(
+ "A segment shall not end with a dot ('.') character [M1.9]: "
+ + partUri.getPath());
+ }
+
+ if ("".equals(seg.replaceAll("\\\\.", ""))) {
+ // Normally will never been invoked with the previous
+ // implementation rule [M1.9]
+ throw new InvalidFormatException(
+ "A segment shall include at least one non-dot character. [M1.10]: "
+ + partUri.getPath());
+ }
+
+ /*
+ * Check for rule M1.6, M1.7, M1.8
+ */
+ checkPCharCompliance(seg);
+ }
+ }
+
+ /**
+ * Throws an exception if a segment any characters other than pchar
+ * characters. [M1.6]
+ *
+ * Throws an exception if a segment contain percent-encoded forward slash
+ * ('/'), or backward slash ('\') characters. [M1.7]
+ *
+ * Throws an exception if a segment contain percent-encoded unreserved
+ * characters. [M1.8]
+ *
+ * @param segment
+ * The segment to check
+ */
+ private static void checkPCharCompliance(String segment)
+ throws InvalidFormatException {
+ boolean errorFlag;
+ for (int i = 0; i < segment.length(); ++i) {
+ char c = segment.charAt(i);
+ errorFlag = true;
+
+ /* Check rule M1.6 */
+
+ // Check for digit or letter
+ if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
+ || (c >= '0' && c <= '9')) {
+ errorFlag = false;
+ } else {
+ // Check "-", ".", "_", "~"
+ for (int j = 0; j < RFC3986_PCHAR_UNRESERVED_SUP.length; ++j) {
+ if (c == RFC3986_PCHAR_UNRESERVED_SUP[j].charAt(0)) {
+ errorFlag = false;
+ break;
+ }
+ }
+
+ // Check ":", "@"
+ for (int j = 0; errorFlag
+ && j < RFC3986_PCHAR_AUTHORIZED_SUP.length; ++j) {
+ if (c == RFC3986_PCHAR_AUTHORIZED_SUP[j].charAt(0)) {
+ errorFlag = false;
+ }
+ }
+
+ // Check "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
+ for (int j = 0; errorFlag
+ && j < RFC3986_PCHAR_SUB_DELIMS.length; ++j) {
+ if (c == RFC3986_PCHAR_SUB_DELIMS[j].charAt(0)) {
+ errorFlag = false;
+ }
+ }
+ }
+
+ if (errorFlag && c == '%') {
+ // We certainly found an encoded character, check for length
+ // now ( '%' HEXDIGIT HEXDIGIT)
+ if (((segment.length() - i) < 2)) {
+ throw new InvalidFormatException("The segment " + segment
+ + " contain invalid encoded character !");
+ }
+
+ // If not percent encoded character error occur then reset the
+ // flag -> the character is valid
+ errorFlag = false;
+
+ // Decode the encoded character
+ char decodedChar = (char) Integer.parseInt(segment.substring(
+ i + 1, i + 3), 16);
+ i += 2;
+
+ /* Check rule M1.7 */
+ if (decodedChar == '/' || decodedChar == '\\')
+ throw new InvalidFormatException(
+ "A segment shall not contain percent-encoded forward slash ('/'), or backward slash ('\') characters. [M1.7]");
+
+ /* Check rule M1.8 */
+
+ // Check for unreserved character like define in RFC3986
+ if ((decodedChar >= 'A' && decodedChar <= 'Z')
+ || (decodedChar >= 'a' && decodedChar <= 'z')
+ || (decodedChar >= '0' && decodedChar <= '9'))
+ errorFlag = true;
+
+ // Check for unreserved character "-", ".", "_", "~"
+ for (int j = 0; !errorFlag
+ && j < RFC3986_PCHAR_UNRESERVED_SUP.length; ++j) {
+ if (c == RFC3986_PCHAR_UNRESERVED_SUP[j].charAt(0)) {
+ errorFlag = true;
+ break;
+ }
+ }
+ if (errorFlag)
+ throw new InvalidFormatException(
+ "A segment shall not contain percent-encoded unreserved characters. [M1.8]");
+ }
+
+ if (errorFlag)
+ throw new InvalidFormatException(
+ "A segment shall not hold any characters other than pchar characters. [M1.6]");
+ }
+ }
+
+ /**
+ * Throws an exception if the specified part name doesn't start with a
+ * forward slash character '/'. [M1.4]
+ *
+ * @param partUri
+ * The part name to check.
+ * @throws InvalidFormatException
+ * If the specified part name doesn't start with a forward slash
+ * character '/'.
+ */
+ private static void throwExceptionIfPartNameNotStartsWithForwardSlashChar(
+ URI partUri) throws InvalidFormatException {
+ String uriPath = partUri.getPath();
+ if (uriPath.length() > 0
+ && uriPath.charAt(0) != PackagingURIHelper.FORWARD_SLASH_CHAR)
+ throw new InvalidFormatException(
+ "A part name shall start with a forward slash ('/') character [M1.4]: "
+ + partUri.getPath());
+ }
+
+ /**
+ * Throws an exception if the specified part name ends with a forwar slash
+ * character '/'. [M1.5]
+ *
+ * @param partUri
+ * The part name to check.
+ * @throws InvalidFormatException
+ * If the specified part name ends with a forwar slash character
+ * '/'.
+ */
+ private static void throwExceptionIfPartNameEndsWithForwardSlashChar(
+ URI partUri) throws InvalidFormatException {
+ String uriPath = partUri.getPath();
+ if (uriPath.length() > 0
+ && uriPath.charAt(uriPath.length() - 1) == PackagingURIHelper.FORWARD_SLASH_CHAR)
+ throw new InvalidFormatException(
+ "A part name shall not have a forward slash as the last character [M1.5]: "
+ + partUri.getPath());
+ }
+
+ /**
+ * Throws an exception if the specified URI is absolute.
+ *
+ * @param partUri
+ * The URI to check.
+ * @throws InvalidFormatException
+ * Throws if the specified URI is absolute.
+ */
+ private static void throwExceptionIfAbsoluteUri(URI partUri)
+ throws InvalidFormatException {
+ if (partUri.isAbsolute())
+ throw new InvalidFormatException("Absolute URI forbidden: "
+ + partUri);
+ }
+
+ /**
+ * Compare two part name following the rule M1.12 :
+ *
+ * Part name equivalence is determined by comparing part names as
+ * case-insensitive ASCII strings. Packages shall not contain equivalent
+ * part names and package implementers shall neither create nor recognize
+ * packages with equivalent part names. [M1.12]
+ */
+ public int compareTo(PackagePartName otherPartName) {
+ if (otherPartName == null)
+ return -1;
+ return this.partNameURI.toASCIIString().toLowerCase().compareTo(
+ otherPartName.partNameURI.toASCIIString().toLowerCase());
+ }
+
+ /**
+ * Retrieves the extension of the part name if any. If there is no extension
+ * returns an empty String. Example : '/document/content.xml' => 'xml'
+ *
+ * @return The extension of the part name.
+ */
+ public String getExtension() {
+ String fragment = this.partNameURI.getPath();
+ if (fragment.length() > 0) {
+ int i = fragment.lastIndexOf(".");
+ if (i > -1)
+ return fragment.substring(i + 1);
+ }
+ return "";
+ }
+
+ /**
+ * Get this part name.
+ *
+ * @return The name of this part name.
+ */
+ public String getName() {
+ return this.partNameURI.toASCIIString();
+ }
+
+ /**
+ * Part name equivalence is determined by comparing part names as
+ * case-insensitive ASCII strings. Packages shall not contain equivalent
+ * part names and package implementers shall neither create nor recognize
+ * packages with equivalent part names. [M1.12]
+ */
+ @Override
+ public boolean equals(Object otherPartName) {
+ if (otherPartName == null
+ || !(otherPartName instanceof PackagePartName))
+ return false;
+ return this.partNameURI.toASCIIString().toLowerCase().equals(
+ ((PackagePartName) otherPartName).partNameURI.toASCIIString()
+ .toLowerCase());
+ }
+
+ @Override
+ public int hashCode() {
+ return this.partNameURI.toASCIIString().toLowerCase().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+
+ /* Getters and setters */
+
+ /**
+ * Part name property getter.
+ *
+ * @return This part name URI.
+ */
+ public URI getURI() {
+ return this.partNameURI;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageProperties.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageProperties.java
new file mode 100755
index 000000000..5ab80dd0f
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageProperties.java
@@ -0,0 +1,227 @@
+/* ====================================================================
+ 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.util.Date;
+
+import org.apache.poi.openxml4j.util.Nullable;
+
+/**
+ * Represents the core properties of an OPC package.
+ *
+ * @author Julien Chable
+ * @version 1.0
+ * @see org.apache.poi.openxml4j.opc.Package
+ */
+public interface PackageProperties {
+
+ /**
+ * Dublin Core Terms URI.
+ */
+ public final static String NAMESPACE_DCTERMS = "http://purl.org/dc/terms/";
+
+ /**
+ * Dublin Core namespace URI.
+ */
+ public final static String NAMESPACE_DC = "http://purl.org/dc/elements/1.1/";
+
+ /* Getters and setters */
+
+ /**
+ * Set the category of the content of this package.
+ */
+ public abstract Nullablenull
.
+ */
+ public static URI relativizeURI(URI sourceURI, URI targetURI) {
+ StringBuilder retVal = new StringBuilder();
+ String[] segmentsSource = sourceURI.getPath().split("/", -1);
+ String[] segmentsTarget = targetURI.getPath().split("/", -1);
+
+ // If the source URI is empty
+ if (segmentsSource.length == 0) {
+ throw new IllegalArgumentException(
+ "Can't relativize an empty source URI !");
+ }
+
+ // If target URI is empty
+ if (segmentsTarget.length == 0) {
+ throw new IllegalArgumentException(
+ "Can't relativize an empty target URI !");
+ }
+
+ // If the source is the root, then the relativized
+ // form must actually be an absolute URI
+ if(sourceURI.toString().equals("/")) {
+ return targetURI;
+ }
+
+
+ // Relativize the source URI against the target URI.
+ // First up, figure out how many steps along we can go
+ // and still have them be the same
+ int segmentsTheSame = 0;
+ for (int i = 0; i < segmentsSource.length && i < segmentsTarget.length; i++) {
+ if (segmentsSource[i].equals(segmentsTarget[i])) {
+ // Match so far, good
+ segmentsTheSame++;
+ } else {
+ break;
+ }
+ }
+
+ // If we didn't have a good match or at least except a first empty element
+ if ((segmentsTheSame == 0 || segmentsTheSame == 1) &&
+ segmentsSource[0].equals("") && segmentsTarget[0].equals("")) {
+ for (int i = 0; i < segmentsSource.length - 2; i++) {
+ retVal.append("../");
+ }
+ for (int i = 0; i < segmentsTarget.length; i++) {
+ if (segmentsTarget[i].equals(""))
+ continue;
+ retVal.append(segmentsTarget[i]);
+ if (i != segmentsTarget.length - 1)
+ retVal.append("/");
+ }
+
+ try {
+ return new URI(retVal.toString());
+ } catch (Exception e) {
+ System.err.println(e);
+ return null;
+ }
+ }
+
+ // Special case for where the two are the same
+ if (segmentsTheSame == segmentsSource.length
+ && segmentsTheSame == segmentsTarget.length) {
+ retVal.append("");
+ } else {
+ // Matched for so long, but no more
+
+ // Do we need to go up a directory or two from
+ // the source to get here?
+ // (If it's all the way up, then don't bother!)
+ if (segmentsTheSame == 1) {
+ retVal.append("/");
+ } else {
+ for (int j = segmentsTheSame; j < segmentsSource.length - 1; j++) {
+ retVal.append("../");
+ }
+ }
+
+ // Now go from here on down
+ for (int j = segmentsTheSame; j < segmentsTarget.length; j++) {
+ if (retVal.length() > 0
+ && retVal.charAt(retVal.length() - 1) != '/') {
+ retVal.append("/");
+ }
+ retVal.append(segmentsTarget[j]);
+ }
+ }
+
+ try {
+ return new URI(retVal.toString());
+ } catch (Exception e) {
+ System.err.println(e);
+ return null;
+ }
+ }
+
+ /**
+ * Resolve a source uri against a target.
+ *
+ * @param sourcePartUri
+ * The source URI.
+ * @param targetUri
+ * The target URI.
+ * @return The resolved URI.
+ */
+ public static URI resolvePartUri(URI sourcePartUri, URI targetUri) {
+ if (sourcePartUri == null || sourcePartUri.isAbsolute()) {
+ throw new IllegalArgumentException("sourcePartUri invalid - "
+ + sourcePartUri);
+ }
+
+ if (targetUri == null || targetUri.isAbsolute()) {
+ throw new IllegalArgumentException("targetUri invalid - "
+ + targetUri);
+ }
+
+ return sourcePartUri.resolve(targetUri);
+ }
+
+ /**
+ * Get URI from a string path.
+ */
+ public static URI getURIFromPath(String path) {
+ URI retUri = null;
+ try {
+ retUri = new URI(path);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("path");
+ }
+ return retUri;
+ }
+
+ /**
+ * Get the source part URI from a specified relationships part.
+ *
+ * @param relationshipPartUri
+ * The relationship part use to retrieve the source part.
+ * @return The source part URI from the specified relationships part.
+ */
+ public static URI getSourcePartUriFromRelationshipPartUri(
+ URI relationshipPartUri) {
+ if (relationshipPartUri == null)
+ throw new IllegalArgumentException(
+ "Le param�tre relationshipPartUri ne doit pas �tre null !");
+
+ if (!isRelationshipPartURI(relationshipPartUri))
+ throw new IllegalArgumentException(
+ "L'URI ne doit pas �tre celle d'une partie de type relation.");
+
+ if (relationshipPartUri.compareTo(PACKAGE_RELATIONSHIPS_ROOT_URI) == 0)
+ return PACKAGE_ROOT_URI;
+
+ String filename = relationshipPartUri.getPath();
+ String filenameWithoutExtension = getFilenameWithoutExtension(relationshipPartUri);
+ filename = filename
+ .substring(0, ((filename.length() - filenameWithoutExtension
+ .length()) - RELATIONSHIP_PART_EXTENSION_NAME.length()));
+ filename = filename.substring(0, filename.length()
+ - RELATIONSHIP_PART_SEGMENT_NAME.length() - 1);
+ filename = combine(filename, filenameWithoutExtension);
+ return getURIFromPath(filename);
+ }
+
+ /**
+ * Create an OPC compliant part name by throwing an exception if the URI is
+ * not valid.
+ *
+ * @param partUri
+ * The part name URI to validate.
+ * @return A valid part name object, else null
.
+ * @throws InvalidFormatException
+ * Throws if the specified URI is not OPC compliant.
+ */
+ public static PackagePartName createPartName(URI partUri)
+ throws InvalidFormatException {
+ if (partUri == null)
+ throw new IllegalArgumentException("partName");
+
+ return new PackagePartName(partUri, true);
+ }
+
+ /**
+ * Create an OPC compliant part name.
+ *
+ * @param partName
+ * The part name to validate.
+ * @return The correspondant part name if valid, else null
.
+ * @throws InvalidFormatException
+ * Throws if the specified part name is not OPC compliant.
+ * @see #createPartName(URI)
+ */
+ public static PackagePartName createPartName(String partName)
+ throws InvalidFormatException {
+ URI partNameURI;
+ try {
+ partNameURI = new URI(partName);
+ } catch (URISyntaxException e) {
+ throw new InvalidFormatException(e.getMessage());
+ }
+ return createPartName(partNameURI);
+ }
+
+ /**
+ * Create an OPC compliant part name by resolving it using a base part.
+ *
+ * @param partName
+ * The part name to validate.
+ * @param relativePart
+ * The relative base part.
+ * @return The correspondant part name if valid, else null
.
+ * @throws InvalidFormatException
+ * Throws if the specified part name is not OPC compliant.
+ * @see #createPartName(URI)
+ */
+ public static PackagePartName createPartName(String partName,
+ PackagePart relativePart) throws InvalidFormatException {
+ URI newPartNameURI;
+ try {
+ newPartNameURI = resolvePartUri(
+ relativePart.getPartName().getURI(), new URI(partName));
+ } catch (URISyntaxException e) {
+ throw new InvalidFormatException(e.getMessage());
+ }
+ return createPartName(newPartNameURI);
+ }
+
+ /**
+ * Create an OPC compliant part name by resolving it using a base part.
+ *
+ * @param partName
+ * The part name URI to validate.
+ * @param relativePart
+ * The relative base part.
+ * @return The correspondant part name if valid, else null
.
+ * @throws InvalidFormatException
+ * Throws if the specified part name is not OPC compliant.
+ * @see #createPartName(URI)
+ */
+ public static PackagePartName createPartName(URI partName,
+ PackagePart relativePart) throws InvalidFormatException {
+ URI newPartNameURI = resolvePartUri(
+ relativePart.getPartName().getURI(), partName);
+ return createPartName(newPartNameURI);
+ }
+
+ /**
+ * Validate a part URI by returning a boolean.
+ * ([M1.1],[M1.3],[M1.4],[M1.5],[M1.6])
+ *
+ * (OPC Specifications 8.1.1 Part names) :
+ *
+ * Part Name Syntax
+ *
+ * The part name grammar is defined as follows:
+ *
+ * part_name = 1*( "/" segment )
+ *
+ * segment = 1*( pchar )
+ *
+ *
+ * (pchar is defined in RFC 3986)
+ *
+ * @param partUri
+ * The URI to validate.
+ * @return true if the URI is valid to the OPC Specifications, else
+ * false
+ *
+ * @see #createPartName(URI)
+ */
+ public static boolean isValidPartName(URI partUri) {
+ if (partUri == null)
+ throw new IllegalArgumentException("partUri");
+
+ try {
+ createPartName(partUri);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Decode a URI by converting all percent encoded character into a String
+ * character.
+ *
+ * @param uri
+ * The URI to decode.
+ * @return The specified URI in a String with converted percent encoded
+ * characters.
+ */
+ public static String decodeURI(URI uri) {
+ StringBuffer retVal = new StringBuffer();
+ String uriStr = uri.toASCIIString();
+ char c;
+ for (int i = 0; i < uriStr.length(); ++i) {
+ c = uriStr.charAt(i);
+ if (c == '%') {
+ // We certainly found an encoded character, check for length
+ // now ( '%' HEXDIGIT HEXDIGIT)
+ if (((uriStr.length() - i) < 2)) {
+ throw new IllegalArgumentException("The uri " + uriStr
+ + " contain invalid encoded character !");
+ }
+
+ // Decode the encoded character
+ char decodedChar = (char) Integer.parseInt(uriStr.substring(
+ i + 1, i + 3), 16);
+ retVal.append(decodedChar);
+ i += 2;
+ continue;
+ }
+ retVal.append(c);
+ }
+ return retVal.toString();
+ }
+
+ /**
+ * Build a part name where the relationship should be stored ((ex
+ * /word/document.xml -> /word/_rels/document.xml.rels)
+ *
+ * @param partName
+ * Source part URI
+ * @return the full path (as URI) of the relation file
+ * @throws InvalidOperationException
+ * Throws if the specified URI is a relationshp part.
+ */
+ public static PackagePartName getRelationshipPartName(
+ PackagePartName partName) {
+ if (partName == null)
+ throw new IllegalArgumentException("partName");
+
+ if (PackagingURIHelper.PACKAGE_ROOT_URI.getPath() == partName.getURI()
+ .getPath())
+ return PackagingURIHelper.PACKAGE_RELATIONSHIPS_ROOT_PART_NAME;
+
+ if (partName.isRelationshipPartURI())
+ throw new InvalidOperationException("Can't be a relationship part");
+
+ String fullPath = partName.getURI().getPath();
+ String filename = getFilename(partName.getURI());
+ fullPath = fullPath.substring(0, fullPath.length() - filename.length());
+ fullPath = combine(fullPath,
+ PackagingURIHelper.RELATIONSHIP_PART_SEGMENT_NAME);
+ fullPath = combine(fullPath, filename);
+ fullPath = fullPath
+ + PackagingURIHelper.RELATIONSHIP_PART_EXTENSION_NAME;
+
+ PackagePartName retPartName;
+ try {
+ retPartName = createPartName(fullPath);
+ } catch (InvalidFormatException e) {
+ // Should never happen in production as all data are fixed but in
+ // case of return null:
+ return null;
+ }
+ return retPartName;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/RelationshipSource.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/RelationshipSource.java
new file mode 100755
index 000000000..ce9d1159c
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/RelationshipSource.java
@@ -0,0 +1,166 @@
+/* ====================================================================
+ 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 org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
+import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+
+public interface RelationshipSource {
+
+ /**
+ * Add a relationship to a part (except relationships part).
+ *
+ * @param targetPartName
+ * Name of the target part. This one must be relative to the
+ * source root directory of the part.
+ * @param targetMode
+ * Mode [Internal|External].
+ * @param relationshipType
+ * Type of relationship.
+ * @return The newly created and added relationship
+ */
+ public abstract PackageRelationship addRelationship(
+ PackagePartName targetPartName, TargetMode targetMode,
+ String relationshipType);
+
+ /**
+ * Add a relationship to a part (except relationships 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
+ * Name of the target part. This one must be relative to the
+ * source root directory of the part.
+ * @param targetMode
+ * Mode [Internal|External].
+ * @param relationshipType
+ * Type of relationship.
+ * @param id
+ * Relationship unique id.
+ * @return The newly created and added relationship
+ *
+ * @throws InvalidFormatException
+ * If the URI point to a relationship part URI.
+ */
+ public abstract PackageRelationship addRelationship(
+ PackagePartName targetPartName, TargetMode targetMode,
+ String relationshipType, String id);
+
+ /**
+ * 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)
+ */
+ public PackageRelationship addExternalRelationship(String target, String relationshipType);
+
+ /**
+ * 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)
+ */
+ public PackageRelationship addExternalRelationship(String target, String relationshipType, String id);
+
+ /**
+ * Delete all the relationships attached to this.
+ */
+ public abstract void clearRelationships();
+
+ /**
+ * Delete the relationship specified by its id.
+ *
+ * @param id
+ * The ID identified the part to delete.
+ */
+ public abstract void removeRelationship(String id);
+
+ /**
+ * Retrieve all the relationships attached to this.
+ *
+ * @return This part's relationships.
+ * @throws OpenXML4JException
+ */
+ public abstract PackageRelationshipCollection getRelationships()
+ throws InvalidFormatException, OpenXML4JException;
+
+ /**
+ * Retrieves a package relationship from its id.
+ *
+ * @param id
+ * ID of the package relationship to retrieve.
+ * @return The package relationship
+ */
+ public abstract PackageRelationship getRelationship(String id);
+
+ /**
+ * Retrieve all relationships attached to this part which have the specified
+ * type.
+ *
+ * @param relationshipType
+ * Relationship type filter.
+ * @return All relationships from this part that have the specified type.
+ * @throws InvalidFormatException
+ * If an error occurs while parsing the part.
+ * @throws InvalidOperationException
+ * If the package is open in write only mode.
+ */
+ public abstract PackageRelationshipCollection getRelationshipsByType(
+ String relationshipType) throws InvalidFormatException,
+ IllegalArgumentException, OpenXML4JException;
+
+ /**
+ * Knows if the part have any relationships.
+ *
+ * @return true if the part have at least one relationship else
+ * false.
+ */
+ public abstract boolean hasRelationships();
+
+ /**
+ * Checks if the specified relationship is part of this package part.
+ *
+ * @param rel
+ * The relationship to check.
+ * @return true if the specified relationship exists in this part,
+ * else returns false
+ */
+ @SuppressWarnings("finally")
+ public abstract boolean isRelationshipExists(PackageRelationship rel);
+
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java
new file mode 100755
index 000000000..1d55c7bb7
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java
@@ -0,0 +1,77 @@
+/* ====================================================================
+ 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.InputStream;
+import java.io.OutputStream;
+
+import org.dom4j.Document;
+import org.dom4j.io.OutputFormat;
+import org.dom4j.io.XMLWriter;
+
+public final class StreamHelper {
+
+ private StreamHelper() {
+ // Do nothing
+ }
+
+ /**
+ * Turning the DOM4j object in the specified output stream.
+ *
+ * @param xmlContent
+ * The XML document.
+ * @param outStream
+ * The OutputStream in which the XML document will be written.
+ * @return true if the xml is successfully written in the stream,
+ * else false.
+ */
+ public static boolean saveXmlInStream(Document xmlContent,
+ OutputStream outStream) {
+ try {
+ OutputFormat outformat = OutputFormat.createPrettyPrint();
+ outformat.setEncoding("UTF-8");
+ XMLWriter writer = new XMLWriter(outStream, outformat);
+ writer.write(xmlContent);
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Copy the input stream into the output stream.
+ *
+ * @param inStream
+ * The source stream.
+ * @param outStream
+ * The destination stream.
+ * @return true if the operation succeed, else return false.
+ */
+ public static boolean copyStream(InputStream inStream, OutputStream outStream) {
+ try {
+ byte[] buffer = new byte[1024];
+ int bytesRead;
+ while ((bytesRead = inStream.read(buffer)) >= 0) {
+ outStream.write(buffer, 0, bytesRead);
+ }
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/TargetMode.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/TargetMode.java
new file mode 100755
index 000000000..12a5a55ff
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/TargetMode.java
@@ -0,0 +1,32 @@
+/* ====================================================================
+ 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;
+
+/**
+ * Specifies whether the target of a PackageRelationship is inside or outside a
+ * Package.
+ *
+ * @author Julien Chable
+ * @version 1.0
+ */
+public enum TargetMode {
+ /** The relationship references a resource that is external to the package. */
+ INTERNAL,
+ /** The relationship references a part that is inside the package. */
+ EXTERNAL
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackage.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackage.java
new file mode 100755
index 000000000..f0db2ab28
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackage.java
@@ -0,0 +1,464 @@
+/* ====================================================================
+ 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.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.log4j.Logger;
+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.opc.internal.ContentTypeManager;
+import org.apache.poi.openxml4j.opc.internal.FileHelper;
+import org.apache.poi.openxml4j.opc.internal.MemoryPackagePart;
+import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
+import org.apache.poi.openxml4j.opc.internal.ZipContentTypeManager;
+import org.apache.poi.openxml4j.opc.internal.ZipHelper;
+import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPackagePropertiesMarshaller;
+import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller;
+import org.apache.poi.openxml4j.util.ZipEntrySource;
+import org.apache.poi.openxml4j.util.ZipFileZipEntrySource;
+import org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource;
+
+/**
+ * Physical zip package.
+ *
+ * @author Julien Chable
+ * @version 0.2
+ */
+public final class ZipPackage extends Package {
+
+ private static Logger logger = Logger.getLogger("org.openxml4j");
+
+ /**
+ * Zip archive, as either a file on disk,
+ * or a stream
+ */
+ private final ZipEntrySource zipArchive;
+
+ /**
+ * Constructor. Creates a new ZipPackage.
+ */
+ public ZipPackage() {
+ super(defaultPackageAccess);
+ this.zipArchive = null;
+ }
+
+ /**
+ * Constructor. Operation not supported.
+ *
+ * @param in
+ * Zip input stream to load.
+ * @param access
+ * @throws IllegalArgumentException
+ * If the specified input stream not an instance of
+ * ZipInputStream.
+ */
+ ZipPackage(InputStream in, PackageAccess access) throws IOException {
+ super(access);
+ this.zipArchive = new ZipInputStreamZipEntrySource(
+ new ZipInputStream(in)
+ );
+ }
+
+ /**
+ * Constructor. Opens a Zip based Open XML document.
+ *
+ * @param path
+ * The path of the file to open or create.
+ * @param access
+ * The package access mode.
+ * @throws InvalidFormatException
+ * If the content type part parsing encounters an error.
+ */
+ ZipPackage(String path, PackageAccess access) throws InvalidFormatException {
+ super(access);
+
+ ZipFile zipFile = ZipHelper.openZipFile(path);
+ if (zipFile == null)
+ throw new InvalidOperationException(
+ "Can't open the specified file: '" + path + "'");
+ this.zipArchive = new ZipFileZipEntrySource(zipFile);
+ }
+
+ /**
+ * Retrieves the parts from this package. We assume that the package has not
+ * been yet inspect to retrieve all the parts, this method will open the
+ * archive and look for all parts contain inside it. If the package part
+ * list is not empty, it will be emptied.
+ *
+ * @return All parts contain in this package.
+ * @throws InvalidFormatException
+ * Throws if the package is not valid.
+ */
+ @Override
+ protected PackagePart[] getPartsImpl() throws InvalidFormatException {
+ if (this.partList == null) {
+ // The package has just been created, we create an empty part
+ // list.
+ this.partList = new PackagePartCollection();
+ }
+
+ if (this.zipArchive == null) {
+ return this.partList.values().toArray(
+ new PackagePart[this.partList.values().size()]);
+ } else {
+ // First we need to parse the content type part
+ Enumeration extends ZipEntry> entries = this.zipArchive.getEntries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (entry.getName().equals(
+ ContentTypeManager.CONTENT_TYPES_PART_NAME)) {
+ try {
+ this.contentTypeManager = new ZipContentTypeManager(
+ getZipArchive().getInputStream(entry), this);
+ } catch (IOException e) {
+ throw new InvalidFormatException(e.getMessage());
+ }
+ break;
+ }
+ }
+
+ // At this point, we should have loaded the content type part
+ if (this.contentTypeManager == null) {
+ throw new InvalidFormatException(
+ "Package should contain a content type part [M1.13]");
+ }
+
+ // Now create all the relationships
+ // (Need to create relationships before other
+ // parts, otherwise we might create a part before
+ // its relationship exists, and then it won't tie up)
+ entries = this.zipArchive.getEntries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = (ZipEntry) entries.nextElement();
+ PackagePartName partName = buildPartName(entry);
+ if(partName == null) continue;
+
+ // Only proceed for Relationships at this stage
+ String contentType = contentTypeManager.getContentType(partName);
+ if (contentType != null && contentType.equals(ContentTypes.RELATIONSHIPS_PART)) {
+ try {
+ partList.put(partName, new ZipPackagePart(this, entry,
+ partName, contentType));
+ } catch (InvalidOperationException e) {
+ throw new InvalidFormatException(e.getMessage());
+ }
+ }
+ }
+
+ // Then we can go through all the other parts
+ entries = this.zipArchive.getEntries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = (ZipEntry) entries.nextElement();
+ PackagePartName partName = buildPartName(entry);
+ if(partName == null) continue;
+
+ String contentType = contentTypeManager
+ .getContentType(partName);
+ if (contentType != null && contentType.equals(ContentTypes.RELATIONSHIPS_PART)) {
+ // Already handled
+ }
+ else if (contentType != null) {
+ try {
+ partList.put(partName, new ZipPackagePart(this, entry,
+ partName, contentType));
+ } catch (InvalidOperationException e) {
+ throw new InvalidFormatException(e.getMessage());
+ }
+ } else {
+ throw new InvalidFormatException(
+ "The part "
+ + partName.getURI().getPath()
+ + " does not have any content type ! Rule: Package require content types when retrieving a part from a package. [M.1.14]");
+ }
+ }
+
+ return (ZipPackagePart[]) partList.values().toArray(
+ new ZipPackagePart[partList.size()]);
+ }
+ }
+
+ /**
+ * Builds a PackagePartName for the given ZipEntry,
+ * or null if it's the content types / invalid part
+ */
+ private PackagePartName buildPartName(ZipEntry entry) {
+ try {
+ // We get an error when we parse [Content_Types].xml
+ // because it's not a valid URI.
+ if (entry.getName().equals(
+ ContentTypeManager.CONTENT_TYPES_PART_NAME)) {
+ return null;
+ } else {
+ return PackagingURIHelper.createPartName(ZipHelper
+ .getOPCNameFromZipItemName(entry.getName()));
+ }
+ } catch (Exception e) {
+ // We assume we can continue, even in degraded mode ...
+ logger.warn("Entry "
+ + entry.getName()
+ + " is not valid, so this part won't be add to the package.");
+ return null;
+ }
+ }
+
+ /**
+ * Create a new MemoryPackagePart from the specified URI and content type
+ *
+ *
+ * aram partName The part URI.
+ *
+ * @param contentType
+ * The part content type.
+ * @return The newly created zip package part, else null.
+ */
+ @Override
+ protected PackagePart createPartImpl(PackagePartName partName,
+ String contentType, boolean loadRelationships) {
+ if (contentType == null)
+ throw new IllegalArgumentException("contentType");
+
+ if (partName == null)
+ throw new IllegalArgumentException("partName");
+
+ try {
+ return new MemoryPackagePart(this, partName, contentType,
+ loadRelationships);
+ } catch (InvalidFormatException e) {
+ System.err.println(e);
+ return null;
+ }
+ }
+
+ /**
+ * Delete a part from the package
+ *
+ * @throws IllegalArgumentException
+ * Throws if the part URI is nulll or invalid.
+ */
+ @Override
+ protected void removePartImpl(PackagePartName partName) {
+ if (partName == null)
+ throw new IllegalArgumentException("partUri");
+ }
+
+ /**
+ * Flush the package. Do nothing.
+ */
+ @Override
+ protected void flushImpl() {
+ // Do nothing
+ }
+
+ /**
+ * Close and save the package.
+ *
+ * @see #close()
+ */
+ @Override
+ protected void closeImpl() throws IOException {
+ // Flush the package
+ flush();
+
+ // Save the content
+ if (this.originalPackagePath != null
+ && !"".equals(this.originalPackagePath)) {
+ File targetFile = new File(this.originalPackagePath);
+ if (targetFile.exists()) {
+ // Case of a package previously open
+
+ File tempFile = File.createTempFile(
+ generateTempFileName(FileHelper
+ .getDirectory(targetFile)), ".tmp");
+
+ // Save the final package to a temporary file
+ try {
+ save(tempFile);
+ this.zipArchive.close(); // Close the zip archive to be
+ // able to delete it
+ FileHelper.copyFile(tempFile, targetFile);
+ } finally {
+ // Either the save operation succeed or not, we delete the
+ // temporary file
+ if (!tempFile.delete()) {
+ logger
+ .warn("The temporary file: '"
+ + targetFile.getAbsolutePath()
+ + "' cannot be deleted ! Make sure that no other application use it.");
+ }
+ }
+ } else {
+ throw new InvalidOperationException(
+ "Can't close a package not previously open with the open() method !");
+ }
+ }
+ }
+
+ /**
+ * Create a unique identifier to be use as a temp file name.
+ *
+ * @return A unique identifier use to be use as a temp file name.
+ */
+ private synchronized String generateTempFileName(File directory) {
+ File tmpFilename;
+ do {
+ tmpFilename = new File(directory.getAbsoluteFile() + File.separator
+ + "OpenXML4J" + System.nanoTime());
+ } while (tmpFilename.exists());
+ return FileHelper.getFilename(tmpFilename.getAbsoluteFile());
+ }
+
+ /**
+ * Close the package without saving the document. Discard all the changes
+ * made to this package.
+ */
+ @Override
+ protected void revertImpl() {
+ try {
+ if (this.zipArchive != null)
+ this.zipArchive.close();
+ } catch (IOException e) {
+ // Do nothing, user dont have to know
+ }
+ }
+
+ /**
+ * Implement the getPart() method to retrieve a part from its URI in the
+ * current package
+ *
+ *
+ * @see #getPart(URI)
+ */
+ @Override
+ protected PackagePart getPartImpl(PackagePartName partName) {
+ if (partList.containsKey(partName)) {
+ return partList.get(partName);
+ }
+ return null;
+ }
+
+ /**
+ * Save this package into the specified stream
+ *
+ *
+ * @param outputStream
+ * The stream use to save this package.
+ *
+ * @see #save(OutputStream)
+ * @see #saveInZip(ZipOutputStream)
+ */
+ @Override
+ public void saveImpl(OutputStream outputStream) {
+ // Check that the document was open in write mode
+ throwExceptionIfReadOnly();
+ ZipOutputStream zos = null;
+
+ try {
+ if (!(outputStream instanceof ZipOutputStream))
+ zos = new ZipOutputStream(outputStream);
+ else
+ zos = (ZipOutputStream) outputStream;
+
+ // If the core properties part does not exist in the part list,
+ // we save it as well
+ if (this.getPartsByRelationshipType(
+ PackageRelationshipTypes.CORE_PROPERTIES).size() == 0) {
+ logger.debug("Save core properties part");
+
+ // We have to save the core properties part ...
+ new ZipPackagePropertiesMarshaller().marshall(
+ this.packageProperties, zos);
+ // ... and to add its relationship ...
+ this.relationships.addRelationship(this.packageProperties
+ .getPartName().getURI(), TargetMode.INTERNAL,
+ PackageRelationshipTypes.CORE_PROPERTIES, null);
+ // ... and the content if it has not been added yet.
+ if (!this.contentTypeManager
+ .isContentTypeRegister(ContentTypes.CORE_PROPERTIES_PART)) {
+ this.contentTypeManager.addContentType(
+ this.packageProperties.getPartName(),
+ ContentTypes.CORE_PROPERTIES_PART);
+ }
+ }
+
+ // Save package relationships part.
+ logger.debug("Save package relationships");
+ ZipPartMarshaller.marshallRelationshipPart(this.getRelationships(),
+ PackagingURIHelper.PACKAGE_RELATIONSHIPS_ROOT_PART_NAME,
+ zos);
+
+ // Save content type part.
+ logger.debug("Save content types part");
+ this.contentTypeManager.save(zos);
+
+ // Save parts.
+ for (PackagePart part : getParts()) {
+ // If the part is a relationship part, we don't save it, it's
+ // the source part that will do the job.
+ if (part.isRelationshipPart())
+ continue;
+
+ logger.debug("Save part '"
+ + ZipHelper.getZipItemNameFromOPCName(part
+ .getPartName().getName()) + "'");
+ PartMarshaller marshaller = partMarshallers
+ .get(part.contentType);
+ if (marshaller != null) {
+ if (!marshaller.marshall(part, zos)) {
+ throw new OpenXML4JException(
+ "The part "
+ + part.getPartName().getURI()
+ + " fail to be saved in the stream with marshaller "
+ + marshaller);
+ }
+ } else {
+ if (!defaultPartMarshaller.marshall(part, zos))
+ throw new OpenXML4JException(
+ "The part "
+ + part.getPartName().getURI()
+ + " fail to be saved in the stream with marshaller "
+ + defaultPartMarshaller);
+ }
+ }
+ zos.close();
+ } catch (Exception e) {
+ logger
+ .error("Fail to save: an error occurs while saving the package : "
+ + e.getMessage());
+ }
+ }
+
+ /**
+ * Get the zip archive
+ *
+ * @return The zip archive.
+ */
+ public ZipEntrySource getZipArchive() {
+ return zipArchive;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackagePart.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackagePart.java
new file mode 100755
index 000000000..2c05898d1
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackagePart.java
@@ -0,0 +1,135 @@
+/* ====================================================================
+ 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+
+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.opc.internal.marshallers.ZipPartMarshaller;
+
+/**
+ * Zip implementation of a PackagePart.
+ *
+ * @author Julien Chable
+ * @version 1.0
+ * @see PackagePart
+ */
+public class ZipPackagePart extends PackagePart {
+
+ /**
+ * The zip entry corresponding to this part.
+ */
+ private ZipEntry zipEntry;
+
+ /**
+ * Constructor.
+ *
+ * @param container
+ * The container package.
+ * @param partName
+ * Part name.
+ * @param contentType
+ * Content type.
+ * @throws InvalidFormatException
+ * Throws if the content of this part invalid.
+ */
+ public ZipPackagePart(Package container, PackagePartName partName,
+ String contentType) throws InvalidFormatException {
+ super(container, partName, contentType);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param container
+ * The container package.
+ * @param zipEntry
+ * The zip entry corresponding to this part.
+ * @param partName
+ * The part name.
+ * @param contentType
+ * Content type.
+ * @throws InvalidFormatException
+ * Throws if the content of this part is invalid.
+ */
+ public ZipPackagePart(Package container, ZipEntry zipEntry,
+ PackagePartName partName, String contentType)
+ throws InvalidFormatException {
+ super(container, partName, contentType);
+ this.zipEntry = zipEntry;
+ }
+
+ /**
+ * Get the zip entry of this part.
+ *
+ * @return The zip entry in the zip structure coresponding to this part.
+ */
+ public ZipEntry getZipArchive() {
+ return zipEntry;
+ }
+
+ /**
+ * Implementation of the getInputStream() which return the inputStream of
+ * this part zip entry.
+ *
+ * @return Input stream of this part zip entry.
+ */
+ @Override
+ protected InputStream getInputStreamImpl() throws IOException {
+ // We use the getInputStream() method from java.util.zip.ZipFile
+ // class which return an InputStream to this part zip entry.
+ return ((ZipPackage) container).getZipArchive()
+ .getInputStream(zipEntry);
+ }
+
+ /**
+ * Implementation of the getOutputStream(). Return null. Normally
+ * will never be called since the MemoryPackage is use instead.
+ *
+ * @return null
+ */
+ @Override
+ protected OutputStream getOutputStreamImpl() {
+ return null;
+ }
+
+ @Override
+ public boolean save(OutputStream os) throws OpenXML4JException {
+ return new ZipPartMarshaller().marshall(this, os);
+ }
+
+ @Override
+ public boolean load(InputStream ios) throws InvalidFormatException {
+ throw new InvalidOperationException("Method not implemented !");
+ }
+
+ @Override
+ public void close() {
+ throw new InvalidOperationException("Method not implemented !");
+ }
+
+ @Override
+ public void flush() {
+ throw new InvalidOperationException("Method not implemented !");
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentType.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentType.java
new file mode 100755
index 000000000..18a6ca4ab
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentType.java
@@ -0,0 +1,224 @@
+/* ====================================================================
+ 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.internal;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Hashtable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+
+/**
+ * Represents a immutable MIME ContentType value (RFC 2616 �3.7)
+ *
+ * media-type = type "/" subtype *( ";" parameter ) type = tokentrue
if the specified content type is already
+ * register, then false
.
+ */
+ public boolean isContentTypeRegister(String contentType) {
+ if (contentType == null)
+ throw new IllegalArgumentException("contentType");
+
+ return (this.defaultContentType.values().contains(contentType) || (this.overrideContentType != null && this.overrideContentType
+ .values().contains(contentType)));
+ }
+
+ /**
+ * Get the content type for the specified part, if any.
+ *
+ * Rule [M2.9]: To get the content type of a part, the package implementer
+ * shall perform the steps described in��9.1.2.4:
+ *
+ * 1. Compare the part name with the values specified for the PartName
+ * attribute of the Override elements. The comparison shall be
+ * case-insensitive ASCII.
+ *
+ * 2. If there is an Override element with a matching PartName attribute,
+ * return the value of its ContentType attribute. No further action is
+ * required.
+ *
+ * 3. If there is no Override element with a matching PartName attribute,
+ * then a. Get the extension from the part name by taking the substring to
+ * the right of the rightmost occurrence of the dot character (.) from the
+ * rightmost segment. b. Check the Default elements of the Content Types
+ * stream, comparing the extension with the value of the Extension
+ * attribute. The comparison shall be case-insensitive ASCII.
+ *
+ * 4. If there is a Default element with a matching Extension attribute,
+ * return the value of its ContentType attribute. No further action is
+ * required.
+ *
+ * 5. If neither Override nor Default elements with matching attributes are
+ * found for the specified part name, the implementation shall not map this
+ * part name to a part.
+ *
+ * @param partUri
+ * The URI part to check.
+ * @return The content type associated with the URI (in case of an override
+ * content type) or the extension (in case of default content type),
+ * else null
.
+ *
+ * @exception OpenXML4JRuntimeException
+ * Throws if the content type manager is not able to find the
+ * content from an existing part.
+ */
+ public String getContentType(PackagePartName partName) {
+ if (partName == null)
+ throw new IllegalArgumentException("partName");
+
+ if ((this.overrideContentType != null)
+ && this.overrideContentType.containsKey(partName))
+ return this.overrideContentType.get(partName);
+
+ String extension = partName.getExtension().toLowerCase();
+ if (this.defaultContentType.containsKey(extension))
+ return this.defaultContentType.get(extension);
+
+ /*
+ * [M2.4] : The package implementer shall require that the Content Types
+ * stream contain one of the following for every part in the package:
+ * One matching Default element, One matching Override element, Both a
+ * matching Default element and a matching Override element, in which
+ * case the Override element takes precedence.
+ */
+ if (this.container != null && this.container.getPart(partName) != null) {
+ throw new OpenXML4JRuntimeException(
+ "Rule M2.4 exception : this error should NEVER happen, if so please send a mail to the developers team, thanks !");
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Clear all content types.
+ */
+ public void clearAll() {
+ this.defaultContentType.clear();
+ if (this.overrideContentType != null)
+ this.overrideContentType.clear();
+ }
+
+ /**
+ * Clear all override content types.
+ *
+ */
+ public void clearOverrideContentTypes() {
+ if (this.overrideContentType != null)
+ this.overrideContentType.clear();
+ }
+
+ /**
+ * Parse the content types part.
+ *
+ * @throws InvalidFormatException
+ * Throws if the content type doesn't exist or the XML format is
+ * invalid.
+ */
+ private void parseContentTypesFile(InputStream in)
+ throws InvalidFormatException {
+ try {
+ SAXReader xmlReader = new SAXReader();
+ Document xmlContentTypetDoc = xmlReader.read(in);
+
+ // Default content types
+ List defaultTypes = xmlContentTypetDoc.getRootElement().elements(
+ DEFAULT_TAG_NAME);
+ Iterator elementIteratorDefault = defaultTypes.iterator();
+ while (elementIteratorDefault.hasNext()) {
+ Element element = (Element) elementIteratorDefault.next();
+ String extension = element.attribute(EXTENSION_ATTRIBUTE_NAME)
+ .getValue();
+ String contentType = element.attribute(
+ CONTENT_TYPE_ATTRIBUTE_NAME).getValue();
+ addDefaultContentType(extension, contentType);
+ }
+
+ // Overriden content types
+ List overrideTypes = xmlContentTypetDoc.getRootElement().elements(
+ OVERRIDE_TAG_NAME);
+ Iterator elementIteratorOverride = overrideTypes.iterator();
+ while (elementIteratorOverride.hasNext()) {
+ Element element = (Element) elementIteratorOverride.next();
+ URI uri = new URI(element.attribute(PART_NAME_ATTRIBUTE_NAME)
+ .getValue());
+ PackagePartName partName = PackagingURIHelper
+ .createPartName(uri);
+ String contentType = element.attribute(
+ CONTENT_TYPE_ATTRIBUTE_NAME).getValue();
+ addOverrideContentType(partName, contentType);
+ }
+ } catch (URISyntaxException urie) {
+ throw new InvalidFormatException(urie.getMessage());
+ } catch (DocumentException e) {
+ throw new InvalidFormatException(e.getMessage());
+ }
+ }
+
+ /**
+ * Save the contents type part.
+ *
+ * @param outStream
+ * The output stream use to save the XML content of the content
+ * types part.
+ * @return true if the operation success, else false.
+ */
+ public boolean save(OutputStream outStream) {
+ Document xmlOutDoc = DocumentHelper.createDocument();
+
+ // Building namespace
+ Namespace dfNs = Namespace.get("", TYPES_NAMESPACE_URI);
+ Element typesElem = xmlOutDoc
+ .addElement(new QName(TYPES_TAG_NAME, dfNs));
+
+ // Adding default types
+ for (Entrytrue
.
+ */
+ public boolean marshall(PackagePart part, OutputStream out)
+ throws OpenXML4JException {
+ if (!(part instanceof PackagePropertiesPart))
+ throw new IllegalArgumentException(
+ "'part' must be a PackagePropertiesPart instance.");
+ propsPart = (PackagePropertiesPart) part;
+
+ // Configure the document
+ xmlDoc = DocumentHelper.createDocument();
+ Element rootElem = xmlDoc.addElement(new QName("coreProperties",
+ namespaceCoreProperties));
+ rootElem.addNamespace("cp", PackagePropertiesPart.NAMESPACE_CP_URI);
+ rootElem.addNamespace("dc", PackagePropertiesPart.NAMESPACE_DC_URI);
+ rootElem.addNamespace("dcterms",
+ PackagePropertiesPart.NAMESPACE_DCTERMS_URI);
+ rootElem.addNamespace("xsi", PackagePropertiesPart.NAMESPACE_XSI_URI);
+
+ addCategory();
+ addContentStatus();
+ addContentType();
+ addCreated();
+ addCreator();
+ addDescription();
+ addIdentifier();
+ addKeywords();
+ addLanguage();
+ addLastModifiedBy();
+ addLastPrinted();
+ addModified();
+ addRevision();
+ addSubject();
+ addTitle();
+ addVersion();
+ return true;
+ }
+
+ /**
+ * Add category property element if needed.
+ */
+ private void addCategory() {
+ if (!propsPart.getCategoryProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_CATEGORY, namespaceCoreProperties));
+ if (elem == null) {
+ // Missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_CATEGORY, namespaceCoreProperties));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getCategoryProperty().getValue());
+ }
+
+ /**
+ * Add content status property element if needed.
+ */
+ private void addContentStatus() {
+ if (!propsPart.getContentStatusProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_CONTENT_STATUS, namespaceCoreProperties));
+ if (elem == null) {
+ // Missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_CONTENT_STATUS, namespaceCoreProperties));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getContentStatusProperty().getValue());
+ }
+
+ /**
+ * Add content type property element if needed.
+ */
+ private void addContentType() {
+ if (!propsPart.getContentTypeProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_CONTENT_TYPE, namespaceCoreProperties));
+ if (elem == null) {
+ // Missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_CONTENT_TYPE, namespaceCoreProperties));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getContentTypeProperty().getValue());
+ }
+
+ /**
+ * Add created property element if needed.
+ */
+ private void addCreated() {
+ if (!propsPart.getCreatedProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_CREATED, namespaceDcTerms));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_CREATED, namespaceDcTerms));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addAttribute(new QName("type", namespaceXSI), "dcterms:W3CDTF");
+ elem.addText(propsPart.getCreatedPropertyString());
+ }
+
+ /**
+ * Add creator property element if needed.
+ */
+ private void addCreator() {
+ if (!propsPart.getCreatorProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_CREATOR, namespaceDC));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_CREATOR, namespaceDC));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getCreatorProperty().getValue());
+ }
+
+ /**
+ * Add description property element if needed.
+ */
+ private void addDescription() {
+ if (!propsPart.getDescriptionProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_DESCRIPTION, namespaceDC));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_DESCRIPTION, namespaceDC));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getDescriptionProperty().getValue());
+ }
+
+ /**
+ * Add identifier property element if needed.
+ */
+ private void addIdentifier() {
+ if (!propsPart.getIdentifierProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_IDENTIFIER, namespaceDC));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_IDENTIFIER, namespaceDC));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getIdentifierProperty().getValue());
+ }
+
+ /**
+ * Add keywords property element if needed.
+ */
+ private void addKeywords() {
+ if (!propsPart.getKeywordsProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_KEYWORDS, namespaceCoreProperties));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_KEYWORDS, namespaceCoreProperties));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getKeywordsProperty().getValue());
+ }
+
+ /**
+ * Add language property element if needed.
+ */
+ private void addLanguage() {
+ if (!propsPart.getLanguageProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_LANGUAGE, namespaceDC));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_LANGUAGE, namespaceDC));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getLanguageProperty().getValue());
+ }
+
+ /**
+ * Add 'last modified by' property if needed.
+ */
+ private void addLastModifiedBy() {
+ if (!propsPart.getLastModifiedByProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_LAST_MODIFIED_BY, namespaceCoreProperties));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement()
+ .addElement(
+ new QName(KEYWORD_LAST_MODIFIED_BY,
+ namespaceCoreProperties));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getLastModifiedByProperty().getValue());
+ }
+
+ /**
+ * Add 'last printed' property if needed.
+ *
+ */
+ private void addLastPrinted() {
+ if (!propsPart.getLastPrintedProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_LAST_PRINTED, namespaceCoreProperties));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_LAST_PRINTED, namespaceCoreProperties));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getLastPrintedPropertyString());
+ }
+
+ /**
+ * Add modified property element if needed.
+ */
+ private void addModified() {
+ if (!propsPart.getModifiedProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_MODIFIED, namespaceDcTerms));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_MODIFIED, namespaceDcTerms));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addAttribute(new QName("type", namespaceXSI), "dcterms:W3CDTF");
+ elem.addText(propsPart.getModifiedPropertyString());
+ }
+
+ /**
+ * Add revision property if needed.
+ */
+ private void addRevision() {
+ if (!propsPart.getRevisionProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_REVISION, namespaceCoreProperties));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_REVISION, namespaceCoreProperties));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getRevisionProperty().getValue());
+ }
+
+ /**
+ * Add subject property if needed.
+ */
+ private void addSubject() {
+ if (!propsPart.getSubjectProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_SUBJECT, namespaceDC));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_SUBJECT, namespaceDC));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getSubjectProperty().getValue());
+ }
+
+ /**
+ * Add title property if needed.
+ */
+ private void addTitle() {
+ if (!propsPart.getTitleProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_TITLE, namespaceDC));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_TITLE, namespaceDC));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getTitleProperty().getValue());
+ }
+
+ private void addVersion() {
+ if (!propsPart.getVersionProperty().hasValue())
+ return;
+
+ Element elem = xmlDoc.getRootElement().element(
+ new QName(KEYWORD_VERSION, namespaceCoreProperties));
+ if (elem == null) {
+ // missing, we add it
+ elem = xmlDoc.getRootElement().addElement(
+ new QName(KEYWORD_VERSION, namespaceCoreProperties));
+ } else {
+ elem.clearContent();// clear the old value
+ }
+ elem.addText(propsPart.getVersionProperty().getValue());
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/marshallers/ZipPackagePropertiesMarshaller.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/marshallers/ZipPackagePropertiesMarshaller.java
new file mode 100755
index 000000000..39e8fa3f3
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/marshallers/ZipPackagePropertiesMarshaller.java
@@ -0,0 +1,64 @@
+/* ====================================================================
+ 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.internal.marshallers;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.StreamHelper;
+import org.apache.poi.openxml4j.opc.internal.ZipHelper;
+
+/**
+ * Package core properties marshaller specialized for zipped package.
+ *
+ * @author Julien Chable
+ * @version 1.0
+ */
+public class ZipPackagePropertiesMarshaller extends PackagePropertiesMarshaller {
+
+ @Override
+ public boolean marshall(PackagePart part, OutputStream out)
+ throws OpenXML4JException {
+ if (!(out instanceof ZipOutputStream)) {
+ throw new IllegalArgumentException("ZipOutputStream expected!");
+ }
+ ZipOutputStream zos = (ZipOutputStream) out;
+
+ // Saving the part in the zip file
+ ZipEntry ctEntry = new ZipEntry(ZipHelper
+ .getZipItemNameFromOPCName(part.getPartName().getURI()
+ .toString()));
+ try {
+ // Save in ZIP
+ zos.putNextEntry(ctEntry); // Add entry in ZIP
+ super.marshall(part, out); // Marshall the properties inside a XML
+ // Document
+ if (!StreamHelper.saveXmlInStream(xmlDoc, out)) {
+ return false;
+ }
+ zos.closeEntry();
+ } catch (IOException e) {
+ throw new OpenXML4JException(e.getLocalizedMessage());
+ }
+ return true;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/marshallers/ZipPartMarshaller.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/marshallers/ZipPartMarshaller.java
new file mode 100755
index 000000000..a54bef574
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/marshallers/ZipPartMarshaller.java
@@ -0,0 +1,193 @@
+/* ====================================================================
+ 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.internal.marshallers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.log4j.Logger;
+import org.dom4j.Document;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.Namespace;
+import org.dom4j.QName;
+import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+import org.apache.poi.openxml4j.opc.PackageNamespaces;
+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.PackagingURIHelper;
+import org.apache.poi.openxml4j.opc.StreamHelper;
+import org.apache.poi.openxml4j.opc.TargetMode;
+import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
+import org.apache.poi.openxml4j.opc.internal.ZipHelper;
+
+/**
+ * Zip part marshaller. This marshaller is use to save any part in a zip stream.
+ *
+ * @author Julien Chable
+ * @version 0.1
+ */
+public class ZipPartMarshaller implements PartMarshaller {
+ private static Logger logger = Logger.getLogger("org.openxml4j");
+
+ /**
+ * Save the specified part.
+ *
+ * @throws OpenXML4JException
+ * Throws if an internal exception is thrown.
+ */
+ public boolean marshall(PackagePart part, OutputStream os)
+ throws OpenXML4JException {
+ if (!(os instanceof ZipOutputStream)) {
+ logger.error("Unexpected class " + os.getClass().getName());
+ throw new OpenXML4JException("ZipOutputStream expected !");
+ // Normally should happen only in developpement phase, so just throw
+ // exception
+ }
+
+ ZipOutputStream zos = (ZipOutputStream) os;
+ ZipEntry partEntry = new ZipEntry(ZipHelper
+ .getZipItemNameFromOPCName(part.getPartName().getURI()
+ .getPath()));
+ try {
+ // Create next zip entry
+ zos.putNextEntry(partEntry);
+
+ // Saving data in the ZIP file
+ InputStream ins = part.getInputStream();
+ byte[] buff = new byte[ZipHelper.READ_WRITE_FILE_BUFFER_SIZE];
+ while (ins.available() > 0) {
+ int resultRead = ins.read(buff);
+ if (resultRead == -1) {
+ // End of file reached
+ break;
+ } else {
+ zos.write(buff, 0, resultRead);
+ }
+ }
+ zos.closeEntry();
+ } catch (IOException ioe) {
+ logger.error("Cannot write: " + part.getPartName() + ": in ZIP",
+ ioe);
+ return false;
+ }
+
+ // Saving relationship part
+ if (part.hasRelationships()) {
+ PackagePartName relationshipPartName = PackagingURIHelper
+ .getRelationshipPartName(part.getPartName());
+
+ marshallRelationshipPart(part.getRelationships(),
+ relationshipPartName, zos);
+
+ }
+ return true;
+ }
+
+ /**
+ * Save relationships into the part.
+ *
+ * @param rels
+ * The relationships collection to marshall.
+ * @param relPartURI
+ * Part name of the relationship part to marshall.
+ * @param zos
+ * Zip output stream in which to save the XML content of the
+ * relationships serialization.
+ */
+ public static boolean marshallRelationshipPart(
+ PackageRelationshipCollection rels, PackagePartName relPartName,
+ ZipOutputStream zos) {
+ // Building xml
+ Document xmlOutDoc = DocumentHelper.createDocument();
+ // make something like