View | Details | Raw Unified | Return to bug 47448
Collapse All | Expand All

(-)src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java (-1 / +1 lines)
Lines 134-140 Link Here
134
		Record r = null;
134
		Record r = null;
135
		
135
		
136
		// Create a new RecordStream and use that
136
		// Create a new RecordStream and use that
137
		HSSFRecordStream recordStream = new HSSFRecordStream(in);
137
		RecordFactoryInputStream recordStream = new RecordFactoryInputStream(in);
138
		
138
		
139
		// Process each record as they come in
139
		// Process each record as they come in
140
		while(going) {
140
		while(going) {
(-)src/java/org/apache/poi/hssf/eventusermodel/HSSFRecordStream.java (-234 lines)
Lines 1-234 Link Here
1
/* ====================================================================
2
   Licensed to the Apache Software Foundation (ASF) under one or more
3
   contributor license agreements.  See the NOTICE file distributed with
4
   this work for additional information regarding copyright ownership.
5
   The ASF licenses this file to You under the Apache License, Version 2.0
6
   (the "License"); you may not use this file except in compliance with
7
   the License.  You may obtain a copy of the License at
8
9
       http://www.apache.org/licenses/LICENSE-2.0
10
11
   Unless required by applicable law or agreed to in writing, software
12
   distributed under the License is distributed on an "AS IS" BASIS,
13
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
   See the License for the specific language governing permissions and
15
   limitations under the License.
16
==================================================================== */
17
package org.apache.poi.hssf.eventusermodel;
18
19
import java.util.Vector;
20
21
import org.apache.poi.hssf.record.ContinueRecord;
22
import org.apache.poi.hssf.record.DrawingGroupRecord;
23
import org.apache.poi.hssf.record.DrawingRecord;
24
import org.apache.poi.hssf.record.ObjRecord;
25
import org.apache.poi.hssf.record.Record;
26
import org.apache.poi.hssf.record.RecordFactory;
27
import org.apache.poi.hssf.record.RecordFormatException;
28
import org.apache.poi.hssf.record.RecordInputStream;
29
import org.apache.poi.hssf.record.TextObjectRecord;
30
import org.apache.poi.hssf.record.UnknownRecord;
31
32
/**
33
 * A stream based way to get at complete records, with
34
 *  as low a memory footprint as possible.
35
 * This handles reading from a RecordInputStream, turning
36
 *  the data into full records, processing continue records
37
 *  etc.
38
 * Most users should use {@link HSSFEventFactory} /
39
 *  {@link HSSFListener} and have new records pushed to
40
 *  them, but this does allow for a "pull" style of coding.  
41
 */
42
public class HSSFRecordStream {
43
	private RecordInputStream in;
44
45
	/** Have we run out of records on the stream? */
46
	private boolean hitEOS = false;
47
	/** Have we returned all the records there are? */
48
	private boolean complete = false;
49
	
50
	/**
51
	 * Sometimes we end up with a bunch of
52
	 *  records. When we do, these should
53
	 *  be returned before the next normal
54
	 *  record processing occurs (i.e. before
55
	 *  we check for continue records and
56
	 *  return rec)
57
	 */
58
	private Vector bonusRecords = null;
59
	
60
	/** 
61
	 * The next record to return, which may need to have its
62
	 *  continue records passed to it before we do
63
	 */
64
	private Record rec = null;
65
	/**
66
	 * The most recent record that we gave to the user
67
	 */
68
	private Record lastRec = null;
69
	/**
70
	 * The most recent DrawingRecord seen
71
	 */
72
	private DrawingRecord lastDrawingRecord = new DrawingRecord();
73
	
74
	public HSSFRecordStream(RecordInputStream inp) {
75
		this.in = inp;
76
	}
77
78
	/**
79
	 * Returns the next (complete) record from the 
80
	 *  stream, or null if there are no more.
81
	 */
82
	public Record nextRecord() {
83
		Record r = null;
84
		
85
		// Loop until we get something
86
		while(r == null && !complete) {
87
			// Are there any bonus records that we need to
88
			//  return?
89
			r = getBonusRecord();
90
			
91
			// If not, ask for the next real record
92
			if(r == null) {
93
				r = getNextRecord();
94
			}
95
		}
96
		
97
		// All done
98
		return r;
99
	}
100
	
101
	/**
102
	 * If there are any "bonus" records, that should
103
	 *  be returned before processing new ones, 
104
	 *  grabs the next and returns it.
105
	 * If not, returns null;
106
	 */
107
	private Record getBonusRecord() {
108
		if(bonusRecords != null) {
109
			Record r = (Record)bonusRecords.remove(0);
110
			if(bonusRecords.size() == 0) {
111
				bonusRecords = null;
112
			}
113
			return r;
114
		}
115
		return null;
116
	}
117
	
118
	/**
119
	 * Returns the next available record, or null if
120
	 *  this pass didn't return a record that's
121
	 *  suitable for returning (eg was a continue record).
122
	 */
123
	private Record getNextRecord() {
124
		Record toReturn = null;
125
		
126
		if(in.hasNextRecord()) {
127
			// Grab our next record
128
			in.nextRecord();
129
			short sid = in.getSid();
130
			
131
            //
132
            // for some reasons we have to make the workbook to be at least 4096 bytes
133
            // but if we have such workbook we fill the end of it with zeros (many zeros)
134
            //
135
            // it is not good:
136
            // if the length( all zero records ) % 4 = 1
137
            // e.g.: any zero record would be readed as  4 bytes at once ( 2 - id and 2 - size ).
138
            // And the last 1 byte will be readed WRONG ( the id must be 2 bytes )
139
            //
140
            // So we should better to check if the sid is zero and not to read more data
141
            // The zero sid shows us that rest of the stream data is a fake to make workbook 
142
            // certain size
143
            //
144
            if ( sid == 0 )
145
                return null;
146
147
148
            // If we had a last record, and this one
149
            //  isn't a continue record, then pass
150
            //  it on to the listener
151
			if ((rec != null) && (sid != ContinueRecord.sid))
152
			{
153
				// This last record ought to be returned
154
				toReturn = rec;
155
			}
156
			
157
			// If this record isn't a continue record,
158
			//  then build it up
159
			if (sid != ContinueRecord.sid)
160
			{
161
				//System.out.println("creating "+sid);
162
				Record[] recs = RecordFactory.createRecord(in);
163
164
				// We know that the multiple record situations
165
				//  don't contain continue records, so just
166
				//  pass those on to the listener now
167
				if (recs.length > 1) {
168
					bonusRecords = new Vector(recs.length-1);
169
					for (int k = 0; k < (recs.length - 1); k++)	{
170
						bonusRecords.add(recs[k]);
171
					}
172
				}
173
				
174
				// Regardless of the number we created, always hold
175
				//  onto the last record to be processed on the next
176
				//  loop, in case it has any continue records
177
				rec = recs[ recs.length - 1 ];
178
				// Don't return it just yet though, as we probably have
179
				//  a record from the last round to return
180
			}
181
			else {
182
				// Normally, ContinueRecords are handled internally
183
				// However, in a few cases, there is a gap between a record at
184
				//  its Continue, so we have to handle them specially
185
				// This logic is much like in RecordFactory.createRecords()
186
				Record[] recs = RecordFactory.createRecord(in);
187
				ContinueRecord crec = (ContinueRecord)recs[0];
188
				if((lastRec instanceof ObjRecord) || (lastRec instanceof TextObjectRecord)) {
189
					// You can have Obj records between a DrawingRecord
190
					//  and its continue!
191
					lastDrawingRecord.processContinueRecord( crec.getData() );
192
					// Trigger them on the drawing record, now it's complete
193
					rec = lastDrawingRecord;
194
				}
195
				else if((lastRec instanceof DrawingGroupRecord)) {
196
					((DrawingGroupRecord)lastRec).processContinueRecord(crec.getData());
197
					// Trigger them on the drawing record, now it's complete
198
					rec = lastRec;
199
				}
200
				else {
201
                    if (rec instanceof UnknownRecord) {
202
                        ;//silently skip records we don't know about
203
                    } else {
204
					    throw new RecordFormatException("Records should handle ContinueRecord internally. Should not see this exception");
205
                    }
206
				}
207
			}
208
209
			// Update our tracking of the last record
210
			lastRec = rec;
211
			if(rec instanceof DrawingRecord) {
212
				lastDrawingRecord = (DrawingRecord)rec;
213
			}
214
		} else {
215
			// No more records
216
			hitEOS = true;
217
		}
218
		
219
		// If we've hit the end-of-stream, then
220
		//  finish off the last record and be done
221
		if(hitEOS) {
222
			complete = true;
223
			
224
			// Return the last record if there was
225
			//  one, otherwise null
226
			if(rec != null) {
227
				toReturn = rec;
228
				rec = null;
229
			}
230
		}
231
			
232
		return toReturn;
233
	}
234
}
(-)src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java (-142 / +139 lines)
Lines 14-33 Link Here
14
   See the License for the specific language governing permissions and
14
   See the License for the specific language governing permissions and
15
   limitations under the License.
15
   limitations under the License.
16
==================================================================== */
16
==================================================================== */
17
package org.apache.poi.hssf.eventusermodel;
17
package org.apache.poi.hssf.record;
18
18
19
import java.util.Vector;
19
import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
20
import org.apache.poi.hssf.eventusermodel.HSSFListener;
20
21
21
import org.apache.poi.hssf.record.ContinueRecord;
22
import java.util.Arrays;
22
import org.apache.poi.hssf.record.DrawingGroupRecord;
23
import java.util.LinkedList;
23
import org.apache.poi.hssf.record.DrawingRecord;
24
import java.util.List;
24
import org.apache.poi.hssf.record.ObjRecord;
25
import org.apache.poi.hssf.record.Record;
26
import org.apache.poi.hssf.record.RecordFactory;
27
import org.apache.poi.hssf.record.RecordFormatException;
28
import org.apache.poi.hssf.record.RecordInputStream;
29
import org.apache.poi.hssf.record.TextObjectRecord;
30
import org.apache.poi.hssf.record.UnknownRecord;
31
25
32
/**
26
/**
33
 * A stream based way to get at complete records, with
27
 * A stream based way to get at complete records, with
Lines 39-49 Link Here
39
 *  {@link HSSFListener} and have new records pushed to
33
 *  {@link HSSFListener} and have new records pushed to
40
 *  them, but this does allow for a "pull" style of coding.  
34
 *  them, but this does allow for a "pull" style of coding.  
41
 */
35
 */
42
public class HSSFRecordStream {
36
public class RecordFactoryInputStream {
43
	private RecordInputStream in;
37
	private final RecordInputStream recStream;
44
38
45
	/** Have we run out of records on the stream? */
46
	private boolean hitEOS = false;
47
	/** Have we returned all the records there are? */
39
	/** Have we returned all the records there are? */
48
	private boolean complete = false;
40
	private boolean complete = false;
49
	
41
	
Lines 55-81 Link Here
55
	 *  we check for continue records and
47
	 *  we check for continue records and
56
	 *  return rec)
48
	 *  return rec)
57
	 */
49
	 */
58
	private Vector bonusRecords = null;
50
	private final LinkedList bonusRecords = new LinkedList();
59
	
51
60
	/** 
52
        /**
61
	 * The next record to return, which may need to have its
62
	 *  continue records passed to it before we do
63
	 */
64
	private Record rec = null;
65
	/**
66
	 * The most recent record that we gave to the user
53
	 * The most recent record that we gave to the user
67
	 */
54
	 */
68
	private Record lastRec = null;
55
	private Record lastRecord = null;
69
	/**
56
	/**
70
	 * The most recent DrawingRecord seen
57
	 * The most recent DrawingRecord seen
71
	 */
58
	 */
72
	private DrawingRecord lastDrawingRecord = new DrawingRecord();
59
	private DrawingRecord lastDrawingRecord = new DrawingRecord();
73
	
60
74
	public HSSFRecordStream(RecordInputStream inp) {
61
        private int bofDepth=0;
75
		this.in = inp;
62
63
        private boolean lastRecordWasEOFLevelZero = false;
64
65
        private boolean includeContinueRecords = false;
66
67
        public RecordFactoryInputStream(RecordInputStream inp) {
68
                recStream = inp;
76
	}
69
	}
77
70
78
	/**
71
        /**
79
	 * Returns the next (complete) record from the 
72
	 * Returns the next (complete) record from the 
80
	 *  stream, or null if there are no more.
73
	 *  stream, or null if there are no more.
81
	 */
74
	 */
Lines 105-116 Link Here
105
	 * If not, returns null;
98
	 * If not, returns null;
106
	 */
99
	 */
107
	private Record getBonusRecord() {
100
	private Record getBonusRecord() {
108
		if(bonusRecords != null) {
101
		if(!bonusRecords.isEmpty()) {
109
			Record r = (Record)bonusRecords.remove(0);
102
                        return (Record) bonusRecords.removeFirst();
110
			if(bonusRecords.size() == 0) {
111
				bonusRecords = null;
112
			}
113
			return r;
114
		}
103
		}
115
		return null;
104
		return null;
116
	}
105
	}
Lines 120-234 Link Here
120
	 *  this pass didn't return a record that's
109
	 *  this pass didn't return a record that's
121
	 *  suitable for returning (eg was a continue record).
110
	 *  suitable for returning (eg was a continue record).
122
	 */
111
	 */
123
	private Record getNextRecord() {
112
        private Record getNextRecord() {
124
		Record toReturn = null;
113
                /*
125
		
114
                 * How to recognise end of stream?
126
		if(in.hasNextRecord()) {
115
                 * In the best case, the underlying input stream (in) ends just after the last EOF record
127
			// Grab our next record
116
                 * Usually however, the stream is padded with an arbitrary byte count.  Excel and most apps
128
			in.nextRecord();
117
                 * reliably use zeros for padding and if this were always the case, this code could just
129
			short sid = in.getSid();
118
                 * skip all the (zero sized) records with sid==0.  However, bug 46987 shows a file with
130
			
119
                 * non-zero padding that is read OK by Excel (Excel also fixes the padding).
131
            //
120
                 *
132
            // for some reasons we have to make the workbook to be at least 4096 bytes
121
                 * So to properly detect the workbook end of stream, this code has to identify the last
133
            // but if we have such workbook we fill the end of it with zeros (many zeros)
122
                 * EOF record.  This is not so easy because the worbook bof+eof pair do not bracket the
134
            //
123
                 * whole stream.  The worksheets follow the workbook, but it is not easy to tell how many
135
            // it is not good:
124
                 * sheet sub-streams should be present.  Hence we are looking for an EOF record that is not
136
            // if the length( all zero records ) % 4 = 1
125
                 * immediately followed by a BOF record.  One extra complication is that bof+eof sub-
137
            // e.g.: any zero record would be readed as  4 bytes at once ( 2 - id and 2 - size ).
126
                 * streams can be nested within worksheet streams and it's not clear in these cases what
138
            // And the last 1 byte will be readed WRONG ( the id must be 2 bytes )
127
                 * record might follow any EOF record.  So we also need to keep track of the bof/eof
139
            //
128
                 * nesting level.
140
            // So we should better to check if the sid is zero and not to read more data
129
                 */
141
            // The zero sid shows us that rest of the stream data is a fake to make workbook 
130
                
142
            // certain size
131
                if (recStream.hasNextRecord()) {
143
            //
132
                        // Grab our next record
144
            if ( sid == 0 )
133
                        recStream.nextRecord();
145
                return null;
146
134
135
                        if (lastRecordWasEOFLevelZero && recStream.getSid() != BOFRecord.sid) {
136
                                // Normally InputStream (in) contains only zero padding after this point
137
                                complete = true;
138
                                return null;
139
                        }
147
140
148
            // If we had a last record, and this one
141
                        Record record = RecordFactory.createSingleRecord(recStream);
149
            //  isn't a continue record, then pass
142
                        lastRecordWasEOFLevelZero = false;
150
            //  it on to the listener
151
			if ((rec != null) && (sid != ContinueRecord.sid))
152
			{
153
				// This last record ought to be returned
154
				toReturn = rec;
155
			}
156
			
157
			// If this record isn't a continue record,
158
			//  then build it up
159
			if (sid != ContinueRecord.sid)
160
			{
161
				//System.out.println("creating "+sid);
162
				Record[] recs = RecordFactory.createRecord(in);
163
143
164
				// We know that the multiple record situations
144
                        if (record instanceof BOFRecord) {
165
				//  don't contain continue records, so just
145
                                bofDepth++;
166
				//  pass those on to the listener now
146
                                return record;
167
				if (recs.length > 1) {
147
                        }
168
					bonusRecords = new Vector(recs.length-1);
169
					for (int k = 0; k < (recs.length - 1); k++)	{
170
						bonusRecords.add(recs[k]);
171
					}
172
				}
173
				
174
				// Regardless of the number we created, always hold
175
				//  onto the last record to be processed on the next
176
				//  loop, in case it has any continue records
177
				rec = recs[ recs.length - 1 ];
178
				// Don't return it just yet though, as we probably have
179
				//  a record from the last round to return
180
			}
181
			else {
182
				// Normally, ContinueRecords are handled internally
183
				// However, in a few cases, there is a gap between a record at
184
				//  its Continue, so we have to handle them specially
185
				// This logic is much like in RecordFactory.createRecords()
186
				Record[] recs = RecordFactory.createRecord(in);
187
				ContinueRecord crec = (ContinueRecord)recs[0];
188
				if((lastRec instanceof ObjRecord) || (lastRec instanceof TextObjectRecord)) {
189
					// You can have Obj records between a DrawingRecord
190
					//  and its continue!
191
					lastDrawingRecord.processContinueRecord( crec.getData() );
192
					// Trigger them on the drawing record, now it's complete
193
					rec = lastDrawingRecord;
194
				}
195
				else if((lastRec instanceof DrawingGroupRecord)) {
196
					((DrawingGroupRecord)lastRec).processContinueRecord(crec.getData());
197
					// Trigger them on the drawing record, now it's complete
198
					rec = lastRec;
199
				}
200
				else {
201
                    if (rec instanceof UnknownRecord) {
202
                        ;//silently skip records we don't know about
203
                    } else {
204
					    throw new RecordFormatException("Records should handle ContinueRecord internally. Should not see this exception");
205
                    }
206
				}
207
			}
208
148
209
			// Update our tracking of the last record
149
                        if (record instanceof EOFRecord) {
210
			lastRec = rec;
150
                                bofDepth--;
211
			if(rec instanceof DrawingRecord) {
151
                                if (bofDepth<1) {
212
				lastDrawingRecord = (DrawingRecord)rec;
152
                                        lastRecordWasEOFLevelZero = true;
213
			}
153
                                }
214
		} else {
154
215
			// No more records
155
                                return record;
216
			hitEOS = true;
156
                        }
217
		}
157
218
		
158
                        if (record instanceof DBCellRecord) {
219
		// If we've hit the end-of-stream, then
159
                                // Not needed by POI.  Regenerated from scratch by POI when spreadsheet is written
220
		//  finish off the last record and be done
160
                                return null;
221
		if(hitEOS) {
161
                        }
222
			complete = true;
162
223
			
163
                        if (record instanceof RKRecord) {
224
			// Return the last record if there was
164
                                return RecordFactory.convertToNumberRecord((RKRecord) record);
225
			//  one, otherwise null
165
                        }
226
			if(rec != null) {
166
227
				toReturn = rec;
167
                        if (record instanceof MulRKRecord) {
228
				rec = null;
168
                                NumberRecord[] records = RecordFactory.convertRKRecords((MulRKRecord) record);
229
			}
169
230
		}
170
                                List<NumberRecord> list = Arrays.asList(records);
231
			
171
                                bonusRecords.addAll(list.subList(1, list.size()));
232
		return toReturn;
172
233
	}
173
                                return records[0];
174
                        }
175
176
                        if (record.getSid() == DrawingGroupRecord.sid
177
                                   && lastRecord instanceof DrawingGroupRecord) {
178
                                DrawingGroupRecord lastDGRecord = (DrawingGroupRecord) lastRecord;
179
                                lastDGRecord.join((AbstractEscherHolderRecord) record);
180
                                return null;
181
                        } else if (record.getSid() == ContinueRecord.sid) {
182
                                ContinueRecord contRec = (ContinueRecord) record;
183
184
                                if (lastRecord instanceof ObjRecord || lastRecord instanceof TextObjectRecord) {
185
                                        // Drawing records have a very strange continue behaviour.
186
                                        //There can actually be OBJ records mixed between the continues.
187
                                        lastDrawingRecord.processContinueRecord(contRec.getData() );
188
                                        //we must remember the position of the continue record.
189
                                        //in the serialization procedure the original structure of records must be preserved
190
                                        if (includeContinueRecords) {
191
                                                return record;
192
                                        } else {
193
                                                return null;
194
                                        }
195
                                } else if (lastRecord instanceof DrawingGroupRecord) {
196
                                        ((DrawingGroupRecord)lastRecord).processContinueRecord(contRec.getData());
197
                                        return null;
198
                                } else if (lastRecord instanceof UnknownRecord) {
199
                                        //Gracefully handle records that we don't know about,
200
                                        //that happen to be continued
201
                                        return record;
202
                                } else if (lastRecord instanceof EOFRecord) {
203
                                        // This is really odd, but excel still sometimes
204
                                        //  outputs a file like this all the same
205
                                        return record;
206
                                } else {
207
                                        throw new RecordFormatException("Unhandled Continue Record");
208
                                }
209
                        } else {
210
                                lastRecord = record;
211
                                if (record instanceof DrawingRecord) {
212
                                        lastDrawingRecord = (DrawingRecord) record;
213
                                }
214
215
                                return record;
216
                        }
217
218
                } else {
219
                        // No more records
220
                        complete = true;
221
                        return null;
222
                }
223
        }
224
225
        /**
226
         *  Return or not ContinueRecord in nextRecord
227
         */
228
        public void setIncludeContinueRecords(boolean includeContinueRecords) {
229
                this.includeContinueRecords = includeContinueRecords;
230
        }
234
}
231
}
235
  + native
