Example #1
0
/**
 * This class is a friendly wrapper on top of the more scary HSLFSlideShow.
 *
 * <p>TODO: - figure out how to match notes to their correct sheet (will involve understanding
 * DocSlideList and DocNotesList) - handle Slide creation cleaner
 *
 * @author Nick Burch
 * @author Yegor kozlov
 */
public final class HSLFSlideShow implements SlideShow<HSLFShape, HSLFTextParagraph> {
  // What we're based on
  private HSLFSlideShowImpl _hslfSlideShow;

  // Pointers to the most recent versions of the core records
  // (Document, Notes, Slide etc)
  private Record[] _mostRecentCoreRecords;
  // Lookup between the PersitPtr "sheet" IDs, and the position
  // in the mostRecentCoreRecords array
  private Map<Integer, Integer> _sheetIdToCoreRecordsLookup;

  // Records that are interesting
  private Document _documentRecord;

  // Friendly objects for people to deal with
  private final List<HSLFSlideMaster> _masters = new ArrayList<HSLFSlideMaster>();
  private final List<HSLFTitleMaster> _titleMasters = new ArrayList<HSLFTitleMaster>();
  private final List<HSLFSlide> _slides = new ArrayList<HSLFSlide>();
  private final List<HSLFNotes> _notes = new ArrayList<HSLFNotes>();
  private FontCollection _fonts;

  // For logging
  private POILogger logger = POILogFactory.getLogger(this.getClass());

  /* ===============================================================
   *                       Setup Code
   * ===============================================================
   */

  /**
   * Constructs a Powerpoint document from the underlying HSLFSlideShow object. Finds the model
   * stuff from this
   *
   * @param hslfSlideShow the HSLFSlideShow to base on
   */
  public HSLFSlideShow(HSLFSlideShowImpl hslfSlideShow) {
    // Get useful things from our base slideshow
    _hslfSlideShow = hslfSlideShow;

    // Handle Parent-aware Records
    for (Record record : _hslfSlideShow.getRecords()) {
      if (record instanceof RecordContainer) {
        RecordContainer.handleParentAwareRecords((RecordContainer) record);
      }
    }

    // Find the versions of the core records we'll want to use
    findMostRecentCoreRecords();

    // Build up the model level Slides and Notes
    buildSlidesAndNotes();
  }

  /** Constructs a new, empty, Powerpoint document. */
  public HSLFSlideShow() {
    this(HSLFSlideShowImpl.create());
  }

  /** Constructs a Powerpoint document from an input stream. */
  public HSLFSlideShow(InputStream inputStream) throws IOException {
    this(new HSLFSlideShowImpl(inputStream));
  }

  /** Constructs a Powerpoint document from an POIFSFileSystem. */
  public HSLFSlideShow(NPOIFSFileSystem npoifs) throws IOException {
    this(new HSLFSlideShowImpl(npoifs));
  }

  /** Constructs a Powerpoint document from an DirectoryNode. */
  public HSLFSlideShow(DirectoryNode root) throws IOException {
    this(new HSLFSlideShowImpl(root));
  }

  /**
   * Use the PersistPtrHolder entries to figure out what is the "most recent" version of all the
   * core records (Document, Notes, Slide etc), and save a record of them. Do this by walking from
   * the oldest PersistPtr to the newest, overwriting any references found along the way with newer
   * ones
   */
  private void findMostRecentCoreRecords() {
    // To start with, find the most recent in the byte offset domain
    Map<Integer, Integer> mostRecentByBytes = new HashMap<Integer, Integer>();
    for (Record record : _hslfSlideShow.getRecords()) {
      if (record instanceof PersistPtrHolder) {
        PersistPtrHolder pph = (PersistPtrHolder) record;

        // If we've already seen any of the "slide" IDs for this
        // PersistPtr, remove their old positions
        int[] ids = pph.getKnownSlideIDs();
        for (int id : ids) {
          if (mostRecentByBytes.containsKey(id)) {
            mostRecentByBytes.remove(id);
          }
        }

        // Now, update the byte level locations with their latest values
        Map<Integer, Integer> thisSetOfLocations = pph.getSlideLocationsLookup();
        for (int id : ids) {
          mostRecentByBytes.put(id, thisSetOfLocations.get(id));
        }
      }
    }

    // We now know how many unique special records we have, so init
    // the array
    _mostRecentCoreRecords = new Record[mostRecentByBytes.size()];

    // We'll also want to be able to turn the slide IDs into a position
    // in this array
    _sheetIdToCoreRecordsLookup = new HashMap<Integer, Integer>();
    Integer[] allIDs = mostRecentByBytes.keySet().toArray(new Integer[mostRecentByBytes.size()]);
    Arrays.sort(allIDs);
    for (int i = 0; i < allIDs.length; i++) {
      _sheetIdToCoreRecordsLookup.put(allIDs[i], i);
    }

    Map<Integer, Integer> mostRecentByBytesRev =
        new HashMap<Integer, Integer>(mostRecentByBytes.size());
    for (Map.Entry<Integer, Integer> me : mostRecentByBytes.entrySet()) {
      mostRecentByBytesRev.put(me.getValue(), me.getKey());
    }

    // Now convert the byte offsets back into record offsets
    for (Record record : _hslfSlideShow.getRecords()) {
      if (!(record instanceof PositionDependentRecord)) continue;

      PositionDependentRecord pdr = (PositionDependentRecord) record;
      int recordAt = pdr.getLastOnDiskOffset();

      Integer thisID = mostRecentByBytesRev.get(recordAt);

      if (thisID == null) continue;

      // Bingo. Now, where do we store it?
      int storeAt = _sheetIdToCoreRecordsLookup.get(thisID);

      // Tell it its Sheet ID, if it cares
      if (pdr instanceof PositionDependentRecordContainer) {
        PositionDependentRecordContainer pdrc = (PositionDependentRecordContainer) record;
        pdrc.setSheetId(thisID);
      }

      // Finally, save the record
      _mostRecentCoreRecords[storeAt] = record;
    }

    // Now look for the interesting records in there
    for (Record record : _mostRecentCoreRecords) {
      // Check there really is a record at this number
      if (record != null) {
        // Find the Document, and interesting things in it
        if (record.getRecordType() == RecordTypes.Document.typeID) {
          _documentRecord = (Document) record;
          _fonts = _documentRecord.getEnvironment().getFontCollection();
        }
      } else {
        // No record at this number
        // Odd, but not normally a problem
      }
    }
  }

  /**
   * For a given SlideAtomsSet, return the core record, based on the refID from the SlidePersistAtom
   */
  private Record getCoreRecordForSAS(SlideAtomsSet sas) {
    SlidePersistAtom spa = sas.getSlidePersistAtom();
    int refID = spa.getRefID();
    return getCoreRecordForRefID(refID);
  }

  /**
   * For a given refID (the internal, 0 based numbering scheme), return the core record
   *
   * @param refID the refID
   */
  private Record getCoreRecordForRefID(int refID) {
    Integer coreRecordId = _sheetIdToCoreRecordsLookup.get(refID);
    if (coreRecordId != null) {
      Record r = _mostRecentCoreRecords[coreRecordId];
      return r;
    }
    logger.log(
        POILogger.ERROR,
        "We tried to look up a reference to a core record, but there was no core ID for reference ID "
            + refID);
    return null;
  }

  /** Build up model level Slide and Notes objects, from the underlying records. */
  private void buildSlidesAndNotes() {
    // Ensure we really found a Document record earlier
    // If we didn't, then the file is probably corrupt
    if (_documentRecord == null) {
      throw new CorruptPowerPointFileException(
          "The PowerPoint file didn't contain a Document Record in its PersistPtr blocks. It is probably corrupt.");
    }

    // Fetch the SlideListWithTexts in the most up-to-date Document Record
    //
    // As far as we understand it:
    // * The first SlideListWithText will contain a SlideAtomsSet
    // for each of the master slides
    // * The second SlideListWithText will contain a SlideAtomsSet
    // for each of the slides, in their current order
    // These SlideAtomsSets will normally contain text
    // * The third SlideListWithText (if present), will contain a
    // SlideAtomsSet for each Notes
    // These SlideAtomsSets will not normally contain text
    //
    // Having indentified the masters, slides and notes + their orders,
    // we have to go and find their matching records
    // We always use the latest versions of these records, and use the
    // SlideAtom/NotesAtom to match them with the StyleAtomSet

    SlideListWithText masterSLWT = _documentRecord.getMasterSlideListWithText();
    SlideListWithText slidesSLWT = _documentRecord.getSlideSlideListWithText();
    SlideListWithText notesSLWT = _documentRecord.getNotesSlideListWithText();

    // Find master slides
    // These can be MainMaster records, but oddly they can also be
    // Slides or Notes, and possibly even other odd stuff....
    // About the only thing you can say is that the master details are in
    // the first SLWT.
    if (masterSLWT != null) {
      for (SlideAtomsSet sas : masterSLWT.getSlideAtomsSets()) {
        Record r = getCoreRecordForSAS(sas);
        int sheetNo = sas.getSlidePersistAtom().getSlideIdentifier();
        if (r instanceof org.apache.poi.hslf.record.Slide) {
          HSLFTitleMaster master =
              new HSLFTitleMaster((org.apache.poi.hslf.record.Slide) r, sheetNo);
          master.setSlideShow(this);
          _titleMasters.add(master);
        } else if (r instanceof org.apache.poi.hslf.record.MainMaster) {
          HSLFSlideMaster master =
              new HSLFSlideMaster((org.apache.poi.hslf.record.MainMaster) r, sheetNo);
          master.setSlideShow(this);
          _masters.add(master);
        }
      }
    }

    // Having sorted out the masters, that leaves the notes and slides

    // Start by finding the notes records to go with the entries in
    // notesSLWT
    org.apache.poi.hslf.record.Notes[] notesRecords;
    Map<Integer, Integer> slideIdToNotes = new HashMap<Integer, Integer>();
    if (notesSLWT == null) {
      // None
      notesRecords = new org.apache.poi.hslf.record.Notes[0];
    } else {
      // Match up the records and the SlideAtomSets
      List<org.apache.poi.hslf.record.Notes> notesRecordsL =
          new ArrayList<org.apache.poi.hslf.record.Notes>();
      int idx = -1;
      for (SlideAtomsSet notesSet : notesSLWT.getSlideAtomsSets()) {
        idx++;
        // Get the right core record
        Record r = getCoreRecordForSAS(notesSet);
        SlidePersistAtom spa = notesSet.getSlidePersistAtom();

        String loggerLoc =
            "A Notes SlideAtomSet at " + idx + " said its record was at refID " + spa.getRefID();

        // Ensure it really is a notes record
        if (r == null || r instanceof org.apache.poi.hslf.record.Notes) {
          if (r == null) {
            logger.log(
                POILogger.WARN, loggerLoc + ", but that record didn't exist - record ignored.");
          }
          // we need to add also null-records, otherwise the index references to other existing
          // don't work anymore
          org.apache.poi.hslf.record.Notes notesRecord = (org.apache.poi.hslf.record.Notes) r;
          notesRecordsL.add(notesRecord);

          // Record the match between slide id and these notes
          int slideId = spa.getSlideIdentifier();
          slideIdToNotes.put(slideId, idx);
        } else {
          logger.log(POILogger.ERROR, loggerLoc + ", but that was actually a " + r);
        }
      }
      notesRecords = new org.apache.poi.hslf.record.Notes[notesRecordsL.size()];
      notesRecords = notesRecordsL.toArray(notesRecords);
    }

    // Now, do the same thing for our slides
    org.apache.poi.hslf.record.Slide[] slidesRecords;
    SlideAtomsSet[] slidesSets = new SlideAtomsSet[0];
    if (slidesSLWT == null) {
      // None
      slidesRecords = new org.apache.poi.hslf.record.Slide[0];
    } else {
      // Match up the records and the SlideAtomSets
      slidesSets = slidesSLWT.getSlideAtomsSets();
      slidesRecords = new org.apache.poi.hslf.record.Slide[slidesSets.length];
      for (int i = 0; i < slidesSets.length; i++) {
        // Get the right core record
        Record r = getCoreRecordForSAS(slidesSets[i]);

        // Ensure it really is a slide record
        if (r instanceof org.apache.poi.hslf.record.Slide) {
          slidesRecords[i] = (org.apache.poi.hslf.record.Slide) r;
        } else {
          logger.log(
              POILogger.ERROR,
              "A Slide SlideAtomSet at "
                  + i
                  + " said its record was at refID "
                  + slidesSets[i].getSlidePersistAtom().getRefID()
                  + ", but that was actually a "
                  + r);
        }
      }
    }

    // Finally, generate model objects for everything
    // Notes first
    for (org.apache.poi.hslf.record.Notes n : notesRecords) {
      HSLFNotes hn = null;
      if (n != null) {
        hn = new HSLFNotes(n);
        hn.setSlideShow(this);
      }
      _notes.add(hn);
    }
    // Then slides
    for (int i = 0; i < slidesRecords.length; i++) {
      SlideAtomsSet sas = slidesSets[i];
      int slideIdentifier = sas.getSlidePersistAtom().getSlideIdentifier();

      // Do we have a notes for this?
      HSLFNotes notes = null;
      // Slide.SlideAtom.notesId references the corresponding notes slide.
      // 0 if slide has no notes.
      int noteId = slidesRecords[i].getSlideAtom().getNotesID();
      if (noteId != 0) {
        Integer notesPos = slideIdToNotes.get(noteId);
        if (notesPos != null) {
          notes = _notes.get(notesPos);
        } else {
          logger.log(POILogger.ERROR, "Notes not found for noteId=" + noteId);
        }
      }

      // Now, build our slide
      HSLFSlide hs = new HSLFSlide(slidesRecords[i], notes, sas, slideIdentifier, (i + 1));
      hs.setSlideShow(this);
      _slides.add(hs);
    }
  }

  @Override
  public void write(OutputStream out) throws IOException {
    // check for text paragraph modifications
    for (HSLFSlide sl : getSlides()) {
      for (HSLFShape sh : sl.getShapes()) {
        if (!(sh instanceof HSLFTextShape)) continue;
        HSLFTextShape hts = (HSLFTextShape) sh;
        boolean isDirty = false;
        for (HSLFTextParagraph p : hts.getTextParagraphs()) {
          isDirty |= p.isDirty();
        }
        if (isDirty) hts.storeText();
      }
    }

    _hslfSlideShow.write(out);
  }

  /*
   * ===============================================================
   *                         Accessor Code
   * ===============================================================
   */

  /** Returns an array of the most recent version of all the interesting records */
  public Record[] getMostRecentCoreRecords() {
    return _mostRecentCoreRecords;
  }

  /** Returns an array of all the normal Slides found in the slideshow */
  @Override
  public List<HSLFSlide> getSlides() {
    return _slides;
  }

  /** Returns an array of all the normal Notes found in the slideshow */
  public List<HSLFNotes> getNotes() {
    return _notes;
  }

  /** Returns an array of all the normal Slide Masters found in the slideshow */
  @Override
  public List<HSLFSlideMaster> getSlideMasters() {
    return _masters;
  }

  /** Returns an array of all the normal Title Masters found in the slideshow */
  public List<HSLFTitleMaster> getTitleMasters() {
    return _titleMasters;
  }

  @Override
  public List<HSLFPictureData> getPictureData() {
    return _hslfSlideShow.getPictureData();
  }

  /** Returns the data of all the embedded OLE object in the SlideShow */
  public HSLFObjectData[] getEmbeddedObjects() {
    return _hslfSlideShow.getEmbeddedObjects();
  }

  /** Returns the data of all the embedded sounds in the SlideShow */
  public HSLFSoundData[] getSoundData() {
    return HSLFSoundData.find(_documentRecord);
  }

  /** Return the current page size */
  public Dimension getPageSize() {
    DocumentAtom docatom = _documentRecord.getDocumentAtom();
    int pgx = (int) Units.masterToPoints((int) docatom.getSlideSizeX());
    int pgy = (int) Units.masterToPoints((int) docatom.getSlideSizeY());
    return new Dimension(pgx, pgy);
  }

  /**
   * Change the current page size
   *
   * @param pgsize page size (in points)
   */
  public void setPageSize(Dimension pgsize) {
    DocumentAtom docatom = _documentRecord.getDocumentAtom();
    docatom.setSlideSizeX(Units.pointsToMaster(pgsize.width));
    docatom.setSlideSizeY(Units.pointsToMaster(pgsize.height));
  }

  /** Helper method for usermodel: Get the font collection */
  protected FontCollection getFontCollection() {
    return _fonts;
  }

  /** Helper method for usermodel and model: Get the document record */
  public Document getDocumentRecord() {
    return _documentRecord;
  }

  /*
   * ===============================================================
   * Re-ordering Code
   * ===============================================================
   */

  /**
   * Re-orders a slide, to a new position.
   *
   * @param oldSlideNumber The old slide number (1 based)
   * @param newSlideNumber The new slide number (1 based)
   */
  public void reorderSlide(int oldSlideNumber, int newSlideNumber) {
    // Ensure these numbers are valid
    if (oldSlideNumber < 1 || newSlideNumber < 1) {
      throw new IllegalArgumentException("Old and new slide numbers must be greater than 0");
    }
    if (oldSlideNumber > _slides.size() || newSlideNumber > _slides.size()) {
      throw new IllegalArgumentException(
          "Old and new slide numbers must not exceed the number of slides ("
              + _slides.size()
              + ")");
    }

    // The order of slides is defined by the order of slide atom sets in the
    // SlideListWithText container.
    SlideListWithText slwt = _documentRecord.getSlideSlideListWithText();
    SlideAtomsSet[] sas = slwt.getSlideAtomsSets();

    SlideAtomsSet tmp = sas[oldSlideNumber - 1];
    sas[oldSlideNumber - 1] = sas[newSlideNumber - 1];
    sas[newSlideNumber - 1] = tmp;

    Collections.swap(_slides, oldSlideNumber - 1, newSlideNumber - 1);
    _slides.get(newSlideNumber - 1).setSlideNumber(newSlideNumber);
    _slides.get(oldSlideNumber - 1).setSlideNumber(oldSlideNumber);

    ArrayList<Record> lst = new ArrayList<Record>();
    for (SlideAtomsSet s : sas) {
      lst.add(s.getSlidePersistAtom());
      lst.addAll(Arrays.asList(s.getSlideRecords()));
    }

    Record[] r = lst.toArray(new Record[lst.size()]);
    slwt.setChildRecord(r);
  }

  /**
   * Removes the slide at the given index (0-based).
   *
   * <p>Shifts any subsequent slides to the left (subtracts one from their slide numbers).
   *
   * @param index the index of the slide to remove (0-based)
   * @return the slide that was removed from the slide show.
   */
  public HSLFSlide removeSlide(int index) {
    int lastSlideIdx = _slides.size() - 1;
    if (index < 0 || index > lastSlideIdx) {
      throw new IllegalArgumentException(
          "Slide index (" + index + ") is out of range (0.." + lastSlideIdx + ")");
    }

    SlideListWithText slwt = _documentRecord.getSlideSlideListWithText();
    SlideAtomsSet[] sas = slwt.getSlideAtomsSets();

    List<Record> records = new ArrayList<Record>();
    List<SlideAtomsSet> sa = new ArrayList<SlideAtomsSet>(Arrays.asList(sas));

    HSLFSlide removedSlide = _slides.remove(index);
    _notes.remove(removedSlide.getNotes());
    sa.remove(index);

    int i = 0;
    for (HSLFSlide s : _slides) s.setSlideNumber(i++);

    for (SlideAtomsSet s : sa) {
      records.add(s.getSlidePersistAtom());
      records.addAll(Arrays.asList(s.getSlideRecords()));
    }
    if (sa.isEmpty()) {
      _documentRecord.removeSlideListWithText(slwt);
    } else {
      slwt.setSlideAtomsSets(sa.toArray(new SlideAtomsSet[sa.size()]));
      slwt.setChildRecord(records.toArray(new Record[records.size()]));
    }

    // if the removed slide had notes - remove references to them too

    int notesId =
        (removedSlide != null) ? removedSlide.getSlideRecord().getSlideAtom().getNotesID() : 0;
    if (notesId != 0) {
      SlideListWithText nslwt = _documentRecord.getNotesSlideListWithText();
      records = new ArrayList<Record>();
      ArrayList<SlideAtomsSet> na = new ArrayList<SlideAtomsSet>();
      for (SlideAtomsSet ns : nslwt.getSlideAtomsSets()) {
        if (ns.getSlidePersistAtom().getSlideIdentifier() == notesId) continue;
        na.add(ns);
        records.add(ns.getSlidePersistAtom());
        if (ns.getSlideRecords() != null) {
          records.addAll(Arrays.asList(ns.getSlideRecords()));
        }
      }
      if (na.isEmpty()) {
        _documentRecord.removeSlideListWithText(nslwt);
      } else {
        nslwt.setSlideAtomsSets(na.toArray(new SlideAtomsSet[na.size()]));
        nslwt.setChildRecord(records.toArray(new Record[records.size()]));
      }
    }

    return removedSlide;
  }

