2010-05-17 56 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

相关问题