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 |