View Javadoc

1   ////////////////////////////////////////////////////////////////////////////////
2   // MillScript-Excel: an Open Spice interpreter and batch website creation tool
3   // Copyright (C) 2005 Open World Ltd, Kevin Rogers
4   //
5   // This file is part of MillScript-Excel.
6   //
7   // MillScript-Excel is free software; you can redistribute it and/or modify it under
8   // the terms of the GNU General Public License as published by the Free
9   // Software Foundation; either version 2 of the License, or (at your option)
10  // any later version.
11  //
12  // MillScript-Excel is distributed in the hope that it will be useful, but WITHOUT
13  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  // more details.
16  //
17  // You should have received a copy of the GNU General Public License along with
18  // MillScript-Excel; if not, write to the Free Software Foundation, Inc., 59 Temple
19  // Place, Suite 330, Boston, MA  02111-1307  USA
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              // Check if the compressed characters flag is currently set
91              if ( ( this.options & 0x01 ) == 0 ) {
92                  // The compresed characters flag is currently set
93                  if ( ( 0 & 0x01 ) == 0 ) {
94                      // The continuation has compressed characters
95                      // There is nothing to do
96                  } else {
97                      // The continuation does NOT have compressed characters
98                      this.options = (byte) ( this.options & 0xFE );
99                  }
100             } else {
101                 // The compressed characters is NOT currently set
102                 if ( ( 0 & 0x01 ) == 0 ) {
103                     // The continuation has compressed characters
104                     this.options = (byte) ( this.options | 0x01 );
105                 } else {
106                     // The continuation does NOT have compressed characters
107                     // There is nothing to do
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         // To determine which type of record parser we require, we must read
447         // the BOF record.
448         final BIFFRecordInputStream bis = new BIFFRecordInputStream( is );
449         // Read the record identifier
450         final int identifier = bis.read2ByteInt();
451         // Read the record data
452         final byte[] recordData = bis.readRecordData();
453         // Get the record syntax for this record
454         final RecordSyntax syntax = TABLE.get( identifier );
455         // Check it's a BOF record...
456         if( syntax instanceof BOFRecordSyntax ) {
457             // Get the BIFF version using the version data from the global BOF
458             // record. This is the only time we are interested in the version
459             // data held in a BOF record(and hence why it's hard coded here!)
460             final BIFFVersion version = BIFFVersion.getVersion(
461                 identifier,
462                 LittleEndianDecoder.DECODER.decode2ByteInt( recordData, 0 )
463             );
464             // Handle the BOF record identifier
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         // Pick a default charset for decoding byte strings when no CODEPAGE
545         // record has been found yet
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         // Store the specified BOF record data as the current record, this will
558         // allow us to properly construct the global BOF record at the end of
559         // this constructor
560         this.currentRecordData = bofData;
561         this.biffVersion = version;
562         this.input = is;
563         // This should be the last thing we initialise, as the interface
564         // requires we pass in the tokenizer for the current file.
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         // Ok, we haven't already read the next record, so do it now...
602         // Read the record identifier
603         final int identifier = this.input.read2ByteInt();
604         // Read the record data
605         this.currentRecordData = this.input.readRecordData();
606         // Reset the current record data offset
607         this.currentRecordDataOffset = 0;
608         // Validate the record size
609         this.biffVersion.checkRecordSize( this.currentRecordData );
610         // Get the record syntax for this record
611         final RecordSyntax syntax = TABLE.get( identifier );
612         // Did we find a syntax for this record identifier and is that syntax
613         // for a CONTINUE record?
614         if ( syntax instanceof ContinueRecordSyntax ) {
615             // Ah, just what we were expecting.
616             if ( this.checkBIFFVersionSupportsRecord( syntax ) ) {
617                 // and the record is supported in this BIFF version
618                 // Nothing to do - just leave.
619                 return;
620             } else {
621                 // Record is not supported in this BIFF version
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             // Ok, we haven't already read the next record, so do it now...
715             // Read the record identifier
716             final int identifier = this.input.read2ByteInt();
717             // Read the record data
718             this.currentRecordData = this.input.readRecordData();
719             // Reset the current record data offset
720             this.currentRecordDataOffset = 0;
721             // Validate the record size
722             this.biffVersion.checkRecordSize( this.currentRecordData );
723             // Get the record syntax for this record
724             final RecordSyntax syntax = TABLE.get( identifier );
725             // Did we find a syntax for this record identifier and is that syntax
726             // supported by this BIFF file version?
727             if ( syntax == null ) {
728                 return new Unrecognised( identifier, this.currentRecordData );
729             } else if ( this.checkBIFFVersionSupportsRecord( syntax ) ) {
730                 // Parse the record using this syntax
731                 try {
732                     final Record record = syntax.newRecord( this );
733                     // Safety check - is there any left over data?
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                     // Return the record
745                     return record;
746                 } catch ( Throwable e ) {
747                     System.out.println( e );
748                     return null;
749                 }
750             } else {
751                 // Record is not supported in this BIFF version
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             // Return the stored next record, clearing it ready for the next
764             // one
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             // NOTE - I'm not sure how sensible it is to return a reference to
923             // the backing record array, but it does save a array creation/copy
924             // sequence
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         // In these versions the column indicies are stored as
989         // individual bytes, so we just need to treat each byte as an
990         // unsigned number
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         // Skip the unused byte
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         // Safety check - store the starting offset
1106         final int startingOffset = this.getCurrentRecordDataOffset();
1107         // The current record offset is now at the start of the formula data
1108         final FormulaTokenizer formulaTokenizer = new FormulaTokenizer( this, formulaDataSize );
1109         // Parse the formula data
1110         try {
1111             final Expr< ? > formula = formulaTokenizer.parse();
1112             // The current offset should be at the end of the formula data, so skip
1113             // past the additional data section
1114             this.skipBytes( additionalDataSize );
1115             // Safety check - check the current offset is in the right place!
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             // Return the formula action
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         // A long string has a 16-bit length
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         // Extract the encoded value
1173         int encodedValue = rkValue & 0xFFFFFFFC;
1174         // Is it an iteger or a floating point value?
1175         if ( 0 == ( rkValue & 0x00000002 ) ) {
1176             // Bit 1 is 0, it's a floating point value
1177             // The encoded value represents the 30 most significant bits of the
1178             // IEEE 754 floating point value, the remaining 34 least
1179             // significant bits should be zero
1180             double doubleValue = Double.longBitsToDouble( (long) rkValue << 34 );
1181             // Was the value multiplied by 100?
1182             if ( 0 == ( rkValue & 0x00000001 ) ) {
1183                 // No, the value is correct
1184                 return new Double( doubleValue );
1185             } else {
1186                 // Yes, the value was multiplied by 100
1187                 return new Double( doubleValue / 100 );
1188             }
1189         } else {
1190             // Bit 1 is 1, it's an integer value
1191             // Shift the value right by 2, as the lowest two bits are used to
1192             // encode the type of value
1193             encodedValue >>>= 2;
1194             // Was the value multiplied by 100?
1195             if ( 0 == ( rkValue & 0x00000001 ) ) {
1196                 // No, the value is correct
1197                 return new Integer( encodedValue );
1198             } else {
1199                 // Yes, the value was multiplied by 100
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         // A short string has an 8-bit length
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         // Strings in BIFF8+ are always UTF-16LE, but all the high bytes may be
1234         // omitted if they are all zero
1235         // Get the options
1236         this.unicodeStringOptions = new StringOptions( this.readByte() );
1237         // Get the number of rich text formatting runs
1238         FormattingRun[] formattingRuns = null;
1239         if ( this.unicodeStringOptions.containsRichTextSettings() ) {
1240             formattingRuns = new FormattingRun[ this.read2ByteInt() ];
1241         } else {
1242             formattingRuns = new FormattingRun[ 0 ];
1243         }
1244         // Get the size of the Asian phonetic settings block
1245         byte[] asianPhoneticSettings = null;
1246         if ( this.unicodeStringOptions.containsAsianPhoneticSettings() ) {
1247             asianPhoneticSettings = new byte[ this.read4ByteInt() ];
1248         } else {
1249             asianPhoneticSettings = new byte[ 0 ];
1250         }
1251         // Build up the string from the record
1252         String string = null;
1253         // Check if we'll nee to read continuation records during the read
1254         int charsAvailable = this.unicodeStringOptions.compressedCharacters() ?
1255             ( this.getBytesLeftInCurrentRecordData() )
1256             : ( this.getBytesLeftInCurrentRecordData() / 2 );
1257         if ( charsAvailable < length ) {
1258             // We have to read continuation records
1259             final StringBuilder builder = new StringBuilder( length );
1260             // Read all the remaining characters into the builder.
1261             if ( this.unicodeStringOptions.compressedCharacters() ) {
1262                 builder.append( this.readCompressedCharArray( charsAvailable ) );
1263             } else {
1264                 builder.append( this.readCharArray( charsAvailable ) );
1265             }
1266             // Now loop until we've read the required string
1267             for ( int charsToRead = length - charsAvailable; charsToRead > 0; ) {
1268                 // Now read the continuation record.
1269                 this.continueRecord();
1270                 // Set the unicode string options for this continuation
1271                 this.unicodeStringOptions.setContinuationOptionsFlag( this.readByte() );
1272                 // Now read the data from the continue record.
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             // We don't have to read continuation records
1293             if ( this.unicodeStringOptions.compressedCharacters() ) {
1294                 string = new String( this.readCompressedCharArray( length ) );
1295             } else {
1296                 string = new String( this.readCharArray( length ) );
1297             }
1298         }
1299         // Read any formatting runs in the string
1300         for ( int i = 0; i < formattingRuns.length; i++ ) {
1301             formattingRuns[ i ] = this.readFormattingRun();
1302         }
1303         // Read any Asian phonetic settings
1304         this.readBytesInto( asianPhoneticSettings );
1305         // Clear out the string options we've been using
1306         this.unicodeStringOptions = null;
1307         // and return the string information
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