  /*
   * ===============================================================
   *  Addition Code
   * ===============================================================
   */

  /**
   * Create a blank <code>Slide</code>.
   *
   * @return the created <code>Slide</code>
   */
  @Override
  public HSLFSlide createSlide() {
    SlideListWithText slist = null;

    // We need to add the records to the SLWT that deals
    // with Slides.
    // Add it, if it doesn't exist
    slist = _documentRecord.getSlideSlideListWithText();
    if (slist == null) {
      // Need to add a new one
      slist = new SlideListWithText();
      slist.setInstance(SlideListWithText.SLIDES);
      _documentRecord.addSlideListWithText(slist);
    }

    // Grab the SlidePersistAtom with the highest Slide Number.
    // (Will stay as null if no SlidePersistAtom exists yet in
    // the slide, or only master slide's ones do)
    SlidePersistAtom prev = null;
    for (SlideAtomsSet sas : slist.getSlideAtomsSets()) {
      SlidePersistAtom spa = sas.getSlidePersistAtom();
      if (spa.getSlideIdentifier() < 0) {
        // This is for a master slide
        // Odd, since we only deal with the Slide SLWT
      } else {
        // Must be for a real slide
        if (prev == null) {
          prev = spa;
        }
        if (prev.getSlideIdentifier() < spa.getSlideIdentifier()) {
          prev = spa;
        }
      }
    }

    // Set up a new SlidePersistAtom for this slide
    SlidePersistAtom sp = new SlidePersistAtom();

    // First slideId is always 256
    sp.setSlideIdentifier(prev == null ? 256 : (prev.getSlideIdentifier() + 1));

    // Add this new SlidePersistAtom to the SlideListWithText
    slist.addSlidePersistAtom(sp);

    // Create a new Slide
    HSLFSlide slide = new HSLFSlide(sp.getSlideIdentifier(), sp.getRefID(), _slides.size() + 1);
    slide.setSlideShow(this);
    slide.onCreate();

    // Add in to the list of Slides
    _slides.add(slide);
    logger.log(
        POILogger.INFO,
        "Added slide "
            + _slides.size()
            + " with ref "
            + sp.getRefID()
            + " and identifier "
            + sp.getSlideIdentifier());

    // Add the core records for this new Slide to the record tree
    org.apache.poi.hslf.record.Slide slideRecord = slide.getSlideRecord();
    int psrId = addPersistentObject(slideRecord);
    sp.setRefID(psrId);
    slideRecord.setSheetId(psrId);

    slide.setMasterSheet(_masters.get(0));
    // All done and added
    return slide;
  }

  @Override
  public HSLFPictureData addPicture(byte[] data, PictureType format) throws IOException {
    if (format == null || format.nativeId == -1) {
      throw new IllegalArgumentException("Unsupported picture format: " + format);
    }

    byte[] uid = HSLFPictureData.getChecksum(data);

    for (HSLFPictureData pd : getPictureData()) {
      if (Arrays.equals(pd.getUID(), uid)) {
        return pd;
      }
    }

    EscherContainerRecord bstore;

    EscherContainerRecord dggContainer = _documentRecord.getPPDrawingGroup().getDggContainer();
    bstore =
        (EscherContainerRecord)
            HSLFShape.getEscherChild(dggContainer, EscherContainerRecord.BSTORE_CONTAINER);
    if (bstore == null) {
      bstore = new EscherContainerRecord();
      bstore.setRecordId(EscherContainerRecord.BSTORE_CONTAINER);

      dggContainer.addChildBefore(bstore, EscherOptRecord.RECORD_ID);
    }

    HSLFPictureData pict = HSLFPictureData.create(format);
    pict.setData(data);

    int offset = _hslfSlideShow.addPicture(pict);

    EscherBSERecord bse = new EscherBSERecord();
    bse.setRecordId(EscherBSERecord.RECORD_ID);
    bse.setOptions((short) (0x0002 | (format.nativeId << 4)));
    bse.setSize(pict.getRawData().length + 8);
    bse.setUid(uid);

    bse.setBlipTypeMacOS((byte) format.nativeId);
    bse.setBlipTypeWin32((byte) format.nativeId);

    if (format == PictureType.EMF) {
      bse.setBlipTypeMacOS((byte) PictureType.PICT.nativeId);
    } else if (format == PictureType.WMF) {
      bse.setBlipTypeMacOS((byte) PictureType.PICT.nativeId);
    } else if (format == PictureType.PICT) {
      bse.setBlipTypeWin32((byte) PictureType.WMF.nativeId);
    }

    bse.setRef(0);
    bse.setOffset(offset);
    bse.setRemainingData(new byte[0]);

    bstore.addChildRecord(bse);
    int count = bstore.getChildRecords().size();
    bstore.setOptions((short) ((count << 4) | 0xF));

    return pict;
  }

  /**
   * Adds a picture to this presentation and returns the associated index.
   *
   * @param pict the file containing the image to add
   * @param format the format of the picture. One of constans defined in the <code>Picture</code>
   *     class.
   * @return the index to this picture (1 based).
   */
  public HSLFPictureData addPicture(File pict, PictureType format) throws IOException {
    int length = (int) pict.length();
    byte[] data = new byte[length];
    FileInputStream is = null;
    try {
      is = new FileInputStream(pict);
      is.read(data);
    } finally {
      if (is != null) is.close();
    }
    return addPicture(data, format);
  }

  /**
   * Add a font in this presentation
   *
   * @param font the font to add
   * @return 0-based index of the font
   */
  public int addFont(PPFont font) {
    FontCollection fonts = getDocumentRecord().getEnvironment().getFontCollection();
    int idx = fonts.getFontIndex(font.getFontName());
    if (idx == -1) {
      idx =
          fonts.addFont(
              font.getFontName(),
              font.getCharSet(),
              font.getFontFlags(),
              font.getFontType(),
              font.getPitchAndFamily());
    }
    return idx;
  }

  /**
   * Get a font by index
   *
   * @param idx 0-based index of the font
   * @return of an instance of <code>PPFont</code> or <code>null</code> if not found
   */
  public PPFont getFont(int idx) {
    FontCollection fonts = getDocumentRecord().getEnvironment().getFontCollection();
    for (Record ch : fonts.getChildRecords()) {
      if (ch instanceof FontEntityAtom) {
        FontEntityAtom atom = (FontEntityAtom) ch;
        if (atom.getFontIndex() == idx) {
          return new PPFont(atom);
        }
      }
    }
    return null;
  }

  /**
   * get the number of fonts in the presentation
   *
   * @return number of fonts
   */
  public int getNumberOfFonts() {
    return getDocumentRecord().getEnvironment().getFontCollection().getNumberOfFonts();
  }

  /**
   * Return Header / Footer settings for slides
   *
   * @return Header / Footer settings for slides
   */
  public HeadersFooters getSlideHeadersFooters() {
    // detect if this ppt was saved in Office2007
    String tag = getSlideMasters().get(0).getProgrammableTag();
    boolean ppt2007 = "___PPT12".equals(tag);

    HeadersFootersContainer hdd = null;
    for (Record ch : _documentRecord.getChildRecords()) {
      if (ch instanceof HeadersFootersContainer
          && ((HeadersFootersContainer) ch).getOptions()
              == HeadersFootersContainer.SlideHeadersFootersContainer) {
        hdd = (HeadersFootersContainer) ch;
        break;
      }
    }
    boolean newRecord = false;
    if (hdd == null) {
      hdd = new HeadersFootersContainer(HeadersFootersContainer.SlideHeadersFootersContainer);
      newRecord = true;
    }
    return new HeadersFooters(hdd, this, newRecord, ppt2007);
  }

  /**
   * Return Header / Footer settings for notes
   *
   * @return Header / Footer settings for notes
   */
  public HeadersFooters getNotesHeadersFooters() {
    // detect if this ppt was saved in Office2007
    String tag = getSlideMasters().get(0).getProgrammableTag();
    boolean ppt2007 = "___PPT12".equals(tag);

    HeadersFootersContainer hdd = null;
    for (Record ch : _documentRecord.getChildRecords()) {
      if (ch instanceof HeadersFootersContainer
          && ((HeadersFootersContainer) ch).getOptions()
              == HeadersFootersContainer.NotesHeadersFootersContainer) {
        hdd = (HeadersFootersContainer) ch;
        break;
      }
    }
    boolean newRecord = false;
    if (hdd == null) {
      hdd = new HeadersFootersContainer(HeadersFootersContainer.NotesHeadersFootersContainer);
      newRecord = true;
    }
    if (ppt2007 && !_notes.isEmpty()) {
      return new HeadersFooters(hdd, _notes.get(0), newRecord, ppt2007);
    }
    return new HeadersFooters(hdd, this, newRecord, ppt2007);
  }

  /**
   * Add a movie in this presentation
   *
   * @param path the path or url to the movie
   * @return 0-based index of the movie
   */
  public int addMovie(String path, int type) {
    ExMCIMovie mci;
    switch (type) {
      case MovieShape.MOVIE_MPEG:
        mci = new ExMCIMovie();
        break;
      case MovieShape.MOVIE_AVI:
        mci = new ExAviMovie();
        break;
      default:
        throw new IllegalArgumentException("Unsupported Movie: " + type);
    }

    ExVideoContainer exVideo = mci.getExVideo();
    exVideo.getExMediaAtom().setMask(0xE80000);
    exVideo.getPathAtom().setText(path);

    int objectId = addToObjListAtom(mci);
    exVideo.getExMediaAtom().setObjectId(objectId);

    return objectId;
  }

  /**
   * Add a control in this presentation
   *
   * @param name name of the control, e.g. "Shockwave Flash Object"
   * @param progId OLE Programmatic Identifier, e.g. "ShockwaveFlash.ShockwaveFlash.9"
   * @return 0-based index of the control
   */
  public int addControl(String name, String progId) {
    ExControl ctrl = new ExControl();
    ctrl.setProgId(progId);
    ctrl.setMenuName(name);
    ctrl.setClipboardName(name);

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

  /**
   * Add a hyperlink to this presentation
   *
   * @return 0-based index of the hyperlink
   */
  public int addHyperlink(HSLFHyperlink link) {
    ExHyperlink ctrl = new ExHyperlink();
    ExHyperlinkAtom obj = ctrl.getExHyperlinkAtom();
    if (link.getType() == HSLFHyperlink.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);
    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<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) {
    slideRecord.setLastOnDiskOffset(HSLFSlideShowImpl.UNSET_OFFSET);
    _hslfSlideShow.appendRootLevelRecord((Record) slideRecord);

    // For position dependent records, hold where they were and now are
    // As we go along, update, and hand over, to any Position Dependent
    // records we happen across
    Map<RecordTypes.Type, PositionDependentRecord> interestingRecords =
        new HashMap<RecordTypes.Type, PositionDependentRecord>();

    try {
      _hslfSlideShow.updateAndWriteDependantRecords(null, interestingRecords);
    } catch (IOException e) {
      throw new HSLFException(e);
    }

    PersistPtrHolder ptr =
        (PersistPtrHolder) interestingRecords.get(RecordTypes.PersistPtrIncrementalBlock);
    UserEditAtom usr = (UserEditAtom) interestingRecords.get(RecordTypes.UserEditAtom);

    // 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)
    int slideOffset = slideRecord.getLastOnDiskOffset();
    slideRecord.setLastOnDiskOffset(slideOffset);
    ptr.addSlideLookup(psrId, slideOffset);
    logger.log(POILogger.INFO, "New slide/object ended up at " + slideOffset);

    return psrId;
  }

  public MasterSheet<HSLFShape, HSLFTextParagraph> createMasterSheet() throws IOException {
    // TODO Auto-generated method stub
    return null;
  }

  public Resources getResources() {
    // TODO Auto-generated method stub
    return null;
  }
}
/**
 * A proxy HSSFListener that keeps track of the document formatting records, and provides an easy
 * way to look up the format strings used by cells from their ids.
 */
public class FormatTrackingHSSFListener implements HSSFListener {
  private static POILogger logger = POILogFactory.getLogger(FormatTrackingHSSFListener.class);
  private final HSSFListener _childListener;
  private final HSSFDataFormatter _formatter;
  private final NumberFormat _defaultFormat;
  private final Map<Integer, FormatRecord> _customFormatRecords =
      new Hashtable<Integer, FormatRecord>();
  private final List<ExtendedFormatRecord> _xfRecords = new ArrayList<ExtendedFormatRecord>();

  /**
   * Creates a format tracking wrapper around the given listener, using the {@link
   * Locale#getDefault() default locale} for the formats.
   */
  public FormatTrackingHSSFListener(HSSFListener childListener) {
    this(childListener, LocaleUtil.getUserLocale());
  }

  /**
   * Creates a format tracking wrapper around the given listener, using the given locale for the
   * formats.
   */
  public FormatTrackingHSSFListener(HSSFListener childListener, Locale locale) {
    _childListener = childListener;
    _formatter = new HSSFDataFormatter(locale);
    _defaultFormat = NumberFormat.getInstance(locale);
  }

  protected int getNumberOfCustomFormats() {
    return _customFormatRecords.size();
  }

  protected int getNumberOfExtendedFormats() {
    return _xfRecords.size();
  }

  /** Process this record ourselves, and then pass it on to our child listener */
  public void processRecord(Record record) {
    // Handle it ourselves
    processRecordInternally(record);

    // Now pass on to our child
    _childListener.processRecord(record);
  }

  /**
   * Process the record ourselves, but do not pass it on to the child Listener.
   *
   * @param record
   */
  public void processRecordInternally(Record record) {
    if (record instanceof FormatRecord) {
      FormatRecord fr = (FormatRecord) record;
      _customFormatRecords.put(Integer.valueOf(fr.getIndexCode()), fr);
    }
    if (record instanceof ExtendedFormatRecord) {
      ExtendedFormatRecord xr = (ExtendedFormatRecord) record;
      _xfRecords.add(xr);
    }
  }

  /**
   * Formats the given numeric of date Cell's contents as a String, in as close as we can to the way
   * that Excel would do so. Uses the various format records to manage this.
   *
   * <p>TODO - move this to a central class in such a way that hssf.usermodel can make use of it too
   */
  public String formatNumberDateCell(CellValueRecordInterface cell) {
    double value;
    if (cell instanceof NumberRecord) {
      value = ((NumberRecord) cell).getValue();
    } else if (cell instanceof FormulaRecord) {
      value = ((FormulaRecord) cell).getValue();
    } else {
      throw new IllegalArgumentException("Unsupported CellValue Record passed in " + cell);
    }

    // Get the built in format, if there is one
    int formatIndex = getFormatIndex(cell);
    String formatString = getFormatString(cell);

    if (formatString == null) {
      return _defaultFormat.format(value);
    }
    // Format, using the nice new
    // HSSFDataFormatter to do the work for us
    return _formatter.formatRawCellContents(value, formatIndex, formatString);
  }

  /** Returns the format string, eg $##.##, for the given number format index. */
  public String getFormatString(int formatIndex) {
    String format = null;
    if (formatIndex >= HSSFDataFormat.getNumberOfBuiltinBuiltinFormats()) {
      FormatRecord tfr = _customFormatRecords.get(Integer.valueOf(formatIndex));
      if (tfr == null) {
        logger.log(
            POILogger.ERROR, "Requested format at index " + formatIndex + ", but it wasn't found");
      } else {
        format = tfr.getFormatString();
      }
    } else {
      format = HSSFDataFormat.getBuiltinFormat((short) formatIndex);
    }
    return format;
  }

  /** Returns the format string, eg $##.##, used by your cell */
  public String getFormatString(CellValueRecordInterface cell) {
    int formatIndex = getFormatIndex(cell);
    if (formatIndex == -1) {
      // Not found
      return null;
    }
    return getFormatString(formatIndex);
  }

  /** Returns the index of the format string, used by your cell, or -1 if none found */
  public int getFormatIndex(CellValueRecordInterface cell) {
    ExtendedFormatRecord xfr = _xfRecords.get(cell.getXFIndex());
    if (xfr == null) {
      logger.log(
          POILogger.ERROR,
          "Cell "
              + cell.getRow()
              + ","
              + cell.getColumn()
              + " uses XF with index "
              + cell.getXFIndex()
              + ", but we don't have that");
      return -1;
    }
    return xfr.getFormatIndex();
  }
}
Example #3
0
/** High level representation of a row of a spreadsheet. */
public class XSSFRow implements Row, Comparable<XSSFRow> {
  private static final POILogger _logger = POILogFactory.getLogger(XSSFRow.class);

  /** the xml bean containing all cell definitions for this row */
  private final CTRow _row;

  /**
   * Cells of this row keyed by their column indexes. The TreeMap ensures that the cells are ordered
   * by columnIndex in the ascending order.
   */
  private final TreeMap<Integer, XSSFCell> _cells;

  /** the parent sheet */
  private final XSSFSheet _sheet;

  /**
   * Construct a XSSFRow.
   *
   * @param row the xml bean containing all cell definitions for this row.
   * @param sheet the parent sheet.
   */
  protected XSSFRow(CTRow row, XSSFSheet sheet) {
    _row = row;
    _sheet = sheet;
    _cells = new TreeMap<Integer, XSSFCell>();
    for (CTCell c : row.getCArray()) {
      XSSFCell cell = new XSSFCell(this, c);
      _cells.put(cell.getColumnIndex(), cell);
      sheet.onReadCell(cell);
    }
  }

  /**
   * Returns the XSSFSheet this row belongs to
   *
   * @return the XSSFSheet that owns this row
   */
  public XSSFSheet getSheet() {
    return this._sheet;
  }

  /**
   * Cell iterator over the physically defined cells:
   *
   * <blockquote>
   *
   * <pre>
   * for (Iterator<Cell> it = row.cellIterator(); it.hasNext(); ) {
   *     Cell cell = it.next();
   *     ...
   * }
   * </pre>
   *
   * </blockquote>
   *
   * @return an iterator over cells in this row.
   */
  public Iterator<Cell> cellIterator() {
    return (Iterator<Cell>) (Iterator<? extends Cell>) _cells.values().iterator();
  }

  /**
   * Alias for {@link #cellIterator()} to allow foreach loops:
   *
   * <blockquote>
   *
   * <pre>
   * for(Cell cell : row){
   *     ...
   * }
   * </pre>
   *
   * </blockquote>
   *
   * @return an iterator over cells in this row.
   */
  public Iterator<Cell> iterator() {
    return cellIterator();
  }

  /**
   * Compares two <code>XSSFRow</code> objects. Two rows are equal if they belong to the same
   * worksheet and their row indexes are equal.
   *
   * @param row the <code>XSSFRow</code> to be compared.
   * @return the value <code>0</code> if the row number of this <code>XSSFRow</code> is equal to the
   *     row number of the argument <code>XSSFRow</code>; a value less than <code>0</code> if the
   *     row number of this this <code>XSSFRow</code> is numerically less than the row number of the
   *     argument <code>XSSFRow</code>; and a value greater than <code>0</code> if the row number of
   *     this this <code>XSSFRow</code> is numerically greater than the row number of the argument
   *     <code>XSSFRow</code>.
   * @throws IllegalArgumentException if the argument row belongs to a different worksheet
   */
  public int compareTo(XSSFRow row) {
    int thisVal = this.getRowNum();
    if (row.getSheet() != getSheet())
      throw new IllegalArgumentException("The compared rows must belong to the same XSSFSheet");

    int anotherVal = row.getRowNum();
    return (thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1));
  }

  /**
   * Use this to create new cells within the row and return it.
   *
   * <p>The cell that is returned is a {@link Cell#CELL_TYPE_BLANK}. The type can be changed either
   * through calling <code>setCellValue</code> or <code>setCellType</code>.
   *
   * @param columnIndex - the column number this cell represents
   * @return Cell a high level representation of the created cell.
   * @throws IllegalArgumentException if columnIndex < 0 or greater than 16384, the maximum number
   *     of columns supported by the SpreadsheetML format (.xlsx)
   */
  public XSSFCell createCell(int columnIndex) {
    return createCell(columnIndex, Cell.CELL_TYPE_BLANK);
  }

