1    /*
2     *  ====================================================================
3     *  The Apache Software License, Version 1.1
4     *
5     *  Copyright (c) 2002 The Apache Software Foundation.  All rights
6     *  reserved.
7     *
8     *  Redistribution and use in source and binary forms, with or without
9     *  modification, are permitted provided that the following conditions
10    *  are met:
11    *
12    *  1. Redistributions of source code must retain the above copyright
13    *  notice, this list of conditions and the following disclaimer.
14    *
15    *  2. Redistributions in binary form must reproduce the above copyright
16    *  notice, this list of conditions and the following disclaimer in
17    *  the documentation and/or other materials provided with the
18    *  distribution.
19    *
20    *  3. The end-user documentation included with the redistribution,
21    *  if any, must include the following acknowledgment:
22    *  "This product includes software developed by the
23    *  Apache Software Foundation (http://www.apache.org/)."
24    *  Alternately, this acknowledgment may appear in the software itself,
25    *  if and wherever such third-party acknowledgments normally appear.
26    *
27    *  4. The names "Apache" and "Apache Software Foundation" and
28    *  "Apache POI" must not be used to endorse or promote products
29    *  derived from this software without prior written permission. For
30    *  written permission, please contact apache@apache.org.
31    *
32    *  5. Products derived from this software may not be called "Apache",
33    *  "Apache POI", nor may "Apache" appear in their name, without
34    *  prior written permission of the Apache Software Foundation.
35    *
36    *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37    *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38    *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39    *  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
40    *  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41    *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42    *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43    *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44    *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45    *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46    *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47    *  SUCH DAMAGE.
48    *  ====================================================================
49    *
50    *  This software consists of voluntary contributions made by many
51    *  individuals on behalf of the Apache Software Foundation.  For more
52    *  information on the Apache Software Foundation, please see
53    *  <http://www.apache.org/>.
54    */
55   /*
56    *  BiffViewer.java
57    *
58    *  Created on November 13, 2001, 9:23 AM
59    */
60   package org.apache.poi.hssf.dev;
61   
62   import org.apache.poi.hssf.record.*;
63   import org.apache.poi.poifs.filesystem.POIFSFileSystem;
64   import org.apache.poi.util.HexDump;
65   import org.apache.poi.util.LittleEndian;
66   
67   import java.io.FileInputStream;
68   import java.io.IOException;
69   import java.io.InputStream;
70   import java.util.ArrayList;
71   
72   /**
73    *  Utillity for reading in BIFF8 records and displaying data from them.
74    *
75    *@author     Andrew C. Oliver (acoliver at apache dot org)
76    *@author     Glen Stampoultzis (glens at apache.org)
77    *@see        #main
78    */
79   
80   public class BiffViewer {
81       String filename;
82       private boolean dump;
83   
84   
85       /**
86        *  Creates new BiffViewer
87        *
88        *@param  args
89        */
90   
91       public BiffViewer(String[] args) {
92           if (args.length > 0) {
93               filename = args[0];
94           } else {
95               System.out.println("BIFFVIEWER REQUIRES A FILENAME***");
96           }
97       }
98   
99   
100      /**
101       *  Method run starts up BiffViewer...
102       */
103  
104      public void run() {
105          try {
106              POIFSFileSystem fs =
107                      new POIFSFileSystem(new FileInputStream(filename));
108              InputStream stream =
109                      fs.createDocumentInputStream("Workbook");
110              Record[] records = createRecords(stream, dump);
111          } catch (Exception e) {
112              e.printStackTrace();
113          }
114      }
115  
116  
117      /**
118       *  Create an array of records from an input stream
119       *
120       *@param  in                         the InputStream from which the records
121       *      will be obtained
122       *@param  dump
123       *@return                            an array of Records created from the
124       *      InputStream
125       *@exception  RecordFormatException  on error processing the InputStream
126       */
127  
128      public static Record[] createRecords(InputStream in, boolean dump)
129               throws RecordFormatException {
130          ArrayList records = new ArrayList();
131          Record last_record = null;
132          int loc = 0;
133  
134          try {
135  //            long  offset  = 0;
136              short rectype = 0;
137  
138              do {
139                  rectype = LittleEndian.readShort(in);
140                  System.out.println("============================================");
141                  System.out.println("Offset 0x" + Integer.toHexString(loc) + " (" + loc + ")");
142                  loc += 2;
143                  if (rectype != 0) {
144                      short recsize = LittleEndian.readShort(in);
145  
146                      loc += 2;
147                      byte[] data = new byte[(int) recsize];
148  
149                      in.read(data);
150                      if ((rectype == WSBoolRecord.sid) && (recsize == 0)) {
151                          System.out.println(loc);
152                      }
153                      loc += recsize;
154  //                    offset += 4 + recsize;
155                      if (dump) {
156                          dump(rectype, recsize, data);
157                      }
158                      Record[] recs = createRecord(rectype, recsize,
159                              data);
160                      // handle MulRK records
161  
162                      Record record = recs[0];
163  
164                      if ((record instanceof UnknownRecord)
165                              && !dump) {
166                          // if we didn't already dump
167                          // just cause dump was on and we're hit an unknow
168                          dumpUnknownRecord(data);
169                      }
170                      if (record != null) {
171                          if (rectype == ContinueRecord.sid) {
172                              dumpContinueRecord(last_record, dump, data);
173                          } else {
174                              last_record = record;
175                              records.add(record);
176                          }
177                      }
178                  }
179              } while (rectype != 0);
180          } catch (IOException e) {
181              throw new RecordFormatException("Error reading bytes");
182          }
183          Record[] retval = new Record[records.size()];
184  
185          retval = (Record[]) records.toArray(retval);
186          return retval;
187      }
188  
189  
190      /**
191       *  Description of the Method
192       *
193       *@param  last_record      Description of the Parameter
194       *@param  dump             Description of the Parameter
195       *@param  data             Description of the Parameter
196       *@exception  IOException  Description of the Exception
197       */
198      private static void dumpContinueRecord(Record last_record, boolean dump, byte[] data) throws IOException {
199          if (last_record == null) {
200              throw new RecordFormatException(
201                      "First record is a ContinueRecord??");
202          }
203          if (dump) {
204              System.out.println(
205                      "-----PRECONTINUED LAST RECORD WOULD SERIALIZE LIKE:");
206              byte[] lr = last_record.serialize();
207  
208              if (lr != null) {
209                  HexDump.dump(last_record.serialize(),
210                          0, System.out, 0);
211              }
212              System.out.println();
213              System.out.println(
214                      "-----PRECONTINUED----------------------------------");
215          }
216          last_record.processContinueRecord(data);
217          if (dump) {
218              System.out.println(
219                      "-----CONTINUED LAST RECORD WOULD SERIALIZE LIKE:");
220              HexDump.dump(last_record.serialize(), 0,
221                      System.out, 0);
222              System.out.println();
223              System.out.println(
224                      "-----CONTINUED----------------------------------");
225          }
226      }
227  
228  
229      /**
230       *  Description of the Method
231       *
232       *@param  data             Description of the Parameter
233       *@exception  IOException  Description of the Exception
234       */
235      private static void dumpUnknownRecord(byte[] data) throws IOException {
236          // record hex dump it!
237          System.out.println(
238                  "-----UNKNOWN----------------------------------");
239          if (data.length > 0) {
240              HexDump.dump(data, 0, System.out, 0);
241          } else {
242              System.out.print("**NO RECORD DATA**");
243          }
244          System.out.println();
245          System.out.println(
246                  "-----UNKNOWN----------------------------------");
247      }
248  
249  
250      private static void dump( short rectype, short recsize, byte[] data ) throws IOException
251      {
252          //                        System.out
253          //                            .println("fixing to recordize the following");
254          System.out.print( "rectype = 0x"
255                  + Integer.toHexString( rectype ) );
256          System.out.println( ", recsize = 0x"
257                  + Integer.toHexString( recsize ) );
258          System.out.println(
259                  "-BEGIN DUMP---------------------------------" );
260          if ( data.length > 0 )
261          {
262              HexDump.dump( data, 0, System.out, 0 );
263          }
264          else
265          {
266              System.out.println( "**NO RECORD DATA**" );
267          }
268          //        System.out.println();
269          System.out.println(
270                  "-END DUMP-----------------------------------" );
271      }
272  
273  
274      /**
275       *  Essentially a duplicate of RecordFactory. Kept seperate as not to screw
276       *  up non-debug operations.
277       *
278       *@param  rectype  Description of the Parameter
279       *@param  size     Description of the Parameter
280       *@param  data     Description of the Parameter
281       *@return          Description of the Return Value
282       */
283  
284      private static Record[] createRecord( short rectype, short size,
285                                            byte[] data )
286      {
287          Record retval = null;
288          Record[] realretval = null;
289  
290          // int irectype = rectype;
291          switch ( rectype )
292          {
293  
294              case ChartRecord.sid:
295                  retval = new ChartRecord( rectype, size, data );
296                  break;
297              case ChartFormatRecord.sid:
298                  retval = new ChartFormatRecord( rectype, size, data );
299                  break;
300              case SeriesRecord.sid:
301                  retval = new SeriesRecord( rectype, size, data );
302                  break;
303              case BeginRecord.sid:
304                  retval = new BeginRecord( rectype, size, data );
305                  break;
306              case EndRecord.sid:
307                  retval = new EndRecord( rectype, size, data );
308                  break;
309              case BOFRecord.sid:
310                  retval = new BOFRecord( rectype, size, data );
311                  break;
312              case InterfaceHdrRecord.sid:
313                  retval = new InterfaceHdrRecord( rectype, size, data );
314                  break;
315              case MMSRecord.sid:
316                  retval = new MMSRecord( rectype, size, data );
317                  break;
318              case InterfaceEndRecord.sid:
319                  retval = new InterfaceEndRecord( rectype, size, data );
320                  break;
321              case WriteAccessRecord.sid:
322                  retval = new WriteAccessRecord( rectype, size, data );
323                  break;
324              case CodepageRecord.sid:
325                  retval = new CodepageRecord( rectype, size, data );
326                  break;
327              case DSFRecord.sid:
328                  retval = new DSFRecord( rectype, size, data );
329                  break;
330              case TabIdRecord.sid:
331                  retval = new TabIdRecord( rectype, size, data );
332                  break;
333              case FnGroupCountRecord.sid:
334                  retval = new FnGroupCountRecord( rectype, size, data );
335                  break;
336              case WindowProtectRecord.sid:
337                  retval = new WindowProtectRecord( rectype, size, data );
338                  break;
339              case ProtectRecord.sid:
340                  retval = new ProtectRecord( rectype, size, data );
341                  break;
342              case PasswordRecord.sid:
343                  retval = new PasswordRecord( rectype, size, data );
344                  break;
345              case ProtectionRev4Record.sid:
346                  retval = new ProtectionRev4Record( rectype, size, data );
347                  break;
348              case PasswordRev4Record.sid:
349                  retval = new PasswordRev4Record( rectype, size, data );
350                  break;
351              case WindowOneRecord.sid:
352                  retval = new WindowOneRecord( rectype, size, data );
353                  break;
354              case BackupRecord.sid:
355                  retval = new BackupRecord( rectype, size, data );
356                  break;
357              case HideObjRecord.sid:
358                  retval = new HideObjRecord( rectype, size, data );
359                  break;
360              case DateWindow1904Record.sid:
361                  retval = new DateWindow1904Record( rectype, size, data );
362                  break;
363              case PrecisionRecord.sid:
364                  retval = new PrecisionRecord( rectype, size, data );
365                  break;
366              case RefreshAllRecord.sid:
367                  retval = new RefreshAllRecord( rectype, size, data );
368                  break;
369              case BookBoolRecord.sid:
370                  retval = new BookBoolRecord( rectype, size, data );
371                  break;
372              case FontRecord.sid:
373                  retval = new FontRecord( rectype, size, data );
374                  break;
375              case FormatRecord.sid:
376                  retval = new FormatRecord( rectype, size, data );
377                  break;
378              case ExtendedFormatRecord.sid:
379                  retval = new ExtendedFormatRecord( rectype, size, data );
380                  break;
381              case StyleRecord.sid:
382                  retval = new StyleRecord( rectype, size, data );
383                  break;
384              case UseSelFSRecord.sid:
385                  retval = new UseSelFSRecord( rectype, size, data );
386                  break;
387              case BoundSheetRecord.sid:
388                  retval = new BoundSheetRecord( rectype, size, data );
389                  break;
390              case CountryRecord.sid:
391                  retval = new CountryRecord( rectype, size, data );
392                  break;
393              case SSTRecord.sid:
394                  retval = new SSTRecord( rectype, size, data );
395                  break;
396              case ExtSSTRecord.sid:
397                  retval = new ExtSSTRecord( rectype, size, data );
398                  break;
399              case EOFRecord.sid:
400                  retval = new EOFRecord( rectype, size, data );
401                  break;
402              case IndexRecord.sid:
403                  retval = new IndexRecord( rectype, size, data );
404                  break;
405              case CalcModeRecord.sid:
406                  retval = new CalcModeRecord( rectype, size, data );
407                  break;
408              case CalcCountRecord.sid:
409                  retval = new CalcCountRecord( rectype, size, data );
410                  break;
411              case RefModeRecord.sid:
412                  retval = new RefModeRecord( rectype, size, data );
413                  break;
414              case IterationRecord.sid:
415                  retval = new IterationRecord( rectype, size, data );
416                  break;
417              case DeltaRecord.sid:
418                  retval = new DeltaRecord( rectype, size, data );
419                  break;
420              case SaveRecalcRecord.sid:
421                  retval = new SaveRecalcRecord( rectype, size, data );
422                  break;
423              case PrintHeadersRecord.sid:
424                  retval = new PrintHeadersRecord( rectype, size, data );
425                  break;
426              case PrintGridlinesRecord.sid:
427                  retval = new PrintGridlinesRecord( rectype, size, data );
428                  break;
429              case GridsetRecord.sid:
430                  retval = new GridsetRecord( rectype, size, data );
431                  break;
432              case GutsRecord.sid:
433                  retval = new GutsRecord( rectype, size, data );
434                  break;
435              case DefaultRowHeightRecord.sid:
436                  retval = new DefaultRowHeightRecord( rectype, size, data );
437                  break;
438              case WSBoolRecord.sid:
439                  retval = new WSBoolRecord( rectype, size, data );
440                  break;
441              case HeaderRecord.sid:
442                  retval = new HeaderRecord( rectype, size, data );
443                  break;
444              case FooterRecord.sid:
445                  retval = new FooterRecord( rectype, size, data );
446                  break;
447              case HCenterRecord.sid:
448                  retval = new HCenterRecord( rectype, size, data );
449                  break;
450              case VCenterRecord.sid:
451                  retval = new VCenterRecord( rectype, size, data );
452                  break;
453              case PrintSetupRecord.sid:
454                  retval = new PrintSetupRecord( rectype, size, data );
455                  break;
456              case DefaultColWidthRecord.sid:
457                  retval = new DefaultColWidthRecord( rectype, size, data );
458                  break;
459              case DimensionsRecord.sid:
460                  retval = new DimensionsRecord( rectype, size, data );
461                  break;
462              case RowRecord.sid:
463                  retval = new RowRecord( rectype, size, data );
464                  break;
465              case LabelSSTRecord.sid:
466                  retval = new LabelSSTRecord( rectype, size, data );
467                  break;
468              case RKRecord.sid:
469                  retval = new RKRecord( rectype, size, data );
470                  break;
471              case NumberRecord.sid:
472                  retval = new NumberRecord( rectype, size, data );
473                  break;
474              case DBCellRecord.sid:
475                  retval = new DBCellRecord( rectype, size, data );
476                  break;
477              case WindowTwoRecord.sid:
478                  retval = new WindowTwoRecord( rectype, size, data );
479                  break;
480              case SelectionRecord.sid:
481                  retval = new SelectionRecord( rectype, size, data );
482                  break;
483              case ContinueRecord.sid:
484                  retval = new ContinueRecord( rectype, size, data );
485                  break;
486              case LabelRecord.sid:
487                  retval = new LabelRecord( rectype, size, data );
488                  break;
489              case MulRKRecord.sid:
490                  retval = new MulRKRecord( rectype, size, data );
491                  break;
492              case MulBlankRecord.sid:
493                  retval = new MulBlankRecord( rectype, size, data );
494                  break;
495              case BlankRecord.sid:
496                  retval = new BlankRecord( rectype, size, data );
497                  break;
498              case BoolErrRecord.sid:
499                  retval = new BoolErrRecord( rectype, size, data );
500                  break;
501              case ColumnInfoRecord.sid:
502                  retval = new ColumnInfoRecord( rectype, size, data );
503                  break;
504              case MergeCellsRecord.sid:
505                  retval = new MergeCellsRecord( rectype, size, data );
506                  break;
507              case AreaRecord.sid:
508                  retval = new AreaRecord( rectype, size, data );
509                  break;
510              case DataFormatRecord.sid:
511                  retval = new DataFormatRecord( rectype, size, data );
512                  break;
513              case BarRecord.sid:
514                  retval = new BarRecord( rectype, size, data );
515                  break;
516              case DatRecord.sid:
517                  retval = new DatRecord( rectype, size, data );
518                  break;
519              case PlotGrowthRecord.sid:
520                  retval = new PlotGrowthRecord( rectype, size, data );
521                  break;
522              case UnitsRecord.sid:
523                  retval = new UnitsRecord( rectype, size, data );
524                  break;
525              case FrameRecord.sid:
526                  retval = new FrameRecord( rectype, size, data );
527                  break;
528              case ValueRangeRecord.sid:
529                  retval = new ValueRangeRecord( rectype, size, data );
530                  break;
531              case SeriesListRecord.sid:
532                  retval = new SeriesListRecord( rectype, size, data );
533                  break;
534              case FontBasisRecord.sid:
535                  retval = new FontBasisRecord( rectype, size, data );
536                  break;
537              case FontIndexRecord.sid:
538                  retval = new FontIndexRecord( rectype, size, data );
539                  break;
540              case LineFormatRecord.sid:
541                  retval = new LineFormatRecord( rectype, size, data );
542                  break;
543              case AreaFormatRecord.sid:
544                  retval = new AreaFormatRecord( rectype, size, data );
545                  break;
546              case LinkedDataRecord.sid:
547                  retval = new LinkedDataRecord( rectype, size, data );
548                  break;
549              case FormulaRecord.sid:
550                  retval = new FormulaRecord( rectype, size, data );
551                  break;
552              case SheetPropertiesRecord.sid:
553                  retval = new SheetPropertiesRecord( rectype, size, data );
554                  break;
555              case DefaultDataLabelTextPropertiesRecord.sid:
556                  retval = new DefaultDataLabelTextPropertiesRecord( rectype, size, data );
557                  break;
558              case TextRecord.sid:
559                  retval = new TextRecord( rectype, size, data );
560                  break;
561              case AxisParentRecord.sid:
562                  retval = new AxisParentRecord( rectype, size, data );
563                  break;
564              case AxisLineFormatRecord.sid:
565                  retval = new AxisLineFormatRecord( rectype, size, data );
566                  break;
567              case SupBookRecord.sid:
568                  retval = new SupBookRecord( rectype, size, data );
569                  break;
570              case ExternSheetRecord.sid:
571                  retval = new ExternSheetRecord( rectype, size, data );
572                  break;
573              case SCLRecord.sid:
574                  retval = new SCLRecord( rectype, size, data );
575                  break;
576              case SeriesToChartGroupRecord.sid:
577                  retval = new SeriesToChartGroupRecord( rectype, size, data );
578                  break;
579              case AxisUsedRecord.sid:
580                  retval = new AxisUsedRecord( rectype, size, data );
581                  break;
582              case AxisRecord.sid:
583                  retval = new AxisRecord( rectype, size, data );
584                  break;
585              case CategorySeriesAxisRecord.sid:
586                  retval = new CategorySeriesAxisRecord( rectype, size, data );
587                  break;
588              case AxisOptionsRecord.sid:
589                  retval = new AxisOptionsRecord( rectype, size, data );
590                  break;
591              case TickRecord.sid:
592                  retval = new TickRecord( rectype, size, data );
593                  break;
594              case SeriesTextRecord.sid:
595                  retval = new SeriesTextRecord( rectype, size, data );
596                  break;
597              case ObjectLinkRecord.sid:
598                  retval = new ObjectLinkRecord( rectype, size, data );
599                  break;
600              case PlotAreaRecord.sid:
601                  retval = new PlotAreaRecord( rectype, size, data );
602                  break;
603              case SeriesIndexRecord.sid:
604                  retval = new SeriesIndexRecord( rectype, size, data );
605                  break;
606              case LegendRecord.sid:
607                  retval = new LegendRecord( rectype, size, data );
608                  break;
609              case LeftMarginRecord.sid:
610                  retval = new LeftMarginRecord( rectype, size, data );
611                  break;
612              case RightMarginRecord.sid:
613                  retval = new RightMarginRecord( rectype, size, data );
614                  break;
615              case TopMarginRecord.sid:
616                  retval = new TopMarginRecord( rectype, size, data );
617                  break;
618              case BottomMarginRecord.sid:
619                  retval = new BottomMarginRecord( rectype, size, data );
620                  break;
621              case PaletteRecord.sid:
622                  retval = new PaletteRecord( rectype, size, data );
623                  break;
624              case StringRecord.sid:
625                  retval = new StringRecord( rectype, size, data );
626                  break;
627              case NameRecord.sid:
628                  retval = new NameRecord( rectype, size, data );
629                  break;
630              case PaneRecord.sid:
631                  retval = new PaneRecord( rectype, size, data );
632                  break;
633              default:
634                  retval = new UnknownRecord( rectype, size, data );
635          }
636          if ( realretval == null )
637          {
638              realretval = new Record[1];
639              realretval[0] = retval;
640              System.out.println( "recordid = 0x" + Integer.toHexString( rectype ) + ", size =" + size );
641              System.out.println( realretval[0].toString() );
642          }
643          return realretval;
644      }
645  
646  
647      /**
648       *  Method setDump - hex dump out data or not.
649       *
650       *@param  dump
651       */
652  
653      public void setDump(boolean dump) {
654          this.dump = dump;
655      }
656  
657  
658      /**
659       *  Method main with 1 argument just run straight biffview against given
660       *  file<P>
661       *
662       *  with 2 arguments where the second argument is "on" - run biffviewer<P>
663       *
664       *  with hex dumps of records <P>
665       *
666       *  with 2 arguments where the second argument is "bfd" just run a big fat
667       *  hex dump of the file...don't worry about biffviewing it at all
668       *
669       *@param  args
670       */
671  
672      public static void main(String[] args) {
673          try {
674              BiffViewer viewer = new BiffViewer(args);
675  
676              if ((args.length > 1) && args[1].equals("on")) {
677                  viewer.setDump(true);
678              }
679              if ((args.length > 1) && args[1].equals("bfd")) {
680                  POIFSFileSystem fs =
681                          new POIFSFileSystem(new FileInputStream(args[0]));
682                  InputStream stream =
683                          fs.createDocumentInputStream("Workbook");
684                  int size = stream.available();
685                  byte[] data = new byte[size];
686  
687                  stream.read(data);
688                  HexDump.dump(data, 0, System.out, 0);
689              } else {
690                  viewer.run();
691              }
692          } catch (Exception e) {
693              e.printStackTrace();
694          }
695      }
696  }
697