2005-05-28 01:36:00 -04:00
/ * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2006-12-22 14:18:16 -05:00
Licensed to the Apache Software Foundation ( ASF ) under one or more
contributor license agreements . See the NOTICE file distributed with
this work for additional information regarding copyright ownership .
The ASF licenses this file to You under the Apache License , Version 2 . 0
( the " License " ) ; you may not use this file except in compliance with
the License . You may obtain a copy of the License at
2005-05-28 01:36:00 -04:00
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an " AS IS " BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = * /
package org.apache.poi.hslf ;
2007-09-08 12:12:29 -04:00
import java.io.ByteArrayInputStream ;
import java.io.ByteArrayOutputStream ;
import java.io.FileInputStream ;
import java.io.FileNotFoundException ;
import java.io.IOException ;
import java.io.InputStream ;
import java.io.OutputStream ;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.Hashtable ;
import java.util.Iterator ;
import java.util.List ;
2005-05-28 01:36:00 -04:00
2006-04-04 12:50:04 -04:00
import org.apache.poi.POIDocument ;
2007-01-12 09:19:35 -05:00
import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException ;
2006-07-03 16:34:41 -04:00
import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException ;
2007-05-24 07:05:24 -04:00
import org.apache.poi.hslf.exceptions.HSLFException ;
2007-09-08 12:12:29 -04:00
import org.apache.poi.hslf.record.CurrentUserAtom ;
import org.apache.poi.hslf.record.ExOleObjStg ;
import org.apache.poi.hslf.record.PersistPtrHolder ;
import org.apache.poi.hslf.record.PositionDependentRecord ;
import org.apache.poi.hslf.record.Record ;
import org.apache.poi.hslf.record.UserEditAtom ;
import org.apache.poi.hslf.usermodel.ObjectData ;
2006-03-26 11:20:08 -05:00
import org.apache.poi.hslf.usermodel.PictureData ;
2007-09-08 12:12:29 -04:00
import org.apache.poi.poifs.filesystem.DocumentEntry ;
import org.apache.poi.poifs.filesystem.DocumentInputStream ;
import org.apache.poi.poifs.filesystem.POIFSFileSystem ;
import org.apache.poi.util.LittleEndian ;
import org.apache.poi.util.POILogFactory ;
import org.apache.poi.util.POILogger ;
2005-05-28 01:36:00 -04:00
/ * *
* This class contains the main functionality for the Powerpoint file
* " reader " . It is only a very basic class for now
*
* @author Nick Burch
* /
2006-04-04 12:50:04 -04:00
public class HSLFSlideShow extends POIDocument
2005-05-28 01:36:00 -04:00
{
2007-05-26 03:22:51 -04:00
// For logging
protected POILogger logger = POILogFactory . getLogger ( this . getClass ( ) ) ;
2006-03-26 11:20:08 -05:00
private InputStream istream ;
2005-05-28 01:36:00 -04:00
2006-04-04 12:50:04 -04:00
// Holds metadata on where things are in our document
2006-03-26 11:20:08 -05:00
private CurrentUserAtom currentUser ;
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
// Low level contents of the file
private byte [ ] _docstream ;
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
// Low level contents
private Record [ ] _records ;
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
// Raw Pictures contained in the pictures stream
private PictureData [ ] _pictures ;
2007-09-08 12:12:29 -04:00
// Embedded objects stored in storage records in the document stream, lazily populated.
private ObjectData [ ] _objects ;
/ * *
2006-07-03 16:34:41 -04:00
* Returns the underlying POIFSFileSystem for the document
* that is open .
* /
protected POIFSFileSystem getPOIFSFileSystem ( ) {
return filesystem ;
}
2006-03-26 11:20:08 -05:00
/ * *
* Constructs a Powerpoint document from fileName . Parses the document
* and places all the important stuff into data structures .
*
* @param fileName The name of the file to read .
* @throws IOException if there is a problem while parsing the document .
* /
public HSLFSlideShow ( String fileName ) throws IOException
{
this ( new FileInputStream ( fileName ) ) ;
}
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
/ * *
* Constructs a Powerpoint document from an input stream . Parses the
* document and places all the important stuff into data structures .
*
* @param inputStream the source of the data
* @throws IOException if there is a problem while parsing the document .
* /
public HSLFSlideShow ( InputStream inputStream ) throws IOException
{
//do Ole stuff
2005-05-28 01:36:00 -04:00
this ( new POIFSFileSystem ( inputStream ) ) ;
2006-03-26 11:20:08 -05:00
istream = inputStream ;
}
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
/ * *
* Constructs a Powerpoint document from a POIFS Filesystem . Parses the
* document and places all the important stuff into data structures .
*
* @param filesystem the POIFS FileSystem to read from
* @throws IOException if there is a problem while parsing the document .
* /
public HSLFSlideShow ( POIFSFileSystem filesystem ) throws IOException
{
2008-04-10 12:59:10 -04:00
super ( filesystem ) ;
2006-07-03 16:34:41 -04:00
// First up, grab the "Current User" stream
// We need this before we can detect Encrypted Documents
readCurrentUserStream ( ) ;
// Next up, grab the data that makes up the
// PowerPoint stream
readPowerPointStream ( ) ;
// Check to see if we have an encrypted document,
// bailing out if we do
boolean encrypted = EncryptedSlideShow . checkIfEncrypted ( this ) ;
if ( encrypted ) {
throw new EncryptedPowerPointFileException ( " Encrypted PowerPoint files are not supported " ) ;
}
2005-05-28 01:36:00 -04:00
2006-07-03 16:34:41 -04:00
// Now, build records based on the PowerPoint stream
buildRecords ( ) ;
2005-05-28 01:36:00 -04:00
// Look for Property Streams:
readProperties ( ) ;
2006-04-04 12:50:04 -04:00
2006-07-03 16:34:41 -04:00
// Look for any other streams
2006-04-04 12:50:04 -04:00
readOtherStreams ( ) ;
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
// Look for Picture Streams:
readPictures ( ) ;
}
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
/ * *
* Constructs a new , empty , Powerpoint document .
* /
public HSLFSlideShow ( ) throws IOException
{
this ( HSLFSlideShow . class . getResourceAsStream ( " /org/apache/poi/hslf/data/empty.ppt " ) ) ;
}
/ * *
* Shuts things down . Closes underlying streams etc
*
* @throws IOException
* /
public void close ( ) throws IOException
{
if ( istream ! = null ) {
istream . close ( ) ;
}
filesystem = null ;
2005-05-28 01:36:00 -04:00
}
2006-07-03 16:34:41 -04:00
/ * *
* Extracts the main PowerPoint document stream from the
* POI file , ready to be passed
*
* @throws IOException
* /
private void readPowerPointStream ( ) throws IOException
{
// Get the main document stream
DocumentEntry docProps =
( DocumentEntry ) filesystem . getRoot ( ) . getEntry ( " PowerPoint Document " ) ;
2005-05-28 01:36:00 -04:00
2006-07-03 16:34:41 -04:00
// Grab the document stream
_docstream = new byte [ docProps . getSize ( ) ] ;
filesystem . createDocumentInputStream ( " PowerPoint Document " ) . read ( _docstream ) ;
}
/ * *
* Builds the list of records , based on the contents
* of the PowerPoint stream
* /
private void buildRecords ( )
{
// The format of records in a powerpoint file are:
// <little endian 2 byte "info">
// <little endian 2 byte "type">
// <little endian 4 byte "length">
// If it has a zero length, following it will be another record
// <xx xx yy yy 00 00 00 00> <xx xx yy yy zz zz zz zz>
// If it has a length, depending on its type it may have children or data
// If it has children, these will follow straight away
// <xx xx yy yy zz zz zz zz <xx xx yy yy zz zz zz zz>>
// If it has data, this will come straigh after, and run for the length
// <xx xx yy yy zz zz zz zz dd dd dd dd dd dd dd>
// All lengths given exclude the 8 byte record header
// (Data records are known as Atoms)
// Document should start with:
// 0F 00 E8 03 ## ## ## ##
// (type 1000 = document, info 00 0f is normal, rest is document length)
// 01 00 E9 03 28 00 00 00
// (type 1001 = document atom, info 00 01 normal, 28 bytes long)
// 80 16 00 00 E0 10 00 00 xx xx xx xx xx xx xx xx
// 05 00 00 00 0A 00 00 00 xx xx xx
// (the contents of the document atom, not sure what it means yet)
// (records then follow)
// When parsing a document, look to see if you know about that type
// of the current record. If you know it's a type that has children,
// process the record's data area looking for more records
// If you know about the type and it doesn't have children, either do
// something with the data (eg TextRun) or skip over it
// If you don't know about the type, play safe and skip over it (using
// its length to know where the next record will start)
//
2007-05-29 04:32:56 -04:00
_records = read ( _docstream , ( int ) currentUser . getCurrentEditOffset ( ) ) ;
2006-07-03 16:34:41 -04:00
}
2005-05-28 01:36:00 -04:00
2007-05-29 04:32:56 -04:00
private Record [ ] read ( byte [ ] docstream , int usrOffset ) {
ArrayList lst = new ArrayList ( ) ;
while ( usrOffset ! = 0 ) {
UserEditAtom usr = ( UserEditAtom ) Record . buildRecordAtOffset ( docstream , usrOffset ) ;
lst . add ( new Integer ( usrOffset ) ) ;
int psrOffset = usr . getPersistPointersOffset ( ) ;
PersistPtrHolder ptr = ( PersistPtrHolder ) Record . buildRecordAtOffset ( docstream , psrOffset ) ;
lst . add ( new Integer ( psrOffset ) ) ;
Hashtable entries = ptr . getSlideLocationsLookup ( ) ;
for ( Iterator it = entries . keySet ( ) . iterator ( ) ; it . hasNext ( ) ; ) {
Integer id = ( Integer ) it . next ( ) ;
Integer offset = ( Integer ) entries . get ( id ) ;
lst . add ( offset ) ;
}
usrOffset = usr . getLastUserEditAtomOffset ( ) ;
}
//sort found records by offset.
//(it is not necessary but SlideShow.findMostRecentCoreRecords() expects them sorted)
Object a [ ] = lst . toArray ( ) ;
Arrays . sort ( a ) ;
Record [ ] rec = new Record [ lst . size ( ) ] ;
for ( int i = 0 ; i < a . length ; i + + ) {
Integer offset = ( Integer ) a [ i ] ;
rec [ i ] = ( Record ) Record . buildRecordAtOffset ( docstream , offset . intValue ( ) ) ;
}
return rec ;
}
2006-03-26 11:20:08 -05:00
/ * *
2006-07-03 16:34:41 -04:00
* Find the " Current User " stream , and load it
2006-03-26 11:20:08 -05:00
* /
2006-07-03 16:34:41 -04:00
private void readCurrentUserStream ( ) {
2006-03-26 11:20:08 -05:00
try {
currentUser = new CurrentUserAtom ( filesystem ) ;
} catch ( IOException ie ) {
2007-05-26 03:22:51 -04:00
logger . log ( POILogger . ERROR , " Error finding Current User Atom: \ n " + ie ) ;
2006-03-26 11:20:08 -05:00
currentUser = new CurrentUserAtom ( ) ;
}
}
2006-07-03 16:34:41 -04:00
/ * *
* Find any other streams from the filesystem , and load them
* /
private void readOtherStreams ( ) {
// Currently, there aren't any
}
2006-03-26 11:20:08 -05:00
/ * *
* Find and read in pictures contained in this presentation
* /
private void readPictures ( ) throws IOException {
byte [ ] pictstream ;
try {
DocumentEntry entry = ( DocumentEntry ) filesystem . getRoot ( ) . getEntry ( " Pictures " ) ;
pictstream = new byte [ entry . getSize ( ) ] ;
DocumentInputStream is = filesystem . createDocumentInputStream ( " Pictures " ) ;
is . read ( pictstream ) ;
} catch ( FileNotFoundException e ) {
// Silently catch exceptions if the presentation doesn't
// contain pictures - will use a null set instead
return ;
}
2006-09-19 18:37:38 -04:00
List p = new ArrayList ( ) ;
int pos = 0 ;
2006-12-14 06:49:56 -05:00
// An empty picture record (length 0) will take up 8 bytes
while ( pos < = ( pictstream . length - 8 ) ) {
2006-09-19 18:37:38 -04:00
int offset = pos ;
2006-12-14 06:49:56 -05:00
// Image signature
2006-09-19 18:37:38 -04:00
int signature = LittleEndian . getUShort ( pictstream , pos ) ;
pos + = LittleEndian . SHORT_SIZE ;
2006-12-14 06:49:56 -05:00
// Image type + 0xF018
2006-09-19 18:37:38 -04:00
int type = LittleEndian . getUShort ( pictstream , pos ) ;
pos + = LittleEndian . SHORT_SIZE ;
2006-12-14 06:49:56 -05:00
// Image size (excluding the 8 byte header)
2006-09-19 18:37:38 -04:00
int imgsize = LittleEndian . getInt ( pictstream , pos ) ;
pos + = LittleEndian . INT_SIZE ;
2007-01-12 09:19:35 -05:00
// The image size must be 0 or greater
// (0 is allowed, but odd, since we do wind on by the header each
// time, so we won't get stuck)
if ( imgsize < 0 ) {
throw new CorruptPowerPointFileException ( " The file contains a picture, at position " + p . size ( ) + " , which has a negatively sized data length, so we can't trust any of the picture data " ) ;
}
2006-12-14 06:49:56 -05:00
// If they type (including the bonus 0xF018) is 0, skip it
if ( type = = 0 ) {
2007-05-26 03:22:51 -04:00
logger . log ( POILogger . ERROR , " Problem reading picture: Invalid image type 0, on picture with length " + imgsize + " . \ nYou document will probably become corrupted if you save it! " ) ;
logger . log ( POILogger . ERROR , " " + pos ) ;
2006-12-14 06:49:56 -05:00
} else {
2007-01-15 11:40:03 -05:00
// Copy the data, ready to pass to PictureData
byte [ ] imgdata = new byte [ imgsize ] ;
if ( imgsize > 0 ) {
System . arraycopy ( pictstream , pos , imgdata , 0 , imgdata . length ) ;
}
2006-12-14 06:49:56 -05:00
// Build the PictureData object from the data
try {
PictureData pict = PictureData . create ( type - 0xF018 ) ;
pict . setRawData ( imgdata ) ;
pict . setOffset ( offset ) ;
p . add ( pict ) ;
} catch ( IllegalArgumentException e ) {
2007-05-26 03:22:51 -04:00
logger . log ( POILogger . ERROR , " Problem reading picture: " + e + " \ nYou document will probably become corrupted if you save it! " ) ;
2006-12-14 06:49:56 -05:00
}
}
2006-10-12 07:26:05 -04:00
2006-09-19 18:37:38 -04:00
pos + = imgsize ;
}
2006-03-26 11:20:08 -05:00
_pictures = ( PictureData [ ] ) p . toArray ( new PictureData [ p . size ( ) ] ) ;
2005-05-28 01:36:00 -04:00
}
2007-09-17 11:21:34 -04:00
/ * *
* Writes out the slideshow file the is represented by an instance
* of this class .
* It will write out the common OLE2 streams . If you require all
* streams to be written out , pass in preserveNodes
* @param out The OutputStream to write to .
* @throws IOException If there is an unexpected IOException from
* the passed in OutputStream
* /
public void write ( OutputStream out ) throws IOException {
// Write out, but only the common streams
write ( out , false ) ;
}
/ * *
* Writes out the slideshow file the is represented by an instance
* of this class .
* If you require all streams to be written out ( eg Marcos , embeded
* documents ) , then set preserveNodes to true
* @param out The OutputStream to write to .
* @param preserveNodes Should all OLE2 streams be written back out , or only the common ones ?
* @throws IOException If there is an unexpected IOException from
* the passed in OutputStream
* /
public void write ( OutputStream out , boolean preserveNodes ) throws IOException {
// Get a new Filesystem to write into
POIFSFileSystem outFS = new POIFSFileSystem ( ) ;
2007-09-17 12:30:31 -04:00
// The list of entries we've written out
List writtenEntries = new ArrayList ( 1 ) ;
2007-09-17 11:21:34 -04:00
// Write out the Property Streams
2007-09-17 12:30:31 -04:00
writeProperties ( outFS , writtenEntries ) ;
2007-09-17 11:21:34 -04:00
// 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
Hashtable oldToNewPositions = new Hashtable ( ) ;
// First pass - figure out where all the position dependent
// records are going to end up, in the new scheme
// (Annoyingly, some powerpoing files have PersistPtrHolders
// that reference slides after the PersistPtrHolder)
ByteArrayOutputStream baos = new ByteArrayOutputStream ( ) ;
for ( int i = 0 ; i < _records . length ; i + + ) {
if ( _records [ i ] instanceof PositionDependentRecord ) {
PositionDependentRecord pdr = ( PositionDependentRecord ) _records [ i ] ;
int oldPos = pdr . getLastOnDiskOffset ( ) ;
int newPos = baos . size ( ) ;
pdr . setLastOnDiskOffset ( newPos ) ;
oldToNewPositions . put ( new Integer ( oldPos ) , new Integer ( newPos ) ) ;
//System.out.println(oldPos + " -> " + newPos);
}
2006-06-13 10:54:52 -04:00
2007-09-17 11:21:34 -04:00
// Dummy write out, so the position winds on properly
_records [ i ] . writeOut ( baos ) ;
}
2005-05-28 01:36:00 -04:00
2007-09-17 11:21:34 -04:00
// No go back through, actually writing ourselves out
baos . reset ( ) ;
for ( int i = 0 ; i < _records . length ; i + + ) {
// For now, we're only handling PositionDependentRecord's that
// happen at the top level.
// In future, we'll need the handle them everywhere, but that's
// a bit trickier
if ( _records [ i ] instanceof PositionDependentRecord ) {
// We've already figured out their new location, and
// told them that
// Tell them of the positions of the other records though
PositionDependentRecord pdr = ( PositionDependentRecord ) _records [ i ] ;
pdr . updateOtherRecordReferences ( oldToNewPositions ) ;
}
2005-06-09 11:07:56 -04:00
2007-09-17 11:21:34 -04:00
// Whatever happens, write out that record tree
_records [ i ] . writeOut ( baos ) ;
}
// Update our cached copy of the bytes that make up the PPT stream
_docstream = baos . toByteArray ( ) ;
2005-05-28 01:36:00 -04:00
2007-09-17 11:21:34 -04:00
// Write the PPT stream into the POIFS layer
ByteArrayInputStream bais = new ByteArrayInputStream ( baos . toByteArray ( ) ) ;
outFS . createDocument ( bais , " PowerPoint Document " ) ;
2007-09-17 12:30:31 -04:00
writtenEntries . add ( " PowerPoint Document " ) ;
2005-06-09 11:07:56 -04:00
2007-09-17 11:21:34 -04:00
// Update and write out the Current User atom
int oldLastUserEditAtomPos = ( int ) currentUser . getCurrentEditOffset ( ) ;
Integer newLastUserEditAtomPos = ( Integer ) oldToNewPositions . get ( new Integer ( oldLastUserEditAtomPos ) ) ;
if ( newLastUserEditAtomPos = = null ) {
throw new HSLFException ( " Couldn't find the new location of the UserEditAtom that used to be at " + oldLastUserEditAtomPos ) ;
}
currentUser . setCurrentEditOffset ( newLastUserEditAtomPos . intValue ( ) ) ;
currentUser . writeToFS ( outFS ) ;
2007-09-17 12:30:31 -04:00
writtenEntries . add ( " Current User " ) ;
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
2007-09-17 11:21:34 -04:00
// Write any pictures, into another stream
if ( _pictures ! = null ) {
ByteArrayOutputStream pict = new ByteArrayOutputStream ( ) ;
for ( int i = 0 ; i < _pictures . length ; i + + ) {
_pictures [ i ] . write ( pict ) ;
}
outFS . createDocument (
new ByteArrayInputStream ( pict . toByteArray ( ) ) , " Pictures "
) ;
2007-09-17 12:30:31 -04:00
writtenEntries . add ( " Pictures " ) ;
}
// If requested, write out any other streams we spot
if ( preserveNodes ) {
copyNodes ( filesystem , outFS , writtenEntries ) ;
2007-09-17 11:21:34 -04:00
}
2005-06-09 11:07:56 -04:00
2007-09-17 11:21:34 -04:00
// Send the POIFSFileSystem object out to the underlying stream
outFS . writeFilesystem ( out ) ;
}
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
/* ******************* adding methods follow ********************* */
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
/ * *
* Adds a new root level record , at the end , but before the last
* PersistPtrIncrementalBlock .
* /
public synchronized int appendRootLevelRecord ( Record newRecord ) {
int addedAt = - 1 ;
Record [ ] r = new Record [ _records . length + 1 ] ;
boolean added = false ;
for ( int i = ( _records . length - 1 ) ; i > = 0 ; i - - ) {
if ( added ) {
// Just copy over
r [ i ] = _records [ i ] ;
} else {
r [ ( i + 1 ) ] = _records [ i ] ;
if ( _records [ i ] instanceof PersistPtrHolder ) {
r [ i ] = newRecord ;
added = true ;
addedAt = i ;
}
}
}
_records = r ;
return addedAt ;
}
/ * *
* Add a new picture to this presentation .
* /
public void addPicture ( PictureData img ) {
// Copy over the existing pictures, into an array one bigger
PictureData [ ] lst ;
if ( _pictures = = null ) {
lst = new PictureData [ 1 ] ;
} else {
lst = new PictureData [ ( _pictures . length + 1 ) ] ;
System . arraycopy ( _pictures , 0 , lst , 0 , _pictures . length ) ;
}
// Add in the new image
lst [ lst . length - 1 ] = img ;
_pictures = lst ;
}
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
/* ******************* fetching methods follow ********************* */
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
/ * *
* Returns an array of all the records found in the slideshow
* /
public Record [ ] getRecords ( ) { return _records ; }
2005-05-28 01:36:00 -04:00
2006-03-26 11:20:08 -05:00
/ * *
* Returns an array of the bytes of the file . Only correct after a
* call to open or write - at all other times might be wrong !
* /
public byte [ ] getUnderlyingBytes ( ) { return _docstream ; }
2005-05-28 01:36:00 -04:00
2006-01-16 13:27:37 -05:00
/ * *
2006-03-26 11:20:08 -05:00
* Fetch the Current User Atom of the document
* /
public CurrentUserAtom getCurrentUserAtom ( ) { return currentUser ; }
/ * *
* Return array of pictures contained in this presentation
2006-01-16 13:27:37 -05:00
*
2006-03-26 11:20:08 -05:00
* @return array with the read pictures or < code > null < / code > if the
2006-01-16 13:27:37 -05:00
* presentation doesn ' t contain pictures .
* /
2006-03-26 11:20:08 -05:00
public PictureData [ ] getPictures ( ) {
return _pictures ;
2006-01-16 13:27:37 -05:00
}
2007-09-08 12:12:29 -04:00
/ * *
* Gets embedded object data from the slide show .
*
* @return the embedded objects .
* /
public ObjectData [ ] getEmbeddedObjects ( ) {
if ( _objects = = null ) {
List objects = new ArrayList ( ) ;
for ( int i = 0 ; i < _records . length ; i + + ) {
if ( _records [ i ] instanceof ExOleObjStg ) {
objects . add ( new ObjectData ( ( ExOleObjStg ) _records [ i ] ) ) ;
}
}
_objects = ( ObjectData [ ] ) objects . toArray ( new ObjectData [ objects . size ( ) ] ) ;
}
return _objects ;
}
2005-05-28 01:36:00 -04:00
}