]> source.dussan.org Git - poi.git/commitdiff
Bug 55579 - [PATCH] Patch for add/embed OLE objects into HSLF
authorAndreas Beeker <kiwiwings@apache.org>
Thu, 26 Dec 2013 00:46:16 +0000 (00:46 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Thu, 26 Dec 2013 00:46:16 +0000 (00:46 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1553435 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/hpsf/ClassID.java
src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java
src/scratchpad/src/org/apache/poi/hslf/record/ExEmbedAtom.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java
src/scratchpad/testcases/org/apache/poi/hslf/model/TestOleEmbedding.java

index 9fab2227a82d0126c77668324177182f639bca53..a49d9df588ccbd24f86f3f2d0fb48f4a337a3fce 100644 (file)
@@ -34,6 +34,12 @@ public class ClassID
     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}"); // ???
+    public static final ClassID EXCEL97      = new ClassID("{00020820-0000-0000-C000-000000000046}");
+    public static final ClassID EXCEL95      = new ClassID("{00020810-0000-0000-C000-000000000046}");
+    public static final ClassID WORD97       = new ClassID("{00020906-0000-0000-C000-000000000046}");
+    public static final ClassID WORD95       = new ClassID("{00020900-0000-0000-C000-000000000046}");
+    public static final ClassID POWERPOINT97 = new ClassID("{64818D10-4F9B-11CF-86EA-00AA00B929E8}");
+    public static final ClassID POWERPOINT95 = new ClassID("{EA7BAE70-FB3B-11CD-A903-00AA00510EA3}");
        
        
     /**
index 717f7a06be15c360ecbd8c2502638d8279446218..659b340e1c46eb9d202956829b812f36937f2927 100644 (file)
@@ -23,6 +23,8 @@ import org.apache.poi.hslf.usermodel.ObjectData;
 import org.apache.poi.hslf.record.ExObjList;
 import org.apache.poi.hslf.record.Record;
 import org.apache.poi.hslf.record.ExEmbed;
+import org.apache.poi.hslf.record.RecordTypes;
+import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.POILogger;
 
 
@@ -73,6 +75,42 @@ public final class OLEShape extends Picture {
         return getEscherProperty(EscherProperties.BLIP__PICTUREID);
     }
 
+    /**
+     * Set the unique identifier for the OLE object and
+     * register it in the necessary structures
+     * 
+     * @param objectId the unique identifier for the OLE object
+     */
+    public void setObjectID(int objectId){
+       setEscherProperty(EscherProperties.BLIP__PICTUREID, objectId);
+       
+       EscherContainerRecord ecr = getSpContainer();
+       EscherSpRecord spRecord = ecr.getChildById(EscherSpRecord.RECORD_ID);
+        spRecord.setFlags(spRecord.getFlags()|EscherSpRecord.FLAG_OLESHAPE);
+
+        EscherContainerRecord uerCont = ecr.getChildById((short)RecordTypes.EscherClientData);
+        if (uerCont == null) {
+               uerCont = new EscherContainerRecord();
+               ecr.addChildRecord(uerCont);
+        }
+        uerCont.setRecordId((short)RecordTypes.EscherClientData);
+        uerCont.setVersion((short)0x000F); // yes, we are still a container ...
+
+        UnknownEscherRecord uer = uerCont.getChildById((short)RecordTypes.ExObjRefAtom.typeID);
+        if (uer == null) {
+               uer = new UnknownEscherRecord();
+               uerCont.addChildRecord(uer);
+        }
+        
+        byte uerData[] = new byte[12];
+        LittleEndian.putShort( uerData, 0, (short)0 ); // options = 0
+        LittleEndian.putShort( uerData, 2, (short)RecordTypes.ExObjRefAtom.typeID); // recordId
+        LittleEndian.putInt( uerData, 4, 4 ); // remaining bytes
+        LittleEndian.putInt( uerData, 8, objectId ); // the data
+        uer.fillFields(uerData, 0, null);        
+    }
+    
+    
     /**
      * Returns unique identifier for the OLE object.
      *
index d2344c4154d05546ad691a66538d312a6ad69d9b..94aba471de8662c0060794ef702b9efb17903d14 100644 (file)
@@ -70,7 +70,7 @@ public class ExEmbedAtom extends RecordAtom {
      */
     protected ExEmbedAtom() {
         _header = new byte[8];
-        _data = new byte[7];
+        _data = new byte[8];
 
         LittleEndian.putShort(_header, 2, (short)getRecordType());
         LittleEndian.putInt(_header, 4, _data.length);
@@ -94,8 +94,8 @@ public class ExEmbedAtom extends RecordAtom {
         _data = new byte[len-8];
         System.arraycopy(source,start+8,_data,0,len-8);
 
-        // Must be at least 4 bytes long
-        if(_data.length < 7) {
+        // Must be at least 8 bytes long
+        if(_data.length < 8) {
                throw new IllegalArgumentException("The length of the data for a ExEmbedAtom must be at least 4 bytes, but was only " + _data.length);
         }
     }
@@ -120,6 +120,10 @@ public class ExEmbedAtom extends RecordAtom {
         return _data[4] != 0;
     }
 
+    public void setCantLockServerB(boolean cantBeLocked) {
+       _data[4] = (byte)(cantBeLocked ? 1 : 0);
+    }
+    
     /**
      * Gets whether it is not required to send the dimensions to the embedded object.
      *
index ca7d839ebb4138d62d8a72583134271906cc1577..edb46d06c192f735c88d1ff9768d97860368595c 100644 (file)
@@ -18,6 +18,7 @@
 package org.apache.poi.hslf.usermodel;
 
 import java.awt.Dimension;
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -30,6 +31,7 @@ import org.apache.poi.ddf.EscherBSERecord;
 import org.apache.poi.ddf.EscherContainerRecord;
 import org.apache.poi.ddf.EscherOptRecord;
 import org.apache.poi.ddf.EscherRecord;
+import org.apache.poi.hpsf.ClassID;
 import org.apache.poi.hslf.HSLFSlideShow;
 import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
 import org.apache.poi.hslf.exceptions.HSLFException;
@@ -38,6 +40,8 @@ import org.apache.poi.hslf.model.Notes;
 import org.apache.poi.hslf.model.Slide;
 import org.apache.poi.hslf.record.*;
 import org.apache.poi.hslf.record.SlideListWithText.SlideAtomsSet;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
 
@@ -723,53 +727,10 @@ public final class SlideShow {
 
                // Add the core records for this new Slide to the record tree
                org.apache.poi.hslf.record.Slide slideRecord = slide.getSlideRecord();
-               int slideRecordPos = _hslfSlideShow.appendRootLevelRecord(slideRecord);
-               _records = _hslfSlideShow.getRecords();
-
-               // Add the new Slide into the PersistPtr stuff
-               int offset = 0;
-               int slideOffset = 0;
-               PersistPtrHolder ptr = null;
-               UserEditAtom usr = null;
-               for (int i = 0; i < _records.length; i++) {
-                       Record record = _records[i];
-                       ByteArrayOutputStream out = new ByteArrayOutputStream();
-                       try {
-                               record.writeOut(out);
-                       } catch (IOException e) {
-                               throw new HSLFException(e);
-                       }
-
-                       // Grab interesting records as they come past
-                       if (_records[i].getRecordType() == RecordTypes.PersistPtrIncrementalBlock.typeID) {
-                               ptr = (PersistPtrHolder) _records[i];
-                       }
-                       if (_records[i].getRecordType() == RecordTypes.UserEditAtom.typeID) {
-                               usr = (UserEditAtom) _records[i];
-                       }
-
-                       if (i == slideRecordPos) {
-                               slideOffset = offset;
-                       }
-                       offset += out.size();
-               }
-
-               // persist ID is UserEditAtom.maxPersistWritten + 1
-               int psrId = usr.getMaxPersistWritten() + 1;
+               int psrId = addPersistentObject(slideRecord);
                sp.setRefID(psrId);
                slideRecord.setSheetId(psrId);
-
-               // Last view is now of the slide
-               usr.setLastViewType((short) UserEditAtom.LAST_VIEW_SLIDE_VIEW);
-               usr.setMaxPersistWritten(psrId); // increment the number of persit
-                                                                                // objects
-
-               // Add the new slide into the last PersistPtr
-               // (Also need to tell it where it is)
-               slideRecord.setLastOnDiskOffset(slideOffset);
-               ptr.addSlideLookup(sp.getRefID(), slideOffset);
-               logger.log(POILogger.INFO, "New slide ended up at " + slideOffset);
-
+               
                slide.setMasterSheet(_masters[0]);
                // All done and added
                return slide;
@@ -978,16 +939,6 @@ public final class SlideShow {
         * @return 0-based index of the movie
         */
        public int addMovie(String path, int type) {
-               ExObjList lst = (ExObjList) _documentRecord.findFirstOfType(RecordTypes.ExObjList.typeID);
-               if (lst == null) {
-                       lst = new ExObjList();
-                       _documentRecord.addChildAfter(lst, _documentRecord.getDocumentAtom());
-               }
-
-               ExObjListAtom objAtom = lst.getExObjListAtom();
-               // increment the object ID seed
-               int objectId = (int) objAtom.getObjectIDSeed() + 1;
-               objAtom.setObjectIDSeed(objectId);
                ExMCIMovie mci;
                switch (type) {
                        case MovieShape.MOVIE_MPEG:
@@ -1000,11 +951,13 @@ public final class SlideShow {
                                throw new IllegalArgumentException("Unsupported Movie: " + type);
                }
 
-               lst.appendChildRecord(mci);
                ExVideoContainer exVideo = mci.getExVideo();
-               exVideo.getExMediaAtom().setObjectId(objectId);
                exVideo.getExMediaAtom().setMask(0xE80000);
                exVideo.getPathAtom().setText(path);
+
+               int objectId = addToObjListAtom(mci);
+               exVideo.getExMediaAtom().setObjectId(objectId);
+               
                return objectId;
        }
 
@@ -1019,27 +972,18 @@ public final class SlideShow {
         * @return 0-based index of the control
         */
        public int addControl(String name, String progId) {
-               ExObjList lst = (ExObjList) _documentRecord.findFirstOfType(RecordTypes.ExObjList.typeID);
-               if (lst == null) {
-                       lst = new ExObjList();
-                       _documentRecord.addChildAfter(lst, _documentRecord.getDocumentAtom());
-               }
-               ExObjListAtom objAtom = lst.getExObjListAtom();
-               // increment the object ID seed
-               int objectId = (int) objAtom.getObjectIDSeed() + 1;
-               objAtom.setObjectIDSeed(objectId);
                ExControl ctrl = new ExControl();
+               ctrl.setProgId(progId);
+               ctrl.setMenuName(name);
+               ctrl.setClipboardName(name);
+               
                ExOleObjAtom oleObj = ctrl.getExOleObjAtom();
-               oleObj.setObjID(objectId);
                oleObj.setDrawAspect(ExOleObjAtom.DRAW_ASPECT_VISIBLE);
                oleObj.setType(ExOleObjAtom.TYPE_CONTROL);
                oleObj.setSubType(ExOleObjAtom.SUBTYPE_DEFAULT);
-
-               ctrl.setProgId(progId);
-               ctrl.setMenuName(name);
-               ctrl.setClipboardName(name);
-               lst.addChildAfter(ctrl, objAtom);
-
+               
+               int objectId = addToObjListAtom(ctrl);
+               oleObj.setObjID(objectId);
                return objectId;
        }
 
@@ -1049,28 +993,166 @@ public final class SlideShow {
         * @return 0-based index of the hyperlink
         */
        public int addHyperlink(Hyperlink link) {
-               ExObjList lst = (ExObjList) _documentRecord.findFirstOfType(RecordTypes.ExObjList.typeID);
-               if (lst == null) {
-                       lst = new ExObjList();
-                       _documentRecord.addChildAfter(lst, _documentRecord.getDocumentAtom());
-               }
-               ExObjListAtom objAtom = lst.getExObjListAtom();
-               // increment the object ID seed
-               int objectId = (int) objAtom.getObjectIDSeed() + 1;
-               objAtom.setObjectIDSeed(objectId);
-
                ExHyperlink ctrl = new ExHyperlink();
                ExHyperlinkAtom obj = ctrl.getExHyperlinkAtom();
-               obj.setNumber(objectId);
         if(link.getType() == Hyperlink.LINK_SLIDENUMBER) {
             ctrl.setLinkURL(link.getAddress(), 0x30);
         } else {
             ctrl.setLinkURL(link.getAddress());
         }
                ctrl.setLinkTitle(link.getTitle());
-               lst.addChildAfter(ctrl, objAtom);
+
+               int objectId = addToObjListAtom(ctrl);
                link.setId(objectId);
+               obj.setNumber(objectId);
+
+               return objectId;
+       }
+
+       /**
+        * Add a embedded object to this presentation
+        *
+        * @return 0-based index of the embedded object
+        */
+       public int addEmbed(POIFSFileSystem poiData) {
+        DirectoryNode root = poiData.getRoot();
+        
+        // prepare embedded data
+        if (new ClassID().equals(root.getStorageClsid())) {
+               // need to set class id
+               Map<String,ClassID> olemap = getOleMap();
+               ClassID classID = null;
+               for (Map.Entry<String,ClassID> entry : olemap.entrySet()) {
+                       if (root.hasEntry(entry.getKey())) {
+                               classID = entry.getValue();
+                               break;
+                       }
+               }
+               if (classID == null) {
+                       throw new IllegalArgumentException("Unsupported embedded document");                    
+               }
+               
+               root.setStorageClsid(classID);
+        }
+        
+               ExEmbed exEmbed = new ExEmbed();
+        // remove unneccessary infos, so we don't need to specify the type
+        // of the ole object multiple times
+        Record children[] = exEmbed.getChildRecords();
+        exEmbed.removeChild(children[2]);
+        exEmbed.removeChild(children[3]);
+        exEmbed.removeChild(children[4]);
+
+        ExEmbedAtom eeEmbed = exEmbed.getExEmbedAtom();
+        eeEmbed.setCantLockServerB(true);
+
+        ExOleObjAtom eeAtom = exEmbed.getExOleObjAtom();
+        eeAtom.setDrawAspect(ExOleObjAtom.DRAW_ASPECT_VISIBLE);
+        eeAtom.setType(ExOleObjAtom.TYPE_EMBEDDED);
+        // eeAtom.setSubType(ExOleObjAtom.SUBTYPE_EXCEL);
+        // should be ignored?!?, see MS-PPT ExOleObjAtom, but Libre Office sets it ...
+        eeAtom.setOptions(1226240);
+
+        ExOleObjStg exOleObjStg = new ExOleObjStg();
+        try {
+               final String OLESTREAM_NAME = "\u0001Ole";
+               if (!root.hasEntry(OLESTREAM_NAME)) {
+                   // 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 };
+                   poiData.createDocument(new ByteArrayInputStream(oleBytes), OLESTREAM_NAME);
+               }        
+
+               ByteArrayOutputStream bos = new ByteArrayOutputStream();
+               poiData.writeFilesystem(bos);
+               exOleObjStg.setData(bos.toByteArray());
+        } catch (IOException e) {
+               throw new HSLFException(e);
+        }
+        
+        int psrId = addPersistentObject(exOleObjStg);
+        exOleObjStg.setPersistId(psrId);
+        eeAtom.setObjStgDataRef(psrId);
+        
+               int objectId = addToObjListAtom(exEmbed);
+               eeAtom.setObjID(objectId);
+               return objectId;
+       }
+
+       protected int addToObjListAtom(RecordContainer exObj) {
+               ExObjList lst = (ExObjList) _documentRecord.findFirstOfType(RecordTypes.ExObjList.typeID);
+               if (lst == null) {
+                       lst = new ExObjList();
+                       _documentRecord.addChildAfter(lst, _documentRecord.getDocumentAtom());
+               }
+               ExObjListAtom objAtom = lst.getExObjListAtom();
+               // increment the object ID seed
+               int objectId = (int) objAtom.getObjectIDSeed() + 1;
+               objAtom.setObjectIDSeed(objectId);
 
+               lst.addChildAfter(exObj, objAtom);
+               
                return objectId;
        }
+
+    protected static Map<String,ClassID> getOleMap() {
+       Map<String,ClassID> olemap = new HashMap<String,ClassID>();
+       olemap.put("PowerPoint Document", ClassID.PPT_SHOW);
+       olemap.put("Workbook", ClassID.EXCEL97); // as per BIFF8 spec
+       olemap.put("WORKBOOK", ClassID.EXCEL97); // Typically from third party programs
+       olemap.put("BOOK", ClassID.EXCEL97); // Typically odd Crystal Reports exports
+       // ... to be continued
+       return olemap;
+    }
+
+    protected int addPersistentObject(PositionDependentRecord slideRecord) {
+               int slideRecordPos = _hslfSlideShow.appendRootLevelRecord((Record)slideRecord);
+               _records = _hslfSlideShow.getRecords();
+
+               // Add the new Slide into the PersistPtr stuff
+               int offset = 0;
+               int slideOffset = 0;
+               PersistPtrHolder ptr = null;
+               UserEditAtom usr = null;
+               int i = 0;
+               for (Record record : _records) {
+            // Grab interesting records as they come past
+                       int recordType = (int)record.getRecordType();
+            if (recordType == RecordTypes.PersistPtrIncrementalBlock.typeID) {
+                ptr = (PersistPtrHolder)record;
+            }
+            if (recordType == RecordTypes.UserEditAtom.typeID) {
+                usr = (UserEditAtom)record;
+            }
+
+                       if (i++ == slideRecordPos) {
+                               slideOffset = offset;
+                       }
+                       
+                       try {
+                               ByteArrayOutputStream out = new ByteArrayOutputStream();
+                               record.writeOut(out);
+                               offset += out.size();
+                       } catch (IOException e) {
+                               throw new HSLFException(e);
+                       }
+               }
+
+               // persist ID is UserEditAtom.maxPersistWritten + 1
+               int psrId = usr.getMaxPersistWritten() + 1;
+
+               // Last view is now of the slide
+               usr.setLastViewType((short) UserEditAtom.LAST_VIEW_SLIDE_VIEW);
+               // increment the number of persistent objects
+               usr.setMaxPersistWritten(psrId);
+
+               // Add the new slide into the last PersistPtr
+               // (Also need to tell it where it is)
+               slideRecord.setLastOnDiskOffset(slideOffset);
+               ptr.addSlideLookup(psrId, slideOffset);
+               logger.log(POILogger.INFO, "New slide/object ended up at " + slideOffset);
+
+               return psrId;
+    }
 }
index 28445210fb6e73998e3347a9528914e082fffa7f..a6819388d10a518dfe5c8b7b2f1ceac8393ae748 100644 (file)
 
 package org.apache.poi.hslf.model;
 
+import java.awt.geom.Rectangle2D;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.util.Arrays;
+
 import junit.framework.TestCase;
 
+import org.apache.poi.POIDataSamples;
 import org.apache.poi.hslf.HSLFSlideShow;
 import org.apache.poi.hslf.usermodel.ObjectData;
 import org.apache.poi.hslf.usermodel.PictureData;
@@ -26,7 +34,8 @@ import org.apache.poi.hslf.usermodel.SlideShow;
 import org.apache.poi.hssf.usermodel.HSSFSheet;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.hwpf.HWPFDocument;
-import org.apache.poi.POIDataSamples;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.util.IOUtils;
 
 public final class TestOleEmbedding extends TestCase {
     private static POIDataSamples _slTests = POIDataSamples.getSlideShowInstance();
@@ -82,4 +91,52 @@ public final class TestOleEmbedding extends TestCase {
         }
         assertEquals("Expected 2 OLE shapes", 2, cnt);
     }
+    
+    public void testEmbedding() throws Exception {
+       HSLFSlideShow _hslfSlideShow = HSLFSlideShow.create();
+       SlideShow ppt = new SlideShow(_hslfSlideShow);
+       
+       File pict = POIDataSamples.getSlideShowInstance().getFile("clock.jpg");
+       int pictId = ppt.addPicture(pict, Picture.JPEG);
+       
+       InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("Employee.xls");
+       POIFSFileSystem poiData1 = new POIFSFileSystem(is);
+       is.close();
+       
+       int oleObjectId1 = ppt.addEmbed(poiData1);
+       
+       Slide slide1 = ppt.createSlide();
+       OLEShape oleShape1 = new OLEShape(pictId);
+       oleShape1.setObjectID(oleObjectId1);
+       slide1.addShape(oleShape1);
+       oleShape1.setAnchor(new Rectangle2D.Double(100,100,100,100));
+       
+       // add second slide with different order in object creation
+       Slide slide2 = ppt.createSlide();
+       OLEShape oleShape2 = new OLEShape(pictId);
+
+        is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleWithImages.xls");
+        POIFSFileSystem poiData2 = new POIFSFileSystem(is);
+        is.close();
+       
+        int oleObjectId2 = ppt.addEmbed(poiData2);
+
+        oleShape2.setObjectID(oleObjectId2);
+        slide2.addShape(oleShape2);
+        oleShape2.setAnchor(new Rectangle2D.Double(100,100,100,100));
+        
+       ByteArrayOutputStream bos = new ByteArrayOutputStream();
+       ppt.write(bos);
+       
+       ppt = new SlideShow(new ByteArrayInputStream(bos.toByteArray()));
+       OLEShape comp = (OLEShape)ppt.getSlides()[0].getShapes()[0];
+       byte compData[] = IOUtils.toByteArray(comp.getObjectData().getData());
+       
+       bos.reset();
+       poiData1.writeFilesystem(bos);
+       byte expData[] = bos.toByteArray();
+       
+       assertTrue(Arrays.equals(expData, compData));
+       
+    }
 }