  /**
   * Use this to create new cells within the row and return it.
   *
   * @param columnIndex - the column number this cell represents
   * @param type - the cell's data type
   * @return XSSFCell a high level representation of the created cell.
   * @throws IllegalArgumentException if the specified cell type is invalid, columnIndex < 0 or
   *     greater than 16384, the maximum number of columns supported by the SpreadsheetML format
   *     (.xlsx)
   * @see Cell#CELL_TYPE_BLANK
   * @see Cell#CELL_TYPE_BOOLEAN
   * @see Cell#CELL_TYPE_ERROR
   * @see Cell#CELL_TYPE_FORMULA
   * @see Cell#CELL_TYPE_NUMERIC
   * @see Cell#CELL_TYPE_STRING
   */
  public XSSFCell createCell(int columnIndex, int type) {
    CTCell ctCell;
    XSSFCell prev = _cells.get(columnIndex);
    if (prev != null) {
      ctCell = prev.getCTCell();
      ctCell.set(CTCell.Factory.newInstance());
    } else {
      ctCell = _row.addNewC();
    }
    XSSFCell xcell = new XSSFCell(this, ctCell);
    xcell.setCellNum(columnIndex);
    if (type != Cell.CELL_TYPE_BLANK) {
      xcell.setCellType(type);
    }
    _cells.put(columnIndex, xcell);
    return xcell;
  }

  /**
   * Returns the cell at the given (0 based) index, with the {@link
   * org.apache.poi.ss.usermodel.Row.MissingCellPolicy} from the parent Workbook.
   *
   * @return the cell at the given (0 based) index
   */
  public XSSFCell getCell(int cellnum) {
    return getCell(cellnum, _sheet.getWorkbook().getMissingCellPolicy());
  }

  /**
   * Returns the cell at the given (0 based) index, with the specified {@link
   * org.apache.poi.ss.usermodel.Row.MissingCellPolicy}
   *
   * @return the cell at the given (0 based) index
   * @throws IllegalArgumentException if cellnum < 0 or the specified MissingCellPolicy is invalid
   * @see Row#RETURN_NULL_AND_BLANK
   * @see Row#RETURN_BLANK_AS_NULL
   * @see Row#CREATE_NULL_AS_BLANK
   */
  public XSSFCell getCell(int cellnum, MissingCellPolicy policy) {
    if (cellnum < 0) throw new IllegalArgumentException("Cell index must be >= 0");

    XSSFCell cell = (XSSFCell) _cells.get(cellnum);
    if (policy == RETURN_NULL_AND_BLANK) {
      return cell;
    }
    if (policy == RETURN_BLANK_AS_NULL) {
      if (cell == null) return cell;
      if (cell.getCellType() == Cell.CELL_TYPE_BLANK) {
        return null;
      }
      return cell;
    }
    if (policy == CREATE_NULL_AS_BLANK) {
      if (cell == null) {
        return createCell((short) cellnum, Cell.CELL_TYPE_BLANK);
      }
      return cell;
    }
    throw new IllegalArgumentException("Illegal policy " + policy + " (" + policy.id + ")");
  }

  /**
   * Get the number of the first cell contained in this row.
   *
   * @return short representing the first logical cell in the row, or -1 if the row does not contain
   *     any cells.
   */
  public short getFirstCellNum() {
    return (short) (_cells.size() == 0 ? -1 : _cells.firstKey());
  }

  /**
   * Gets the index of the last cell contained in this row <b>PLUS ONE</b>. The result also happens
   * to be the 1-based column number of the last cell. This value can be used as a standard upper
   * bound when iterating over cells:
   *
   * <pre>
   * short minColIx = row.getFirstCellNum();
   * short maxColIx = row.getLastCellNum();
   * for(short colIx=minColIx; colIx&lt;maxColIx; colIx++) {
   *   XSSFCell cell = row.getCell(colIx);
   *   if(cell == null) {
   *     continue;
   *   }
   *   //... do something with cell
   * }
   * </pre>
   *
   * @return short representing the last logical cell in the row <b>PLUS ONE</b>, or -1 if the row
   *     does not contain any cells.
   */
  public short getLastCellNum() {
    return (short) (_cells.size() == 0 ? -1 : (_cells.lastKey() + 1));
  }

  /**
   * Get the row's height measured in twips (1/20th of a point). If the height is not set, the
   * default worksheet value is returned, See {@link
   * org.apache.poi.xssf.usermodel.XSSFSheet#getDefaultRowHeightInPoints()}
   *
   * @return row height measured in twips (1/20th of a point)
   */
  public short getHeight() {
    return (short) (getHeightInPoints() * 20);
  }

  /**
   * Returns row height measured in point size. If the height is not set, the default worksheet
   * value is returned, See {@link
   * org.apache.poi.xssf.usermodel.XSSFSheet#getDefaultRowHeightInPoints()}
   *
   * @return row height measured in point size
   * @see org.apache.poi.xssf.usermodel.XSSFSheet#getDefaultRowHeightInPoints()
   */
  public float getHeightInPoints() {
    if (this._row.isSetHt()) {
      return (float) this._row.getHt();
    }
    return _sheet.getDefaultRowHeightInPoints();
  }

  /**
   * Set the height in "twips" or 1/20th of a point.
   *
   * @param height the height in "twips" or 1/20th of a point. <code>-1</code> resets to the default
   *     height
   */
  public void setHeight(short height) {
    if (height == -1) {
      if (_row.isSetHt()) _row.unsetHt();
      if (_row.isSetCustomHeight()) _row.unsetCustomHeight();
    } else {
      _row.setHt((double) height / 20);
      _row.setCustomHeight(true);
    }
  }

  /**
   * Set the row's height in points.
   *
   * @param height the height in points. <code>-1</code> resets to the default height
   */
  public void setHeightInPoints(float height) {
    setHeight((short) (height == -1 ? -1 : (height * 20)));
  }

  /**
   * Gets the number of defined cells (NOT number of cells in the actual row!). That is to say if
   * only columns 0,4,5 have values then there would be 3.
   *
   * @return int representing the number of defined cells in the row.
   */
  public int getPhysicalNumberOfCells() {
    return _cells.size();
  }

  /**
   * Get row number this row represents
   *
   * @return the row number (0 based)
   */
  public int getRowNum() {
    return (int) (_row.getR() - 1);
  }

  /**
   * Set the row number of this row.
   *
   * @param rowIndex the row number (0-based)
   * @throws IllegalArgumentException if rowNum < 0 or greater than 1048575
   */
  public void setRowNum(int rowIndex) {
    int maxrow = SpreadsheetVersion.EXCEL2007.getLastRowIndex();
    if (rowIndex < 0 || rowIndex > maxrow) {
      throw new IllegalArgumentException(
          "Invalid row number (" + rowIndex + ") outside allowable range (0.." + maxrow + ")");
    }
    _row.setR(rowIndex + 1);
  }

  /**
   * Get whether or not to display this row with 0 height
   *
   * @return - height is zero or not.
   */
  public boolean getZeroHeight() {
    return this._row.getHidden();
  }

  /**
   * Set whether or not to display this row with 0 height
   *
   * @param height height is zero or not.
   */
  public void setZeroHeight(boolean height) {
    this._row.setHidden(height);
  }

  /**
   * Remove the Cell from this row.
   *
   * @param cell the cell to remove
   */
  public void removeCell(Cell cell) {
    if (cell.getRow() != this) {
      throw new IllegalArgumentException("Specified cell does not belong to this row");
    }

    XSSFCell xcell = (XSSFCell) cell;
    if (xcell.isPartOfArrayFormulaGroup()) {
      xcell.notifyArrayFormulaChanging();
    }
    _cells.remove(cell.getColumnIndex());
  }

  /**
   * Returns the underlying CTRow xml bean containing all cell definitions in this row
   *
   * @return the underlying CTRow xml bean
   */
  @Internal
  public CTRow getCTRow() {
    return _row;
  }

  /**
   * Fired when the document is written to an output stream.
   *
   * @see org.apache.poi.xssf.usermodel.XSSFSheet#write(java.io.OutputStream) ()
   */
  protected void onDocumentWrite() {
    // check if cells in the CTRow are ordered
    boolean isOrdered = true;
    if (_row.sizeOfCArray() != _cells.size()) isOrdered = false;
    else {
      int i = 0;
      CTCell[] xcell = _row.getCArray();
      for (XSSFCell cell : _cells.values()) {
        CTCell c1 = cell.getCTCell();
        CTCell c2 = xcell[i++];

        String r1 = c1.getR();
        String r2 = c2.getR();
        if (!(r1 == null ? r2 == null : r1.equals(r2))) {
          isOrdered = false;
          break;
        }
      }
    }

    if (!isOrdered) {
      CTCell[] cArray = new CTCell[_cells.size()];
      int i = 0;
      for (XSSFCell c : _cells.values()) {
        cArray[i++] = c.getCTCell();
      }
      _row.setCArray(cArray);
    }
  }

  /** @return formatted xml representation of this row */
  @Override
  public String toString() {
    return _row.toString();
  }

  /**
   * update cell references when shifting rows
   *
   * @param n the number of rows to move
   */
  protected void shift(int n) {
    int rownum = getRowNum() + n;
    CalculationChain calcChain = _sheet.getWorkbook().getCalculationChain();
    int sheetId = (int) _sheet.sheet.getSheetId();
    String msg =
        "Row[rownum="
            + getRowNum()
            + "] contains cell(s) included in a multi-cell array formula. "
            + "You cannot change part of an array.";
    for (Cell c : this) {
      XSSFCell cell = (XSSFCell) c;
      if (cell.isPartOfArrayFormulaGroup()) {
        cell.notifyArrayFormulaChanging(msg);
      }

      // remove the reference in the calculation chain
      if (calcChain != null) calcChain.removeItem(sheetId, cell.getReference());

      CTCell ctCell = cell.getCTCell();
      String r = new CellReference(rownum, cell.getColumnIndex()).formatAsString();
      ctCell.setR(r);
    }
    setRowNum(rownum);
  }

  // 20100915, [email protected]: remove all cells
  public void removeAllCells() {
    _cells.clear();
  }
  // 20100915, [email protected]: return cells TreeMap
  public TreeMap<Integer, XSSFCell> getCells() {
    return _cells;
  }
}
/**
 * ftPictFmla (0x0009)<br>
 * A sub-record within the OBJ record which stores a reference to an object stored in a separate
 * entry within the OLE2 compound file.
 *
 * @author Daniel Noll
 */
public final class EmbeddedObjectRefSubRecord extends SubRecord implements Cloneable {
  private static POILogger logger = POILogFactory.getLogger(EmbeddedObjectRefSubRecord.class);
  public static final short sid = 0x0009;

  private static final byte[] EMPTY_BYTE_ARRAY = {};

  private int field_1_unknown_int;
  /** either an area or a cell ref */
  private Ptg field_2_refPtg;
  /** for when the 'formula' doesn't parse properly */
  private byte[] field_2_unknownFormulaData;
  /** note- this byte is not present in the encoding if the string length is zero */
  private boolean field_3_unicode_flag; // Flags whether the string is Unicode.

  private String
      field_4_ole_classname; // Classname of the embedded OLE document (e.g. Word.Document.8)
  /**
   * Formulas often have a single non-zero trailing byte. This is in a similar position to he
   * pre-streamId padding It is unknown if the value is important (it seems to mirror a value a few
   * bytes earlier)
   */
  private Byte field_4_unknownByte;

  private Integer field_5_stream_id; // ID of the OLE stream containing the actual data.
  private byte[] field_6_unknown;

  // currently for testing only - needs review
  public EmbeddedObjectRefSubRecord() {
    field_2_unknownFormulaData =
        new byte[] {
          0x02, 0x6C, 0x6A, 0x16, 0x01,
        }; // just some sample data.  These values vary a lot
    field_6_unknown = EMPTY_BYTE_ARRAY;
    field_4_ole_classname = null;
  }

  public short getSid() {
    return sid;
  }

  public EmbeddedObjectRefSubRecord(LittleEndianInput in, int size) {

    // Much guess-work going on here due to lack of any documentation.
    // See similar source code in OOO:
    // http://svn.services.openoffice.org/ooo/trunk/sc/source/filter/excel/xiescher.cxx
    // 1223 void XclImpOleObj::ReadPictFmla( XclImpStream& rStrm, sal_uInt16 nRecSize )

    int streamIdOffset = in.readShort(); // OOO calls this 'nFmlaLen'
    int remaining = size - LittleEndian.SHORT_SIZE;

    int dataLenAfterFormula = remaining - streamIdOffset;
    int formulaSize = in.readUShort();
    remaining -= LittleEndian.SHORT_SIZE;
    field_1_unknown_int = in.readInt();
    remaining -= LittleEndian.INT_SIZE;
    byte[] formulaRawBytes = readRawData(in, formulaSize);
    remaining -= formulaSize;
    field_2_refPtg = readRefPtg(formulaRawBytes);
    if (field_2_refPtg == null) {
      // common case
      // field_2_n16 seems to be 5 here
      // The formula almost looks like tTbl but the row/column values seem like garbage.
      field_2_unknownFormulaData = formulaRawBytes;
    } else {
      field_2_unknownFormulaData = null;
    }

    int stringByteCount;
    if (remaining >= dataLenAfterFormula + 3) {
      int tag = in.readByte();
      stringByteCount = LittleEndian.BYTE_SIZE;
      if (tag != 0x03) {
        throw new RecordFormatException("Expected byte 0x03 here");
      }
      int nChars = in.readUShort();
      stringByteCount += LittleEndian.SHORT_SIZE;
      if (nChars > 0) {
        // OOO: the 4th way Xcl stores a unicode string: not even a Grbit byte present if length 0
        field_3_unicode_flag = (in.readByte() & 0x01) != 0;
        stringByteCount += LittleEndian.BYTE_SIZE;
        if (field_3_unicode_flag) {
          field_4_ole_classname = StringUtil.readUnicodeLE(in, nChars);
          stringByteCount += nChars * 2;
        } else {
          field_4_ole_classname = StringUtil.readCompressedUnicode(in, nChars);
          stringByteCount += nChars;
        }
      } else {
        field_4_ole_classname = "";
      }
    } else {
      field_4_ole_classname = null;
      stringByteCount = 0;
    }
    remaining -= stringByteCount;
    // Pad to next 2-byte boundary
    if (((stringByteCount + formulaSize) % 2) != 0) {
      int b = in.readByte();
      remaining -= LittleEndian.BYTE_SIZE;
      if (field_2_refPtg != null && field_4_ole_classname == null) {
        field_4_unknownByte = Byte.valueOf((byte) b);
      }
    }
    int nUnexpectedPadding = remaining - dataLenAfterFormula;

    if (nUnexpectedPadding > 0) {
      logger.log(
          POILogger.ERROR, "Discarding " + nUnexpectedPadding + " unexpected padding bytes ");
      readRawData(in, nUnexpectedPadding);
      remaining -= nUnexpectedPadding;
    }

    // Fetch the stream ID
    if (dataLenAfterFormula >= 4) {
      field_5_stream_id = Integer.valueOf(in.readInt());
      remaining -= LittleEndian.INT_SIZE;
    } else {
      field_5_stream_id = null;
    }
    field_6_unknown = readRawData(in, remaining);
  }

  private static Ptg readRefPtg(byte[] formulaRawBytes) {
    LittleEndianInput in = new LittleEndianInputStream(new ByteArrayInputStream(formulaRawBytes));
    byte ptgSid = in.readByte();
    switch (ptgSid) {
      case AreaPtg.sid:
        return new AreaPtg(in);
      case Area3DPtg.sid:
        return new Area3DPtg(in);
      case RefPtg.sid:
        return new RefPtg(in);
      case Ref3DPtg.sid:
        return new Ref3DPtg(in);
    }
    return null;
  }

  private static byte[] readRawData(LittleEndianInput in, int size) {
    if (size < 0) {
      throw new IllegalArgumentException("Negative size (" + size + ")");
    }
    if (size == 0) {
      return EMPTY_BYTE_ARRAY;
    }
    byte[] result = new byte[size];
    in.readFully(result);
    return result;
  }

  private int getStreamIDOffset(int formulaSize) {
    int result = 2 + 4; // formulaSize + f2unknown_int
    result += formulaSize;

    int stringLen;
    if (field_4_ole_classname == null) {
      // don't write 0x03, stringLen, flag, text
      stringLen = 0;
    } else {
      result += 1 + 2; // 0x03, stringLen
      stringLen = field_4_ole_classname.length();
      if (stringLen > 0) {
        result += 1; // flag
        if (field_3_unicode_flag) {
          result += stringLen * 2;
        } else {
          result += stringLen;
        }
      }
    }
    // pad to next 2 byte boundary
    if ((result % 2) != 0) {
      result++;
    }
    return result;
  }

  private int getDataSize(int idOffset) {

    int result = 2 + idOffset; // 2 for idOffset short field itself
    if (field_5_stream_id != null) {
      result += 4;
    }
    return result + field_6_unknown.length;
  }

  protected int getDataSize() {
    int formulaSize =
        field_2_refPtg == null ? field_2_unknownFormulaData.length : field_2_refPtg.getSize();
    int idOffset = getStreamIDOffset(formulaSize);
    return getDataSize(idOffset);
  }

  public void serialize(LittleEndianOutput out) {

    int formulaSize =
        field_2_refPtg == null ? field_2_unknownFormulaData.length : field_2_refPtg.getSize();
    int idOffset = getStreamIDOffset(formulaSize);
    int dataSize = getDataSize(idOffset);

    out.writeShort(sid);
    out.writeShort(dataSize);

    out.writeShort(idOffset);
    out.writeShort(formulaSize);
    out.writeInt(field_1_unknown_int);

    int pos = 12;

    if (field_2_refPtg == null) {
      out.write(field_2_unknownFormulaData);
    } else {
      field_2_refPtg.write(out);
    }
    pos += formulaSize;

    int stringLen;
    if (field_4_ole_classname == null) {
      // don't write 0x03, stringLen, flag, text
      stringLen = 0;
    } else {
      out.writeByte(0x03);
      pos += 1;
      stringLen = field_4_ole_classname.length();
      out.writeShort(stringLen);
      pos += 2;
      if (stringLen > 0) {
        out.writeByte(field_3_unicode_flag ? 0x01 : 0x00);
        pos += 1;

        if (field_3_unicode_flag) {
          StringUtil.putUnicodeLE(field_4_ole_classname, out);
          pos += stringLen * 2;
        } else {
          StringUtil.putCompressedUnicode(field_4_ole_classname, out);
          pos += stringLen;
        }
      }
    }

    // pad to next 2-byte boundary (requires 0 or 1 bytes)
    switch (idOffset - (pos - 6)) { // 6 for 3 shorts: sid, dataSize, idOffset
      case 1:
        out.writeByte(field_4_unknownByte == null ? 0x00 : field_4_unknownByte.intValue());
        pos++;
      case 0:
        break;
      default:
        throw new IllegalStateException("Bad padding calculation (" + idOffset + ", " + pos + ")");
    }

    if (field_5_stream_id != null) {
      out.writeInt(field_5_stream_id.intValue());
      pos += 4;
    }
    out.write(field_6_unknown);
  }

  /**
   * Gets the stream ID containing the actual data. The data itself can be found under a top-level
   * directory entry in the OLE2 filesystem under the name "MBD<var>xxxxxxxx</var>" where
   * <var>xxxxxxxx</var> is this ID converted into hex (in big endian order, funnily enough.)
   *
   * @return the data stream ID. Possibly <code>null</code>
   */
  public Integer getStreamId() {
    return field_5_stream_id;
  }

  public String getOLEClassName() {
    return field_4_ole_classname;
  }

  public byte[] getObjectData() {
    return field_6_unknown;
  }

  @Override
  public EmbeddedObjectRefSubRecord clone() {
    return this; // TODO proper clone
  }

  public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append("[ftPictFmla]\n");
    sb.append("    .f2unknown     = ").append(HexDump.intToHex(field_1_unknown_int)).append("\n");
    if (field_2_refPtg == null) {
      sb.append("    .f3unknown     = ")
          .append(HexDump.toHex(field_2_unknownFormulaData))
          .append("\n");
    } else {
      sb.append("    .formula       = ").append(field_2_refPtg.toString()).append("\n");
    }
    if (field_4_ole_classname != null) {
      sb.append("    .unicodeFlag   = ").append(field_3_unicode_flag).append("\n");
      sb.append("    .oleClassname  = ").append(field_4_ole_classname).append("\n");
    }
    if (field_4_unknownByte != null) {
      sb.append("    .f4unknown   = ")
          .append(HexDump.byteToHex(field_4_unknownByte.intValue()))
          .append("\n");
    }
    if (field_5_stream_id != null) {
      sb.append("    .streamId      = ")
          .append(HexDump.intToHex(field_5_stream_id.intValue()))
          .append("\n");
    }
    if (field_6_unknown.length > 0) {
      sb.append("    .f7unknown     = ").append(HexDump.toHex(field_6_unknown)).append("\n");
    }
    sb.append("[/ftPictFmla]");
    return sb.toString();
  }

  public void setUnknownFormulaData(byte[] formularData) {
    field_2_unknownFormulaData = formularData;
  }

  public void setOleClassname(String oleClassname) {
    field_4_ole_classname = oleClassname;
  }

  public void setStorageId(int storageId) {
    field_5_stream_id = storageId;
  }
}
Example #5
0
/**
 * Definition of a special kind of property of some text, or its paragraph. For these properties, a
 * flag in the "contains" header field tells you the data property family will exist. The value of
 * the property is itself a mask, encoding several different (but related) properties
 */
