Index: src/java/org/apache/poi/hpsf/ClassID.java =================================================================== --- src/java/org/apache/poi/hpsf/ClassID.java (revision 1523427) +++ src/java/org/apache/poi/hpsf/ClassID.java (working copy) @@ -31,6 +31,19 @@ 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}"); // ??? + 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}"); + + + /** *

The bytes making out the class ID in correct order, * i.e. big-endian.

@@ -63,6 +76,19 @@ bytes[i] = 0x00; } + /** + *

Creates a {@link ClassID} from a human-readable representation of the Class ID in standard + * format "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}".

+ * + * @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; iThe number of bytes occupied by this object in the byte Index: src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java (revision 1523427) +++ src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java (working copy) @@ -23,6 +23,8 @@ 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; @@ -74,6 +76,42 @@ } /** + * 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. * * @return the unique identifier for the OLE object Index: src/scratchpad/src/org/apache/poi/hslf/record/ExEmbedAtom.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/record/ExEmbedAtom.java (revision 1523427) +++ src/scratchpad/src/org/apache/poi/hslf/record/ExEmbedAtom.java (working copy) @@ -70,7 +70,7 @@ */ 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); @@ -95,7 +95,7 @@ System.arraycopy(source,start+8,_data,0,len-8); // Must be at least 4 bytes long - if(_data.length < 7) { + 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 @@ 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: src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java (revision 1523427) +++ src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java (working copy) @@ -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.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.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 @@ // 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 @@ * @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 @@ 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 @@ * @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,19 +993,8 @@ * @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 { @@ -1068,9 +1001,158 @@ 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 olemap = getOleMap(); + ClassID classID = null; + for (Map.Entry 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 getOleMap() { + Map olemap = new HashMap(); + 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: src/scratchpad/testcases/org/apache/poi/hslf/model/TestOleEmbedding.java =================================================================== --- src/scratchpad/testcases/org/apache/poi/hslf/model/TestOleEmbedding.java (revision 1523427) +++ src/scratchpad/testcases/org/apache/poi/hslf/model/TestOleEmbedding.java (working copy) @@ -17,6 +17,14 @@ 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.FileOutputStream; +import java.io.InputStream; +import java.util.Arrays; + import junit.framework.TestCase; import org.apache.poi.hslf.HSLFSlideShow; @@ -26,6 +34,8 @@ import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.util.IOUtils; import org.apache.poi.POIDataSamples; public final class TestOleEmbedding extends TestCase { @@ -82,4 +92,44 @@ } 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 poiData = new POIFSFileSystem(is); + is.close(); + + int oleObjectId = ppt.addEmbed(poiData); + + Slide slide = ppt.createSlide(); + OLEShape oleShape1 = new OLEShape(pictId); + oleShape1.setObjectID(oleObjectId); + slide.addShape(oleShape1); + oleShape1.setAnchor(new Rectangle2D.Double(100,100,100,100)); + + if (false) { + FileOutputStream fos = new FileOutputStream("embed.ppt"); + ppt.write(fos); + fos.close(); + } + + 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(); + poiData.writeFilesystem(bos); + byte expData[] = bos.toByteArray(); + + assertTrue(Arrays.equals(expData, compData)); + + } }