<!-- Don't forget to update status.xml too! -->
<release version="3.5.1-beta2" date="2008-??-??">
+ <action dev="POI-DEVELOPERS" type="add">45431 - Support for .xlsm files, sufficient for simple files to be loaded by excel without warning</action>
<action dev="POI-DEVELOPERS" type="add">New class org.apache.poi.hssf.record.RecordFormatException, which DDF uses instead of the HSSF version, and the HSSF version inherits from</action>
<action dev="POI-DEVELOPERS" type="add">45431 - Partial support for .xlm files. Not quite enough for excel to load them though</action>
<action dev="POI-DEVELOPERS" type="fix">45430 - Correct named range sheet reporting when no local sheet id is given in the xml</action>
<!-- Don't forget to update changes.xml too! -->
<changes>
<release version="3.5.1-beta2" date="2008-??-??">
+ <action dev="POI-DEVELOPERS" type="add">45431 - Support for .xlsm files, sufficient for simple files to be loaded by excel without warning</action>
<action dev="POI-DEVELOPERS" type="add">New class org.apache.poi.hssf.record.RecordFormatException, which DDF uses instead of the HSSF version, and the HSSF version inherits from</action>
<action dev="POI-DEVELOPERS" type="add">45431 - Partial support for .xlm files. Not quite enough for excel to load them though</action>
<action dev="POI-DEVELOPERS" type="fix">45430 - Correct named range sheet reporting when no local sheet id is given in the xml</action>
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
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
* @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);
}
--- /dev/null
+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<XSSFActiveXData> activexBins;
+
+ public Control(InputStream is, String originalId) throws IOException {
+ readFrom(is);
+ this.originalId = originalId;
+ this.activexBins = new ArrayList<XSSFActiveXData>();
+ }
+
+ 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<XSSFActiveXData> getData() {
+ return this.activexBins;
+ }
+
+ public void addData(XSSFActiveXData activeX) {
+ this.activexBins.add(activeX);
+ }
+}
\ No newline at end of file
--- /dev/null
+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<XSSFPictureData> pictures;
+
+ public Drawing(InputStream is, String originalId) throws IOException {
+ readFrom(is);
+ this.originalId = originalId;
+ this.pictures = new ArrayList<XSSFPictureData>();
+ }
+
+ 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<XSSFPictureData> getPictures()
+ {
+ return this.pictures;
+ }
+
+ public void addPictures(XSSFPictureData picture)
+ {
+ this.pictures.add(picture);
+ }
+}
\ No newline at end of file
--- /dev/null
+/* ====================================================================
+ 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;
+}
--- /dev/null
+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();
+ }
+}
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();
}
-
}
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;
protected XSSFWorkbook workbook;
protected CommentsSource sheetComments;
protected CTMergeCells ctMergeCells;
+ protected ArrayList<Drawing> drawings;
+ protected ArrayList<Control> controls;
public static final short LeftMargin = 0;
public static final short RightMargin = 1;
public static final short HeaderMargin = 4;
public static final short FooterMargin = 5;
+ public XSSFSheet(CTSheet sheet, CTWorksheet worksheet, XSSFWorkbook workbook, CommentsSource sheetComments, ArrayList<Drawing> drawings, ArrayList<Control> controls) {
+ this(sheet, worksheet, workbook, sheetComments);
+ this.drawings = drawings;
+ this.controls = controls;
+ }
+
+ public ArrayList<Drawing> getDrawings()
+ {
+ return drawings;
+ }
+
+ public ArrayList<Control> getControls()
+ {
+ return controls;
+ }
+
public XSSFSheet(CTSheet sheet, CTWorksheet worksheet, XSSFWorkbook workbook, CommentsSource sheetComments) {
this(sheet, worksheet, workbook);
this.sheetComments = sheetComments;
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;
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;
"/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(
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 {
comments = new CommentsTable(commentsPart.getInputStream());
}
+ // Get the drawings for the sheet, if there are any
+ ArrayList<Drawing> drawings = new ArrayList<Drawing>();
+ 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<Control> controls = new ArrayList<Control>();
+ 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,
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
*/
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());
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(
// 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")
);