public abstract class BitMaskTextProp extends TextProp implements Cloneable {
  protected static final POILogger logger = POILogFactory.getLogger(BitMaskTextProp.class);

  private String[] subPropNames;
  private int[] subPropMasks;
  private boolean[] subPropMatches;

  /** Fetch the list of the names of the sub properties */
  public String[] getSubPropNames() {
    return subPropNames;
  }
  /** Fetch the list of if the sub properties match or not */
  public boolean[] getSubPropMatches() {
    return subPropMatches;
  }

  protected BitMaskTextProp(
      int sizeOfDataBlock, int maskInHeader, String overallName, String... subPropNames) {
    super(sizeOfDataBlock, maskInHeader, "bitmask");
    this.subPropNames = subPropNames;
    this.propName = overallName;
    subPropMasks = new int[subPropNames.length];
    subPropMatches = new boolean[subPropNames.length];

    int LSB = Integer.lowestOneBit(maskInHeader);

    // Initialise the masks list
    for (int i = 0; i < subPropMasks.length; i++) {
      subPropMasks[i] = (LSB << i);
    }
  }

  /** Calculate mask from the subPropMatches. */
  @Override
  public int getWriteMask() {
    /*
     * The dataValue can't be taken as a mask, as sometimes certain properties
     * are explicitly set to false, i.e. the mask says the property is defined
     * but in the actually nibble the property is set to false
     */
    int mask = 0, i = 0;
    for (int subMask : subPropMasks) {
      if (subPropMatches[i++]) mask |= subMask;
    }
    return mask;
  }

  /**
   * Sets the write mask, i.e. which defines the text properties to be considered
   *
   * @param writeMask the mask, bit values outside the property mask range will be ignored
   */
  public void setWriteMask(int writeMask) {
    int i = 0;
    for (int subMask : subPropMasks) {
      subPropMatches[i++] = ((writeMask & subMask) != 0);
    }
  }

  /**
   * Return the text property value. Clears all bits of the value, which are marked as unset.
   *
   * @return the text property value.
   */
  @Override
  public int getValue() {
    int val = dataValue, i = 0;
    ;
    for (int mask : subPropMasks) {
      if (!subPropMatches[i++]) {
        val &= ~mask;
      }
    }
    return val;
  }

  /**
   * Set the value of the text property, and recompute the sub properties based on it, i.e. all
   * unset subvalues will be cleared. Use {@link #setSubValue(boolean, int)} to explicitly set
   * subvalues to {@code false}.
   */
  @Override
  public void setValue(int val) {
    dataValue = val;

    // Figure out the values of the sub properties
    int i = 0;
    for (int mask : subPropMasks) {
      subPropMatches[i++] = ((val & mask) != 0);
    }
  }

  /**
   * Convenience method to set a value with mask, without splitting it into the subvalues
   *
   * @param val
   * @param writeMask
   */
  public void setValueWithMask(int val, int writeMask) {
    setWriteMask(writeMask);
    dataValue = val;
    dataValue = getValue();
    if (val != dataValue) {
      logger.log(
          POILogger.WARN,
          "Style properties of '" + getName() + "' don't match mask - output will be sanitized");
      if (logger.check(POILogger.DEBUG)) {
        StringBuilder sb =
            new StringBuilder(
                "The following style attributes of the '"
                    + getName()
                    + "' property will be ignored:\n");
        int i = 0;
        for (int mask : subPropMasks) {
          if (!subPropMatches[i] && (val & mask) != 0) {
            sb.append(subPropNames[i] + ",");
          }
          i++;
        }
        logger.log(POILogger.DEBUG, sb.toString());
      }
    }
  }

  /** Fetch the true/false status of the subproperty with the given index */
  public boolean getSubValue(int idx) {
    return subPropMatches[idx] && ((dataValue & subPropMasks[idx]) != 0);
  }

  /** Set the true/false status of the subproperty with the given index */
  public void setSubValue(boolean value, int idx) {
    subPropMatches[idx] = true;
    if (value) {
      dataValue |= subPropMasks[idx];
    } else {
      dataValue &= ~subPropMasks[idx];
    }
  }

  @Override
  public BitMaskTextProp clone() {
    BitMaskTextProp newObj = (BitMaskTextProp) super.clone();

    // Don't carry over matches, but keep everything
    //  else as it was
    newObj.subPropMatches = new boolean[subPropMatches.length];

    return newObj;
  }

  public BitMaskTextProp cloneAll() {
    return (BitMaskTextProp) super.clone();
  }
}
Example #6
0
/**
 * Converts Word files (95-2007) into HTML files.
 *
 * <p>This implementation doesn't create images or links to them. This can be changed by overriding
 * {@link #processImage(Element, boolean, Picture)} method.
 *
 * @author Sergey Vladimirov (vlsergey {at} gmail {dot} com)
 */
public class DocConverter extends CustomerAbsConverter {
  /**
   * test
   *
   * @param args
   */
  public static void main(String[] args) {
    String filePath = "c:\\poi\\test.doc";
    //		String filePath = "c:\\poi\\××××内部文档保密系统20120425 v4.1 [秘密]1.doc";
    String output = "c:/poi/output/test.html";

    DocConverter.convert(filePath, output);
  }

  /** 文档图片集 */
  PicturesTable picstab = null;
  /** added pageContainer */
  static Element pageContainer = null;

  private static String output;

