More work on bug #45431 - Support for .xlsm files, sufficient for simple files to be loaded by excel without warning

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@680871 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2008-07-29 23:03:25 +00:00
parent d7f86f111f
commit a89961ba81
12 changed files with 460 additions and 12 deletions

View File

@ -37,6 +37,7 @@
<!-- 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>

View File

@ -34,6 +34,7 @@
<!-- 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Drawing> drawings;
protected ArrayList<Control> 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<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;

View File

@ -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
"image/x-emf", // TODO
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
"/xl/image#.xml",
"/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<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,
@ -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

View File

@ -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")
);