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