@@ -, +, @@ --- .../org/apache/poi/openxml4j/opc/OPCPackage.java | 27 ++++++++ .../org/apache/poi/openxml4j/opc/PackagePart.java | 13 ++-- .../apache/poi/xssf/usermodel/XSSFRelation.java | 2 +- .../org/apache/poi/xssf/usermodel/XSSFVBAPart.java | 52 +++++++++++++++ .../apache/poi/xssf/usermodel/XSSFWorkbook.java | 77 +++++++++++++++++++++- .../poi/xssf/usermodel/XSSFWorkbookType.java | 45 +++++++++++++ .../poi/xssf/usermodel/TestXSSFWorkbook.java | 55 +++++++++++++++- 7 files changed, 261 insertions(+), 10 deletions(-) create mode 100644 src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFVBAPart.java create mode 100644 src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbookType.java --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java +++ a/src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java @@ -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; + } + } --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackagePart.java +++ a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackagePart.java @@ -601,11 +601,14 @@ public abstract class PackagePart implements RelationshipSource, Comparable 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); + } + } } --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbookType.java +++ a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbookType.java @@ -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; + } + +} --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java +++ a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java @@ -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,10 @@ 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.usermodel.BaseTestWorkbook; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; @@ -60,6 +62,7 @@ import org.apache.poi.util.TempFile; import org.apache.poi.xssf.XSSFITestDataProvider; import org.apache.poi.xssf.XSSFTestDataSamples; import org.apache.poi.xssf.model.StylesTable; +import org.bouncycastle.util.Arrays; import org.junit.Test; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCalcPr; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTPivotCache; @@ -869,4 +872,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); + } + } } --