*/
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>
}
+ /**
+ * <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> */
// 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;
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;
+ }
}
--- /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.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
--- /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.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
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);
}
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;
return createPicture((HSSFClientAnchor) anchor, pictureIndex);
}
+ /**
+ * 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
*
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;
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;
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;
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.*;
/**
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
break;
}
- blipRecord.setRecordId( (short) ( EscherBitmapBlip.RECORD_ID_START + format ) );
+ blipRecord.setRecordId((short) (EscherBitmapBlip.RECORD_ID_START + format));
switch (format)
{
case PICTURE_TYPE_EMF:
}
+ 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)?
*/
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() )
{
See the License for the specific language governing permissions and\r
limitations under the License.\r
==================================================================== */\r
-\r
-package org.apache.poi.poifs.filesystem;\r
-\r
-import java.io.FileNotFoundException;\r
-import java.io.IOException;\r
-\r
-import org.apache.poi.util.HexDump;\r
-import org.apache.poi.util.LittleEndian;\r
+
+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;\r
import org.apache.poi.util.StringUtil;\r
\r
* Represents an Ole10Native record which is wrapped around certain binary\r
* files being embedded in OLE2 documents.\r
*\r
- * @author Rainer Schwarze\r
- */\r
-public class Ole10Native {\r
- // (the fields as they appear in the raw record:)\r
- private final int totalSize; // 4 bytes, total size of record not including this field\r
- private short flags1; // 2 bytes, unknown, mostly [02 00]\r
- private final String label; // ASCIIZ, stored in this field without the terminating zero\r
- private final String fileName; // ASCIIZ, stored in this field without the terminating zero\r
- private short flags2; // 2 bytes, unknown, mostly [00 00]\r
- // private byte unknown1Length; // 1 byte, specifying the length of the following byte array (unknown1)\r
- private byte[] unknown1; // see below\r
- private byte[] unknown2; // 3 bytes, unknown, mostly [00 00 00]\r
- private final String command; // ASCIIZ, stored in this field without the terminating zero\r
- private final int dataSize; // 4 bytes (if space), size of following buffer\r
- private final byte[] dataBuffer; // varying size, the actual native data\r
- private short flags3; // some final flags? or zero terminators?, sometimes not there\r
- public static final String OLE10_NATIVE = "\u0001Ole10Native";\r
-\r
- /**\r
- * Creates an instance of this class from an embedded OLE Object. The OLE Object is expected\r
- * to include a stream "{01}Ole10Native" which contains the actual\r
+ * @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 "{01}Ole10Native" which contains the actual
* data relevant for this class.\r
*\r
* @param poifs POI Filesystem object\r
directory.createDocumentInputStream(nativeEntry).read(data);\r
\r
return new Ole10Native(data, 0, plain);\r
- }\r
- \r
- /**\r
- * Creates an instance and fills the fields based on the data in the given buffer.\r
- *\r
- * @param data The buffer containing the Ole10Native record\r
+ }
+
+ /**
+ * 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\r
* @throws Ole10NativeException on invalid or unexcepted data format\r
*/\r
totalSize = LittleEndian.getInt(data, ofs);\r
ofs += LittleEndianConsts.INT_SIZE;\r
\r
- if (plain) {\r
- dataBuffer = new byte[totalSize-4];\r
- System.arraycopy(data, 4, dataBuffer, 0, dataBuffer.length);\r
- dataSize = totalSize - 4;\r
- \r
- byte[] oleLabel = new byte[8];\r
- System.arraycopy(dataBuffer, 0, oleLabel, 0, Math.min(dataBuffer.length, 8));\r
+ 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);\r
fileName = label;\r
command = label;\r
- } else {\r
- flags1 = LittleEndian.getShort(data, ofs);\r
- ofs += LittleEndianConsts.SHORT_SIZE;\r
- int len = getStringLength(data, ofs);\r
- label = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);\r
- ofs += len;\r
- len = getStringLength(data, ofs);\r
- fileName = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);\r
- ofs += len;\r
- flags2 = LittleEndian.getShort(data, ofs);\r
- ofs += LittleEndianConsts.SHORT_SIZE;\r
- len = LittleEndian.getUByte(data, ofs);\r
- unknown1 = new byte[len];\r
- ofs += len;\r
- len = 3;\r
- unknown2 = new byte[len];\r
- ofs += len;\r
- len = getStringLength(data, ofs);\r
- command = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);\r
- ofs += len;\r
-\r
- if (totalSize + LittleEndianConsts.INT_SIZE - ofs > LittleEndianConsts.INT_SIZE) {\r
- dataSize = LittleEndian.getInt(data, ofs);\r
- ofs += LittleEndianConsts.INT_SIZE;\r
-\r
- if (dataSize > totalSize || dataSize<0) {\r
- throw new Ole10NativeException("Invalid Ole10Native");\r
- }\r
-\r
- dataBuffer = new byte[dataSize];\r
- System.arraycopy(data, ofs, dataBuffer, 0, dataSize);\r
- ofs += dataSize;\r
-\r
- if (unknown1.length > 0) {\r
- flags3 = LittleEndian.getShort(data, ofs);\r
- ofs += LittleEndianConsts.SHORT_SIZE;\r
- } else {\r
- flags3 = 0;\r
- }\r
- } else {\r
- throw new Ole10NativeException("Invalid Ole10Native");\r
- }\r
- }\r
- }\r
-\r
+ } 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;
+ }
+ }
+
/*\r
* Helper - determine length of zero terminated string (ASCIIZ).\r
*/\r
\r
/**\r
* Returns unknown1 field - currently unknown.\r
- *\r
- * @return the unknown1\r
- */\r
- public byte[] getUnknown1() {\r
- return unknown1;\r
- }\r
-\r
- /**\r
- * Returns the unknown2 field - currently being a byte[3] - mostly {0, 0, 0}.\r
- *\r
- * @return the unknown2\r
- */\r
- public byte[] getUnknown2() {\r
- return unknown2;\r
- }\r
-\r
- /**\r
- * Returns the command field - usually the name of the file being embedded\r
- * including the full path, may be a command specified during embedding the file.\r
- *\r
+ *
+ * @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\r
*/\r
public String getCommand() {\r
* embedded. To be sure, that no data has been embedded, check whether\r
* {@link #getDataBuffer()} returns <code>null</code>.\r
*\r
- * @return the dataSize\r
- */\r
- public int getDataSize() {\r
- return dataSize;\r
- }\r
-\r
- /**\r
+ * @return the dataSize
+ */
+ public int getDataSize() {
+ return dataBuffer.length;
+ }
+
+ /**
* Returns the buffer containing the embedded file's data, or <code>null</code>\r
* if no data was embedded. Note that an embedding may provide information about\r
* the data, but the actual data is not included. (So label, filename etc. are\r
*\r
* @return the flags3\r
*/\r
- public short getFlags3() {\r
- return flags3;\r
- }\r
-}\r
+ 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;
+ }
+}
System.arraycopy(pictureDataWmf, 22, wmfNoHeader, 0, pictureDataWmf.length-22);
pictureDataOut = wb.getAllPictures().get(2).getData();
assertTrue(Arrays.equals(wmfNoHeader, pictureDataOut));
-
- FileOutputStream fos = new FileOutputStream("vect.xls");
- wb.write(fos);
- fos.close();
+
}
}
package org.apache.poi.hssf.usermodel;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
+import org.apache.poi.POIDataSamples;
import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.Ole10Native;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.ss.usermodel.ClientAnchor;
+import org.apache.poi.ss.usermodel.CreationHelper;
/**
*
public void testEmbeddedObjects() throws Exception {
HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook("ole2-embedding.xls");
- List objects = workbook.getAllEmbeddedObjects();
+ List<HSSFObjectData> objects = workbook.getAllEmbeddedObjects();
assertEquals("Wrong number of objects", 2, objects.size());
assertEquals("Wrong name for first object", "MBD06CAB431",
((HSSFObjectData)
((HSSFObjectData)
objects.get(1)).getDirectory().getName());
}
-}
+
+ public void testReallyEmbedSomething() throws Exception {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet();
+ HSSFPatriarch patriarch = sheet.createDrawingPatriarch();
+ byte[] pictureData = HSSFTestDataSamples.getTestDataFileContent("logoKarmokar4.png");
+ byte[] picturePPT = POIDataSamples.getSlideShowInstance().readFile("clock.jpg");
+ int imgIdx = wb.addPicture(pictureData, HSSFWorkbook.PICTURE_TYPE_PNG);
+ POIFSFileSystem pptPoifs = getSamplePPT();
+ int pptIdx = wb.addOlePackage(pptPoifs, "Sample-PPT", "sample.ppt", "sample.ppt");
+ POIFSFileSystem xlsPoifs = getSampleXLS();
+ int imgPPT = wb.addPicture(picturePPT, HSSFWorkbook.PICTURE_TYPE_JPEG);
+ int xlsIdx = wb.addOlePackage(xlsPoifs, "Sample-XLS", "sample.xls", "sample.xls");
+ int txtIdx = wb.addOlePackage(getSampleTXT(), "Sample-TXT", "sample.txt", "sample.txt");
+
+ int rowoffset = 5;
+ int coloffset = 5;
+
+ CreationHelper ch = wb.getCreationHelper();
+ HSSFClientAnchor anchor = (HSSFClientAnchor)ch.createClientAnchor();
+ anchor.setAnchor((short)(2+coloffset), 1+rowoffset, 0, 0, (short)(3+coloffset), 5+rowoffset, 0, 0);
+ anchor.setAnchorType(ClientAnchor.DONT_MOVE_AND_RESIZE);
+
+ patriarch.createObjectData(anchor, pptIdx, imgPPT);
+
+ anchor = (HSSFClientAnchor)ch.createClientAnchor();
+ anchor.setAnchor((short)(5+coloffset), 1+rowoffset, 0, 0, (short)(6+coloffset), 5+rowoffset, 0, 0);
+ anchor.setAnchorType(ClientAnchor.DONT_MOVE_AND_RESIZE);
+
+ patriarch.createObjectData(anchor, xlsIdx, imgIdx);
+
+ anchor = (HSSFClientAnchor)ch.createClientAnchor();
+ anchor.setAnchor((short)(3+coloffset), 10+rowoffset, 0, 0, (short)(5+coloffset), 11+rowoffset, 0, 0);
+ anchor.setAnchorType(ClientAnchor.DONT_MOVE_AND_RESIZE);
+
+ patriarch.createObjectData(anchor, txtIdx, imgIdx);
+
+ anchor = (HSSFClientAnchor)ch.createClientAnchor();
+ anchor.setAnchor((short)(1+coloffset), -2+rowoffset, 0, 0, (short)(7+coloffset), 14+rowoffset, 0, 0);
+ anchor.setAnchorType(ClientAnchor.DONT_MOVE_AND_RESIZE);
+
+ HSSFSimpleShape circle = patriarch.createSimpleShape(anchor);
+ circle.setShapeType(HSSFSimpleShape.OBJECT_TYPE_OVAL);
+ circle.setNoFill(true);
+
+ if (false) {
+ FileOutputStream fos = new FileOutputStream("embed.xls");
+ wb.write(fos);
+ fos.close();
+ }
+
+ wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ HSSFObjectData od = wb.getAllEmbeddedObjects().get(0);
+ Ole10Native ole10 = Ole10Native.createFromEmbeddedOleObject((DirectoryNode)od.getDirectory());
+ bos.reset();
+ pptPoifs.writeFilesystem(bos);
+ assertTrue(Arrays.equals(ole10.getDataBuffer(), bos.toByteArray()));
+
+ od = wb.getAllEmbeddedObjects().get(1);
+ ole10 = Ole10Native.createFromEmbeddedOleObject((DirectoryNode)od.getDirectory());
+ bos.reset();
+ xlsPoifs.writeFilesystem(bos);
+ assertTrue(Arrays.equals(ole10.getDataBuffer(), bos.toByteArray()));
+
+ od = wb.getAllEmbeddedObjects().get(2);
+ ole10 = Ole10Native.createFromEmbeddedOleObject((DirectoryNode)od.getDirectory());
+ assertTrue(Arrays.equals(ole10.getDataBuffer(), getSampleTXT()));
+
+ }
+
+ static POIFSFileSystem getSamplePPT() throws IOException {
+ // scratchpad classes are not available, so we use something pre-cooked
+ InputStream is = POIDataSamples.getSlideShowInstance().openResourceAsStream("with_textbox.ppt");
+ POIFSFileSystem poifs = new POIFSFileSystem(is);
+ is.close();
+
+ return poifs;
+ }
+
+ static POIFSFileSystem getSampleXLS() throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet();
+ sheet.createRow(5).createCell(2).setCellValue("yo dawg i herd you like embeddet objekts, so we put a ole in your ole so you can save a file while you save a file");
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ wb.write(bos);
+ POIFSFileSystem poifs = new POIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));
+
+ return poifs;
+ }
+
+ static byte[] getSampleTXT() {
+ return "All your base are belong to us".getBytes();
+ }
+}
\ No newline at end of file