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   package org.apache.poi.hssf.model;
57   
58   import java.io.OutputStream;
59   
60   import java.util.List;
61   import java.util.ArrayList;
62   import java.util.Iterator;
63   
64   import org.apache.poi.util.POILogFactory;
65   import org.apache.poi.hssf
66       .record.*;       // normally I don't do this, buy we literally mean ALL
67   import org.apache.poi.hssf.record.formula.FormulaUtil;
68   import org.apache.poi.hssf.record.formula.Ptg;
69   import org.apache.poi.util.IntList;
70   import org.apache.poi.util.POILogger;
71   import org.apache.poi.hssf.record
72       .aggregates.*;   // normally I don't do this, buy we literally mean ALL
73   
74   /**
75    * Low level model implementation of a Sheet (one workbook contains many sheets)
76    * This file contains the low level binary records starting at the sheets BOF and
77    * ending with the sheets EOF.  Use HSSFSheet for a high level representation.
78    * <P>
79    * The structures of the highlevel API use references to this to perform most of their
80    * operations.  Its probably unwise to use these low level structures directly unless you
81    * really know what you're doing.  I recommend you read the Microsoft Excel 97 Developer's
82    * Kit (Microsoft Press) and the documentation at http://sc.openoffice.org/excelfileformat.pdf
83    * before even attempting to use this.
84    * <P>
85    * @author  Andrew C. Oliver (acoliver at apache dot org)
86    * @author  Glen Stampoultzis (glens at apache.org)
87    * @see org.apache.poi.hssf.model.Workbook
88    * @see org.apache.poi.hssf.usermodel.HSSFSheet
89    * @version 1.0-pre
90    */
91   
92   public class Sheet
93       extends java.lang.java.lang.Objecttected ArrayList              records        = null;
94       int                              preoffset      =
95           0;      // offset of the sheet in a new file
96       int                              loc            = 0;
97       protected boolean                containsLabels = false;
98       ;
99       protected int                    dimsloc        = 0;
100      protected DimensionsRecord       dims;
101      protected DefaultColWidthRecord  defaultcolwidth  = null;
102      protected DefaultRowHeightRecord defaultrowheight = null;
103      protected GridsetRecord          gridset          = null;
104      protected MergeCellsRecord       merged           = null;
105      protected int                    mergedloc        = 0;
106      private static POILogger         log              =
107          POILogFactory.getLogger(Sheet.class);
108      private ArrayList                columnSizes      =
109          null;   // holds column info
110      protected ValueRecordsAggregate  cells            = null;
111      protected RowRecordsAggregate    rows             = null;
112      private Iterator                 valueRecIterator = null;
113      private Iterator                 rowRecIterator   = null;
114  
115      /**
116       * Creates new Sheet with no intialization --useless at this point
117       * @see #createSheet(List,int,int)
118       */
119  
120      public Sheet()
121      {
122      }
123  
124      /**
125       * read support  (offset used as starting point for search) for low level
126       * API.  Pass in an array of Record objects, the sheet number (0 based) and
127       * a record offset (should be the location of the sheets BOF record).  A Sheet
128       * object is constructed and passed back with all of its initialization set
129       * to the passed in records and references to those records held. This function
130       * is normally called via Workbook.
131       *
132       * @param recs array containing those records in the sheet in sequence (normally obtained from RecordFactory)
133       * @param sheetnum integer specifying the sheet's number (0,1 or 2 in this release)
134       * @param offset of the sheet's BOF record
135       *
136       * @return Sheet object with all values set to those read from the file
137       *
138       * @see org.apache.poi.hssf.model.Workbook
139       * @see org.apache.poi.hssf.record.Record
140       */
141      public static Sheet createSheet(List recs, int sheetnum, int offset)
142      {
143          log.logFormatted(log.DEBUG,
144                           "Sheet createSheet (existing file) with %",
145                           new Integer(recs.size()));
146          Sheet     retval             = new Sheet();
147          ArrayList records            = new ArrayList(recs.size() / 5);
148          boolean   isfirstcell        = true;
149          boolean   isfirstrow         = true;
150          int       bofEofNestingLevel = 0;
151  
152          for (int k = offset; k < recs.size(); k++)
153          {
154              Record rec = ( Record ) recs.get(k);
155  
156              if (rec.getSid() == LabelRecord.sid)
157              {
158                  log.log(log.DEBUG, "Hit label record");
159                  retval.containsLabels = true;
160              }
161              else if (rec.getSid() == BOFRecord.sid)
162              {
163                  bofEofNestingLevel++;
164              }
165              else if ((rec.getSid() == EOFRecord.sid)
166                       && (--bofEofNestingLevel == 0))
167              {
168                  log.log(log.DEBUG, "Hit EOF record at ");
169                  records.add(rec);
170                  break;
171              }
172              else if (rec.getSid() == DimensionsRecord.sid)
173              {
174                  retval.dims    = ( DimensionsRecord ) rec;
175                  retval.dimsloc = records.size();
176              }
177              else if (rec.getSid() == MergeCellsRecord.sid)
178              {
179                  retval.merged    = ( MergeCellsRecord ) rec;
180                  retval.mergedloc = k - offset;
181              }
182              else if (rec.getSid() == ColumnInfoRecord.sid)
183              {
184                  if (retval.columnSizes == null)
185                  {
186                      retval.columnSizes = new ArrayList();
187                  }
188                  retval.columnSizes.add(( ColumnInfoRecord ) rec);
189              }
190              else if (rec.getSid() == DefaultColWidthRecord.sid)
191              {
192                  retval.defaultcolwidth = ( DefaultColWidthRecord ) rec;
193              }
194              else if (rec.getSid() == DefaultRowHeightRecord.sid)
195              {
196                  retval.defaultrowheight = ( DefaultRowHeightRecord ) rec;
197              }
198              else if (rec.isValue())
199              {
200                  if (isfirstcell)
201                  {
202                      retval.cells = new ValueRecordsAggregate();
203                      rec          = retval.cells;
204                      retval.cells.construct(k, recs);
205                      isfirstcell = false;
206                  }
207                  else
208                  {
209                      rec = null;
210                  }
211              }
212              else if (rec.getSid() == RowRecord.sid)
213              {
214                  if (isfirstrow)
215                  {
216                      retval.rows = new RowRecordsAggregate();
217                      rec         = retval.rows;
218                      retval.rows.construct(k, recs);
219                      isfirstrow = false;
220                  }
221                  else
222                  {
223                      rec = null;
224                  }
225              }
226              if (rec != null)
227              {
228                  records.add(rec);
229              }
230          }
231          retval.records = records;
232          log.log(log.DEBUG, "sheet createSheet (existing file) exited");
233          return retval;
234      }
235  
236      /**
237       * read support  (offset = 0) Same as createSheet(Record[] recs, int, int)
238       * only the record offset is assumed to be 0.
239       *
240       * @param records  array containing those records in the sheet in sequence (normally obtained from RecordFactory)
241       * @param sheetnum integer specifying the sheet's number (0,1 or 2 in this release)
242       * @return Sheet object
243       */
244  
245      public static Sheet createSheet(List records, int sheetnum)
246      {
247          log.log(log.DEBUG,
248                  "Sheet createSheet (exisiting file) assumed offset 0");
249          return createSheet(records, sheetnum, 0);
250      }
251  
252      /**
253       * Creates a sheet with all the usual records minus values and the "index"
254       * record (not required).  Sets the location pointer to where the first value
255       * records should go.  Use this to create a sheet from "scratch".
256       *
257       * @return Sheet object with all values set to defaults
258       */
259  
260      public static Sheet createSheet()
261      {
262          log.log(log.DEBUG, "Sheet createsheet from scratch called");
263          Sheet     retval  = new Sheet();
264          ArrayList records = new ArrayList(30);
265  
266          records.add(retval.createBOF());
267  
268          // records.add(retval.createIndex());
269          records.add(retval.createCalcMode());
270          records.add(retval.createCalcCount());
271          records.add(retval.createRefMode());
272          records.add(retval.createIteration());
273          records.add(retval.createDelta());
274          records.add(retval.createSaveRecalc());
275          records.add(retval.createPrintHeaders());
276          records.add(retval.createPrintGridlines());
277          retval.gridset = ( GridsetRecord ) retval.createGridset();
278          records.add(retval.gridset);
279          records.add(retval.createGuts());
280          retval.defaultrowheight =
281              ( DefaultRowHeightRecord ) retval.createDefaultRowHeight();
282          records.add(retval.defaultrowheight);
283          records.add(retval.createWSBool());
284          records.add(retval.createHeader());
285          records.add(retval.createFooter());
286          records.add(retval.createHCenter());
287          records.add(retval.createVCenter());
288          records.add(retval.createPrintSetup());
289          retval.defaultcolwidth =
290              ( DefaultColWidthRecord ) retval.createDefaultColWidth();
291          records.add(retval.defaultcolwidth);
292          retval.dims    = ( DimensionsRecord ) retval.createDimensions();
293          retval.dimsloc = 19;
294          records.add(retval.dims);
295          records.add(retval.createWindowTwo());
296          retval.setLoc(records.size() - 1);
297          records.add(retval.createSelection());
298          records.add(retval.createEOF());
299          retval.records = records;
300          log.log(log.DEBUG, "Sheet createsheet from scratch exit");
301          return retval;
302      }
303  
304      private void checkCells()
305      {
306          if (cells == null)
307          {
308              cells = new ValueRecordsAggregate();
309              records.add(getDimsLoc() + 1, cells);
310          }
311      }
312  
313      private void checkRows()
314      {
315          if (rows == null)
316          {
317              rows = new RowRecordsAggregate();
318              records.add(getDimsLoc() + 1, rows);
319          }
320      }
321  
322      public int addMergedRegion(short rowFrom, short colFrom, short rowTo,
323                                 short colTo)
324      {
325          if (merged == null)
326          {
327              merged    = ( MergeCellsRecord ) createMergedCells();
328              mergedloc = records.size() - 1;
329              records.add(records.size() - 1, merged);
330          }
331          return merged.addArea(rowFrom, colFrom, rowTo, colTo);
332      }
333  
334      public void removeMergedRegion(int index)
335      {
336          merged.removeAreaAt(index);
337          if (merged.getNumAreas() == 0)
338          {
339              merged = null;
340              records.remove(mergedloc);
341              mergedloc = 0;
342          }
343      }
344  
345      public MergeCellsRecord.MergedRegion getMergedRegionAt(int index)
346      {
347          return merged.getAreaAt(index);
348      }
349  
350      public int getNumMergedRegions()
351      {
352          return merged.getNumAreas();
353      }
354  
355      /**
356       * This is basically a kludge to deal with the now obsolete Label records.  If
357       * you have to read in a sheet that contains Label records, be aware that the rest
358       * of the API doesn't deal with them, the low level structure only provides read-only
359       * semi-immutable structures (the sets are there for interface conformance with NO
360       * impelmentation).  In short, you need to call this function passing it a reference
361       * to the Workbook object.  All labels will be converted to LabelSST records and their
362       * contained strings will be written to the Shared String tabel (SSTRecord) within
363       * the Workbook.
364       *
365       * @param wb sheet's matching low level Workbook structure containing the SSTRecord.
366       * @see org.apache.poi.hssf.record.LabelRecord
367       * @see org.apache.poi.hssf.record.LabelSSTRecord
368       * @see org.apache.poi.hssf.record.SSTRecord
369       */
370  
371      public void convertLabelRecords(Workbook wb)
372      {
373          log.log(log.DEBUG, "convertLabelRecords called");
374          if (containsLabels)
375          {
376              for (int k = 0; k < records.size(); k++)
377              {
378                  Record rec = ( Record ) records.get(k);
379  
380                  if (rec.getSid() == LabelRecord.sid)
381                  {
382                      LabelRecord oldrec = ( LabelRecord ) rec;
383  
384                      records.remove(k);
385                      LabelSSTRecord newrec   = new LabelSSTRecord();
386                      int            stringid =
387                          wb.addSSTString(oldrec.getValue());
388  
389                      newrec.setRow(oldrec.getRow());
390                      newrec.setColumn(oldrec.getColumn());
391                      newrec.setXFIndex(oldrec.getXFIndex());
392                      newrec.setSSTIndex(stringid);
393                      records.add(k, newrec);
394                  }
395              }
396          }
397          log.log(log.DEBUG, "convertLabelRecords exit");
398      }
399  
400      /**
401       * Returns the number of low level binary records in this sheet.  This adjusts things for the so called
402       * AgregateRecords.
403       *
404       * @see org.apache.poi.hssf.record.Record
405       */
406  
407      public int getNumRecords()
408      {
409          checkCells();
410          checkRows();
411          log.log(log.DEBUG, "Sheet.getNumRecords");
412          log.logFormatted(log.DEBUG, "returning % + % + % - 2 = %", new int[]
413          {
414              records.size(), cells.getPhysicalNumberOfCells(),
415              rows.getPhysicalNumberOfRows(),
416              records.size() + cells.getPhysicalNumberOfCells()
417              + rows.getPhysicalNumberOfRows() - 2
418          });
419          return records.size() + cells.getPhysicalNumberOfCells()
420                 + rows.getPhysicalNumberOfRows() - 2;
421      }
422  
423      /**
424       * Per an earlier reported bug in working with Andy Khan's excel read library.  This
425       * sets the values in the sheet's DimensionsRecord object to be correct.  Excel doesn't
426       * really care, but we want to play nice with other libraries.
427       *
428       * @see org.apache.poi.hssf.record.DimensionsRecord
429       */
430  
431      public void setDimensions(short firstrow, short firstcol, short lastrow,
432                                short lastcol)
433      {
434          log.log(log.DEBUG, "Sheet.setDimensions");
435          log.log(log.DEBUG,
436                  (new StringBuffer("firstrow")).append(firstrow)
437                      .append("firstcol").append(firstcol).append("lastrow")
438                      .append(lastrow).append("lastcol").append(lastcol)
439                      .toString());
440          dims.setFirstCol(firstcol);
441          dims.setFirstRow(firstrow);
442          dims.setLastCol(lastcol);
443          dims.setLastRow(lastrow);
444          log.log(log.DEBUG, "Sheet.setDimensions exiting");
445      }
446  
447      /**
448       * set the locator for where we should look for the next value record.  The
449       * algorythm will actually start here and find the correct location so you
450       * can set this to 0 and watch performance go down the tubes but it will work.
451       * After a value is set this is automatically advanced.  Its also set by the
452       * create method.  So you probably shouldn't mess with this unless you have
453       * a compelling reason why or the help for the method you're calling says so.
454       * Check the other methods for whether they care about
455       * the loc pointer.  Many of the "modify" and "remove" methods re-initialize this
456       * to "dimsloc" which is the location of the Dimensions Record and presumably the
457       * start of the value section (at or around 19 dec).
458       *
459       * @param loc the record number to start at
460       *
461       */
462  
463      public void setLoc(int loc)
464      {
465          valueRecIterator = null;
466          log.log(log.DEBUG, "sheet.setLoc(): " + loc);
467          this.loc = loc;
468      }
469  
470      /**
471       * Returns the location pointer to the first record to look for when adding rows/values
472       *
473       */
474  
475      public int getLoc()
476      {
477          log.log(log.DEBUG, "sheet.getLoc():" + loc);
478          return loc;
479      }
480  
481      /**
482       * Set the preoffset when using DBCELL records (currently unused) - this is
483       * the position of this sheet within the whole file.
484       *
485       * @param offset the offset of the sheet's BOF within the file.
486       */
487  
488      public void setPreOffset(int offset)
489      {
490          this.preoffset = offset;
491      }
492  
493      /**
494       * get the preoffset when using DBCELL records (currently unused) - this is
495       * the position of this sheet within the whole file.
496       *
497       * @return offset the offset of the sheet's BOF within the file.
498       */
499  
500      public int getPreOffset()
501      {
502          return preoffset;
503      }
504  
505      /**
506       * Serializes all records in the sheet into one big byte array.  Use this to write
507       * the sheet out.
508       *
509       * @return byte[] array containing the binary representation of the records in this sheet
510       *
511       */
512  
513      public byte [] serialize()
514      {
515          log.log(log.DEBUG, "Sheet.serialize");
516  
517          // addDBCellRecords();
518          byte[] retval    = null;
519  
520          // ArrayList bytes     = new ArrayList(4096);
521          int    arraysize = getSize();
522          int    pos       = 0;
523  
524          // for (int k = 0; k < records.size(); k++)
525          // {
526          // bytes.add((( Record ) records.get(k)).serialize());
527          //
528          // }
529          // for (int k = 0; k < bytes.size(); k++)
530          // {
531          // arraysize += (( byte [] ) bytes.get(k)).length;
532          // log.debug((new StringBuffer("arraysize=")).append(arraysize)
533          // .toString());
534          // }
535          retval = new byte[ arraysize ];
536          for (int k = 0; k < records.size(); k++)
537          {
538  
539              // byte[] rec = (( byte [] ) bytes.get(k));
540              // System.arraycopy(rec, 0, retval, pos, rec.length);
541              pos += (( Record ) records.get(k)).serialize(pos,
542                      retval);   // rec.length;
543          }
544          log.log(log.DEBUG, "Sheet.serialize returning " + retval);
545          return retval;
546      }
547  
548      /**
549       * Serializes all records in the sheet into one big byte array.  Use this to write
550       * the sheet out.
551       *
552       * @param offset to begin write at
553       * @param data   array containing the binary representation of the records in this sheet
554       *
555       */
556  
557      public int serialize(int offset, byte [] data)
558      {
559          log.log(log.DEBUG, "Sheet.serialize using offsets");
560  
561          // addDBCellRecords();
562          // ArrayList bytes     = new ArrayList(4096);
563          // int arraysize = getSize();   // 0;
564          int pos       = 0;
565  
566          // for (int k = 0; k < records.size(); k++)
567          // {
568          // bytes.add((( Record ) records.get(k)).serialize());
569          //
570          // }
571          // for (int k = 0; k < bytes.size(); k++)
572          // {
573          // arraysize += (( byte [] ) bytes.get(k)).length;
574          // log.debug((new StringBuffer("arraysize=")).append(arraysize)
575          // .toString());
576          // }
577          for (int k = 0; k < records.size(); k++)
578          {
579  
580              // byte[] rec = (( byte [] ) bytes.get(k));
581              // System.arraycopy(rec, 0, data, offset + pos, rec.length);
582              pos += (( Record ) records.get(k)).serialize(pos + offset,
583                      data);   // rec.length;
584          }
585          log.log(log.DEBUG, "Sheet.serialize returning ");
586          return pos;
587      }
588  
589      /**
590       * Create a row record.  (does not add it to the records contained in this sheet)
591       *
592       * @param row number
593       * @return RowRecord created for the passed in row number
594       * @see org.apache.poi.hssf.record.RowRecord
595       */
596  
597      public RowRecord createRow(int row)
598      {
599          log.log(log.DEBUG, "create row number " + row);
600          RowRecord rowrec = new RowRecord();
601  
602          rowrec.setRowNumber(( short ) row);
603          rowrec.setHeight(( short ) 0xff);
604          rowrec.setOptimize(( short ) 0x0);
605          rowrec.setOptionFlags(( short ) 0x0);
606          rowrec.setXFIndex(( short ) 0x0);
607          return rowrec;
608      }
609  
610      /**
611       * Create a LABELSST Record (does not add it to the records contained in this sheet)
612       *
613       * @param row the row the LabelSST is a member of
614       * @param col the column the LabelSST defines
615       * @param index the index of the string within the SST (use workbook addSSTString method)
616       * @return LabelSSTRecord newly created containing your SST Index, row,col.
617       * @see org.apache.poi.hssf.record.SSTRecord
618       */
619  
620      public LabelSSTRecord createLabelSST(short row, short col, int index)
621      {
622          log.logFormatted(log.DEBUG, "create labelsst row,col,index %,%,%",
623                           new int[]
624          {
625              row, col, index
626          });
627          LabelSSTRecord rec = new LabelSSTRecord();
628  
629          rec.setRow(row);
630          rec.setColumn(col);
631          rec.setSSTIndex(index);
632          rec.setXFIndex(( short ) 0x0f);
633          return rec;
634      }
635  
636      /**
637       * Create a NUMBER Record (does not add it to the records contained in this sheet)
638       *
639       * @param row the row the NumberRecord is a member of
640       * @param col the column the NumberRecord defines
641       * @param value for the number record
642       *
643       * @return NumberRecord for that row, col containing that value as added to the sheet
644       */
645  
646      public NumberRecord createNumber(short row, short col, double value)
647      {
648          log.logFormatted(log.DEBUG, "create number row,col,value %,%,%",
649                           new double[]
650          {
651              row, col, value
652          });
653          NumberRecord rec = new NumberRecord();
654  
655          rec.setRow(( short ) row);
656          rec.setColumn(col);
657          rec.setValue(value);
658          rec.setXFIndex(( short ) 0x0f);
659          return rec;
660      }
661  
662      /**
663       * create a BLANK record (does not add it to the records contained in this sheet)
664       *
665       * @param row - the row the BlankRecord is a member of
666       * @param col - the column the BlankRecord is a member of
667       */
668  
669      public BlankRecord createBlank(short row, short col)
670      {
671          log.logFormatted(log.DEBUG, "create blank row,col %,%", new short[]
672          {
673              row, col
674          });
675          BlankRecord rec = new BlankRecord();
676  
677          rec.setRow(( short ) row);
678          rec.setColumn(col);
679          rec.setXFIndex(( short ) 0x0f);
680          return rec;
681      }
682  
683      /**
684       * Attempts to parse the formula into PTGs and create a formula record
685       * DOES NOT WORK YET
686       *
687       * @param row - the row for the formula record
688       * @param col - the column of the formula record
689       * @param formula - a String representing the formula.  To be parsed to PTGs
690       * @return bogus/useless formula record
691       */
692  
693      public FormulaRecord createFormula(short row, short col, String formula)
694      {
695          log.logFormatted(log.DEBUG, "create formula row,col,formula %,%,%",
696                           new short[]
697          {
698              row, col
699          }, formula);
700          FormulaRecord rec = new FormulaRecord();
701  
702          rec.setRow(row);
703          rec.setColumn(col);
704          rec.setOptions(( short ) 2);
705          rec.setValue(0);
706          rec.setXFIndex(( short ) 0x0f);
707          Ptg[] ptg  = FormulaUtil.parseFormula(formula);
708          int   size = 0;
709  
710          for (int k = 0; k < ptg.length; k++)
711          {
712              size += ptg[ k ].getSize();
713              rec.pushExpressionToken(ptg[ k ]);
714          }
715          rec.setExpressionLength(( short ) size);
716          return rec;
717      }
718  
719      /**
720       * Adds a value record to the sheet's contained binary records
721       * (i.e. LabelSSTRecord or NumberRecord).
722       * <P>
723       * This method is "loc" sensitive.  Meaning you need to set LOC to where you
724       * want it to start searching.  If you don't know do this: setLoc(getDimsLoc).
725       * When adding several rows you can just start at the last one by leaving loc
726       * at what this sets it to.
727       *
728       * @param row the row to add the cell value to
729       * @param col the cell value record itself.
730       */
731  
732      public void addValueRecord(short row, CellValueRecordInterface col)
733      {
734          checkCells();
735          log.logFormatted(log.DEBUG, "add value record  row,loc %,%", new int[]
736          {
737              row, loc
738          });
739          DimensionsRecord d = ( DimensionsRecord ) records.get(getDimsLoc());
740  
741          if (col.getColumn() > d.getLastCol())
742          {
743              d.setLastCol(( short ) (col.getColumn() + 1));
744          }
745          if (col.getColumn() < d.getFirstCol())
746          {
747              d.setFirstCol(col.getColumn());
748          }
749          cells.insertCell(col);
750  
751          /*
752           * for (int k = loc; k < records.size(); k++)
753           * {
754           *   Record rec = ( Record ) records.get(k);
755           *
756           *   if (rec.getSid() == RowRecord.sid)
757           *   {
758           *       RowRecord rowrec = ( RowRecord ) rec;
759           *
760           *       if (rowrec.getRowNumber() == col.getRow())
761           *       {
762           *           records.add(k + 1, col);
763           *           loc = k;
764           *           if (rowrec.getLastCol() <= col.getColumn())
765           *           {
766           *               rowrec.setLastCol((( short ) (col.getColumn() + 1)));
767           *           }
768           *           break;
769           *       }
770           *   }
771           * }
772           */
773      }
774  
775      /**
776       * remove a value record from the records array.
777       *
778       * This method is not loc sensitive, it resets loc to = dimsloc so no worries.
779       *
780       * @param row - the row of the value record you wish to remove
781       * @param col - a record supporting the CellValueRecordInterface.
782       * @see org.apache.poi.hssf.record.CellValueRecordInterface
783       */
784  
785      public void removeValueRecord(short row, CellValueRecordInterface col)
786      {
787          checkCells();
788          log.logFormatted(log.DEBUG, "remove value record row,dimsloc %,%",
789                           new int[]
790          {
791              row, dimsloc
792          });
793          loc = dimsloc;
794          cells.removeCell(col);
795  
796          /*
797           * for (int k = loc; k < records.size(); k++)
798           * {
799           *   Record rec = ( Record ) records.get(k);
800           *
801           *   // checkDimsLoc(rec,k);
802           *   if (rec.isValue())
803           *   {
804           *       CellValueRecordInterface cell =
805           *           ( CellValueRecordInterface ) rec;
806           *
807           *       if ((cell.getRow() == col.getRow())
808           *               && (cell.getColumn() == col.getColumn()))
809           *       {
810           *           records.remove(k);
811           *           break;
812           *       }
813           *   }
814           * }
815           */
816      }
817  
818      /**
819       * replace a value record from the records array.
820       *
821       * This method is not loc sensitive, it resets loc to = dimsloc so no worries.
822       *
823       * @param newval - a record supporting the CellValueRecordInterface.  this will replace
824       *                the cell value with the same row and column.  If there isn't one, one will
825       *                be added.
826       */
827  
828      public void replaceValueRecord(CellValueRecordInterface newval)
829      {
830          checkCells();
831          setLoc(dimsloc);
832          log.log(log.DEBUG, "replaceValueRecord ");
833          cells.insertCell(newval);
834  
835          /*
836           * CellValueRecordInterface oldval = getNextValueRecord();
837           *
838           * while (oldval != null)
839           * {
840           *   if (oldval.isEqual(newval))
841           *   {
842           *       records.set(( short ) (getLoc() - 1), newval);
843           *       return;
844           *   }
845           *   oldval = getNextValueRecord();
846           * }
847           * addValueRecord(newval.getRow(), newval);
848           * setLoc(dimsloc);
849           */
850      }
851  
852      /**
853       * Adds a row record to the sheet
854       *
855       * <P>
856       * This method is "loc" sensitive.  Meaning you need to set LOC to where you
857       * want it to start searching.  If you don't know do this: setLoc(getDimsLoc).
858       * When adding several rows you can just start at the last one by leaving loc
859       * at what this sets it to.
860       *
861       * @param row the row record to be added
862       * @see #setLoc(int)
863       */
864  
865      public void addRow(RowRecord row)
866      {
867          checkRows();
868          log.log(log.DEBUG, "addRow ");
869          DimensionsRecord d = ( DimensionsRecord ) records.get(getDimsLoc());
870  
871          if (row.getRowNumber() > d.getLastRow())
872          {
873              d.setLastRow(row.getRowNumber() + 1);
874          }
875          if (row.getRowNumber() < d.getFirstRow())
876          {
877              d.setFirstRow(row.getRowNumber());
878          }
879          //IndexRecord index = null;
880  
881          rows.insertRow(row);
882  
883          /*
884           * for (int k = loc; k < records.size(); k++)
885           * {
886           *   Record rec = ( Record ) records.get(k);
887           *
888           *   if (rec.getSid() == IndexRecord.sid)
889           *   {
890           *       index = ( IndexRecord ) rec;
891           *   }
892           *   if (rec.getSid() == RowRecord.sid)
893           *   {
894           *       RowRecord rowrec = ( RowRecord ) rec;
895           *
896           *       if (rowrec.getRowNumber() > row.getRowNumber())
897           *       {
898           *           records.add(k, row);
899           *           loc = k;
900           *           break;
901           *       }
902           *   }
903           *   if (rec.getSid() == WindowTwoRecord.sid)
904           *   {
905           *       records.add(k, row);
906           *       loc = k;
907           *       break;
908           *   }
909           * }
910           * if (index != null)
911           * {
912           *   if (index.getLastRowAdd1() <= row.getRowNumber())
913           *   {
914           *       index.setLastRowAdd1(row.getRowNumber() + 1);
915           *   }
916           * }
917           */
918          log.log(log.DEBUG, "exit addRow");
919      }
920  
921      /**
922       * Removes a row record
923       *
924       * This method is not loc sensitive, it resets loc to = dimsloc so no worries.
925       *
926       * @param row  the row record to remove
927       */
928  
929      public void removeRow(RowRecord row)
930      {
931          checkRows();
932          // IndexRecord index = null;
933  
934          setLoc(getDimsLoc());
935          rows.removeRow(row);
936  
937          /*
938           * for (int k = loc; k < records.size(); k++)
939           * {
940           *   Record rec = ( Record ) records.get(k);
941           *
942           *   // checkDimsLoc(rec,k);
943           *   if (rec.getSid() == RowRecord.sid)
944           *   {
945           *       RowRecord rowrec = ( RowRecord ) rec;
946           *
947           *       if (rowrec.getRowNumber() == row.getRowNumber())
948           *       {
949           *           records.remove(k);
950           *           break;
951           *       }
952           *   }
953           *   if (rec.getSid() == WindowTwoRecord.sid)
954           *   {
955           *       break;
956           *   }
957           * }
958           */
959      }
960  
961      /**
962       * get the NEXT value record (from LOC).  The first record that is a value record
963       * (starting at LOC) will be returned.
964       *
965       * <P>
966       * This method is "loc" sensitive.  Meaning you need to set LOC to where you
967       * want it to start searching.  If you don't know do this: setLoc(getDimsLoc).
968       * When adding several rows you can just start at the last one by leaving loc
969       * at what this sets it to.  For this method, set loc to dimsloc to start with,
970       * subsequent calls will return values in (physical) sequence or NULL when you get to the end.
971       *
972       * @return CellValueRecordInterface representing the next value record or NULL if there are no more
973       * @see #setLoc(int)
974       */
975  
976      public CellValueRecordInterface getNextValueRecord()
977      {
978          log.log(log.DEBUG, "getNextValue loc= " + loc);
979          if (valueRecIterator == null)
980          {
981              valueRecIterator = cells.getIterator();
982          }
983          if (!valueRecIterator.hasNext())
984          {
985              return null;
986          }
987          return ( CellValueRecordInterface ) valueRecIterator.next();
988  
989          /*
990           *      if (this.getLoc() < records.size())
991           *     {
992           *         for (int k = getLoc(); k < records.size(); k++)
993           *         {
994           *             Record rec = ( Record ) records.get(k);
995           *
996           *             this.setLoc(k + 1);
997           *             if (rec instanceof CellValueRecordInterface)
998           *             {
999           *                 return ( CellValueRecordInterface ) rec;
1000          *             }
1001          *         }
1002          *     }
1003          *     return null;
1004          */
1005     }
1006 
1007     /**
1008      * get the NEXT RowRecord or CellValueRecord(from LOC).  The first record that
1009      * is a Row record or CellValueRecord(starting at LOC) will be returned.
1010      * <P>
1011      * This method is "loc" sensitive.  Meaning you need to set LOC to where you
1012      * want it to start searching.  If you don't know do this: setLoc(getDimsLoc).
1013      * When adding several rows you can just start at the last one by leaving loc
1014      * at what this sets it to.  For this method, set loc to dimsloc to start with.
1015      * subsequent calls will return rows in (physical) sequence or NULL when you get to the end.
1016      *
1017      * @return RowRecord representing the next row record or CellValueRecordInterface
1018      *  representing the next cellvalue or NULL if there are no more
1019      * @see #setLoc(int)
1020      *
1021      */
1022 
1023 /*    public Record getNextRowOrValue()
1024     {
1025         log.debug((new StringBuffer("getNextRow loc= ")).append(loc)
1026             .toString());
1027         if (this.getLoc() < records.size())
1028         {
1029             for (int k = this.getLoc(); k < records.size(); k++)
1030             {
1031                 Record rec = ( Record ) records.get(k);
1032 
1033                 this.setLoc(k + 1);
1034                 if (rec.getSid() == RowRecord.sid)
1035                 {
1036                     return rec;
1037                 }
1038                 else if (rec.isValue())
1039                 {
1040                     return rec;
1041                 }
1042             }
1043         }
1044         return null;
1045     }
1046  */
1047 
1048     /**
1049      * get the NEXT RowRecord (from LOC).  The first record that is a Row record
1050      * (starting at LOC) will be returned.
1051      * <P>
1052      * This method is "loc" sensitive.  Meaning you need to set LOC to where you
1053      * want it to start searching.  If you don't know do this: setLoc(getDimsLoc).
1054      * When adding several rows you can just start at the last one by leaving loc
1055      * at what this sets it to.  For this method, set loc to dimsloc to start with.
1056      * subsequent calls will return rows in (physical) sequence or NULL when you get to the end.
1057      *
1058      * @return RowRecord representing the next row record or NULL if there are no more
1059      * @see #setLoc(int)
1060      *
1061      */
1062 
1063     public RowRecord getNextRow()
1064     {
1065         log.log(log.DEBUG, "getNextRow loc= " + loc);
1066         if (rowRecIterator == null)
1067         {
1068             rowRecIterator = rows.getIterator();
1069         }
1070         if (!rowRecIterator.hasNext())
1071         {
1072             return null;
1073         }
1074         return ( RowRecord ) rowRecIterator.next();
1075 
1076 /*        if (this.getLoc() < records.size())
1077         {
1078             for (int k = this.getLoc(); k < records.size(); k++)
1079             {
1080                 Record rec = ( Record ) records.get(k);
1081 
1082                 this.setLoc(k + 1);
1083                 if (rec.getSid() == RowRecord.sid)
1084                 {
1085                     return ( RowRecord ) rec;
1086                 }
1087             }
1088         }*/
1089     }
1090 
1091     /**
1092      * get the NEXT (from LOC) RowRecord where rownumber matches the given rownum.
1093      * The first record that is a Row record (starting at LOC) that has the
1094      * same rownum as the given rownum will be returned.
1095      * <P>
1096      * This method is "loc" sensitive.  Meaning you need to set LOC to where you
1097      * want it to start searching.  If you don't know do this: setLoc(getDimsLoc).
1098      * When adding several rows you can just start at the last one by leaving loc
1099      * at what this sets it to.  For this method, set loc to dimsloc to start with.
1100      * subsequent calls will return rows in (physical) sequence or NULL when you get to the end.
1101      *
1102      * @param rownum   which row to return (careful with LOC)
1103      * @return RowRecord representing the next row record or NULL if there are no more
1104      * @see #setLoc(int)
1105      *
1106      */
1107 
1108     public RowRecord getRow(short rownum)
1109     {
1110         log.log(log.DEBUG, "getNextRow loc= " + loc);
1111         return rows.getRow(rownum);
1112 
1113         /*
1114          * if (this.getLoc() < records.size())
1115          * {
1116          *   for (int k = this.getLoc(); k < records.size(); k++)
1117          *   {
1118          *       Record rec = ( Record ) records.get(k);
1119          *
1120          *       this.setLoc(k + 1);
1121          *       if (rec.getSid() == RowRecord.sid)
1122          *       {
1123          *           if ((( RowRecord ) rec).getRowNumber() == rownum)
1124          *           {
1125          *               return ( RowRecord ) rec;
1126          *           }
1127          *       }
1128          *   }
1129          * }
1130          */
1131 
1132         // return null;
1133     }
1134 
1135     /**
1136      * Not currently used method to calculate and add dbcell records
1137      *
1138      */
1139 
1140     public void addDBCellRecords()
1141     {
1142         int         offset        = 0;
1143         int         recnum        = 0;
1144         int         rownum        = 0;
1145         //int         lastrow       = 0;
1146         //long        lastrowoffset = 0;
1147         IndexRecord index         = null;
1148 
1149         // ArrayList rowOffsets = new ArrayList();
1150         IntList     rowOffsets    = new IntList();
1151 
1152         for (recnum = 0; recnum < records.size(); recnum++)
1153         {
1154             Record rec = ( Record ) records.get(recnum);
1155 
1156             if (rec.getSid() == IndexRecord.sid)
1157             {
1158                 index = ( IndexRecord ) rec;
1159             }
1160             if (rec.getSid() != RowRecord.sid)
1161             {
1162                 offset += rec.serialize().length;
1163             }
1164             else
1165             {
1166                 break;
1167             }
1168         }
1169 
1170         // First Row Record
1171         for (; recnum < records.size(); recnum++)
1172         {
1173             Record rec = ( Record ) records.get(recnum);
1174 
1175             if (rec.getSid() == RowRecord.sid)
1176             {
1177                 rownum++;
1178                 rowOffsets.add(offset);
1179                 if ((rownum % 32) == 0)
1180                 {
1181 
1182                     // if this is the last rec in a  dbcell block
1183                     // find the next row or last value record
1184                     for (int rn = recnum; rn < records.size(); rn++)
1185                     {
1186                         rec = ( Record ) records.get(rn);
1187                         if ((!rec.isInValueSection())
1188                                 || (rec.getSid() == RowRecord.sid))
1189                         {
1190 
1191                             // here is the next row or last value record
1192                             records.add(rn,
1193                                         createDBCell(offset, rowOffsets,
1194                                                      index));
1195                             recnum = rn;
1196                             break;
1197                         }
1198                     }
1199                 }
1200                 else
1201                 {
1202                 }
1203             }
1204             if (!rec.isInValueSection())
1205             {
1206                 records.add(recnum, createDBCell(offset, rowOffsets, index));
1207                 break;
1208             }
1209             offset += rec.serialize().length;
1210         }
1211     }
1212 
1213     /** not currently used */
1214 
1215     private DBCellRecord createDBCell(int offset, IntList rowoffsets,
1216                                       IndexRecord index)
1217     {
1218         DBCellRecord rec = new DBCellRecord();
1219 
1220         rec.setRowOffset(offset - rowoffsets.get(0));
1221 
1222         // test hack
1223         rec.addCellOffset(( short ) 0x0);
1224 
1225         // end test hack
1226         addDbCellToIndex(offset, index);
1227         return rec;
1228     }
1229 
1230     /** not currently used */
1231 
1232     private void addDbCellToIndex(int offset, IndexRecord index)
1233     {
1234         int numdbcells = index.getNumDbcells() + 1;
1235 
1236         index.addDbcell(offset + preoffset);
1237 
1238         // stupid but whenever we add an offset that causes everything to be shifted down 4
1239         for (int k = 0; k < numdbcells; k++)
1240         {
1241             int dbval = index.getDbcellAt(k);
1242 
1243             index.setDbcell(k, dbval + 4);
1244         }
1245     }
1246 
1247     /**
1248      * creates the BOF record
1249      * @see org.apache.poi.hssf.record.BOFRecord
1250      * @see org.apache.poi.hssf.record.Record
1251      * @return record containing a BOFRecord
1252      */
1253 
1254     protected Record createBOF()
1255     {
1256         BOFRecord retval = new BOFRecord();
1257 
1258         retval.setVersion(( short ) 0x600);
1259         retval.setType(( short ) 0x010);
1260 
1261         // retval.setBuild((short)0x10d3);
1262         retval.setBuild(( short ) 0x0dbb);
1263         retval.setBuildYear(( short ) 1996);
1264         retval.setHistoryBitMask(0xc1);
1265         retval.setRequiredVersion(0x6);
1266         return retval;
1267     }
1268 
1269     /**
1270      * creates the Index record  - not currently used
1271      * @see org.apache.poi.hssf.record.IndexRecord
1272      * @see org.apache.poi.hssf.record.Record
1273      * @return record containing a IndexRecord
1274      */
1275 
1276     protected Record createIndex()
1277     {
1278         IndexRecord retval = new IndexRecord();
1279 
1280         retval.setFirstRow(0);   // must be set explicitly
1281         retval.setLastRowAdd1(0);
1282         return retval;
1283     }
1284 
1285     /**
1286      * creates the CalcMode record and sets it to 1 (automatic formula caculation)
1287      * @see org.apache.poi.hssf.record.CalcModeRecord
1288      * @see org.apache.poi.hssf.record.Record
1289      * @return record containing a CalcModeRecord
1290      */
1291 
1292     protected Record createCalcMode()
1293     {
1294         CalcModeRecord retval = new CalcModeRecord();
1295 
1296         retval.setCalcMode(( short ) 1);
1297         return retval;
1298     }
1299 
1300     /**
1301      * creates the CalcCount record and sets it to 0x64 (default number of iterations)
1302      * @see org.apache.poi.hssf.record.CalcCountRecord
1303      * @see org.apache.poi.hssf.record.Record
1304      * @return record containing a CalcCountRecord
1305      */
1306 
1307     protected Record createCalcCount()
1308     {
1309         CalcCountRecord retval = new CalcCountRecord();
1310 
1311         retval.setIterations(( short ) 0x64);   // default 64 iterations
1312         return retval;
1313     }
1314 
1315     /**
1316      * creates the RefMode record and sets it to A1 Mode (default reference mode)
1317      * @see org.apache.poi.hssf.record.RefModeRecord
1318      * @see org.apache.poi.hssf.record.Record
1319      * @return record containing a RefModeRecord
1320      */
1321 
1322     protected Record createRefMode()
1323     {
1324         RefModeRecord retval = new RefModeRecord();
1325 
1326         retval.setMode(retval.USE_A1_MODE);
1327         return retval;
1328     }
1329 
1330     /**
1331      * creates the Iteration record and sets it to false (don't iteratively calculate formulas)
1332      * @see org.apache.poi.hssf.record.IterationRecord
1333      * @see org.apache.poi.hssf.record.Record
1334      * @return record containing a IterationRecord
1335      */
1336 
1337     protected Record createIteration()
1338     {
1339         IterationRecord retval = new IterationRecord();
1340 
1341         retval.setIteration(false);
1342         return retval;
1343     }
1344 
1345     /**
1346      * creates the Delta record and sets it to 0.0010 (default accuracy)
1347      * @see org.apache.poi.hssf.record.DeltaRecord
1348      * @see org.apache.poi.hssf.record.Record
1349      * @return record containing a DeltaRecord
1350      */
1351 
1352     protected Record createDelta()
1353     {
1354         DeltaRecord retval = new DeltaRecord();
1355 
1356         retval.setMaxChange((( double ) 0.0010));
1357         return retval;
1358     }
1359 
1360     /**
1361      * creates the SaveRecalc record and sets it to true (recalculate before saving)
1362      * @see org.apache.poi.hssf.record.SaveRecalcRecord
1363      * @see org.apache.poi.hssf.record.Record
1364      * @return record containing a SaveRecalcRecord
1365      */
1366 
1367     protected Record createSaveRecalc()
1368     {
1369         SaveRecalcRecord retval = new SaveRecalcRecord();
1370 
1371         retval.setRecalc(true);
1372         return retval;
1373     }
1374 
1375     /**
1376      * creates the PrintHeaders record and sets it to false (we don't create headers yet so why print them)
1377      * @see org.apache.poi.hssf.record.PrintHeadersRecord
1378      * @see org.apache.poi.hssf.record.Record
1379      * @return record containing a PrintHeadersRecord
1380      */
1381 
1382     protected Record createPrintHeaders()
1383     {
1384         PrintHeadersRecord retval = new PrintHeadersRecord();
1385 
1386         retval.setPrintHeaders(false);
1387         return retval;
1388     }
1389 
1390     /**
1391      * creates the PrintGridlines record and sets it to false (that makes for ugly sheets).  As far as I can
1392      * tell this does the same thing as the GridsetRecord
1393      *
1394      * @see org.apache.poi.hssf.record.PrintGridlinesRecord
1395      * @see org.apache.poi.hssf.record.Record
1396      * @return record containing a PrintGridlinesRecord
1397      */
1398 
1399     protected Record createPrintGridlines()
1400     {
1401         PrintGridlinesRecord retval = new PrintGridlinesRecord();
1402 
1403         retval.setPrintGridlines(false);
1404         return retval;
1405     }
1406 
1407     /**
1408      * creates the Gridset record and sets it to true (user has mucked with the gridlines)
1409      * @see org.apache.poi.hssf.record.GridsetRecord
1410      * @see org.apache.poi.hssf.record.Record
1411      * @return record containing a GridsetRecord
1412      */
1413 
1414     protected Record createGridset()
1415     {
1416         GridsetRecord retval = new GridsetRecord();
1417 
1418         retval.setGridset(true);
1419         return retval;
1420     }
1421 
1422     /**
1423      * creates the Guts record and sets leftrow/topcol guttter and rowlevelmax/collevelmax to 0
1424      * @see org.apache.poi.hssf.record.GutsRecord
1425      * @see org.apache.poi.hssf.record.Record
1426      * @return record containing a GutsRecordRecord
1427      */
1428 
1429     protected Record createGuts()
1430     {
1431         GutsRecord retval = new GutsRecord();
1432 
1433         retval.setLeftRowGutter(( short ) 0);
1434         retval.setTopColGutter(( short ) 0);
1435         retval.setRowLevelMax(( short ) 0);
1436         retval.setColLevelMax(( short ) 0);
1437         return retval;
1438     }
1439 
1440     /**
1441      * creates the DefaultRowHeight Record and sets its options to 0 and rowheight to 0xff
1442      * @see org.apache.poi.hssf.record.DefaultRowHeightRecord
1443      * @see org.apache.poi.hssf.record.Record
1444      * @return record containing a DefaultRowHeightRecord
1445      */
1446 
1447     protected Record createDefaultRowHeight()
1448     {
1449         DefaultRowHeightRecord retval = new DefaultRowHeightRecord();
1450 
1451         retval.setOptionFlags(( short ) 0);
1452         retval.setRowHeight(( short ) 0xff);
1453         return retval;
1454     }
1455 
1456     /**
1457      * creates the WSBoolRecord and sets its values to defaults
1458      * @see org.apache.poi.hssf.record.WSBoolRecord
1459      * @see org.apache.poi.hssf.record.Record
1460      * @return record containing a WSBoolRecord
1461      */
1462 
1463     protected Record createWSBool()
1464     {
1465         WSBoolRecord retval = new WSBoolRecord();
1466 
1467         retval.setWSBool1(( byte ) 0x4);
1468         retval.setWSBool2(( byte ) 0xffffffc1);
1469         return retval;
1470     }
1471 
1472     /**
1473      * creates the Header Record and sets it to nothing/0 length
1474      * @see org.apache.poi.hssf.record.HeaderRecord
1475      * @see org.apache.poi.hssf.record.Record
1476      * @return record containing a HeaderRecord
1477      */
1478 
1479     protected Record createHeader()
1480     {
1481         HeaderRecord retval = new HeaderRecord();
1482 
1483         retval.setHeaderLength(( byte ) 0);
1484         retval.setHeader(null);
1485         return retval;
1486     }
1487 
1488     /**
1489      * creates the Footer Record and sets it to nothing/0 length
1490      * @see org.apache.poi.hssf.record.FooterRecord
1491      * @see org.apache.poi.hssf.record.Record
1492      * @return record containing a FooterRecord
1493      */
1494 
1495     protected Record createFooter()
1496     {
1497         FooterRecord retval = new FooterRecord();
1498 
1499         retval.setFooterLength(( byte ) 0);
1500         retval.setFooter(null);
1501         return retval;
1502     }
1503 
1504     /**
1505      * creates the HCenter Record and sets it to false (don't horizontally center)
1506      * @see org.apache.poi.hssf.record.HCenterRecord
1507      * @see org.apache.poi.hssf.record.Record
1508      * @return record containing a HCenterRecord
1509      */
1510 
1511     protected Record createHCenter()
1512     {
1513         HCenterRecord retval = new HCenterRecord();
1514 
1515         retval.setHCenter(false);
1516         return retval;
1517     }
1518 
1519     /**
1520      * creates the VCenter Record and sets it to false (don't horizontally center)
1521      * @see org.apache.poi.hssf.record.VCenterRecord
1522      * @see org.apache.poi.hssf.record.Record
1523      * @return record containing a VCenterRecord
1524      */
1525 
1526     protected Record createVCenter()
1527     {
1528         VCenterRecord retval = new VCenterRecord();
1529 
1530         retval.setVCenter(false);
1531         return retval;
1532     }
1533 
1534     /**
1535      * creates the PrintSetup Record and sets it to defaults and marks it invalid
1536      * @see org.apache.poi.hssf.record.PrintSetupRecord
1537      * @see org.apache.poi.hssf.record.Record
1538      * @return record containing a PrintSetupRecord
1539      */
1540 
1541     protected Record createPrintSetup()
1542     {
1543         PrintSetupRecord retval = new PrintSetupRecord();
1544 
1545         retval.setPaperSize(( short ) 1);
1546         retval.setScale(( short ) 100);
1547         retval.setPageStart(( short ) 1);
1548         retval.setFitWidth(( short ) 1);
1549         retval.setFitHeight(( short ) 1);
1550         retval.setOptions(( short ) 2);
1551         retval.setHResolution(( short ) 300);
1552         retval.setVResolution(( short ) 300);
1553         retval.setHeaderMargin(( double ) 0.5);
1554         retval.setFooterMargin(( double ) 0.5);
1555         retval.setCopies(( short ) 0);
1556         return retval;
1557     }
1558 
1559     /**
1560      * creates the DefaultColWidth Record and sets it to 8
1561      * @see org.apache.poi.hssf.record.DefaultColWidthRecord
1562      * @see org.apache.poi.hssf.record.Record
1563      * @return record containing a DefaultColWidthRecord
1564      */
1565 
1566     protected Record createDefaultColWidth()
1567     {
1568         DefaultColWidthRecord retval = new DefaultColWidthRecord();
1569 
1570         retval.setColWidth(( short ) 8);
1571         return retval;
1572     }
1573 
1574     /**
1575      * creates the ColumnInfo Record and sets it to a default column/width
1576      * @see org.apache.poi.hssf.record.ColumnInfoRecord
1577      * @return record containing a ColumnInfoRecord
1578      */
1579 
1580     protected Record createColInfo()
1581     {
1582         ColumnInfoRecord retval = new ColumnInfoRecord();
1583 
1584         retval.setColumnWidth(( short ) 0x8);
1585         retval.setOptions(( short ) 6);
1586         retval.setXFIndex(( short ) 0x0f);
1587         return retval;
1588     }
1589 
1590     /**
1591      * get the default column width for the sheet (if the columns do not define their own width)
1592      * @return default column width
1593      */
1594 
1595     public short getDefaultColumnWidth()
1596     {
1597         return defaultcolwidth.getColWidth();
1598     }
1599 
1600     /**
1601      * get whether gridlines are printed.
1602      * @return true if printed
1603      */
1604 
1605     public boolean isGridsPrinted()
1606     {
1607         return !gridset.getGridset();
1608     }
1609 
1610     /**
1611      * set whether gridlines printed or not.
1612      * @param value     True if gridlines printed.
1613      */
1614 
1615     public void setGridsPrinted(boolean value)
1616     {
1617         gridset.setGridset(!value);
1618     }
1619 
1620     /**
1621      * set the default column width for the sheet (if the columns do not define their own width)
1622      * @param dcw  default column width
1623      */
1624 
1625     public void setDefaultColumnWidth(short dcw)
1626     {
1627         defaultcolwidth.setColWidth(dcw);
1628     }
1629 
1630     /**
1631      * set the default row height for the sheet (if the rows do not define their own height)
1632      */
1633 
1634     public void setDefaultRowHeight(short dch)
1635     {
1636         defaultrowheight.setRowHeight(dch);
1637     }
1638 
1639     /**
1640      * get the default row height for the sheet (if the rows do not define their own height)
1641      * @return  default row height
1642      */
1643 
1644     public short getDefaultRowHeight()
1645     {
1646         return defaultrowheight.getRowHeight();
1647     }
1648 
1649     /**
1650      * get the width of a given column in units of 1/20th of a point width (twips?)
1651      * @param column index
1652      * @see org.apache.poi.hssf.record.DefaultColWidthRecord
1653      * @see org.apache.poi.hssf.record.ColumnInfoRecord
1654      * @see #setColumnWidth(short,short)
1655      * @return column width in units of 1/20th of a point (twips?)
1656      */
1657 
1658     public short getColumnWidth(short column)
1659     {
1660         short            retval = 0;
1661         ColumnInfoRecord ci     = null;
1662         int              k      = 0;
1663 
1664         if (columnSizes != null)
1665         {
1666             for (k = 0; k < columnSizes.size(); k++)
1667             {
1668                 ci = ( ColumnInfoRecord ) columnSizes.get(k);
1669                 if ((ci.getFirstColumn() >= column)
1670                         && (ci.getLastColumn() <= column))
1671                 {
1672                     break;
1673                 }
1674                 ci = null;
1675             }
1676         }
1677         if (ci != null)
1678         {
1679             retval = ci.getColumnWidth();
1680         }
1681         else
1682         {
1683             retval = defaultcolwidth.getColWidth();
1684         }
1685         return retval;
1686     }
1687 
1688     /**
1689      * set the width for a given column in 1/20th of a character width units
1690      * @param column - the column number
1691      * @param width (in units of 1/20th of a character width)
1692      */
1693 
1694     public void setColumnWidth(short column, short width)
1695     {
1696         ColumnInfoRecord ci = null;
1697         int              k  = 0;
1698 
1699         if (columnSizes == null)
1700         {
1701             columnSizes = new ArrayList();
1702         }
1703         //int cioffset = getDimsLoc() - columnSizes.size();
1704 
1705         for (k = 0; k < columnSizes.size(); k++)
1706         {
1707             ci = ( ColumnInfoRecord ) columnSizes.get(k);
1708             if ((ci.getFirstColumn() >= column)
1709                     && (ci.getLastColumn() <= column))
1710             {
1711                 break;
1712             }
1713             ci = null;
1714         }
1715         if (ci != null)
1716         {
1717             if (ci.getColumnWidth() == width)
1718             {
1719 
1720                 // do nothing...the cell's width is equal to what we're setting it to.
1721             }
1722             else if ((ci.getFirstColumn() == column)
1723                      && (ci.getLastColumn() == column))
1724             {                               // if its only for this cell then
1725                 ci.setColumnWidth(width);   // who cares, just change the width
1726             }
1727             else if ((ci.getFirstColumn() == column)
1728                      || (ci.getLastColumn() == column))
1729             {
1730 
1731                 // okay so the width is different but the first or last column == the column we'return setting
1732                 // we'll just divide the info and create a new one
1733                 if (ci.getFirstColumn() == column)
1734                 {
1735                     ci.setFirstColumn(( short ) (column + 1));
1736                 }
1737                 else
1738                 {
1739                     ci.setLastColumn(( short ) (column - 1));
1740                 }
1741                 ColumnInfoRecord nci = ( ColumnInfoRecord ) createColInfo();
1742 
1743                 nci.setFirstColumn(column);
1744                 nci.setLastColumn(column);
1745                 nci.setOptions(ci.getOptions());
1746                 nci.setXFIndex(ci.getXFIndex());
1747                 nci.setColumnWidth(width);
1748                 columnSizes.add(k, nci);
1749                 records.add((1 + getDimsLoc() - columnSizes.size()) + k, nci);
1750                 dimsloc++;
1751             }
1752         }
1753         else
1754         {
1755 
1756             // okay so there ISN'T a column info record that cover's this column so lets create one!
1757             ColumnInfoRecord nci = ( ColumnInfoRecord ) createColInfo();
1758 
1759             nci.setFirstColumn(column);
1760             nci.setLastColumn(column);
1761             nci.setColumnWidth(width);
1762             columnSizes.add(k, nci);
1763             records.add((1 + getDimsLoc() - columnSizes.size()) + k, nci);
1764             dimsloc++;
1765         }
1766     }
1767 
1768     /**
1769      * creates the Dimensions Record and sets it to bogus values (you should set this yourself
1770      * or let the high level API do it for you)
1771      * @see org.apache.poi.hssf.record.DimensionsRecord
1772      * @see org.apache.poi.hssf.record.Record
1773      * @return record containing a DimensionsRecord
1774      */
1775 
1776     protected Record createDimensions()
1777     {
1778         DimensionsRecord retval = new DimensionsRecord();
1779 
1780         retval.setFirstCol(( short ) 0);
1781         retval.setLastRow(1);             // one more than it is
1782         retval.setFirstRow(0);
1783         retval.setLastCol(( short ) 1);   // one more than it is
1784         return retval;
1785     }
1786 
1787     /**
1788      * creates the WindowTwo Record and sets it to:  <P>
1789      * options        = 0x6b6 <P>
1790      * toprow         = 0 <P>
1791      * leftcol        = 0 <P>
1792      * headercolor    = 0x40 <P>
1793      * pagebreakzoom  = 0x0 <P>
1794      * normalzoom     = 0x0 <p>
1795      * @see org.apache.poi.hssf.record.WindowTwoRecord
1796      * @see org.apache.poi.hssf.record.Record
1797      * @return record containing a WindowTwoRecord
1798      */
1799 
1800     protected Record createWindowTwo()
1801     {
1802         WindowTwoRecord retval = new WindowTwoRecord();
1803 
1804         retval.setOptions(( short ) 0x6b6);
1805         retval.setTopRow(( short ) 0);
1806         retval.setLeftCol(( short ) 0);
1807         retval.setHeaderColor(0x40);
1808         retval.setPageBreakZoom(( short ) 0);
1809         retval.setNormalZoom(( short ) 0);
1810         return retval;
1811     }
1812 
1813     /**
1814      * Creates the Selection record and sets it to nothing selected
1815      *
1816      * @see org.apache.poi.hssf.record.SelectionRecord
1817      * @see org.apache.poi.hssf.record.Record
1818      * @return record containing a SelectionRecord
1819      */
1820 
1821     protected Record createSelection()
1822     {
1823         SelectionRecord retval = new SelectionRecord();
1824 
1825         retval.setPane(( byte ) 0x3);
1826         retval.setActiveCellCol(( short ) 0x0);
1827         retval.setActiveCellRow(( short ) 0x0);
1828         retval.setNumRefs(( short ) 0x0);
1829         return retval;
1830     }
1831 
1832     protected Record createMergedCells()
1833     {
1834         MergeCellsRecord retval = new MergeCellsRecord();
1835 
1836         retval.setNumAreas(( short ) 0);
1837         return retval;
1838     }
1839 
1840     /**
1841      * creates the EOF record
1842      * @see org.apache.poi.hssf.record.EOFRecord
1843      * @see org.apache.poi.hssf.record.Record
1844      * @return record containing a EOFRecord
1845      */
1846 
1847     protected Record createEOF()
1848     {
1849         return new EOFRecord();
1850     }
1851 
1852     /**
1853      * get the location of the DimensionsRecord (which is the last record before the value section)
1854      * @return location in the array of records of the DimensionsRecord
1855      */
1856 
1857     public int getDimsLoc()
1858     {
1859         log.log(log.DEBUG, "getDimsLoc dimsloc= " + dimsloc);
1860         return dimsloc;
1861     }
1862 
1863     /**
1864      * in the event the record is a dimensions record, resets both the loc index and dimsloc index
1865      */
1866 
1867     public void checkDimsLoc(Record rec, int recloc)
1868     {
1869         if (rec.getSid() == DimensionsRecord.sid)
1870         {
1871             loc     = recloc;
1872             dimsloc = recloc;
1873         }
1874     }
1875 
1876     public int getSize()
1877     {
1878         int retval = 0;
1879 
1880         for (int k = 0; k < records.size(); k++)
1881         {
1882             retval += (( Record ) records.get(k)).getRecordSize();
1883         }
1884         return retval;
1885     }
1886 
1887     public List getRecords()
1888     {
1889         return records;
1890     }
1891 
1892     /**
1893      * Gets the gridset record for this sheet.
1894      */
1895 
1896     public GridsetRecord getGridsetRecord()
1897     {
1898         return gridset;
1899     }
1900 
1901     /**
1902      * Returns the first occurance of a record matching a particular sid.
1903      */
1904 
1905     public Record findFirstRecordBySid(short sid)
1906     {
1907         for (Iterator iterator = records.iterator(); iterator.hasNext(); )
1908         {
1909             Record record = ( Record ) iterator.next();
1910 
1911             if (record.getSid() == sid)
1912             {
1913                 return record;
1914             }
1915         }
1916         return null;
1917     }
1918 }
1919