  private static String fileName;
  /** 转换文档 */
  public static String convert(String filePath, String output) {

    HtmlDocumentFacade facade = null;

    //		File docFile = new File(docPath);
    try {
      facade =
          new HtmlDocumentFacade(
              DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument());

      String s1 = "background-color:gray";
      facade.addStyleClass(facade.body, "body", s1);

    } catch (Exception e) {
      e.printStackTrace();
    }

    if (AbstractWordUtils.isEmpty(filePath) || AbstractWordUtils.isEmpty(output)) {
      System.err.println("docPath OR output is empty. >>!quit");
      return "";
    }

    System.out.println("Converting " + filePath);
    System.out.println("Saving output to " + output);

    DocConverter.output =
        output
            .substring(0, output.lastIndexOf(".html"))
            .concat(File.separator)
            .concat("images")
            .concat(File.separator);
    // get fileName

    new File(DocConverter.output).mkdirs();

    try {
      Document doc = null;
      if (facade == null) doc = DocConverter.process(new File(filePath));
      else {
        Document document = facade.getDocument();
        Element window = document.createElement("div");
        Element center = document.createElement("center");
        center.appendChild(window);
        facade.addStyleClass(
            window,
            "window",
            "border:2px solid green;width:800px!important; margin:0 auto; background-color:#fff; text-align:left;");
        facade.body.appendChild(center);
        pageContainer = window;

        doc = DocConverter.process(new File(filePath), facade);
      }

      FileWriter out = new FileWriter(output);
      DOMSource domSource = new DOMSource(doc);
      StreamResult streamResult = new StreamResult(out);

      TransformerFactory tf = TransformerFactory.newInstance();
      Transformer serializer = tf.newTransformer();
      // TODO set encoding from a command argument
      serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
      serializer.setOutputProperty(OutputKeys.INDENT, "yes");
      serializer.setOutputProperty(OutputKeys.METHOD, "html");
      serializer.transform(domSource, streamResult);
      out.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return "";
  }

  static Document process(File docFile) throws Exception {

    final HWPFDocument wordDocument = new HWPFDocument(new FileInputStream(docFile));
    DocConverter converter =
        new DocConverter(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument());
    converter.processDocument(wordDocument);
    return converter.getDocument();
  }

  static Document process(File docFile, HtmlDocumentFacade htmlDocumentFacade) throws Exception {

    final HWPFDocument wordDocument = new HWPFDocument(new FileInputStream(docFile));
    DocConverter converter = new DocConverter(htmlDocumentFacade);
    converter.processDocument(wordDocument);
    return converter.getDocument();
  }

  @Override
  public void processDocument(HWPFDocument wordDocument) {
    if (wordDocument.getPicturesTable().getAllPictures().size() > 0) {
      this.picstab = wordDocument.getPicturesTable();
    }
    super.processDocument(wordDocument);
  }

  /**
   * Holds properties values, applied to current <tt>p</tt> element. Those properties shall not be
   * doubled in children <tt>span</tt> elements.
   */
  private static class BlockProperies {
    final String pFontName;
    final int pFontSize;

    public BlockProperies(String pFontName, int pFontSize) {
      this.pFontName = pFontName;
      this.pFontSize = pFontSize;
    }
  }

  private static final POILogger logger = POILogFactory.getLogger(DocConverter.class);

  private static String getSectionStyle(Section section) {
    float leftMargin = section.getMarginLeft() / TWIPS_PER_INCH;
    float rightMargin = section.getMarginRight() / TWIPS_PER_INCH;
    float topMargin = section.getMarginTop() / TWIPS_PER_INCH;
    float bottomMargin = section.getMarginBottom() / TWIPS_PER_INCH;

    String style =
        "margin: "
            + topMargin
            + "in "
            + rightMargin
            + "in "
            + bottomMargin
            + "in "
            + leftMargin
            + "in;";

    if (section.getNumColumns() > 1) {
      style += "column-count: " + (section.getNumColumns()) + ";";
      if (section.isColumnsEvenlySpaced()) {
        float distance = section.getDistanceBetweenColumns() / TWIPS_PER_INCH;
        style += "column-gap: " + distance + "in;";
      } else {
        style += "column-gap: 0.25in;";
      }
    }
    return style;
  }

  private final Stack<BlockProperies> blocksProperies = new Stack<BlockProperies>();

  private final HtmlDocumentFacade htmlDocumentFacade;

  private Element notes = null;

  /**
   * Creates new instance of {@link DocConverter}. Can be used for output several {@link
   * HWPFDocument}s into single HTML document.
   *
   * @param document XML DOM Document used as HTML document
   */
  public DocConverter(Document document) {
    this.htmlDocumentFacade = new HtmlDocumentFacade(document);
  }

  public DocConverter(HtmlDocumentFacade htmlDocumentFacade) {
    this.htmlDocumentFacade = htmlDocumentFacade;
  }

  @Override
  protected void afterProcess() {
    if (notes != null) htmlDocumentFacade.getBody().appendChild(notes);

    htmlDocumentFacade.updateStylesheet();
  }

  public Document getDocument() {
    return htmlDocumentFacade.getDocument();
  }

  @Override
  protected void outputCharacters(Element pElement, CharacterRun characterRun, String text) {
    Element span = htmlDocumentFacade.document.createElement("span");
    pElement.appendChild(span);

    StringBuilder style = new StringBuilder();
    BlockProperies blockProperies = this.blocksProperies.peek();
    Triplet triplet = getCharacterRunTriplet(characterRun);

    if (WordToHtmlUtils.isNotEmpty(triplet.fontName)
        && !WordToHtmlUtils.equals(triplet.fontName, blockProperies.pFontName)) {
      style.append("font-family:" + triplet.fontName + ";");
    }
    if (characterRun.getFontSize() / 2 != blockProperies.pFontSize) {
      style.append("font-size:" + characterRun.getFontSize() / 2 + "pt;");
    }
    if (triplet.bold) {
      style.append("font-weight:bold;");
    }
    if (triplet.italic) {
      style.append("font-style:italic;");
    }

    WordToHtmlUtils.addCharactersProperties(characterRun, style);
    if (style.length() != 0) htmlDocumentFacade.addStyleClass(span, "s", style.toString());

    Text textNode = htmlDocumentFacade.createText(text);
    span.appendChild(textNode);
  }

  @Override
  protected void processBookmarks(
      HWPFDocument wordDocument,
      Element currentBlock,
      Range range,
      int currentTableLevel,
      List<Bookmark> rangeBookmarks) {
    Element parent = currentBlock;
    for (Bookmark bookmark : rangeBookmarks) {
      Element bookmarkElement = htmlDocumentFacade.createBookmark(bookmark.getName());
      parent.appendChild(bookmarkElement);
      parent = bookmarkElement;
    }

    if (range != null) processCharacters(wordDocument, currentTableLevel, range, parent);
  }

  @Override
  protected void processDocumentInformation(SummaryInformation summaryInformation) {
    if (WordToHtmlUtils.isNotEmpty(summaryInformation.getTitle()))
      htmlDocumentFacade.setTitle(summaryInformation.getTitle());

    if (WordToHtmlUtils.isNotEmpty(summaryInformation.getAuthor()))
      htmlDocumentFacade.addAuthor(summaryInformation.getAuthor());

    if (WordToHtmlUtils.isNotEmpty(summaryInformation.getKeywords()))
      htmlDocumentFacade.addKeywords(summaryInformation.getKeywords());

    if (WordToHtmlUtils.isNotEmpty(summaryInformation.getComments()))
      htmlDocumentFacade.addDescription(summaryInformation.getComments());
  }

  @Override
  public void processDocumentPart(HWPFDocument wordDocument, Range range) {
    super.processDocumentPart(wordDocument, range);
    afterProcess();
  }

  @Override
  protected void processDrawnObject(
      HWPFDocument doc,
      CharacterRun characterRun,
      OfficeDrawing officeDrawing,
      String path,
      Element block) {
    Element img = htmlDocumentFacade.createImage(path);
    block.appendChild(img);
  }

  @Override
  protected void processEndnoteAutonumbered(
      HWPFDocument wordDocument, int noteIndex, Element block, Range endnoteTextRange) {
    processNoteAutonumbered(wordDocument, "end", noteIndex, block, endnoteTextRange);
  }

  @Override
  protected void processFootnoteAutonumbered(
      HWPFDocument wordDocument, int noteIndex, Element block, Range footnoteTextRange) {
    processNoteAutonumbered(wordDocument, "foot", noteIndex, block, footnoteTextRange);
  }

  @Override
  protected void processHyperlink(
      HWPFDocument wordDocument,
      Element currentBlock,
      Range textRange,
      int currentTableLevel,
      String hyperlink) {
    Element basicLink = htmlDocumentFacade.createHyperlink(hyperlink);
    currentBlock.appendChild(basicLink);

    if (textRange != null) processCharacters(wordDocument, currentTableLevel, textRange, basicLink);
  }

  @SuppressWarnings("deprecation")
  protected void processImage(
      Element currentBlock, boolean inlined, Picture picture, String imageSourcePath) {
    final int aspectRatioX = picture.getHorizontalScalingFactor();
    final int aspectRatioY = picture.getVerticalScalingFactor();

    StringBuilder style = new StringBuilder();

    final float imageWidth;
    final float imageHeight;

    final float cropTop;
    final float cropBottom;
    final float cropLeft;
    final float cropRight;

    if (aspectRatioX > 0) {
      imageWidth = picture.getDxaGoal() * aspectRatioX / 1000 / TWIPS_PER_INCH;
      cropRight = picture.getDxaCropRight() * aspectRatioX / 1000 / TWIPS_PER_INCH;
      cropLeft = picture.getDxaCropLeft() * aspectRatioX / 1000 / TWIPS_PER_INCH;
    } else {
      imageWidth = picture.getDxaGoal() / TWIPS_PER_INCH;
      cropRight = picture.getDxaCropRight() / TWIPS_PER_INCH;
      cropLeft = picture.getDxaCropLeft() / TWIPS_PER_INCH;
    }

    if (aspectRatioY > 0) {
      imageHeight = picture.getDyaGoal() * aspectRatioY / 1000 / TWIPS_PER_INCH;
      cropTop = picture.getDyaCropTop() * aspectRatioY / 1000 / TWIPS_PER_INCH;
      cropBottom = picture.getDyaCropBottom() * aspectRatioY / 1000 / TWIPS_PER_INCH;
    } else {
      imageHeight = picture.getDyaGoal() / TWIPS_PER_INCH;
      cropTop = picture.getDyaCropTop() / TWIPS_PER_INCH;
      cropBottom = picture.getDyaCropBottom() / TWIPS_PER_INCH;
    }

    Element root;
    if (cropTop != 0 || cropRight != 0 || cropBottom != 0 || cropLeft != 0) {
      float visibleWidth = Math.max(0, imageWidth - cropLeft - cropRight);
      float visibleHeight = Math.max(0, imageHeight - cropTop - cropBottom);

      root = htmlDocumentFacade.createBlock();
      htmlDocumentFacade.addStyleClass(
          root,
          "d",
          "vertical-align:text-bottom;width:"
              + visibleWidth
              + "in;height:"
              + visibleHeight
              + "in;");

      // complex
      Element inner = htmlDocumentFacade.createBlock();
      htmlDocumentFacade.addStyleClass(
          inner,
          "d",
          "position:relative;width:"
              + visibleWidth
              + "in;height:"
              + visibleHeight
              + "in;overflow:hidden;");
      root.appendChild(inner);

      Element image = htmlDocumentFacade.createImage(imageSourcePath);
      htmlDocumentFacade.addStyleClass(
          image,
          "i",
          "position:absolute;left:-"
              + cropLeft
              + ";top:-"
              + cropTop
              + ";width:"
              + imageWidth
              + "in;height:"
              + imageHeight
              + "in;");
      inner.appendChild(image);

      style.append("overflow:hidden;");
    } else {
      root = htmlDocumentFacade.createImage(imageSourcePath);
      root.setAttribute(
          "style",
          "width:" + imageWidth + "in;height:" + imageHeight + "in;vertical-align:text-bottom;");
    }

    currentBlock.appendChild(root);
  }

  @Override
  protected void processImageWithoutPicturesManager(
      Element currentBlock, boolean inlined, Picture picture) {
    // no default implementation -- skip
    currentBlock.appendChild(
        htmlDocumentFacade.document.createComment(
            "Image link to '" + picture.suggestFullFileName() + "' can be here"));
  }

  @Override
  protected void processLineBreak(Element block, CharacterRun characterRun) {
    block.appendChild(htmlDocumentFacade.createLineBreak());
  }

  protected void processNoteAutonumbered(
      HWPFDocument doc, String type, int noteIndex, Element block, Range noteTextRange) {
    final String textIndex = String.valueOf(noteIndex + 1);
    final String textIndexClass =
        htmlDocumentFacade.getOrCreateCssClass("a", "vertical-align:super;font-size:smaller;");
    final String forwardNoteLink = type + "note_" + textIndex;
    final String backwardNoteLink = type + "note_back_" + textIndex;

    Element anchor = htmlDocumentFacade.createHyperlink("#" + forwardNoteLink);
    anchor.setAttribute("name", backwardNoteLink);
    anchor.setAttribute("class", textIndexClass + " " + type + "noteanchor");
    anchor.setTextContent(textIndex);
    block.appendChild(anchor);

    if (notes == null) {
      notes = htmlDocumentFacade.createBlock();
      notes.setAttribute("class", "notes");
    }

    Element note = htmlDocumentFacade.createBlock();
    note.setAttribute("class", type + "note");
    notes.appendChild(note);

    Element bookmark = htmlDocumentFacade.createBookmark(forwardNoteLink);
    bookmark.setAttribute("href", "#" + backwardNoteLink);
    bookmark.setTextContent(textIndex);
    bookmark.setAttribute("class", textIndexClass + " " + type + "noteindex");
    note.appendChild(bookmark);
    note.appendChild(htmlDocumentFacade.createText(" "));

    Element span = htmlDocumentFacade.getDocument().createElement("span");
    span.setAttribute("class", type + "notetext");
    note.appendChild(span);

    this.blocksProperies.add(new BlockProperies("", -1));
    try {
      processCharacters(doc, Integer.MIN_VALUE, noteTextRange, span);
    } finally {
      this.blocksProperies.pop();
    }
  }

  @Override
  protected void processPageBreak(HWPFDocument wordDocument, Element flow) {
    flow.appendChild(htmlDocumentFacade.createLineBreak());
  }

  protected void processPageref(
      HWPFDocument hwpfDocument,
      Element currentBlock,
      Range textRange,
      int currentTableLevel,
      String pageref) {
    Element basicLink = htmlDocumentFacade.createHyperlink("#" + pageref);
    currentBlock.appendChild(basicLink);

    if (textRange != null) processCharacters(hwpfDocument, currentTableLevel, textRange, basicLink);
  }

  protected void processParagraph(
      HWPFDocument hwpfDocument,
      Element parentElement,
      int currentTableLevel,
      Paragraph paragraph,
      String bulletText) {
    final Element pElement = htmlDocumentFacade.createParagraph();
    parentElement.appendChild(pElement);
    /*if(itemSymbol)
    System.out.println(itemSymbol);*/
    if (itemSymbol) {
      Element span = htmlDocumentFacade.getDocument().createElement("span");
      htmlDocumentFacade.addStyleClass(
          span,
          "itemSymbol",
          "font-size:12.0pt;line-height:150%;font-family:Wingdings;mso-ascii-font-family:Wingdings;mso-hide:none;mso-ansi-language:EN-US;mso-fareast-language:ZH-CN;font-weight:normal;mso-bidi-font-weight:normal;font-style:normal;mso-bidi-font-style:normal;text-underline:windowtext none;text-decoration:none;background:transparent");
      span.setTextContent("Ø");
      pElement.appendChild(span);
      itemSymbol = false;
    }

    StringBuilder style = new StringBuilder();
    WordToHtmlUtils.addParagraphProperties(paragraph, style);

    final int charRuns = paragraph.numCharacterRuns();
    if (charRuns == 0) {
      return;
    }

    {
      final String pFontName;
      final int pFontSize;
      final CharacterRun characterRun = paragraph.getCharacterRun(0);
      if ("".equals(paragraph.text().trim())) {
        pElement.setTextContent(String.valueOf(UNICODECHAR_NO_BREAK_SPACE));
      }
      if (characterRun != null) {
        Triplet triplet = getCharacterRunTriplet(characterRun);
        pFontSize = characterRun.getFontSize() / 2;
        pFontName = triplet.fontName;
        WordToHtmlUtils.addFontFamily(pFontName, style);
        WordToHtmlUtils.addFontSize(pFontSize, style);
      } else {
        pFontSize = -1;
        pFontName = WordToHtmlUtils.EMPTY;
      }
      blocksProperies.push(new BlockProperies(pFontName, pFontSize));
    }
    try {
      if (WordToHtmlUtils.isNotEmpty(bulletText)) {
        if (bulletText.endsWith("\t")) {
          /*
           * We don't know how to handle all cases in HTML, but at
           * least simplest case shall be handled
           */
          final float defaultTab = TWIPS_PER_INCH / 2;
          float firstLinePosition =
              paragraph.getIndentFromLeft() + paragraph.getFirstLineIndent() + 20; // char have
          // some space

          float nextStop = (float) (Math.ceil(firstLinePosition / defaultTab) * defaultTab);

          final float spanMinWidth = nextStop - firstLinePosition;

          Element span = htmlDocumentFacade.getDocument().createElement("span");
          htmlDocumentFacade.addStyleClass(
              span,
              "s",
              "display: inline-block; text-indent: 0; min-width: "
                  + (spanMinWidth / TWIPS_PER_INCH)
                  + "in;");
          pElement.appendChild(span);

          Text textNode =
              htmlDocumentFacade.createText(
                  bulletText.substring(0, bulletText.length() - 1)
                      + UNICODECHAR_ZERO_WIDTH_SPACE
                      + UNICODECHAR_NO_BREAK_SPACE);
          span.appendChild(textNode);
        } else {
          Text textNode =
              htmlDocumentFacade.createText(bulletText.substring(0, bulletText.length() - 1));
          pElement.appendChild(textNode);
        }
      }

      processCharacters(hwpfDocument, currentTableLevel, paragraph, pElement);
    } finally {
      blocksProperies.pop();
    }

    if (style.length() > 0) htmlDocumentFacade.addStyleClass(pElement, "p", style.toString());

    WordToHtmlUtils.compactSpans(pElement);
    return;
  }

  protected void processSection(HWPFDocument wordDocument, Section section, int sectionCounter) {
    // TODO   解析章节
    Element div = htmlDocumentFacade.createBlock();
    htmlDocumentFacade.addStyleClass(div, "d", getSectionStyle(section));
    //      htmlDocumentFacade.body.appendChild( div );
    pageContainer.appendChild(div);

    processParagraphes(wordDocument, div, section, Integer.MIN_VALUE);
  }

  protected void processSingleSection(HWPFDocument wordDocument, Section section) {
    htmlDocumentFacade.addStyleClass(htmlDocumentFacade.body, "b", getSectionStyle(section));

    processParagraphes(wordDocument, htmlDocumentFacade.body, section, Integer.MIN_VALUE);
  }
  /** 解析table */
  protected void processTable(HWPFDocument hwpfDocument, Element flow, Table table) {
    Element tableHeader = htmlDocumentFacade.createTableHeader();
    Element tableBody = htmlDocumentFacade.createTableBody();

    final int[] tableCellEdges = WordToHtmlUtils.buildTableCellEdgesArray(table);
    final int tableRows = table.numRows();

    int maxColumns = Integer.MIN_VALUE;
    for (int r = 0; r < tableRows; r++) {
      maxColumns = Math.max(maxColumns, table.getRow(r).numCells());
    }

    for (int r = 0; r < tableRows; r++) {
      TableRow tableRow = table.getRow(r);

      Element tableRowElement = htmlDocumentFacade.createTableRow();
      StringBuilder tableRowStyle = new StringBuilder();

      WordToHtmlUtils.addTableRowProperties(tableRow, tableRowStyle);

      // index of current element in tableCellEdges[]
      int currentEdgeIndex = 0;
      final int rowCells = tableRow.numCells();
      for (int c = 0; c < rowCells; c++) {
        TableCell tableCell = tableRow.getCell(c);

        if (tableCell.isVerticallyMerged() && !tableCell.isFirstVerticallyMerged()) {
          currentEdgeIndex += getNumberColumnsSpanned(tableCellEdges, currentEdgeIndex, tableCell);
          continue;
        }

        Element tableCellElement;
        if (tableRow.isTableHeader()) {
          tableCellElement = htmlDocumentFacade.createTableHeaderCell();
        } else {
          tableCellElement = htmlDocumentFacade.createTableCell();
        }
        StringBuilder tableCellStyle = new StringBuilder();
        WordToHtmlUtils.addTableCellProperties(
            tableRow,
            tableCell,
            r == 0,
            r == tableRows - 1,
            c == 0,
            c == rowCells - 1,
            tableCellStyle);

        int colSpan = getNumberColumnsSpanned(tableCellEdges, currentEdgeIndex, tableCell);
        currentEdgeIndex += colSpan;

        if (colSpan == 0) continue;

        if (colSpan != 1) tableCellElement.setAttribute("colspan", String.valueOf(colSpan));

        final int rowSpan = getNumberRowsSpanned(table, tableCellEdges, r, c, tableCell);
        if (rowSpan > 1) tableCellElement.setAttribute("rowspan", String.valueOf(rowSpan));

        processParagraphes(hwpfDocument, tableCellElement, tableCell, table.getTableLevel());

        if (!tableCellElement.hasChildNodes()) {
          tableCellElement.appendChild(htmlDocumentFacade.createParagraph());
        }
        if (tableCellStyle.length() > 0)
          htmlDocumentFacade.addStyleClass(
              tableCellElement, tableCellElement.getTagName(), tableCellStyle.toString());

        tableRowElement.appendChild(tableCellElement);
      }

      if (tableRowStyle.length() > 0)
        tableRowElement.setAttribute(
            "class", htmlDocumentFacade.getOrCreateCssClass("r", tableRowStyle.toString()));

      if (tableRow.isTableHeader()) {
        tableHeader.appendChild(tableRowElement);
      } else {
        tableBody.appendChild(tableRowElement);
      }
    }

    final Element tableElement = htmlDocumentFacade.createTable();
    tableElement.setAttribute(
        "class",
        htmlDocumentFacade.getOrCreateCssClass(
            "t", "table-layout:fixed;border-collapse:collapse;border-spacing:0;"));
    if (tableHeader.hasChildNodes()) {
      tableElement.appendChild(tableHeader);
    }
    if (tableBody.hasChildNodes()) {
      tableElement.appendChild(tableBody);
      flow.appendChild(tableElement);
    } else {
      logger.log(
          POILogger.WARN,
          "Table without body starting at [",
          Integer.valueOf(table.getStartOffset()),
          "; ",
          Integer.valueOf(table.getEndOffset()),
          ")");
    }
  }

  /** 加入图片 */
  protected void processImage(Element parent, CharacterRun cr) {
    if (this.picstab.hasPicture(cr)) {
      Picture pic = picstab.extractPicture(cr, false);

      String fileName = pic.suggestFullFileName();
      OutputStream out = null;
      try {

        //					TWIPS_PER_INCH/
        out = new FileOutputStream(new File(output.concat(File.separator).concat(fileName)));
        pic.writeImageContent(out);
        Element img = htmlDocumentFacade.getDocument().createElement("img");
        String uri = fileName.concat("image").concat(File.separator).concat(fileName);
        img.setAttribute("src", uri);
        if (pic.getWidth() > 600) img.setAttribute("style", "width: 600px;");
        Element imgBlock = htmlDocumentFacade.createBlock();
        this.htmlDocumentFacade.addStyleClass(imgBlock, "imgs", "text-align:center;");
        imgBlock.appendChild(img);
        parent.appendChild(imgBlock);

      } catch (Exception e) {
        e.printStackTrace();
      } finally {
        if (out != null)
          try {
            out.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
      }
    }
  } // 图片END

  private boolean itemSymbol = false;

  @Override
  protected void processParagraphes(
      HWPFDocument wordDocument, Element flow, Range range, int currentTableLevel) {
    // TODO  mc process paragraphes

    final ListTables listTables = wordDocument.getListTables();
    int currentListInfo = 0;

    final int paragraphs = range.numParagraphs();
    for (int p = 0; p < paragraphs; p++) {
      Paragraph paragraph = range.getParagraph(p);

      //			加入图片
      CharacterRun cr = paragraph.getCharacterRun(0);
      this.processImage(flow, cr);
      //          table
      if (paragraph.isInTable() && paragraph.getTableLevel() != currentTableLevel) {
        if (paragraph.getTableLevel() < currentTableLevel)
          throw new IllegalStateException(
              "Trying to process table cell with higher level ("
                  + paragraph.getTableLevel()
                  + ") than current table level ("
                  + currentTableLevel
                  + ") as inner table part");

        Table table = range.getTable(paragraph);
        processTable(wordDocument, flow, table);

        p += table.numParagraphs();
        p--;
        continue;
      }
      //          换页
      if (paragraph.text().equals("\u000c")) {
        processPageBreak(wordDocument, flow);
      }
      if (paragraph.getIlfo() != currentListInfo) {
        currentListInfo = paragraph.getIlfo();
      }
      //          嵌套段落
      if (currentListInfo != 0) {
        if (listTables != null) {

          final ListFormatOverride listFormatOverride = listTables.getOverride(paragraph.getIlfo());

          String label = getBulletText(listTables, paragraph, listFormatOverride.getLsid());

          if ("".equals(label)) {
            itemSymbol = true;
            /*
            Element span = htmlDocumentFacade.getDocument().createElement("span");
            span.setAttribute("style", "font-size:12.0pt;line-height:150%;font-family:Wingdings;mso-ascii-font-family:Wingdings;mso-hide:none;mso-ansi-language:EN-US;mso-fareast-language:ZH-CN;font-weight:normal;mso-bidi-font-weight:normal;font-style:normal;mso-bidi-font-style:normal;text-underline:windowtext none;text-decoration:none;background:transparent");
            span.setTextContent("Ø");

            flow.appendChild(span);
            */
          }

          processParagraph(wordDocument, flow, currentTableLevel, paragraph, label);
        } else {
          logger.log(
              POILogger.WARN,
              "Paragraph #"
                  + paragraph.getStartOffset()
                  + "-"
                  + paragraph.getEndOffset()
                  + " has reference to list structure #"
                  + currentListInfo
                  + ", but listTables not defined in file");

          processParagraph(
              wordDocument, flow, currentTableLevel, paragraph, AbstractWordUtils.EMPTY);
        }
      } else {
        processParagraph(wordDocument, flow, currentTableLevel, paragraph, AbstractWordUtils.EMPTY);
      }
    }
  }

  ///////////////
  private static String getBulletText(ListTables listTables, Paragraph paragraph, int listId) {
    final ListLevel listLevel = listTables.getLevel(listId, paragraph.getIlvl());

    if (listLevel.getNumberText() == null) return "";

    StringBuffer bulletBuffer = new StringBuffer();
    char[] xst = listLevel.getNumberText().toCharArray();
    for (char element : xst) {
      if (element < 9) {
        ListLevel numLevel = listTables.getLevel(listId, element);

        int num = numLevel.getStartAt();
        bulletBuffer.append(NumberFormatter.getNumber(num, listLevel.getNumberFormat()));

        if (numLevel == listLevel) {
          numLevel.setStartAt(numLevel.getStartAt() + 1);
        }

      } else {
        bulletBuffer.append(element);
      }
    }

    byte follow = listLevel.getTypeOfCharFollowingTheNumber();
    switch (follow) {
      case 0:
        bulletBuffer.append("\t");
        break;
      case 1:
        bulletBuffer.append(" ");
        break;
      default:
        break;
    }

    return bulletBuffer.toString();
  }
}
/**
 * Helper for part and pack URI.
 *
 * @author Julien Chable, CDubet, Kim Ung
 * @version 0.1
 */
public final class PackagingURIHelper {
  private static final POILogger _logger = POILogFactory.getLogger(PackagingURIHelper.class);

  /** Package root URI. */
  private static URI packageRootUri;

  /** Extension name of a relationship part. */
  public static final String RELATIONSHIP_PART_EXTENSION_NAME;

  /** Segment name of a relationship part. */
  public static final String RELATIONSHIP_PART_SEGMENT_NAME;

  /** Segment name of the package properties folder. */
  public static final String PACKAGE_PROPERTIES_SEGMENT_NAME;

  /** Core package properties art name. */
  public static final String PACKAGE_CORE_PROPERTIES_NAME;

  /** Forward slash URI separator. */
  public static final char FORWARD_SLASH_CHAR;

  /** Forward slash URI separator. */
  public static final String FORWARD_SLASH_STRING;

  /** Package relationships part URI */
  public static final URI PACKAGE_RELATIONSHIPS_ROOT_URI;

  /** Package relationships part name. */
  public static final PackagePartName PACKAGE_RELATIONSHIPS_ROOT_PART_NAME;

  /** Core properties part URI. */
  public static final URI CORE_PROPERTIES_URI;

  /** Core properties partname. */
  public static final PackagePartName CORE_PROPERTIES_PART_NAME;

  /** Root package URI. */
  public static final URI PACKAGE_ROOT_URI;

  /** Root package part name. */
  public static final PackagePartName PACKAGE_ROOT_PART_NAME;

  /* Static initialization */
  static {
    RELATIONSHIP_PART_SEGMENT_NAME = "_rels";
    RELATIONSHIP_PART_EXTENSION_NAME = ".rels";
    FORWARD_SLASH_CHAR = '/';
    FORWARD_SLASH_STRING = "/";
    PACKAGE_PROPERTIES_SEGMENT_NAME = "docProps";
    PACKAGE_CORE_PROPERTIES_NAME = "core.xml";

    // Make URI
    URI uriPACKAGE_ROOT_URI = null;
    URI uriPACKAGE_RELATIONSHIPS_ROOT_URI = null;
    URI uriPACKAGE_PROPERTIES_URI = null;
    try {
      uriPACKAGE_ROOT_URI = new URI("/");
      uriPACKAGE_RELATIONSHIPS_ROOT_URI =
          new URI(
              FORWARD_SLASH_CHAR
                  + RELATIONSHIP_PART_SEGMENT_NAME
                  + FORWARD_SLASH_CHAR
                  + RELATIONSHIP_PART_EXTENSION_NAME);
      packageRootUri = new URI("/");
      uriPACKAGE_PROPERTIES_URI =
          new URI(
              FORWARD_SLASH_CHAR
                  + PACKAGE_PROPERTIES_SEGMENT_NAME
                  + FORWARD_SLASH_CHAR
                  + PACKAGE_CORE_PROPERTIES_NAME);
    } catch (URISyntaxException e) {
      // Should never happen in production as all data are fixed
    }
    PACKAGE_ROOT_URI = uriPACKAGE_ROOT_URI;
    PACKAGE_RELATIONSHIPS_ROOT_URI = uriPACKAGE_RELATIONSHIPS_ROOT_URI;
    CORE_PROPERTIES_URI = uriPACKAGE_PROPERTIES_URI;

    // Make part name from previous URI
    PackagePartName tmpPACKAGE_ROOT_PART_NAME = null;
    PackagePartName tmpPACKAGE_RELATIONSHIPS_ROOT_PART_NAME = null;
    PackagePartName tmpCORE_PROPERTIES_URI = null;
    try {
      tmpPACKAGE_RELATIONSHIPS_ROOT_PART_NAME = createPartName(PACKAGE_RELATIONSHIPS_ROOT_URI);
      tmpCORE_PROPERTIES_URI = createPartName(CORE_PROPERTIES_URI);
      tmpPACKAGE_ROOT_PART_NAME = new PackagePartName(PACKAGE_ROOT_URI, false);
    } catch (InvalidFormatException e) {
      // Should never happen in production as all data are fixed
    }
    PACKAGE_RELATIONSHIPS_ROOT_PART_NAME = tmpPACKAGE_RELATIONSHIPS_ROOT_PART_NAME;
    CORE_PROPERTIES_PART_NAME = tmpCORE_PROPERTIES_URI;
    PACKAGE_ROOT_PART_NAME = tmpPACKAGE_ROOT_PART_NAME;
  }

  /**
   * Gets the URI for the package root.
   *
   * @return URI of the package root.
   */
  public static URI getPackageRootUri() {
    return packageRootUri;
  }

  /**
   * Know if the specified URI is a relationship part name.
   *
   * @param partUri URI to check.
   * @return <i>true</i> if the URI <i>false</i>.
   */
  public static boolean isRelationshipPartURI(URI partUri) {
    if (partUri == null) throw new IllegalArgumentException("partUri");

    return partUri
        .getPath()
        .matches(
            ".*" + RELATIONSHIP_PART_SEGMENT_NAME + ".*" + RELATIONSHIP_PART_EXTENSION_NAME + "$");
  }

  /** Get file name from the specified URI. */
  public static String getFilename(URI uri) {
    if (uri != null) {
      String path = uri.getPath();
      int len = path.length();
      int num2 = len;
      while (--num2 >= 0) {
        char ch1 = path.charAt(num2);
        if (ch1 == PackagingURIHelper.FORWARD_SLASH_CHAR) return path.substring(num2 + 1, len);
      }
    }
    return "";
  }

  /** Get the file name without the trailing extension. */
  public static String getFilenameWithoutExtension(URI uri) {
    String filename = getFilename(uri);
    int dotIndex = filename.lastIndexOf(".");
    if (dotIndex == -1) return filename;
    return filename.substring(0, dotIndex);
  }

  /** Get the directory path from the specified URI. */
  public static URI getPath(URI uri) {
    if (uri != null) {
      String path = uri.getPath();
      int len = path.length();
      int num2 = len;
      while (--num2 >= 0) {
        char ch1 = path.charAt(num2);
        if (ch1 == PackagingURIHelper.FORWARD_SLASH_CHAR) {
          try {
            return new URI(path.substring(0, num2));
          } catch (URISyntaxException e) {
            return null;
          }
        }
      }
    }
    return null;
  }

  /**
   * Combine two URIs.
   *
   * @param prefix the prefix URI
   * @param suffix the suffix URI
   * @return the combined URI
   */
  public static URI combine(URI prefix, URI suffix) {
    URI retUri = null;
    try {
      retUri = new URI(combine(prefix.getPath(), suffix.getPath()));
    } catch (URISyntaxException e) {
      throw new IllegalArgumentException("Prefix and suffix can't be combine !");
    }
    return retUri;
  }

  /** Combine a string URI with a prefix and a suffix. */
  public static String combine(String prefix, String suffix) {
    if (!prefix.endsWith("" + FORWARD_SLASH_CHAR) && !suffix.startsWith("" + FORWARD_SLASH_CHAR))
      return prefix + FORWARD_SLASH_CHAR + suffix;
    else if ((!prefix.endsWith("" + FORWARD_SLASH_CHAR)
            && suffix.startsWith("" + FORWARD_SLASH_CHAR)
        || (prefix.endsWith("" + FORWARD_SLASH_CHAR)
            && !suffix.startsWith("" + FORWARD_SLASH_CHAR)))) return prefix + suffix;
    else return "";
  }

  /**
   * Fully relativize the source part URI against the target part URI.
   *
   * @param sourceURI The source part URI.
   * @param targetURI The target part URI.
   * @param msCompatible if true then remove leading slash from the relativized URI. This flag
   *     violates [M1.4]: A part name shall start with a forward slash ('/') character, but allows
   *     generating URIs compatible with MS Office and OpenOffice.
   * @return A fully relativize part name URI ('word/media/image1.gif', '/word/document.xml' =>
   *     'media/image1.gif') else <code>null</code>.
   */
  public static URI relativizeURI(URI sourceURI, URI targetURI, boolean msCompatible) {
    StringBuilder retVal = new StringBuilder();
    String[] segmentsSource = sourceURI.getPath().split("/", -1);
    String[] segmentsTarget = targetURI.getPath().split("/", -1);

    // If the source URI is empty
    if (segmentsSource.length == 0) {
      throw new IllegalArgumentException("Can't relativize an empty source URI !");
    }

    // If target URI is empty
    if (segmentsTarget.length == 0) {
      throw new IllegalArgumentException("Can't relativize an empty target URI !");
    }

    // If the source is the root, then the relativized
    //  form must actually be an absolute URI
    if (sourceURI.toString().equals("/")) {
      String path = targetURI.getPath();
      if (msCompatible && path.length() > 0 && path.charAt(0) == '/') {
        try {
          targetURI = new URI(path.substring(1));
        } catch (Exception e) {
          _logger.log(POILogger.WARN, e);
          return null;
        }
      }
      return targetURI;
    }

    // Relativize the source URI against the target URI.
    // First up, figure out how many steps along we can go
    // and still have them be the same
    int segmentsTheSame = 0;
    for (int i = 0; i < segmentsSource.length && i < segmentsTarget.length; i++) {
      if (segmentsSource[i].equals(segmentsTarget[i])) {
        // Match so far, good
        segmentsTheSame++;
      } else {
        break;
      }
    }

    // If we didn't have a good match or at least except a first empty element
    if ((segmentsTheSame == 0 || segmentsTheSame == 1)
        && segmentsSource[0].equals("")
        && segmentsTarget[0].equals("")) {
      for (int i = 0; i < segmentsSource.length - 2; i++) {
        retVal.append("../");
      }
      for (int i = 0; i < segmentsTarget.length; i++) {
        if (segmentsTarget[i].equals("")) continue;
        retVal.append(segmentsTarget[i]);
        if (i != segmentsTarget.length - 1) retVal.append("/");
      }

      try {
        return new URI(retVal.toString());
      } catch (Exception e) {
        _logger.log(POILogger.WARN, e);
        return null;
      }
    }

    // Special case for where the two are the same
    if (segmentsTheSame == segmentsSource.length && segmentsTheSame == segmentsTarget.length) {
      if (sourceURI.equals(targetURI)) {
        // if source and target are the same they should be resolved to the last segment,
        // Example: if a slide references itself, e.g. the source URI is
        // "/ppt/slides/slide1.xml" and the targetURI is "slide1.xml" then
        // this it should be relativized as "slide1.xml", i.e. the last segment.
        retVal.append(segmentsSource[segmentsSource.length - 1]);
      } else {
        retVal.append("");
      }

    } else {
      // Matched for so long, but no more

      // Do we need to go up a directory or two from
      // the source to get here?
      // (If it's all the way up, then don't bother!)
      if (segmentsTheSame == 1) {
        retVal.append("/");
      } else {
        for (int j = segmentsTheSame; j < segmentsSource.length - 1; j++) {
          retVal.append("../");
        }
      }

      // Now go from here on down
      for (int j = segmentsTheSame; j < segmentsTarget.length; j++) {
        if (retVal.length() > 0 && retVal.charAt(retVal.length() - 1) != '/') {
          retVal.append("/");
        }
        retVal.append(segmentsTarget[j]);
      }
    }

    // if the target had a fragment then append it to the result
    String fragment = targetURI.getRawFragment();
    if (fragment != null) {
      retVal.append("#").append(fragment);
    }

    try {
      return new URI(retVal.toString());
    } catch (Exception e) {
      _logger.log(POILogger.WARN, e);
      return null;
    }
  }

  /**
   * Fully relativize the source part URI against the target part URI.
   *
   * @param sourceURI The source part URI.
   * @param targetURI The target part URI.
   * @return A fully relativize part name URI ('word/media/image1.gif', '/word/document.xml' =>
   *     'media/image1.gif') else <code>null</code>.
   */
  public static URI relativizeURI(URI sourceURI, URI targetURI) {
    return relativizeURI(sourceURI, targetURI, false);
  }

  /**
   * Resolve a source uri against a target.
   *
   * @param sourcePartUri The source URI.
   * @param targetUri The target URI.
   * @return The resolved URI.
   */
  public static URI resolvePartUri(URI sourcePartUri, URI targetUri) {
    if (sourcePartUri == null || sourcePartUri.isAbsolute()) {
      throw new IllegalArgumentException("sourcePartUri invalid - " + sourcePartUri);
    }

    if (targetUri == null || targetUri.isAbsolute()) {
      throw new IllegalArgumentException("targetUri invalid - " + targetUri);
    }

    return sourcePartUri.resolve(targetUri);
  }

  /** Get URI from a string path. */
  public static URI getURIFromPath(String path) {
    URI retUri;
    try {
      retUri = toURI(path);
    } catch (URISyntaxException e) {
      throw new IllegalArgumentException("path");
    }
    return retUri;
  }

  /**
   * Get the source part URI from a specified relationships part.
   *
   * @param relationshipPartUri The relationship part use to retrieve the source part.
   * @return The source part URI from the specified relationships part.
   */
  public static URI getSourcePartUriFromRelationshipPartUri(URI relationshipPartUri) {
    if (relationshipPartUri == null) throw new IllegalArgumentException("Must not be null");

    if (!isRelationshipPartURI(relationshipPartUri))
      throw new IllegalArgumentException("Must be a relationship part");

    if (relationshipPartUri.compareTo(PACKAGE_RELATIONSHIPS_ROOT_URI) == 0) return PACKAGE_ROOT_URI;

    String filename = relationshipPartUri.getPath();
    String filenameWithoutExtension = getFilenameWithoutExtension(relationshipPartUri);
    filename =
        filename.substring(
            0,
            ((filename.length() - filenameWithoutExtension.length())
                - RELATIONSHIP_PART_EXTENSION_NAME.length()));
    filename =
        filename.substring(0, filename.length() - RELATIONSHIP_PART_SEGMENT_NAME.length() - 1);
    filename = combine(filename, filenameWithoutExtension);
    return getURIFromPath(filename);
  }

  /**
   * Create an OPC compliant part name by throwing an exception if the URI is not valid.
   *
   * @param partUri The part name URI to validate.
   * @return A valid part name object, else <code>null</code>.
   * @throws InvalidFormatException Throws if the specified URI is not OPC compliant.
   */
  public static PackagePartName createPartName(URI partUri) throws InvalidFormatException {
    if (partUri == null) throw new IllegalArgumentException("partName");

    return new PackagePartName(partUri, true);
  }

  /**
   * Create an OPC compliant part name.
   *
   * @param partName The part name to validate.
   * @return The correspondant part name if valid, else <code>null</code>.
   * @throws InvalidFormatException Throws if the specified part name is not OPC compliant.
   * @see #createPartName(URI)
   */
  public static PackagePartName createPartName(String partName) throws InvalidFormatException {
    URI partNameURI;
    try {
      partNameURI = toURI(partName);
    } catch (URISyntaxException e) {
      throw new InvalidFormatException(e.getMessage());
    }
    return createPartName(partNameURI);
  }

  /**
   * Create an OPC compliant part name by resolving it using a base part.
   *
   * @param partName The part name to validate.
   * @param relativePart The relative base part.
   * @return The correspondant part name if valid, else <code>null</code>.
   * @throws InvalidFormatException Throws if the specified part name is not OPC compliant.
   * @see #createPartName(URI)
   */
  public static PackagePartName createPartName(String partName, PackagePart relativePart)
      throws InvalidFormatException {
    URI newPartNameURI;
    try {
      newPartNameURI = resolvePartUri(relativePart.getPartName().getURI(), new URI(partName));
    } catch (URISyntaxException e) {
      throw new InvalidFormatException(e.getMessage());
    }
    return createPartName(newPartNameURI);
  }

  /**
   * Create an OPC compliant part name by resolving it using a base part.
   *
   * @param partName The part name URI to validate.
   * @param relativePart The relative base part.
   * @return The correspondant part name if valid, else <code>null</code>.
   * @throws InvalidFormatException Throws if the specified part name is not OPC compliant.
   * @see #createPartName(URI)
   */
  public static PackagePartName createPartName(URI partName, PackagePart relativePart)
      throws InvalidFormatException {
    URI newPartNameURI = resolvePartUri(relativePart.getPartName().getURI(), partName);
    return createPartName(newPartNameURI);
  }

  /**
   * Validate a part URI by returning a boolean. ([M1.1],[M1.3],[M1.4],[M1.5],[M1.6])
   *
   * <p>(OPC Specifications 8.1.1 Part names) :
   *
   * <p>Part Name Syntax
   *
   * <p>The part name grammar is defined as follows:
   *
   * <p><i>part_name = 1*( "/" segment )
   *
   * <p>segment = 1*( pchar )</i>
   *
   * <p>(pchar is defined in RFC 3986)
   *
   * @param partUri The URI to validate.
   * @return <b>true</b> if the URI is valid to the OPC Specifications, else <b>false</b>
   * @see #createPartName(URI)
   */
  public static boolean isValidPartName(URI partUri) {
    if (partUri == null) throw new IllegalArgumentException("partUri");

    try {
      createPartName(partUri);
      return true;
    } catch (Exception e) {
      return false;
    }
  }

  /**
   * Decode a URI by converting all percent encoded character into a String character.
   *
   * @param uri The URI to decode.
   * @return The specified URI in a String with converted percent encoded characters.
   */
  public static String decodeURI(URI uri) {
    StringBuffer retVal = new StringBuffer();
    String uriStr = uri.toASCIIString();
    char c;
    for (int i = 0; i < uriStr.length(); ++i) {
      c = uriStr.charAt(i);
      if (c == '%') {
        // We certainly found an encoded character, check for length
        // now ( '%' HEXDIGIT HEXDIGIT)
        if (((uriStr.length() - i) < 2)) {
          throw new IllegalArgumentException(
              "The uri " + uriStr + " contain invalid encoded character !");
        }

        // Decode the encoded character
        char decodedChar = (char) Integer.parseInt(uriStr.substring(i + 1, i + 3), 16);
        retVal.append(decodedChar);
        i += 2;
        continue;
      }
      retVal.append(c);
    }
    return retVal.toString();
  }

  /**
   * Build a part name where the relationship should be stored ((ex /word/document.xml ->
   * /word/_rels/document.xml.rels)
   *
   * @param partName Source part URI
   * @return the full path (as URI) of the relation file
   * @throws InvalidOperationException Throws if the specified URI is a relationshp part.
   */
  public static PackagePartName getRelationshipPartName(PackagePartName partName) {
    if (partName == null) throw new IllegalArgumentException("partName");

    if (PackagingURIHelper.PACKAGE_ROOT_URI.getPath().equals(partName.getURI().getPath()))
      return PackagingURIHelper.PACKAGE_RELATIONSHIPS_ROOT_PART_NAME;

    if (partName.isRelationshipPartURI())
      throw new InvalidOperationException("Can't be a relationship part");

    String fullPath = partName.getURI().getPath();
    String filename = getFilename(partName.getURI());
    fullPath = fullPath.substring(0, fullPath.length() - filename.length());
    fullPath = combine(fullPath, PackagingURIHelper.RELATIONSHIP_PART_SEGMENT_NAME);
    fullPath = combine(fullPath, filename);
    fullPath = fullPath + PackagingURIHelper.RELATIONSHIP_PART_EXTENSION_NAME;

    PackagePartName retPartName;
    try {
      retPartName = createPartName(fullPath);
    } catch (InvalidFormatException e) {
      // Should never happen in production as all data are fixed but in
      // case of return null:
      return null;
    }
    return retPartName;
  }

  /**
   * Convert a string to {@link java.net.URI}
   *
   * <p>If part name is not a valid URI, it is resolved as follows:
   *
   * <p>1. Percent-encode each open bracket ([) and close bracket (]). 2. Percent-encode each
   * percent (%) character that is not followed by a hexadecimal notation of an octet value. 3.
   * Un-percent-encode each percent-encoded unreserved character. 4. Un-percent-encode each forward
   * slash (/) and back slash (\). 5. Convert all back slashes to forward slashes. 6. If present in
   * a segment containing non-dot (?.?) characters, remove trailing dot (?.?) characters from each
   * segment. 7. Replace each occurrence of multiple consecutive forward slashes (/) with a single
   * forward slash. 8. If a single trailing forward slash (/) is present, remove that trailing
   * forward slash. 9. Remove complete segments that consist of three or more dots. 10. Resolve the
   * relative reference against the base URI of the part holding the Unicode string, as it is
   * defined in ?5.2 of RFC 3986. The path component of the resulting absolute URI is the part name.
   *
   * @param value the string to be parsed into a URI
   * @return the resolved part name that should be OK to construct a URI
   *     <p>TODO YK: for now this method does only (5). Finish the rest.
   */
  public static URI toURI(String value) throws URISyntaxException {
    // 5. Convert all back slashes to forward slashes
    if (value.indexOf("\\") != -1) {
      value = value.replace('\\', '/');
    }

    // URI fragemnts (those starting with '#') are not encoded
    // and may contain white spaces and raw unicode characters
    int fragmentIdx = value.indexOf('#');
    if (fragmentIdx != -1) {
      String path = value.substring(0, fragmentIdx);
      String fragment = value.substring(fragmentIdx + 1);

      value = path + "#" + encode(fragment);
    }

    return new URI(value);
  }

  /**
   * percent-encode white spaces and characters above 0x80.
   *
   * <p>Examples: 'Apache POI' --> 'Apache%20POI' 'Apache\u0410POI' --> 'Apache%04%10POI'
   *
   * @param s the string to encode
   * @return the encoded string
   */
  public static String encode(String s) {
    int n = s.length();
    if (n == 0) return s;

    ByteBuffer bb;
    try {
      bb = ByteBuffer.wrap(s.getBytes("UTF-8"));
    } catch (UnsupportedEncodingException e) {
      // should not happen
      throw new RuntimeException(e);
    }
    StringBuilder sb = new StringBuilder();
    while (bb.hasRemaining()) {
      int b = bb.get() & 0xff;
      if (isUnsafe(b)) {
        sb.append('%');
        sb.append(hexDigits[(b >> 4) & 0x0F]);
        sb.append(hexDigits[(b >> 0) & 0x0F]);
      } else {
        sb.append((char) b);
      }
    }
    return sb.toString();
  }

  private static final char[] hexDigits = {
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
  };

  private static boolean isUnsafe(int ch) {
    return ch > 0x80 || " ".indexOf(ch) >= 0;
  }
}
Example #8
0
/**
 * High level representation of a ooxml slideshow. This is the first object most users will
 * construct whether they are reading or writing a slideshow. It is also the top level object for
 * creating new slides/etc.
 */
@Beta
public class XMLSlideShow extends POIXMLDocument {
  private static POILogger _logger = POILogFactory.getLogger(XMLSlideShow.class);

  private CTPresentation _presentation;
  private List<XSLFSlide> _slides;
  private Map<String, XSLFSlideMaster> _masters;
  private List<XSLFPictureData> _pictures;
  private XSLFTableStyles _tableStyles;
  private XSLFNotesMaster _notesMaster;
  private XSLFCommentAuthors _commentAuthors;

  public XMLSlideShow() {
    this(empty());
  }

  public XMLSlideShow(OPCPackage pkg) {
    super(pkg);

    try {
      if (getCorePart().getContentType().equals(XSLFRelation.THEME_MANAGER.getContentType())) {
        rebase(getPackage());
      }

      // build a tree of POIXMLDocumentParts, this presentation being the root
      load(XSLFFactory.getInstance());
    } catch (Exception e) {
      throw new POIXMLException(e);
    }
  }

  public XMLSlideShow(InputStream is) throws IOException {
    this(PackageHelper.open(is));
  }

  static final OPCPackage empty() {
    InputStream is = XMLSlideShow.class.getResourceAsStream("empty.pptx");
    if (is == null) {
      throw new RuntimeException("Missing resource 'empty.pptx'");
    }
    try {
      return OPCPackage.open(is);
    } catch (Exception e) {
      throw new POIXMLException(e);
    }
  }

  // TODO get rid of this method
  @Deprecated
  public XSLFSlideShow _getXSLFSlideShow() throws OpenXML4JException, IOException, XmlException {
    return new XSLFSlideShow(getPackage());
  }

  @Override
  @SuppressWarnings("deprecation")
  protected void onDocumentRead() throws IOException {
    try {
      PresentationDocument doc = PresentationDocument.Factory.parse(getCorePart().getInputStream());
      _presentation = doc.getPresentation();
      Map<String, XSLFSlide> shIdMap = new HashMap<String, XSLFSlide>();

      _masters = new HashMap<String, XSLFSlideMaster>();
      for (POIXMLDocumentPart p : getRelations()) {
        if (p instanceof XSLFSlide) {
          shIdMap.put(p.getPackageRelationship().getId(), (XSLFSlide) p);
        } else if (p instanceof XSLFSlideMaster) {
          XSLFSlideMaster master = (XSLFSlideMaster) p;
          _masters.put(p.getPackageRelationship().getId(), master);
        } else if (p instanceof XSLFTableStyles) {
          _tableStyles = (XSLFTableStyles) p;
        } else if (p instanceof XSLFNotesMaster) {
          _notesMaster = (XSLFNotesMaster) p;
        } else if (p instanceof XSLFCommentAuthors) {
          _commentAuthors = (XSLFCommentAuthors) p;
        }
      }

      _slides = new ArrayList<XSLFSlide>();
      if (_presentation.isSetSldIdLst()) {
        for (CTSlideIdListEntry slId : _presentation.getSldIdLst().getSldIdArray()) {
          XSLFSlide sh = shIdMap.get(slId.getId2());
          if (sh == null) {
            _logger.log(
                POILogger.WARN,
                "Slide with r:id "
                    + slId.getId()
                    + " was defined, but didn't exist in package, skipping");
            continue;
          }
          _slides.add(sh);
        }
      }
    } catch (XmlException e) {
      throw new POIXMLException(e);
    }
  }

  @Override
  protected void commit() throws IOException {
    XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS);
    Map<String, String> map = new HashMap<String, String>();
    map.put(STRelationshipId.type.getName().getNamespaceURI(), "r");
    xmlOptions.setSaveSuggestedPrefixes(map);

    PackagePart part = getPackagePart();
    OutputStream out = part.getOutputStream();
    _presentation.save(out, xmlOptions);
    out.close();
  }

  /** Get the document's embedded files. */
  @Override
  public List<PackagePart> getAllEmbedds() throws OpenXML4JException {
    return Collections.unmodifiableList(
        getPackage().getPartsByName(Pattern.compile("/ppt/embeddings/.*?")));
  }

  /**
   * Returns all Pictures, which are referenced from the document itself.
   *
   * @return a {@link List} of {@link PackagePart}. The returned {@link List} is unmodifiable.
   */
  public List<XSLFPictureData> getAllPictures() {
    if (_pictures == null) {
      List<PackagePart> mediaParts = getPackage().getPartsByName(Pattern.compile("/ppt/media/.*?"));
      _pictures = new ArrayList<XSLFPictureData>(mediaParts.size());
      for (PackagePart part : mediaParts) {
        _pictures.add(new XSLFPictureData(part, null));
      }
    }
    return Collections.unmodifiableList(_pictures);
  }

  /**
   * Create a slide and initialize it from the specified layout.
   *
   * @param layout
   * @return created slide
   */
  @SuppressWarnings("deprecation")
  public XSLFSlide createSlide(XSLFSlideLayout layout) {
    int slideNumber = 256, cnt = 1;
    CTSlideIdList slideList;
    if (!_presentation.isSetSldIdLst()) slideList = _presentation.addNewSldIdLst();
    else {
      slideList = _presentation.getSldIdLst();
      for (CTSlideIdListEntry slideId : slideList.getSldIdArray()) {
        slideNumber = (int) Math.max(slideId.getId() + 1, slideNumber);
        cnt++;
      }
    }

    XSLFSlide slide =
        (XSLFSlide) createRelationship(XSLFRelation.SLIDE, XSLFFactory.getInstance(), cnt);

    CTSlideIdListEntry slideId = slideList.addNewSldId();
    slideId.setId(slideNumber);
    slideId.setId2(slide.getPackageRelationship().getId());

    layout.copyLayout(slide);
    slide.addRelation(layout.getPackageRelationship().getId(), layout);

    PackagePartName ppName = layout.getPackagePart().getPartName();
    slide
        .getPackagePart()
        .addRelationship(
            ppName, TargetMode.INTERNAL, layout.getPackageRelationship().getRelationshipType());

    _slides.add(slide);
    return slide;
  }

  /** Create a blank slide. */
  public XSLFSlide createSlide() {
    String masterId = _presentation.getSldMasterIdLst().getSldMasterIdArray(0).getId2();
    XSLFSlideMaster master = _masters.get(masterId);

    XSLFSlideLayout layout = master.getLayout(SlideLayout.BLANK);
    if (layout == null) throw new IllegalArgumentException("Blank layout was not found");

    return createSlide(layout);
  }

  /** Return notes slide for the specified slide or create new if it does not exist yet. */
  public XSLFNotes getNotesSlide(XSLFSlide slide) {

    XSLFNotes notesSlide = slide.getNotes();
    if (notesSlide == null) {
      notesSlide = createNotesSlide(slide);
    }

    return notesSlide;
  }

  /** Create a blank notes slide. */
  private XSLFNotes createNotesSlide(XSLFSlide slide) {

    if (_notesMaster == null) {
      createNotesMaster();
    }

    Integer slideIndex = XSLFRelation.SLIDE.getFileNameIndex(slide);

    XSLFNotes notesSlide =
        (XSLFNotes) createRelationship(XSLFRelation.NOTES, XSLFFactory.getInstance(), slideIndex);

    notesSlide.addRelation(_notesMaster.getPackageRelationship().getId(), _notesMaster);
    PackagePartName notesMasterPackagePartName = _notesMaster.getPackagePart().getPartName();
    notesSlide
        .getPackagePart()
        .addRelationship(
            notesMasterPackagePartName,
            TargetMode.INTERNAL,
            _notesMaster.getPackageRelationship().getRelationshipType());

    slide.addRelation(notesSlide.getPackageRelationship().getId(), notesSlide);
    PackagePartName notesSlidesPackagePartName = notesSlide.getPackagePart().getPartName();
    slide
        .getPackagePart()
        .addRelationship(
            notesSlidesPackagePartName,
            TargetMode.INTERNAL,
            notesSlide.getPackageRelationship().getRelationshipType());

    notesSlide.addRelation(slide.getPackageRelationship().getId(), slide);
    PackagePartName slidesPackagePartName = slide.getPackagePart().getPartName();
    notesSlide
        .getPackagePart()
        .addRelationship(
            slidesPackagePartName,
            TargetMode.INTERNAL,
            slide.getPackageRelationship().getRelationshipType());

    notesSlide.importContent(_notesMaster);

    return notesSlide;
  }

  /** Create a notes master. */
  public void createNotesMaster() {

    _notesMaster =
        (XSLFNotesMaster)
            createRelationship(XSLFRelation.NOTES_MASTER, XSLFFactory.getInstance(), 1);

    CTNotesMasterIdList notesMasterIdList = _presentation.addNewNotesMasterIdLst();
    CTNotesMasterIdListEntry notesMasterId = notesMasterIdList.addNewNotesMasterId();
    notesMasterId.setId(_notesMaster.getPackageRelationship().getId());

    Integer themeIndex = 1;
    List<Integer> themeIndexList = new ArrayList<Integer>();
    for (POIXMLDocumentPart p : getRelations()) {
      if (p instanceof XSLFTheme) {
        themeIndexList.add(XSLFRelation.THEME.getFileNameIndex(p));
      }
    }

    if (!themeIndexList.isEmpty()) {
      Boolean found = false;
      for (Integer i = 1; i <= themeIndexList.size(); i++) {
        if (!themeIndexList.contains(i)) {
          found = true;
          themeIndex = i;
        }
      }
      if (!found) {
        themeIndex = themeIndexList.size() + 1;
      }
    }

    XSLFTheme theme =
        (XSLFTheme) createRelationship(XSLFRelation.THEME, XSLFFactory.getInstance(), themeIndex);
    theme.importTheme(getSlides()[0].getTheme());

    _notesMaster.addRelation(theme.getPackageRelationship().getId(), theme);
    PackagePartName themePackagePartName = theme.getPackagePart().getPartName();
    _notesMaster
        .getPackagePart()
        .addRelationship(
            themePackagePartName,
            TargetMode.INTERNAL,
            theme.getPackageRelationship().getRelationshipType());
  }

  /** Return the Notes Master, if there is one. (May not be present if no notes exist) */
  public XSLFNotesMaster getNotesMaster() {
    return _notesMaster;
  }

  public XSLFSlideMaster[] getSlideMasters() {
    return _masters.values().toArray(new XSLFSlideMaster[_masters.size()]);
  }

  /** Return all the slides in the slideshow */
  public XSLFSlide[] getSlides() {
    return _slides.toArray(new XSLFSlide[_slides.size()]);
  }

  /**
   * Returns the list of comment authors, if there is one. Will only be present if at least one
   * slide has comments on it.
   */
  public XSLFCommentAuthors getCommentAuthors() {
    return _commentAuthors;
  }

  /** @param newIndex 0-based index of the slide */
  @SuppressWarnings("deprecation")
  public void setSlideOrder(XSLFSlide slide, int newIndex) {
    int oldIndex = _slides.indexOf(slide);
    if (oldIndex == -1) throw new IllegalArgumentException("Slide not found");
    if (oldIndex == newIndex) return;

    // fix the usermodel container
    _slides.add(newIndex, _slides.remove(oldIndex));

    // fix ordering in the low-level xml
    CTSlideIdList sldIdLst = _presentation.getSldIdLst();
    CTSlideIdListEntry[] entries = sldIdLst.getSldIdArray();
    CTSlideIdListEntry oldEntry = entries[oldIndex];
    if (oldIndex < newIndex) {
      System.arraycopy(entries, oldIndex + 1, entries, oldIndex, newIndex - oldIndex);
    } else {
      System.arraycopy(entries, newIndex, entries, newIndex + 1, oldIndex - newIndex);
    }
    entries[newIndex] = oldEntry;
    sldIdLst.setSldIdArray(entries);
  }

  public XSLFSlide removeSlide(int index) {
    XSLFSlide slide = _slides.remove(index);
    removeRelation(slide);
    _presentation.getSldIdLst().removeSldId(index);
    return slide;
  }

  /**
   * Returns the current page size
   *
   * @return the page size
   */
  public Dimension getPageSize() {
    CTSlideSize sz = _presentation.getSldSz();
    int cx = sz.getCx();
    int cy = sz.getCy();
    return new Dimension((int) Units.toPoints(cx), (int) Units.toPoints(cy));
  }

  /**
   * Sets the page size to the given <code>Dimension</code> object.
   *
   * @param pgSize page size
   */
  public void setPageSize(Dimension pgSize) {
    CTSlideSize sz = CTSlideSize.Factory.newInstance();
    sz.setCx(Units.toEMU(pgSize.getWidth()));
    sz.setCy(Units.toEMU(pgSize.getHeight()));
    _presentation.setSldSz(sz);
  }

  @Internal
  public CTPresentation getCTPresentation() {
    return _presentation;
  }

  /**
   * Adds a picture to the workbook.
   *
   * @param pictureData The bytes of the picture
   * @param format The format of the picture.
   * @return the index to this picture (1 based).
   * @see XSLFPictureData#PICTURE_TYPE_EMF
   * @see XSLFPictureData#PICTURE_TYPE_WMF
   * @see XSLFPictureData#PICTURE_TYPE_PICT
   * @see XSLFPictureData#PICTURE_TYPE_JPEG
   * @see XSLFPictureData#PICTURE_TYPE_PNG
   * @see XSLFPictureData#PICTURE_TYPE_DIB
   */
  public int addPicture(byte[] pictureData, int format) {
    XSLFPictureData img = findPictureData(pictureData);
    POIXMLRelation relDesc = XSLFPictureData.RELATIONS[format];

    if (img == null) {
      int imageNumber = _pictures.size();
      img =
          (XSLFPictureData)
              createRelationship(
                  XSLFPictureData.RELATIONS[format],
                  XSLFFactory.getInstance(),
                  imageNumber + 1,
                  true);
      _pictures.add(img);
      try {
        OutputStream out = img.getPackagePart().getOutputStream();
        out.write(pictureData);
        out.close();
      } catch (IOException e) {
        throw new POIXMLException(e);
      }
      return _pictures.size() - 1;
    } else {
      return _pictures.indexOf(img);
    }
  }

  /** check if a picture with this picture data already exists in this presentation */
  XSLFPictureData findPictureData(byte[] pictureData) {
    long checksum = IOUtils.calculateChecksum(pictureData);
    for (XSLFPictureData pic : getAllPictures()) {
      if (pic.getChecksum() == checksum) {
        return pic;
      }
    }
    return null;
  }

  public XSLFTableStyles getTableStyles() {
    return _tableStyles;
  }

  CTTextParagraphProperties getDefaultParagraphStyle(int level) {
    XmlObject[] o =
        _presentation.selectPath(
            "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' "
                + "declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' "
                + ".//p:defaultTextStyle/a:lvl"
                + (level + 1)
                + "pPr");
    if (o.length == 1) {
      return (CTTextParagraphProperties) o[0];
    }
    return null;
  }
}
Example #9
0
public final class FIBFieldHandler {