232
  + native
(-)src/java/org/apache/poi/hssf/record/RecordFactory.java (-112 / +13 lines)
Lines 17-38 Link Here
17
17
18
package org.apache.poi.hssf.record;
18
package org.apache.poi.hssf.record;
19
19
20
import org.apache.poi.hssf.record.chart.*;
21
import org.apache.poi.hssf.record.pivottable.*;
22
20
import java.io.InputStream;
23
import java.io.InputStream;
21
import java.lang.reflect.Constructor;
24
import java.lang.reflect.Constructor;
22
import java.lang.reflect.InvocationTargetException;
25
import java.lang.reflect.InvocationTargetException;
23
import java.lang.reflect.Modifier;
26
import java.lang.reflect.Modifier;
24
import java.util.ArrayList;
27
import java.util.*;
25
import java.util.Arrays;
26
import java.util.HashMap;
27
import java.util.HashSet;
28
import java.util.Iterator;
29
import java.util.List;
30
import java.util.Map;
31
import java.util.Set;
32
28
33
import org.apache.poi.hssf.record.chart.*;
34
import org.apache.poi.hssf.record.pivottable.*;
35
36
/**
29
/**
37
 * Title:  Record Factory<P>
30
 * Title:  Record Factory<P>
38
 * Description:  Takes a stream and outputs an array of Record objects.<P>
31
 * Description:  Takes a stream and outputs an array of Record objects.<P>
Lines 259-265 Link Here
259
		return new Record[] { record, };
252
		return new Record[] { record, };
260
	}
253
	}
261
254
262
	static Record createSingleRecord(RecordInputStream in) {
255
	public static Record createSingleRecord(RecordInputStream in) {
263
		I_RecordCreator constructor = _recordCreatorsById.get(new Integer(in.getSid()));
256
		I_RecordCreator constructor = _recordCreatorsById.get(new Integer(in.getSid()));
264
257
265
		if (constructor == null) {
258
		if (constructor == null) {
Lines 273-279 Link Here
273
	 * RK record is a slightly smaller alternative to NumberRecord
266
	 * RK record is a slightly smaller alternative to NumberRecord
274
	 * POI likes NumberRecord better
267
	 * POI likes NumberRecord better
275
	 */
