summaryrefslogtreecommitdiffstats
path: root/src/java
diff options
context:
space:
mode:
authorYegor Kozlov <yegor@apache.org>2013-10-13 07:39:40 +0000
committerYegor Kozlov <yegor@apache.org>2013-10-13 07:39:40 +0000
commitf6cb7f14acf47450b536174202a05ce793a2cd52 (patch)
treeebba94f3cbad64f6038ee79826ebd37a70cb6431 /src/java
parent1ffc66625a01158ff5411d7dcdac45352ca786cc (diff)
downloadpoi-f6cb7f14acf47450b536174202a05ce793a2cd52.tar.gz
poi-f6cb7f14acf47450b536174202a05ce793a2cd52.zip
Bugzilla 55578 - Support embedding OLE1.0 packages in HSSF
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1531623 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java')
-rw-r--r--src/java/org/apache/poi/hpsf/ClassID.java21
-rw-r--r--src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java14
-rw-r--r--src/java/org/apache/poi/hssf/record/FtCfSubRecord.java113
-rw-r--r--src/java/org/apache/poi/hssf/record/FtPioGrbitSubRecord.java167
-rw-r--r--src/java/org/apache/poi/hssf/record/SubRecord.java4
-rw-r--r--src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java101
-rw-r--r--src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java85
-rw-r--r--src/java/org/apache/poi/poifs/filesystem/EntryUtils.java4
-rw-r--r--src/java/org/apache/poi/poifs/filesystem/Ole10Native.java317
9 files changed, 689 insertions, 137 deletions
diff --git a/src/java/org/apache/poi/hpsf/ClassID.java b/src/java/org/apache/poi/hpsf/ClassID.java
index dd623b8b77..9fab2227a8 100644
--- a/src/java/org/apache/poi/hpsf/ClassID.java
+++ b/src/java/org/apache/poi/hpsf/ClassID.java
@@ -30,7 +30,12 @@ import org.apache.poi.util.HexDump;
*/
public class ClassID
{
-
+ public static final ClassID OLE10_PACKAGE = new ClassID("{0003000C-0000-0000-C000-000000000046}");
+ public static final ClassID PPT_SHOW = new ClassID("{64818D10-4F9B-11CF-86EA-00AA00B929E8}");
+ public static final ClassID XLS_WORKBOOK = new ClassID("{00020841-0000-0000-C000-000000000046}");
+ public static final ClassID TXT_ONLY = new ClassID("{5e941d80-bf96-11cd-b579-08002b30bfeb}"); // ???
+
+
/**
* <p>The bytes making out the class ID in correct order,
* i.e. big-endian.</p>
@@ -64,6 +69,20 @@ public class ClassID
}
+ /**
+ * <p>Creates a {@link ClassID} from a human-readable representation of the Class ID in standard
+ * format <code>"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"</code>.</p>
+ *
+ * @param externalForm representation of the Class ID represented by this object.
+ */
+ public ClassID(String externalForm) {
+ bytes = new byte[LENGTH];
+ String clsStr = externalForm.replaceAll("[{}-]", "");
+ for (int i=0; i<clsStr.length(); i+=2) {
+ bytes[i/2] = (byte)Integer.parseInt(clsStr.substring(i, i+2), 16);
+ }
+ }
+
/** <p>The number of bytes occupied by this object in the byte
* stream.</p> */
diff --git a/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java b/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java
index 77e9fd86d6..88fb10005c 100644
--- a/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java
+++ b/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java
@@ -64,7 +64,7 @@ public final class EmbeddedObjectRefSubRecord extends SubRecord {
// currently for testing only - needs review
- EmbeddedObjectRefSubRecord() {
+ public EmbeddedObjectRefSubRecord() {
field_2_unknownFormulaData = new byte[] { 0x02, 0x6C, 0x6A, 0x16, 0x01, }; // just some sample data. These values vary a lot
field_6_unknown = EMPTY_BYTE_ARRAY;
field_4_ole_classname = null;
@@ -334,4 +334,16 @@ public final class EmbeddedObjectRefSubRecord extends SubRecord {
sb.append("[/ftPictFmla]");
return sb.toString();
}
+
+ public void setUnknownFormulaData(byte[] formularData) {
+ field_2_unknownFormulaData = formularData;
+ }
+
+ public void setOleClassname(String oleClassname) {
+ field_4_ole_classname = oleClassname;
+ }
+
+ public void setStorageId(int storageId) {
+ field_5_stream_id = storageId;
+ }
}
diff --git a/src/java/org/apache/poi/hssf/record/FtCfSubRecord.java b/src/java/org/apache/poi/hssf/record/FtCfSubRecord.java
new file mode 100644
index 0000000000..95438334e1
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/FtCfSubRecord.java
@@ -0,0 +1,113 @@
+/* ====================================================================
+ 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.hssf.record;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianOutput;
+
+
+/**
+ * The FtCf structure specifies the clipboard format of the picture-type Obj record containing this FtCf.
+ */
+public final class FtCfSubRecord extends SubRecord {
+ public final static short sid = 0x07;
+ public final static short length = 0x02;
+
+ /**
+ * Specifies the format of the picture is an enhanced metafile.
+ */
+ public static short METAFILE_BIT = (short)0x0002;
+
+ /**
+ * Specifies the format of the picture is a bitmap.
+ */
+ public static short BITMAP_BIT = (short)0x0009;
+
+ /**
+ * Specifies the picture is in an unspecified format that is
+ * neither and enhanced metafile nor a bitmap.
+ */
+ public static short UNSPECIFIED_BIT = (short)0xFFFF;
+
+ private short flags = 0;
+
+ /**
+ * Construct a new <code>FtPioGrbitSubRecord</code> and
+ * fill its data with the default values
+ */
+ public FtCfSubRecord() {
+ }
+
+ public FtCfSubRecord(LittleEndianInput in, int size) {
+ if (size != length) {
+ throw new RecordFormatException("Unexpected size (" + size + ")");
+ }
+ flags = in.readShort();
+ }
+
+ /**
+ * Convert this record to string.
+ * Used by BiffViewer and other utilities.
+ */
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("[FtCf ]\n");
+ buffer.append(" size = ").append(length).append("\n");
+ buffer.append(" flags = ").append(HexDump.toHex(flags)).append("\n");
+ buffer.append("[/FtCf ]\n");
+ return buffer.toString();
+ }
+
+ /**
+ * Serialize the record data into the supplied array of bytes
+ *
+ * @param out the stream to serialize into
+ */
+ public void serialize(LittleEndianOutput out) {
+ out.writeShort(sid);
+ out.writeShort(length);
+ out.writeShort(flags);
+ }
+
+ protected int getDataSize() {
+ return length;
+ }
+
+ /**
+ * @return id of this record.
+ */
+ public short getSid()
+ {
+ return sid;
+ }
+
+ public Object clone() {
+ FtCfSubRecord rec = new FtCfSubRecord();
+ rec.flags = this.flags;
+ return rec;
+ }
+
+ public short getFlags() {
+ return flags;
+ }
+
+ public void setFlags(short flags) {
+ this.flags = flags;
+ }
+} \ No newline at end of file
diff --git a/src/java/org/apache/poi/hssf/record/FtPioGrbitSubRecord.java b/src/java/org/apache/poi/hssf/record/FtPioGrbitSubRecord.java
new file mode 100644
index 0000000000..8562a05352
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/FtPioGrbitSubRecord.java
@@ -0,0 +1,167 @@
+/* ====================================================================
+ 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.hssf.record;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianOutput;
+
+
+/**
+ * This structure appears as part of an Obj record that represents image display properties.
+ */
+public final class FtPioGrbitSubRecord extends SubRecord {
+ public final static short sid = 0x08;
+ public final static short length = 0x02;
+
+ /**
+ * A bit that specifies whether the picture's aspect ratio is preserved when rendered in
+ * different views (Normal view, Page Break Preview view, Page Layout view and printing).
+ */
+ public static int AUTO_PICT_BIT = 1 << 0;
+
+ /**
+ * A bit that specifies whether the pictFmla field of the Obj record that contains
+ * this FtPioGrbit specifies a DDE reference.
+ */
+ public static int DDE_BIT = 1 << 1;
+
+ /**
+ * A bit that specifies whether this object is expected to be updated on print to
+ * reflect the values in the cell associated with the object.
+ */
+ public static int PRINT_CALC_BIT = 1 << 2;
+
+ /**
+ * A bit that specifies whether the picture is displayed as an icon.
+ */
+ public static int ICON_BIT = 1 << 3;
+
+ /**
+ * A bit that specifies whether this object is an ActiveX control.
+ * It MUST NOT be the case that both fCtl and fDde are equal to 1.
+ */
+ public static int CTL_BIT = 1 << 4;
+
+ /**
+ * A bit that specifies whether the object data are stored in an
+ * embedding storage (= 0) or in the controls stream (ctls) (= 1).
+ */
+ public static int PRSTM_BIT = 1 << 5;
+
+ /**
+ * A bit that specifies whether this is a camera picture.
+ */
+ public static int CAMERA_BIT = 1 << 7;
+
+ /**
+ * A bit that specifies whether this picture's size has been explicitly set.
+ * 0 = picture size has been explicitly set, 1 = has not been set
+ */
+ public static int DEFAULT_SIZE_BIT = 1 << 8;
+
+ /**
+ * A bit that specifies whether the OLE server for the object is called
+ * to load the object's data automatically when the parent workbook is opened.
+ */
+ public static int AUTO_LOAD_BIT = 1 << 9;
+
+
+ private short flags = 0;
+
+ /**
+ * Construct a new <code>FtPioGrbitSubRecord</code> and
+ * fill its data with the default values
+ */
+ public FtPioGrbitSubRecord() {
+ }
+
+ public FtPioGrbitSubRecord(LittleEndianInput in, int size) {
+ if (size != length) {
+ throw new RecordFormatException("Unexpected size (" + size + ")");
+ }
+ flags = in.readShort();
+ }
+
+ /**
+ * Use one of the bitmasks MANUAL_ADVANCE_BIT ... CURSOR_VISIBLE_BIT
+ * @param bitmask
+ * @param enabled
+ */
+ public void setFlagByBit(int bitmask, boolean enabled) {
+ if (enabled) {
+ flags |= bitmask;
+ } else {
+ flags &= (0xFFFF ^ bitmask);
+ }
+ }
+
+ public boolean getFlagByBit(int bitmask) {
+ return ((flags & bitmask) != 0);
+ }
+
+ /**
+ * Convert this record to string.
+ * Used by BiffViewer and other utilities.
+ */
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("[FtPioGrbit ]\n");
+ buffer.append(" size = ").append(length).append("\n");
+ buffer.append(" flags = ").append(HexDump.toHex(flags)).append("\n");
+ buffer.append("[/FtPioGrbit ]\n");
+ return buffer.toString();
+ }
+
+ /**
+ * Serialize the record data into the supplied array of bytes
+ *
+ * @param out the stream to serialize into
+ */
+ public void serialize(LittleEndianOutput out) {
+ out.writeShort(sid);
+ out.writeShort(length);
+ out.writeShort(flags);
+ }
+
+ protected int getDataSize() {
+ return length;
+ }
+
+ /**
+ * @return id of this record.
+ */
+ public short getSid()
+ {
+ return sid;
+ }
+
+ public Object clone() {
+ FtPioGrbitSubRecord rec = new FtPioGrbitSubRecord();
+ rec.flags = this.flags;
+ return rec;
+ }
+
+ public short getFlags() {
+ return flags;
+ }
+
+ public void setFlags(short flags) {
+ this.flags = flags;
+ }
+} \ No newline at end of file
diff --git a/src/java/org/apache/poi/hssf/record/SubRecord.java b/src/java/org/apache/poi/hssf/record/SubRecord.java
index fd54acc0a7..35b62c36c5 100644
--- a/src/java/org/apache/poi/hssf/record/SubRecord.java
+++ b/src/java/org/apache/poi/hssf/record/SubRecord.java
@@ -59,6 +59,10 @@ public abstract class SubRecord {
return new LbsDataSubRecord(in, secondUShort, cmoOt);
case FtCblsSubRecord.sid:
return new FtCblsSubRecord(in, secondUShort);
+ case FtPioGrbitSubRecord.sid:
+ return new FtPioGrbitSubRecord(in, secondUShort);
+ case FtCfSubRecord.sid:
+ return new FtCfSubRecord(in, secondUShort);
}
return new UnknownSubRecord(in, sid, secondUShort);
}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java
index c508c51ed0..6d61b22d56 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java
@@ -17,27 +17,35 @@
package org.apache.poi.hssf.usermodel;
+import java.io.FileNotFoundException;
import java.util.*;
import org.apache.poi.ddf.*;
import org.apache.poi.hssf.model.DrawingManager2;
+import org.apache.poi.hssf.record.CommonObjectDataSubRecord;
+import org.apache.poi.hssf.record.EmbeddedObjectRefSubRecord;
+import org.apache.poi.hssf.record.EndSubRecord;
import org.apache.poi.hssf.record.EscherAggregate;
+import org.apache.poi.hssf.record.FtCfSubRecord;
+import org.apache.poi.hssf.record.FtPioGrbitSubRecord;
import org.apache.poi.hssf.record.NoteRecord;
+import org.apache.poi.hssf.record.ObjRecord;
import org.apache.poi.hssf.util.CellReference;
+import org.apache.poi.poifs.filesystem.DirectoryEntry;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.ss.usermodel.Chart;
-import org.apache.poi.util.POILogFactory;
-import org.apache.poi.util.POILogger;
-import org.apache.poi.util.StringUtil;
-import org.apache.poi.util.Internal;
-import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.ClientAnchor;
+import org.apache.poi.ss.usermodel.Drawing;
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.StringUtil;
/**
* The patriarch is the toplevel container for shapes in a sheet. It does
* little other than act as a container for other shapes and groups.
*/
public final class HSSFPatriarch implements HSSFShapeContainer, Drawing {
- private static POILogger log = POILogFactory.getLogger(HSSFPatriarch.class);
+ // private static POILogger log = POILogFactory.getLogger(HSSFPatriarch.class);
private final List<HSSFShape> _shapes = new ArrayList<HSSFShape>();
private final EscherSpgrRecord _spgrRecord;
@@ -194,6 +202,87 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing {
}
/**
+ * Adds a new OLE Package Shape
+ *
+ * @param anchor the client anchor describes how this picture is
+ * attached to the sheet.
+ * @param storageId the storageId returned by {@Link HSSFWorkbook.addOlePackage}
+ * @param pictureIndex the index of the picture (used as preview image) in the
+ * workbook collection of pictures.
+ *
+ * @return newly created shape
+ */
+ public HSSFObjectData createObjectData(HSSFClientAnchor anchor, int storageId, int pictureIndex) {
+ ObjRecord obj = new ObjRecord();
+
+ CommonObjectDataSubRecord ftCmo = new CommonObjectDataSubRecord();
+ ftCmo.setObjectType(CommonObjectDataSubRecord.OBJECT_TYPE_PICTURE);
+ // ftCmo.setObjectId(oleShape.getShapeId()); ... will be set by onCreate(...)
+ ftCmo.setLocked(true);
+ ftCmo.setPrintable(true);
+ ftCmo.setAutofill(true);
+ ftCmo.setAutoline(true);
+ ftCmo.setReserved1(0);
+ ftCmo.setReserved2(0);
+ ftCmo.setReserved3(0);
+ obj.addSubRecord(ftCmo);
+
+ // FtCf (pictFormat)
+ FtCfSubRecord ftCf = new FtCfSubRecord();
+ HSSFPictureData pictData = getSheet().getWorkbook().getAllPictures().get(pictureIndex-1);
+ switch (pictData.getFormat()) {
+ case HSSFWorkbook.PICTURE_TYPE_WMF:
+ case HSSFWorkbook.PICTURE_TYPE_EMF:
+ // this needs patch #49658 to be applied to actually work
+ ftCf.setFlags(FtCfSubRecord.METAFILE_BIT);
+ break;
+ case HSSFWorkbook.PICTURE_TYPE_DIB:
+ case HSSFWorkbook.PICTURE_TYPE_PNG:
+ case HSSFWorkbook.PICTURE_TYPE_JPEG:
+ case HSSFWorkbook.PICTURE_TYPE_PICT:
+ ftCf.setFlags(FtCfSubRecord.BITMAP_BIT);
+ break;
+ }
+ obj.addSubRecord(ftCf);
+ // FtPioGrbit (pictFlags)
+ FtPioGrbitSubRecord ftPioGrbit = new FtPioGrbitSubRecord();
+ ftPioGrbit.setFlagByBit(FtPioGrbitSubRecord.AUTO_PICT_BIT, true);
+ obj.addSubRecord(ftPioGrbit);
+
+ EmbeddedObjectRefSubRecord ftPictFmla = new EmbeddedObjectRefSubRecord();
+ ftPictFmla.setUnknownFormulaData(new byte[]{2, 0, 0, 0, 0});
+ ftPictFmla.setOleClassname("Paket");
+ ftPictFmla.setStorageId(storageId);
+
+ obj.addSubRecord(ftPictFmla);
+ obj.addSubRecord(new EndSubRecord());
+
+ String entryName = "MBD"+HexDump.toHex(storageId);
+ DirectoryEntry oleRoot;
+ try {
+ DirectoryNode dn = _sheet.getWorkbook().getRootDirectory();
+ if (dn == null) throw new FileNotFoundException();
+ oleRoot = (DirectoryEntry)dn.getEntry(entryName);
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException("trying to add ole shape without actually adding data first - use HSSFWorkbook.addOlePackage first", e);
+ }
+
+ // create picture shape, which need to be minimal modified for oleshapes
+ HSSFPicture shape = new HSSFPicture(null, anchor);
+ shape.setPictureIndex(pictureIndex);
+ EscherContainerRecord spContainer = shape.getEscherContainer();
+ EscherSpRecord spRecord = spContainer.getChildById(EscherSpRecord.RECORD_ID);
+ spRecord.setFlags(spRecord.getFlags() | EscherSpRecord.FLAG_OLESHAPE);
+
+ HSSFObjectData oleShape = new HSSFObjectData(spContainer, obj, oleRoot);
+ addShape(oleShape);
+ onCreate(oleShape);
+
+
+ return oleShape;
+ }
+
+ /**
* Creates a polygon
*
* @param anchor the client anchor describes how this group is attached
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
index 9e42beab98..2ab0f6b2c5 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
@@ -18,15 +18,19 @@
package org.apache.poi.hssf.usermodel;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.regex.Pattern;
import org.apache.commons.codec.digest.DigestUtils;
@@ -36,6 +40,7 @@ import org.apache.poi.ddf.EscherBitmapBlip;
import org.apache.poi.ddf.EscherBlipRecord;
import org.apache.poi.ddf.EscherMetafileBlip;
import org.apache.poi.ddf.EscherRecord;
+import org.apache.poi.hpsf.ClassID;
import org.apache.poi.hssf.OldExcelFormatException;
import org.apache.poi.hssf.model.DrawingManager2;
import org.apache.poi.hssf.model.HSSFFormulaParser;
@@ -46,7 +51,11 @@ import org.apache.poi.hssf.record.*;
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
import org.apache.poi.hssf.record.common.UnicodeString;
import org.apache.poi.hssf.util.CellReference;
+import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.EntryUtils;
+import org.apache.poi.poifs.filesystem.FilteringDirectoryNode;
+import org.apache.poi.poifs.filesystem.Ole10Native;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.formula.FormulaShifter;
import org.apache.poi.ss.formula.FormulaType;
@@ -57,10 +66,7 @@ import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.WorkbookUtil;
-import org.apache.poi.util.Configurator;
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.POILogFactory;
-import org.apache.poi.util.POILogger;
+import org.apache.poi.util.*;
/**
@@ -1190,15 +1196,15 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
if (preserveNodes) {
// Don't write out the old Workbook, we'll be doing our new one
- excepts.add("Workbook");
// If the file had an "incorrect" name for the workbook stream,
// don't write the old one as we'll use the correct name shortly
- for (String wrongName : WORKBOOK_DIR_ENTRY_NAMES) {
- excepts.add(wrongName);
- }
+ excepts.addAll(Arrays.asList(WORKBOOK_DIR_ENTRY_NAMES));
// Copy over all the other nodes to our new poifs
- copyNodes(this.directory, fs.getRoot(), excepts);
+ EntryUtils.copyNodes(
+ new FilteringDirectoryNode(this.directory, excepts)
+ , new FilteringDirectoryNode(fs.getRoot(), excepts)
+ );
// YK: preserve StorageClsid, it is important for embedded workbooks,
// see Bugzilla 47920
@@ -1623,7 +1629,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
break;
}
- blipRecord.setRecordId( (short) ( EscherBitmapBlip.RECORD_ID_START + format ) );
+ blipRecord.setRecordId((short) (EscherBitmapBlip.RECORD_ID_START + format));
switch (format)
{
case PICTURE_TYPE_EMF:
@@ -1713,6 +1719,65 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
}
+ protected static Map<String,ClassID> getOleMap() {
+ Map<String,ClassID> olemap = new HashMap<String,ClassID>();
+ olemap.put("PowerPoint Document", ClassID.PPT_SHOW);
+ for (String str : WORKBOOK_DIR_ENTRY_NAMES) {
+ olemap.put(str, ClassID.XLS_WORKBOOK);
+ }
+ // ... to be continued
+ return olemap;
+ }
+
+ public int addOlePackage(POIFSFileSystem poiData, String label, String fileName, String command)
+ throws IOException {
+ DirectoryNode root = poiData.getRoot();
+ Map<String,ClassID> olemap = getOleMap();
+ for (Map.Entry<String,ClassID> entry : olemap.entrySet()) {
+ if (root.hasEntry(entry.getKey())) {
+ root.setStorageClsid(entry.getValue());
+ break;
+ }
+ }
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ poiData.writeFilesystem(bos);
+ return addOlePackage(bos.toByteArray(), label, fileName, command);
+ }
+
+ public int addOlePackage(byte[] oleData, String label, String fileName, String command)
+ throws IOException {
+ // check if we were created by POIFS otherwise create a new dummy POIFS for storing the package data
+ if (directory == null) {
+ directory = new POIFSFileSystem().getRoot();
+ preserveNodes = true;
+ }
+
+ // get free MBD-Node
+ int storageId = 0;
+ DirectoryEntry oleDir = null;
+ do {
+ String storageStr = "MBD"+ HexDump.toHex(++storageId);
+ if (!directory.hasEntry(storageStr)) {
+ oleDir = directory.createDirectory(storageStr);
+ oleDir.setStorageClsid(ClassID.OLE10_PACKAGE);
+ }
+ } while (oleDir == null);
+
+ // the following data was taken from an example libre office document
+ // beside this "\u0001Ole" record there were several other records, e.g. CompObj,
+ // OlePresXXX, but it seems, that they aren't neccessary
+ byte oleBytes[] = { 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ oleDir.createDocument("\u0001Ole", new ByteArrayInputStream(oleBytes));
+
+ Ole10Native oleNative = new Ole10Native(label, fileName, command, oleData);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ oleNative.writeOut(bos);
+ oleDir.createDocument(Ole10Native.OLE10_NATIVE, new ByteArrayInputStream(bos.toByteArray()));
+
+ return storageId;
+ }
+
/**
* Is the workbook protected with a password (not encrypted)?
*/
diff --git a/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java b/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java
index 60f2b8d38c..4bce0641ab 100644
--- a/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java
+++ b/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java
@@ -41,8 +41,10 @@ public class EntryUtils
DirectoryEntry newTarget = null;
if ( entry.isDirectoryEntry() )
{
+ DirectoryEntry dirEntry = (DirectoryEntry)entry;
newTarget = target.createDirectory( entry.getName() );
- Iterator<Entry> entries = ( (DirectoryEntry) entry ).getEntries();
+ newTarget.setStorageClsid( dirEntry.getStorageClsid() );
+ Iterator<Entry> entries = dirEntry.getEntries();
while ( entries.hasNext() )
{
diff --git a/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java b/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java
index fbc58ab0c4..2c950da3ff 100644
--- a/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java
+++ b/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java
@@ -14,14 +14,16 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
-package org.apache.poi.poifs.filesystem;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-import org.apache.poi.util.HexDump;
-import org.apache.poi.util.LittleEndian;
+
+package org.apache.poi.poifs.filesystem;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.StringUtil;
@@ -29,27 +31,27 @@ import org.apache.poi.util.StringUtil;
* Represents an Ole10Native record which is wrapped around certain binary
* files being embedded in OLE2 documents.
*
- * @author Rainer Schwarze
- */
-public class Ole10Native {
- // (the fields as they appear in the raw record:)
- private final int totalSize; // 4 bytes, total size of record not including this field
- private short flags1; // 2 bytes, unknown, mostly [02 00]
- private final String label; // ASCIIZ, stored in this field without the terminating zero
- private final String fileName; // ASCIIZ, stored in this field without the terminating zero
- private short flags2; // 2 bytes, unknown, mostly [00 00]
- // private byte unknown1Length; // 1 byte, specifying the length of the following byte array (unknown1)
- private byte[] unknown1; // see below
- private byte[] unknown2; // 3 bytes, unknown, mostly [00 00 00]
- private final String command; // ASCIIZ, stored in this field without the terminating zero
- private final int dataSize; // 4 bytes (if space), size of following buffer
- private final byte[] dataBuffer; // varying size, the actual native data
- private short flags3; // some final flags? or zero terminators?, sometimes not there
- public static final String OLE10_NATIVE = "\u0001Ole10Native";
-
- /**
- * Creates an instance of this class from an embedded OLE Object. The OLE Object is expected
- * to include a stream &quot;{01}Ole10Native&quot; which contains the actual
+ * @author Rainer Schwarze
+ */
+public class Ole10Native {
+
+ public static final String OLE10_NATIVE = "\u0001Ole10Native";
+ protected static final String ISO1 = "ISO-8859-1";
+
+ // (the fields as they appear in the raw record:)
+ private int totalSize; // 4 bytes, total size of record not including this field
+ private short flags1 = 2; // 2 bytes, unknown, mostly [02 00]
+ private String label; // ASCIIZ, stored in this field without the terminating zero
+ private String fileName; // ASCIIZ, stored in this field without the terminating zero
+ private short flags2 = 0; // 2 bytes, unknown, mostly [00 00]
+ private short unknown1 = 3; // see below
+ private String command; // ASCIIZ, stored in this field without the terminating zero
+ private byte[] dataBuffer; // varying size, the actual native data
+ private short flags3 = 0; // some final flags? or zero terminators?, sometimes not there
+
+ /**
+ * Creates an instance of this class from an embedded OLE Object. The OLE Object is expected
+ * to include a stream &quot;{01}Ole10Native&quot; which contains the actual
* data relevant for this class.
*
* @param poifs POI Filesystem object
@@ -87,12 +89,22 @@ public class Ole10Native {
directory.createDocumentInputStream(nativeEntry).read(data);
return new Ole10Native(data, 0, plain);
- }
-
- /**
- * Creates an instance and fills the fields based on the data in the given buffer.
- *
- * @param data The buffer containing the Ole10Native record
+ }
+
+ /**
+ * Creates an instance and fills the fields based on ... the fields
+ */
+ public Ole10Native(String label, String filename, String command, byte[] data) {
+ setLabel(label);
+ setFileName(filename);
+ setCommand(command);
+ setDataBuffer(data);
+ }
+
+ /**
+ * Creates an instance and fills the fields based on the data in the given buffer.
+ *
+ * @param data The buffer containing the Ole10Native record
* @param offset The start offset of the record in the buffer
* @throws Ole10NativeException on invalid or unexcepted data format
*/
@@ -117,61 +129,57 @@ public class Ole10Native {
totalSize = LittleEndian.getInt(data, ofs);
ofs += LittleEndianConsts.INT_SIZE;
- if (plain) {
- dataBuffer = new byte[totalSize-4];
- System.arraycopy(data, 4, dataBuffer, 0, dataBuffer.length);
- dataSize = totalSize - 4;
-
- byte[] oleLabel = new byte[8];
- System.arraycopy(dataBuffer, 0, oleLabel, 0, Math.min(dataBuffer.length, 8));
+ if (plain) {
+ dataBuffer = new byte[totalSize-4];
+ System.arraycopy(data, 4, dataBuffer, 0, dataBuffer.length);
+ // int dataSize = totalSize - 4;
+
+ byte[] oleLabel = new byte[8];
+ System.arraycopy(dataBuffer, 0, oleLabel, 0, Math.min(dataBuffer.length, 8));
label = "ole-"+ HexDump.toHex(oleLabel);
fileName = label;
command = label;
- } else {
- flags1 = LittleEndian.getShort(data, ofs);
- ofs += LittleEndianConsts.SHORT_SIZE;
- int len = getStringLength(data, ofs);
- label = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
- ofs += len;
- len = getStringLength(data, ofs);
- fileName = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
- ofs += len;
- flags2 = LittleEndian.getShort(data, ofs);
- ofs += LittleEndianConsts.SHORT_SIZE;
- len = LittleEndian.getUByte(data, ofs);
- unknown1 = new byte[len];
- ofs += len;
- len = 3;
- unknown2 = new byte[len];
- ofs += len;
- len = getStringLength(data, ofs);
- command = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
- ofs += len;
-
- if (totalSize + LittleEndianConsts.INT_SIZE - ofs > LittleEndianConsts.INT_SIZE) {
- dataSize = LittleEndian.getInt(data, ofs);
- ofs += LittleEndianConsts.INT_SIZE;
-
- if (dataSize > totalSize || dataSize<0) {
- throw new Ole10NativeException("Invalid Ole10Native");
- }
-
- dataBuffer = new byte[dataSize];
- System.arraycopy(data, ofs, dataBuffer, 0, dataSize);
- ofs += dataSize;
-
- if (unknown1.length > 0) {
- flags3 = LittleEndian.getShort(data, ofs);
- ofs += LittleEndianConsts.SHORT_SIZE;
- } else {
- flags3 = 0;
- }
- } else {
- throw new Ole10NativeException("Invalid Ole10Native");
- }
- }
- }
-
+ } else {
+ flags1 = LittleEndian.getShort(data, ofs);
+ ofs += LittleEndianConsts.SHORT_SIZE;
+
+ int len = getStringLength(data, ofs);
+ label = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
+ ofs += len;
+
+ len = getStringLength(data, ofs);
+ fileName = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
+ ofs += len;
+
+ flags2 = LittleEndian.getShort(data, ofs);
+ ofs += LittleEndianConsts.SHORT_SIZE;
+
+ unknown1 = LittleEndian.getShort(data, ofs);
+ ofs += LittleEndianConsts.SHORT_SIZE;
+
+ len = LittleEndian.getInt(data, ofs);
+ ofs += LittleEndianConsts.INT_SIZE;
+
+ command = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
+ ofs += len;
+
+ if (totalSize < ofs) {
+ throw new Ole10NativeException("Invalid Ole10Native");
+ }
+
+ int dataSize = LittleEndian.getInt(data, ofs);
+ ofs += LittleEndianConsts.INT_SIZE;
+
+ if (dataSize < 0 || totalSize - (ofs - LittleEndianConsts.INT_SIZE) < dataSize) {
+ throw new Ole10NativeException("Invalid Ole10Native");
+ }
+
+ dataBuffer = new byte[dataSize];
+ System.arraycopy(data, ofs, dataBuffer, 0, dataSize);
+ ofs += dataSize;
+ }
+ }
+
/*
* Helper - determine length of zero terminated string (ASCIIZ).
*/
@@ -234,26 +242,17 @@ public class Ole10Native {
/**
* Returns unknown1 field - currently unknown.
- *
- * @return the unknown1
- */
- public byte[] getUnknown1() {
- return unknown1;
- }
-
- /**
- * Returns the unknown2 field - currently being a byte[3] - mostly {0, 0, 0}.
- *
- * @return the unknown2
- */
- public byte[] getUnknown2() {
- return unknown2;
- }
-
- /**
- * Returns the command field - usually the name of the file being embedded
- * including the full path, may be a command specified during embedding the file.
- *
+ *
+ * @return the unknown1
+ */
+ public short getUnknown1() {
+ return unknown1;
+ }
+
+ /**
+ * Returns the command field - usually the name of the file being embedded
+ * including the full path, may be a command specified during embedding the file.
+ *
* @return the command
*/
public String getCommand() {
@@ -265,13 +264,13 @@ public class Ole10Native {
* embedded. To be sure, that no data has been embedded, check whether
* {@link #getDataBuffer()} returns <code>null</code>.
*
- * @return the dataSize
- */
- public int getDataSize() {
- return dataSize;
- }
-
- /**
+ * @return the dataSize
+ */
+ public int getDataSize() {
+ return dataBuffer.length;
+ }
+
+ /**
* Returns the buffer containing the embedded file's data, or <code>null</code>
* if no data was embedded. Note that an embedding may provide information about
* the data, but the actual data is not included. (So label, filename etc. are
@@ -288,7 +287,89 @@ public class Ole10Native {
*
* @return the flags3
*/
- public short getFlags3() {
- return flags3;
- }
-}
+ public short getFlags3() {
+ return flags3;
+ }
+
+ /**
+ * Have the contents printer out into an OutputStream, used when writing a
+ * file back out to disk (Normally, atom classes will keep their bytes
+ * around, but non atom classes will just request the bytes from their
+ * children, then chuck on their header and return)
+ */
+ public void writeOut(OutputStream out) throws IOException {
+ byte intbuf[] = new byte[LittleEndianConsts.INT_SIZE];
+ byte shortbuf[] = new byte[LittleEndianConsts.SHORT_SIZE];
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ bos.write(intbuf); // total size, will be determined later ..
+
+ LittleEndian.putShort(shortbuf, 0, getFlags1());
+ bos.write(shortbuf);
+
+ bos.write(getLabel().getBytes(ISO1));
+ bos.write(0);
+
+ bos.write(getFileName().getBytes(ISO1));
+ bos.write(0);
+
+ LittleEndian.putShort(shortbuf, 0, getFlags2());
+ bos.write(shortbuf);
+
+ LittleEndian.putShort(shortbuf, 0, getUnknown1());
+ bos.write(shortbuf);
+
+ LittleEndian.putInt(intbuf, 0, getCommand().length()+1);
+ bos.write(intbuf);
+
+ bos.write(getCommand().getBytes(ISO1));
+ bos.write(0);
+
+ LittleEndian.putInt(intbuf, 0, getDataBuffer().length);
+ bos.write(intbuf);
+
+ bos.write(getDataBuffer());
+
+ LittleEndian.putShort(shortbuf, 0, getFlags3());
+ bos.write(shortbuf);
+
+ // update total size - length of length-field (4 bytes)
+ byte data[] = bos.toByteArray();
+ totalSize = data.length - LittleEndianConsts.INT_SIZE;
+ LittleEndian.putInt(data, 0, totalSize);
+
+ out.write(data);
+ }
+
+ public void setFlags1(short flags1) {
+ this.flags1 = flags1;
+ }
+
+ public void setFlags2(short flags2) {
+ this.flags2 = flags2;
+ }
+
+ public void setFlags3(short flags3) {
+ this.flags3 = flags3;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+ public void setCommand(String command) {
+ this.command = command;
+ }
+
+ public void setUnknown1(short unknown1) {
+ this.unknown1 = unknown1;
+ }
+
+ public void setDataBuffer(byte dataBuffer[]) {
+ this.dataBuffer = dataBuffer;
+ }
+}