  public static final int STSHFORIG = 0;
  public static final int STSHF = 1;
  public static final int PLCFFNDREF = 2;
  public static final int PLCFFNDTXT = 3;
  public static final int PLCFANDREF = 4;
  public static final int PLCFANDTXT = 5;
  public static final int PLCFSED = 6;
  public static final int PLCFPAD = 7;
  public static final int PLCFPHE = 8;
  public static final int STTBGLSY = 9;
  public static final int PLCFGLSY = 10;
  public static final int PLCFHDD = 11;
  public static final int PLCFBTECHPX = 12;
  public static final int PLCFBTEPAPX = 13;
  public static final int PLCFSEA = 14;
  public static final int STTBFFFN = 15;
  public static final int PLCFFLDMOM = 16;
  public static final int PLCFFLDHDR = 17;
  public static final int PLCFFLDFTN = 18;
  public static final int PLCFFLDATN = 19;
  public static final int PLCFFLDMCR = 20;
  public static final int STTBFBKMK = 21;
  public static final int PLCFBKF = 22;
  public static final int PLCFBKL = 23;
  public static final int CMDS = 24;
  public static final int PLCMCR = 25;
  public static final int STTBFMCR = 26;
  public static final int PRDRVR = 27;
  public static final int PRENVPORT = 28;
  public static final int PRENVLAND = 29;
  public static final int WSS = 30;
  public static final int DOP = 31;
  public static final int STTBFASSOC = 32;
  public static final int CLX = 33;
  public static final int PLCFPGDFTN = 34;
  public static final int AUTOSAVESOURCE = 35;
  public static final int GRPXSTATNOWNERS = 36;
  public static final int STTBFATNBKMK = 37;
  public static final int PLCFDOAMOM = 38;
  public static final int PLCDOAHDR = 39;
  public static final int PLCSPAMOM = 40;
  public static final int PLCSPAHDR = 41;
  public static final int PLCFATNBKF = 42;
  public static final int PLCFATNBKL = 43;
  public static final int PMS = 44;
  public static final int FORMFLDSTTBS = 45;
  public static final int PLCFENDREF = 46;
  public static final int PLCFENDTXT = 47;
  public static final int PLCFFLDEDN = 48;
  public static final int PLCFPGDEDN = 49;
  public static final int DGGINFO = 50;
  public static final int STTBFRMARK = 51;
  public static final int STTBCAPTION = 52;
  public static final int STTBAUTOCAPTION = 53;
  public static final int PLCFWKB = 54;
  public static final int PLCFSPL = 55;
  public static final int PLCFTXBXTXT = 56;
  public static final int PLCFFLDTXBX = 57;
  public static final int PLCFHDRTXBXTXT = 58;
  public static final int PLCFFLDHDRTXBX = 59;
  public static final int STWUSER = 60;
  public static final int STTBTTMBD = 61;
  public static final int UNUSED = 62;
  public static final int PGDMOTHER = 63;
  public static final int BKDMOTHER = 64;
  public static final int PGDFTN = 65;
  public static final int BKDFTN = 66;
  public static final int PGDEDN = 67;
  public static final int BKDEDN = 68;
  public static final int STTBFINTFLD = 69;
  public static final int ROUTESLIP = 70;
  public static final int STTBSAVEDBY = 71;
  public static final int STTBFNM = 72;
  public static final int PLCFLST = 73;
  public static final int PLFLFO = 74;
  public static final int PLCFTXBXBKD = 75;
  public static final int PLCFTXBXHDRBKD = 76;
  public static final int DOCUNDO = 77;
  public static final int RGBUSE = 78;
  public static final int USP = 79;
  public static final int USKF = 80;
  public static final int PLCUPCRGBUSE = 81;
  public static final int PLCUPCUSP = 82;
  public static final int STTBGLSYSTYLE = 83;
  public static final int PLGOSL = 84;
  public static final int PLCOCX = 85;
  public static final int PLCFBTELVC = 86;
  public static final int MODIFIED = 87;
  public static final int PLCFLVC = 88;
  public static final int PLCASUMY = 89;
  public static final int PLCFGRAM = 90;
  public static final int STTBLISTNAMES = 91;
  public static final int STTBFUSSR = 92;
  private static POILogger log =
      POILogFactory.getLogger(org / apache / poi / hwpf / model / FIBFieldHandler);
  private static final int FIELD_SIZE = 8;
  private Map _unknownMap;
  private int _fields[];

