Bug 55579 - [PATCH] Patch for add/embed OLE objects into HSLF

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1553435 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2013-12-26 00:46:16 +00:00
parent 2788aca235
commit e34a1f9620
5 changed files with 277 additions and 90 deletions

View 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 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 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 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}");
/** /**

View 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.ExObjList;
import org.apache.poi.hslf.record.Record; import org.apache.poi.hslf.record.Record;
import org.apache.poi.hslf.record.ExEmbed; 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; import org.apache.poi.util.POILogger;
@ -73,6 +75,42 @@ public final class OLEShape extends Picture {
return getEscherProperty(EscherProperties.BLIP__PICTUREID); 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. * Returns unique identifier for the OLE object.
* *

View File

@ -70,7 +70,7 @@ public class ExEmbedAtom extends RecordAtom {
*/ */
protected ExEmbedAtom() { protected ExEmbedAtom() {
_header = new byte[8]; _header = new byte[8];
_data = new byte[7]; _data = new byte[8];
LittleEndian.putShort(_header, 2, (short)getRecordType()); LittleEndian.putShort(_header, 2, (short)getRecordType());
LittleEndian.putInt(_header, 4, _data.length); LittleEndian.putInt(_header, 4, _data.length);
@ -94,8 +94,8 @@ public class ExEmbedAtom extends RecordAtom {
_data = new byte[len-8]; _data = new byte[len-8];
System.arraycopy(source,start+8,_data,0,len-8); System.arraycopy(source,start+8,_data,0,len-8);
// Must be at least 4 bytes long // Must be at least 8 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); 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; 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. * Gets whether it is not required to send the dimensions to the embedded object.
* *

View File

@ -18,6 +18,7 @@
package org.apache.poi.hslf.usermodel; package org.apache.poi.hslf.usermodel;
import java.awt.Dimension; import java.awt.Dimension;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; 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.EscherContainerRecord;
import org.apache.poi.ddf.EscherOptRecord; import org.apache.poi.ddf.EscherOptRecord;
import org.apache.poi.ddf.EscherRecord; import org.apache.poi.ddf.EscherRecord;
import org.apache.poi.hpsf.ClassID;
import org.apache.poi.hslf.HSLFSlideShow; import org.apache.poi.hslf.HSLFSlideShow;
import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException; import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
import org.apache.poi.hslf.exceptions.HSLFException; 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.model.Slide;
import org.apache.poi.hslf.record.*; import org.apache.poi.hslf.record.*;
import org.apache.poi.hslf.record.SlideListWithText.SlideAtomsSet; 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.POILogFactory;
import org.apache.poi.util.POILogger; 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 // Add the core records for this new Slide to the record tree
org.apache.poi.hslf.record.Slide slideRecord = slide.getSlideRecord(); org.apache.poi.hslf.record.Slide slideRecord = slide.getSlideRecord();
int slideRecordPos = _hslfSlideShow.appendRootLevelRecord(slideRecord); int psrId = addPersistentObject(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;
sp.setRefID(psrId); sp.setRefID(psrId);
slideRecord.setSheetId(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]); slide.setMasterSheet(_masters[0]);
// All done and added // All done and added
return slide; return slide;
@ -978,16 +939,6 @@ public final class SlideShow {
* @return 0-based index of the movie * @return 0-based index of the movie
*/ */
public int addMovie(String path, int type) { 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; ExMCIMovie mci;
switch (type) { switch (type) {
case MovieShape.MOVIE_MPEG: case MovieShape.MOVIE_MPEG:
@ -1000,11 +951,13 @@ public final class SlideShow {
throw new IllegalArgumentException("Unsupported Movie: " + type); throw new IllegalArgumentException("Unsupported Movie: " + type);
} }
lst.appendChildRecord(mci);
ExVideoContainer exVideo = mci.getExVideo(); ExVideoContainer exVideo = mci.getExVideo();
exVideo.getExMediaAtom().setObjectId(objectId);
exVideo.getExMediaAtom().setMask(0xE80000); exVideo.getExMediaAtom().setMask(0xE80000);
exVideo.getPathAtom().setText(path); exVideo.getPathAtom().setText(path);
int objectId = addToObjListAtom(mci);
exVideo.getExMediaAtom().setObjectId(objectId);
return objectId; return objectId;
} }
@ -1019,27 +972,18 @@ public final class SlideShow {
* @return 0-based index of the control * @return 0-based index of the control
*/ */
public int addControl(String name, String progId) { 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(); ExControl ctrl = new ExControl();
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.setProgId(progId);
ctrl.setMenuName(name); ctrl.setMenuName(name);
ctrl.setClipboardName(name); ctrl.setClipboardName(name);
lst.addChildAfter(ctrl, objAtom);
ExOleObjAtom oleObj = ctrl.getExOleObjAtom();
oleObj.setDrawAspect(ExOleObjAtom.DRAW_ASPECT_VISIBLE);
oleObj.setType(ExOleObjAtom.TYPE_CONTROL);
oleObj.setSubType(ExOleObjAtom.SUBTYPE_DEFAULT);
int objectId = addToObjListAtom(ctrl);
oleObj.setObjID(objectId);
return objectId; return objectId;
} }
@ -1049,6 +993,94 @@ public final class SlideShow {
* @return 0-based index of the hyperlink * @return 0-based index of the hyperlink
*/ */
public int addHyperlink(Hyperlink link) { public int addHyperlink(Hyperlink link) {
ExHyperlink ctrl = new ExHyperlink();
ExHyperlinkAtom obj = ctrl.getExHyperlinkAtom();
if(link.getType() == Hyperlink.LINK_SLIDENUMBER) {
ctrl.setLinkURL(link.getAddress(), 0x30);
} else {
ctrl.setLinkURL(link.getAddress());
}
ctrl.setLinkTitle(link.getTitle());
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); ExObjList lst = (ExObjList) _documentRecord.findFirstOfType(RecordTypes.ExObjList.typeID);
if (lst == null) { if (lst == null) {
lst = new ExObjList(); lst = new ExObjList();
@ -1059,18 +1091,68 @@ public final class SlideShow {
int objectId = (int) objAtom.getObjectIDSeed() + 1; int objectId = (int) objAtom.getObjectIDSeed() + 1;
objAtom.setObjectIDSeed(objectId); objAtom.setObjectIDSeed(objectId);
ExHyperlink ctrl = new ExHyperlink(); lst.addChildAfter(exObj, objAtom);
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);
link.setId(objectId);
return objectId; 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;
}
} }

View File

@ -17,8 +17,16 @@
package org.apache.poi.hslf.model; 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 junit.framework.TestCase;
import org.apache.poi.POIDataSamples;
import org.apache.poi.hslf.HSLFSlideShow; import org.apache.poi.hslf.HSLFSlideShow;
import org.apache.poi.hslf.usermodel.ObjectData; import org.apache.poi.hslf.usermodel.ObjectData;
import org.apache.poi.hslf.usermodel.PictureData; 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.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hwpf.HWPFDocument; 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 { public final class TestOleEmbedding extends TestCase {
private static POIDataSamples _slTests = POIDataSamples.getSlideShowInstance(); private static POIDataSamples _slTests = POIDataSamples.getSlideShowInstance();
@ -82,4 +91,52 @@ public final class TestOleEmbedding extends TestCase {
} }
assertEquals("Expected 2 OLE shapes", 2, cnt); 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));
}
} }