From fed01cb1ad4eea07ee7cdb232e65aa6271bdef7f Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Tue, 12 Aug 2014 07:19:51 +0000 Subject: [PATCH 1/2] Fix bug #56814 - Switch from dom4j to JAXP git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1617428 13f79535-47bb-0310-9956-ffa450edef68 --- .classpath | 1 - build.xml | 12 - legal/LICENSE | 44 --- legal/NOTICE | 3 - maven/poi-ooxml.pom | 5 - sonar/ooxml/pom.xml | 6 - .../apache/poi/ss/excelant/ExcelAntTask.java | 2 +- .../poi/openxml4j/opc/Configuration.java | 2 +- .../opc/PackageRelationshipCollection.java | 31 +- .../poi/openxml4j/opc/StreamHelper.java | 63 +++- .../opc/internal/ContentTypeManager.java | 79 ++--- .../opc/internal/PackagePropertiesPart.java | 2 - .../opc/internal/ZipContentTypeManager.java | 17 +- .../PackagePropertiesMarshaller.java | 318 +++++------------- .../marshallers/ZipPartMarshaller.java | 30 +- .../PackagePropertiesUnmarshaller.java | 221 ++++-------- .../org/apache/poi/util/DocumentHelper.java | 60 ++++ .../java/org/apache/poi/util/SAXHelper.java | 64 ++-- .../apache/poi/openxml4j/opc/TestPackage.java | 72 ++-- 19 files changed, 388 insertions(+), 644 deletions(-) create mode 100644 src/ooxml/java/org/apache/poi/util/DocumentHelper.java diff --git a/.classpath b/.classpath index 286f8badad..a7dc0d8fc5 100644 --- a/.classpath +++ b/.classpath @@ -19,7 +19,6 @@ - diff --git a/build.xml b/build.xml index 6704bd03fd..3dbf0a81b8 100644 --- a/build.xml +++ b/build.xml @@ -148,8 +148,6 @@ under the License. - - @@ -221,7 +219,6 @@ under the License. - @@ -251,7 +248,6 @@ under the License. - @@ -429,7 +425,6 @@ under the License. - @@ -440,10 +435,6 @@ under the License. - - - - @@ -1288,7 +1279,6 @@ under the License. - @@ -1316,7 +1306,6 @@ under the License. - @@ -1449,7 +1438,6 @@ under the License. - diff --git a/legal/LICENSE b/legal/LICENSE index bd3a24b84c..dd2cbd5fbc 100644 --- a/legal/LICENSE +++ b/legal/LICENSE @@ -229,50 +229,6 @@ Office Open XML schemas (ooxml-schemas-1.1.jar) [5] http://www.ecma-international.org/publications/files/ECMA-ST/Ecma%20PATENT/Patent%20statements%20ok/ECMA-376%20Adobe%20Patent%20Declaration.pdf -DOM4J library (dom4j-1.6.1.jar) - - Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved. - - Redistribution and use of this software and associated documentation - ("Software"), with or without modification, are permitted provided - that the following conditions are met: - - 1. Redistributions of source code must retain copyright - statements and notices. Redistributions must also contain a - copy of this document. - - 2. Redistributions in binary form must reproduce the - above copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - - 3. The name "DOM4J" must not be used to endorse or promote - products derived from this Software without prior written - permission of MetaStuff, Ltd. For written permission, - please contact dom4j-info@metastuff.com. - - 4. Products derived from this Software may not be called "DOM4J" - nor may "DOM4J" appear in their names without prior written - permission of MetaStuff, Ltd. DOM4J is a registered - trademark of MetaStuff, Ltd. - - 5. Due credit should be given to the DOM4J Project - - http://www.dom4j.org - - THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT - NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL - METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - OF THE POSSIBILITY OF SUCH DAMAGE. - - JUnit test library (junit-4.11.jar) Common Public License - v 1.0 diff --git a/legal/NOTICE b/legal/NOTICE index 52251a3388..e256293db9 100644 --- a/legal/NOTICE +++ b/legal/NOTICE @@ -4,9 +4,6 @@ Copyright 2003-2014 The Apache Software Foundation This product includes software developed by The Apache Software Foundation (http://www.apache.org/). -This product contains the DOM4J library (http://www.dom4j.org). -Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved. - This product contains parts that were originally based on software from BEA. Copyright (c) 2000-2003, BEA Systems, . diff --git a/maven/poi-ooxml.pom b/maven/poi-ooxml.pom index 3f32c8cab7..28195b5510 100644 --- a/maven/poi-ooxml.pom +++ b/maven/poi-ooxml.pom @@ -69,10 +69,5 @@ poi-ooxml-schemas @VERSION@ - - dom4j - dom4j - 1.6.1 - diff --git a/sonar/ooxml/pom.xml b/sonar/ooxml/pom.xml index bc1feabb4d..6cc06f0eae 100644 --- a/sonar/ooxml/pom.xml +++ b/sonar/ooxml/pom.xml @@ -116,12 +116,6 @@ 2.3.0 - - dom4j - dom4j - 1.6.1 - - junit diff --git a/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTask.java b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTask.java index e79a30bd1a..23798e499e 100644 --- a/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTask.java +++ b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTask.java @@ -179,7 +179,7 @@ public class ExcelAntTask extends Task { throw new BuildException( "The for must include poi.jar and poi-ooxml.jar " + "if not in Ant's own classpath. Processing .xlsx spreadsheets requires " + - "additional poi-ooxml-schemas.jar, xmlbeans.jar and dom4j.jar" , + "additional poi-ooxml-schemas.jar, xmlbeans.jar" , e, getLocation()); } diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/Configuration.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/Configuration.java index 40141befc8..9109f28f30 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/Configuration.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/Configuration.java @@ -21,7 +21,7 @@ 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 ? + * TODO xml syntax checking is not done with JAXP by default -> remove the schema or do it ? * * @author CDubettier, Julen Chable * @version 1.0 diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java index 6adc737926..4867810a5c 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java @@ -27,9 +27,10 @@ import org.apache.poi.openxml4j.exceptions.InvalidOperationException; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; import org.apache.poi.util.SAXHelper; -import org.dom4j.Attribute; -import org.dom4j.Document; -import org.dom4j.Element; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; /** * Represents a collection of PackageRelationship elements that are owned by a @@ -313,22 +314,19 @@ public final class PackageRelationshipCollection implements Document xmlRelationshipsDoc = SAXHelper.readSAXDocument(relPart.getInputStream()); // Browse default types - Element root = xmlRelationshipsDoc.getRootElement(); + Element root = xmlRelationshipsDoc.getDocumentElement(); // Check OPC compliance M4.1 rule boolean fCorePropertiesRelationship = false; - @SuppressWarnings("unchecked") - Iterator iter = (Iterator) - root.elementIterator(PackageRelationship.RELATIONSHIP_TAG_NAME); - while (iter.hasNext()) { - Element element = iter.next(); + NodeList nodeList = root.getElementsByTagName(PackageRelationship.RELATIONSHIP_TAG_NAME); + int nodeCount = nodeList.getLength(); + for (int i = 0; i < nodeCount; i++) { + Element element = (Element)nodeList.item(i); // Relationship ID - String id = element.attribute( - PackageRelationship.ID_ATTRIBUTE_NAME).getValue(); + String id = element.getAttribute(PackageRelationship.ID_ATTRIBUTE_NAME); // Relationship type - String type = element.attribute( - PackageRelationship.TYPE_ATTRIBUTE_NAME).getValue(); + String type = element.getAttribute(PackageRelationship.TYPE_ATTRIBUTE_NAME); /* Check OPC Compliance */ // Check Rule M4.1 @@ -342,8 +340,7 @@ public final class PackageRelationshipCollection implements /* End OPC Compliance */ // TargetMode (default value "Internal") - Attribute targetModeAttr = element - .attribute(PackageRelationship.TARGET_MODE_ATTRIBUTE_NAME); + Attr targetModeAttr = element.getAttributeNode(PackageRelationship.TARGET_MODE_ATTRIBUTE_NAME); TargetMode targetMode = TargetMode.INTERNAL; if (targetModeAttr != null) { targetMode = targetModeAttr.getValue().toLowerCase() @@ -353,9 +350,7 @@ public final class PackageRelationshipCollection implements // Target converted in URI URI target = PackagingURIHelper.toURI("http://invalid.uri"); // dummy url - String value = element.attribute( - PackageRelationship.TARGET_ATTRIBUTE_NAME) - .getValue(); + String value = element.getAttribute(PackageRelationship.TARGET_ATTRIBUTE_NAME); try { // when parsing of the given uri fails, we can either // ignore this relationship, which leads to IllegalStateException diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java index a02264ea68..860d711590 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java @@ -17,21 +17,36 @@ package org.apache.poi.openxml4j.opc; +import java.io.FilterOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import org.dom4j.Document; -import org.dom4j.io.OutputFormat; -import org.dom4j.io.XMLWriter; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; public final class StreamHelper { private StreamHelper() { // Do nothing } + + private static final TransformerFactory transformerFactory = TransformerFactory.newInstance(); + + private static synchronized Transformer getIdentityTransformer() throws TransformerException { + return transformerFactory.newTransformer(); + } /** - * Turning the DOM4j object in the specified output stream. + * Save the document object in the specified output stream. * * @param xmlContent * The XML document. @@ -40,18 +55,34 @@ public final class StreamHelper { * @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; - } + public static boolean saveXmlInStream(Document xmlContent, + OutputStream outStream) { + try { + Transformer trans = getIdentityTransformer(); + Source xmlSource = new DOMSource(xmlContent); + // prevent close of stream by transformer: + Result outputTarget = new StreamResult(new FilterOutputStream( + outStream) { + @Override + public void write(byte b[], int off, int len) + throws IOException { + out.write(b, off, len); + } + + @Override + public void close() throws IOException { + out.flush(); // only flush, don't close! + } + }); + trans.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + trans.setOutputProperty(OutputKeys.INDENT, "yes"); + trans.setOutputProperty(OutputKeys.STANDALONE, "yes"); + trans.transform(xmlSource, outputTarget); + } catch (TransformerException e) { + return false; + } + return true; + } /** * Copy the input stream into the output stream. diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java index 2c3b97a7f4..7478590a29 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java @@ -17,12 +17,11 @@ package org.apache.poi.openxml4j.opc.internal; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; -import java.util.Iterator; -import java.util.List; import java.util.Map.Entry; import java.util.TreeMap; @@ -33,13 +32,12 @@ import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackagePartName; import org.apache.poi.openxml4j.opc.PackagingURIHelper; +import org.apache.poi.util.DocumentHelper; import org.apache.poi.util.SAXHelper; -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.DocumentHelper; -import org.dom4j.Element; -import org.dom4j.Namespace; -import org.dom4j.QName; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; /** * Manage package content types ([Content_Types].xml part). @@ -376,38 +374,33 @@ public abstract class ContentTypeManager { Document xmlContentTypetDoc = SAXHelper.readSAXDocument(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(); + NodeList defaultTypes = xmlContentTypetDoc.getDocumentElement().getElementsByTagName(DEFAULT_TAG_NAME); + int defaultTypeCount = defaultTypes.getLength(); + for (int i = 0; i < defaultTypeCount; i++) { + Element element = (Element) defaultTypes.item(i); + String extension = element.getAttribute(EXTENSION_ATTRIBUTE_NAME); + String contentType = element.getAttribute(CONTENT_TYPE_ATTRIBUTE_NAME); 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(); + NodeList overrideTypes = xmlContentTypetDoc.getDocumentElement().getElementsByTagName(OVERRIDE_TAG_NAME); + int overrideTypeCount = overrideTypes.getLength(); + for (int i = 0; i < overrideTypeCount; i++) { + Element element = (Element) overrideTypes.item(i); + URI uri = new URI(element.getAttribute(PART_NAME_ATTRIBUTE_NAME)); + PackagePartName partName = PackagingURIHelper.createPartName(uri); + String contentType = element.getAttribute(CONTENT_TYPE_ATTRIBUTE_NAME); addOverrideContentType(partName, contentType); } } catch (URISyntaxException urie) { throw new InvalidFormatException(urie.getMessage()); - } catch (DocumentException e) { - throw new InvalidFormatException(e.getMessage()); - } - } + } catch (SAXException e) { + throw new InvalidFormatException(e.getMessage()); + } catch (IOException e) { + throw new InvalidFormatException(e.getMessage()); + } + } /** * Save the contents type part. @@ -421,9 +414,8 @@ public abstract class ContentTypeManager { Document xmlOutDoc = DocumentHelper.createDocument(); // Building namespace - Namespace dfNs = Namespace.get("", TYPES_NAMESPACE_URI); - Element typesElem = xmlOutDoc - .addElement(new QName(TYPES_TAG_NAME, dfNs)); + Element typesElem = xmlOutDoc.createElementNS(TYPES_NAMESPACE_URI, TYPES_TAG_NAME); + xmlOutDoc.appendChild(typesElem); // Adding default types for (Entry entry : defaultContentType.entrySet()) { @@ -454,10 +446,10 @@ public abstract class ContentTypeManager { */ private void appendSpecificTypes(Element root, Entry entry) { - root.addElement(OVERRIDE_TAG_NAME).addAttribute( - PART_NAME_ATTRIBUTE_NAME, - entry.getKey().getName()).addAttribute( - CONTENT_TYPE_ATTRIBUTE_NAME, entry.getValue()); + Element specificType = root.getOwnerDocument().createElement(OVERRIDE_TAG_NAME); + specificType.setAttribute(PART_NAME_ATTRIBUTE_NAME, entry.getKey().getName()); + specificType.setAttribute(CONTENT_TYPE_ATTRIBUTE_NAME, entry.getValue()); + root.appendChild(specificType); } /** @@ -470,11 +462,10 @@ public abstract class ContentTypeManager { * @see #save(java.io.OutputStream) */ private void appendDefaultType(Element root, Entry entry) { - root.addElement(DEFAULT_TAG_NAME).addAttribute( - EXTENSION_ATTRIBUTE_NAME, entry.getKey()) - .addAttribute(CONTENT_TYPE_ATTRIBUTE_NAME, - entry.getValue()); - + Element defaultType = root.getOwnerDocument().createElement(DEFAULT_TAG_NAME); + defaultType.setAttribute(EXTENSION_ATTRIBUTE_NAME, entry.getKey()); + defaultType.setAttribute(CONTENT_TYPE_ATTRIBUTE_NAME, entry.getValue()); + root.appendChild(defaultType); } /** diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/PackagePropertiesPart.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/PackagePropertiesPart.java index 44191e1443..523cdeca6f 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/PackagePropertiesPart.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/PackagePropertiesPart.java @@ -48,8 +48,6 @@ public final class PackagePropertiesPart extends PackagePart implements public final static String NAMESPACE_DCTERMS_URI = "http://purl.org/dc/terms/"; - public final static String NAMESPACE_XSI_URI = "http://www.w3.org/2001/XMLSchema-instance"; - /** * Constructor. * diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ZipContentTypeManager.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ZipContentTypeManager.java index 3491dc6424..1b9e7fe2ca 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ZipContentTypeManager.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ZipContentTypeManager.java @@ -17,8 +17,6 @@ package org.apache.poi.openxml4j.opc.internal; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -30,7 +28,7 @@ import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.StreamHelper; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; -import org.dom4j.Document; +import org.w3c.dom.Document; /** * Zip implementation of the ContentTypeManager. @@ -69,17 +67,8 @@ public class ZipContentTypeManager extends ContentTypeManager { // Referenced in ZIP zos.putNextEntry(partEntry); // Saving data in the ZIP file - ByteArrayOutputStream outTemp = new ByteArrayOutputStream(); - StreamHelper.saveXmlInStream(content, out); - InputStream ins = new ByteArrayInputStream(outTemp.toByteArray()); - 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; - } - zos.write(buff, 0, resultRead); + if (!StreamHelper.saveXmlInStream(content, zos)) { + return false; } zos.closeEntry(); } catch (IOException ioe) { diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/marshallers/PackagePropertiesMarshaller.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/marshallers/PackagePropertiesMarshaller.java index 1ad4d26b71..ca549f91f6 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/marshallers/PackagePropertiesMarshaller.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/marshallers/PackagePropertiesMarshaller.java @@ -19,15 +19,18 @@ package org.apache.poi.openxml4j.opc.internal.marshallers; import java.io.OutputStream; -import org.dom4j.Document; -import org.dom4j.DocumentHelper; -import org.dom4j.Element; -import org.dom4j.Namespace; -import org.dom4j.QName; +import javax.xml.XMLConstants; +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.events.Namespace; + import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart; import org.apache.poi.openxml4j.opc.internal.PartMarshaller; +import org.apache.poi.openxml4j.util.Nullable; +import org.apache.poi.util.DocumentHelper; +import org.w3c.dom.Document; +import org.w3c.dom.Element; /** * Package properties marshaller. @@ -36,17 +39,15 @@ import org.apache.poi.openxml4j.opc.internal.PartMarshaller; */ public class PackagePropertiesMarshaller implements PartMarshaller { - private final static Namespace namespaceDC = new Namespace("dc", - PackagePropertiesPart.NAMESPACE_DC_URI); - - private final static Namespace namespaceCoreProperties = new Namespace("", - PackagePropertiesPart.NAMESPACE_CP_URI); - - private final static Namespace namespaceDcTerms = new Namespace("dcterms", - PackagePropertiesPart.NAMESPACE_DCTERMS_URI); - - private final static Namespace namespaceXSI = new Namespace("xsi", - PackagePropertiesPart.NAMESPACE_XSI_URI); + + private final static Namespace namespaceDC, namespaceCoreProperties, namespaceDcTerms, namespaceXSI; + static { + final XMLEventFactory f = XMLEventFactory.newFactory(); + namespaceDC = f.createNamespace("dc", PackagePropertiesPart.NAMESPACE_DC_URI); + namespaceCoreProperties = f.createNamespace("cp", PackagePropertiesPart.NAMESPACE_CP_URI); + namespaceDcTerms = f.createNamespace("dcterms", PackagePropertiesPart.NAMESPACE_DCTERMS_URI); + namespaceXSI = f.createNamespace("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); + } protected static final String KEYWORD_CATEGORY = "category"; @@ -98,13 +99,13 @@ public class PackagePropertiesMarshaller implements PartMarshaller { // 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); + Element rootElem = xmlDoc.createElementNS(namespaceCoreProperties.getNamespaceURI(), + getQName("coreProperties", namespaceCoreProperties)); + DocumentHelper.addNamespaceDeclaration(rootElem, namespaceCoreProperties); + DocumentHelper.addNamespaceDeclaration(rootElem, namespaceDC); + DocumentHelper.addNamespaceDeclaration(rootElem, namespaceDcTerms); + DocumentHelper.addNamespaceDeclaration(rootElem, namespaceXSI); + xmlDoc.appendChild(rootElem); addCategory(); addContentStatus(); @@ -125,197 +126,110 @@ public class PackagePropertiesMarshaller implements PartMarshaller { return true; } - /** + /** + * Sets the given element's text content, creating it if necessary. + */ + private Element setElementTextContent(String localName, Namespace namespace, Nullable property) { + return setElementTextContent(localName, namespace, property, property.getValue()); + } + + private String getQName(String localName, Namespace namespace) { + return namespace.getPrefix().isEmpty() ? localName : namespace.getPrefix() + ':' + localName; + } + + private Element setElementTextContent(String localName, Namespace namespace, Nullable property, String propertyValue) { + if (!property.hasValue()) + return null; + + Element root = xmlDoc.getDocumentElement(); + Element elem = (Element) root.getElementsByTagNameNS(namespace.getNamespaceURI(), localName).item(0); + if (elem == null) { + // missing, we add it + elem = xmlDoc.createElementNS(namespace.getNamespaceURI(), getQName(localName, namespace)); + root.appendChild(elem); + } + elem.setTextContent(propertyValue); + return elem; + } + + private Element setElementTextContent(String localName, Namespace namespace, Nullable property, String propertyValue, String xsiType) { + Element element = setElementTextContent(localName, namespace, property, propertyValue); + if (element != null) { + element.setAttributeNS(namespaceXSI.getNamespaceURI(), getQName("type", namespaceXSI), xsiType); + } + return element; + } + + + /** * 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()); + setElementTextContent(KEYWORD_CATEGORY, namespaceCoreProperties, propsPart.getCategoryProperty()); } /** * 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()); + setElementTextContent(KEYWORD_CONTENT_STATUS, namespaceCoreProperties, propsPart.getContentStatusProperty()); } /** * 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()); + setElementTextContent(KEYWORD_CONTENT_TYPE, namespaceCoreProperties, propsPart.getContentTypeProperty()); } /** * 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()); + setElementTextContent(KEYWORD_CREATED, namespaceDcTerms, propsPart.getCreatedProperty(), + propsPart.getCreatedPropertyString(), "dcterms:W3CDTF"); } /** * 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()); + setElementTextContent(KEYWORD_CREATOR, namespaceDC, propsPart.getCreatorProperty()); } /** * 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()); + setElementTextContent(KEYWORD_DESCRIPTION, namespaceDC, propsPart.getDescriptionProperty()); } /** * 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()); + setElementTextContent(KEYWORD_IDENTIFIER, namespaceDC, propsPart.getIdentifierProperty()); } - /** + /** * 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()); + setElementTextContent(KEYWORD_KEYWORDS, namespaceCoreProperties, propsPart.getKeywordsProperty()); } /** * 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()); + setElementTextContent(KEYWORD_LANGUAGE, namespaceDC, propsPart.getLanguageProperty()); } /** * 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()); + setElementTextContent(KEYWORD_LAST_MODIFIED_BY, namespaceCoreProperties, propsPart.getLastModifiedByProperty()); } /** @@ -323,111 +237,39 @@ public class PackagePropertiesMarshaller implements PartMarshaller { * */ 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()); + setElementTextContent(KEYWORD_LAST_PRINTED, namespaceCoreProperties, propsPart.getLastPrintedProperty(), 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()); - } + setElementTextContent(KEYWORD_MODIFIED, namespaceDcTerms, propsPart.getModifiedProperty(), + propsPart.getModifiedPropertyString(), "dcterms:W3CDTF"); + } /** * 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()); + setElementTextContent(KEYWORD_REVISION, namespaceCoreProperties, propsPart.getRevisionProperty()); } /** * 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()); + setElementTextContent(KEYWORD_SUBJECT, namespaceDC, propsPart.getSubjectProperty()); } /** * 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()); + setElementTextContent(KEYWORD_TITLE, namespaceDC, propsPart.getTitleProperty()); } 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()); + setElementTextContent(KEYWORD_VERSION, namespaceCoreProperties, propsPart.getVersionProperty()); } } 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 index 4a9fec855e..37a538dc26 100644 --- 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 @@ -24,11 +24,6 @@ import java.net.URI; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -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; @@ -40,8 +35,11 @@ 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; +import org.apache.poi.util.DocumentHelper; import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; /** * Zip part marshaller. This marshaller is use to save any part in a zip stream. @@ -122,9 +120,8 @@ public final class ZipPartMarshaller implements PartMarshaller { Document xmlOutDoc = DocumentHelper.createDocument(); // make something like - Namespace dfNs = Namespace.get("", PackageNamespaces.RELATIONSHIPS); - Element root = xmlOutDoc.addElement(new QName( - PackageRelationship.RELATIONSHIPS_TAG_NAME, dfNs)); + Element root = xmlOutDoc.createElementNS(PackageNamespaces.RELATIONSHIPS, PackageRelationship.RELATIONSHIPS_TAG_NAME); + xmlOutDoc.appendChild(root); // declaredNamespaces = el.declaredNamespaces(); - Iterator itNS = declaredNamespaces.iterator(); - while (itNS.hasNext()) { - Namespace ns = itNS.next(); + NamedNodeMap namedNodeMap = el.getAttributes(); + int namedNodeCount = namedNodeMap.getLength(); + for (int i = 0; i < namedNodeCount; i++) { + Attr attr = (Attr)namedNodeMap.item(0); - // Rule M4.2 - if (ns.getURI().equals(PackageNamespaces.MARKUP_COMPATIBILITY)) - throw new InvalidFormatException( - "OPC Compliance error [M4.2]: A format consumer shall consider the use of the Markup Compatibility namespace to be an error."); - } + if (attr.getNamespaceURI().equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) { + // Rule M4.2 + if (attr.getValue().equals(PackageNamespaces.MARKUP_COMPATIBILITY)) + throw new InvalidFormatException( + "OPC Compliance error [M4.2]: A format consumer shall consider the use of the Markup Compatibility namespace to be an error."); + + } + } // Rule M4.3 - if (el.getNamespace().getURI().equals( - PackageProperties.NAMESPACE_DCTERMS) - && !(el.getName().equals(KEYWORD_CREATED) || el.getName() - .equals(KEYWORD_MODIFIED))) - throw new InvalidFormatException( - "OPC Compliance error [M4.3]: Producers shall not create a document element that contains refinements to the Dublin Core elements, except for the two specified in the schema: and Consumers shall consider a document element that violates this constraint to be an error."); + String elName = el.getLocalName(); + if (el.getNamespaceURI().equals(PackageProperties.NAMESPACE_DCTERMS)) + if (!(elName.equals(KEYWORD_CREATED) || elName.equals(KEYWORD_MODIFIED))) + throw new InvalidFormatException( + "OPC Compliance error [M4.3]: Producers shall not create a document element that contains refinements to the Dublin Core elements, except for the two specified in the schema: and Consumers shall consider a document element that violates this constraint to be an error."); // Rule M4.4 - if (el.attribute(new QName("lang", namespaceXML)) != null) + if (el.getAttributeNodeNS(XMLConstants.XML_NS_URI, "lang") != null) throw new InvalidFormatException( "OPC Compliance error [M4.4]: Producers shall not create a document element that contains the xml:lang attribute. Consumers shall consider a document element that violates this constraint to be an error."); // Rule M4.5 - if (el.getNamespace().getURI().equals( - PackageProperties.NAMESPACE_DCTERMS)) { + if (el.getNamespaceURI().equals(PackageProperties.NAMESPACE_DCTERMS)) { // DCTerms namespace only use with 'created' and 'modified' elements - String elName = el.getName(); - if (!(elName.equals(KEYWORD_CREATED) || elName - .equals(KEYWORD_MODIFIED))) + if (!(elName.equals(KEYWORD_CREATED) || elName.equals(KEYWORD_MODIFIED))) throw new InvalidFormatException("Namespace error : " + elName + " shouldn't have the following naemspace -> " + PackageProperties.NAMESPACE_DCTERMS); // Check for the 'xsi:type' attribute - Attribute typeAtt = el.attribute(new QName("type", namespaceXSI)); + Attr typeAtt = el.getAttributeNodeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type"); if (typeAtt == null) throw new InvalidFormatException("The element '" + elName - + "' must have the '" + namespaceXSI.getPrefix() - + ":type' attribute present !"); + + "' must have the 'xsi:type' attribute present !"); // Check for the attribute value => 'dcterms:W3CDTF' if (!typeAtt.getValue().equals("dcterms:W3CDTF")) throw new InvalidFormatException("The element '" + elName - + "' must have the '" + namespaceXSI.getPrefix() - + ":type' attribute with the value 'dcterms:W3CDTF' !"); + + "' must have the 'xsi:type' attribute with the value 'dcterms:W3CDTF' !"); } // Check its children - @SuppressWarnings("unchecked") - Iterator itChildren = el.elementIterator(); - while (itChildren.hasNext()) - checkElementForOPCCompliance(itChildren.next()); + NodeList childElements = el.getElementsByTagName("*"); + int childElementCount = childElements.getLength(); + for (int i = 0; i < childElementCount; i++) + checkElementForOPCCompliance((Element)childElements.item(i)); } } diff --git a/src/ooxml/java/org/apache/poi/util/DocumentHelper.java b/src/ooxml/java/org/apache/poi/util/DocumentHelper.java new file mode 100644 index 0000000000..22bdd4e0fd --- /dev/null +++ b/src/ooxml/java/org/apache/poi/util/DocumentHelper.java @@ -0,0 +1,60 @@ +/* ==================================================================== + 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.util; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.events.Namespace; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class DocumentHelper { + + private static final DocumentBuilder newDocumentBuilder; + static { + try { + newDocumentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new IllegalStateException("cannot create a DocumentBuilder", e); + } + } + + public static synchronized Document createDocument() { + return newDocumentBuilder.newDocument(); + } + + /** + * Adds a namespace declaration attribute to the given element. + */ + public static void addNamespaceDeclaration(Element element, String namespacePrefix, String namespaceURI) { + element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, + XMLConstants.XMLNS_ATTRIBUTE + ':' + namespacePrefix, + namespaceURI); + } + + /** + * Adds a namespace declaration attribute to the given element. + */ + public static void addNamespaceDeclaration(Element element, Namespace namespace) { + addNamespaceDeclaration(element, namespace.getPrefix(), namespace.getNamespaceURI()); + } + +} diff --git a/src/ooxml/java/org/apache/poi/util/SAXHelper.java b/src/ooxml/java/org/apache/poi/util/SAXHelper.java index b38b2c2be9..81049a9a2e 100644 --- a/src/ooxml/java/org/apache/poi/util/SAXHelper.java +++ b/src/ooxml/java/org/apache/poi/util/SAXHelper.java @@ -23,10 +23,11 @@ import java.io.StringReader; import java.lang.reflect.Method; import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.io.SAXReader; +import org.w3c.dom.Document; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -37,31 +38,44 @@ import org.xml.sax.SAXException; */ public final class SAXHelper { private static POILogger logger = POILogFactory.getLogger(SAXHelper.class); - - /** - * Creates a new SAX Reader, with sensible defaults - */ - public static SAXReader getSAXReader() { - SAXReader xmlReader = new SAXReader(); - xmlReader.setValidation(false); - xmlReader.setEntityResolver(new EntityResolver() { - public InputSource resolveEntity(String publicId, String systemId) - throws SAXException, IOException { - return new InputSource(new StringReader("")); - } - }); - trySetSAXFeature(xmlReader, XMLConstants.FEATURE_SECURE_PROCESSING, true); - trySetXercesSecurityManager(xmlReader); - return xmlReader; + + private static final EntityResolver IGNORING_ENTITY_RESOLVER = new EntityResolver() { + @Override + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + return new InputSource(new StringReader("")); + } + }; + + private static final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + static { + documentBuilderFactory.setNamespaceAware(true); + documentBuilderFactory.setValidating(false); + trySetSAXFeature(documentBuilderFactory, XMLConstants.FEATURE_SECURE_PROCESSING, true); + trySetXercesSecurityManager(documentBuilderFactory); } - private static void trySetSAXFeature(SAXReader xmlReader, String feature, boolean enabled) { + + /** + * Creates a new document builder, with sensible defaults + */ + public static synchronized DocumentBuilder getDocumentBuilder() { try { - xmlReader.setFeature(feature, enabled); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + documentBuilder.setEntityResolver(IGNORING_ENTITY_RESOLVER); + return documentBuilder; + } catch (ParserConfigurationException e) { + throw new IllegalStateException("cannot create a DocumentBuilder", e); + } + } + + private static void trySetSAXFeature(DocumentBuilderFactory documentBuilderFactory, String feature, boolean enabled) { + try { + documentBuilderFactory.setFeature(feature, enabled); } catch (Exception e) { logger.log(POILogger.INFO, "SAX Feature unsupported", feature, e); } } - private static void trySetXercesSecurityManager(SAXReader xmlReader) { + private static void trySetXercesSecurityManager(DocumentBuilderFactory documentBuilderFactory) { // Try built-in JVM one first, standalone if not for (String securityManagerClassName : new String[] { "com.sun.org.apache.xerces.internal.util.SecurityManager", @@ -71,7 +85,7 @@ public final class SAXHelper { Object mgr = Class.forName(securityManagerClassName).newInstance(); Method setLimit = mgr.getClass().getMethod("setEntityExpansionLimit", Integer.TYPE); setLimit.invoke(mgr, 4096); - xmlReader.setProperty("http://apache.org/xml/properties/security-manager", mgr); + documentBuilderFactory.setAttribute("http://apache.org/xml/properties/security-manager", mgr); // Stop once one can be setup without error return; } catch (Exception e) { @@ -86,7 +100,7 @@ public final class SAXHelper { * @param inp Stream to read the XML data from * @return the SAX processed Document */ - public static Document readSAXDocument(InputStream inp) throws DocumentException { - return getSAXReader().read(inp); + public static Document readSAXDocument(InputStream inp) throws IOException, SAXException { + return getDocumentBuilder().parse(inp); } } diff --git a/src/ooxml/testcases/org/apache/poi/openxml4j/opc/TestPackage.java b/src/ooxml/testcases/org/apache/poi/openxml4j/opc/TestPackage.java index fe5bb21d46..1c64fd8629 100644 --- a/src/ooxml/testcases/org/apache/poi/openxml4j/opc/TestPackage.java +++ b/src/ooxml/testcases/org/apache/poi/openxml4j/opc/TestPackage.java @@ -27,7 +27,6 @@ import java.io.OutputStream; import java.lang.reflect.Field; import java.net.URI; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.TreeMap; import java.util.regex.Pattern; @@ -40,15 +39,14 @@ import org.apache.poi.openxml4j.exceptions.InvalidOperationException; import org.apache.poi.openxml4j.opc.internal.ContentTypeManager; import org.apache.poi.openxml4j.opc.internal.FileHelper; import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart; +import org.apache.poi.util.DocumentHelper; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; import org.apache.poi.util.SAXHelper; import org.apache.poi.util.TempFile; -import org.dom4j.Document; -import org.dom4j.DocumentHelper; -import org.dom4j.Element; -import org.dom4j.Namespace; -import org.dom4j.QName; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; public final class TestPackage extends TestCase { private static final POILogger logger = POILogFactory.getLogger(TestPackage.class); @@ -127,18 +125,17 @@ public final class TestPackage extends TestCase { "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"); Document doc = DocumentHelper.createDocument(); - Namespace nsWordprocessinML = new Namespace("w", - "http://schemas.openxmlformats.org/wordprocessingml/2006/main"); - Element elDocument = doc.addElement(new QName("document", - nsWordprocessinML)); - Element elBody = elDocument.addElement(new QName("body", - nsWordprocessinML)); - Element elParagraph = elBody.addElement(new QName("p", - nsWordprocessinML)); - Element elRun = elParagraph - .addElement(new QName("r", nsWordprocessinML)); - Element elText = elRun.addElement(new QName("t", nsWordprocessinML)); - elText.setText("Hello Open XML !"); + Element elDocument = doc.createElementNS("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "w:document"); + doc.appendChild(elDocument); + Element elBody = doc.createElementNS("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "w:body"); + elDocument.appendChild(elBody); + Element elParagraph = doc.createElementNS("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "w:p"); + elBody.appendChild(elParagraph); + Element elRun = doc.createElementNS("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "w:r"); + elParagraph.appendChild(elRun); + Element elText = doc.createElementNS("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "w:t"); + elRun.appendChild(elText); + elText.setTextContent("Hello Open XML !"); StreamHelper.saveXmlInStream(doc, corePart.getOutputStream()); pkg.close(); @@ -223,15 +220,13 @@ public final class TestPackage extends TestCase { Document xmlRelationshipsDoc = SAXHelper.readSAXDocument(relPart.getInputStream()); - Element root = xmlRelationshipsDoc.getRootElement(); - for (Iterator i = root - .elementIterator(PackageRelationship.RELATIONSHIP_TAG_NAME); i - .hasNext();) { - Element element = (Element) i.next(); - String value = element.attribute( - PackageRelationship.TARGET_ATTRIBUTE_NAME) - .getValue(); - assertTrue("Root target must not start with a leadng slash ('/'): " + value, value.charAt(0) != '/'); + Element root = xmlRelationshipsDoc.getDocumentElement(); + NodeList nodeList = root.getElementsByTagName(PackageRelationship.RELATIONSHIP_TAG_NAME); + int nodeCount = nodeList.getLength(); + for (int i = 0; i < nodeCount; i++) { + Element element = (Element) nodeList.item(i); + String value = element.getAttribute(PackageRelationship.TARGET_ATTRIBUTE_NAME); + assertTrue("Root target must not start with a leading slash ('/'): " + value, value.charAt(0) != '/'); } } @@ -268,18 +263,17 @@ public final class TestPackage extends TestCase { // Create a content Document doc = DocumentHelper.createDocument(); - Namespace nsWordprocessinML = new Namespace("w", - "http://schemas.openxmlformats.org/wordprocessingml/2006/main"); - Element elDocument = doc.addElement(new QName("document", - nsWordprocessinML)); - Element elBody = elDocument.addElement(new QName("body", - nsWordprocessinML)); - Element elParagraph = elBody.addElement(new QName("p", - nsWordprocessinML)); - Element elRun = elParagraph - .addElement(new QName("r", nsWordprocessinML)); - Element elText = elRun.addElement(new QName("t", nsWordprocessinML)); - elText.setText("Hello Open XML !"); + Element elDocument = doc.createElementNS("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "w:document"); + doc.appendChild(elDocument); + Element elBody = doc.createElementNS("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "w:body"); + elDocument.appendChild(elBody); + Element elParagraph = doc.createElementNS("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "w:p"); + elBody.appendChild(elParagraph); + Element elRun = doc.createElementNS("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "w:r"); + elParagraph.appendChild(elRun); + Element elText = doc.createElementNS("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "w:t"); + elRun.appendChild(elText); + elText.setTextContent("Hello Open XML !"); StreamHelper.saveXmlInStream(doc, corePart.getOutputStream()); From effe9463eecb25de2a55f205d03926e1ccd8d773 Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Tue, 12 Aug 2014 11:33:02 +0000 Subject: [PATCH 2/2] Add some extra safety test to check that external entities are not loaded by xmlbeans git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1617453 13f79535-47bb-0310-9956-ffa450edef68 --- .../xwpf/extractor/TestExternalEntities.java | 47 ++++++++++++++++++ test-data/document/ExternalEntityInText.docx | Bin 0 -> 12756 bytes 2 files changed, 47 insertions(+) create mode 100644 src/ooxml/testcases/org/apache/poi/xwpf/extractor/TestExternalEntities.java create mode 100644 test-data/document/ExternalEntityInText.docx diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/extractor/TestExternalEntities.java b/src/ooxml/testcases/org/apache/poi/xwpf/extractor/TestExternalEntities.java new file mode 100644 index 0000000000..05d6b2f844 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xwpf/extractor/TestExternalEntities.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.xwpf.extractor; + +import java.io.IOException; + +import junit.framework.TestCase; + +import org.apache.poi.xwpf.XWPFTestDataSamples; +import org.apache.poi.xwpf.usermodel.XWPFDocument; + +public class TestExternalEntities extends TestCase { + + /** + * Get text out of the simple file + * @throws IOException + */ + public void testFile() throws IOException { + XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("ExternalEntityInText.docx"); + XWPFWordExtractor extractor = new XWPFWordExtractor(doc); + + String text = extractor.getText(); + + assertTrue(text.length() > 0); + + // Check contents, they should not contain the text from POI web site after colon! + assertEquals("Here should not be the POI web site: \"\"", text.trim()); + + extractor.close(); + } + +} diff --git a/test-data/document/ExternalEntityInText.docx b/test-data/document/ExternalEntityInText.docx new file mode 100644 index 0000000000000000000000000000000000000000..0b47852de44e25cfbe931ab1967428b933dcf330 GIT binary patch literal 12756 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