]> source.dussan.org Git - poi.git/commitdiff
Patch from Chris Boyle to add basic support for .xlsm (macro-enabled) workbooks....
authorDavid North <dnorth@apache.org>
Mon, 13 Jul 2015 09:05:17 +0000 (09:05 +0000)
committerDavid North <dnorth@apache.org>
Mon, 13 Jul 2015 09:05:17 +0000 (09:05 +0000)
Fixes #58036

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1690593 13f79535-47bb-0310-9956-ffa450edef68

src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java
src/ooxml/java/org/apache/poi/openxml4j/opc/PackagePart.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFVBAPart.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbookType.java [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java

index 8b65418e086afe0acd898d2df558f01d774587a4..ccf57a6553c6a17e13bdc6b30ae517ab208fdb10 100644 (file)
@@ -1551,4 +1551,31 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
         }
         return success;
     }
+
+    /**
+    * Add the specified part, and register its content type with the content
+    * type manager.
+    *
+    * @param part
+    *            The part to add.
+    */
+    public void registerPartAndContentType(PackagePart part) {
+        addPackagePart(part);
+        this.contentTypeManager.addContentType(part.getPartName(), part.getContentType());
+        this.isDirty = true;
+    }
+
+    /**
+     * Remove the specified part, and clear its content type from the content
+     * type manager.
+     *
+     * @param partName
+     *            The part name of the part to remove.
+     */
+    public void unregisterPartAndContentType(PackagePartName partName) {
+        removePart(partName);
+        this.contentTypeManager.removeContentType(partName);
+        this.isDirty = true;
+    }
+
 }
index 1943cf6a5460d2d66666ee2722528f792fd4ac14..8f961c9335a6621a07b1877faf0a950c79970080 100644 (file)
@@ -601,11 +601,14 @@ public abstract class PackagePart implements RelationshipSource, Comparable<Pack
         */
        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.");
