]> source.dussan.org Git - poi.git/commitdiff
Bugzilla 55578 - Support embedding OLE1.0 packages in HSSF
authorYegor Kozlov <yegor@apache.org>
Sun, 13 Oct 2013 07:39:40 +0000 (07:39 +0000)
committerYegor Kozlov <yegor@apache.org>
Sun, 13 Oct 2013 07:39:40 +0000 (07:39 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1531623 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/hpsf/ClassID.java
src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java
src/java/org/apache/poi/hssf/record/FtCfSubRecord.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/record/FtPioGrbitSubRecord.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/record/SubRecord.java
src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java
src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
src/java/org/apache/poi/poifs/filesystem/EntryUtils.java
src/java/org/apache/poi/poifs/filesystem/Ole10Native.java
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java
src/testcases/org/apache/poi/hssf/usermodel/TestOLE2Embeding.java

index dd623b8b7731a1696cd2b16d633c20720ec810d8..9fab2227a82d0126c77668324177182f639bca53 100644 (file)
@@ -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> */
index 77e9fd86d61899bde062b75a628ea89153466328..88fb10005cd39b8e9999e951418eb75458e99d54 100644 (file)
@@ -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 (file)
index 0000000..9543833
--- /dev/null
@@ -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 (file)
index 0000000..8562a05
--- /dev/null
@@ -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
index fd54acc0a7ae4c7cdc5e5077f10e4bd23f39d8e3..35b62c36c54fc9f2f4af13637af55a91952e9766 100644 (file)
@@ -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);
        }
index c508c51ed0dc57f1976c9d372bb42dee19b90006..6d61b22d5653801e1982947faaafe2cdc0a7e7f9 100644 (file)
 
 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;
@@ -193,6 +201,87 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing {
         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
      *
index 9e42beab98b013ff35abccbd9c357333d12031cf..2ab0f6b2c5c1f4d22073c1e2782aa079911b2489 100644 (file)
 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)?
      */
index 60f2b8d38cba63719fd4446371025f9f9a790b6e..4bce0641abb45e80372e980fad430dab0201ffa3 100644 (file)
@@ -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() )
             {
index fbc58ab0c44381580f71eae2118b53948dcb3b79..2c950da3ff06951b4b1066ad1910d525fa30dcb0 100644 (file)
    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
@@ -29,27 +31,27 @@ import org.apache.poi.util.StringUtil;
  * 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 &quot;{01}Ole10Native&quot; 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 &quot;{01}Ole10Native&quot; which contains the actual
    * data relevant for this class.\r
    *\r
    * @param poifs POI Filesystem object\r
@@ -87,12 +89,22 @@ public class Ole10Native {
      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
@@ -117,61 +129,57 @@ public class Ole10Native {
     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
@@ -234,26 +242,17 @@ public class Ole10Native {
 \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
@@ -265,13 +264,13 @@ public class Ole10Native {
    * 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
@@ -288,7 +287,89 @@ public class Ole10Native {
    *\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;
+  }
+}
index de8ffabf8bc42eff6f3ce25baf1e938643acae48..de2b683a7e371c7293acc76d6d6b7c685a51d9ab 100644 (file)
@@ -262,10 +262,7 @@ public final class TestHSSFPicture extends BaseTestPicture {
         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();
+
     }
 
 }
index 661b1d51f11f4b5d26eaa07e1ac89b6abe6bc9ca..22700b28cca5082fd3ea7553bbb50560f4b8450c 100644 (file)
 
 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;
 
 /**
  * 
@@ -39,7 +51,7 @@ public final class TestOLE2Embeding extends TestCase {
     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)
@@ -48,5 +60,101 @@ public final class TestOLE2Embeding extends TestCase {
                 ((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