268
	 */
276
	private static NumberRecord convertToNumberRecord(RKRecord rk) {
269
	public static NumberRecord convertToNumberRecord(RKRecord rk) {
277
		NumberRecord num = new NumberRecord();
270
		NumberRecord num = new NumberRecord();
278
271
279
		num.setColumn(rk.getColumn());
272
		num.setColumn(rk.getColumn());
Lines 286-292 Link Here
286
	/**
279
	/**
287
	 * Converts a {@link MulRKRecord} into an equivalent array of {@link NumberRecord}s
280
	 * Converts a {@link MulRKRecord} into an equivalent array of {@link NumberRecord}s
288
	 */
281
	 */
289
	private static NumberRecord[] convertRKRecords(MulRKRecord mrk) {
282
	public static NumberRecord[] convertRKRecords(MulRKRecord mrk) {
290
283
291
		NumberRecord[] mulRecs = new NumberRecord[mrk.getNumColumns()];
284
		NumberRecord[] mulRecs = new NumberRecord[mrk.getNumColumns()];
292
		for (int k = 0; k < mrk.getNumColumns(); k++) {
285
		for (int k = 0; k < mrk.getNumColumns(); k++) {
Lines 374-482 Link Here
374
	 * @exception RecordFormatException on error processing the InputStream
367
	 * @exception RecordFormatException on error processing the InputStream
375
	 */
368
	 */
376
	public static List<Record> createRecords(InputStream in) throws RecordFormatException {
369
	public static List<Record> createRecords(InputStream in) throws RecordFormatException {
377
378
		List<Record> records = new ArrayList<Record>(NUM_RECORDS);
370
		List<Record> records = new ArrayList<Record>(NUM_RECORDS);
379
371
380
		RecordInputStream recStream = new RecordInputStream(in);
372
		RecordFactoryInputStream recStream = new RecordFactoryInputStream(new RecordInputStream(in));
381
		DrawingRecord lastDrawingRecord = new DrawingRecord( );
373
                recStream.setIncludeContinueRecords(true);
382
		Record lastRecord = null;
383
		/*
384
		 * How to recognise end of stream?
385
		 * In the best case, the underlying input stream (in) ends just after the last EOF record
386
		 * Usually however, the stream is padded with an arbitrary byte count.  Excel and most apps
387
		 * reliably use zeros for padding and if this were always the case, this code could just
388
		 * skip all the (zero sized) records with sid==0.  However, bug 46987 shows a file with
389
		 * non-zero padding that is read OK by Excel (Excel also fixes the padding).
390
		 *
391
		 * So to properly detect the workbook end of stream, this code has to identify the last
392
		 * EOF record.  This is not so easy because the worbook bof+eof pair do not bracket the
393
		 * whole stream.  The worksheets follow the workbook, but it is not easy to tell how many
394
		 * sheet sub-streams should be present.  Hence we are looking for an EOF record that is not
395
		 * immediately followed by a BOF record.  One extra complication is that bof+eof sub-
396
		 * streams can be nested within worksheet streams and it's not clear in these cases what
397
		 * record might follow any EOF record.  So we also need to keep track of the bof/eof
398
		 * nesting level.
399
		 */
400
374
401
		int bofDepth=0;
375
                Record record;
402
		boolean lastRecordWasEOFLevelZero = false;
403
		while (recStream.hasNextRecord()) {
404
			recStream.nextRecord();
405
			if (lastRecordWasEOFLevelZero && recStream.getSid() != BOFRecord.sid) {
406
				// Normally InputStream (in) contains only zero padding after this point
407
				break;
408
			}
409
			Record record = createSingleRecord(recStream);
410
			lastRecordWasEOFLevelZero = false;
411
			if (record instanceof BOFRecord) {
412
				bofDepth++;
413
				records.add(record);
414
				continue;
415
			}
416
			if (record instanceof EOFRecord) {
417
				bofDepth--;
418
				records.add(record);
419
				if (bofDepth<1) {
420
					lastRecordWasEOFLevelZero = true;
421
				}
422
				continue;
423
			}
424
376
425
			if (record instanceof DBCellRecord) {
377
		while ((record = recStream.nextRecord())!=null) {
426
				// Not needed by POI.  Regenerated from scratch by POI when spreadsheet is written
378
                        records.add(record);
427
				continue;
428
			}
429
430
			if (record instanceof RKRecord) {
431
				records.add(convertToNumberRecord((RKRecord) record));
432
				continue;
433
			}
434
			if (record instanceof MulRKRecord) {
435
				addAll(records, convertRKRecords((MulRKRecord)record));
436
				continue;
437
			}
438
439
			if (record.getSid() == DrawingGroupRecord.sid
440
				   && lastRecord instanceof DrawingGroupRecord) {
441
				DrawingGroupRecord lastDGRecord = (DrawingGroupRecord) lastRecord;
442
				lastDGRecord.join((AbstractEscherHolderRecord) record);
443
			} else if (record.getSid() == ContinueRecord.sid) {
444
				ContinueRecord contRec = (ContinueRecord)record;
445
446
				if (lastRecord instanceof ObjRecord || lastRecord instanceof TextObjectRecord) {
447
					// Drawing records have a very strange continue behaviour.
448
					//There can actually be OBJ records mixed between the continues.
449
					lastDrawingRecord.processContinueRecord(contRec.getData() );
450
					//we must remember the position of the continue record.
451
					//in the serialization procedure the original structure of records must be preserved
452
					records.add(record);
453
				} else if (lastRecord instanceof DrawingGroupRecord) {
454
					((DrawingGroupRecord)lastRecord).processContinueRecord(contRec.getData());
455
				} else if (lastRecord instanceof UnknownRecord) {
456
					//Gracefully handle records that we don't know about,
457
					//that happen to be continued
458
					records.add(record);
459
				} else if (lastRecord instanceof EOFRecord) {
460
					// This is really odd, but excel still sometimes
461
					//  outputs a file like this all the same
462
					records.add(record);
463
				} else {
464
					throw new RecordFormatException("Unhandled Continue Record");
465
				}
466
			} else {
467
				lastRecord = record;
468
				if (record instanceof DrawingRecord) {
469
					lastDrawingRecord = (DrawingRecord) record;
470
				}
471
				records.add(record);
472
			}
473
		}
379
		}
380
474
		return records;
381
		return records;
475
	}
382
	}
476
477
	private static void addAll(List<Record> destList, Record[] srcRecs) {
478
		for (int i = 0; i < srcRecs.length; i++) {
479
			destList.add(srcRecs[i]);
480
		}
481
	}
482
}
383
}

Return to bug 47448