1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.millscript.office.excel;
22
23 import java.awt.Color;
24 import java.io.InputStream;
25 import java.nio.ByteBuffer;
26 import java.nio.CharBuffer;
27 import java.nio.charset.CharacterCodingException;
28 import java.nio.charset.Charset;
29 import java.nio.charset.CharsetDecoder;
30 import java.nio.charset.IllegalCharsetNameException;
31 import java.nio.charset.UnsupportedCharsetException;
32 import java.util.Arrays;
33 import java.util.HashMap;
34
35 import org.millscript.commons.alert.Alert;
36 import org.millscript.commons.alert.alerts.Fault;
37 import org.millscript.commons.alert.reporters.StandardWarningAlertReporter;
38 import org.millscript.millscript.expr.ConstantExpr;
39 import org.millscript.millscript.expr.Expr;
40 import org.millscript.office.endianness.LittleEndianDecoder;
41 import org.millscript.office.excel.alerts.BIFFAlert;
42 import org.millscript.office.excel.records.Unrecognised;
43 import org.millscript.office.excel.records.substructures.CellRangeAddress;
44 import org.millscript.office.excel.records.substructures.ConstantCachedValue;
45 import org.millscript.office.excel.records.substructures.FormattingRun;
46 import org.millscript.office.excel.records.syntax.*;
47 import org.millscript.office.excel.versions.BIFF2;
48 import org.millscript.office.excel.versions.BIFF3;
49 import org.millscript.office.excel.versions.BIFF4S;
50 import org.millscript.office.excel.versions.BIFF4W;
51 import org.millscript.office.excel.versions.BIFF5;
52 import org.millscript.office.excel.versions.BIFF7;
53 import org.millscript.office.excel.versions.BIFF8;
54 import org.millscript.office.excel.versions.BIFF8X;
55 import org.millscript.office.excel.versions.BIFFVersion;
56 import org.millscript.office.excel.versions.biff7.Biff7RecordTokenizer;
57 import org.millscript.office.excel.versions.biff8.Biff8RecordTokenizer;
58 import org.millscript.office.spreadsheet.formula.AbsoluteCellAddress;
59 import org.millscript.office.spreadsheet.formula.OffsetCellAddress;
60
61 /**
62 * This class tokenizes BIFF records from an underlying stream.
63 */
64 public abstract class RecordTokenizer {
65
66 /**
67 *
68 */
69 public static class StringOptions {
70
71 private byte options;
72
73 public StringOptions( final byte o ) {
74 this.options = o;
75 }
76
77 public boolean compressedCharacters() {
78 return 0 == ( this.options & 0x01 );
79 }
80
81 public boolean containsAsianPhoneticSettings() {
82 return 0 != ( this.options & 0x04 );
83 }
84
85 public boolean containsRichTextSettings() {
86 return 0 != ( this.options & 0x08 );
87 }
88
89 public void setContinuationOptionsFlag( final byte o ) {
90
91 if ( ( this.options & 0x01 ) == 0 ) {
92
93 if ( ( 0 & 0x01 ) == 0 ) {
94
95
96 } else {
97
98 this.options = (byte) ( this.options & 0xFE );
99 }
100 } else {
101
102 if ( ( 0 & 0x01 ) == 0 ) {
103
104 this.options = (byte) ( this.options | 0x01 );
105 } else {
106
107
108 }
109 }
110 }
111
112 }
113
114 private static HashMap< Integer, RecordSyntax > TABLE = new HashMap< Integer, RecordSyntax >();
115
116 static {
117 TABLE.put( 0x0087, new AddInRecordSyntax() );
118 TABLE.put( 0x00C2, new AddMenuRecordSyntax() );
119 TABLE.put( 0x0021, new ArrayRecordSyntax() );
120 TABLE.put( 0x0221, new ArrayRecordSyntax() );
121 TABLE.put( 0x004B, new AutoDecRecordSyntax() );
122 TABLE.put( 0x009E, new AutoFilterRecordSyntax() );
123 TABLE.put( 0x009D, new AutoFilterInfoRecordSyntax() );
124 TABLE.put( 0x0040, new BackupRecordSyntax() );
125 TABLE.put( 0x003A, new BeginPrefRecordSyntax() );
126 TABLE.put( 0x00E9, new BitmapRecordSyntax() );
127 TABLE.put( 0x0001, new BlankRecordSyntax() );
128 TABLE.put( 0x0201, new BlankRecordSyntax() );
129 TABLE.put( 0x0009, new BOFRecordSyntax() );
130 TABLE.put( 0x0209, new BOFRecordSyntax() );
131 TABLE.put( 0x0409, new BOFRecordSyntax() );
132 TABLE.put( 0x0809, new BOFRecordSyntax() );
133 TABLE.put( 0x00DA, new BookBoolRecordSyntax() );
134 TABLE.put( 0x0005, new BoolErrRecordSyntax() );
135 TABLE.put( 0x0205, new BoolErrRecordSyntax() );
136 TABLE.put( 0x0029, new BottomMarginRecordSyntax() );
137 TABLE.put( 0x0085, new BoundSheetRecordSyntax() );
138 TABLE.put( 0x0056, new BuiltinFmtCountRecordSyntax() );
139 TABLE.put( 0x000C, new CalcCountRecordSyntax() );
140 TABLE.put( 0x000D, new CalcModeRecordSyntax() );
141 TABLE.put( 0x00E5, new CellMergingRecordSyntax() );
142 TABLE.put( 0x01B1, new CFRecordSyntax() );
143 TABLE.put( 0x105F, new Chart3DDataFormatRecordSyntax() );
144 TABLE.put( 0x101A, new ChartAreaRecordSyntax() );
145 TABLE.put( 0x100A, new ChartAreaFormatRecordSyntax() );
146 TABLE.put( 0x102D, new ChartArrowRecordSyntax() );
147 TABLE.put( 0x102F, new ChartArrowHeadRecordSyntax() );
148 TABLE.put( 0x1038, new ChartArrowRelPosRecordSyntax() );
149 TABLE.put( 0x100C, new ChartAttachedLabelRecordSyntax() );
150 TABLE.put( 0x1041, new ChartAxesSetRecordSyntax() );
151 TABLE.put( 0x101D, new ChartAxisRecordSyntax() );
152 TABLE.put( 0x1021, new ChartAxisLineRecordSyntax() );
153 TABLE.put( 0x1017, new ChartBarRecordSyntax() );
154 TABLE.put( 0x1033, new ChartBeginRecordSyntax() );
155 TABLE.put( 0x1002, new ChartChartRecordSyntax() );
156 TABLE.put( 0x103A, new ChartChart3DRecordSyntax() );
157 TABLE.put( 0x1014, new ChartChartFormatRecordSyntax() );
158 TABLE.put( 0x101C, new ChartChartLineRecordSyntax() );
159 TABLE.put( 0x1036, new ChartChartSizeRecordSyntax() );
160 TABLE.put( 0x1006, new ChartDataFormatRecordSyntax() );
161 TABLE.put( 0x1063, new ChartDataTableRecordSyntax() );
162 TABLE.put( 0x1024, new ChartDefaultTextRecordSyntax() );
163 TABLE.put( 0x103D, new ChartDropBarRecordSyntax() );
164 TABLE.put( 0x1034, new ChartEndRecordSyntax() );
165 TABLE.put( 0x1066, new ChartEscherFormatRecordSyntax() );
166 TABLE.put( 0x1062, new ChartExtRangeRecordSyntax() );
167 TABLE.put( 0x1026, new ChartFontRecordSyntax() );
168 TABLE.put( 0x1060, new ChartFontBaseRecordSyntax() );
169 TABLE.put( 0x104E, new ChartFormatRecordSyntax() );
170 TABLE.put( 0x1022, new ChartFormatLinkRecordSyntax() );
171 TABLE.put( 0x1050, new ChartFormatRunsRecordSyntax() );
172 TABLE.put( 0x1032, new ChartFrameRecordSyntax() );
173 TABLE.put( 0x1020, new ChartLabelRangeRecordSyntax() );
174 TABLE.put( 0x1015, new ChartLegendRecordSyntax() );
175 TABLE.put( 0x1043, new ChartLegendEntryRecordSyntax() );
176 TABLE.put( 0x1018, new ChartLineRecordSyntax() );
177 TABLE.put( 0x1007, new ChartLineFormatRecordSyntax() );
178 TABLE.put( 0x1009, new ChartMarkerFormatRecordSyntax() );
179 TABLE.put( 0x103B, new ChartMultiLinkRecordSyntax() );
180 TABLE.put( 0x1027, new ChartObjectLinkRecordSyntax() );
181 TABLE.put( 0x103C, new ChartPicFormatRecordSyntax() );
182 TABLE.put( 0x1019, new ChartPieRecordSyntax() );
183 TABLE.put( 0x1061, new ChartPieExtRecordSyntax() );
184 TABLE.put( 0x1067, new ChartPieExtSettRecordSyntax() );
185 TABLE.put( 0x100B, new ChartPieFormatRecordSyntax() );
186 TABLE.put( 0x1048, new ChartPivotRefRecordSyntax() );
187 TABLE.put( 0x1035, new ChartPlotAreaRecordSyntax() );
188 TABLE.put( 0x1064, new ChartPlotGrowthRecordSyntax() );
189 TABLE.put( 0x104F, new ChartPosRecordSyntax() );
190 TABLE.put( 0x1044, new ChartPropertiesRecordSyntax() );
191 TABLE.put( 0x1040, new ChartRadarAreaRecordSyntax() );
192 TABLE.put( 0x103E, new ChartRadarLineRecordSyntax() );
193 TABLE.put( 0x1037, new ChartRelPositionRecordSyntax() );
194 TABLE.put( 0x101B, new ChartScatterRecordSyntax() );
195 TABLE.put( 0x105B, new ChartSerErrorBarRecordSyntax() );
196 TABLE.put( 0x1045, new ChartSerGroupRecordSyntax() );
197 TABLE.put( 0x1003, new ChartSeriesRecordSyntax() );
198 TABLE.put( 0x105D, new ChartSeriesFormatRecordSyntax() );
199 TABLE.put( 0x1016, new ChartSeriesListRecordSyntax() );
200 TABLE.put( 0x1065, new ChartSerIndexRecordSyntax() );
201 TABLE.put( 0x104A, new ChartSerParentRecordSyntax() );
202 TABLE.put( 0x104B, new ChartSerTrendLineRecordSyntax() );
203 TABLE.put( 0x1004, new ChartSourceLinkRecordSyntax() );
204 TABLE.put( 0x1051, new ChartSourceLinkRecordSyntax() );
205 TABLE.put( 0x100D, new ChartStringRecordSyntax() );
206 TABLE.put( 0x103F, new ChartSurfaceRecordSyntax() );
207 TABLE.put( 0x1025, new ChartTextRecordSyntax() );
208 TABLE.put( 0x101E, new ChartTickRecordSyntax() );
209 TABLE.put( 0x013B, new ChartTrCellContentRecordSyntax() );
210 TABLE.put( 0x0196, new ChartTrHeaderRecordSyntax() );
211 TABLE.put( 0x0138, new ChartTrInfoRecordSyntax() );
212 TABLE.put( 0x0137, new ChartTrInsertRecordSyntax() );
213 TABLE.put( 0x014D, new ChartTrInsertTabRecordSyntax() );
214 TABLE.put( 0x0140, new ChartTrMoveRangeRecordSyntax() );
215 TABLE.put( 0x1001, new ChartUnitsRecordSyntax() );
216 TABLE.put( 0x1046, new ChartUsedAxesSetsRecordSyntax() );
217 TABLE.put( 0x101F, new ChartValueRangeRecordSyntax() );
218 TABLE.put( 0x01BA, new CodeNameRecordSyntax() );
219 TABLE.put( 0x0042, new CodePageRecordSyntax() );
220 TABLE.put( 0x007D, new ColInfoRecordSyntax() );
221 TABLE.put( 0x0020, new ColumnDefaultRecordSyntax() );
222 TABLE.put( 0x0024, new ColWidthRecordSyntax() );
223 TABLE.put( 0x01B0, new CondFmtRecordSyntax() );
224 TABLE.put( 0x003C, new ContinueRecordSyntax() );
225 TABLE.put( 0x00A9, new CoordListRecordSyntax() );
226 TABLE.put( 0x008C, new CountryRecordSyntax() );
227 TABLE.put( 0x005A, new CRNRecordSyntax() );
228 TABLE.put( 0x0022, new DateModeRecordSyntax() );
229 TABLE.put( 0x00D7, new DbCellRecordSyntax() );
230 TABLE.put( 0x0050, new DConRecordSyntax() );
231 TABLE.put( 0x01B5, new DConBinRecordSyntax() );
232 TABLE.put( 0x0052, new DConNameRecordSyntax() );
233 TABLE.put( 0x0051, new DConRefRecordSyntax() );
234 TABLE.put( 0x004A, new DDEEnabledRecordSyntax() );
235 TABLE.put( 0x0025, new DefaultRowHeightRecordSyntax() );
236 TABLE.put( 0x0225, new DefaultRowHeightRecordSyntax() );
237 TABLE.put( 0x0055, new DefColWidthRecordSyntax() );
238 TABLE.put( 0x00C3, new DelMenuRecordSyntax() );
239 TABLE.put( 0x0010, new DeltaRecordSyntax() );
240 TABLE.put( 0x0000, new DimensionsRecordSyntax() );
241 TABLE.put( 0x0200, new DimensionsRecordSyntax() );
242 TABLE.put( 0x00B8, new DocRouteRecordSyntax() );
243 TABLE.put( 0x00A8, new DragDropRecordSyntax() );
244 TABLE.put( 0x0161, new DSFRecordSyntax() );
245 TABLE.put( 0x01BE, new DVRecordSyntax() );
246 TABLE.put( 0x01B2, new DVALRecordSyntax() );
247 TABLE.put( 0x0088, new EDGRecordSyntax() );
248 TABLE.put( 0x0045, new EFontRecordSyntax() );
249 TABLE.put( 0x003B, new EndPrefRecordSyntax() );
250 TABLE.put( 0x000A, new EOFRecordSyntax() );
251 TABLE.put( 0x0016, new ExternCountRecordSyntax() );
252 TABLE.put( 0x0023, new ExternNameRecordSyntax() );
253 TABLE.put( 0x0223, new ExternNameRecordSyntax() );
254 TABLE.put( 0x0017, new ExternSheetRecordSyntax() );
255 TABLE.put( 0x00FF, new ExtSSTRecordSyntax() );
256 TABLE.put( 0x002F, new FilePassRecordSyntax() );
257 TABLE.put( 0x005B, new FileSharingRecordSyntax() );
258 TABLE.put( 0x00A5, new FileSharing2RecordSyntax() );
259 TABLE.put( 0x009B, new FilterModeRecordSyntax() );
260 TABLE.put( 0x009C, new FnGroupCountRecordSyntax() );
261 TABLE.put( 0x009A, new FnGroupNameRecordSyntax() );
262 TABLE.put( 0x0031, new FontRecordSyntax() );
263 TABLE.put( 0x0231, new FontRecordSyntax() );
264 TABLE.put( 0x0032, new Font2RecordSyntax() );
265 TABLE.put( 0x0015, new FooterRecordSyntax() );
266 TABLE.put( 0x001E, new FormatRecordSyntax() );
267 TABLE.put( 0x041E, new FormatRecordSyntax() );
268 TABLE.put( 0x001F, new FormatCountRecordSyntax() );
269 TABLE.put( 0x0006, new FormulaRecordSyntax() );
270 TABLE.put( 0x0206, new FormulaRecordSyntax() );
271 TABLE.put( 0x0406, new FormulaRecordSyntax() );
272 TABLE.put( 0x00AB, new GCWRecordSyntax() );
273 TABLE.put( 0x0082, new GridSetRecordSyntax() );
274 TABLE.put( 0x0080, new GutsRecordSyntax() );
275 TABLE.put( 0x0083, new HCenterRecordSyntax() );
276 TABLE.put( 0x0014, new HeaderRecordSyntax() );
277 TABLE.put( 0x008D, new HideObjRecordSyntax() );
278 TABLE.put( 0x01B8, new HLinkRecordSyntax() );
279 TABLE.put( 0x001B, new HorizontalPageBreaksRecordSyntax() );
280 TABLE.put( 0x007F, new ImDataRecordSyntax() );
281 TABLE.put( 0x000B, new IndexRecordSyntax() );
282 TABLE.put( 0x020B, new IndexRecordSyntax() );
283 TABLE.put( 0x0035, new InFooPtsRecordSyntax() );
284 TABLE.put( 0x0002, new IntegerRecordSyntax() );
285 TABLE.put( 0x00E2, new InterfaceEndRecordSyntax() );
286 TABLE.put( 0x00E1, new InterfaceHdrRecordSyntax() );
287 TABLE.put( 0x0011, new IterationRecordSyntax() );
288 TABLE.put( 0x0044, new IXFERecordSyntax() );
289 TABLE.put( 0x0004, new LabelRecordSyntax() );
290 TABLE.put( 0x0204, new LabelRecordSyntax() );
291 TABLE.put( 0x015F, new LabelRangesRecordSyntax() );
292 TABLE.put( 0x00FD, new LabelSSTRecordSyntax() );
293 TABLE.put( 0x0026, new LeftMarginRecordSyntax() );
294 TABLE.put( 0x008B, new LhRecordRecordSyntax() );
295 TABLE.put( 0x0095, new LhnGraphRecordSyntax() );
296 TABLE.put( 0x0094, new LhRecordRecordSyntax() );
297 TABLE.put( 0x0098, new LPRRecordSyntax() );
298 TABLE.put( 0x004C, new MenuKeyRecordSyntax() );
299 TABLE.put( 0x004E, new MenuUndRecordSyntax() );
300 TABLE.put( 0x00E5, new MergedCellsRecordSyntax() );
301 TABLE.put( 0x00C1, new MMSRecordSyntax() );
302 TABLE.put( 0x004F, new MoveSelRecordSyntax() );
303 TABLE.put( 0x00EC, new MsoDrawingRecordSyntax() );
304 TABLE.put( 0x00EB, new MsoDrawingGroupRecordSyntax() );
305 TABLE.put( 0x00ED, new MsoDrawingSelectionRecordSyntax() );
306 TABLE.put( 0x00BE, new MulBlankRecordSyntax() );
307 TABLE.put( 0x00BD, new MulRKRecordSyntax() );
308 TABLE.put( 0x0018, new NameRecordSyntax() );
309 TABLE.put( 0x0218, new NameRecordSyntax() );
310 TABLE.put( 0x001C, new NoteRecordSyntax() );
311 TABLE.put( 0x008A, new NoteOffRecordSyntax() );
312 TABLE.put( 0x0003, new NumberRecordSyntax() );
313 TABLE.put( 0x0203, new NumberRecordSyntax() );
314 TABLE.put( 0x005D, new ObjRecordSyntax() );
315 TABLE.put( 0x00D3, new ObjProjRecordSyntax() );
316 TABLE.put( 0x0063, new ObjectProtectRecordSyntax() );
317 TABLE.put( 0x00DE, new OleSizeRecordSyntax() );
318 TABLE.put( 0x0092, new PaletteRecordSyntax() );
319 TABLE.put( 0x0041, new PaneRecordSyntax() );
320 TABLE.put( 0x00DC, new ParamQryRecordSyntax() );
321 TABLE.put( 0x0013, new PasswordRecordSyntax() );
322 TABLE.put( 0x00EF, new PhoneticRecordSyntax() );
323 TABLE.put( 0x004D, new PlsRecordSyntax() );
324 TABLE.put( 0x000E, new PrecisionRecordSyntax() );
325 TABLE.put( 0x002B, new PrintGridLinesRecordSyntax() );
326 TABLE.put( 0x002A, new PrintHeadersRecordSyntax() );
327 TABLE.put( 0x0033, new PrintSizeRecordSyntax() );
328 TABLE.put( 0x00A3, new ProjExtSheetRecordSyntax() );
329 TABLE.put( 0x01AF, new Prot4RevRecordSyntax() );
330 TABLE.put( 0x01BC, new Prot4RevPassRecordSyntax() );
331 TABLE.put( 0x0012, new ProtectRecordSyntax() );
332 TABLE.put( 0x0089, new PubRecordSyntax() );
333 TABLE.put( 0x01AD, new QSIRecordSyntax() );
334 TABLE.put( 0x0800, new QuickTipRecordSyntax() );
335 TABLE.put( 0x0868, new RangeProtectionRecordSyntax() );
336 TABLE.put( 0x00B9, new RecipNameRecordSyntax() );
337 TABLE.put( 0x000F, new RefModeRecordSyntax() );
338 TABLE.put( 0x01B7, new RefreshAllRecordSyntax() );
339 TABLE.put( 0x00DB, new RevertRecordSyntax() );
340 TABLE.put( 0x0027, new RightMarginRecordSyntax() );
341 TABLE.put( 0x007E, new RKRecordSyntax() );
342 TABLE.put( 0x027E, new RKRecordSyntax() );
343 TABLE.put( 0x0008, new RowRecordSyntax() );
344 TABLE.put( 0x0208, new RowRecordSyntax() );
345 TABLE.put( 0x00D6, new RStringRecordSyntax() );
346 TABLE.put( 0x005F, new SafeRecalcRecordSyntax() );
347 TABLE.put( 0x00AF, new ScenarioRecordSyntax() );
348 TABLE.put( 0x00AE, new ScenManRecordSyntax() );
349 TABLE.put( 0x00DD, new ScenProtectRecordSyntax() );
350 TABLE.put( 0x00A0, new SCLRecordSyntax() );
351 TABLE.put( 0x0800, new ScreenTipRecordSyntax() );
352 TABLE.put( 0x001D, new SelectionRecordSyntax() );
353 TABLE.put( 0x00A1, new SetupRecordSyntax() );
354 TABLE.put( 0x008F, new SheetHeaderRecordSyntax() );
355 TABLE.put( 0x0862, new SheetLayoutRecordSyntax() );
356 TABLE.put( 0x0867, new SheetProtectionRecordSyntax() );
357 TABLE.put( 0x008E, new SheetsOffsetRecordSyntax() );
358 TABLE.put( 0x0049, new ShortMenusRecordSyntax() );
359 TABLE.put( 0x0047, new ShowFormulaRecordSyntax() );
360 TABLE.put( 0x0046, new ShowScrollRecordSyntax() );
361 TABLE.put( 0x00BC, new ShrFmlaRecordSyntax() );
362 TABLE.put( 0x04BC, new ShrFmlaRecordSyntax() );
363 TABLE.put( 0x0090, new SortRecordSyntax() );
364 TABLE.put( 0x0096, new SoundRecordSyntax() );
365 TABLE.put( 0x00FC, new SSTRecordSyntax() );
366 TABLE.put( 0x0099, new StandardWidthRecordSyntax() );
367 TABLE.put( 0x0048, new StatusBarRecordSyntax() );
368 TABLE.put( 0x0007, new StringRecordSyntax() );
369 TABLE.put( 0x0207, new StringRecordSyntax() );
370 TABLE.put( 0x0093, new StyleRecordSyntax() );
371 TABLE.put( 0x0293, new StyleRecordSyntax() );
372 TABLE.put( 0x0091, new SubRecordSyntax() );
373 TABLE.put( 0x01AE, new SupBookRecordSyntax() );
374 TABLE.put( 0x00CA, new SxBooleanRecordSyntax() );
375 TABLE.put( 0x00CE, new SxDateTimeRecordSyntax() );
376 TABLE.put( 0x00C6, new SxDbRecordSyntax() );
377 TABLE.put( 0x0122, new SxDbexRecordSyntax() );
378 TABLE.put( 0x00C5, new SxDiRecordSyntax() );
379 TABLE.put( 0x00C9, new SxDoubleRecordSyntax() );
380 TABLE.put( 0x00CF, new SxEmptyRecordSyntax() );
381 TABLE.put( 0x00CB, new SxErrorRecordSyntax() );
382 TABLE.put( 0x00F1, new SxExRecordSyntax() );
383 TABLE.put( 0x01BB, new SxFdbTypeRecordSyntax() );
384 TABLE.put( 0x00C7, new SxFieldRecordSyntax() );
385 TABLE.put( 0x00F2, new SxFiltRecordSyntax() );
386 TABLE.put( 0x00F9, new SxFmlaRecordSyntax() );
387 TABLE.put( 0x00FB, new SxFormatRecordSyntax() );
388 TABLE.put( 0x0103, new SxFormulaRecordSyntax() );
389 TABLE.put( 0x00D9, new SxGroupInfoRecordSyntax() );
390 TABLE.put( 0x00D5, new SxIdStmRecordSyntax() );
391 TABLE.put( 0x00C8, new SxIndexListRecordSyntax() );
392 TABLE.put( 0x00CC, new SxIntegerRecordSyntax() );
393 TABLE.put( 0x00B4, new SxIvdRecordSyntax() );
394 TABLE.put( 0x00B5, new SxLiRecordSyntax() );
395 TABLE.put( 0x00F6, new SxNameRecordSyntax() );
396 TABLE.put( 0x00D8, new SxNumGroupRecordSyntax() );
397 TABLE.put( 0x00F8, new SxPairRecordSyntax() );
398 TABLE.put( 0x00B6, new SxPiRecordSyntax() );
399 TABLE.put( 0x00F0, new SxRuleRecordSyntax() );
400 TABLE.put( 0x00F7, new SxSelectRecordSyntax() );
401 TABLE.put( 0x00CD, new SxStringRecordSyntax() );
402 TABLE.put( 0x00D0, new SxTblRecordSyntax() );
403 TABLE.put( 0x00D2, new SxTbpgRecordSyntax() );
404 TABLE.put( 0x00D1, new SxTbrgItemRecordSyntax() );
405 TABLE.put( 0x00B1, new SxVdRecordSyntax() );
406 TABLE.put( 0x0100, new SxVdexRecordSyntax() );
407 TABLE.put( 0x00B2, new SxViRecordSyntax() );
408 TABLE.put( 0x00B0, new SxViewRecordSyntax() );
409 TABLE.put( 0x00E3, new SxVsRecordSyntax() );
410 TABLE.put( 0x003D, new TabIdRecordSyntax() );
411 TABLE.put( 0x00EA, new TabIdConfRecordSyntax() );
412 TABLE.put( 0x0036, new TableOpRecordSyntax() );
413 TABLE.put( 0x0236, new TableOpRecordSyntax() );
414 TABLE.put( 0x0037, new TableOp2RecordSyntax() );
415 TABLE.put( 0x0060, new TemplateRecordSyntax() );
416 TABLE.put( 0x0058, new ToolBarRecordSyntax() );
417 TABLE.put( 0x0028, new TopMarginRecordSyntax() );
418 TABLE.put( 0x01B6, new TxoRecordSyntax() );
419 TABLE.put( 0x00DF, new UdDescRecordSyntax() );
420 TABLE.put( 0x005E, new UnCalcedRecordSyntax() );
421 TABLE.put( 0x01A9, new UserBViewRecordSyntax() );
422 TABLE.put( 0x01AA, new UserSViewBeginRecordSyntax() );
423 TABLE.put( 0x01AB, new UserSViewEndRecordSyntax() );
424 TABLE.put( 0x0160, new UseSelfsRecordSyntax() );
425 TABLE.put( 0x0084, new VCenterRecordSyntax() );
426 TABLE.put( 0x001A, new VerticalPageBreaksRecordSyntax() );
427 TABLE.put( 0x0803, new WebQrySettingsRecordSyntax() );
428 TABLE.put( 0x0804, new WebQryTablesRecordSyntax() );
429 TABLE.put( 0x003D, new Window1RecordSyntax() );
430 TABLE.put( 0x003E, new Window2RecordSyntax() );
431 TABLE.put( 0x023E, new Window2RecordSyntax() );
432 TABLE.put( 0x0019, new WindowProtectionRecordSyntax() );
433 TABLE.put( 0x0038, new WnDeskRecordSyntax() );
434 TABLE.put( 0x005C, new WriteAccessRecordSyntax() );
435 TABLE.put( 0x0086, new WriteProtRecordSyntax() );
436 TABLE.put( 0x0081, new WsBoolRecordSyntax() );
437 TABLE.put( 0x0059, new XCTRecordSyntax() );
438 TABLE.put( 0x0043, new XFRecordSyntax() );
439 TABLE.put( 0x0243, new XFRecordSyntax() );
440 TABLE.put( 0x0443, new XFRecordSyntax() );
441 TABLE.put( 0x00E0, new XFRecordSyntax() );
442 TABLE.put( 0x0162, new Xl5ModifyRecordSyntax() );
443 }
444
445 public static final RecordTokenizer getRecordTokenizer( final InputStream is ) {
446
447
448 final BIFFRecordInputStream bis = new BIFFRecordInputStream( is );
449
450 final int identifier = bis.read2ByteInt();
451
452 final byte[] recordData = bis.readRecordData();
453
454 final RecordSyntax syntax = TABLE.get( identifier );
455
456 if( syntax instanceof BOFRecordSyntax ) {
457
458
459
460 final BIFFVersion version = BIFFVersion.getVersion(
461 identifier,
462 LittleEndianDecoder.DECODER.decode2ByteInt( recordData, 0 )
463 );
464
465 switch ( version ) {
466 case BIFF2:
467 throw new BIFFAlert(
468 "Excel 2.1 files are not supported at the moment"
469 ).culprit( "identifier", identifier ).mishap();
470 case BIFF3:
471 throw new BIFFAlert(
472 "Excel 3.0 files are not supported at the moment"
473 ).culprit( "identifier", identifier ).mishap();
474 case BIFF4S:
475 case BIFF4W:
476 throw new BIFFAlert(
477 "Excel 4.0 files are not supported at the moment"
478 ).culprit( "identifier", identifier ).mishap();
479 case BIFF5:
480 case BIFF7:
481 return new Biff7RecordTokenizer( version, bis, recordData );
482 case BIFF8:
483 case BIFF8X:
484 return new Biff8RecordTokenizer( BIFFVersion.BIFF8X, bis, recordData );
485 default:
486 throw new BIFFAlert(
487 "Unknown BIFF version in BOF record"
488 ).culprit(
489 "identifier",
490 identifier
491 ).culprit(
492 "version",
493 version
494 ).mishap();
495 }
496 } else {
497 throw new BIFFAlert(
498 "Unexpected record in BIFF file"
499 ).culprit(
500 "expected identifier",
501 "0x0009, 0x0209, 0x0409 or 0x0809(i.e. a BOF record)"
502 ).culprit(
503 "identifier",
504 identifier
505 ).mishap();
506 }
507 }
508
509 /**
510 * The BIFF version for this file.
511 */
512 final BIFFVersion biffVersion;
513
514 Charset codepage;
515
516 CharsetDecoder codepageDecoder;
517
518 byte[] currentRecordData;
519
520 int currentRecordDataOffset;
521
522 /**
523 * The underlying input stream to read records from.
524 */
525 BIFFRecordInputStream input;
526
527 /**
528 * The next record to be returned by a call to the nextRecord method. This
529 * may be <code>null</code>, in which case a record will be fetched from
530 * the underlying stream.
531 */
532 Record nextRecord;
533
534 /**
535 * The string options currently in force, only valid when reading a Unicode
536 * string.
537 */
538 StringOptions unicodeStringOptions;
539
540 /**
541 *
542 */
543 protected RecordTokenizer( final BIFFVersion version, final BIFFRecordInputStream is, final byte[] bofData ) {
544
545
546 try {
547 this.setCodepage( Charset.forName( "windows-1252" ) );
548 } catch ( IllegalCharsetNameException ex ) {
549 throw new Fault(
550 "Illegal default codepage"
551 ).mishap();
552 } catch ( UnsupportedCharsetException ex ) {
553 throw new Fault(
554 "Unsupported default codepage"
555 ).mishap();
556 }
557
558
559
560 this.currentRecordData = bofData;
561 this.biffVersion = version;
562 this.input = is;
563
564
565 this.nextRecord = new BOFRecordSyntax().newRecord( this );
566 }
567
568 public boolean checkBIFFVersionSupportsRecord( final Object object ) {
569 switch ( this.getBiffVersion() ) {
570 case BIFF2:
571 return BIFF2.class.isAssignableFrom( object.getClass() );
572 case BIFF3:
573 return BIFF3.class.isAssignableFrom( object.getClass() );
574 case BIFF4S:
575 return BIFF4S.class.isAssignableFrom( object.getClass() );
576 case BIFF4W:
577 return BIFF4W.class.isAssignableFrom( object.getClass() );
578 case BIFF5:
579 return BIFF5.class.isAssignableFrom( object.getClass() );
580 case BIFF7:
581 return BIFF7.class.isAssignableFrom( object.getClass() );
582 case BIFF8:
583 return BIFF8.class.isAssignableFrom( object.getClass() );
584 case BIFF8X:
585 return BIFF8X.class.isAssignableFrom( object.getClass() );
586 default:
587 throw new Fault(
588 "Unsupported BIFF version"
589 ).culprit( "version", this.getBiffVersion() ).mishap();
590 }
591 }
592
593 /**
594 * This method should ONLY be called when a continuation is expected or
595 * required during a pre-existing tokenization. Any existing record data
596 * will be overwritten and replaced with that from the CONTINUE record.
597 *
598 * @throws BIFFAlert if the next record is not a CONTINUE
599 */
600 private void continueRecord() {
601
602
603 final int identifier = this.input.read2ByteInt();
604
605 this.currentRecordData = this.input.readRecordData();
606
607 this.currentRecordDataOffset = 0;
608
609 this.biffVersion.checkRecordSize( this.currentRecordData );
610
611 final RecordSyntax syntax = TABLE.get( identifier );
612
613
614 if ( syntax instanceof ContinueRecordSyntax ) {
615
616 if ( this.checkBIFFVersionSupportsRecord( syntax ) ) {
617
618
619 return;
620 } else {
621
622 throw new BIFFAlert(
623 "This record is not supported by the current BIFF version"
624 ).culprit(
625 "identifier",
626 identifier
627 ).culprit(
628 "record",
629 syntax
630 ).mishap();
631 }
632 } else {
633 throw new BIFFAlert(
634 "A CONTINUE record was expected"
635 ).culprit(
636 "identifier",
637 identifier
638 ).culprit(
639 "record",
640 syntax
641 ).mishap();
642 }
643 }
644
645 /**
646 * Returns the string resulting from decoding the specified data using the
647 * current character encoding.
648 *
649 * @param data the byte data to decode
650 * @return a String containing the decoded byte data using the current
651 * character encoding
652 */
653 public String decode( final byte[] data ) {
654 try {
655 final CharBuffer characters = this.codepageDecoder.decode( ByteBuffer.wrap( data ) );
656 return characters.toString();
657 } catch ( CharacterCodingException ex ) {
658 throw new BIFFAlert(
659 "Failed to decode string data"
660 ).culprit( "data", data ).setParentThrowable( ex ).mishap();
661 }
662 }
663
664 /**
665 * Returns the BIFF version of the
666 * @return Returns the biffVersion.
667 */
668 public BIFFVersion getBiffVersion() {
669 return this.biffVersion;
670 }
671
672 /**
673 * Returns the number of bytes left in the current record.
674 *
675 * @return the integer number of bytes left in the current record
676 */
677 public int getBytesLeftInCurrentRecordData() {
678 return this.currentRecordData.length - this.currentRecordDataOffset;
679 }
680
681 /**
682 * @return Returns the codepage.
683 */
684 public Charset getCodepage() {
685 return this.codepage;
686 }
687
688 /**
689 * @return Returns the codepageDecoder.
690 */
691 public CharsetDecoder getCodepageDecoder() {
692 return this.codepageDecoder;
693 }
694
695 /**
696 * Returns the current record data offset. This is necessary in combination
697 * with {@link #setCurrentRecordDataOffset(int)} when record data is not
698 * stored sequentially, such as in formulas.
699 *
700 * @return the current record data offset
701 */
702 protected int getCurrentRecordDataOffset() {
703 return currentRecordDataOffset;
704 }
705
706 public abstract RecordParser getRecordParser();
707
708 public boolean hasMoreRecords() {
709 return this.input.hasMoreBytes();
710 }
711
712 public Record nextRecord() {
713 if ( this.nextRecord == null ) {
714
715
716 final int identifier = this.input.read2ByteInt();
717
718 this.currentRecordData = this.input.readRecordData();
719
720 this.currentRecordDataOffset = 0;
721
722 this.biffVersion.checkRecordSize( this.currentRecordData );
723
724 final RecordSyntax syntax = TABLE.get( identifier );
725
726
727 if ( syntax == null ) {
728 return new Unrecognised( identifier, this.currentRecordData );
729 } else if ( this.checkBIFFVersionSupportsRecord( syntax ) ) {
730
731 try {
732 final Record record = syntax.newRecord( this );
733
734 if ( this.getBytesLeftInCurrentRecordData() != 0 ) {
735 throw new Fault(
736 "There is unhandled record data left in the buffer"
737 ).decorate( record ).culprit(
738 "remaining data",
739 Arrays.toString(
740 this.readBytesLeftInCurrentRecordData()
741 )
742 ).mishap();
743 }
744
745 return record;
746 } catch ( Throwable e ) {
747 System.out.println( e );
748 return null;
749 }
750 } else {
751
752 throw new BIFFAlert(
753 "This record is not supported by the current BIFF version"
754 ).culprit(
755 "identifier",
756 identifier
757 ).culprit(
758 "record",
759 syntax
760 ).mishap();
761 }
762 } else {
763
764
765 final Record record = this.nextRecord;
766 this.nextRecord = null;
767 return record;
768 }
769 }
770
771 public Record peekRecord() {
772 if ( this.nextRecord == null ) {
773 this.nextRecord = this.nextRecord();
774 }
775 return this.nextRecord;
776 }
777
778 /**
779 * Returns a char value by decoding the 2 bytes at the current offset in
780 * the current record data. The current offset is incremented by 2 as a
781 * result of a call to this method.
782 *
783 * @return a <code>char</code> value resulting from decoding the 2 bytes
784 * at the current offset in the current record
785 */
786 public char read2ByteChar() {
787 final char val = LittleEndianDecoder.DECODER.decode2ByteChar(
788 this.currentRecordData,
789 this.currentRecordDataOffset
790 );
791 this.currentRecordDataOffset += 2;
792 return val;
793 }
794
795 /**
796 * Returns an int value by decoding the 2 bytes at the current offset in
797 * the current record data. The current offset is incremented by 2 as a
798 * result of a call to this method.
799 *
800 * @return an <code>int</code> value resulting from decoding the 2 bytes
801 * at the current offset in the current record
802 */
803 public int read2ByteInt() {
804 final int val = LittleEndianDecoder.DECODER.decode2ByteInt(
805 this.currentRecordData,
806 this.currentRecordDataOffset
807 );
808 this.currentRecordDataOffset += 2;
809 return val;
810 }
811
812 /**
813 * Returns an int value by decoding the 4 bytes at the current offset in
814 * the current record data. The current offset is incremented by 4 as a
815 * result of a call to this method.
816 *
817 * @return an <code>int</code> value resulting from decoding the 4 bytes
818 * at the current offset in the current record
819 */
820 public int read4ByteInt() {
821 final int val = LittleEndianDecoder.DECODER.decode4ByteInt(
822 this.currentRecordData,
823 this.currentRecordDataOffset
824 );
825 this.currentRecordDataOffset += 4;
826 return val;
827 }
828
829 /**
830 * Returns a double value by decoding the 8 bytes at the current offset in
831 * the current record data as an IEEE 754 floating-point value. The current
832 * offset is incremented by 8 as a result of a call to this method.
833 *
834 * @return a <code>double</code> value resulting from decoding the 8 bytes
835 * at the current offset in the current record
836 */
837 public double read8ByteDouble() {
838 final long val = LittleEndianDecoder.DECODER.decode8ByteLong(
839 this.currentRecordData,
840 this.currentRecordDataOffset
841 );
842 this.currentRecordDataOffset += 8;
843 return Double.longBitsToDouble( val );
844 }
845
846 /**
847 * Returns a long value by decoding the 8 bytes at the current offset in
848 * the current record data. The current offset is incremented by 8 as a
849 * result of a call to this method.
850 *
851 * @return a <code>long</code> value resulting from decoding the 8 bytes
852 * at the current offset in the current record
853 */
854 public long read8ByteLong() {
855 final long val = LittleEndianDecoder.DECODER.decode8ByteLong(
856 this.currentRecordData,
857 this.currentRecordDataOffset
858 );
859 this.currentRecordDataOffset += 8;
860 return val;
861 }
862
863 /**
864 * Returns the <code>AbsoluteCellAddress</code> at the current offset in
865 * the current record data. This method allows for the changes in the
866 * format of this record between BIFF7 and BIFF8. The current offset is
867 * incremented by the appropriate amount for the current BIFF version.
868 *
869 * @return the <code>AbsoluteCellAddress</code> at the current offset in
870 * the current record data
871 */
872 public abstract AbsoluteCellAddress readAbsoluteCellAddress();
873
874 /**
875 * Returns the single byte value by at the current offset in the current
876 * record data. The current offset is incremented by 1 as a result of a
877 * call to this method.
878 *
879 * @return a <code>byte</code> value resulting from decoding the byte at
880 * the current offset in the current record
881 */
882 public byte readByte() {
883 return this.currentRecordData[ this.currentRecordDataOffset++ ];
884 }
885
886 /**
887 * Returns a byte array of the specified size, filled with the specified
888 * number of bytes from the current offset in the current record. The
889 * current offset is incremented by the same amount.
890 *
891 * @param length the number of bytes to read
892 * @return a byte array of the specified size, filled with that number of
893 * bytes from the current offset in the current record data
894 */
895 public byte[] readBytes( final int length ) {
896 return this.readBytesInto( new byte[ length ] );
897 }
898
899 /**
900 * Fills the specified byte array with bytes from the current offset in the
901 * current record. The current offset is incremented by the length of the
902 * specified byte array.
903 *
904 * @param bytes the byte array to fill with data
905 * @return the specified byte array, filled with bytes from the current
906 * offset in the current record data
907 */
908 public byte[] readBytesInto( final byte[] bytes ) {
909 System.arraycopy( this.currentRecordData, this.currentRecordDataOffset, bytes, 0, bytes.length );
910 this.currentRecordDataOffset += bytes.length;
911 return bytes;
912 }
913
914 /**
915 * Returns a byte array holding all the remaining data in the current
916 * record.
917 *
918 * @return a byte array holding the remaining data in the current record
919 */
920 public byte[] readBytesLeftInCurrentRecordData() {
921 if ( this.currentRecordDataOffset == 0 ) {
922
923
924
925 this.currentRecordDataOffset += this.currentRecordData.length;
926 return this.currentRecordData;
927 } else {
928 return this.readBytes( this.getBytesLeftInCurrentRecordData() );
929 }
930 }
931
932 /**
933 * Returns the <code>CellRangeAddress</code> at the current offset in the
934 * current record data. This method allows for the changes in the format of
935 * this record between BIFF7 and BIFF8. The current offset is incremented
936 * by the appropriate amount for the current BIFF version.
937 *
938 * @return the <code>CellRangeAddress</code> at the current offset in the
939 * current record data
940 */
941 public abstract CellRangeAddress readCellRangeAddress();
942
943 /**
944 * Returns an array of the <code>CellRangeAddress</code> for the cell range
945 * address list at the current offset in the current record data. The
946 * current offset is incremented by the appropriate amount for the current
947 * BIFF version.
948 *
949 * @return an array of <code>CellRangeAddress</code> objects for the cell
950 * range address at the current offset in the current record data
951 */
952 public CellRangeAddress[] readCellRangeAddressList() {
953 final int size = this.read2ByteInt();
954 final CellRangeAddress[] addressList = new CellRangeAddress[ size ];
955 for ( int i = 0; i < size; i++ ) {
956 addressList[ i ] = this.readCellRangeAddress();
957 }
958 return addressList;
959 }
960
961 /**
962 * Returns an array of the <code>CellRangeAddress</code> for the cell range
963 * address list at the current offset in the current record data. This
964 * method always reads the record in the BIFF2-BIFF7 format. The current
965 * offset is incremented by a multiple of 6 bytes.
966 *
967 * @return an array of <code>CellRangeAddress</code> objects for the cell
968 * range address at the current offset in the current record data
969 */
970 public CellRangeAddress[] readCellRangeAddressListPreBIFF8() {
971 final int size = this.read2ByteInt();
972 final CellRangeAddress[] addressList = new CellRangeAddress[ size ];
973 for ( int i = 0; i < size; i++ ) {
974 addressList[ i ] = this.readCellRangeAddressPreBIFF8();
975 }
976 return addressList;
977 }
978
979 /**
980 * Returns the <code>CellRangeAddress</code> at the current offset in the
981 * current record data. This method always reads the record in the
982 * BIFF2-BIFF7 format. The current offset is incremented by 6 bytes.
983 *
984 * @return the <code>CellRangeAddress</code> at the current offset in the
985 * current record data
986 */
987 public CellRangeAddress readCellRangeAddressPreBIFF8() {
988
989
990
991 return new CellRangeAddress(
992 this.readUnsigned2Byte(),
993 this.readUnsigned2Byte(),
994 this.readUnsignedByte(),
995 this.readUnsignedByte()
996 );
997 }
998
999 /**
1000 * Reads the specified number of uncompressed 16-bit Unicode characters
1001 * from the current record.
1002 *
1003 * @param length the number of 16-bit characters to read
1004 * @return a char array contained the specified number of characters from
1005 * the current record
1006 */
1007 public char[] readCharArray( final int length ) {
1008 final char[] array = new char[ length ];
1009 for ( int i = 0; i < length; i++ ) {
1010 array[ i ] = this.read2ByteChar();
1011 }
1012 return array;
1013 }
1014
1015 /**
1016 * Reads an RGB color from the current position in the current record.
1017 *
1018 * @return the Color for the RGB color read
1019 */
1020 public Color readColor() {
1021 final Color color = new Color(
1022 this.readUnsignedByte(),
1023 this.readUnsignedByte(),
1024 this.readUnsignedByte()
1025 );
1026
1027 this.skipBytes( 1 );
1028 return color;
1029 }
1030
1031 /**
1032 * Reads the specified number of compressed 8-bit Unicode characters from
1033 * the current record.
1034 *
1035 * @param length the number of 8-bit characters to read
1036 * @return a char array contained the specified number of characters from
1037 * the current record
1038 */
1039 public char[] readCompressedCharArray( final int length ) {
1040 final char[] array = new char[ length ];
1041 for ( int i = 0; i < length; i++ ) {
1042 array[ i ] = (char) this.readUnsignedByte();
1043 }
1044 return array;
1045 }
1046
1047 /**
1048 * Returns an array of the <code>ConstantCachedValues</code> from the
1049 * current record offset. The number returned is decoded from the first
1050 * three bytes in a BIFF version dependant way.
1051 *
1052 * @return an array of <code>ConstantCachedValues</code>
1053 */
1054 public abstract ConstantCachedValue[] readConstantCachedValueArray();
1055
1056 /**
1057 * Returns an array of the specified number of
1058 * <code>ConstantCachedValues</code> from the current record offset.
1059 *
1060 * @param size the number of cached values to read
1061 * @return an array of the specified number of
1062 * <code>ConstantCachedValues</code>
1063 */
1064 public ConstantCachedValue[] readConstantCachedValues( final int size ) {
1065 final ConstantCachedValue[] values = new ConstantCachedValue[ size ];
1066 for ( int i = 0; i < values.length; i++ ) {
1067 values[ i ] = ConstantCachedValue.getValue( this );
1068 }
1069 return values;
1070 }
1071
1072 /**
1073 * Returns the formating run at the current offset in the current record
1074 * data. This method allows for the changes in the format of this record
1075 * between BIFF7 and BIFF8.
1076 *
1077 * @return the <code>FormattingRun</code> at the current offset in the
1078 * current record
1079 */
1080 public abstract FormattingRun readFormattingRun();
1081
1082 /**
1083 * Returns the {@link Expr} for the formula at the current offset in the
1084 * current record with the specified total byte size. This method will
1085 * first read the number of bytes used by the formula data, which is a BIFF
1086 * version dependant field.
1087 *
1088 * @param formulaSize the number of bytes the formula uses, including the
1089 * version dependant formula data byte size field
1090 * @return the <code>Expr</code> for the parsed formula
1091 */
1092 public abstract Expr< ? > readFormula( final int formulaSize );
1093
1094 /**
1095 * Returns the {@link Expr} for the formula at the current offset in the
1096 * current record with the specified number of bytes for the formula data
1097 * and additional data.
1098 *
1099 * @param formulaSize the number of bytes used by the formula data
1100 * @param additionalDataSize the number of bytes used by the additional
1101 * data
1102 * @return the <code>Expr</code> for the parsed formula
1103 */
1104 public Expr< ? > readFormula( final int formulaDataSize, final int additionalDataSize ) {
1105
1106 final int startingOffset = this.getCurrentRecordDataOffset();
1107
1108 final FormulaTokenizer formulaTokenizer = new FormulaTokenizer( this, formulaDataSize );
1109
1110 try {
1111 final Expr< ? > formula = formulaTokenizer.parse();
1112
1113
1114 this.skipBytes( additionalDataSize );
1115
1116 final int leftOverBytes = this.getCurrentRecordDataOffset() - startingOffset - formulaDataSize - additionalDataSize;
1117 if ( leftOverBytes < 0 ) {
1118 throw new Fault(
1119 "There is unhandled formula data left in the buffer"
1120 ).decorate( formula ).culprit(
1121 "remaining data",
1122 Arrays.toString(
1123 this.readBytes( leftOverBytes )
1124 )
1125 ).mishap();
1126 } else if ( leftOverBytes > 0 ) {
1127 throw new Fault(
1128 "Too much data read while parsing formula"
1129 ).decorate( formula ).culprit(
1130 "extra bytes read",
1131 leftOverBytes
1132 ).mishap();
1133 }
1134
1135 return formula;
1136 } catch ( Alert alert ) {
1137 StandardWarningAlertReporter.WARNING_REPORTER.report( alert );
1138 this.skipBytes( this.getBytesLeftInCurrentRecordData() );
1139 return new ConstantExpr( null );
1140 }
1141 }
1142
1143 /**
1144 * Returns a string with a 16-bit length from the current record at the
1145 * current offset.
1146 *
1147 * @return a String from the current offset in the current record
1148 */
1149 public String readLongString() {
1150
1151 return this.readString( this.read2ByteInt() );
1152 }
1153
1154 /**
1155 * Returns the <code>OffsetCellAddress</code> at the current offset in
1156 * the current record data. This method allows for the changes in the
1157 * format of this record between BIFF7 and BIFF8. The current offset is
1158 * incremented by the appropriate amount for the current BIFF version.
1159 *
1160 * @return the <code>OffsetCellAddress</code> at the current offset in
1161 * the current record data
1162 */
1163 public abstract OffsetCellAddress readOffsetCellAddress();
1164
1165 /**
1166 * Reads an RKValue from the current record.
1167 *
1168 * @return the Number contained in the RKValue structure
1169 */
1170 public Number readRKValue() {
1171 final int rkValue = this.read4ByteInt();
1172
1173 int encodedValue = rkValue & 0xFFFFFFFC;
1174
1175 if ( 0 == ( rkValue & 0x00000002 ) ) {
1176
1177
1178
1179
1180 double doubleValue = Double.longBitsToDouble( (long) rkValue << 34 );
1181
1182 if ( 0 == ( rkValue & 0x00000001 ) ) {
1183
1184 return new Double( doubleValue );
1185 } else {
1186
1187 return new Double( doubleValue / 100 );
1188 }
1189 } else {
1190
1191
1192
1193 encodedValue >>>= 2;
1194
1195 if ( 0 == ( rkValue & 0x00000001 ) ) {
1196
1197 return new Integer( encodedValue );
1198 } else {
1199
1200 return new Integer( encodedValue / 100 );
1201 }
1202 }
1203 }
1204
1205 /**
1206 * Returns a string with an 8-bit length from the current record at the
1207 * current offset.
1208 *
1209 * @return a String from the current offset in the current record
1210 */
1211 public String readShortString() {
1212
1213 return this.readString( this.readUnsignedByte() );
1214 }
1215
1216 /**
1217 * Returns a string of the specified length from the current record at the
1218 * current offset. This method allows for the varying storage of strings
1219 * between BIFF versions.
1220 *
1221 * @return a String from the current offset in the current record
1222 */
1223 public abstract String readString( final int length );
1224
1225 /**
1226 * Returns a Unicode string of the specified length from the current record
1227 * at the current offset.
1228 *
1229 * @param length the length of the string to read
1230 * @return a String from the current offset in the current record
1231 */
1232 public String readUnicodeString( final int length ) {
1233
1234
1235
1236 this.unicodeStringOptions = new StringOptions( this.readByte() );
1237
1238 FormattingRun[] formattingRuns = null;
1239 if ( this.unicodeStringOptions.containsRichTextSettings() ) {
1240 formattingRuns = new FormattingRun[ this.read2ByteInt() ];
1241 } else {
1242 formattingRuns = new FormattingRun[ 0 ];
1243 }
1244
1245 byte[] asianPhoneticSettings = null;
1246 if ( this.unicodeStringOptions.containsAsianPhoneticSettings() ) {
1247 asianPhoneticSettings = new byte[ this.read4ByteInt() ];
1248 } else {
1249 asianPhoneticSettings = new byte[ 0 ];
1250 }
1251
1252 String string = null;
1253
1254 int charsAvailable = this.unicodeStringOptions.compressedCharacters() ?
1255 ( this.getBytesLeftInCurrentRecordData() )
1256 : ( this.getBytesLeftInCurrentRecordData() / 2 );
1257 if ( charsAvailable < length ) {
1258
1259 final StringBuilder builder = new StringBuilder( length );
1260
1261 if ( this.unicodeStringOptions.compressedCharacters() ) {
1262 builder.append( this.readCompressedCharArray( charsAvailable ) );
1263 } else {
1264 builder.append( this.readCharArray( charsAvailable ) );
1265 }
1266
1267 for ( int charsToRead = length - charsAvailable; charsToRead > 0; ) {
1268
1269 this.continueRecord();
1270
1271 this.unicodeStringOptions.setContinuationOptionsFlag( this.readByte() );
1272
1273 final int maxCharsAvailable = this.unicodeStringOptions.compressedCharacters() ?
1274 ( this.getBytesLeftInCurrentRecordData() )
1275 : ( this.getBytesLeftInCurrentRecordData() / 2 );
1276 final int charsToReadThisIteration = charsToRead > maxCharsAvailable ? maxCharsAvailable : charsToRead;
1277 if ( this.unicodeStringOptions.compressedCharacters() ) {
1278 builder.append(
1279 this.readCompressedCharArray(
1280 charsToReadThisIteration
1281 )
1282 );
1283 } else {
1284 builder.append(
1285 this.readCharArray( charsToReadThisIteration )
1286 );
1287 }
1288 charsToRead -= charsToReadThisIteration;
1289 }
1290 string = builder.toString();
1291 } else {
1292
1293 if ( this.unicodeStringOptions.compressedCharacters() ) {
1294 string = new String( this.readCompressedCharArray( length ) );
1295 } else {
1296 string = new String( this.readCharArray( length ) );
1297 }
1298 }
1299
1300 for ( int i = 0; i < formattingRuns.length; i++ ) {
1301 formattingRuns[ i ] = this.readFormattingRun();
1302 }
1303
1304 this.readBytesInto( asianPhoneticSettings );
1305
1306 this.unicodeStringOptions = null;
1307
1308 return string;
1309 }
1310
1311 /**
1312 * Returns a char value by decoding the 2 bytes at the current offset in
1313 * the current record data. The current offset is incremented by 2 as a
1314 * result of a call to this method.
1315