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));
+
+ }
}