  public FIBFieldHandler(
      byte mainStream[],
      int startOffset,
      byte tableStream[],
      HashSet offsetList,
      boolean areKnown) {
    _unknownMap = new HashMap();
    int numFields = LittleEndian.getShort(mainStream, startOffset);
    int offset = startOffset + 2;
    _fields = new int[numFields * 2];
    for (int x = 0; x < numFields; x++) {
      int fieldOffset = x * 8 + offset;
      int dsOffset = LittleEndian.getInt(mainStream, fieldOffset);
      fieldOffset += 4;
      int dsSize = LittleEndian.getInt(mainStream, fieldOffset);
      if (offsetList.contains(Integer.valueOf(x)) ^ areKnown && dsSize > 0)
        if (dsOffset + dsSize > tableStream.length) {
          log.log(
              POILogger.WARN,
              (new StringBuilder())
                  .append("Unhandled data structure points to outside the buffer. offset = ")
                  .append(dsOffset)
                  .append(", length = ")
                  .append(dsSize)
                  .append(", buffer length = ")
                  .append(tableStream.length)
                  .toString());
        } else {
          UnhandledDataStructure unhandled =
              new UnhandledDataStructure(tableStream, dsOffset, dsSize);
          _unknownMap.put(Integer.valueOf(x), unhandled);
        }
      _fields[x * 2] = dsOffset;
      _fields[x * 2 + 1] = dsSize;
    }
  }

  public void clearFields() {
    Arrays.fill(_fields, 0);
  }

  public int getFieldOffset(int field) {
    return _fields[field * 2];
  }

  public int getFieldSize(int field) {
    return _fields[field * 2 + 1];
  }

  public void setFieldOffset(int field, int offset) {
    _fields[field * 2] = offset;
  }

  public void setFieldSize(int field, int size) {
    _fields[field * 2 + 1] = size;
  }

  public int sizeInBytes() {
    return _fields.length * 4 + 2;
  }

  void writeTo(byte mainStream[], int offset, HWPFOutputStream tableStream) throws IOException {
    int length = _fields.length / 2;
    LittleEndian.putShort(mainStream, offset, (short) length);
    offset += 2;
    for (int x = 0; x < length; x++) {
      UnhandledDataStructure ds = (UnhandledDataStructure) _unknownMap.get(Integer.valueOf(x));
      if (ds != null) {
        _fields[x * 2] = tableStream.getOffset();
        LittleEndian.putInt(mainStream, offset, tableStream.getOffset());
        offset += 4;
        byte buf[] = ds.getBuf();
        tableStream.write(buf);
        _fields[x * 2 + 1] = buf.length;
        LittleEndian.putInt(mainStream, offset, buf.length);
        offset += 4;
      } else {
        LittleEndian.putInt(mainStream, offset, _fields[x * 2]);
        offset += 4;
        LittleEndian.putInt(mainStream, offset, _fields[x * 2 + 1]);
        offset += 4;
      }
    }
  }
}
/**
 * The <code>HyperlinkRecord</code> (0x01B8) wraps an HLINK-record from the Excel-97 format.
 * Supports only external links for now (eg http://)
 *
 * @author Mark Hissink Muller <a href="mailto:[email protected] >mark&064;hissinkmuller.nl</a>
 * @author Yegor Kozlov (yegor at apache dot org)
 */
public final class HyperlinkRecord extends StandardRecord {
  public static final short sid = 0x01B8;
  private POILogger logger = POILogFactory.getLogger(getClass());

  static final class GUID {
    /*
     * this class is currently only used here, but could be moved to a
     * common package if needed
     */
    private static final int TEXT_FORMAT_LENGTH = 36;

    public static final int ENCODED_SIZE = 16;

    /** 4 bytes - little endian */
    private final int _d1;
    /** 2 bytes - little endian */
    private final int _d2;
    /** 2 bytes - little endian */
    private final int _d3;
    /** 8 bytes - serialized as big endian, stored with inverted endianness here */
    private final long _d4;

    public GUID(LittleEndianInput in) {
      this(in.readInt(), in.readUShort(), in.readUShort(), in.readLong());
    }

    public GUID(int d1, int d2, int d3, long d4) {
      _d1 = d1;
      _d2 = d2;
      _d3 = d3;
      _d4 = d4;
    }

    public void serialize(LittleEndianOutput out) {
      out.writeInt(_d1);
      out.writeShort(_d2);
      out.writeShort(_d3);
      out.writeLong(_d4);
    }

    @Override
    public boolean equals(Object obj) {
      GUID other = (GUID) obj;
      if (obj == null || !(obj instanceof GUID)) return false;
      return _d1 == other._d1 && _d2 == other._d2 && _d3 == other._d3 && _d4 == other._d4;
    }

    public int getD1() {
      return _d1;
    }

    public int getD2() {
      return _d2;
    }

    public int getD3() {
      return _d3;
    }

    public long getD4() {
      //
      ByteArrayOutputStream baos = new ByteArrayOutputStream(8);
      try {
        new DataOutputStream(baos).writeLong(_d4);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
      byte[] buf = baos.toByteArray();
      return new LittleEndianByteArrayInputStream(buf).readLong();
    }

    public String formatAsString() {

      StringBuilder sb = new StringBuilder(36);

      int PREFIX_LEN = "0x".length();
      sb.append(HexDump.intToHex(_d1), PREFIX_LEN, 8);
      sb.append("-");
      sb.append(HexDump.shortToHex(_d2), PREFIX_LEN, 4);
      sb.append("-");
      sb.append(HexDump.shortToHex(_d3), PREFIX_LEN, 4);
      sb.append("-");
      char[] d4Chars = HexDump.longToHex(getD4());
      sb.append(d4Chars, PREFIX_LEN, 4);
      sb.append("-");
      sb.append(d4Chars, PREFIX_LEN + 4, 12);
      return sb.toString();
    }

    @Override
    public String toString() {
      StringBuilder sb = new StringBuilder(64);
      sb.append(getClass().getName()).append(" [");
      sb.append(formatAsString());
      sb.append("]");
      return sb.toString();
    }

    /**
     * Read a GUID in standard text form e.g.<br>
     * 13579BDF-0246-8ACE-0123-456789ABCDEF <br>
     * -&gt; <br>
     * 0x13579BDF, 0x0246, 0x8ACE 0x0123456789ABCDEF
     */
    public static GUID parse(String rep) {
      char[] cc = rep.toCharArray();
      if (cc.length != TEXT_FORMAT_LENGTH) {
        throw new RecordFormatException("supplied text is the wrong length for a GUID");
      }
      int d0 = (parseShort(cc, 0) << 16) + (parseShort(cc, 4) << 0);
      int d1 = parseShort(cc, 9);
      int d2 = parseShort(cc, 14);
      for (int i = 23; i > 19; i--) {
        cc[i] = cc[i - 1];
      }
      long d3 = parseLELong(cc, 20);

      return new GUID(d0, d1, d2, d3);
    }

    private static long parseLELong(char[] cc, int startIndex) {
      long acc = 0;
      for (int i = startIndex + 14; i >= startIndex; i -= 2) {
        acc <<= 4;
        acc += parseHexChar(cc[i + 0]);
        acc <<= 4;
        acc += parseHexChar(cc[i + 1]);
      }
      return acc;
    }

    private static int parseShort(char[] cc, int startIndex) {
      int acc = 0;
      for (int i = 0; i < 4; i++) {
        acc <<= 4;
        acc += parseHexChar(cc[startIndex + i]);
      }
      return acc;
    }

    private static int parseHexChar(char c) {
      if (c >= '0' && c <= '9') {
        return c - '0';
      }
      if (c >= 'A' && c <= 'F') {
        return c - 'A' + 10;
      }
      if (c >= 'a' && c <= 'f') {
        return c - 'a' + 10;
      }
      throw new RecordFormatException("Bad hex char '" + c + "'");
    }
  }

  /** Link flags */
  static final int HLINK_URL = 0x01; // File link or URL.

  static final int HLINK_ABS = 0x02; // Absolute path.
  static final int HLINK_LABEL = 0x14; // Has label/description.
  /** Place in worksheet. If set, the {@link #_textMark} field will be present */
  static final int HLINK_PLACE = 0x08;

  private static final int HLINK_TARGET_FRAME = 0x80; // has 'target frame'
  private static final int HLINK_UNC_PATH = 0x100; // has UNC path

