2010-05-17 59 views
6

我想使用apache poi將excel表單嵌入到演示文稿(PPT)中。我們應該怎麼做?如果有人知道,請幫助我。使用apache poi將HSSF(excel)嵌入HSLF(ppt)

+0

目前我只管理像它在該[鏈接](HTTP真實描述修改一個已經嵌入excel工作表://apache-poi.1045710.n5.nabble。 COM /變通的解決方案對嵌入-工作表到幻燈片-使用-POI-td2300252.html)。還有一個類似的[POI錯誤](https://issues.apache.org/bugzilla/show_bug.cgi?id=44939)指出,ole嵌入是不完整的支持。另一方面自由辦公室似乎支持它 - 不確定,如果他們使用POI寫作和他們定製了多少... – kiwiwings 2013-05-04 20:04:43

回答

5

這花了我一段時間才能找出各部分是如何屬於一起...

嵌入可以通過兩種方式來完成:

  • by updating an already embedded worksheet
    • 臨:只需要調用ObjectData.get/setData()和您的工作已完成
    • Con:如果您想要嵌入多個OLE對象,該怎麼辦?
  • 或者你可以嵌入從頭元素(見下文)

像往常一樣,當我揣摩,如何實現某種功能的POI,我比較自由報Office文件的結果在這種情況下幾部分必須創建/修改:

    在PowerPoint對象
  • ...
    • 的emebedded對象的二進制數據被存儲爲一個根級記錄。大部分根記錄是position dependent,所以當新記錄(例如,記錄)被重新計算時,您需要重新計算它們的所有偏移量。幻燈片,創建
    • 二進制數據記錄是由嵌入記錄引用的Document記錄
    • 裏面......和混淆多一點,這個文件引用由實際形狀對象引用一次
  • 在嵌入的工作表的POIFS
  • ...
    • Ole Stream條目需要創建
    • 和根節點有嵌入的文件類型的類ID
    • 除此之外,還有在嵌入工作簿對象neccessary和數據本身並沒有改變,是一個自包含的excel文件

而且我用兩個實用信息類:BiffViewerPOIFSLister

由於這僅僅是一個概念證明,它遠非完整。 有關嵌入元素表示的進一步修改,您需要諮詢the spec

爲嵌入式對象創建預覽圖像仍有一個未解決的問題。您可能希望使用中性圖像,只要用戶激活(雙擊)該ole對象,就會被替換。另一種方法是使用jodconverter,但比POI方法有點沒有意義。

(測試POI3.9/Libre Office 4。0/MS Excel查看/ MS Office 2003)中

import java.awt.geom.Rectangle2D; 
import java.io.*; 
import java.lang.reflect.Field; 

import org.apache.poi.POIDocument; 
import org.apache.poi.ddf.*; 
import org.apache.poi.hpsf.ClassID; 
import org.apache.poi.hslf.HSLFSlideShow; 
import org.apache.poi.hslf.exceptions.HSLFException; 
import org.apache.poi.hslf.model.*; 
import org.apache.poi.hslf.model.Picture; 
import org.apache.poi.hslf.model.Slide; 
import org.apache.poi.hslf.record.*; 
import org.apache.poi.hslf.usermodel.*; 
import org.apache.poi.hssf.usermodel.*; 
import org.apache.poi.hwpf.HWPFDocument; 
import org.apache.poi.hwpf.usermodel.*; 
import org.apache.poi.poifs.filesystem.*; 
import org.apache.poi.util.*; 

public class PoiOleXlsInPpt { 
    static final OleType EXCEL97  = new OleType("{00020820-0000-0000-C000-000000000046}"); 
    static final OleType EXCEL95  = new OleType("{00020810-0000-0000-C000-000000000046}"); 
    static final OleType WORD97  = new OleType("{00020906-0000-0000-C000-000000000046}"); 
    static final OleType WORD95  = new OleType("{00020900-0000-0000-C000-000000000046}"); 
    static final OleType POWERPOINT97 = new OleType("{64818D10-4F9B-11CF-86EA-00AA00B929E8}"); 
    static final OleType POWERPOINT95 = new OleType("{EA7BAE70-FB3B-11CD-A903-00AA00510EA3}"); 

    static class OleType { 
     final String classId; 
     OleType(String classId) { 
      this.classId = classId; 
     } 
     ClassID getClassID() { 
      ClassID cls = new ClassID(); 
      byte clsBytes[] = cls.getBytes(); 
      String clsStr = classId.replaceAll("[{}-]", ""); 
      for (int i=0; i<clsStr.length(); i+=2) { 
       clsBytes[i/2] = (byte)Integer.parseInt(clsStr.substring(i, i+2), 16); 
      } 
      return cls; 
     } 
    } 

    public static void main(String[] args) throws Exception { 
     HSLFSlideShow _hslfSlideShow = HSLFSlideShow.create(); 
     SlideShow ppt = new SlideShow(_hslfSlideShow); 

     OLEShape oleShape1 = createOLEShape(getSampleWorkbook1(), ppt, _hslfSlideShow, EXCEL97); 
     oleShape1.setAnchor(new Rectangle2D.Double(100,100,100,100)); 
     OLEShape oleShape2 = createOLEShape(getSampleWorkbook2(), ppt, _hslfSlideShow, EXCEL97); 
     oleShape2.setAnchor(new Rectangle2D.Double(300,300,100,100)); 
     OLEShape oleShape3 = createOLEShape(getSampleDocument(), ppt, _hslfSlideShow, WORD97); 
     oleShape3.setAnchor(new Rectangle2D.Double(300,100,100,100)); 

     // create and link visuals to the ole data 
     Slide slide = ppt.createSlide(); 
     slide.addShape(oleShape1); 
     slide.addShape(oleShape2); 
     slide.addShape(oleShape3); 

     FileOutputStream fos = new FileOutputStream("ole_xls_in_ppt_out2.ppt"); 
     ppt.write(fos); 
     fos.close(); 
    } 

    static OLEShape createOLEShape(
      POIDocument sample 
     , SlideShow ppt 
     , HSLFSlideShow _hslfSlideShow 
     , OleType oleType 
    ) throws IOException { 
     // generate a preview image 
     int prevIdx = generatePreview(ppt, sample); 

     // add the data to the SlideShow 
     ExEmbed eeEmbed = addOleDataToDocumentRecord(ppt); 
     ExOleObjStg exOleObjStg = addOleDataToRootRecords(_hslfSlideShow, sample, oleType); 
     eeEmbed.getExOleObjAtom().setObjStgDataRef(exOleObjStg.getPersistId()); 

     OLEShape oleShape = new OLEShape(prevIdx); 
     linkOleDataToShape(oleShape, eeEmbed); 

     return oleShape; 
    } 

    static POIDocument getSampleWorkbook1() { 
     HSSFWorkbook wb = new HSSFWorkbook(); 
     HSSFSheet sheet = wb.createSheet(); 
     sheet.createRow(1).createCell(1).setCellValue("First Workbook"); 
     return wb; 
    } 

    static POIDocument getSampleWorkbook2() { 
     HSSFWorkbook wb = new HSSFWorkbook(); 
     HSSFSheet sheet = wb.createSheet(); 
     sheet.createRow(1).createCell(1).setCellValue("Second Workbook"); 
     return wb; 
    } 

    // the sample document has apparently a problem, 
    // i.e. word inside ms powerpoint crashed, and libre office doesn't display the text 
    // it was just a test, if embedding elements != Excel works 
    // in case HWPF is interesting to you, you probably know anyway, where the error below is ... 
    static POIDocument getSampleDocument() throws IOException { 
     FileInputStream fis = new FileInputStream("src/test/resources/empty.doc"); 
     HWPFDocument doc = new HWPFDocument(fis); 
     fis.close(); 
     Range range = doc.getRange(); 
     CharacterRun run1 = range.insertAfter("Sample text");  
     run1.setFontSize(11);  
     return doc; 
    } 

    /** 
    * Generates a modified version of the sample element, which 
    * contains embedding informations 
    */ 
    static byte[] wrapOleData(POIDocument oleData, OleType oleType) { 
     try { 
      ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
      oleData.write(bos); 

      ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); 
      bos.reset(); 
      POIFSFileSystem poifs = new POIFSFileSystem(bis); 

      final String OLESTREAM_NAME = "\u0001Ole"; 
      DirectoryNode root = poifs.getRoot(); 
      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 }; 
       poifs.createDocument(new ByteArrayInputStream(oleBytes), OLESTREAM_NAME); 
      } 

      // need to set storage clsid, otherwise embedded object is not recognized 
      root.setStorageClsid(oleType.getClassID()); 

      poifs.writeFilesystem(bos); 
      return bos.toByteArray(); 
     } catch (IOException e) { 
      throw new RuntimeException("wth?!", e); 
     } 
    } 


    /** 
    * to be defined, how to create a preview image 
    * for a start, I've taken just a dummy image, which will be 
    * replaced, when the user activates the ole object 
    * 
    * not really an alternativ: 
    * http://stackoverflow.com/questions/16704624/how-to-print-a-workbook-file-made-using-apache-poi-and-java 
    * 
    * @return image index of the preview image 
    */ 
    static int generatePreview(SlideShow ppt, POIDocument oleData) { 
     try { 
      FileInputStream fis = new FileInputStream("src/test/resources/dilbert-2011-09-28-powerpoint.jpg"); 
      byte previewImg[] = IOUtils.toByteArray(fis); 
      fis.close(); 
      return ppt.addPicture(previewImg, Picture.JPEG); 
     } catch (IOException e) { 
      throw new RuntimeException("not really?", e); 
     } 
    } 

    static ExEmbed addOleDataToDocumentRecord(SlideShow ppt) { 
     // taken from SlideShow.addControl() 
     Document _documentRecord = ppt.getDocumentRecord(); 
     ExObjList lst = _documentRecord.getExObjList(); 
     if (lst == null) { 
      lst = new ExObjList(); 
      _documentRecord.addChildAfter(lst, _documentRecord.getDocumentAtom()); 
      try { 
       Field f = Document.class.getDeclaredField("exObjList"); 
       f.setAccessible(true); 
       f.set(_documentRecord, lst); 
      } catch (Exception e) { 
       throw new RuntimeException("not here", e); 
      } 
     } 
     ExObjListAtom objAtom = lst.getExObjListAtom(); 
     // increment the object ID seed 
     int objectId = (int) objAtom.getObjectIDSeed() + 1; 
     objAtom.setObjectIDSeed(objectId); 

     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(); 
     try { 
      Field f = ExEmbedAtom.class.getDeclaredField("_data"); 
      f.setAccessible(true); 
      f.set(eeEmbed, new byte[]{0,0,0,0,1/*CantLockServerB*/,0,0,0}); 
      // oops, there seems to be an error in the default constructor ... 
      // should be 8 and not 7 bytes 
      setRecordLength(eeEmbed, 8); 
     } catch (Exception e) { 
      throw new RuntimeException("trust me ;)", e); 
     } 

     ExOleObjAtom eeAtom = exEmbed.getExOleObjAtom(); 
     eeAtom.setObjID(objectId); 
     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); 

     lst.addChildAfter(exEmbed, objAtom); 

     return exEmbed; 
    } 

    static ExOleObjStg addOleDataToRootRecords(
      HSLFSlideShow _hslfSlideShow 
     , POIDocument oleData 
     , OleType oleType 
    ) throws IOException { 
     ExOleObjStg exOleObjStg = new ExOleObjStg(); 
     int slideRecordPos = _hslfSlideShow.appendRootLevelRecord(exOleObjStg); 
     exOleObjStg.setPersistId(slideRecordPos); 
     exOleObjStg.setData(wrapOleData(oleData, oleType)); 

     // taken from SlideShow.createSlide 
     Record _records[] = _hslfSlideShow.getRecords(); 

     // Add the new OLE record 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(); 
     } 

     // the ole objects needs to know its position within 
     // the root records, because it will be later accessed 
     // via its index from the shape 
     int psrId = usr.getMaxPersistWritten() + 1; 
     exOleObjStg.setPersistId(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) 
     exOleObjStg.setLastOnDiskOffset(slideOffset); 
     ptr.addSlideLookup(psrId, slideOffset); 

     return exOleObjStg; 
    } 

    static void linkOleDataToShape(OLEShape oleShape, ExEmbed exEmbed) { 
     oleShape.setEscherProperty(EscherProperties.BLIP__PICTUREID, exEmbed.getExOleObjAtom().getObjID()); 

     EscherSpRecord spRecord = oleShape.getSpContainer().getChildById(EscherSpRecord.RECORD_ID); 
     spRecord.setFlags(spRecord.getFlags()|EscherSpRecord.FLAG_OLESHAPE); 

     // ExObjRefAtom is not set in OLEShape 
     UnknownEscherRecord uer = new UnknownEscherRecord(); 
     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, exEmbed.getExOleObjAtom().getObjID()); // the data 
     uer.fillFields(uerData, 0, null); 

     EscherContainerRecord uerCont = new EscherContainerRecord(); 
     uerCont.setRecordId((short)RecordTypes.EscherClientData); 
     uerCont.setVersion((short)0x000F); // yes, we are still a container ... 
     uerCont.addChildRecord(uer); 

     oleShape.getSpContainer().addChildRecord(uerCont); 
    } 

    static void setRecordLength(Record record, int len) throws NoSuchFieldException, IllegalAccessException { 
     Field f = record.getClass().getDeclaredField("_header"); 
     f.setAccessible(true); 
     byte _header[] = (byte[])f.get(record); 
     LittleEndian.putInt(_header, 4, len); 
     f.set(record, _header); 
    } 
} 
+1

修補程序可以在錯誤報告[#55579](https://issues.apache .ORG/bugzilla的/ show_bug.cgi?ID = 55579) – kiwiwings 2013-09-21 23:58:40

相關問題