Patch from Chris Boyle to add basic support for .xlsm (macro-enabled) workbooks. The binary blob containing the VBA macros may be copied from one such workbook into another.

Fixes #58036

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1690593 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
David North 2015-07-13 09:05:17 +00:00
parent 5e1c3ce90f
commit 2f8aa558b3
7 changed files with 266 additions and 13 deletions

View 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;
}
}

View 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() {

View 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(

View File

@ -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
}
}

View 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);
}
}
}

View File

@ -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;
}
}

View 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);
}
}
}