+               if (_container == null) {
+                       _contentType = new ContentType(contentType);
+               }
+               else {
+                   _container.unregisterPartAndContentType(_partName);
+                   _contentType = new ContentType(contentType);
+                   _container.registerPartAndContentType(this);
+               }
        }
 
        public OPCPackage getPackage() {
index 27caf13e5a4d5c697f60789d9056ea94fc4cadcf..fbe170f3fa70893ba8eaf29957a825052fa72107 100644 (file)
@@ -281,7 +281,7 @@ public final class XSSFRelation extends POIXMLRelation {
         "application/vnd.ms-office.vbaProject",
         "http://schemas.microsoft.com/office/2006/relationships/vbaProject",
         "/xl/vbaProject.bin",
-        null
+        XSSFVBAPart.class
     );
 
     public static final XSSFRelation ACTIVEX_CONTROLS = new XSSFRelation(
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFVBAPart.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFVBAPart.java
new file mode 100644 (file)
index 0000000..263dae0
--- /dev/null
@@ -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.xssf.usermodel;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+
+public class XSSFVBAPart extends POIXMLDocumentPart {
+
+    /**
+     * Create a new XSSFVBAPart node
+     */
+    protected XSSFVBAPart() {
+        super();
+    }
+
+    /**
+     * Construct XSSFVBAPart from a package part
+     *
+     * @param part the package part holding the VBA data,
+     * @param rel  the package relationship holding this part
+     */
+    protected XSSFVBAPart(PackagePart part, PackageRelationship rel) {
+        super(part, rel);
+    }
+
+    /**
+     * Like *PictureData, VBA objects store the actual content in the part
+     * directly without keeping a copy like all others therefore we need to
+     * handle them differently.
+     */
+    protected void prepareForCommit() {
+        // do not clear the part here
+    }
+
+}
index 9406fb17c76d67ef63ee1542b29267d13c082264..f90b48dfa17369bbbb2d332f68068e47cec7769a 100644 (file)
@@ -215,7 +215,15 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X
      * Create a new SpreadsheetML workbook.
      */
     public XSSFWorkbook() {
-        super(newPackage());
+        this(XSSFWorkbookType.XLSX);
+    }
+
+    /**
+     * Create a new SpreadsheetML workbook.
+     * @param workbookType The type of workbook to make (.xlsx or .xlsm).
+     */
+    public XSSFWorkbook(XSSFWorkbookType workbookType) {
+        super(newPackage(workbookType));
         onWorkbookCreate();
     }
 
@@ -429,7 +437,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X
     /**
      * Create a new SpreadsheetML package and setup the default minimal content
      */
-    protected static OPCPackage newPackage() {
+    protected static OPCPackage newPackage(XSSFWorkbookType workbookType) {
         try {
             OPCPackage pkg = OPCPackage.create(new ByteArrayOutputStream());
             // Main part
@@ -437,7 +445,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X
             // Create main part relationship
             pkg.addRelationship(corePartName, TargetMode.INTERNAL, PackageRelationshipTypes.CORE_DOCUMENT);
             // Create main document part
-            pkg.createPart(corePartName, XSSFRelation.WORKBOOK.getContentType());
+            pkg.createPart(corePartName, workbookType.getContentType());
 
             pkg.getPackageProperties().setCreatorProperty(DOCUMENT_CREATOR);
 
@@ -2044,4 +2052,67 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X
     protected void setPivotTables(List<XSSFPivotTable> pivotTables) {
         this.pivotTables = pivotTables;
     }
+
+    public XSSFWorkbookType getWorkbookType() {
+        return isMacroEnabled() ? XSSFWorkbookType.XLSM : XSSFWorkbookType.XLSX;
+    }
+
+    /**
+     * Sets whether the workbook will be an .xlsx or .xlsm (macro-enabled) file.
+     */
+    public void setWorkbookType(XSSFWorkbookType type) {
+        try {
+            getPackagePart().setContentType(type.getContentType());
+        } catch (InvalidFormatException e) {
+            throw new POIXMLException(e);
+        }
+    }
+
+    /**
+     * Adds a vbaProject.bin file to the workbook.  This will change the workbook
+     * type if necessary.
+     *
+     * @throws IOException
+     */
+    public void setVBAProject(InputStream vbaProjectStream) throws IOException {
+        if (!isMacroEnabled()) {
+            setWorkbookType(XSSFWorkbookType.XLSM);
+        }
+
+        PackagePartName ppName;
+        try {
+            ppName = PackagingURIHelper.createPartName(XSSFRelation.VBA_MACROS.getDefaultFileName());
+        } catch (InvalidFormatException e) {
+            throw new POIXMLException(e);
+        }
+        OPCPackage opc = getPackage();
+        OutputStream outputStream;
+        if (!opc.containPart(ppName)) {
+            POIXMLDocumentPart relationship = createRelationship(XSSFRelation.VBA_MACROS, XSSFFactory.getInstance());
+            outputStream = relationship.getPackagePart().getOutputStream();
+        } else {
+            PackagePart part = opc.getPart(ppName);
+            outputStream = part.getOutputStream();
+        }
+        try {
+            IOUtils.copy(vbaProjectStream, outputStream);
+        } finally {
+            IOUtils.closeQuietly(outputStream);
+        }
+    }
+
+    /**
+     * Adds a vbaProject.bin file taken from another, given workbook to this one.
+     * @throws IOException
+     * @throws InvalidFormatException
+     */
+    public void setVBAProject(XSSFWorkbook macroWorkbook) throws IOException, InvalidFormatException {
+        if (!macroWorkbook.isMacroEnabled()) {
+            return;
+        }
+        InputStream vbaProjectStream = XSSFRelation.VBA_MACROS.getContents(macroWorkbook.getCorePart());
+        if (vbaProjectStream != null) {
+            setVBAProject(vbaProjectStream);
+        }
+    }
 }
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbookType.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbookType.java
new file mode 100644 (file)
index 0000000..917d3d6
--- /dev/null
@@ -0,0 +1,45 @@
+/* ====================================================================
+   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.xssf.usermodel;
+
+
+/**
+ * Represents the two different kinds of XML-based OOXML document.
+ */
+public enum XSSFWorkbookType {
+
+    XLSX(XSSFRelation.WORKBOOK.getContentType(), "xlsx"),
+    XLSM(XSSFRelation.MACROS_WORKBOOK.getContentType(), "xlsm");
+
+    private final String _contentType;
+    private final String _extension;
+
+    private XSSFWorkbookType(String contentType, String extension) {
+        _contentType = contentType;
+        _extension = extension;
+    }
+
+    public String getContentType() {
+        return _contentType;
+    }
+
+    public String getExtension() {
+        return _extension;
+    }
+
+}
index 209bded8c4c2e60441efffefea34031023ebe5c1..392ab906efeebc182fe2308fdd15f596a7b84af1 100644 (file)
@@ -17,6 +17,7 @@
 
 package org.apache.poi.xssf.usermodel;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -25,6 +26,7 @@ import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -39,10 +41,11 @@ import org.apache.poi.openxml4j.opc.ContentTypes;
 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.PackageRelationshipCollection;
 import org.apache.poi.openxml4j.opc.PackagingURIHelper;
 import org.apache.poi.openxml4j.opc.internal.MemoryPackagePart;
 import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart;
-
+import org.apache.poi.ss.SpreadsheetVersion;
 import org.apache.poi.ss.usermodel.BaseTestWorkbook;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.CellStyle;
@@ -78,7 +81,8 @@ public final class TestXSSFWorkbook extends BaseTestWorkbook {
         */
        @Test
        public void saveLoadNew() throws Exception {
-               XSSFWorkbook workbook = new XSSFWorkbook();
+               @SuppressWarnings("resource")
+        XSSFWorkbook workbook = new XSSFWorkbook();
 
                //check that the default date system is set to 1900
                CTWorkbookPr pr = workbook.getCTWorkbook().getWorkbookPr();
@@ -121,7 +125,8 @@ public final class TestXSSFWorkbook extends BaseTestWorkbook {
                // Links to the three sheets, shared strings and styles
                assertTrue(wbPart.hasRelationships());
                assertEquals(5, wbPart.getRelationships().size());
-
+               workbook.close();
+               
                // Load back the XSSFWorkbook
                workbook = new XSSFWorkbook(pkg);
                assertEquals(3, workbook.getNumberOfSheets());
@@ -777,7 +782,7 @@ public final class TestXSSFWorkbook extends BaseTestWorkbook {
         Cell cell9 = row3.createCell(2);
         cell9.setCellValue("Bepa");
 
-        AreaReference source = new AreaReference("A1:B2");
+        AreaReference source = new AreaReference("A1:B2", SpreadsheetVersion.EXCEL2007);
         sheet.createPivotTable(source, new CellReference("H5"));
     }
 
@@ -869,4 +874,54 @@ public final class TestXSSFWorkbook extends BaseTestWorkbook {
             wb.close();
         }
     }
+
+    /**
+     * Tests that we can save a workbook with macros and reload it.
+     */
+    @Test
+    public void testSetVBAProject() throws Exception {
+        XSSFWorkbook workbook = null;
+        OutputStream out = null;
+        File file;
+        final byte[] allBytes = new byte[256];
+        for (int i = 0; i < 256; i++) {
+            allBytes[i] = (byte) (i - 128);
+        }
+        try {
+            workbook = new XSSFWorkbook();
+            workbook.createSheet();
+            workbook.setVBAProject(new ByteArrayInputStream(allBytes));
+            file = TempFile.createTempFile("poi-", ".xlsm");
+            out = new FileOutputStream(file);
+            workbook.write(out);
+        }
+        finally {
+            IOUtils.closeQuietly(out);
+            IOUtils.closeQuietly(workbook);
+        }
+
+        try {
+            // Check the package contains what we'd expect it to
+            OPCPackage pkg = OPCPackage.open(file.toString());
+            PackagePart wbPart = pkg.getPart(PackagingURIHelper.createPartName("/xl/workbook.xml"));
+            assertTrue(wbPart.hasRelationships());
+            final PackageRelationshipCollection relationships = wbPart.getRelationships().getRelationships(XSSFRelation.VBA_MACROS.getRelation());
+            assertEquals(1, relationships.size());
+            assertEquals(XSSFRelation.VBA_MACROS.getDefaultFileName(), relationships.getRelationship(0).getTargetURI().toString());
+            PackagePart vbaPart = pkg.getPart(PackagingURIHelper.createPartName(XSSFRelation.VBA_MACROS.getDefaultFileName()));
+            assertNotNull(vbaPart);
+            assertFalse(vbaPart.isRelationshipPart());
+            assertEquals(XSSFRelation.VBA_MACROS.getContentType(), vbaPart.getContentType());
+            final byte[] fromFile = IOUtils.toByteArray(vbaPart.getInputStream());
+            assertArrayEquals(allBytes, fromFile);
+
+            // Load back the XSSFWorkbook just to check nothing explodes
+            workbook = new XSSFWorkbook(pkg);
+            assertEquals(1, workbook.getNumberOfSheets());
+            assertEquals(XSSFWorkbookType.XLSM, workbook.getWorkbookType());
+        }
+        finally {
+            IOUtils.closeQuietly(workbook);
+        }
+    }
 }