  static final GUID STD_MONIKER = GUID.parse("79EAC9D0-BAF9-11CE-8C82-00AA004BA90B");
  static final GUID URL_MONIKER = GUID.parse("79EAC9E0-BAF9-11CE-8C82-00AA004BA90B");
  static final GUID FILE_MONIKER = GUID.parse("00000303-0000-0000-C000-000000000046");
  /** expected Tail of a URL link */
  private static final byte[] URL_TAIL =
      HexRead.readFromString(
          "79 58 81 F4  3B 1D 7F 48   AF 2C 82 5D  C4 85 27 63   00 00 00 00  A5 AB 00 00");
  /** expected Tail of a file link */
  private static final byte[] FILE_TAIL =
      HexRead.readFromString(
          "FF FF AD DE  00 00 00 00   00 00 00 00  00 00 00 00   00 00 00 00  00 00 00 00");

  private static final int TAIL_SIZE = FILE_TAIL.length;

  /** cell range of this hyperlink */
  private CellRangeAddress _range;

  /** 16-byte GUID */
  private GUID _guid;
  /** Some sort of options for file links. */
  private int _fileOpts;
  /** Link options. Can include any of HLINK_* flags. */
  private int _linkOpts;
  /** Test label */
  private String _label;

  private String _targetFrame;
  /** Moniker. Makes sense only for URL and file links */
  private GUID _moniker;
  /** in 8:3 DOS format No Unicode string header, always 8-bit characters, zero-terminated */
  private String _shortFilename;
  /** Link */
  private String _address;
  /**
   * Text describing a place in document. In Excel UI, this is appended to the address, (after a '#'
   * delimiter).<br>
   * This field is optional. If present, the {@link #HLINK_PLACE} must be set.
   */
  private String _textMark;

  private byte[] _uninterpretedTail;

  /** Create a new hyperlink */
  public HyperlinkRecord() {}

  /** @return the 0-based column of the first cell that contains this hyperlink */
  public int getFirstColumn() {
    return _range.getFirstColumn();
  }

  /** Set the first column (zero-based)of the range that contains this hyperlink */
  public void setFirstColumn(int col) {
    _range.setFirstColumn(col);
  }

  /** @return the 0-based column of the last cell that contains this hyperlink */
  public int getLastColumn() {
    return _range.getLastColumn();
  }

  /** Set the last column (zero-based)of the range that contains this hyperlink */
  public void setLastColumn(int col) {
    _range.setLastColumn(col);
  }

  /** @return the 0-based row of the first cell that contains this hyperlink */
  public int getFirstRow() {
    return _range.getFirstRow();
  }

  /** Set the first row (zero-based)of the range that contains this hyperlink */
  public void setFirstRow(int col) {
    _range.setFirstRow(col);
  }

  /** @return the 0-based row of the last cell that contains this hyperlink */
  public int getLastRow() {
    return _range.getLastRow();
  }

  /** Set the last row (zero-based)of the range that contains this hyperlink */
  public void setLastRow(int col) {
    _range.setLastRow(col);
  }

  /** @return 16-byte guid identifier Seems to always equal {@link #STD_MONIKER} */
  GUID getGuid() {
    return _guid;
  }

  /** @return 16-byte moniker */
  GUID getMoniker() {
    return _moniker;
  }

  private static String cleanString(String s) {
    if (s == null) {
      return null;
    }
    int idx = s.indexOf('\u0000');
    if (idx < 0) {
      return s;
    }
    return s.substring(0, idx);
  }

  private static String appendNullTerm(String s) {
    if (s == null) {
      return null;
    }
    return s + '\u0000';
  }

  /**
   * Return text label for this hyperlink
   *
   * @return text to display
   */
  public String getLabel() {
    return cleanString(_label);
  }

  /**
   * Sets text label for this hyperlink
   *
   * @param label text label for this hyperlink
   */
  public void setLabel(String label) {
    _label = appendNullTerm(label);
  }

  public String getTargetFrame() {
    return cleanString(_targetFrame);
  }

  /**
   * Hyperlink address. Depending on the hyperlink type it can be URL, e-mail, path to a file, etc.
   *
   * @return the address of this hyperlink
   */
  public String getAddress() {
    if ((_linkOpts & HLINK_URL) != 0 && FILE_MONIKER.equals(_moniker))
      return cleanString(_address != null ? _address : _shortFilename);
    else if ((_linkOpts & HLINK_PLACE) != 0) return cleanString(_textMark);
    else return cleanString(_address);
  }

  /**
   * Hyperlink address. Depending on the hyperlink type it can be URL, e-mail, path to a file, etc.
   *
   * @param address the address of this hyperlink
   */
  public void setAddress(String address) {
    if ((_linkOpts & HLINK_URL) != 0 && FILE_MONIKER.equals(_moniker))
      _shortFilename = appendNullTerm(address);
    else if ((_linkOpts & HLINK_PLACE) != 0) _textMark = appendNullTerm(address);
    else _address = appendNullTerm(address);
  }

  public String getShortFilename() {
    return cleanString(_shortFilename);
  }

  public void setShortFilename(String shortFilename) {
    _shortFilename = appendNullTerm(shortFilename);
  }

  public String getTextMark() {
    return cleanString(_textMark);
  }

  public void setTextMark(String textMark) {
    _textMark = appendNullTerm(textMark);
  }

  /** Link options. Must be a combination of HLINK_* constants. For testing only */
  int getLinkOptions() {
    return _linkOpts;
  }

  /** Label options */
  public int getLabelOptions() {
    return 2; // always 2
  }

  /** Options for a file link */
  public int getFileOptions() {
    return _fileOpts;
  }

  public HyperlinkRecord(RecordInputStream in) {
    _range = new CellRangeAddress(in);

    _guid = new GUID(in);

    /**
     * streamVersion (4 bytes): An unsigned integer that specifies the version number of the
     * serialization implementation used to save this structure. This value MUST equal 2.
     */
    int streamVersion = in.readInt();
    if (streamVersion != 0x00000002) {
      throw new RecordFormatException("Stream Version must be 0x2 but found " + streamVersion);
    }
    _linkOpts = in.readInt();

    if ((_linkOpts & HLINK_LABEL) != 0) {
      int label_len = in.readInt();
      _label = in.readUnicodeLEString(label_len);
    }

    if ((_linkOpts & HLINK_TARGET_FRAME) != 0) {
      int len = in.readInt();
      _targetFrame = in.readUnicodeLEString(len);
    }

    if ((_linkOpts & HLINK_URL) != 0 && (_linkOpts & HLINK_UNC_PATH) != 0) {
      _moniker = null;
      int nChars = in.readInt();
      _address = in.readUnicodeLEString(nChars);
    }

    if ((_linkOpts & HLINK_URL) != 0 && (_linkOpts & HLINK_UNC_PATH) == 0) {
      _moniker = new GUID(in);

      if (URL_MONIKER.equals(_moniker)) {
        int length = in.readInt();
        /**
         * The value of <code>length<code> be either the byte size of the url field
         * (including the terminating NULL character) or the byte size of the url field plus 24.
         * If the value of this field is set to the byte size of the url field,
         * then the tail bytes fields are not present.
         */
        int remaining = in.remaining();
        if (length == remaining) {
          int nChars = length / 2;
          _address = in.readUnicodeLEString(nChars);
        } else {
          int nChars = (length - TAIL_SIZE) / 2;
          _address = in.readUnicodeLEString(nChars);
          /**
           * TODO: make sense of the remaining bytes According to the spec they consist of: 1.
           * 16-byte GUID: This field MUST equal {0xF4815879, 0x1D3B, 0x487F, 0xAF, 0x2C, 0x82,
           * 0x5D, 0xC4, 0x85, 0x27, 0x63} 2. Serial version, this field MUST equal 0 if present. 3.
           * URI Flags
           */
          _uninterpretedTail = readTail(URL_TAIL, in);
        }
      } else if (FILE_MONIKER.equals(_moniker)) {
        _fileOpts = in.readShort();

        int len = in.readInt();
        _shortFilename = StringUtil.readCompressedUnicode(in, len);
        _uninterpretedTail = readTail(FILE_TAIL, in);
        int size = in.readInt();
        if (size > 0) {
          int charDataSize = in.readInt();

          // From the spec: An optional unsigned integer that MUST be 3 if present
          // but some files has 4
          int usKeyValue = in.readUShort();

          _address = StringUtil.readUnicodeLE(in, charDataSize / 2);
        } else {
          _address = null;
        }
      } else if (STD_MONIKER.equals(_moniker)) {
        _fileOpts = in.readShort();

        int len = in.readInt();

        byte[] path_bytes = new byte[len];
        in.readFully(path_bytes);

        _address = new String(path_bytes);
      }
    }

    if ((_linkOpts & HLINK_PLACE) != 0) {

      int len = in.readInt();
      _textMark = in.readUnicodeLEString(len);
    }

    if (in.remaining() > 0) {
      logger.log(
          POILogger.WARN,
          "Hyperlink data remains: " + in.remaining() + " : " + HexDump.toHex(in.readRemainder()));
    }
  }

  public void serialize(LittleEndianOutput out) {
    _range.serialize(out);

    _guid.serialize(out);
    out.writeInt(0x00000002); // TODO const
    out.writeInt(_linkOpts);

    if ((_linkOpts & HLINK_LABEL) != 0) {
      out.writeInt(_label.length());
      StringUtil.putUnicodeLE(_label, out);
    }
    if ((_linkOpts & HLINK_TARGET_FRAME) != 0) {
      out.writeInt(_targetFrame.length());
      StringUtil.putUnicodeLE(_targetFrame, out);
    }

    if ((_linkOpts & HLINK_URL) != 0 && (_linkOpts & HLINK_UNC_PATH) != 0) {
      out.writeInt(_address.length());
      StringUtil.putUnicodeLE(_address, out);
    }

    if ((_linkOpts & HLINK_URL) != 0 && (_linkOpts & HLINK_UNC_PATH) == 0) {
      _moniker.serialize(out);
      if (URL_MONIKER.equals(_moniker)) {
        if (_uninterpretedTail == null) {
          out.writeInt(_address.length() * 2);
          StringUtil.putUnicodeLE(_address, out);
        } else {
          out.writeInt(_address.length() * 2 + TAIL_SIZE);
          StringUtil.putUnicodeLE(_address, out);
          writeTail(_uninterpretedTail, out);
        }
      } else if (FILE_MONIKER.equals(_moniker)) {
        out.writeShort(_fileOpts);
        out.writeInt(_shortFilename.length());
        StringUtil.putCompressedUnicode(_shortFilename, out);
        writeTail(_uninterpretedTail, out);
        if (_address == null) {
          out.writeInt(0);
        } else {
          int addrLen = _address.length() * 2;
          out.writeInt(addrLen + 6);
          out.writeInt(addrLen);
          out.writeShort(0x0003); // TODO const
          StringUtil.putUnicodeLE(_address, out);
        }
      }
    }
    if ((_linkOpts & HLINK_PLACE) != 0) {
      out.writeInt(_textMark.length());
      StringUtil.putUnicodeLE(_textMark, out);
    }
  }

  protected int getDataSize() {
    int size = 0;
    size += 2 + 2 + 2 + 2; // rwFirst, rwLast, colFirst, colLast
    size += GUID.ENCODED_SIZE;
    size += 4; // label_opts
    size += 4; // link_opts
    if ((_linkOpts & HLINK_LABEL) != 0) {
      size += 4; // link length
      size += _label.length() * 2;
    }
    if ((_linkOpts & HLINK_TARGET_FRAME) != 0) {
      size += 4; // int nChars
      size += _targetFrame.length() * 2;
    }
    if ((_linkOpts & HLINK_URL) != 0 && (_linkOpts & HLINK_UNC_PATH) != 0) {
      size += 4; // int nChars
      size += _address.length() * 2;
    }
    if ((_linkOpts & HLINK_URL) != 0 && (_linkOpts & HLINK_UNC_PATH) == 0) {
      size += GUID.ENCODED_SIZE;
      if (URL_MONIKER.equals(_moniker)) {
        size += 4; // address length
        size += _address.length() * 2;
        if (_uninterpretedTail != null) {
          size += TAIL_SIZE;
        }
      } else if (FILE_MONIKER.equals(_moniker)) {
        size += 2; // file_opts
        size += 4; // address length
        size += _shortFilename.length();
        size += TAIL_SIZE;
        size += 4;
        if (_address != null) {
          size += 6;
          size += _address.length() * 2;
        }
      }
    }
    if ((_linkOpts & HLINK_PLACE) != 0) {
      size += 4; // address length
      size += _textMark.length() * 2;
    }
    return size;
  }

  private static byte[] readTail(byte[] expectedTail, LittleEndianInput in) {
    byte[] result = new byte[TAIL_SIZE];
    in.readFully(result);
    if (false) { // Quite a few examples in the unit tests which don't have the exact expected tail
      for (int i = 0; i < expectedTail.length; i++) {
        if (expectedTail[i] != result[i]) {
          System.err.println(
              "Mismatch in tail byte ["
                  + i
                  + "]"
                  + "expected "
                  + (expectedTail[i] & 0xFF)
                  + " but got "
                  + (result[i] & 0xFF));
        }
      }
    }
    return result;
  }

  private static void writeTail(byte[] tail, LittleEndianOutput out) {
    out.write(tail);
  }

  public short getSid() {
    return HyperlinkRecord.sid;
  }

  public String toString() {
    StringBuffer buffer = new StringBuffer();

    buffer.append("[HYPERLINK RECORD]\n");
    buffer.append("    .range   = ").append(_range.formatAsString()).append("\n");
    buffer.append("    .guid    = ").append(_guid.formatAsString()).append("\n");
    buffer.append("    .linkOpts= ").append(HexDump.intToHex(_linkOpts)).append("\n");
    buffer.append("    .label   = ").append(getLabel()).append("\n");
    if ((_linkOpts & HLINK_TARGET_FRAME) != 0) {
      buffer.append("    .targetFrame= ").append(getTargetFrame()).append("\n");
    }
    if ((_linkOpts & HLINK_URL) != 0 && _moniker != null) {
      buffer.append("    .moniker   = ").append(_moniker.formatAsString()).append("\n");
    }
    if ((_linkOpts & HLINK_PLACE) != 0) {
      buffer.append("    .textMark= ").append(getTextMark()).append("\n");
    }
    buffer.append("    .address   = ").append(getAddress()).append("\n");
    buffer.append("[/HYPERLINK RECORD]\n");
    return buffer.toString();
  }

  /** Based on the link options, is this a url? */
  public boolean isUrlLink() {
    return (_linkOpts & HLINK_URL) > 0 && (_linkOpts & HLINK_ABS) > 0;
  }
  /** Based on the link options, is this a file? */
  public boolean isFileLink() {
    return (_linkOpts & HLINK_URL) > 0 && (_linkOpts & HLINK_ABS) == 0;
  }
  /** Based on the link options, is this a document? */
  public boolean isDocumentLink() {
    return (_linkOpts & HLINK_PLACE) > 0;
  }

  /** Initialize a new url link */
  public void newUrlLink() {
    _range = new CellRangeAddress(0, 0, 0, 0);
    _guid = STD_MONIKER;
    _linkOpts = HLINK_URL | HLINK_ABS | HLINK_LABEL;
    setLabel("");
    _moniker = URL_MONIKER;
    setAddress("");
    _uninterpretedTail = URL_TAIL;
  }

  /** Initialize a new file link */
  public void newFileLink() {
    _range = new CellRangeAddress(0, 0, 0, 0);
    _guid = STD_MONIKER;
    _linkOpts = HLINK_URL | HLINK_LABEL;
    _fileOpts = 0;
    setLabel("");
    _moniker = FILE_MONIKER;
    setAddress(null);
    setShortFilename("");
    _uninterpretedTail = FILE_TAIL;
  }

  /** Initialize a new document link */
  public void newDocumentLink() {
    _range = new CellRangeAddress(0, 0, 0, 0);
    _guid = STD_MONIKER;
    _linkOpts = HLINK_LABEL | HLINK_PLACE;
    setLabel("");
    _moniker = FILE_MONIKER;
    setAddress("");
    setTextMark("");
  }

  public Object clone() {
    HyperlinkRecord rec = new HyperlinkRecord();
    rec._range = _range.copy();
    rec._guid = _guid;
    rec._linkOpts = _linkOpts;
    rec._fileOpts = _fileOpts;
    rec._label = _label;
    rec._address = _address;
    rec._moniker = _moniker;
    rec._shortFilename = _shortFilename;
    rec._targetFrame = _targetFrame;
    rec._textMark = _textMark;
    rec._uninterpretedTail = _uninterpretedTail;
    return rec;
  }
}
Example #11
0
/**
 * Label Record (0x0204) - read only support for strings stored directly in the cell.. Don't use
 * this (except to read), use LabelSST instead
 *
 * <p>REFERENCE: PG 325 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)
 *
 * <p>
 *
 * @author Andrew C. Oliver (acoliver at apache dot org)
 * @author Jason Height (jheight at chariot dot net dot au)
 * @version 2.0-pre
 * @see org.apache.poi.hssf.record.LabelSSTRecord
 */
public final class LabelRecord extends Record implements CellValueRecordInterface {
  private static final POILogger logger = POILogFactory.getLogger(LabelRecord.class);

  public static final short sid = 0x0204;

  private int field_1_row;
  private short field_2_column;
  private short field_3_xf_index;
  private short field_4_string_len;
  private byte field_5_unicode_flag;
  private String field_6_value;

  /** Creates new LabelRecord */
  public LabelRecord() {}

  /** @param in the RecordInputstream to read the record from */
  public LabelRecord(RecordInputStream in) {
    field_1_row = in.readUShort();
    field_2_column = in.readShort();
    field_3_xf_index = in.readShort();
    field_4_string_len = in.readShort();
    field_5_unicode_flag = in.readByte();
    if (field_4_string_len > 0) {
      if (isUnCompressedUnicode()) {
        field_6_value = in.readUnicodeLEString(field_4_string_len);
      } else {
        field_6_value = in.readCompressedUnicode(field_4_string_len);
      }
    } else {
      field_6_value = "";
    }

    if (in.remaining() > 0) {
      logger.log(
          POILogger.INFO,
          "LabelRecord data remains: "
              + in.remaining()
              + " : "
              + HexDump.toHex(in.readRemainder()));
    }
  }

  /*
   * READ ONLY ACCESS... THIS IS FOR COMPATIBILITY ONLY...USE LABELSST! public
   */
  public int getRow() {
    return field_1_row;
  }

  public short getColumn() {
    return field_2_column;
  }

  public short getXFIndex() {
    return field_3_xf_index;
  }

  /**
   * get the number of characters this string contains
   *
   * @return number of characters
   */
  public short getStringLength() {
    return field_4_string_len;
  }

  /**
   * is this uncompressed unicode (16bit)? Or just 8-bit compressed?
   *
   * @return isUnicode - True for 16bit- false for 8bit
   */
  public boolean isUnCompressedUnicode() {
    return (field_5_unicode_flag & 0x01) != 0;
  }

  /**
   * get the value
   *
   * @return the text string
   * @see #getStringLength()
   */
  public String getValue() {
    return field_6_value;
  }

  /** THROWS A RUNTIME EXCEPTION.. USE LABELSSTRecords. YOU HAVE NO REASON to use LABELRecord!! */
  public int serialize(int offset, byte[] data) {
    throw new RecordFormatException("Label Records are supported READ ONLY...convert to LabelSST");
  }

  public int getRecordSize() {
    throw new RecordFormatException("Label Records are supported READ ONLY...convert to LabelSST");
  }

  public short getSid() {
    return sid;
  }

  public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append("[LABEL]\n");
    sb.append("    .row       = ").append(HexDump.shortToHex(getRow())).append("\n");
    sb.append("    .column    = ").append(HexDump.shortToHex(getColumn())).append("\n");
    sb.append("    .xfindex   = ").append(HexDump.shortToHex(getXFIndex())).append("\n");
    sb.append("    .string_len= ").append(HexDump.shortToHex(field_4_string_len)).append("\n");
    sb.append("    .unicode_flag= ").append(HexDump.byteToHex(field_5_unicode_flag)).append("\n");
    sb.append("    .value       = ").append(getValue()).append("\n");
    sb.append("[/LABEL]\n");
    return sb.toString();
  }

  /** NO-OP! */
  public void setColumn(short col) {}

  /** NO-OP! */
  public void setRow(int row) {}

  /** no op! */
  public void setXFIndex(short xf) {}

  public Object clone() {
    LabelRecord rec = new LabelRecord();
    rec.field_1_row = field_1_row;
    rec.field_2_column = field_2_column;
    rec.field_3_xf_index = field_3_xf_index;
    rec.field_4_string_len = field_4_string_len;
    rec.field_5_unicode_flag = field_5_unicode_flag;
    rec.field_6_value = field_6_value;
    return rec;
  }
}