diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 12bce0ccf5..d7f6d17324 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 45431 - Support for .xlsm files, sufficient for simple files to be loaded by excel without warning New class org.apache.poi.hssf.record.RecordFormatException, which DDF uses instead of the HSSF version, and the HSSF version inherits from 45431 - Partial support for .xlm files. Not quite enough for excel to load them though 45430 - Correct named range sheet reporting when no local sheet id is given in the xml diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 3b6644db96..208799a004 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 45431 - Support for .xlsm files, sufficient for simple files to be loaded by excel without warning New class org.apache.poi.hssf.record.RecordFormatException, which DDF uses instead of the HSSF version, and the HSSF version inherits from 45431 - Partial support for .xlm files. Not quite enough for excel to load them though 45430 - Correct named range sheet reporting when no local sheet id is given in the xml diff --git a/src/java/org/apache/poi/util/IOUtils.java b/src/java/org/apache/poi/util/IOUtils.java index c3aa8695e2..2e60e9bdbe 100644 --- a/src/java/org/apache/poi/util/IOUtils.java +++ b/src/java/org/apache/poi/util/IOUtils.java @@ -22,6 +22,7 @@ package org.apache.poi.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; public class IOUtils { @@ -84,4 +85,19 @@ public class IOUtils } } } + + /** + * Copies all the data from the given InputStream to the + * OutputStream. It leaves both streams open, so you + * will still need to close them once done. + */ + public static void copy(InputStream inp, OutputStream out) throws IOException { + byte[] buff = new byte[4096]; + int count; + while( (count = inp.read(buff)) != -1 ) { + if(count > 0) { + out.write(buff, 0, count); + } + } + } } \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/POIXMLDocument.java b/src/ooxml/java/org/apache/poi/POIXMLDocument.java index 3fb718f6ce..1f61a5cdcf 100644 --- a/src/ooxml/java/org/apache/poi/POIXMLDocument.java +++ b/src/ooxml/java/org/apache/poi/POIXMLDocument.java @@ -117,8 +117,19 @@ public abstract class POIXMLDocument { * @throws InvalidFormatException */ protected PackagePart getTargetPart(PackageRelationship rel) throws InvalidFormatException { + return getTargetPart(getPackage(), rel); + } + /** + * Get the PackagePart that is the target of a relationship. + * + * @param rel The relationship + * @param pkg The package to fetch from + * @return The target part + * @throws InvalidFormatException + */ + public static PackagePart getTargetPart(Package pkg, PackageRelationship rel) throws InvalidFormatException { PackagePartName relName = PackagingURIHelper.createPartName(rel.getTargetURI()); - PackagePart part = getPackage().getPart(relName); + PackagePart part = pkg.getPart(relName); if (part == null) { throw new IllegalArgumentException("No part found for relationship " + rel); } diff --git a/src/ooxml/java/org/apache/poi/xssf/model/Control.java b/src/ooxml/java/org/apache/poi/xssf/model/Control.java new file mode 100644 index 0000000000..55f4872bec --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/model/Control.java @@ -0,0 +1,101 @@ +package org.apache.poi.xssf.model; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; + +import org.apache.poi.xssf.usermodel.XSSFActiveXData; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.apache.xmlbeans.XmlException; +import org.apache.xmlbeans.XmlOptions; +import org.openxml4j.exceptions.InvalidFormatException; +import org.openxml4j.opc.PackagePart; +import org.openxml4j.opc.PackagePartName; +import org.openxml4j.opc.PackageRelationship; +import org.openxml4j.opc.PackagingURIHelper; +import org.openxml4j.opc.TargetMode; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTControl; + +/** + * A control object in XSSF, which will typically + * have active x data associated with it. + */ +public class Control implements XSSFChildContainingModel { + private CTControl control; + private String originalId; + private ArrayList activexBins; + + public Control(InputStream is, String originalId) throws IOException { + readFrom(is); + this.originalId = originalId; + this.activexBins = new ArrayList(); + } + + public String getOriginalId() { + return this.originalId; + } + + public Control() { + this.control = CTControl.Factory.newInstance(); + } + /** + * For unit testing only! + */ + protected Control(CTControl control) { + this.control = control; + } + + public void readFrom(InputStream is) throws IOException { + try { + CTControl doc = CTControl.Factory.parse(is); + control = doc; + } catch (XmlException e) { + throw new IOException(e.getLocalizedMessage()); + } + } + public void writeTo(OutputStream out) throws IOException { + XmlOptions options = new XmlOptions(); + options.setSaveOuter(); + options.setUseDefaultNamespace(); + // Requests use of whitespace for easier reading + options.setSavePrettyPrint(); + control.save(out, options); + } + + /** + * Finds our XSSFActiveXData children + */ + public void findChildren(PackagePart modelPart) throws IOException, InvalidFormatException { + for(PackageRelationship rel : modelPart.getRelationshipsByType(XSSFWorkbook.ACTIVEX_BINS.getRelation())) { + PackagePart binPart = XSSFWorkbook.getTargetPart(modelPart.getPackage(), rel); + XSSFActiveXData actX = new XSSFActiveXData(binPart, rel.getId()); + activexBins.add(actX); + } + } + /** + * Writes back out our XSSFPictureData children + */ + public void writeChildren(PackagePart modelPart) throws IOException, InvalidFormatException { + int binIndex = 1; + OutputStream out; + + for(XSSFActiveXData actX : activexBins) { + PackagePartName binPartName = PackagingURIHelper.createPartName(XSSFWorkbook.ACTIVEX_BINS.getFileName(binIndex)); + modelPart.addRelationship(binPartName, TargetMode.INTERNAL, XSSFWorkbook.ACTIVEX_BINS.getRelation(), getOriginalId()); + PackagePart imagePart = modelPart.getPackage().createPart(binPartName, XSSFWorkbook.ACTIVEX_BINS.getContentType()); + out = imagePart.getOutputStream(); + actX.writeTo(out); + out.close(); + binIndex++; + } + } + + public ArrayList getData() { + return this.activexBins; + } + + public void addData(XSSFActiveXData activeX) { + this.activexBins.add(activeX); + } +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xssf/model/Drawing.java b/src/ooxml/java/org/apache/poi/xssf/model/Drawing.java new file mode 100644 index 0000000000..41123e0005 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/model/Drawing.java @@ -0,0 +1,105 @@ +package org.apache.poi.xssf.model; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; + +import org.apache.poi.xssf.usermodel.XSSFPictureData; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.apache.xmlbeans.XmlException; +import org.apache.xmlbeans.XmlOptions; +import org.openxml4j.exceptions.InvalidFormatException; +import org.openxml4j.opc.PackagePart; +import org.openxml4j.opc.PackagePartName; +import org.openxml4j.opc.PackageRelationship; +import org.openxml4j.opc.PackagingURIHelper; +import org.openxml4j.opc.TargetMode; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDrawing; + +/** + * A drawing object in XSSF. May well have raw pictures + * attached to it as children. + */ +public class Drawing implements XSSFChildContainingModel { + private CTDrawing drawing; + private String originalId; + + /** Raw pictures attached to the drawing */ + private ArrayList pictures; + + public Drawing(InputStream is, String originalId) throws IOException { + readFrom(is); + this.originalId = originalId; + this.pictures = new ArrayList(); + } + + public String getOriginalId() { + return this.originalId; + } + + public Drawing() { + this.drawing = CTDrawing.Factory.newInstance(); + } + /** + * For unit testing only! + */ + protected Drawing(CTDrawing drawing) { + this.drawing = drawing; + } + + public void readFrom(InputStream is) throws IOException { + try { + CTDrawing doc = CTDrawing.Factory.parse(is); + drawing = doc; + } catch (XmlException e) { + throw new IOException(e.getLocalizedMessage()); + } + } + public void writeTo(OutputStream out) throws IOException { + XmlOptions options = new XmlOptions(); + options.setSaveOuter(); + options.setUseDefaultNamespace(); + // Requests use of whitespace for easier reading + options.setSavePrettyPrint(); + drawing.save(out, options); + } + + /** + * Finds our XSSFPictureData children + */ + public void findChildren(PackagePart modelPart) throws IOException, InvalidFormatException { + for(PackageRelationship rel : modelPart.getRelationshipsByType(XSSFWorkbook.IMAGES.getRelation())) { + PackagePart imagePart = XSSFWorkbook.getTargetPart(modelPart.getPackage(), rel); + XSSFPictureData pd = new XSSFPictureData(imagePart, rel.getId()); + pictures.add(pd); + } + } + /** + * Writes back out our XSSFPictureData children + */ + public void writeChildren(PackagePart modelPart) throws IOException, InvalidFormatException { + int pictureIndex = 1; + OutputStream out; + + for(XSSFPictureData picture : pictures) { + PackagePartName imagePartName = PackagingURIHelper.createPartName(XSSFWorkbook.IMAGES.getFileName(pictureIndex)); + modelPart.addRelationship(imagePartName, TargetMode.INTERNAL, XSSFWorkbook.IMAGES.getRelation(), getOriginalId()); + PackagePart imagePart = modelPart.getPackage().createPart(imagePartName, XSSFWorkbook.IMAGES.getContentType()); + out = imagePart.getOutputStream(); + picture.writeTo(out); + out.close(); + pictureIndex++; + } + } + + public ArrayList getPictures() + { + return this.pictures; + } + + public void addPictures(XSSFPictureData picture) + { + this.pictures.add(picture); + } +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xssf/model/XSSFChildContainingModel.java b/src/ooxml/java/org/apache/poi/xssf/model/XSSFChildContainingModel.java new file mode 100644 index 0000000000..eedfcc0869 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/model/XSSFChildContainingModel.java @@ -0,0 +1,42 @@ +/* ==================================================================== + 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.model; + +import java.io.IOException; + +import org.openxml4j.exceptions.InvalidFormatException; +import org.openxml4j.opc.PackagePart; + +/** + * Common interface for XSSF models, which have (typically + * binary) children to them. + * One example is a VmlDrawing (Drawing), which can have + * raw images associated with it. + */ +public interface XSSFChildContainingModel extends XSSFModel { + /** + * Find any children associated with the {@link XSSFModel}. + * @param modelPart The PackagePart of this model + */ + public void findChildren(PackagePart modelPart) throws IOException, InvalidFormatException; + /** + * Writes out any children associated with the {@link XSSFModel}, + * along with the required relationship stuff. + * @param modelPart The new PackagePart of this model + */ + public void writeChildren(PackagePart modelPart) throws IOException, InvalidFormatException; +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFActiveXData.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFActiveXData.java new file mode 100644 index 0000000000..84ef049725 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFActiveXData.java @@ -0,0 +1,49 @@ +package org.apache.poi.xssf.usermodel; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.poi.ss.usermodel.PictureData; +import org.apache.poi.util.IOUtils; +import org.openxml4j.opc.PackagePart; + +public class XSSFActiveXData implements PictureData { + + private PackagePart packagePart; + private String originalId; + + public XSSFActiveXData(PackagePart packagePart, String originalId) { + this(packagePart); + this.originalId = originalId; + } + + public XSSFActiveXData(PackagePart packagePart) { + this.packagePart = packagePart; + } + + public String getOriginalId() { + return originalId; + } + + public PackagePart getPart() { + return packagePart; + } + + public void writeTo(OutputStream out) throws IOException { + IOUtils.copy(packagePart.getInputStream(), out); + } + + public byte[] getData() { + // TODO - is this right? + // Are there headers etc? + try { + return IOUtils.toByteArray(packagePart.getInputStream()); + } catch(IOException e) { + throw new RuntimeException(e); + } + } + + public String suggestFileExtension() { + return packagePart.getPartName().getExtension(); + } +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFPictureData.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFPictureData.java index 91ed5f8658..eb153ce09d 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFPictureData.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFPictureData.java @@ -17,26 +17,51 @@ package org.apache.poi.xssf.usermodel; +import java.io.IOException; +import java.io.OutputStream; + import org.apache.poi.ss.usermodel.PictureData; +import org.apache.poi.util.IOUtils; import org.openxml4j.opc.PackagePart; - +/** + * Raw picture data, normally attached to a + * vmlDrawing + */ public class XSSFPictureData implements PictureData { - private PackagePart packagePart; + private String originalId; + + public XSSFPictureData(PackagePart packagePart, String originalId) { + this(packagePart); + this.originalId = originalId; + } public XSSFPictureData(PackagePart packagePart) { this.packagePart = packagePart; } + public String getOriginalId() { + return originalId; + } + + protected PackagePart getPart() { + return packagePart; + } + + public void writeTo(OutputStream out) throws IOException { + IOUtils.copy(packagePart.getInputStream(), out); + } + public byte[] getData() { - // TODO Auto-generated method stub - return null; + try { + return IOUtils.toByteArray(packagePart.getInputStream()); + } catch(IOException e) { + throw new RuntimeException(e); + } } public String suggestFileExtension() { - // TODO Auto-generated method stub - return null; + return packagePart.getPartName().getExtension(); } - } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java index b4378cdd42..18c0e8f161 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java @@ -38,6 +38,8 @@ import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.Region; import org.apache.poi.xssf.model.CommentsTable; +import org.apache.poi.xssf.model.Control; +import org.apache.poi.xssf.model.Drawing; import org.apache.poi.xssf.usermodel.helpers.ColumnHelper; import org.apache.xmlbeans.XmlOptions; import org.openxml4j.opc.PackagePart; @@ -77,6 +79,8 @@ public class XSSFSheet implements Sheet { protected XSSFWorkbook workbook; protected CommentsSource sheetComments; protected CTMergeCells ctMergeCells; + protected ArrayList drawings; + protected ArrayList controls; public static final short LeftMargin = 0; public static final short RightMargin = 1; @@ -85,6 +89,22 @@ public class XSSFSheet implements Sheet { public static final short HeaderMargin = 4; public static final short FooterMargin = 5; + public XSSFSheet(CTSheet sheet, CTWorksheet worksheet, XSSFWorkbook workbook, CommentsSource sheetComments, ArrayList drawings, ArrayList controls) { + this(sheet, worksheet, workbook, sheetComments); + this.drawings = drawings; + this.controls = controls; + } + + public ArrayList getDrawings() + { + return drawings; + } + + public ArrayList getControls() + { + return controls; + } + public XSSFSheet(CTSheet sheet, CTWorksheet worksheet, XSSFWorkbook workbook, CommentsSource sheetComments) { this(sheet, worksheet, workbook); this.sheetComments = sheetComments; diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index 3c395264ec..9aea48a0f3 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Constructor; +import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -46,6 +47,8 @@ import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; import org.apache.poi.xssf.model.BinaryPart; import org.apache.poi.xssf.model.CommentsTable; +import org.apache.poi.xssf.model.Control; +import org.apache.poi.xssf.model.Drawing; import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.model.XSSFModel; @@ -110,10 +113,16 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { "/xl/drawings/drawing#.xml", null ); + public static final XSSFRelation VML_DRAWINGS = new XSSFRelation( + "application/vnd.openxmlformats-officedocument.vmlDrawing", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing", + "/xl/drawings/vmlDrawing#.vml", + null + ); public static final XSSFRelation IMAGES = new XSSFRelation( - null, // TODO - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", - "/xl/image#.xml", + "image/x-emf", // TODO + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + "/xl/media/image#.emf", null ); public static final XSSFRelation SHEET_COMMENTS = new XSSFRelation( @@ -140,12 +149,25 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { null, BinaryPart.class ); + public static final XSSFRelation VBA_MACROS = new XSSFRelation( "application/vnd.ms-office.vbaProject", "http://schemas.microsoft.com/office/2006/relationships/vbaProject", "/xl/vbaProject.bin", BinaryPart.class ); + public static final XSSFRelation ACTIVEX_CONTROLS = new XSSFRelation( + "application/vnd.ms-office.activeX+xml", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/control", + "/xl/activeX/activeX#.xml", + null + ); + public static final XSSFRelation ACTIVEX_BINS = new XSSFRelation( + "application/vnd.ms-office.activeX", + "http://schemas.microsoft.com/office/2006/relationships/activeXControlBinary", + "/xl/activeX/activeX#.bin", + BinaryPart.class + ); public static class XSSFRelation { @@ -335,9 +357,27 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { comments = new CommentsTable(commentsPart.getInputStream()); } + // Get the drawings for the sheet, if there are any + ArrayList drawings = new ArrayList(); + for(PackageRelationship rel : part.getRelationshipsByType(VML_DRAWINGS.REL)) { + PackagePart drawingPart = getTargetPart(rel); + Drawing drawing = new Drawing(drawingPart.getInputStream(), rel.getId()); + drawing.findChildren(drawingPart); + drawings.add(drawing); + } + + // Get the activeX controls for the sheet, if there are any + ArrayList controls = new ArrayList(); + for(PackageRelationship rel : part.getRelationshipsByType(ACTIVEX_CONTROLS.REL)) { + PackagePart controlPart = getTargetPart(rel); + Control control = new Control(controlPart.getInputStream(), rel.getId()); + control.findChildren(controlPart); + controls.add(control); + } + // Now create the sheet WorksheetDocument worksheetDoc = WorksheetDocument.Factory.parse(part.getInputStream()); - XSSFSheet sheet = new XSSFSheet(ctSheet, worksheetDoc.getWorksheet(), this, comments); + XSSFSheet sheet = new XSSFSheet(ctSheet, worksheetDoc.getWorksheet(), this, comments, drawings, controls); this.sheets.add(sheet); // Process external hyperlinks for the sheet, @@ -839,6 +879,40 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { ct.writeTo(out); out.close(); } + + // If our sheet has drawings, then write out those + if(sheet.getDrawings() != null) { + int drawingIndex = 1; + for(Drawing drawing : sheet.getDrawings()) { + PackagePartName drName = PackagingURIHelper.createPartName( + VML_DRAWINGS.getFileName(drawingIndex)); + part.addRelationship(drName, TargetMode.INTERNAL, VML_DRAWINGS.getRelation(), drawing.getOriginalId()); + PackagePart drPart = pkg.createPart(drName, VML_DRAWINGS.getContentType()); + + drawing.writeChildren(drPart); + out = drPart.getOutputStream(); + drawing.writeTo(out); + out.close(); + drawingIndex++; + } + } + + // If our sheet has controls, then write out those + if(sheet.getControls() != null) { + int controlIndex = 1; + for(Control control : sheet.getControls()) { + PackagePartName crName = PackagingURIHelper.createPartName( + ACTIVEX_CONTROLS.getFileName(controlIndex)); + part.addRelationship(crName, TargetMode.INTERNAL, ACTIVEX_CONTROLS.getRelation(), control.getOriginalId()); + PackagePart crPart = pkg.createPart(crName, ACTIVEX_CONTROLS.getContentType()); + + control.writeChildren(crPart); + out = crPart.getOutputStream(); + control.writeTo(out); + out.close(); + controlIndex++; + } + } } // Write shared strings and styles diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java index 518a15f56a..703133f425 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java @@ -55,6 +55,7 @@ public class TestXSSFBugs extends TestCase { */ public void test45430() throws Exception { XSSFWorkbook wb = new XSSFWorkbook(getFilePath("45430.xlsx")); + assertFalse(wb.isMacroEnabled()); assertEquals(3, wb.getNumberOfNames()); assertEquals(0, wb.getNameAt(0).getCTName().getLocalSheetId()); @@ -85,6 +86,7 @@ public class TestXSSFBugs extends TestCase { public void test45431() throws Exception { Package pkg = Package.open(getFilePath("45431.xlsm")); XSSFWorkbook wb = new XSSFWorkbook(pkg); + assertTrue(wb.isMacroEnabled()); // Check the various macro related bits can be found PackagePart vba = pkg.getPart( @@ -95,6 +97,7 @@ public class TestXSSFBugs extends TestCase { // Save and re-open, is still there Package nPkg = saveAndOpen(wb); XSSFWorkbook nwb = new XSSFWorkbook(nPkg); + assertTrue(nwb.isMacroEnabled()); vba = nPkg.getPart( PackagingURIHelper.createPartName("/xl/vbaProject.bin") );