Replaced the Excel Writer engine with XLS driver (the XML driver can later be downloaded)
[fa-stable.git] / reporting / includes / Workbook.php
1 <?php
2 /*
3 *  Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
4 *
5 *  The majority of this is _NOT_ my code.  I simply ported it from the
6 *  PERL Spreadsheet::WriteExcel module.
7 *
8 *  The author of the Spreadsheet::WriteExcel module is John McNamara 
9 *  <jmcnamara@cpan.org>
10 *
11 *  I _DO_ maintain this code, and John McNamara has nothing to do with the
12 *  porting of this code to PHP.  Any questions directly related to this
13 *  class library should be directed to me.
14 *
15 *  License Information:
16 *
17 *    Spreadsheet::WriteExcel:  A library for generating Excel Spreadsheets
18 *    Copyright (C) 2002 Xavier Noguer xnoguer@rezebra.com
19 *
20 *    This library is free software; you can redistribute it and/or
21 *    modify it under the terms of the GNU Lesser General Public
22 *    License as published by the Free Software Foundation; either
23 *    version 2.1 of the License, or (at your option) any later version.
24 *
25 *    This library is distributed in the hope that it will be useful,
26 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
27 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
28 *    Lesser General Public License for more details.
29 *
30 *    You should have received a copy of the GNU Lesser General Public
31 *    License along with this library; if not, write to the Free Software
32 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
33 */
34
35 /**
36 * @const ADD token identifier for character "+"
37 */
38 define('ADD',"+");
39
40 /**
41 * @const SUB token identifier for character "-"
42 */
43 define('SUB',"-");
44
45 /**
46 * @const EQUAL token identifier for character "="
47 */
48 define('EQUAL',"=");
49
50 /**
51 * @const MUL token identifier for character "*"
52 */
53 define('MUL',"*");
54
55 /**
56 * @const DIV token identifier for character "/"
57 */
58 define('DIV',"/");
59
60 /**
61 * @const OPEN token identifier for character "("
62 */
63 define('OPEN',"(");
64
65 /**
66 * @const CLOSE token identifier for character ")"
67 */
68 define('CLOSE',")");
69
70 /**
71 * @const COMA token identifier for character ","
72 */
73 define('COMA',",");
74
75 /**
76 * Class for writing Excel BIFF records.
77
78 * From "MICROSOFT EXCEL BINARY FILE FORMAT" by Mark O'Brien (Microsoft Corporation):
79 *
80 * BIFF (BInary File Format) is the file format in which Excel documents are 
81 * saved on disk.  A BIFF file is a complete description of an Excel document.
82 * BIFF files consist of sequences of variable-length records. There are many 
83 * different types of BIFF records.  For example, one record type describes a 
84 * formula entered into a cell; one describes the size and location of a 
85 * window into a document; another describes a picture format.
86 *
87 * @author Xavier Noguer <xnoguer@rezebra.com>
88 * @package Spreadsheet_WriteExcel
89 */
90
91 class BIFFWriter
92 {
93     var $_BIFF_version = 0x0500;
94
95 /**
96 * Constructor
97 *
98 * @access public
99 */
100     function BIFFwriter()
101     {
102         // The byte order of this architecture. 0 => little endian, 1 => big endian
103         $this->_byte_order = '';
104         // The string containing the data of the BIFF stream
105         $this->_data       = '';
106         // Should be the same as strlen($this->_data)
107         $this->_datasize   = 0;
108         // The maximun length for a BIFF record. See _add_continue()
109         $this->_limit      = 2080;   
110         // Set the byte order
111         $this->_set_byte_order();
112     }
113
114 /**
115 * Determine the byte order and store it as class data to avoid
116 * recalculating it for each call to new().
117 *
118 * @access private
119 */
120     function _set_byte_order()
121     {
122         if ($this->_byte_order == '')
123         {
124             // Check if "pack" gives the required IEEE 64bit float
125             $teststr = pack("d", 1.2345);
126             $number  = pack("C8", 0x8D, 0x97, 0x6E, 0x12, 0x83, 0xC0, 0xF3, 0x3F);
127             if ($number == $teststr) {
128                 $byte_order = 0;    // Little Endian
129             }
130             elseif ($number == strrev($teststr)){
131                 $byte_order = 1;    // Big Endian
132             }
133             else {
134                 // Give up. I'll fix this in a later version.
135                 die("Required floating point format not supported ".
136                     "on this platform. See the portability section ".
137                     "of the documentation."
138                    );
139             }
140         }
141         $this->_byte_order = $byte_order;
142     }
143
144 /**
145 * General storage function
146 *
147 * @param string $data binary data to prepend
148 * @access private
149 */
150     function _prepend($data)
151     {
152         if (strlen($data) > $this->_limit) {
153             $data = $this->_add_continue($data);
154         }
155         $this->_data      = $data.$this->_data;
156         $this->_datasize += strlen($data);
157     }
158
159 /**
160 * General storage function
161 *
162 * @param string $data binary data to append
163 * @access private
164 */
165     function _append($data)
166     {
167         if (strlen($data) > $this->_limit) {
168             $data = $this->_add_continue($data);
169         }
170         $this->_data      = $this->_data.$data;
171         $this->_datasize += strlen($data);
172     }
173
174 /**
175 * Writes Excel BOF record to indicate the beginning of a stream or
176 * sub-stream in the BIFF file.
177 *
178 * @param  integer $type type of BIFF file to write: 0x0005 Workbook, 0x0010 Worksheet.
179 * @access private
180 */
181     function _store_bof($type)
182     {
183         $record  = 0x0809;        // Record identifier
184         $length  = 0x0008;        // Number of bytes to follow
185         $version = $this->_BIFF_version;
186    
187         // According to the SDK $build and $year should be set to zero.
188         // However, this throws a warning in Excel 5. So, use these
189         // magic numbers.
190         $build   = 0x096C;
191         $year    = 0x07C9;
192    
193         $header  = pack("vv",   $record, $length);
194         $data    = pack("vvvv", $version, $type, $build, $year);
195         $this->_prepend($header.$data);
196     }
197
198 /**
199 * Writes Excel EOF record to indicate the end of a BIFF stream.
200 *
201 * @access private
202 */
203     function _store_eof() 
204     {
205         $record    = 0x000A;   // Record identifier
206         $length    = 0x0000;   // Number of bytes to follow
207         $header    = pack("vv", $record, $length);
208         $this->_append($header);
209     }
210
211 /**
212 * Excel limits the size of BIFF records. In Excel 5 the limit is 2084 bytes. In
213 * Excel 97 the limit is 8228 bytes. Records that are longer than these limits
214 * must be split up into CONTINUE blocks.
215 *
216 * This function takes a long BIFF record and inserts CONTINUE records as
217 * necessary.
218 *
219 * @param  string  $data The original binary data to be written
220 * @return string        A very convenient string of continue blocks
221 * @access private
222 */
223     function _add_continue($data)
224     {
225         $limit      = $this->_limit;
226         $record     = 0x003C;         // Record identifier
227  
228         // The first 2080/8224 bytes remain intact. However, we have to change
229         // the length field of the record.
230         $tmp = substr($data, 0, 2).pack("v", $limit-4).substr($data, 4, $limit - 4);
231         
232         $header = pack("vv", $record, $limit);  // Headers for continue records
233  
234         // Retrieve chunks of 2080/8224 bytes +4 for the header.
235         for($i = $limit; $i < strlen($data) - $limit; $i += $limit)
236         {
237             $tmp .= $header;
238             $tmp .= substr($data, $i, $limit);
239         }
240
241         // Retrieve the last chunk of data
242         $header  = pack("vv", $record, strlen($data) - $i);
243         $tmp    .= $header;
244         $tmp    .= substr($data,$i,strlen($data) - $i);
245  
246         return($tmp);
247     }
248 }
249
250 /**
251 * Class for creating OLE streams for Excel Spreadsheets
252 *
253 * @author Xavier Noguer <xnoguer@rezebra.com>
254 * @package Spreadsheet_WriteExcel
255 */
256 class OLEwriter
257 {
258     /**
259     * Filename for the OLE stream
260     * @var string
261     * @see _initialize()
262     */
263     var $_OLEfilename;
264
265     /**
266     * Filehandle for the OLE stream
267     * @var resource
268     */
269     var $_filehandle;
270
271     /**
272     * Name of the temporal file in case OLE stream goes to stdout
273     * @var string
274     */
275     var $_tmp_filename;
276
277     /**
278     * Variable for preventing closing two times
279     * @var integer
280     */
281     var $_fileclosed;
282
283     /**
284     * Size of the data to be written to the OLE stream
285     * @var integer
286     */
287     var $_biffsize;
288
289     /**
290     * Real data size to be written to the OLE stream
291     * @var integer
292     */
293     var $_booksize;
294
295     /**
296     * Number of big blocks in the OLE stream
297     * @var integer
298     */
299     var $_big_blocks;
300
301     /**
302     * Number of list blocks in the OLE stream
303     * @var integer
304     */
305     var $_list_blocks;
306
307     /**
308     * Number of big blocks in the OLE stream
309     * @var integer
310     */
311     var $_root_start;
312
313     /**
314     * Class for creating an OLEwriter
315     *
316     * @param string $OLEfilename the name of the file for the OLE stream
317     */
318     function OLEwriter($OLEfilename)
319     {
320         $this->_OLEfilename  = $OLEfilename;
321         $this->_filehandle   = "";
322         $this->_tmp_filename = "";
323         $this->_fileclosed   = 0;
324         //$this->_size_allowed = 0;
325         $this->_biffsize     = 0;
326         $this->_booksize     = 0;
327         $this->_big_blocks   = 0;
328         $this->_list_blocks  = 0;
329         $this->_root_start   = 0;
330         //$this->_block_count  = 4;
331         $this->_initialize();
332     }
333
334 /**
335 * Check for a valid filename and store the filehandle.
336 * Filehandle "-" writes to STDOUT
337 */
338     function _initialize()
339     {
340         $OLEfile = $this->_OLEfilename;
341  
342         if(($OLEfile == '-') or ($OLEfile == ''))
343         {
344             $this->_tmp_filename = tempnam("/tmp", "OLEwriter");
345             $fh = fopen($this->_tmp_filename,"wb");
346             if ($fh == false) {
347                 die("Can't create temporary file.");
348             }
349         }
350         else
351         {
352             // Create a new file, open for writing (in binmode)
353             $fh = fopen($OLEfile,"wb");
354             if ($fh == false) {
355                 die("Can't open $OLEfile. It may be in use or protected.");
356             }
357         }
358
359         // Store filehandle
360         $this->_filehandle = $fh;
361     }
362
363
364     /**
365     * Set the size of the data to be written to the OLE stream.
366     * The maximun size comes from this:
367     *   $big_blocks = (109 depot block x (128 -1 marker word)
368     *                 - (1 x end words)) = 13842
369     *   $maxsize    = $big_blocks * 512 bytes = 7087104
370     *
371     * @access public
372     * @see Workbook::store_OLE_file()
373     * @param integer $biffsize The size of the data to be written to the OLE stream
374     * @return integer 1 for success
375     */
376     function set_size($biffsize)
377     {
378         $maxsize = 7087104; // TODO: extend max size
379  
380         if ($biffsize > $maxsize) {
381             die("Maximum file size, $maxsize, exceeded.");
382         }
383  
384         $this->_biffsize = $biffsize;
385         // Set the min file size to 4k to avoid having to use small blocks
386         if ($biffsize > 4096) {
387             $this->_booksize = $biffsize;
388         }
389         else {
390             $this->_booksize = 4096;
391         }
392         //$this->_size_allowed = 1;
393         return(1);
394     }
395
396
397     /**
398     * Calculate various sizes needed for the OLE stream
399     */
400     function _calculate_sizes()
401     {
402         $datasize = $this->_booksize;
403         if ($datasize % 512 == 0) {
404             $this->_big_blocks = $datasize/512;
405         }
406         else {
407             $this->_big_blocks = floor($datasize/512) + 1;
408         }
409         // There are 127 list blocks and 1 marker blocks for each big block
410         // depot + 1 end of chain block
411         $this->_list_blocks = floor(($this->_big_blocks)/127) + 1;
412         $this->_root_start  = $this->_big_blocks;
413     }
414
415     /**
416     * Write root entry, big block list and close the filehandle.
417     * This routine is used to explicitly close the open filehandle without
418     * having to wait for DESTROY.
419     *
420     * @access public
421     * @see Workbook::store_OLE_file()
422     */
423     function close() 
424     {
425         //return if not $this->{_size_allowed};
426         $this->_write_padding();
427         $this->_write_property_storage();
428         $this->_write_big_block_depot();
429         // Close the filehandle 
430         fclose($this->_filehandle);
431         if(($this->_OLEfilename == '-') or ($this->_OLEfilename == ''))
432         {
433             $fh = fopen($this->_tmp_filename, "rb");
434             if ($fh == false) {
435                 die("Can't read temporary file.");
436             }
437             fpassthru($fh);
438             // Delete the temporary file.
439             @unlink($this->_tmp_filename);
440         }
441         $this->_fileclosed = 1;
442     }
443
444
445     /**
446     * Write BIFF data to OLE file.
447     *
448     * @param string $data string of bytes to be written
449     */
450     function write($data) //por ahora sólo a STDOUT
451     {
452         fwrite($this->_filehandle,$data,strlen($data));
453     }
454
455
456     /**
457     * Write OLE header block.
458     */
459     function write_header()
460     {
461         $this->_calculate_sizes();
462         $root_start      = $this->_root_start;
463         $num_lists       = $this->_list_blocks;
464         $id              = pack("nnnn", 0xD0CF, 0x11E0, 0xA1B1, 0x1AE1);
465         $unknown1        = pack("VVVV", 0x00, 0x00, 0x00, 0x00);
466         $unknown2        = pack("vv",   0x3E, 0x03);
467         $unknown3        = pack("v",    -2);
468         $unknown4        = pack("v",    0x09);
469         $unknown5        = pack("VVV",  0x06, 0x00, 0x00);
470         $num_bbd_blocks  = pack("V",    $num_lists);
471         $root_startblock = pack("V",    $root_start);
472         $unknown6        = pack("VV",   0x00, 0x1000);
473         $sbd_startblock  = pack("V",    -2);
474         $unknown7        = pack("VVV",  0x00, -2 ,0x00);
475         $unused          = pack("V",    -1);
476  
477         fwrite($this->_filehandle,$id);
478         fwrite($this->_filehandle,$unknown1);
479         fwrite($this->_filehandle,$unknown2);
480         fwrite($this->_filehandle,$unknown3);
481         fwrite($this->_filehandle,$unknown4);
482         fwrite($this->_filehandle,$unknown5);
483         fwrite($this->_filehandle,$num_bbd_blocks);
484         fwrite($this->_filehandle,$root_startblock);
485         fwrite($this->_filehandle,$unknown6);
486         fwrite($this->_filehandle,$sbd_startblock);
487         fwrite($this->_filehandle,$unknown7);
488  
489         for($i=1; $i <= $num_lists; $i++)
490         {
491             $root_start++;
492             fwrite($this->_filehandle,pack("V",$root_start));
493         }
494         for($i = $num_lists; $i <=108; $i++)
495         {
496             fwrite($this->_filehandle,$unused);
497         }
498     }
499
500
501     /**
502     * Write big block depot.
503     */
504     function _write_big_block_depot()
505     {
506         $num_blocks   = $this->_big_blocks;
507         $num_lists    = $this->_list_blocks;
508         $total_blocks = $num_lists *128;
509         $used_blocks  = $num_blocks + $num_lists +2;
510  
511         $marker       = pack("V", -3);
512         $end_of_chain = pack("V", -2);
513         $unused       = pack("V", -1);
514  
515         for($i=1; $i < $num_blocks; $i++)
516         {
517             fwrite($this->_filehandle,pack("V",$i));
518         }
519         fwrite($this->_filehandle,$end_of_chain);
520         fwrite($this->_filehandle,$end_of_chain);
521         for($i=0; $i < $num_lists; $i++)
522         {
523             fwrite($this->_filehandle,$marker);
524         }
525         for($i=$used_blocks; $i <= $total_blocks; $i++)
526         {
527             fwrite($this->_filehandle,$unused);
528         }
529     }
530
531 /**
532 * Write property storage. TODO: add summary sheets
533 */
534     function _write_property_storage()
535     {
536         //$rootsize = -2;
537         /***************  name         type   dir start size */
538         $this->_write_pps("Root Entry", 0x05,   1,   -2, 0x00);
539         $this->_write_pps("Book",       0x02,  -1, 0x00, $this->_booksize);
540         $this->_write_pps('',           0x00,  -1, 0x00, 0x0000);
541         $this->_write_pps('',           0x00,  -1, 0x00, 0x0000);
542     }
543
544 /**
545 * Write property sheet in property storage
546 *
547 * @param string  $name  name of the property storage.
548 * @param integer $type  type of the property storage.
549 * @param integer $dir   dir of the property storage.
550 * @param integer $start start of the property storage.
551 * @param integer $size  size of the property storage.
552 * @access private
553 */
554     function _write_pps($name,$type,$dir,$start,$size)
555     {
556         $length  = 0;
557         $rawname = '';
558  
559         if ($name != '')
560         {
561             $name = $name . "\0";
562             for($i=0;$i<strlen($name);$i++)
563             {
564                 // Simulate a Unicode string
565                 $rawname .= pack("H*",dechex(ord($name{$i}))).pack("C",0);
566             }
567             $length = strlen($name) * 2;
568         }
569        
570         $zero            = pack("C",  0);
571         $pps_sizeofname  = pack("v",  $length);    // 0x40
572         $pps_type        = pack("v",  $type);      // 0x42
573         $pps_prev        = pack("V",  -1);         // 0x44
574         $pps_next        = pack("V",  -1);         // 0x48
575         $pps_dir         = pack("V",  $dir);       // 0x4c
576        
577         $unknown1        = pack("V",  0);
578        
579         $pps_ts1s        = pack("V",  0);          // 0x64
580         $pps_ts1d        = pack("V",  0);          // 0x68
581         $pps_ts2s        = pack("V",  0);          // 0x6c
582         $pps_ts2d        = pack("V",  0);          // 0x70
583         $pps_sb          = pack("V",  $start);     // 0x74
584         $pps_size        = pack("V",  $size);      // 0x78
585        
586        
587         fwrite($this->_filehandle,$rawname);
588         for($i=0; $i < (64 -$length); $i++) {
589             fwrite($this->_filehandle,$zero);
590         }
591         fwrite($this->_filehandle,$pps_sizeofname);
592         fwrite($this->_filehandle,$pps_type);
593         fwrite($this->_filehandle,$pps_prev);
594         fwrite($this->_filehandle,$pps_next);
595         fwrite($this->_filehandle,$pps_dir);
596         for($i=0; $i < 5; $i++) {
597             fwrite($this->_filehandle,$unknown1);
598         }
599         fwrite($this->_filehandle,$pps_ts1s);
600         fwrite($this->_filehandle,$pps_ts1d);
601         fwrite($this->_filehandle,$pps_ts2d);
602         fwrite($this->_filehandle,$pps_ts2d);
603         fwrite($this->_filehandle,$pps_sb);
604         fwrite($this->_filehandle,$pps_size);
605         fwrite($this->_filehandle,$unknown1);
606     }
607
608     /**
609     * Pad the end of the file
610     */
611     function _write_padding()
612     {
613         $biffsize = $this->_biffsize;
614         if ($biffsize < 4096) {
615             $min_size = 4096;
616         }
617         else {    
618             $min_size = 512;
619         }
620         if ($biffsize % $min_size != 0)
621         {
622             $padding  = $min_size - ($biffsize % $min_size);
623             for($i=0; $i < $padding; $i++) {
624                 fwrite($this->_filehandle,"\0");
625             }
626         }
627     }
628 }
629
630 /**
631 * Class for generating Excel XF records (formats)
632 *
633 * @author Xavier Noguer <xnoguer@rezebra.com>
634 * @package Spreadsheet_WriteExcel
635 */
636
637 class Format
638 {
639   /**
640   * Constructor
641   *
642   * @access public
643   * @param integer $index the XF index for the format.
644   * @param array   $properties array with properties to be set on initialization.
645   */
646     function Format($index = 0,$properties =  array())
647     {
648         $this->xf_index       = $index;
649     
650         $this->font_index     = 0;
651         $this->font           = 'Arial';
652         $this->size           = 10;
653         $this->bold           = 0x0190;
654         $this->_italic        = 0;
655         $this->color          = 0x7FFF;
656         $this->_underline     = 0;
657         $this->font_strikeout = 0;
658         $this->font_outline   = 0;
659         $this->font_shadow    = 0;
660         $this->font_script    = 0;
661         $this->font_family    = 0;
662         $this->font_charset   = 0;
663     
664         $this->_num_format    = 0;
665     
666         $this->hidden         = 0;
667         $this->locked         = 1;
668     
669         $this->_text_h_align  = 0;
670         $this->_text_wrap     = 0;
671         $this->text_v_align   = 2;
672         $this->text_justlast  = 0;
673         $this->rotation       = 0;
674     
675         $this->fg_color       = 0x40;
676         $this->bg_color       = 0x41;
677     
678         $this->pattern        = 0;
679     
680         $this->bottom         = 0;
681         $this->top            = 0;
682         $this->left           = 0;
683         $this->right          = 0;
684     
685         $this->bottom_color   = 0x40;
686         $this->top_color      = 0x40;
687         $this->left_color     = 0x40;
688         $this->right_color    = 0x40;
689     
690         // Set properties passed to Workbook::add_format()
691         foreach($properties as $property => $value)
692         {
693             if(method_exists($this,"set_$property"))
694             {
695                 $aux = 'set_'.$property;
696                 $this->$aux($value);
697             }
698         }
699     }
700     
701     /**
702     * Generate an Excel BIFF XF record (style or cell).
703     *
704     * @param string $style The type of the XF record ('style' or 'cell').
705     * @return string The XF record
706     */
707     function get_xf($style)
708     {
709         // Set the type of the XF record and some of the attributes.
710         if ($style == "style") {
711             $style = 0xFFF5;
712         }
713         else {
714             $style   = $this->locked;
715             $style  |= $this->hidden << 1;
716         }
717     
718         // Flags to indicate if attributes have been set.
719         $atr_num     = ($this->_num_format != 0)?1:0;
720         $atr_fnt     = ($this->font_index != 0)?1:0;
721         $atr_alc     = ($this->_text_wrap)?1:0;
722         $atr_bdr     = ($this->bottom   ||
723                         $this->top      ||
724                         $this->left     ||
725                         $this->right)?1:0;
726         $atr_pat     = (($this->fg_color != 0x40) ||
727                         ($this->bg_color != 0x41) ||
728                         $this->pattern)?1:0;
729         $atr_prot    = 0;
730     
731         // Zero the default border colour if the border has not been set.
732         if ($this->bottom == 0) {
733             $this->bottom_color = 0;
734             }
735         if ($this->top  == 0) {
736             $this->top_color = 0;
737             }
738         if ($this->right == 0) {
739             $this->right_color = 0;
740             }
741         if ($this->left == 0) {
742             $this->left_color = 0;
743             }
744     
745         $record         = 0x00E0;              // Record identifier
746         $length         = 0x0010;              // Number of bytes to follow
747                                                
748         $ifnt           = $this->font_index;   // Index to FONT record
749         $ifmt           = $this->_num_format;  // Index to FORMAT record
750     
751         $align          = $this->_text_h_align;       // Alignment
752         $align         |= $this->_text_wrap    << 3;
753         $align         |= $this->text_v_align  << 4;
754         $align         |= $this->text_justlast << 7;
755         $align         |= $this->rotation      << 8;
756         $align         |= $atr_num                << 10;
757         $align         |= $atr_fnt                << 11;
758         $align         |= $atr_alc                << 12;
759         $align         |= $atr_bdr                << 13;
760         $align         |= $atr_pat                << 14;
761         $align         |= $atr_prot               << 15;
762     
763         $icv            = $this->fg_color;           // fg and bg pattern colors
764         $icv           |= $this->bg_color      << 7;
765     
766         $fill           = $this->pattern;            // Fill and border line style
767         $fill          |= $this->bottom        << 6;
768         $fill          |= $this->bottom_color  << 9;
769     
770         $border1        = $this->top;                // Border line style and color
771         $border1       |= $this->left          << 3;
772         $border1       |= $this->right         << 6;
773         $border1       |= $this->top_color     << 9;
774
775         $border2        = $this->left_color;         // Border color
776         $border2       |= $this->right_color   << 7;
777     
778         $header      = pack("vv",       $record, $length);
779         $data        = pack("vvvvvvvv", $ifnt, $ifmt, $style, $align,
780                                         $icv, $fill,
781                                         $border1, $border2);
782         return($header.$data);
783     }
784     
785     /**
786     * Generate an Excel BIFF FONT record.
787     *
788     * @see Workbook::_store_all_fonts()
789     * @return string The FONT record
790     */
791     function get_font()
792     {
793         $dyHeight   = $this->size * 20;    // Height of font (1/20 of a point)
794         $icv        = $this->color;        // Index to color palette
795         $bls        = $this->bold;         // Bold style
796         $sss        = $this->font_script;  // Superscript/subscript
797         $uls        = $this->_underline;   // Underline
798         $bFamily    = $this->font_family;  // Font family
799         $bCharSet   = $this->font_charset; // Character set
800         $rgch       = $this->font;         // Font name
801     
802         $cch        = strlen($rgch);       // Length of font name
803         $record     = 0x31;                // Record identifier
804         $length     = 0x0F + $cch;         // Record length
805         $reserved   = 0x00;                // Reserved
806         $grbit      = 0x00;                // Font attributes
807         if ($this->_italic) {
808             $grbit     |= 0x02;
809         }
810         if ($this->font_strikeout) {
811             $grbit     |= 0x08;
812         }
813         if ($this->font_outline) {
814             $grbit     |= 0x10;
815         }
816         if ($this->font_shadow) {
817             $grbit     |= 0x20;
818         }
819     
820         $header  = pack("vv",         $record, $length);
821         $data    = pack("vvvvvCCCCC", $dyHeight, $grbit, $icv, $bls,
822                                       $sss, $uls, $bFamily,
823                                       $bCharSet, $reserved, $cch);
824         return($header . $data. $this->font);
825     }
826     
827     /**
828     * Returns a unique hash key for a font. Used by Workbook->_store_all_fonts()
829     *
830     * The elements that form the key are arranged to increase the probability of
831     * generating a unique key. Elements that hold a large range of numbers
832     * (eg. _color) are placed between two binary elements such as _italic
833     *
834     * @return string A key for this font
835     */
836     function get_font_key()
837     {
838         $key  = "$this->font$this->size";
839         $key .= "$this->font_script$this->_underline";
840         $key .= "$this->font_strikeout$this->bold$this->font_outline";
841         $key .= "$this->font_family$this->font_charset";
842         $key .= "$this->font_shadow$this->color$this->_italic";
843         $key  = str_replace(" ","_",$key);
844         return ($key);
845     }
846     
847     /**
848     * Returns the index used by Worksheet->_XF()
849     *
850     * @return integer The index for the XF record
851     */
852     function get_xf_index()
853     {
854         return($this->xf_index);
855     }
856     
857     /**
858     * Used in conjunction with the set_xxx_color methods to convert a color
859     * string into a number. Color range is 0..63 but we will restrict it
860     * to 8..63 to comply with Gnumeric. Colors 0..7 are repeated in 8..15.
861     *
862     * @param string $name_color name of the color (i.e.: 'blue', 'red', etc..). Optional.
863     * @return integer The color index
864     */
865     function _get_color($name_color = '')
866     {
867         $colors = array(
868                         'aqua'    => 0x0F,
869                         'cyan'    => 0x0F,
870                         'black'   => 0x08,
871                         'blue'    => 0x0C,
872                         'brown'   => 0x10,
873                         'magenta' => 0x0E,
874                         'fuchsia' => 0x0E,
875                         'gray'    => 0x17,
876                         'grey'    => 0x17,
877                         'green'   => 0x11,
878                         'lime'    => 0x0B,
879                         'navy'    => 0x12,
880                         'orange'  => 0x35,
881                         'purple'  => 0x14,
882                         'red'     => 0x0A,
883                         'silver'  => 0x16,
884                         'white'   => 0x09,
885                         'yellow'  => 0x0D
886                        );
887     
888         // Return the default color, 0x7FFF, if undef,
889         if($name_color == '') {
890             return(0x7FFF);
891         }
892     
893         // or the color string converted to an integer,
894         if(isset($colors[$name_color])) {
895             return($colors[$name_color]);
896         }
897     
898         // or the default color if string is unrecognised,
899         if(preg_match("/\D/",$name_color)) {
900             return(0x7FFF);
901         }
902     
903         // or an index < 8 mapped into the correct range,
904         if($name_color < 8) {
905             return($name_color + 8);
906         }
907     
908         // or the default color if arg is outside range,
909         if($name_color > 63) {
910             return(0x7FFF);
911         }
912     
913         // or an integer in the valid range
914         return($name_color);
915     }
916     
917     /**
918     * Set cell alignment.
919     *
920     * @access public
921     * @param string $location alignment for the cell ('left', 'right', etc...).
922     */
923     function set_align($location)
924     {
925         if (preg_match("/\d/",$location)) {
926             return;                      // Ignore numbers
927         }
928     
929         $location = strtolower($location);
930     
931         if ($location == 'left')
932             $this->_text_h_align = 1; 
933         if ($location == 'centre')
934             $this->_text_h_align = 2; 
935         if ($location == 'center')
936             $this->_text_h_align = 2; 
937         if ($location == 'right')
938             $this->_text_h_align = 3; 
939         if ($location == 'fill')
940             $this->_text_h_align = 4; 
941         if ($location == 'justify')
942             $this->_text_h_align = 5;
943         if ($location == 'merge')
944             $this->_text_h_align = 6;
945         if ($location == 'equal_space') // For T.K.
946             $this->_text_h_align = 7; 
947         if ($location == 'top')
948             $this->text_v_align = 0; 
949         if ($location == 'vcentre')
950             $this->text_v_align = 1; 
951         if ($location == 'vcenter')
952             $this->text_v_align = 1; 
953         if ($location == 'bottom')
954             $this->text_v_align = 2; 
955         if ($location == 'vjustify')
956             $this->text_v_align = 3; 
957         if ($location == 'vequal_space') // For T.K.
958             $this->text_v_align = 4; 
959     }
960     
961     /**
962     * This is an alias for the unintuitive set_align('merge')
963     *
964     * @access public
965     */
966     function set_merge()
967     {
968         $this->set_align('merge');
969     }
970     
971     /**
972     * Bold has a range 0x64..0x3E8.
973     * 0x190 is normal. 0x2BC is bold.
974     *
975     * @access public
976     * @param integer $weight Weight for the text, 0 maps to 0x190, 1 maps to 0x2BC. 
977                              It's Optional, default is 1 (bold).
978     */
979     function set_bold($weight = 1)
980     {
981         if($weight == 1) {
982             $weight = 0x2BC;  // Bold text
983         }
984         if($weight == 0) {
985             $weight = 0x190;  // Normal text
986         }
987         if($weight <  0x064) {
988             $weight = 0x190;  // Lower bound
989         }
990         if($weight >  0x3E8) {
991             $weight = 0x190;  // Upper bound
992         }
993         $this->bold = $weight;
994     }
995     
996     
997     /************************************
998     * FUNCTIONS FOR SETTING CELLS BORDERS
999     */
1000     
1001     /**
1002     * Sets the bottom border of the cell
1003     *
1004     * @access public
1005     * @param integer $style style of the cell border. 1 => thin, 2 => thick.
1006     */
1007     function set_bottom($style)
1008     {
1009         $this->bottom = $style;
1010     }
1011     
1012     /**
1013     * Sets the top border of the cell
1014     *
1015     * @access public
1016     * @param integer $style style of the cell top border. 1 => thin, 2 => thick.
1017     */
1018     function set_top($style)
1019     {
1020         $this->top = $style;
1021     }
1022     
1023     /**
1024     * Sets the left border of the cell
1025     *
1026     * @access public
1027     * @param integer $style style of the cell left border. 1 => thin, 2 => thick.
1028     */
1029     function set_left($style)
1030     {
1031         $this->left = $style;
1032     }
1033     
1034     /**
1035     * Sets the right border of the cell
1036     *
1037     * @access public
1038     * @param integer $style style of the cell right border. 1 => thin, 2 => thick.
1039     */
1040     function set_right($style)
1041     {
1042         $this->right = $style;
1043     }
1044     
1045     
1046     /**
1047     * Set cells borders to the same style
1048     *
1049     * @access public
1050     * @param integer $style style to apply for all cell borders. 1 => thin, 2 => thick.
1051     */
1052     function set_border($style)
1053     {
1054         $this->set_bottom($style);
1055         $this->set_top($style);
1056         $this->set_left($style);
1057         $this->set_right($style);
1058     }
1059     
1060     
1061     /*******************************************
1062     * FUNCTIONS FOR SETTING CELLS BORDERS COLORS
1063     */
1064     
1065     /**
1066     * Sets all the cell's borders to the same color
1067     *
1068     * @access public
1069     * @param mixed $color The color we are setting. Either a string (like 'blue'), 
1070     *                     or an integer (like 0x41).
1071     */
1072     function set_border_color($color)
1073     {
1074         $this->set_bottom_color($color);
1075         $this->set_top_color($color);
1076         $this->set_left_color($color);
1077         $this->set_right_color($color);
1078     }
1079     
1080     /**
1081     * Sets the cell's bottom border color
1082     *
1083     * @access public
1084     * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63]).
1085     */
1086     function set_bottom_color($color)
1087     {
1088         $value = $this->_get_color($color);
1089         $this->bottom_color = $value;
1090     }
1091     
1092     /**
1093     * Sets the cell's top border color
1094     *
1095     * @access public
1096     * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63]).
1097     */
1098     function set_top_color($color)
1099     {
1100         $value = $this->_get_color($color);
1101         $this->top_color = $value;
1102     }
1103     
1104     /**
1105     * Sets the cell's left border color
1106     *
1107     * @access public
1108     * @param mixed $color either a string (like 'blue'), or an integer (like 0x41).
1109     */
1110     function set_left_color($color)
1111     {
1112         $value = $this->_get_color($color);
1113         $this->left_color = $value;
1114     }
1115     
1116     /**
1117     * Sets the cell's right border color
1118     *
1119     * @access public
1120     * @param mixed $color either a string (like 'blue'), or an integer (like 0x41).
1121     */
1122     function set_right_color($color)
1123     {
1124         $value = $this->_get_color($color);
1125         $this->right_color = $value;
1126     }
1127     
1128     
1129     /**
1130     * Sets the cell's foreground color
1131     *
1132     * @access public
1133     * @param mixed $color either a string (like 'blue'), or an integer (like 0x41).
1134     */
1135     function set_fg_color($color)
1136     {
1137         $value = $this->_get_color($color);
1138         $this->fg_color = $value;
1139     }
1140       
1141     /**
1142     * Sets the cell's background color
1143     *
1144     * @access public
1145     * @param mixed $color either a string (like 'blue'), or an integer (like 0x41).
1146     */
1147     function set_bg_color($color)
1148     {
1149         $value = $this->_get_color($color);
1150         $this->bg_color = $value;
1151     }
1152     
1153     /**
1154     * Sets the cell's color
1155     *
1156     * @access public
1157     * @param mixed $color either a string (like 'blue'), or an integer (like 0x41).
1158     */
1159     function set_color($color)
1160     {
1161         $value = $this->_get_color($color);
1162         $this->color = $value;
1163     }
1164     
1165     /**
1166     * Sets the pattern attribute of a cell
1167     *
1168     * @access public
1169     * @param integer $arg Optional. Defaults to 1.
1170     */
1171     function set_pattern($arg = 1)
1172     {
1173         $this->pattern = $arg;
1174     }
1175     
1176     /**
1177     * Sets the underline of the text
1178     *
1179     * @access public
1180     * @param integer $underline The value for underline. Possible values are:
1181     *                          1 => underline, 2 => double underline.
1182     */
1183     function set_underline($underline)
1184     {
1185         $this->_underline = $underline;
1186     }
1187  
1188     /**
1189     * Sets the font style as italic
1190     *
1191     * @access public
1192     */
1193     function set_italic()
1194     {
1195         $this->_italic = 1;
1196     }
1197
1198     /**
1199     * Sets the font size 
1200     *
1201     * @access public
1202     * @param integer $size The font size (in pixels I think).
1203     */
1204     function set_size($size)
1205     {
1206         $this->size = $size;
1207     }
1208     
1209     /**
1210     * Sets the num format
1211     *
1212     * @access public
1213     * @param integer $num_format The num format.
1214     */
1215     function set_num_format($num_format)
1216     {
1217         $this->_num_format = $num_format;
1218     }
1219     
1220     /**
1221     * Sets text wrapping
1222     *
1223     * @access public
1224     * @param integer $text_wrap Optional. 0 => no text wrapping, 1 => text wrapping. 
1225     *                           Defaults to 1.
1226     */
1227     function set_text_wrap($text_wrap = 1)
1228     {
1229         $this->_text_wrap = $text_wrap;
1230     }
1231 }
1232
1233
1234 /**
1235 * Class for parsing Excel formulas
1236 *
1237 * @author Xavier Noguer <xnoguer@rezebra.com>
1238 * @package Spreadsheet_WriteExcel
1239 */
1240 class Parser
1241   {
1242 /**
1243 * The class constructor
1244 *
1245 * @param integer $byte_order The byte order (Little endian or Big endian) of the architecture
1246                              (optional). 1 => big endian, 0 (default) => little endian. 
1247 */
1248   function Parser($byte_order = 0)
1249     {
1250     $this->_current_char  = 0;        // The index of the character we are currently looking at.
1251     $this->_current_token = '';       // The token we are working on.
1252     $this->_formula       = "";       // The formula to parse.
1253     $this->_lookahead     = '';       // The character ahead of the current char.
1254     $this->_parse_tree    = '';       // The parse tree to be generated.
1255     $this->_initialize_hashes();      // Initialize the hashes: ptg's and function's ptg's
1256     $this->_byte_order = $byte_order; // Little Endian or Big Endian
1257     $this->_func_args  = 0;           // Number of arguments for the current function
1258     $this->_volatile   = 0;
1259     }
1260
1261 /**
1262 * Initialize the ptg and function hashes. 
1263 */
1264   function _initialize_hashes()
1265     {
1266     // The Excel ptg indices
1267     $this->ptg = array(
1268         'ptgExp'       => 0x01,
1269         'ptgTbl'       => 0x02,
1270         'ptgAdd'       => 0x03,
1271         'ptgSub'       => 0x04,
1272         'ptgMul'       => 0x05,
1273         'ptgDiv'       => 0x06,
1274         'ptgPower'     => 0x07,
1275         'ptgConcat'    => 0x08,
1276         'ptgLT'        => 0x09,
1277         'ptgLE'        => 0x0A,
1278         'ptgEQ'        => 0x0B,
1279         'ptgGE'        => 0x0C,
1280         'ptgGT'        => 0x0D,
1281         'ptgNE'        => 0x0E,
1282         'ptgIsect'     => 0x0F,
1283         'ptgUnion'     => 0x10,
1284         'ptgRange'     => 0x11,
1285         'ptgUplus'     => 0x12,
1286         'ptgUminus'    => 0x13,
1287         'ptgPercent'   => 0x14,
1288         'ptgParen'     => 0x15,
1289         'ptgMissArg'   => 0x16,
1290         'ptgStr'       => 0x17,
1291         'ptgAttr'      => 0x19,
1292         'ptgSheet'     => 0x1A,
1293         'ptgEndSheet'  => 0x1B,
1294         'ptgErr'       => 0x1C,
1295         'ptgBool'      => 0x1D,
1296         'ptgInt'       => 0x1E,
1297         'ptgNum'       => 0x1F,
1298         'ptgArray'     => 0x20,
1299         'ptgFunc'      => 0x21,
1300         'ptgFuncVar'   => 0x22,
1301         'ptgName'      => 0x23,
1302         'ptgRef'       => 0x24,
1303         'ptgArea'      => 0x25,
1304         'ptgMemArea'   => 0x26,
1305         'ptgMemErr'    => 0x27,
1306         'ptgMemNoMem'  => 0x28,
1307         'ptgMemFunc'   => 0x29,
1308         'ptgRefErr'    => 0x2A,
1309         'ptgAreaErr'   => 0x2B,
1310         'ptgRefN'      => 0x2C,
1311         'ptgAreaN'     => 0x2D,
1312         'ptgMemAreaN'  => 0x2E,
1313         'ptgMemNoMemN' => 0x2F,
1314         'ptgNameX'     => 0x39,
1315         'ptgRef3d'     => 0x3A,
1316         'ptgArea3d'    => 0x3B,
1317         'ptgRefErr3d'  => 0x3C,
1318         'ptgAreaErr3d' => 0x3D,
1319         'ptgArrayV'    => 0x40,
1320         'ptgFuncV'     => 0x41,
1321         'ptgFuncVarV'  => 0x42,
1322         'ptgNameV'     => 0x43,
1323         'ptgRefV'      => 0x44,
1324         'ptgAreaV'     => 0x45,
1325         'ptgMemAreaV'  => 0x46,
1326         'ptgMemErrV'   => 0x47,
1327         'ptgMemNoMemV' => 0x48,
1328         'ptgMemFuncV'  => 0x49,
1329         'ptgRefErrV'   => 0x4A,
1330         'ptgAreaErrV'  => 0x4B,
1331         'ptgRefNV'     => 0x4C,
1332         'ptgAreaNV'    => 0x4D,
1333         'ptgMemAreaNV' => 0x4E,
1334         'ptgMemNoMemN' => 0x4F,
1335         'ptgFuncCEV'   => 0x58,
1336         'ptgNameXV'    => 0x59,
1337         'ptgRef3dV'    => 0x5A,
1338         'ptgArea3dV'   => 0x5B,
1339         'ptgRefErr3dV' => 0x5C,
1340         'ptgAreaErr3d' => 0x5D,
1341         'ptgArrayA'    => 0x60,
1342         'ptgFuncA'     => 0x61,
1343         'ptgFuncVarA'  => 0x62,
1344         'ptgNameA'     => 0x63,
1345         'ptgRefA'      => 0x64,
1346         'ptgAreaA'     => 0x65,
1347         'ptgMemAreaA'  => 0x66,
1348         'ptgMemErrA'   => 0x67,
1349         'ptgMemNoMemA' => 0x68,
1350         'ptgMemFuncA'  => 0x69,
1351         'ptgRefErrA'   => 0x6A,
1352         'ptgAreaErrA'  => 0x6B,
1353         'ptgRefNA'     => 0x6C,
1354         'ptgAreaNA'    => 0x6D,
1355         'ptgMemAreaNA' => 0x6E,
1356         'ptgMemNoMemN' => 0x6F,
1357         'ptgFuncCEA'   => 0x78,
1358         'ptgNameXA'    => 0x79,
1359         'ptgRef3dA'    => 0x7A,
1360         'ptgArea3dA'   => 0x7B,
1361         'ptgRefErr3dA' => 0x7C,
1362         'ptgAreaErr3d' => 0x7D
1363         );
1364
1365     // Thanks to Michael Meeks and Gnumeric for the initial arg values.
1366     //
1367     // The following hash was generated by "function_locale.pl" in the distro.
1368     // Refer to function_locale.pl for non-English function names.
1369     //
1370     // The array elements are as follow:
1371     // ptg:   The Excel function ptg code.
1372     // args:  The number of arguments that the function takes:
1373     //           >=0 is a fixed number of arguments.
1374     //           -1  is a variable  number of arguments.
1375     // class: The reference, value or array class of the function args.
1376     // vol:   The function is volatile.
1377     //
1378     $this->_functions = array(
1379           // function                  ptg  args  class  vol
1380           'COUNT'           => array(   0,   -1,    0,    0 ),
1381           'IF'              => array(   1,   -1,    1,    0 ),
1382           'ISNA'            => array(   2,    1,    1,    0 ),
1383           'ISERROR'         => array(   3,    1,    1,    0 ),
1384           'SUM'             => array(   4,   -1,    0,    0 ),
1385           'AVERAGE'         => array(   5,   -1,    0,    0 ),
1386           'MIN'             => array(   6,   -1,    0,    0 ),
1387           'MAX'             => array(   7,   -1,    0,    0 ),
1388           'ROW'             => array(   8,   -1,    0,    0 ),
1389           'COLUMN'          => array(   9,   -1,    0,    0 ),
1390           'NA'              => array(  10,    0,    0,    0 ),
1391           'NPV'             => array(  11,   -1,    1,    0 ),
1392           'STDEV'           => array(  12,   -1,    0,    0 ),
1393           'DOLLAR'          => array(  13,   -1,    1,    0 ),
1394           'FIXED'           => array(  14,   -1,    1,    0 ),
1395           'SIN'             => array(  15,    1,    1,    0 ),
1396           'COS'             => array(  16,    1,    1,    0 ),
1397           'TAN'             => array(  17,    1,    1,    0 ),
1398           'ATAN'            => array(  18,    1,    1,    0 ),
1399           'PI'              => array(  19,    0,    1,    0 ),
1400           'SQRT'            => array(  20,    1,    1,    0 ),
1401           'EXP'             => array(  21,    1,    1,    0 ),
1402           'LN'              => array(  22,    1,    1,    0 ),
1403           'LOG10'           => array(  23,    1,    1,    0 ),
1404           'ABS'             => array(  24,    1,    1,    0 ),
1405           'INT'             => array(  25,    1,    1,    0 ),
1406           'SIGN'            => array(  26,    1,    1,    0 ),
1407           'ROUND'           => array(  27,    2,    1,    0 ),
1408           'LOOKUP'          => array(  28,   -1,    0,    0 ),
1409           'INDEX'           => array(  29,   -1,    0,    1 ),
1410           'REPT'            => array(  30,    2,    1,    0 ),
1411           'MID'             => array(  31,    3,    1,    0 ),
1412           'LEN'             => array(  32,    1,    1,    0 ),
1413           'VALUE'           => array(  33,    1,    1,    0 ),
1414           'TRUE'            => array(  34,    0,    1,    0 ),
1415           'FALSE'           => array(  35,    0,    1,    0 ),
1416           'AND'             => array(  36,   -1,    0,    0 ),
1417           'OR'              => array(  37,   -1,    0,    0 ),
1418           'NOT'             => array(  38,    1,    1,    0 ),
1419           'MOD'             => array(  39,    2,    1,    0 ),
1420           'DCOUNT'          => array(  40,    3,    0,    0 ),
1421           'DSUM'            => array(  41,    3,    0,    0 ),
1422           'DAVERAGE'        => array(  42,    3,    0,    0 ),
1423           'DMIN'            => array(  43,    3,    0,    0 ),
1424           'DMAX'            => array(  44,    3,    0,    0 ),
1425           'DSTDEV'          => array(  45,    3,    0,    0 ),
1426           'VAR'             => array(  46,   -1,    0,    0 ),
1427           'DVAR'            => array(  47,    3,    0,    0 ),
1428           'TEXT'            => array(  48,    2,    1,    0 ),
1429           'LINEST'          => array(  49,   -1,    0,    0 ),
1430           'TREND'           => array(  50,   -1,    0,    0 ),
1431           'LOGEST'          => array(  51,   -1,    0,    0 ),
1432           'GROWTH'          => array(  52,   -1,    0,    0 ),
1433           'PV'              => array(  56,   -1,    1,    0 ),
1434           'FV'              => array(  57,   -1,    1,    0 ),
1435           'NPER'            => array(  58,   -1,    1,    0 ),
1436           'PMT'             => array(  59,   -1,    1,    0 ),
1437           'RATE'            => array(  60,   -1,    1,    0 ),
1438           'MIRR'            => array(  61,    3,    0,    0 ),
1439           'IRR'             => array(  62,   -1,    0,    0 ),
1440           'RAND'            => array(  63,    0,    1,    1 ),
1441           'MATCH'           => array(  64,   -1,    0,    0 ),
1442           'DATE'            => array(  65,    3,    1,    0 ),
1443           'TIME'            => array(  66,    3,    1,    0 ),
1444           'DAY'             => array(  67,    1,    1,    0 ),
1445           'MONTH'           => array(  68,    1,    1,    0 ),
1446           'YEAR'            => array(  69,    1,    1,    0 ),
1447           'WEEKDAY'         => array(  70,   -1,    1,    0 ),
1448           'HOUR'            => array(  71,    1,    1,    0 ),
1449           'MINUTE'          => array(  72,    1,    1,    0 ),
1450           'SECOND'          => array(  73,    1,    1,    0 ),
1451           'NOW'             => array(  74,    0,    1,    1 ),
1452           'AREAS'           => array(  75,    1,    0,    1 ),
1453           'ROWS'            => array(  76,    1,    0,    1 ),
1454           'COLUMNS'         => array(  77,    1,    0,    1 ),
1455           'OFFSET'          => array(  78,   -1,    0,    1 ),
1456           'SEARCH'          => array(  82,   -1,    1,    0 ),
1457           'TRANSPOSE'       => array(  83,    1,    1,    0 ),
1458           'TYPE'            => array(  86,    1,    1,    0 ),
1459           'ATAN2'           => array(  97,    2,    1,    0 ),
1460           'ASIN'            => array(  98,    1,    1,    0 ),
1461           'ACOS'            => array(  99,    1,    1,    0 ),
1462           'CHOOSE'          => array( 100,   -1,    1,    0 ),
1463           'HLOOKUP'         => array( 101,   -1,    0,    0 ),
1464           'VLOOKUP'         => array( 102,   -1,    0,    0 ),
1465           'ISREF'           => array( 105,    1,    0,    0 ),
1466           'LOG'             => array( 109,   -1,    1,    0 ),
1467           'CHAR'            => array( 111,    1,    1,    0 ),
1468           'LOWER'           => array( 112,    1,    1,    0 ),
1469           'UPPER'           => array( 113,    1,    1,    0 ),
1470           'PROPER'          => array( 114,    1,    1,    0 ),
1471           'LEFT'            => array( 115,   -1,    1,    0 ),
1472           'RIGHT'           => array( 116,   -1,    1,    0 ),
1473           'EXACT'           => array( 117,    2,    1,    0 ),
1474           'TRIM'            => array( 118,    1,    1,    0 ),
1475           'REPLACE'         => array( 119,    4,    1,    0 ),
1476           'SUBSTITUTE'      => array( 120,   -1,    1,    0 ),
1477           'CODE'            => array( 121,    1,    1,    0 ),
1478           'FIND'            => array( 124,   -1,    1,    0 ),
1479           'CELL'            => array( 125,   -1,    0,    1 ),
1480           'ISERR'           => array( 126,    1,    1,    0 ),
1481           'ISTEXT'          => array( 127,    1,    1,    0 ),
1482           'ISNUMBER'        => array( 128,    1,    1,    0 ),
1483           'ISBLANK'         => array( 129,    1,    1,    0 ),
1484           'T'               => array( 130,    1,    0,    0 ),
1485           'N'               => array( 131,    1,    0,    0 ),
1486           'DATEVALUE'       => array( 140,    1,    1,    0 ),
1487           'TIMEVALUE'       => array( 141,    1,    1,    0 ),
1488           'SLN'             => array( 142,    3,    1,    0 ),
1489           'SYD'             => array( 143,    4,    1,    0 ),
1490           'DDB'             => array( 144,   -1,    1,    0 ),
1491           'INDIRECT'        => array( 148,   -1,    1,    1 ),
1492           'CALL'            => array( 150,   -1,    1,    0 ),
1493           'CLEAN'           => array( 162,    1,    1,    0 ),
1494           'MDETERM'         => array( 163,    1,    2,    0 ),
1495           'MINVERSE'        => array( 164,    1,    2,    0 ),
1496           'MMULT'           => array( 165,    2,    2,    0 ),
1497           'IPMT'            => array( 167,   -1,    1,    0 ),
1498           'PPMT'            => array( 168,   -1,    1,    0 ),
1499           'COUNTA'          => array( 169,   -1,    0,    0 ),
1500           'PRODUCT'         => array( 183,   -1,    0,    0 ),
1501           'FACT'            => array( 184,    1,    1,    0 ),
1502           'DPRODUCT'        => array( 189,    3,    0,    0 ),
1503           'ISNONTEXT'       => array( 190,    1,    1,    0 ),
1504           'STDEVP'          => array( 193,   -1,    0,    0 ),
1505           'VARP'            => array( 194,   -1,    0,    0 ),
1506           'DSTDEVP'         => array( 195,    3,    0,    0 ),
1507           'DVARP'           => array( 196,    3,    0,    0 ),
1508           'TRUNC'           => array( 197,   -1,    1,    0 ),
1509           'ISLOGICAL'       => array( 198,    1,    1,    0 ),
1510           'DCOUNTA'         => array( 199,    3,    0,    0 ),
1511           'ROUNDUP'         => array( 212,    2,    1,    0 ),
1512           'ROUNDDOWN'       => array( 213,    2,    1,    0 ),
1513           'RANK'            => array( 216,   -1,    0,    0 ),
1514           'ADDRESS'         => array( 219,   -1,    1,    0 ),
1515           'DAYS360'         => array( 220,   -1,    1,    0 ),
1516           'TODAY'           => array( 221,    0,    1,    1 ),
1517           'VDB'             => array( 222,   -1,    1,    0 ),
1518           'MEDIAN'          => array( 227,   -1,    0,    0 ),
1519           'SUMPRODUCT'      => array( 228,   -1,    2,    0 ),
1520           'SINH'            => array( 229,    1,    1,    0 ),
1521           'COSH'            => array( 230,    1,    1,    0 ),
1522           'TANH'            => array( 231,    1,    1,    0 ),
1523           'ASINH'           => array( 232,    1,    1,    0 ),
1524           'ACOSH'           => array( 233,    1,    1,    0 ),
1525           'ATANH'           => array( 234,    1,    1,    0 ),
1526           'DGET'            => array( 235,    3,    0,    0 ),
1527           'INFO'            => array( 244,    1,    1,    1 ),
1528           'DB'              => array( 247,   -1,    1,    0 ),
1529           'FREQUENCY'       => array( 252,    2,    0,    0 ),
1530           'ERROR.TYPE'      => array( 261,    1,    1,    0 ),
1531           'REGISTER.ID'     => array( 267,   -1,    1,    0 ),
1532           'AVEDEV'          => array( 269,   -1,    0,    0 ),
1533           'BETADIST'        => array( 270,   -1,    1,    0 ),
1534           'GAMMALN'         => array( 271,    1,    1,    0 ),
1535           'BETAINV'         => array( 272,   -1,    1,    0 ),
1536           'BINOMDIST'       => array( 273,    4,    1,    0 ),
1537           'CHIDIST'         => array( 274,    2,    1,    0 ),
1538           'CHIINV'          => array( 275,    2,    1,    0 ),
1539           'COMBIN'          => array( 276,    2,    1,    0 ),
1540           'CONFIDENCE'      => array( 277,    3,    1,    0 ),
1541           'CRITBINOM'       => array( 278,    3,    1,    0 ),
1542           'EVEN'            => array( 279,    1,    1,    0 ),
1543           'EXPONDIST'       => array( 280,    3,    1,    0 ),
1544           'FDIST'           => array( 281,    3,    1,    0 ),
1545           'FINV'            => array( 282,    3,    1,    0 ),
1546           'FISHER'          => array( 283,    1,    1,    0 ),
1547           'FISHERINV'       => array( 284,    1,    1,    0 ),
1548           'FLOOR'           => array( 285,    2,    1,    0 ),
1549           'GAMMADIST'       => array( 286,    4,    1,    0 ),
1550           'GAMMAINV'        => array( 287,    3,    1,    0 ),
1551           'CEILING'         => array( 288,    2,    1,    0 ),
1552           'HYPGEOMDIST'     => array( 289,    4,    1,    0 ),
1553           'LOGNORMDIST'     => array( 290,    3,    1,    0 ),
1554           'LOGINV'          => array( 291,    3,    1,    0 ),
1555           'NEGBINOMDIST'    => array( 292,    3,    1,    0 ),
1556           'NORMDIST'        => array( 293,    4,    1,    0 ),
1557           'NORMSDIST'       => array( 294,    1,    1,    0 ),
1558           'NORMINV'         => array( 295,    3,    1,    0 ),
1559           'NORMSINV'        => array( 296,    1,    1,    0 ),
1560           'STANDARDIZE'     => array( 297,    3,    1,    0 ),
1561           'ODD'             => array( 298,    1,    1,    0 ),
1562           'PERMUT'          => array( 299,    2,    1,    0 ),
1563           'POISSON'         => array( 300,    3,    1,    0 ),
1564           'TDIST'           => array( 301,    3,    1,    0 ),
1565           'WEIBULL'         => array( 302,    4,    1,    0 ),
1566           'SUMXMY2'         => array( 303,    2,    2,    0 ),
1567           'SUMX2MY2'        => array( 304,    2,    2,    0 ),
1568           'SUMX2PY2'        => array( 305,    2,    2,    0 ),
1569           'CHITEST'         => array( 306,    2,    2,    0 ),
1570           'CORREL'          => array( 307,    2,    2,    0 ),
1571           'COVAR'           => array( 308,    2,    2,    0 ),
1572           'FORECAST'        => array( 309,    3,    2,    0 ),
1573           'FTEST'           => array( 310,    2,    2,    0 ),
1574           'INTERCEPT'       => array( 311,    2,    2,    0 ),
1575           'PEARSON'         => array( 312,    2,    2,    0 ),
1576           'RSQ'             => array( 313,    2,    2,    0 ),
1577           'STEYX'           => array( 314,    2,    2,    0 ),
1578           'SLOPE'           => array( 315,    2,    2,    0 ),
1579           'TTEST'           => array( 316,    4,    2,    0 ),
1580           'PROB'            => array( 317,   -1,    2,    0 ),
1581           'DEVSQ'           => array( 318,   -1,    0,    0 ),
1582           'GEOMEAN'         => array( 319,   -1,    0,    0 ),
1583           'HARMEAN'         => array( 320,   -1,    0,    0 ),
1584           'SUMSQ'           => array( 321,   -1,    0,    0 ),
1585           'KURT'            => array( 322,   -1,    0,    0 ),
1586           'SKEW'            => array( 323,   -1,    0,    0 ),
1587           'ZTEST'           => array( 324,   -1,    0,    0 ),
1588           'LARGE'           => array( 325,    2,    0,    0 ),
1589           'SMALL'           => array( 326,    2,    0,    0 ),
1590           'QUARTILE'        => array( 327,    2,    0,    0 ),
1591           'PERCENTILE'      => array( 328,    2,    0,    0 ),
1592           'PERCENTRANK'     => array( 329,   -1,    0,    0 ),
1593           'MODE'            => array( 330,   -1,    2,    0 ),
1594           'TRIMMEAN'        => array( 331,    2,    0,    0 ),
1595           'TINV'            => array( 332,    2,    1,    0 ),
1596           'CONCATENATE'     => array( 336,   -1,    1,    0 ),
1597           'POWER'           => array( 337,    2,    1,    0 ),
1598           'RADIANS'         => array( 342,    1,    1,    0 ),
1599           'DEGREES'         => array( 343,    1,    1,    0 ),
1600           'SUBTOTAL'        => array( 344,   -1,    0,    0 ),
1601           'SUMIF'           => array( 345,   -1,    0,    0 ),
1602           'COUNTIF'         => array( 346,    2,    0,    0 ),
1603           'COUNTBLANK'      => array( 347,    1,    0,    0 ),
1604           'ROMAN'           => array( 354,   -1,    1,    0 )
1605           );
1606     }
1607
1608 /**
1609 * Convert a token to the proper ptg value.
1610 *
1611 * @param mixed $token The token to convert.
1612 */
1613   function _convert($token)
1614     {
1615     if(is_numeric($token))
1616         {
1617         return($this->_convert_number($token));
1618         }
1619     // match references like A1
1620     elseif(preg_match("/^([A-I]?[A-Z])(\d+)$/",$token))
1621         {
1622         return($this->_convert_ref2d($token));
1623         }
1624     // match ranges like A1:B2
1625     elseif(preg_match("/^([A-I]?[A-Z])(\d+)\:([A-I]?[A-Z])(\d+)$/",$token))
1626         {
1627         return($this->_convert_range2d($token));
1628         }
1629     // match ranges like A1..B2
1630     elseif(preg_match("/^([A-I]?[A-Z])(\d+)\.\.([A-I]?[A-Z])(\d+)$/",$token))
1631         {
1632         return($this->_convert_range2d($token));
1633         }
1634     elseif(isset($this->ptg[$token])) // operators (including parentheses)
1635         {
1636         return(pack("C", $this->ptg[$token]));
1637         }
1638     elseif(preg_match("/[A-Z0-9À-Ãœ\.]+/",$token))
1639         {
1640         return($this->_convert_function($token,$this->_func_args));
1641         }
1642     // if it's an argument, ignore the token (the argument remains)
1643     elseif($token == 'arg')
1644         {
1645         $this->_func_args++;
1646         return('');
1647         }
1648     die("Unknown token $token");
1649     }
1650
1651 /**
1652 * Convert a number token to ptgInt or ptgNum
1653 *
1654 * @param mixed $num an integer or double for conersion to its ptg value
1655 */
1656   function _convert_number($num)
1657     {
1658     // Integer in the range 0..2**16-1
1659     if ((preg_match("/^\d+$/",$num)) and ($num <= 65535)) {
1660         return pack("Cv", $this->ptg['ptgInt'], $num);
1661         }
1662     else // A float
1663         {
1664         if($this->_byte_order) // if it's Big Endian
1665             {
1666             $num = strrev($num);
1667             }
1668         return pack("Cd", $this->ptg['ptgNum'], $num);
1669         }
1670     }
1671
1672 /**
1673 * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
1674 * args that it takes.
1675 *
1676 * @param string  $token    The name of the function for convertion to ptg value.
1677 * @param integer $num_args The number of arguments the function recieves.
1678 */
1679   function _convert_function($token, $num_args)
1680     {
1681     $this->_func_args = 0; // re initialize the number of arguments
1682     $args     = $this->_functions[$token][1];
1683     $volatile = $this->_functions[$token][3];
1684
1685     if($volatile) {
1686         $this->_volatile = 1;
1687         }
1688     // Fixed number of args eg. TIME($i,$j,$k).
1689     if ($args >= 0)
1690         {
1691         return(pack("Cv", $this->ptg['ptgFuncV'], $this->_functions[$token][0]));
1692         }
1693     // Variable number of args eg. SUM($i,$j,$k, ..).
1694     if ($args == -1) {
1695         return(pack("CCv", $this->ptg['ptgFuncVarV'], $num_args, $this->_functions[$token][0]));
1696         }
1697     }
1698
1699 /**
1700 * Convert an Excel range such as A1:D4 to a ptgRefV.
1701 *
1702 * @param string $range An Excel range in the A1:A2 or A1..A2 format.
1703 */
1704   function _convert_range2d($range)
1705     {
1706     $class = 2; // as far as I know, this is magick.
1707
1708     // Split the range into 2 cell refs
1709     if(preg_match("/^([A-I]?[A-Z])(\d+)\:([A-I]?[A-Z])(\d+)$/",$range)) {
1710         list($cell1, $cell2) = split(':', $range);
1711         }
1712     elseif(preg_match("/^([A-I]?[A-Z])(\d+)\.\.([A-I]?[A-Z])(\d+)$/",$range)) {
1713         list($cell1, $cell2) = split('\.\.', $range);
1714         }
1715     else {
1716         die("Unknown range separator");
1717         }
1718
1719     // Convert the cell references
1720     list($row1, $col1) = $this->_cell_to_packed_rowcol($cell1);
1721     list($row2, $col2) = $this->_cell_to_packed_rowcol($cell2);
1722
1723     // The ptg value depends on the class of the ptg.
1724     if ($class == 0) {
1725         $ptgArea = pack("C", $this->ptg['ptgArea']);
1726         }
1727     elseif ($class == 1) {
1728         $ptgArea = pack("C", $this->ptg['ptgAreaV']);
1729         }
1730     elseif ($class == 2) {
1731         $ptgArea = pack("C", $this->ptg['ptgAreaA']);
1732         }
1733     else{
1734         die("Unknown class ");
1735         }
1736
1737     return($ptgArea . $row1 . $row2 . $col1. $col2);
1738     }
1739
1740 /**
1741 * Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
1742 *
1743 * @param string $cell An Excel cell reference
1744 */
1745   function _convert_ref2d($cell)
1746     {
1747     $class = 2; // as far as I know, this is magick.
1748
1749     // Convert the cell reference
1750     list($row, $col) = $this->_cell_to_packed_rowcol($cell);
1751
1752     // The ptg value depends on the class of the ptg.
1753     if ($class == 0) {
1754         $ptgRef = pack("C", $this->ptg['ptgRef']);
1755         }
1756     elseif ($class == 1) {
1757         $ptgRef = pack("C", $this->ptg['ptgRefV']);
1758         }
1759     elseif ($class == 2) {
1760         $ptgRef = pack("C", $this->ptg['ptgRefA']);
1761         }
1762     else{
1763         die("Unknown class ");
1764         }
1765     return $ptgRef.$row.$col;
1766     }
1767
1768 /**
1769 * pack() row and column into the required 3 byte format.
1770 *
1771 * @param string $cell The Excel cell reference to be packed
1772 */
1773   function _cell_to_packed_rowcol($cell)
1774     {
1775     list($row, $col, $row_rel, $col_rel) = $this->_cell_to_rowcol($cell);
1776     if ($col >= 256) {
1777         die("Column in: $cell greater than 255 ");
1778         }
1779     if ($row >= 16384) {
1780         die("Row in: $cell greater than 16384 ");
1781         }
1782
1783     // Set the high bits to indicate if row or col are relative.
1784     $row    |= $col_rel << 14;
1785     $row    |= $row_rel << 15;
1786
1787     $row     = pack('v', $row);
1788     $col     = pack('C', $col);
1789
1790     return (array($row, $col));
1791     }
1792
1793 /**
1794 * Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
1795 * indexed row and column number. Also returns two boolean values to indicate
1796 * whether the row or column are relative references.
1797 *
1798 * @param string $cell The Excel cell reference in A1 format.
1799 */
1800   function _cell_to_rowcol($cell)
1801     {
1802     preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/',$cell,$match);
1803     // return absolute column if there is a $ in the ref
1804     $col_rel = empty($match[1]) ? 1 : 0;
1805     $col_ref = $match[2];
1806     $row_rel = empty($match[3]) ? 1 : 0;
1807     $row     = $match[4];
1808
1809     // Convert base26 column string to a number.
1810     $expn   = strlen($col_ref) - 1;
1811     $col    = 0;
1812     for($i=0; $i < strlen($col_ref); $i++)
1813     {
1814         $col += (ord($col_ref{$i}) - ord('A') + 1) * pow(26, $expn);
1815         $expn--;
1816     }
1817
1818     // Convert 1-index to zero-index
1819     $row--;
1820     $col--;
1821
1822     return(array($row, $col, $row_rel, $col_rel));
1823     }
1824
1825 /**
1826 * Advance to the next valid token.
1827 */
1828   function _advance()
1829     {
1830     $i = $this->_current_char;
1831     // eat up white spaces
1832     if($i < strlen($this->_formula))
1833         {
1834         while($this->_formula{$i} == " ")
1835             {
1836             $i++;
1837             }
1838         if($i < strlen($this->_formula) - 1)
1839             {
1840             $this->_lookahead = $this->_formula{$i+1};
1841             }
1842         $token = "";
1843         }
1844     while($i < strlen($this->_formula))
1845         {
1846         $token .= $this->_formula{$i};
1847         if($this->_match($token) != '')
1848             {
1849             if($i < strlen($this->_formula) - 1)
1850                 {
1851                 $this->_lookahead = $this->_formula{$i+1};
1852                 }
1853             $this->_current_char = $i + 1;
1854             $this->_current_token = $token;
1855             return(1);
1856             }
1857         $this->_lookahead = $this->_formula{$i+2};
1858         $i++;
1859         }
1860     //die("Lexical error ".$this->_current_char);
1861     }
1862
1863 /**
1864 * Checks if it's a valid token.
1865 *
1866 * @param mixed $token The token to check.
1867 */
1868   function _match($token)
1869     {
1870     switch($token)
1871         {
1872         case ADD:
1873             return($token);
1874             break;
1875         case SUB:
1876             return($token);
1877             break;
1878         case MUL:
1879             return($token);
1880             break;
1881         case DIV:
1882             return($token);
1883             break;
1884         case OPEN:
1885             return($token);
1886             break;
1887         case CLOSE:
1888             return($token);
1889             break;
1890         case COMA:
1891             return($token);
1892             break;
1893         default:
1894             // if it's a reference
1895             if(eregi("^[A-I]?[A-Z][0-9]+$",$token) and 
1896                !ereg("[0-9]",$this->_lookahead) and 
1897                ($this->_lookahead != ':') and ($this->_lookahead != '.'))
1898                 {
1899                 return($token);
1900                 }
1901             // if it's a range (A1:A2)
1902             elseif(eregi("^[A-I]?[A-Z][0-9]+:[A-I]?[A-Z][0-9]+$",$token) and 
1903                    !ereg("[0-9]",$this->_lookahead))
1904                 {
1905                 return($token);
1906                 }
1907             // if it's a range (A1..A2)
1908             elseif(eregi("^[A-I]?[A-Z][0-9]+\.\.[A-I]?[A-Z][0-9]+$",$token) and 
1909                    !ereg("[0-9]",$this->_lookahead))
1910                 {
1911                 return($token);
1912                 }
1913             elseif(is_numeric($token) and !is_numeric($token.$this->_lookahead))
1914                 {
1915                 return($token);
1916                 }
1917             // if it's a function call
1918             elseif(eregi("^[A-Z0-9À-Ãœ\.]+$",$token) and ($this->_lookahead == "("))
1919
1920                 {
1921                 return($token);
1922                 }
1923             return '';
1924         }
1925     }
1926
1927 /**
1928 * The parsing method. It parses a formula.
1929 *
1930 * @access public
1931 * @param string $formula The formula to parse, without the initial equal sign (=).
1932 */
1933   function parse($formula)
1934     {
1935     $this->_current_char = 0;
1936     $this->_formula      = $formula;
1937     $this->_lookahead    = $formula{1};
1938     $this->_advance();
1939     $this->_parse_tree   = $this->_expression();
1940     }
1941
1942 /**
1943 * It parses a expression. It assumes the following rule:
1944 * Expr -> Term [("+" | "-") Term]
1945 *
1946 * @return mixed The parsed ptg'd tree
1947 */
1948   function _expression()
1949     {
1950     $result = $this->_term();
1951     while ($this->_current_token == ADD or $this->_current_token == SUB)
1952         {
1953         if ($this->_current_token == ADD)
1954             {
1955             $this->_advance();
1956             $result = $this->_create_tree('ptgAdd', $result, $this->_term());
1957             }
1958         else 
1959             {
1960             $this->_advance();
1961             $result = $this->_create_tree('ptgSub', $result, $this->_term());
1962             }
1963         }
1964     return $result;
1965     }
1966
1967 /**
1968 * This function just introduces a ptgParen element in the tree, so that Excel
1969 * doesn't get confused when working with a parenthesized formula afterwards.
1970 *
1971 * @see _fact
1972 * @return mixed The parsed ptg'd tree
1973 */
1974   function _parenthesized_expression()
1975     {
1976     $result = $this->_create_tree('ptgParen', $this->_expression(), '');
1977     return($result);
1978     }
1979
1980 /**
1981 * It parses a term. It assumes the following rule:
1982 * Term -> Fact [("*" | "/") Fact]
1983 *
1984 * @return mixed The parsed ptg'd tree
1985 */
1986   function _term()
1987     {
1988     $result = $this->_fact();
1989     while ($this->_current_token == MUL || $this->_current_token == DIV)
1990         {
1991         if ($this->_current_token == MUL)
1992             {
1993             $this->_advance();
1994             $result = $this->_create_tree('ptgMul', $result, $this->_fact());
1995             }
1996         else 
1997             {
1998             $this->_advance();
1999             $result = $this->_create_tree('ptgDiv', $result, $this->_fact());
2000             }
2001         }
2002     return($result);
2003     }
2004
2005 /**
2006 * It parses a factor. It assumes the following rule:
2007 * Fact -> ( Expr )
2008 *       | CellRef
2009 *       | CellRange
2010 *       | Number
2011 *       | Function
2012 *
2013 * @return mixed The parsed ptg'd tree
2014 */
2015   function _fact()
2016     {
2017     if ($this->_current_token == OPEN)
2018         {
2019         $this->_advance();         // eat the "("
2020         $result = $this->_parenthesized_expression();//$this->_expression();
2021
2022         if ($this->_current_token != CLOSE) {
2023             die("')' token expected.");
2024             }
2025         $this->_advance();         // eat the ")"
2026         return($result);
2027         }
2028     // if it's a reference
2029     if (eregi("^[A-I]?[A-Z][0-9]+$",$this->_current_token))
2030         {
2031         $result = $this->_create_tree($this->_current_token, '', '');
2032         $this->_advance();
2033         return($result);
2034         }
2035     // if it's a range
2036     elseif (eregi("^[A-I]?[A-Z][0-9]+:[A-I]?[A-Z][0-9]+$",$this->_current_token) or 
2037             eregi("^[A-I]?[A-Z][0-9]+\.\.[A-I]?[A-Z][0-9]+$",$this->_current_token)) 
2038         {
2039         $result = $this->_current_token;
2040         $this->_advance();
2041         return($result);
2042         }
2043     elseif (is_numeric($this->_current_token))
2044         {
2045         $result = $this->_create_tree($this->_current_token, '', '');
2046         $this->_advance();
2047         return($result);
2048         }
2049     // if it's a function call
2050     elseif (eregi("^[A-Z0-9À-Ãœ\.]+$",$this->_current_token))
2051         {
2052         $result = $this->_func();
2053         return($result);
2054         }
2055     die("Sintactic error: ".$this->_current_token.", lookahead: ".
2056         $this->_lookahead.", current char: ".$this->_current_char);
2057     }
2058
2059 /**
2060 * It parses a function call. It assumes the following rule:
2061 * Func -> ( Expr [,Expr]* )
2062 *
2063 */
2064   function _func()
2065     {
2066     $num_args = 0; // number of arguments received
2067     $function = $this->_current_token;
2068     $this->_advance();
2069     $this->_advance();         // eat the "("
2070     while($this->_current_token != ')')
2071         {
2072         if($num_args > 0)
2073             {
2074             if($this->_current_token == COMA) {
2075                 $this->_advance();  // eat the ","
2076                 }
2077             else {
2078                 die("Sintactic error: coma expected $num_args");
2079                 }
2080             $result = $this->_create_tree('arg', $result, $this->_expression());
2081             }
2082         else {
2083             $result = $this->_create_tree('arg', '', $this->_expression());
2084             }
2085         $num_args++;
2086         }
2087     $args = $this->_functions[$function][1];
2088     // If fixed number of args eg. TIME($i,$j,$k). Check that the number of args is valid.
2089     if (($args >= 0) and ($args != $num_args))
2090         {
2091         die("Incorrect number of arguments in function $function() ");
2092         }
2093
2094     $result = $this->_create_tree($function, $result, '');
2095     $this->_advance();         // eat the ")"
2096     return($result);
2097     }
2098
2099 /**
2100 * Creates a tree. In fact an array which may have one or two arrays (sub-trees)
2101 * as elements.
2102 *
2103 * @param mixed $value The value of this node.
2104 * @param mixed $left  The left array (sub-tree) or a final node.
2105 * @param mixed $right The right array (sub-tree) or a final node.
2106 */
2107   function _create_tree($value, $left, $right)
2108     {
2109     return array('value' => $value, 'left' => $left, 'right' => $right);
2110     }
2111
2112 /**
2113 * Builds a string containing the tree in reverse polish notation (What you 
2114 * would use in a HP calculator stack).
2115 * The following tree:
2116
2117 *    +
2118 *   / \
2119 *  2   3
2120 *
2121 * produces: "23+"
2122 *
2123 * The following tree:
2124 *
2125 *    +
2126 *   / \
2127 *  3   *
2128 *     / \
2129 *    6   A1
2130 *
2131 * produces: "36A1*+"
2132 *
2133 * In fact all operands, functions, references, etc... are written as ptg's
2134 *
2135 * @access public
2136 * @param array $tree The optional tree to convert.
2137 */
2138   function to_reverse_polish($tree = array())
2139     {
2140     $polish = ""; // the string we are going to return
2141     if (empty($tree)) // If it's the first call use _parse_tree
2142         {
2143         $tree = $this->_parse_tree;
2144         }
2145     if (is_array($tree['left']))
2146         {
2147         $polish .= $this->to_reverse_polish($tree['left']);
2148         }
2149     elseif($tree['left'] != '') // It's a final node
2150         {
2151         $polish .= $this->_convert($tree['left']); //$tree['left'];
2152         }
2153     if (is_array($tree['right']))
2154         {
2155         $polish .= $this->to_reverse_polish($tree['right']);
2156         }
2157     elseif($tree['right'] != '') // It's a final node
2158         {
2159         $polish .= $this->_convert($tree['right']);
2160         }
2161     $polish .= $this->_convert($tree['value']);
2162     return $polish;
2163     }
2164   }
2165
2166 /**
2167 * Class for generating Excel Spreadsheets
2168 *
2169 * @author Xavier Noguer <xnoguer@rezebra.com>
2170 * @package Spreadsheet_WriteExcel
2171 */
2172
2173 class Worksheet extends BIFFwriter
2174 {
2175
2176     /**
2177     * Constructor
2178     *
2179     * @param string  $name         The name of the new worksheet
2180     * @param integer $index        The index of the new worksheet
2181     * @param mixed   &$activesheet The current activesheet of the workbook we belong to
2182     * @param mixed   &$firstsheet  The first worksheet in the workbook we belong to 
2183     * @param mixed   &$url_format  The default format for hyperlinks
2184     * @param mixed   &$parser      The formula parser created for the Workbook
2185     */
2186     function Worksheet($name,$index,&$activesheet,&$firstsheet,&$url_format,&$parser)
2187     {
2188         $this->BIFFwriter();     // It needs to call its parent's constructor explicitly
2189         $rowmax                = 65536; // 16384 in Excel 5
2190         $colmax                = 256;
2191         $strmax                = 255;
2192     
2193         $this->name            = $name;
2194         $this->index           = $index;
2195         $this->activesheet     = &$activesheet;
2196         $this->firstsheet      = &$firstsheet;
2197         $this->_url_format     = $url_format;
2198         $this->_parser         = &$parser;
2199     
2200         $this->ext_sheets      = array();
2201         $this->_using_tmpfile  = 1;
2202         $this->_filehandle     = "";
2203         $this->fileclosed      = 0;
2204         $this->offset          = 0;
2205         $this->xls_rowmax      = $rowmax;
2206         $this->xls_colmax      = $colmax;
2207         $this->xls_strmax      = $strmax;
2208         $this->dim_rowmin      = $rowmax +1;
2209         $this->dim_rowmax      = 0;
2210         $this->dim_colmin      = $colmax +1;
2211         $this->dim_colmax      = 0;
2212         $this->colinfo         = array();
2213         $this->_selection      = array(0,0,0,0);
2214         $this->_panes          = array();
2215         $this->_active_pane    = 3;
2216         $this->_frozen         = 0;
2217         $this->selected        = 0;
2218     
2219         $this->_paper_size      = 0x0;
2220         $this->_orientation     = 0x1;
2221         $this->_header          = '';
2222         $this->_footer          = '';
2223         $this->_hcenter         = 0;
2224         $this->_vcenter         = 0;
2225         $this->_margin_head     = 0.50;
2226         $this->_margin_foot     = 0.50;
2227         $this->_margin_left     = 0.75;
2228         $this->_margin_right    = 0.75;
2229         $this->_margin_top      = 1.00;
2230         $this->_margin_bottom   = 1.00;
2231     
2232         $this->_title_rowmin    = NULL;
2233         $this->_title_rowmax    = NULL;
2234         $this->_title_colmin    = NULL;
2235         $this->_title_colmax    = NULL;
2236         $this->_print_rowmin    = NULL;
2237         $this->_print_rowmax    = NULL;
2238         $this->_print_colmin    = NULL;
2239         $this->_print_colmax    = NULL;
2240     
2241         $this->_print_gridlines = 1;
2242         $this->_print_headers   = 0;
2243     
2244         $this->_fit_page        = 0;
2245         $this->_fit_width       = 0;
2246         $this->_fit_height      = 0;
2247     
2248         $this->_hbreaks         = array();
2249         $this->_vbreaks         = array();
2250     
2251         $this->_protect         = 0;
2252         $this->_password        = NULL;
2253     
2254         $this->col_sizes        = array();
2255         $this->row_sizes        = array();
2256     
2257         $this->_zoom            = 100;
2258         $this->_print_scale     = 100;
2259         $this->_rtl                             = 0;    // Added by Joe Hunt 2009-03-05 for arabic languages
2260     
2261         $this->_initialize();
2262     }
2263     
2264     /**
2265     * Open a tmp file to store the majority of the Worksheet data. If this fails,
2266     * for example due to write permissions, store the data in memory. This can be
2267     * slow for large files.
2268     */
2269     function _initialize()
2270     {
2271         // Open tmp file for storing Worksheet data
2272         $fh = tmpfile();
2273         if ( $fh) {
2274             // Store filehandle
2275             $this->_filehandle = $fh;
2276         }
2277         else {
2278             // If tmpfile() fails store data in memory
2279             $this->_using_tmpfile = 0;
2280         }
2281     }
2282     
2283     /**
2284     * Add data to the beginning of the workbook (note the reverse order)
2285     * and to the end of the workbook.
2286     *
2287     * @access public 
2288     * @see Workbook::store_workbook()
2289     * @param array $sheetnames The array of sheetnames from the Workbook this 
2290     *                          worksheet belongs to
2291     */
2292     function close($sheetnames)
2293     {
2294         $num_sheets = count($sheetnames);
2295
2296         /***********************************************
2297         * Prepend in reverse order!!
2298         */
2299     
2300         // Prepend the sheet dimensions
2301         $this->_store_dimensions();
2302     
2303         // Prepend the sheet password
2304         $this->_store_password();
2305     
2306         // Prepend the sheet protection
2307         $this->_store_protect();
2308     
2309         // Prepend the page setup
2310         $this->_store_setup();
2311     
2312         // Prepend the bottom margin
2313         $this->_store_margin_bottom();
2314     
2315         // Prepend the top margin
2316         $this->_store_margin_top();
2317     
2318         // Prepend the right margin
2319         $this->_store_margin_right();
2320     
2321         // Prepend the left margin
2322         $this->_store_margin_left();
2323     
2324         // Prepend the page vertical centering
2325         $this->store_vcenter();
2326     
2327         // Prepend the page horizontal centering
2328         $this->store_hcenter();
2329     
2330         // Prepend the page footer
2331         $this->store_footer();
2332     
2333         // Prepend the page header
2334         $this->store_header();
2335     
2336         // Prepend the vertical page breaks
2337         $this->_store_vbreak();
2338     
2339         // Prepend the horizontal page breaks
2340         $this->_store_hbreak();
2341     
2342         // Prepend WSBOOL
2343         $this->_store_wsbool();
2344     
2345         // Prepend GRIDSET
2346         $this->_store_gridset();
2347     
2348         // Prepend PRINTGRIDLINES
2349         $this->_store_print_gridlines();
2350     
2351         // Prepend PRINTHEADERS
2352         $this->_store_print_headers();
2353     
2354         // Prepend EXTERNSHEET references
2355         for ($i = $num_sheets; $i > 0; $i--) {
2356             $sheetname = $sheetnames[$i-1];
2357             $this->_store_externsheet($sheetname);
2358         }
2359     
2360         // Prepend the EXTERNCOUNT of external references.
2361         $this->_store_externcount($num_sheets);
2362     
2363         // Prepend the COLINFO records if they exist
2364         if (!empty($this->colinfo)){
2365             for($i=0; $i < count($this->colinfo); $i++)
2366             {
2367                 $this->_store_colinfo($this->colinfo[$i]);
2368             }
2369             $this->_store_defcol();
2370         }
2371     
2372         // Prepend the BOF record
2373         $this->_store_bof(0x0010);
2374     
2375         /*
2376         * End of prepend. Read upwards from here.
2377         ***********************************************/
2378     
2379         // Append
2380         $this->_store_window2();
2381         $this->_store_zoom();
2382         if(!empty($this->_panes))
2383           $this->_store_panes($this->_panes);
2384         $this->_store_selection($this->_selection);
2385         $this->_store_eof();
2386     }
2387     
2388     /**
2389     * Retrieve the worksheet name. This is usefull when creating worksheets
2390     * without a name.
2391     *
2392     * @access public
2393     * @return string The worksheet's name
2394     */
2395     function get_name()
2396     {
2397         return($this->name);
2398     }
2399     
2400     /**
2401     * Retrieves data from memory in one chunk, or from disk in $buffer
2402     * sized chunks.
2403     *
2404     * @return string The data
2405     */
2406     function get_data()
2407     {
2408         $buffer = 4096;
2409     
2410         // Return data stored in memory
2411         if (isset($this->_data)) {
2412             $tmp   = $this->_data;
2413             unset($this->_data);
2414             $fh    = $this->_filehandle;
2415             if ($this->_using_tmpfile) {
2416                 fseek($fh, 0);
2417             }
2418             return($tmp);
2419         }
2420         // Return data stored on disk
2421         if ($this->_using_tmpfile) {
2422             if ($tmp = fread($this->_filehandle, $buffer)) {
2423                 return($tmp);
2424             }
2425         }
2426     
2427         // No data to return
2428         return('');
2429     }
2430     
2431     /**
2432     * Set this worksheet as a selected worksheet, i.e. the worksheet has its tab
2433     * highlighted.
2434     *
2435     * @access public
2436     */
2437     function select()
2438     {
2439         $this->selected = 1;
2440     }
2441     
2442     /**
2443     * Set this worksheet as the active worksheet, i.e. the worksheet that is
2444     * displayed when the workbook is opened. Also set it as selected.
2445     *
2446     * @access public
2447     */
2448     function activate()
2449     {
2450         $this->selected = 1;
2451         $this->activesheet =& $this->index;
2452     }
2453     
2454     /**
2455     * Set this worksheet as the first visible sheet. This is necessary
2456     * when there are a large number of worksheets and the activated
2457     * worksheet is not visible on the screen.
2458     *
2459     * @access public
2460     */
2461     function set_first_sheet()
2462     {
2463         $this->firstsheet = $this->index;
2464     }
2465     
2466     /**
2467     * Set the worksheet protection flag to prevent accidental modification and to
2468     * hide formulas if the locked and hidden format properties have been set.
2469     *
2470     * @access public
2471     * @param string $password The password to use for protecting the sheet.
2472     */
2473     function protect($password)
2474     {
2475         $this->_protect   = 1;
2476         $this->_password  = $this->_encode_password($password);
2477     }
2478     
2479     /**
2480     * Set the width of a single column or a range of columns.
2481     *
2482     * @access public
2483     * @see _store_colinfo()
2484     * @param integer $firstcol first column on the range
2485     * @param integer $lastcol  last column on the range
2486     * @param integer $width    width to set
2487     * @param mixed   $format   The optional XF format to apply to the columns
2488     * @param integer $hidden   The optional hidden atribute
2489     */
2490     function set_column($firstcol, $lastcol, $width, $format = null, $hidden = 0)
2491     {
2492         $this->colinfo[] = array($firstcol, $lastcol, $width, $format, $hidden);
2493
2494         // Set width to zero if column is hidden
2495         $width = ($hidden) ? 0 : $width;
2496     
2497         for($col = $firstcol; $col <= $lastcol; $col++) {
2498             $this->col_sizes[$col] = $width;
2499         }
2500     }
2501     
2502     /**
2503     * Set which cell or cells are selected in a worksheet
2504     *
2505     * @access public
2506     * @param integer $first_row    first row in the selected quadrant
2507     * @param integer $first_column first column in the selected quadrant
2508     * @param integer $last_row     last row in the selected quadrant
2509     * @param integer $last_column  last column in the selected quadrant
2510     * @see _store_selection()
2511     */
2512     function set_selection($first_row,$first_column,$last_row,$last_column)
2513     {
2514         $this->_selection = array($first_row,$first_column,$last_row,$last_column);
2515     }
2516     
2517     /**
2518     * Set panes and mark them as frozen.
2519     *
2520     * @access public
2521     * @param array $panes This is the only parameter received and is composed of the following:
2522     *                     0 => Vertical split position,
2523     *                     1 => Horizontal split position
2524     *                     2 => Top row visible
2525     *                     3 => Leftmost column visible
2526     *                     4 => Active pane
2527     */
2528     function freeze_panes($panes)
2529     {
2530         $this->_frozen = 1;
2531         $this->_panes  = $panes;
2532     }
2533     
2534     /**
2535     * Set panes and mark them as unfrozen.
2536     *
2537     * @access public
2538     * @param array $panes This is the only parameter received and is composed of the following:
2539     *                     0 => Vertical split position,
2540     *                     1 => Horizontal split position
2541     *                     2 => Top row visible
2542     *                     3 => Leftmost column visible
2543     *                     4 => Active pane
2544     */
2545     function thaw_panes($panes)
2546     {
2547         $this->_frozen = 0;
2548         $this->_panes  = $panes;
2549     }
2550     
2551     /**
2552     * Set the page orientation as portrait.
2553     *
2554     * @access public
2555     */
2556     function set_portrait()
2557     {
2558         $this->_orientation = 1;
2559     }
2560     
2561     /**
2562     * Set the page orientation as landscape.
2563     *
2564     * @access public
2565     */
2566     function set_landscape()
2567     {
2568         $this->_orientation = 0;
2569     }
2570     
2571     /**
2572     * Set the paper type. Ex. 1 = US Letter, 9 = A4
2573     *
2574     * @access public
2575     * @param integer $size The type of paper size to use
2576     */
2577     function set_paper($size = 0)
2578     {
2579         $this->_paper_size = $size;
2580     }
2581     
2582     
2583     /**
2584     * Set the page header caption and optional margin.
2585     *
2586     * @access public
2587     * @param string $string The header text
2588     * @param float  $margin optional head margin in inches.
2589     */
2590     function set_header($string,$margin = 0.50)
2591     {
2592         if (strlen($string) >= 255) {
2593             //carp 'Header string must be less than 255 characters';
2594             return;
2595         }
2596         $this->_header      = $string;
2597         $this->_margin_head = $margin;
2598     }
2599     
2600     /**
2601     * Set the page footer caption and optional margin.
2602     *
2603     * @access public
2604     * @param string $string The footer text
2605     * @param float  $margin optional foot margin in inches.
2606     */
2607     function set_footer($string,$margin = 0.50)
2608     {
2609         if (strlen($string) >= 255) {
2610             //carp 'Footer string must be less than 255 characters';
2611             return;
2612         }
2613         $this->_footer      = $string;
2614         $this->_margin_foot = $margin;
2615     }
2616     
2617     /**
2618     * Center the page horinzontally.
2619     *
2620     * @access public
2621     * @param integer $center the optional value for centering. Defaults to 1 (center).
2622     */
2623     function center_horizontally($center = 1)
2624     {
2625         $this->_hcenter = $center;
2626     }
2627     
2628     /**
2629     * Center the page horinzontally.
2630     *
2631     * @access public
2632     * @param integer $center the optional value for centering. Defaults to 1 (center).
2633     */
2634     function center_vertically($center = 1)
2635     {
2636         $this->_vcenter = $center;
2637     }
2638     
2639     /**
2640     * Set all the page margins to the same value in inches.
2641     *
2642     * @access public
2643     * @param float $margin The margin to set in inches
2644     */
2645     function set_margins($margin)
2646     {
2647         $this->set_margin_left($margin);
2648         $this->set_margin_right($margin);
2649         $this->set_margin_top($margin);
2650         $this->set_margin_bottom($margin);
2651     }
2652     
2653     /**
2654     * Set the left and right margins to the same value in inches.
2655     *
2656     * @access public
2657     * @param float $margin The margin to set in inches
2658     */
2659     function set_margins_LR($margin)
2660     {
2661         $this->set_margin_left($margin);
2662         $this->set_margin_right($margin);
2663     }
2664     
2665     /**
2666     * Set the top and bottom margins to the same value in inches.
2667     *
2668     * @access public
2669     * @param float $margin The margin to set in inches
2670     */
2671     function set_margins_TB($margin)
2672     {
2673         $this->set_margin_top($margin);
2674         $this->set_margin_bottom($margin);
2675     }
2676     
2677     /**
2678     * Set the left margin in inches.
2679     *
2680     * @access public
2681     * @param float $margin The margin to set in inches
2682     */
2683     function set_margin_left($margin = 0.75)
2684     {
2685         $this->_margin_left = $margin;
2686     }
2687     
2688     /**
2689     * Set the right margin in inches.
2690     *
2691     * @access public
2692     * @param float $margin The margin to set in inches
2693     */
2694     function set_margin_right($margin = 0.75)
2695     {
2696         $this->_margin_right = $margin;
2697     }
2698     
2699     /**
2700     * Set the top margin in inches.
2701     *
2702     * @access public
2703     * @param float $margin The margin to set in inches
2704     */
2705     function set_margin_top($margin = 1.00)
2706     {
2707         $this->_margin_top = $margin;
2708     }
2709     
2710     /**
2711     * Set the bottom margin in inches.
2712     *
2713     * @access public
2714     * @param float $margin The margin to set in inches
2715     */
2716     function set_margin_bottom($margin = 1.00)
2717     {
2718         $this->_margin_bottom = $margin;
2719     }
2720     
2721     /**
2722     * Set the rows to repeat at the top of each printed page. See also the
2723     * _store_name_xxxx() methods in Workbook.php
2724     *
2725     * @access public
2726     * @param integer $first_row First row to repeat
2727     * @param integer $last_row  Last row to repeat. Optional.
2728     */
2729     function repeat_rows($first_row, $last_row = NULL)
2730     {
2731         $this->_title_rowmin  = $first_row;
2732         if(isset($last_row)) { //Second row is optional
2733             $this->_title_rowmax  = $last_row;
2734         }
2735         else {
2736             $this->_title_rowmax  = $first_row;
2737         }
2738     }
2739     
2740     /**
2741     * Set the columns to repeat at the left hand side of each printed page.
2742     * See also the _store_names() methods in Workbook.php
2743     *
2744     * @access public
2745     * @param integer $first_col First column to repeat
2746     * @param integer $last_col  Last column to repeat. Optional.
2747     */
2748     function repeat_columns($first_col, $last_col = NULL)
2749     {
2750         $this->_title_colmin  = $first_col;
2751         if(isset($last_col)) { // Second col is optional
2752             $this->_title_colmax  = $last_col;
2753         }
2754         else {
2755             $this->_title_colmax  = $first_col;
2756         }
2757     }
2758     
2759     /**
2760     * Set the area of each worksheet that will be printed.
2761     *
2762     * @access public
2763     * @see Workbook::_store_names()
2764     * @param integer $first_row First row of the area to print
2765     * @param integer $first_col First column of the area to print
2766     * @param integer $last_row  Last row of the area to print
2767     * @param integer $last_col  Last column of the area to print
2768     */
2769     function print_area($first_row, $first_col, $last_row, $last_col)
2770     {
2771         $this->_print_rowmin  = $first_row;
2772         $this->_print_colmin  = $first_col;
2773         $this->_print_rowmax  = $last_row;
2774         $this->_print_colmax  = $last_col;
2775     }
2776     
2777     
2778     /**
2779     * Set the option to hide gridlines on the printed page. 
2780     *
2781     * @access public
2782     * @see _store_print_gridlines(), _store_gridset()
2783     */
2784     function hide_gridlines()
2785     {
2786         $this->_print_gridlines = 0;
2787     }
2788     
2789     /**
2790     * Set the option to print the row and column headers on the printed page.
2791     * See also the _store_print_headers() method below.
2792     *
2793     * @access public
2794     * @see _store_print_headers()
2795     * @param integer $print Whether to print the headers or not. Defaults to 1 (print).
2796     */
2797     function print_row_col_headers($print = 1)
2798     {
2799         $this->_print_headers = $print;
2800     }
2801     
2802     /**
2803     * Store the vertical and horizontal number of pages that will define the
2804     * maximum area printed. It doesn't seem to work with OpenOffice.
2805     *
2806     * @access public
2807     * @param  integer $width  Maximun width of printed area in pages
2808     * @param  integer $heigth Maximun heigth of printed area in pages
2809     * @see set_print_scale()
2810     */
2811     function fit_to_pages($width, $height)
2812     {
2813         $this->_fit_page      = 1;
2814         $this->_fit_width     = $width;
2815         $this->_fit_height    = $height;
2816     }
2817     
2818     /**
2819     * Store the horizontal page breaks on a worksheet (for printing).
2820     * The breaks represent the row after which the break is inserted.
2821     *
2822     * @access public
2823     * @param array $breaks Array containing the horizontal page breaks
2824     */
2825     function set_h_pagebreaks($breaks)
2826     {
2827         foreach($breaks as $break) {
2828             array_push($this->_hbreaks,$break);
2829         }
2830     }
2831     
2832     /**
2833     * Store the vertical page breaks on a worksheet (for printing).
2834     * The breaks represent the column after which the break is inserted.
2835     *
2836     * @access public
2837     * @param array $breaks Array containing the vertical page breaks
2838     */
2839     function set_v_pagebreaks($breaks)
2840     {
2841         foreach($breaks as $break) {
2842             array_push($this->_vbreaks,$break);
2843         }
2844     }
2845     
2846     
2847     /**
2848     * Set the worksheet zoom factor.
2849     *
2850     * @access public
2851     * @param integer $scale The zoom factor
2852     */
2853     function set_zoom($scale = 100)
2854     {
2855         // Confine the scale to Excel's range
2856         if ($scale < 10 or $scale > 400) {
2857             //carp "Zoom factor $scale outside range: 10 <= zoom <= 400";
2858             $scale = 100;
2859         }
2860     
2861         $this->_zoom = floor($scale);
2862     }
2863     
2864     /**
2865     * Set the scale factor for the printed page. 
2866     * It turns off the "fit to page" option
2867     *
2868     * @access public
2869     * @param integer $scale The optional scale factor. Defaults to 100
2870     */
2871     function set_print_scale($scale = 100)
2872     {
2873         // Confine the scale to Excel's range
2874         if ($scale < 10 or $scale > 400)
2875         {
2876             // REPLACE THIS FOR A WARNING
2877             die("Print scale $scale outside range: 10 <= zoom <= 400");
2878             $scale = 100;
2879         }
2880     
2881         // Turn off "fit to page" option
2882         $this->_fit_page    = 0;
2883     
2884         $this->_print_scale = floor($scale);
2885     }
2886     
2887     /** added 2009-03-05 by Joe Hunt, FA for arabic languages */
2888     function set_rtl()
2889     {
2890         $this->_rtl = 1;
2891     }   
2892     /**
2893     * Map to the appropriate write method acording to the token recieved.
2894     *
2895     * @access public
2896     * @param integer $row    The row of the cell we are writing to
2897     * @param integer $col    The column of the cell we are writing to
2898     * @param mixed   $token  What we are writing
2899     * @param mixed   $format The optional format to apply to the cell
2900     */
2901     function write($row, $col, $token, $format = null)
2902     {
2903         // Check for a cell reference in A1 notation and substitute row and column
2904         /*if ($_[0] =~ /^\D/) {
2905             @_ = $this->_substitute_cellref(@_);
2906     }*/
2907     
2908         /*
2909         # Match an array ref.
2910         if (ref $token eq "ARRAY") {
2911             return $this->write_row(@_);
2912     }*/
2913     
2914         // Match number
2915         if (preg_match("/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/",$token)) {
2916             return $this->write_number($row,$col,$token,$format);
2917         }
2918         // Match http or ftp URL
2919         elseif (preg_match("/^[fh]tt?p:\/\//",$token)) {
2920             return $this->write_url($row, $col, $token, $format);
2921         }
2922         // Match mailto:
2923         elseif (preg_match("/^mailto:/",$token)) {
2924             return $this->write_url($row, $col, $token, $format);
2925         }
2926         // Match internal or external sheet link
2927         elseif (preg_match("/^(?:in|ex)ternal:/",$token)) {
2928             return $this->write_url($row, $col, $token, $format);
2929         }
2930         // Match formula
2931         elseif (preg_match("/^=/",$token)) {
2932             return $this->write_formula($row, $col, $token, $format);
2933         }
2934         // Match formula
2935         elseif (preg_match("/^@/",$token)) {
2936             return $this->write_formula($row, $col, $token, $format);
2937         }
2938         // Match blank
2939         elseif ($token == '') {
2940             return $this->write_blank($row,$col,$format);
2941         }
2942         // Default: match string
2943         else {
2944             return $this->write_string($row,$col,$token,$format);
2945         }
2946     }
2947  
2948     /**
2949     * Returns an index to the XF record in the workbook
2950     *
2951     * @param mixed $format The optional XF format
2952     * @return integer The XF record index
2953     */
2954     //function _XF(&$format)
2955     function _XF($format=null)
2956     {
2957         if($format != null)
2958         {
2959             return($format->get_xf_index());
2960         }
2961         else
2962         {
2963             return(0x0F);
2964         }
2965     }
2966     
2967     
2968     /******************************************************************************
2969     *******************************************************************************
2970     *
2971     * Internal methods
2972     */
2973     
2974     
2975     /**
2976     * Store Worksheet data in memory using the parent's class append() or to a
2977     * temporary file, the default.
2978     *
2979     * @param string $data The binary data to append
2980     */
2981     function _append($data)
2982     {
2983         if ($this->_using_tmpfile)
2984         {
2985             // Add CONTINUE records if necessary
2986             if (strlen($data) > $this->_limit) {
2987                 $data = $this->_add_continue($data);
2988             }
2989             fwrite($this->_filehandle,$data);
2990             $this->_datasize += strlen($data);
2991         }
2992         else {
2993             parent::_append($data);
2994         }
2995     }
2996     
2997     /**
2998     * Substitute an Excel cell reference in A1 notation for  zero based row and
2999     * column values in an argument list.
3000     *
3001     * Ex: ("A4", "Hello") is converted to (3, 0, "Hello").
3002     *
3003     * @param string $cell The cell reference. Or range of cells.
3004     * @return array
3005     */
3006     function _substitute_cellref($cell)
3007     {
3008         $cell = strtoupper($cell);
3009     
3010         // Convert a column range: 'A:A' or 'B:G'
3011         if (preg_match("/([A-I]?[A-Z]):([A-I]?[A-Z])/",$cell,$match)) {
3012             list($no_use, $col1) =  $this->_cell_to_rowcol($match[1] .'1'); // Add a dummy row
3013             list($no_use, $col2) =  $this->_cell_to_rowcol($match[2] .'1'); // Add a dummy row
3014             return(array($col1, $col2));
3015         }
3016     
3017         // Convert a cell range: 'A1:B7'
3018         if (preg_match("/\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)/",$cell,$match)) {
3019             list($row1, $col1) =  $this->_cell_to_rowcol($match[1]);
3020             list($row2, $col2) =  $this->_cell_to_rowcol($match[2]);
3021             return(array($row1, $col1, $row2, $col2));
3022         }
3023     
3024         // Convert a cell reference: 'A1' or 'AD2000'
3025         if (preg_match("/\$?([A-I]?[A-Z]\$?\d+)/",$cell)) {
3026             list($row1, $col1) =  $this->_cell_to_rowcol($match[1]);
3027             return(array($row1, $col1));
3028         }
3029     
3030         die("Unknown cell reference $cell ");
3031     }
3032     
3033     /**
3034     * Convert an Excel cell reference in A1 notation to a zero based row and column
3035     * reference; converts C1 to (0, 2).
3036     *
3037     * @param string $cell The cell reference.
3038     * @return array containing (row, column)
3039     */
3040     function _cell_to_rowcol($cell)
3041     {
3042         preg_match("/\$?([A-I]?[A-Z])\$?(\d+)/",$cell,$match);
3043         $col     = $match[1];
3044         $row     = $match[2];
3045     
3046         // Convert base26 column string to number
3047         $chars = split('', $col);
3048         $expn  = 0;
3049         $col   = 0;
3050     
3051         while ($chars) {
3052             $char = array_pop($chars);        // LS char first
3053             $col += (ord($char) -ord('A') +1) * pow(26,$expn);
3054             $expn++;
3055         }
3056     
3057         // Convert 1-index to zero-index
3058         $row--;
3059         $col--;
3060     
3061         return(array($row, $col));
3062     }
3063     
3064     /**
3065     * Based on the algorithm provided by Daniel Rentz of OpenOffice.
3066     *
3067     * @param string $plaintext The password to be encoded in plaintext.
3068     * @return string The encoded password
3069     */
3070     function _encode_password($plaintext)
3071     {
3072         $password = 0x0000;
3073         $i        = 1;       // char position
3074  
3075         // split the plain text password in its component characters
3076         $chars = preg_split('//', $plaintext, -1, PREG_SPLIT_NO_EMPTY);
3077         foreach($chars as $char)
3078         {
3079             $value     = ord($char) << $i;   // shifted ASCII value 
3080             $bit_16    = $value & 0x8000;    // the bit 16
3081             $bit_16  >>= 15;                 // 0x0000 or 0x0001
3082             //$bit_17    = $value & 0x00010000;
3083             //$bit_17  >>= 15;
3084             $value    &= 0x7fff;             // first 15 bits
3085             $password ^= ($value | $bit_16);
3086             //$password ^= ($value | $bit_16 | $bit_17);
3087             $i++;
3088         }
3089     
3090         $password ^= strlen($plaintext);
3091         $password ^= 0xCE4B;
3092
3093         return($password);
3094     }
3095     
3096     /******************************************************************************
3097     *******************************************************************************
3098     *
3099     * BIFF RECORDS
3100     */
3101     
3102     
3103     /**
3104     * Write a double to the specified row and column (zero indexed).
3105     * An integer can be written as a double. Excel will display an
3106     * integer. $format is optional.
3107     *
3108     * Returns  0 : normal termination
3109     *         -2 : row or column out of range
3110     *
3111     * @access public
3112     * @param integer $row    Zero indexed row
3113     * @param integer $col    Zero indexed column
3114     * @param float   $num    The number to write
3115     * @param mixed   $format The optional XF format
3116     */
3117     function write_number($row, $col, $num, $format = null)
3118     {
3119         $record    = 0x0203;                 // Record identifier
3120         $length    = 0x000E;                 // Number of bytes to follow
3121         $xf        = $this->_XF($format);    // The cell format
3122     
3123         // Check that row and col are valid and store max and min values
3124         if ($row >= $this->xls_rowmax)
3125         {
3126             return(-2);
3127         }
3128         if ($col >= $this->xls_colmax)
3129         {
3130             return(-2);
3131         }
3132         if ($row <  $this->dim_rowmin) 
3133         {
3134             $this->dim_rowmin = $row;
3135         }
3136         if ($row >  $this->dim_rowmax) 
3137         {
3138             $this->dim_rowmax = $row;
3139         }
3140         if ($col <  $this->dim_colmin) 
3141         {
3142             $this->dim_colmin = $col;
3143         }
3144         if ($col >  $this->dim_colmax) 
3145         {
3146             $this->dim_colmax = $col;
3147         }
3148     
3149         $header    = pack("vv",  $record, $length);
3150         $data      = pack("vvv", $row, $col, $xf);
3151         $xl_double = pack("d",   $num);
3152         if ($this->_byte_order) // if it's Big Endian
3153         {
3154             $xl_double = strrev($xl_double);
3155         }
3156     
3157         $this->_append($header.$data.$xl_double);
3158         return(0);
3159     }
3160     
3161     /**
3162     * Write a string to the specified row and column (zero indexed).
3163     * NOTE: there is an Excel 5 defined limit of 255 characters.
3164     * $format is optional.
3165     * Returns  0 : normal termination
3166     *         -1 : insufficient number of arguments
3167     *         -2 : row or column out of range
3168     *         -3 : long string truncated to 255 chars
3169     *
3170     * @access public
3171     * @param integer $row    Zero indexed row
3172     * @param integer $col    Zero indexed column
3173     * @param string  $str    The string to write
3174     * @param mixed   $format The XF format for the cell
3175     */
3176     function write_string($row, $col, $str, $format = null)
3177     {
3178         $strlen    = strlen($str);
3179         $record    = 0x0204;                   // Record identifier
3180         $length    = 0x0008 + $strlen;         // Bytes to follow
3181         $xf        = $this->_XF($format);      // The cell format
3182         
3183         $str_error = 0;
3184     
3185         // Check that row and col are valid and store max and min values
3186         if ($row >= $this->xls_rowmax) 
3187         {
3188             return(-2);
3189         }
3190         if ($col >= $this->xls_colmax) 
3191         {
3192             return(-2);
3193         }
3194         if ($row <  $this->dim_rowmin) 
3195         {
3196             $this->dim_rowmin = $row;
3197         }
3198         if ($row >  $this->dim_rowmax) 
3199         {
3200             $this->dim_rowmax = $row;
3201         }
3202         if ($col <  $this->dim_colmin) 
3203         {
3204             $this->dim_colmin = $col;
3205         }
3206         if ($col >  $this->dim_colmax) 
3207         {
3208             $this->dim_colmax = $col;
3209         }
3210     
3211         if ($strlen > $this->xls_strmax)  // LABEL must be < 255 chars
3212         {
3213             $str       = substr($str, 0, $this->xls_strmax);
3214             $length    = 0x0008 + $this->xls_strmax;
3215             $strlen    = $this->xls_strmax;
3216             $str_error = -3;
3217         }
3218     
3219         $header    = pack("vv",   $record, $length);
3220         $data      = pack("vvvv", $row, $col, $xf, $strlen);
3221         $this->_append($header.$data.$str);
3222         return($str_error);
3223     }
3224  
3225     /**
3226     * Writes a note associated with the cell given by the row and column.
3227     * NOTE records don't have a length limit.
3228     *
3229     * @access public
3230     * @param integer $row    Zero indexed row
3231     * @param integer $col    Zero indexed column
3232     * @param string  $note   The note to write
3233     */
3234     function write_note($row, $col, $note)
3235     {
3236         $note_length    = strlen($note);
3237         $record         = 0x001C;                // Record identifier
3238         $max_length     = 2048;                  // Maximun length for a NOTE record
3239         //$length      = 0x0006 + $note_length;    // Bytes to follow
3240
3241         // Check that row and col are valid and store max and min values
3242         if ($row >= $this->xls_rowmax) 
3243         {
3244             return(-2);
3245         }
3246         if ($col >= $this->xls_colmax) 
3247         {
3248             return(-2);
3249         }
3250         if ($row <  $this->dim_rowmin) 
3251         {
3252             $this->dim_rowmin = $row;
3253         }
3254         if ($row >  $this->dim_rowmax) 
3255         {
3256             $this->dim_rowmax = $row;
3257         }
3258         if ($col <  $this->dim_colmin) 
3259         {
3260             $this->dim_colmin = $col;
3261         }
3262         if ($col >  $this->dim_colmax) 
3263         {
3264             $this->dim_colmax = $col;
3265         }
3266  
3267         // Length for this record is no more than 2048 + 6
3268         $length    = 0x0006 + min($note_length, 2048);
3269         $header    = pack("vv",   $record, $length);
3270         $data      = pack("vvv", $row, $col, $note_length);
3271         $this->_append($header.$data.substr($note, 0, 2048));
3272
3273         for($i = $max_length; $i < $note_length; $i += $max_length)
3274         {
3275             $chunk  = substr($note, $i, $max_length);
3276             $length = 0x0006 + strlen($chunk);
3277             $header = pack("vv",   $record, $length);
3278             $data   = pack("vvv", -1, 0, strlen($chunk));
3279             $this->_append($header.$data.$chunk);
3280         }
3281         return(0);
3282     }
3283  
3284     /**
3285     * Write a blank cell to the specified row and column (zero indexed).
3286     * A blank cell is used to specify formatting without adding a string
3287     * or a number.
3288     *
3289     * A blank cell without a format serves no purpose. Therefore, we don't write
3290     * a BLANK record unless a format is specified. This is mainly an optimisation
3291     * for the write_row() and write_col() methods.
3292     *
3293     * Returns  0 : normal termination (including no format)
3294     *         -1 : insufficient number of arguments
3295     *         -2 : row or column out of range
3296     *
3297     * @access public
3298     * @param integer $row    Zero indexed row
3299     * @param integer $col    Zero indexed column
3300     * @param mixed   $format The XF format
3301     */
3302     function write_blank($row, $col, $format = null)
3303     {
3304         // Don't write a blank cell unless it has a format
3305         if ($format == null)
3306         {
3307             return(0);
3308         }
3309     
3310         $record    = 0x0201;                 // Record identifier
3311         $length    = 0x0006;                 // Number of bytes to follow
3312         $xf        = $this->_XF($format);    // The cell format
3313     
3314         // Check that row and col are valid and store max and min values
3315         if ($row >= $this->xls_rowmax) 
3316         {
3317             return(-2);
3318         }
3319         if ($col >= $this->xls_colmax) 
3320         {
3321             return(-2);
3322         }
3323         if ($row <  $this->dim_rowmin) 
3324         {
3325             $this->dim_rowmin = $row;
3326         }
3327         if ($row >  $this->dim_rowmax) 
3328         {
3329             $this->dim_rowmax = $row;
3330         }
3331         if ($col <  $this->dim_colmin) 
3332         {
3333             $this->dim_colmin = $col;
3334         }
3335         if ($col >  $this->dim_colmax) 
3336         {
3337             $this->dim_colmax = $col;
3338         }
3339     
3340         $header    = pack("vv",  $record, $length);
3341         $data      = pack("vvv", $row, $col, $xf);
3342         $this->_append($header.$data);
3343         return 0;
3344     }
3345  
3346     /**
3347     * Write a formula to the specified row and column (zero indexed).
3348     * The textual representation of the formula is passed to the parser in
3349     * Parser.php which returns a packed binary string.
3350     *
3351     * Returns  0 : normal termination
3352     *         -2 : row or column out of range
3353     *
3354     * @access public
3355     * @param integer $row     Zero indexed row
3356     * @param integer $col     Zero indexed column
3357     * @param string  $formula The formula text string
3358     * @param mixed   $format  The optional XF format
3359     */
3360     function write_formula($row, $col, $formula, $format = null)
3361     {
3362         $record    = 0x0006;     // Record identifier
3363     
3364         // Excel normally stores the last calculated value of the formula in $num.
3365         // Clearly we are not in a position to calculate this a priori. Instead
3366         // we set $num to zero and set the option flags in $grbit to ensure
3367         // automatic calculation of the formula when the file is opened.
3368         //
3369         $xf        = $this->_XF($format); // The cell format
3370         $num       = 0x00;                // Current value of formula
3371         $grbit     = 0x03;                // Option flags
3372         $chn       = 0x0000;              // Must be zero
3373     
3374     
3375         // Check that row and col are valid and store max and min values
3376         if ($row >= $this->xls_rowmax)
3377         {
3378             return(-2);
3379         }
3380         if ($col >= $this->xls_colmax)
3381         {
3382             return(-2);
3383         }
3384         if ($row <  $this->dim_rowmin) 
3385         {
3386             $this->dim_rowmin = $row;
3387         }
3388         if ($row >  $this->dim_rowmax) 
3389         {
3390             $this->dim_rowmax = $row;
3391         }
3392         if ($col <  $this->dim_colmin) 
3393         {
3394             $this->dim_colmin = $col;
3395         }
3396         if ($col >  $this->dim_colmax) 
3397         {
3398             $this->dim_colmax = $col;
3399         }
3400     
3401         // Strip the '=' or '@' sign at the beginning of the formula string
3402         if (ereg("^=",$formula)) {
3403             $formula = preg_replace("/(^=)/","",$formula);
3404         }
3405         elseif(ereg("^@",$formula)) {
3406             $formula = preg_replace("/(^@)/","",$formula);
3407         }
3408         else {
3409             die("Unrecognised character for formula");
3410         }
3411     
3412         // Parse the formula using the parser in Parser.php
3413         //$tree      = new Parser($this->_byte_order);
3414         $this->_parser->parse($formula);
3415         //$tree->parse($formula);
3416         $formula = $this->_parser->to_reverse_polish();
3417     
3418         $formlen    = strlen($formula);    // Length of the binary string
3419         $length     = 0x16 + $formlen;     // Length of the record data
3420     
3421         $header    = pack("vv",      $record, $length);
3422         $data      = pack("vvvdvVv", $row, $col, $xf, $num,
3423                                      $grbit, $chn, $formlen);
3424     
3425         $this->_append($header.$data.$formula);
3426         return 0;
3427     }
3428     
3429     /**
3430     * Write a hyperlink. This is comprised of two elements: the visible label and
3431     * the invisible link. The visible label is the same as the link unless an
3432     * alternative string is specified. The label is written using the
3433     * write_string() method. Therefore the 255 characters string limit applies.
3434     * $string and $format are optional and their order is interchangeable.
3435     *
3436     * The hyperlink can be to a http, ftp, mail, internal sheet, or external
3437     * directory url.
3438     *
3439     * Returns  0 : normal termination
3440     *         -1 : insufficient number of arguments
3441     *         -2 : row or column out of range
3442     *         -3 : long string truncated to 255 chars
3443     *
3444     * @access public
3445     * @param integer $row    Row
3446     * @param integer $col    Column
3447     * @param string  $url    URL string
3448     * @param string  $string Alternative label
3449     * @param mixed   $format The cell format
3450     */
3451     function write_url($row, $col, $url, $string = '', $format = null)
3452     {
3453         // Add start row and col to arg list
3454         return($this->_write_url_range($row, $col, $row, $col, $url, $string, $format));
3455     }
3456     
3457     /**
3458     * This is the more general form of write_url(). It allows a hyperlink to be
3459     * written to a range of cells. This function also decides the type of hyperlink
3460     * to be written. These are either, Web (http, ftp, mailto), Internal
3461     * (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1').
3462     *
3463     * See also write_url() above for a general description and return values.
3464     *
3465     * @param integer $row1   Start row
3466     * @param integer $col1   Start column
3467     * @param integer $row2   End row
3468     * @param integer $col2   End column
3469     * @param string  $url    URL string
3470     * @param string  $string Alternative label
3471     * @param mixed   $format The cell format
3472     */
3473     
3474     function _write_url_range($row1, $col1, $row2, $col2, $url, $string = '', $format = null)
3475     {
3476         // Check for internal/external sheet links or default to web link
3477         if (preg_match('[^internal:]', $url)) {
3478             return($this->_write_url_internal($row1, $col1, $row2, $col2, $url, $string, $format));
3479         }
3480         if (preg_match('[^external:]', $url)) {
3481             return($this->_write_url_external($row1, $col1, $row2, $col2, $url, $string, $format));
3482         }
3483         return($this->_write_url_web($row1, $col1, $row2, $col2, $url, $string, $format));
3484     }
3485     
3486     
3487     /**
3488     * Used to write http, ftp and mailto hyperlinks.
3489     * The link type ($options) is 0x03 is the same as absolute dir ref without
3490     * sheet. However it is differentiated by the $unknown2 data stream.
3491     *
3492     * @see write_url()
3493     * @param integer $row1   Start row
3494     * @param integer $col1   Start column
3495     * @param integer $row2   End row
3496     * @param integer $col2   End column
3497     * @param string  $url    URL string
3498     * @param string  $str    Alternative label
3499     * @param mixed   $format The cell format
3500     */
3501     function _write_url_web($row1, $col1, $row2, $col2, $url, $str, $format = null)
3502     {
3503         $record      = 0x01B8;                       // Record identifier
3504         $length      = 0x00000;                      // Bytes to follow
3505     
3506         if($format == null) {
3507             $format = $this->_url_format;
3508         }
3509     
3510         // Write the visible label using the write_string() method.
3511         if($str == '') {
3512             $str = $url;
3513         }
3514         $str_error = $this->write_string($row1, $col1, $str, $format);
3515         if ($str_error == -2) {
3516             return($str_error);
3517         }
3518     
3519         // Pack the undocumented parts of the hyperlink stream
3520         $unknown1    = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
3521         $unknown2    = pack("H*", "E0C9EA79F9BACE118C8200AA004BA90B");
3522     
3523         // Pack the option flags
3524         $options     = pack("V", 0x03);
3525     
3526         // Convert URL to a null terminated wchar string
3527         $url         = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
3528         $url         = $url . "\0\0\0";
3529     
3530         // Pack the length of the URL
3531         $url_len     = pack("V", strlen($url));
3532     
3533         // Calculate the data length
3534         $length      = 0x34 + strlen($url);
3535     
3536         // Pack the header data
3537         $header      = pack("vv",   $record, $length);
3538         $data        = pack("vvvv", $row1, $row2, $col1, $col2);
3539     
3540         // Write the packed data
3541         $this->_append( $header. $data.
3542                         $unknown1. $options.
3543                         $unknown2. $url_len. $url);
3544         return($str_error);
3545     }
3546     
3547     /**
3548     * Used to write internal reference hyperlinks such as "Sheet1!A1".
3549     *
3550     * @see write_url()
3551     * @param integer $row1   Start row
3552     * @param integer $col1   Start column
3553     * @param integer $row2   End row
3554     * @param integer $col2   End column
3555     * @param string  $url    URL string
3556     * @param string  $str    Alternative label
3557     * @param mixed   $format The cell format
3558     */
3559     function _write_url_internal($row1, $col1, $row2, $col2, $url, $str, $format = null)
3560     {
3561         $record      = 0x01B8;                       // Record identifier
3562         $length      = 0x00000;                      // Bytes to follow
3563     
3564         if ($format == null) {
3565             $format = $this->_url_format;
3566         }
3567     
3568         // Strip URL type
3569         $url = preg_replace('s[^internal:]', '', $url);
3570     
3571         // Write the visible label
3572         if($str == '') {
3573             $str = $url;
3574         }
3575         $str_error = $this->write_string($row1, $col1, $str, $format);
3576         if ($str_error == -2) {
3577             return($str_error);
3578         }
3579     
3580         // Pack the undocumented parts of the hyperlink stream
3581         $unknown1    = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
3582     
3583         // Pack the option flags
3584         $options     = pack("V", 0x08);
3585     
3586         // Convert the URL type and to a null terminated wchar string
3587         $url         = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
3588         $url         = $url . "\0\0\0";
3589     
3590         // Pack the length of the URL as chars (not wchars)
3591         $url_len     = pack("V", floor(strlen($url)/2));
3592     
3593         // Calculate the data length
3594         $length      = 0x24 + strlen($url);
3595     
3596         // Pack the header data
3597         $header      = pack("vv",   $record, $length);
3598         $data        = pack("vvvv", $row1, $row2, $col1, $col2);
3599     
3600         // Write the packed data
3601         $this->_append($header. $data.
3602                        $unknown1. $options.
3603                        $url_len. $url);
3604         return($str_error);
3605     }
3606     
3607     /**
3608     * Write links to external directory names such as 'c:\foo.xls',
3609     * c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.
3610     *
3611     * Note: Excel writes some relative links with the $dir_long string. We ignore
3612     * these cases for the sake of simpler code.
3613     *
3614     * @see write_url()
3615     * @param integer $row1   Start row
3616     * @param integer $col1   Start column
3617     * @param integer $row2   End row
3618     * @param integer $col2   End column
3619     * @param string  $url    URL string
3620     * @param string  $str    Alternative label
3621     * @param mixed   $format The cell format
3622     */
3623     function _write_url_external($row1, $col1, $row2, $col2, $url, $str, $format = null)
3624     {
3625         // Network drives are different. We will handle them separately
3626         // MS/Novell network drives and shares start with \\
3627         if (preg_match('[^external:\\\\]', $url)) {
3628             return($this->_write_url_external_net($row1, $col1, $row2, $col2, $url, $str, $format));
3629         }
3630     
3631         $record      = 0x01B8;                       // Record identifier
3632         $length      = 0x00000;                      // Bytes to follow
3633     
3634         if ($format == null) {
3635             $format = $this->_url_format;
3636         }
3637     
3638         // Strip URL type and change Unix dir separator to Dos style (if needed)
3639         //
3640         $url = preg_replace('[^external:]', '', $url);
3641         $url = preg_replace('[/]', "\\", $url);
3642     
3643         // Write the visible label
3644         if ($str == '') {
3645             $str = preg_replace('[\#]', ' - ', $url);
3646         }
3647         $str_error = $this->write_string($row1, $col1, $str, $format);
3648         if ($str_error == -2) {
3649             return($str_error);
3650         }
3651     
3652         // Determine if the link is relative or absolute:
3653         //   relative if link contains no dir separator, "somefile.xls"
3654         //   relative if link starts with up-dir, "..\..\somefile.xls"
3655         //   otherwise, absolute
3656         
3657         $absolute    = 0x02; // Bit mask
3658         if (!preg_match('[\\]', $url)) {
3659             $absolute    = 0x00;
3660         }
3661         if (preg_match('[^\.\.\\]', $url)) {
3662             $absolute    = 0x00;
3663         }
3664     
3665         // Determine if the link contains a sheet reference and change some of the
3666         // parameters accordingly.
3667         // Split the dir name and sheet name (if it exists)
3668         list($dir_long , $sheet) = split('/\#/', $url);
3669         $link_type               = 0x01 | $absolute;
3670     
3671         if (isset($sheet)) {
3672             $link_type |= 0x08;
3673             $sheet_len  = pack("V", strlen($sheet) + 0x01);
3674             $sheet      = join("\0", split('', $sheet));
3675             $sheet     .= "\0\0\0";
3676         }
3677         else {
3678             $sheet_len   = '';
3679             $sheet       = '';
3680         }
3681     
3682         // Pack the link type
3683         $link_type   = pack("V", $link_type);
3684     
3685         // Calculate the up-level dir count e.g.. (..\..\..\ == 3)
3686         $up_count    = preg_match_all("/\.\.\\/", $dir_long, $useless);
3687         $up_count    = pack("v", $up_count);
3688     
3689         // Store the short dos dir name (null terminated)
3690         $dir_short   = preg_replace('/\.\.\\/', '', $dir_long) . "\0";
3691     
3692         // Store the long dir name as a wchar string (non-null terminated)
3693         $dir_long       = join("\0", split('', $dir_long));
3694         $dir_long       = $dir_long . "\0";
3695     
3696         // Pack the lengths of the dir strings
3697         $dir_short_len = pack("V", strlen($dir_short)      );
3698         $dir_long_len  = pack("V", strlen($dir_long)       );
3699         $stream_len    = pack("V", strlen($dir_long) + 0x06);
3700     
3701         // Pack the undocumented parts of the hyperlink stream
3702         $unknown1 = pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000'       );
3703         $unknown2 = pack("H*",'0303000000000000C000000000000046'               );
3704         $unknown3 = pack("H*",'FFFFADDE000000000000000000000000000000000000000');
3705         $unknown4 = pack("v",  0x03                                            );
3706     
3707         // Pack the main data stream
3708         $data        = pack("vvvv", $row1, $row2, $col1, $col2) .
3709                           $unknown1     .
3710                           $link_type    .
3711                           $unknown2     .
3712                           $up_count     .
3713                           $dir_short_len.
3714                           $dir_short    .
3715                           $unknown3     .
3716                           $stream_len   .
3717                           $dir_long_len .
3718                           $unknown4     .
3719                           $dir_long     .
3720                           $sheet_len    .
3721                           $sheet        ;
3722     
3723         // Pack the header data
3724         $length   = strlen($data);
3725         $header   = pack("vv", $record, $length);
3726     
3727         // Write the packed data
3728         $this->_append($header. $data);
3729         return($str_error);
3730     }
3731     
3732     
3733     /*
3734     ###############################################################################
3735     #
3736     # write_url_xxx($row1, $col1, $row2, $col2, $url, $string, $format)
3737     #
3738     # Write links to external MS/Novell network drives and shares such as
3739     # '//NETWORK/share/foo.xls' and '//NETWORK/share/foo.xls#Sheet1!A1'.
3740     #
3741     # See also write_url() above for a general description and return values.
3742     #
3743     sub _write_url_external_net {
3744     
3745         my $this    = shift;
3746     
3747         my $record      = 0x01B8;                       # Record identifier
3748         my $length      = 0x00000;                      # Bytes to follow
3749     
3750         my $row1        = $_[0];                        # Start row
3751         my $col1        = $_[1];                        # Start column
3752         my $row2        = $_[2];                        # End row
3753         my $col2        = $_[3];                        # End column
3754         my $url         = $_[4];                        # URL string
3755         my $str         = $_[5];                        # Alternative label
3756         my $xf          = $_[6] || $this->{_url_format};# The cell format
3757     
3758     
3759         # Strip URL type and change Unix dir separator to Dos style (if needed)
3760         #
3761         $url            =~ s[^external:][];
3762         $url            =~ s[/][\\]g;
3763     
3764     
3765         # Write the visible label
3766         ($str = $url)   =~ s[\#][ - ] unless defined $str;
3767         my $str_error   = $this->write_string($row1, $col1, $str, $xf);
3768         return $str_error if $str_error == -2;
3769     
3770     
3771         # Determine if the link contains a sheet reference and change some of the
3772         # parameters accordingly.
3773         # Split the dir name and sheet name (if it exists)
3774         #
3775         my ($dir_long , $sheet) = split /\#/, $url;
3776         my $link_type           = 0x0103; # Always absolute
3777         my $sheet_len;
3778     
3779         if (defined $sheet) {
3780             $link_type |= 0x08;
3781             $sheet_len  = pack("V", length($sheet) + 0x01);
3782             $sheet      = join("\0", split('', $sheet));
3783             $sheet     .= "\0\0\0";
3784     }
3785         else {
3786             $sheet_len   = '';
3787             $sheet       = '';
3788     }
3789     
3790         # Pack the link type
3791         $link_type      = pack("V", $link_type);
3792     
3793     
3794         # Make the string null terminated
3795         $dir_long       = $dir_long . "\0";
3796     
3797     
3798         # Pack the lengths of the dir string
3799         my $dir_long_len  = pack("V", length $dir_long);
3800     
3801     
3802         # Store the long dir name as a wchar string (non-null terminated)
3803         $dir_long       = join("\0", split('', $dir_long));
3804         $dir_long       = $dir_long . "\0";
3805     
3806     
3807         # Pack the undocumented part of the hyperlink stream
3808         my $unknown1    = pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000');
3809     
3810     
3811         # Pack the main data stream
3812         my $data        = pack("vvvv", $row1, $row2, $col1, $col2) .
3813                           $unknown1     .
3814                           $link_type    .
3815                           $dir_long_len .
3816                           $dir_long     .
3817                           $sheet_len    .
3818                           $sheet        ;
3819     
3820     
3821         # Pack the header data
3822         $length         = length $data;
3823         my $header      = pack("vv",   $record, $length);
3824     
3825     
3826         # Write the packed data
3827         $this->_append( $header, $data);
3828     
3829         return $str_error;
3830 }*/
3831     
3832     /**
3833     * This method is used to set the height and XF format for a row.
3834     * Writes the  BIFF record ROW.
3835     *
3836     * @access public
3837     * @param integer $row    The row to set
3838     * @param integer $height Height we are giving to the row. 
3839     *                        Use NULL to set XF without setting height
3840     * @param mixed   $format XF format we are giving to the row
3841     */
3842     function set_row($row, $height, $format = null)
3843     {
3844         $record      = 0x0208;               // Record identifier
3845         $length      = 0x0010;               // Number of bytes to follow
3846     
3847         $colMic      = 0x0000;               // First defined column
3848         $colMac      = 0x0000;               // Last defined column
3849         $irwMac      = 0x0000;               // Used by Excel to optimise loading
3850         $reserved    = 0x0000;               // Reserved
3851         $grbit       = 0x01C0;               // Option flags. (monkey) see $1 do
3852         $ixfe        = $this->_XF($format); // XF index
3853     
3854         // Use set_row($row, NULL, $XF) to set XF without setting height
3855         if ($height != NULL) {
3856             $miyRw = $height * 20;  // row height
3857         }
3858         else {
3859             $miyRw = 0xff;          // default row height is 256
3860         }
3861     
3862         $header   = pack("vv",       $record, $length);
3863         $data     = pack("vvvvvvvv", $row, $colMic, $colMac, $miyRw,
3864                                      $irwMac,$reserved, $grbit, $ixfe);
3865         $this->_append($header.$data);
3866     }
3867     
3868     /**
3869     * Writes Excel DIMENSIONS to define the area in which there is data.
3870     */
3871     function _store_dimensions()
3872     {
3873         $record    = 0x0000;               // Record identifier
3874         $length    = 0x000A;               // Number of bytes to follow
3875         $row_min   = $this->dim_rowmin;    // First row
3876         $row_max   = $this->dim_rowmax;    // Last row plus 1
3877         $col_min   = $this->dim_colmin;    // First column
3878         $col_max   = $this->dim_colmax;    // Last column plus 1
3879         $reserved  = 0x0000;               // Reserved by Excel
3880     
3881         $header    = pack("vv",    $record, $length);
3882         $data      = pack("vvvvv", $row_min, $row_max,
3883                                    $col_min, $col_max, $reserved);
3884         $this->_prepend($header.$data);
3885     }
3886     
3887     /**
3888     * Write BIFF record Window2.
3889     */
3890     function _store_window2()
3891     {
3892         $record         = 0x023E;     // Record identifier
3893         $length         = 0x000A;     // Number of bytes to follow
3894     
3895         $grbit          = 0x00B6;     // Option flags
3896         $rwTop          = 0x0000;     // Top row visible in window
3897         $colLeft        = 0x0000;     // Leftmost column visible in window
3898         $rgbHdr         = 0x00000000; // Row/column heading and gridline color
3899     
3900         // The options flags that comprise $grbit
3901         $fDspFmla       = 0;                     // 0 - bit
3902         $fDspGrid       = 1;                     // 1
3903         $fDspRwCol      = 1;                     // 2
3904         $fFrozen        = $this->_frozen;        // 3
3905         $fDspZeros      = 1;                     // 4
3906         $fDefaultHdr    = 1;                     // 5
3907         $fArabic        = $this->_rtl;           // 6
3908         $fDspGuts       = 1;                     // 7
3909         $fFrozenNoSplit = 0;                     // 0 - bit
3910         $fSelected      = $this->selected;       // 1
3911         $fPaged         = 1;                     // 2
3912     
3913         $grbit             = $fDspFmla;
3914         $grbit            |= $fDspGrid       << 1;
3915         $grbit            |= $fDspRwCol      << 2;
3916         $grbit            |= $fFrozen        << 3;
3917         $grbit            |= $fDspZeros      << 4;
3918         $grbit            |= $fDefaultHdr    << 5;
3919         $grbit            |= $fArabic        << 6;
3920         $grbit            |= $fDspGuts       << 7;
3921         $grbit            |= $fFrozenNoSplit << 8;
3922         $grbit            |= $fSelected      << 9;
3923         $grbit            |= $fPaged         << 10;
3924     
3925         $header  = pack("vv",   $record, $length);
3926         $data    = pack("vvvV", $grbit, $rwTop, $colLeft, $rgbHdr);
3927         $this->_append($header.$data);
3928     }
3929     
3930     /**
3931     * Write BIFF record DEFCOLWIDTH if COLINFO records are in use.
3932     */
3933     function _store_defcol()
3934     {
3935         $record   = 0x0055;      // Record identifier
3936         $length   = 0x0002;      // Number of bytes to follow
3937         $colwidth = 0x0008;      // Default column width
3938     
3939         $header   = pack("vv", $record, $length);
3940         $data     = pack("v",  $colwidth);
3941         $this->_prepend($header.$data);
3942     }
3943     
3944     /**
3945     * Write BIFF record COLINFO to define column widths
3946     *
3947     * Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
3948     * length record.
3949     *
3950     * @param array $col_array This is the only parameter received and is composed of the following:
3951     *                0 => First formatted column,
3952     *                1 => Last formatted column,
3953     *                2 => Col width (8.43 is Excel default),
3954     *                3 => The optional XF format of the column,
3955     *                4 => Option flags.
3956     */
3957     function _store_colinfo($col_array)
3958     {
3959         if(isset($col_array[0])) {
3960             $colFirst = $col_array[0];
3961         }
3962         if(isset($col_array[1])) {
3963             $colLast = $col_array[1];
3964         }
3965         if(isset($col_array[2])) {
3966             $coldx = $col_array[2];
3967         }
3968         else {
3969             $coldx = 8.43;
3970         }
3971         if(isset($col_array[3])) {
3972             $format = $col_array[3];
3973         }
3974         else {
3975             $format = null;
3976         }
3977         if(isset($col_array[4])) {
3978             $grbit = $col_array[4];
3979         }
3980         else {
3981             $grbit = 0;
3982         }
3983         $record   = 0x007D;          // Record identifier
3984         $length   = 0x000B;          // Number of bytes to follow
3985     
3986         $coldx   += 0.72;            // Fudge. Excel subtracts 0.72 !?
3987         $coldx   *= 256;             // Convert to units of 1/256 of a char
3988     
3989         $ixfe     = $this->_XF($format);
3990         $reserved = 0x00;            // Reserved
3991     
3992         $header   = pack("vv",     $record, $length);
3993         $data     = pack("vvvvvC", $colFirst, $colLast, $coldx,
3994                                    $ixfe, $grbit, $reserved);
3995         $this->_prepend($header.$data);
3996     }
3997     
3998     /**
3999     * Write BIFF record SELECTION.
4000     *
4001     * @param array $array array containing ($rwFirst,$colFirst,$rwLast,$colLast)
4002     * @see set_selection()
4003     */
4004     function _store_selection($array)
4005     {
4006         list($rwFirst,$colFirst,$rwLast,$colLast) = $array;
4007         $record   = 0x001D;                  // Record identifier
4008         $length   = 0x000F;                  // Number of bytes to follow
4009     
4010         $pnn      = $this->_active_pane;     // Pane position
4011         $rwAct    = $rwFirst;                // Active row
4012         $colAct   = $colFirst;               // Active column
4013         $irefAct  = 0;                       // Active cell ref
4014         $cref     = 1;                       // Number of refs
4015     
4016         if (!isset($rwLast)) {
4017             $rwLast   = $rwFirst;       // Last  row in reference
4018         }
4019         if (!isset($colLast)) {
4020             $colLast  = $colFirst;      // Last  col in reference
4021         }
4022     
4023         // Swap last row/col for first row/col as necessary
4024         if ($rwFirst > $rwLast)
4025         {
4026             list($rwFirst, $rwLast) = array($rwLast, $rwFirst);
4027         }
4028     
4029         if ($colFirst > $colLast)
4030         {
4031             list($colFirst, $colLast) = array($colLast, $colFirst);
4032         }
4033     
4034         $header   = pack("vv",         $record, $length);
4035         $data     = pack("CvvvvvvCC",  $pnn, $rwAct, $colAct,
4036                                        $irefAct, $cref,
4037                                        $rwFirst, $rwLast,
4038                                        $colFirst, $colLast);
4039         $this->_append($header.$data);
4040     }
4041     
4042     
4043     /**
4044     * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
4045     * references in a worksheet.
4046     *
4047     * Excel only stores references to external sheets that are used in formulas.
4048     * For simplicity we store references to all the sheets in the workbook
4049     * regardless of whether they are used or not. This reduces the overall
4050     * complexity and eliminates the need for a two way dialogue between the formula
4051     * parser the worksheet objects.
4052     *
4053     * @param integer $count The number of external sheet references in this worksheet
4054     */
4055     function _store_externcount($count)
4056     {
4057         $record   = 0x0016;          // Record identifier
4058         $length   = 0x0002;          // Number of bytes to follow
4059     
4060         $header   = pack("vv", $record, $length);
4061         $data     = pack("v",  $count);
4062         $this->_prepend($header.$data);
4063     }
4064     
4065     /**
4066     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
4067     * formulas. A formula references a sheet name via an index. Since we store a
4068     * reference to all of the external worksheets the EXTERNSHEET index is the same
4069     * as the worksheet index.
4070     *
4071     * @param string $sheetname The name of a external worksheet
4072     */
4073     function _store_externsheet($sheetname)
4074     {
4075         $record    = 0x0017;         // Record identifier
4076     
4077         // References to the current sheet are encoded differently to references to
4078         // external sheets.
4079         //
4080         if ($this->name == $sheetname) {
4081             $sheetname = '';
4082             $length    = 0x02;  // The following 2 bytes
4083             $cch       = 1;     // The following byte
4084             $rgch      = 0x02;  // Self reference
4085         }
4086         else {
4087             $length    = 0x02 + strlen($sheetname);
4088             $cch       = strlen($sheetname);
4089             $rgch      = 0x03;  // Reference to a sheet in the current workbook
4090         }
4091     
4092         $header     = pack("vv",  $record, $length);
4093         $data       = pack("CC", $cch, $rgch);
4094         $this->_prepend($header.$data.$sheetname);
4095     }
4096     
4097     /**
4098     * Writes the Excel BIFF PANE record.
4099     * The panes can either be frozen or thawed (unfrozen).
4100     * Frozen panes are specified in terms of an integer number of rows and columns.
4101     * Thawed panes are specified in terms of Excel's units for rows and columns.
4102     *
4103     * @param array $panes This is the only parameter received and is composed of the following:
4104     *                     0 => Vertical split position,
4105     *                     1 => Horizontal split position
4106     *                     2 => Top row visible
4107     *                     3 => Leftmost column visible
4108     *                     4 => Active pane
4109     */
4110     function _store_panes($panes)
4111     {
4112         $y       = $panes[0];
4113         $x       = $panes[1];
4114         $rwTop   = $panes[2];
4115         $colLeft = $panes[3];
4116         if(count($panes) > 4) { // if Active pane was received
4117             $pnnAct = $panes[4];
4118         }
4119         else {
4120             $pnnAct = NULL;
4121         }
4122         $record  = 0x0041;       // Record identifier
4123         $length  = 0x000A;       // Number of bytes to follow
4124     
4125         // Code specific to frozen or thawed panes.
4126         if ($this->_frozen) {
4127             // Set default values for $rwTop and $colLeft
4128             if(!isset($rwTop)) {
4129                 $rwTop   = $y;
4130             }
4131             if(!isset($colLeft)) {
4132                 $colLeft = $x;
4133             }
4134         }
4135         else {
4136             // Set default values for $rwTop and $colLeft
4137             if(!isset($rwTop)) {
4138                 $rwTop   = 0;
4139             }
4140             if(!isset($colLeft)) {
4141                 $colLeft = 0;
4142             }
4143     
4144             // Convert Excel's row and column units to the internal units.
4145             // The default row height is 12.75
4146             // The default column width is 8.43
4147             // The following slope and intersection values were interpolated.
4148             //
4149             $y = 20*$y      + 255;
4150             $x = 113.879*$x + 390;
4151         }
4152     
4153     
4154         // Determine which pane should be active. There is also the undocumented
4155         // option to override this should it be necessary: may be removed later.
4156         //
4157         if (!isset($pnnAct))
4158         {
4159             if ($x != 0 and $y != 0)
4160                 $pnnAct = 0; // Bottom right
4161             if ($x != 0 and $y == 0)
4162                 $pnnAct = 1; // Top right
4163             if ($x == 0 and $y != 0)
4164                 $pnnAct = 2; // Bottom left
4165             if ($x == 0 and $y == 0)
4166                 $pnnAct = 3; // Top left
4167         }
4168     
4169         $this->_active_pane = $pnnAct; // Used in _store_selection
4170     
4171         $header     = pack("vv",    $record, $length);
4172         $data       = pack("vvvvv", $x, $y, $rwTop, $colLeft, $pnnAct);
4173         $this->_append($header.$data);
4174     }
4175     
4176     /**
4177     * Store the page setup SETUP BIFF record.
4178     */
4179     function _store_setup()
4180     {
4181         $record       = 0x00A1;                  // Record identifier
4182         $length       = 0x0022;                  // Number of bytes to follow
4183     
4184         $iPaperSize   = $this->_paper_size;    // Paper size
4185         $iScale       = $this->_print_scale;   // Print scaling factor
4186         $iPageStart   = 0x01;                 // Starting page number
4187         $iFitWidth    = $this->_fit_width;    // Fit to number of pages wide
4188         $iFitHeight   = $this->_fit_height;   // Fit to number of pages high
4189         $grbit        = 0x00;                 // Option flags
4190         $iRes         = 0x0258;               // Print resolution
4191         $iVRes        = 0x0258;               // Vertical print resolution
4192         $numHdr       = $this->_margin_head;  // Header Margin
4193         $numFtr       = $this->_margin_foot;   // Footer Margin
4194         $iCopies      = 0x01;                 // Number of copies
4195     
4196         $fLeftToRight = 0x0;                     // Print over then down
4197         $fLandscape   = $this->_orientation;     // Page orientation
4198         $fNoPls       = 0x0;                     // Setup not read from printer
4199         $fNoColor     = 0x0;                     // Print black and white
4200         $fDraft       = 0x0;                     // Print draft quality
4201         $fNotes       = 0x0;                     // Print notes
4202         $fNoOrient    = 0x0;                     // Orientation not set
4203         $fUsePage     = 0x0;                     // Use custom starting page
4204     
4205         $grbit           = $fLeftToRight;
4206         $grbit          |= $fLandscape    << 1;
4207         $grbit          |= $fNoPls        << 2;
4208         $grbit          |= $fNoColor      << 3;
4209         $grbit          |= $fDraft        << 4;
4210         $grbit          |= $fNotes        << 5;
4211         $grbit          |= $fNoOrient     << 6;
4212         $grbit          |= $fUsePage      << 7;
4213     
4214         $numHdr = pack("d", $numHdr);
4215         $numFtr = pack("d", $numFtr);
4216         if ($this->_byte_order) // if it's Big Endian
4217         {
4218             $numHdr = strrev($numHdr);
4219             $numFtr = strrev($numFtr);
4220         }
4221     
4222         $header = pack("vv", $record, $length);
4223         $data1  = pack("vvvvvvvv", $iPaperSize,
4224                                    $iScale,
4225                                    $iPageStart,
4226                                    $iFitWidth,
4227                                    $iFitHeight,
4228                                    $grbit,
4229                                    $iRes,
4230                                    $iVRes);
4231         $data2  = $numHdr .$numFtr;
4232         $data3  = pack("v", $iCopies);
4233         $this->_prepend($header.$data1.$data2.$data3);
4234     }
4235     
4236     /**
4237     * Store the header caption BIFF record.
4238     */
4239     function store_header()
4240     {
4241         $record  = 0x0014;               // Record identifier
4242     
4243         $str     = $this->_header;        // header string
4244         $cch     = strlen($str);         // Length of header string
4245         $length  = 1 + $cch;             // Bytes to follow
4246     
4247         $header    = pack("vv",  $record, $length);
4248         $data      = pack("C",   $cch);
4249     
4250         $this->_append($header.$data.$str);
4251     }
4252     
4253     /**
4254     * Store the footer caption BIFF record.
4255     */
4256     function store_footer()
4257     {
4258         $record  = 0x0015;               // Record identifier
4259     
4260         $str     = $this->_footer;       // Footer string
4261         $cch     = strlen($str);         // Length of footer string
4262         $length  = 1 + $cch;             // Bytes to follow
4263     
4264         $header    = pack("vv",  $record, $length);
4265         $data      = pack("C",   $cch);
4266     
4267         $this->_append($header.$data.$str);
4268     }
4269     
4270     /**
4271     * Store the horizontal centering HCENTER BIFF record.
4272     */
4273     function store_hcenter()
4274     {
4275         $record   = 0x0083;              // Record identifier
4276         $length   = 0x0002;              // Bytes to follow
4277     
4278         $fHCenter = $this->_hcenter;      // Horizontal centering
4279     
4280         $header    = pack("vv",  $record, $length);
4281         $data      = pack("v",   $fHCenter);
4282     
4283         $this->_append($header.$data);
4284     }
4285     
4286     /**
4287     * Store the vertical centering VCENTER BIFF record.
4288     */
4289     function store_vcenter()
4290     {
4291         $record   = 0x0084;              // Record identifier
4292         $length   = 0x0002;              // Bytes to follow
4293     
4294         $fVCenter = $this->_vcenter;      // Horizontal centering
4295     
4296         $header    = pack("vv", $record, $length);
4297         $data      = pack("v", $fVCenter);
4298         $this->_append($header.$data);
4299     }
4300     
4301     /**
4302     * Store the LEFTMARGIN BIFF record.
4303     */
4304     function _store_margin_left()
4305     {
4306         $record  = 0x0026;                   // Record identifier
4307         $length  = 0x0008;                   // Bytes to follow
4308     
4309         $margin  = $this->_margin_left;       // Margin in inches
4310     
4311         $header    = pack("vv",  $record, $length);
4312         $data      = pack("d",   $margin);
4313         if ($this->_byte_order) // if it's Big Endian
4314         { 
4315             $data = strrev($data);
4316         }
4317     
4318         $this->_append($header.$data);
4319     }
4320     
4321     /**
4322     * Store the RIGHTMARGIN BIFF record.
4323     */
4324     function _store_margin_right()
4325     {
4326         $record  = 0x0027;                   // Record identifier
4327         $length  = 0x0008;                   // Bytes to follow
4328     
4329         $margin  = $this->_margin_right;      // Margin in inches
4330     
4331         $header    = pack("vv",  $record, $length);
4332         $data      = pack("d",   $margin);
4333         if ($this->_byte_order) // if it's Big Endian
4334         { 
4335             $data = strrev($data);
4336         }
4337     
4338         $this->_append($header.$data);
4339     }
4340     
4341     /**
4342     * Store the TOPMARGIN BIFF record.
4343     */
4344     function _store_margin_top()
4345     {
4346         $record  = 0x0028;                   // Record identifier
4347         $length  = 0x0008;                   // Bytes to follow
4348     
4349         $margin  = $this->_margin_top;        // Margin in inches
4350     
4351         $header    = pack("vv",  $record, $length);
4352         $data      = pack("d",   $margin);
4353         if ($this->_byte_order) // if it's Big Endian
4354         { 
4355             $data = strrev($data);
4356         }
4357     
4358         $this->_append($header.$data);
4359     }
4360     
4361     /**
4362     * Store the BOTTOMMARGIN BIFF record.
4363     */
4364     function _store_margin_bottom()
4365     {
4366         $record  = 0x0029;                   // Record identifier
4367         $length  = 0x0008;                   // Bytes to follow
4368     
4369         $margin  = $this->_margin_bottom;     // Margin in inches
4370     
4371         $header    = pack("vv",  $record, $length);
4372         $data      = pack("d",   $margin);
4373         if ($this->_byte_order) // if it's Big Endian
4374         { 
4375             $data = strrev($data);
4376         }
4377     
4378         $this->_append($header.$data);
4379     }
4380
4381     /**
4382     * This is an Excel97/2000 method. It is required to perform more complicated
4383     * merging than the normal set_align('merge'). It merges the area given by 
4384     * its arguments.
4385     *
4386     * @access public
4387     * @param integer $first_row First row of the area to merge
4388     * @param integer $first_col First column of the area to merge
4389     * @param integer $last_row  Last row of the area to merge
4390     * @param integer $last_col  Last column of the area to merge
4391     */
4392     function merge_cells($first_row, $first_col, $last_row, $last_col)
4393     {
4394         $record  = 0x00E5;                   // Record identifier
4395         $length  = 0x000A;                   // Bytes to follow
4396         $cref     = 1;                       // Number of refs
4397
4398         // Swap last row/col for first row/col as necessary
4399         if ($first_row > $last_row) {
4400             list($first_row, $last_row) = array($last_row, $first_row);
4401         }
4402     
4403         if ($first_col > $last_col) {
4404             list($first_col, $last_col) = array($last_col, $first_col);
4405         }
4406     
4407         $header   = pack("vv",    $record, $length);
4408         $data     = pack("vvvvv", $cref, $first_row, $last_row,
4409                                   $first_col, $last_col);
4410     
4411         $this->_append($header.$data);
4412     }
4413     
4414     /**
4415     * Write the PRINTHEADERS BIFF record.
4416     */
4417     function _store_print_headers()
4418     {
4419         $record      = 0x002a;                   // Record identifier
4420         $length      = 0x0002;                   // Bytes to follow
4421     
4422         $fPrintRwCol = $this->_print_headers;     // Boolean flag
4423     
4424         $header      = pack("vv", $record, $length);
4425         $data        = pack("v", $fPrintRwCol);
4426         $this->_prepend($header.$data);
4427     }
4428     
4429     /**
4430     * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the
4431     * GRIDSET record.
4432     */
4433     function _store_print_gridlines()
4434     {
4435         $record      = 0x002b;                    // Record identifier
4436         $length      = 0x0002;                    // Bytes to follow
4437     
4438         $fPrintGrid  = $this->_print_gridlines;    // Boolean flag
4439     
4440         $header      = pack("vv", $record, $length);
4441         $data        = pack("v", $fPrintGrid);
4442         $this->_prepend($header.$data);
4443     }
4444     
4445     /**
4446     * Write the GRIDSET BIFF record. Must be used in conjunction with the
4447     * PRINTGRIDLINES record.
4448     */
4449     function _store_gridset()
4450     {
4451         $record      = 0x0082;                        // Record identifier
4452         $length      = 0x0002;                        // Bytes to follow
4453     
4454         $fGridSet    = !($this->_print_gridlines);     // Boolean flag
4455     
4456         $header      = pack("vv",  $record, $length);
4457         $data        = pack("v",   $fGridSet);
4458         $this->_prepend($header.$data);
4459     }
4460     
4461     /**
4462     * Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction
4463     * with the SETUP record.
4464     */
4465     function _store_wsbool()
4466     {
4467         $record      = 0x0081;   // Record identifier
4468         $length      = 0x0002;   // Bytes to follow
4469     
4470         // The only option that is of interest is the flag for fit to page. So we
4471         // set all the options in one go.
4472         //
4473         if ($this->_fit_page) {
4474             $grbit = 0x05c1;
4475         }
4476         else {
4477             $grbit = 0x04c1;
4478         }
4479     
4480         $header      = pack("vv", $record, $length);
4481         $data        = pack("v",  $grbit);
4482         $this->_prepend($header.$data);
4483     }
4484     
4485     
4486     /**
4487     * Write the HORIZONTALPAGEBREAKS BIFF record.
4488     */
4489     function _store_hbreak()
4490     {
4491         // Return if the user hasn't specified pagebreaks
4492         if(empty($this->_hbreaks)) {
4493             return;
4494         }
4495     
4496         // Sort and filter array of page breaks
4497         $breaks = $this->_hbreaks;
4498         sort($breaks,SORT_NUMERIC);
4499         if($breaks[0] == 0) { // don't use first break if it's 0
4500             array_shift($breaks);
4501         }
4502     
4503         $record  = 0x001b;               // Record identifier
4504         $cbrk    = count($breaks);       // Number of page breaks
4505         $length  = ($cbrk + 1) * 2;      // Bytes to follow
4506     
4507         $header  = pack("vv", $record, $length);
4508         $data    = pack("v",  $cbrk);
4509     
4510         // Append each page break
4511         foreach($breaks as $break) {
4512             $data .= pack("v", $break);
4513         }
4514     
4515         $this->_prepend($header.$data);
4516     }
4517     
4518     
4519     /**
4520     * Write the VERTICALPAGEBREAKS BIFF record.
4521     */
4522     function _store_vbreak()
4523     {
4524         // Return if the user hasn't specified pagebreaks
4525         if(empty($this->_vbreaks)) {
4526             return;
4527         }
4528     
4529         // 1000 vertical pagebreaks appears to be an internal Excel 5 limit.
4530         // It is slightly higher in Excel 97/200, approx. 1026
4531         $breaks = array_slice($this->_vbreaks,0,1000);
4532     
4533         // Sort and filter array of page breaks
4534         sort($breaks,SORT_NUMERIC);
4535         if($breaks[0] == 0) { // don't use first break if it's 0
4536             array_shift($breaks);
4537         }
4538     
4539         $record  = 0x001a;               // Record identifier
4540         $cbrk    = count($breaks);       // Number of page breaks
4541         $length  = ($cbrk + 1) * 2;      // Bytes to follow
4542     
4543         $header  = pack("vv",  $record, $length);
4544         $data    = pack("v",   $cbrk);
4545     
4546         // Append each page break
4547         foreach ($breaks as $break) {
4548             $data .= pack("v", $break);
4549         }
4550     
4551         $this->_prepend($header.$data);
4552     }
4553     
4554     /**
4555     * Set the Biff PROTECT record to indicate that the worksheet is protected.
4556     */
4557     function _store_protect()
4558     {
4559         // Exit unless sheet protection has been specified
4560         if($this->_protect == 0) {
4561             return;
4562         }
4563     
4564         $record      = 0x0012;             // Record identifier
4565         $length      = 0x0002;             // Bytes to follow
4566     
4567         $fLock       = $this->_protect;    // Worksheet is protected
4568     
4569         $header      = pack("vv", $record, $length);
4570         $data        = pack("v",  $fLock);
4571     
4572         $this->_prepend($header.$data);
4573     }
4574     
4575     /**
4576     * Write the worksheet PASSWORD record.
4577     */
4578     function _store_password()
4579     {
4580         // Exit unless sheet protection and password have been specified
4581         if(($this->_protect == 0) or (!isset($this->_password))) {
4582             return;
4583         }
4584     
4585         $record      = 0x0013;               // Record identifier
4586         $length      = 0x0002;               // Bytes to follow
4587     
4588         $wPassword   = $this->_password;     // Encoded password
4589     
4590         $header      = pack("vv", $record, $length);
4591         $data        = pack("v",  $wPassword);
4592     
4593         $this->_prepend($header.$data);
4594     }
4595     
4596     /**
4597     * Insert a 24bit bitmap image in a worksheet. The main record required is
4598     * IMDATA but it must be proceeded by a OBJ record to define its position.
4599     *
4600     * @access public
4601     * @param integer $row     The row we are going to insert the bitmap into
4602     * @param integer $col     The column we are going to insert the bitmap into
4603     * @param string  $bitmap  The bitmap filename
4604     * @param integer $x       The horizontal position (offset) of the image inside the cell.
4605     * @param integer $y       The vertical position (offset) of the image inside the cell.
4606     * @param integer $scale_x The horizontal scale
4607     * @param integer $scale_y The vertical scale
4608     */
4609     function insert_bitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1)
4610     {
4611         list($width, $height, $size, $data) = $this->_process_bitmap($bitmap);
4612     
4613         // Scale the frame of the image.
4614         $width  *= $scale_x;
4615         $height *= $scale_y;
4616     
4617         // Calculate the vertices of the image and write the OBJ record
4618         $this->_position_image($col, $row, $x, $y, $width, $height);
4619     
4620         // Write the IMDATA record to store the bitmap data
4621         $record      = 0x007f;
4622         $length      = 8 + $size;
4623         $cf          = 0x09;
4624         $env         = 0x01;
4625         $lcb         = $size;
4626     
4627         $header      = pack("vvvvV", $record, $length, $cf, $env, $lcb);
4628         $this->_append($header.$data);
4629     }
4630     
4631     /**
4632     * Calculate the vertices that define the position of the image as required by
4633     * the OBJ record.
4634     *
4635     *         +------------+------------+
4636     *         |     A      |      B     |
4637     *   +-----+------------+------------+
4638     *   |     |(x1,y1)     |            |
4639     *   |  1  |(A1)._______|______      |
4640     *   |     |    |              |     |
4641     *   |     |    |              |     |
4642     *   +-----+----|    BITMAP    |-----+
4643     *   |     |    |              |     |
4644     *   |  2  |    |______________.     |
4645     *   |     |            |        (B2)|
4646     *   |     |            |     (x2,y2)|
4647     *   +---- +------------+------------+
4648     *
4649     * Example of a bitmap that covers some of the area from cell A1 to cell B2.
4650     *
4651     * Based on the width and height of the bitmap we need to calculate 8 vars:
4652     *     $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
4653     * The width and height of the cells are also variable and have to be taken into
4654     * account.
4655     * The values of $col_start and $row_start are passed in from the calling
4656     * function. The values of $col_end and $row_end are calculated by subtracting
4657     * the width and height of the bitmap from the width and height of the
4658     * underlying cells.
4659     * The vertices are expressed as a percentage of the underlying cell width as
4660     * follows (rhs values are in pixels):
4661     *
4662     *       x1 = X / W *1024
4663     *       y1 = Y / H *256
4664     *       x2 = (X-1) / W *1024
4665     *       y2 = (Y-1) / H *256
4666     *
4667     *       Where:  X is distance from the left side of the underlying cell
4668     *               Y is distance from the top of the underlying cell
4669     *               W is the width of the cell
4670     *               H is the height of the cell
4671     *
4672     * @note  the SDK incorrectly states that the height should be expressed as a
4673     *        percentage of 1024.
4674     * @param integer $col_start Col containing upper left corner of object
4675     * @param integer $row_start Row containing top left corner of object
4676     * @param integer $x1        Distance to left side of object
4677     * @param integer $y1        Distance to top of object
4678     * @param integer $width     Width of image frame
4679     * @param integer $height    Height of image frame
4680     */
4681     function _position_image($col_start, $row_start, $x1, $y1, $width, $height)
4682     {
4683         // Initialise end cell to the same as the start cell
4684         $col_end    = $col_start;  // Col containing lower right corner of object
4685         $row_end    = $row_start;  // Row containing bottom right corner of object
4686     
4687         // Zero the specified offset if greater than the cell dimensions
4688         if ($x1 >= $this->size_col($col_start))
4689         {
4690             $x1 = 0;
4691         }
4692         if ($y1 >= $this->size_row($row_start))
4693         {
4694             $y1 = 0;
4695         }
4696     
4697         $width      = $width  + $x1 -1;
4698         $height     = $height + $y1 -1;
4699     
4700         // Subtract the underlying cell widths to find the end cell of the image
4701         while ($width >= $this->size_col($col_end)) {
4702             $width -= $this->size_col($col_end);
4703             $col_end++;
4704         }
4705     
4706         // Subtract the underlying cell heights to find the end cell of the image
4707         while ($height >= $this->size_row($row_end)) {
4708             $height -= $this->size_row($row_end);
4709             $row_end++;
4710         }
4711     
4712         // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
4713         // with zero eight or width.
4714         //
4715         if ($this->size_col($col_start) == 0)
4716             return;
4717         if ($this->size_col($col_end)   == 0)
4718             return;
4719         if ($this->size_row($row_start) == 0)
4720             return;
4721         if ($this->size_row($row_end)   == 0)
4722             return;
4723     
4724         // Convert the pixel values to the percentage value expected by Excel
4725         $x1 = $x1     / $this->size_col($col_start)   * 1024;
4726         $y1 = $y1     / $this->size_row($row_start)   *  256;
4727         $x2 = $width  / $this->size_col($col_end)     * 1024; // Distance to right side of object
4728         $y2 = $height / $this->size_row($row_end)     *  256; // Distance to bottom of object
4729     
4730         $this->_store_obj_picture( $col_start, $x1,
4731                                   $row_start, $y1,
4732                                   $col_end, $x2,
4733                                   $row_end, $y2
4734                                 );
4735     }
4736     
4737     /**
4738     * Convert the width of a cell from user's units to pixels. By interpolation
4739     * the relationship is: y = 7x +5. If the width hasn't been set by the user we
4740     * use the default value. If the col is hidden we use a value of zero.
4741     *
4742     * @param integer  $col The column 
4743     * @return integer The width in pixels
4744     */
4745     function size_col($col)
4746     {
4747         // Look up the cell value to see if it has been changed
4748         if (isset($this->col_sizes[$col])) {
4749             if ($this->col_sizes[$col] == 0) {
4750                 return(0);
4751             }
4752             else {
4753                 return(floor(7 * $this->col_sizes[$col] + 5));
4754             }
4755         }
4756         else {
4757             return(64);
4758         }
4759     }
4760     
4761     /**
4762     * Convert the height of a cell from user's units to pixels. By interpolation
4763     * the relationship is: y = 4/3x. If the height hasn't been set by the user we
4764     * use the default value. If the row is hidden we use a value of zero. (Not
4765     * possible to hide row yet).
4766     *
4767     * @param integer $row The row
4768     * @return integer The width in pixels
4769     */
4770     function size_row($row)
4771     {
4772         // Look up the cell value to see if it has been changed
4773         if (isset($this->row_sizes[$row])) {
4774             if ($this->row_sizes[$row] == 0) {
4775                 return(0);
4776             }
4777             else {
4778                 return(floor(4/3 * $this->row_sizes[$row]));
4779             }
4780         }
4781         else {
4782             return(17);
4783         }
4784     }
4785     
4786     /**
4787     * Store the OBJ record that precedes an IMDATA record. This could be generalise
4788     * to support other Excel objects.
4789     *
4790     * @param integer $colL Column containing upper left corner of object
4791     * @param integer $dxL  Distance from left side of cell
4792     * @param integer $rwT  Row containing top left corner of object
4793     * @param integer $dyT  Distance from top of cell
4794     * @param integer $colR Column containing lower right corner of object
4795     * @param integer $dxR  Distance from right of cell
4796     * @param integer $rwB  Row containing bottom right corner of object
4797     * @param integer $dyB  Distance from bottom of cell
4798     */
4799     function _store_obj_picture($colL,$dxL,$rwT,$dyT,$colR,$dxR,$rwB,$dyB)
4800     {
4801         $record      = 0x005d;   // Record identifier
4802         $length      = 0x003c;   // Bytes to follow
4803     
4804         $cObj        = 0x0001;   // Count of objects in file (set to 1)
4805         $OT          = 0x0008;   // Object type. 8 = Picture
4806         $id          = 0x0001;   // Object ID
4807         $grbit       = 0x0614;   // Option flags
4808     
4809         $cbMacro     = 0x0000;   // Length of FMLA structure
4810         $Reserved1   = 0x0000;   // Reserved
4811         $Reserved2   = 0x0000;   // Reserved
4812     
4813         $icvBack     = 0x09;     // Background colour
4814         $icvFore     = 0x09;     // Foreground colour
4815         $fls         = 0x00;     // Fill pattern
4816         $fAuto       = 0x00;     // Automatic fill
4817         $icv         = 0x08;     // Line colour
4818         $lns         = 0xff;     // Line style
4819         $lnw         = 0x01;     // Line weight
4820         $fAutoB      = 0x00;     // Automatic border
4821         $frs         = 0x0000;   // Frame style
4822         $cf          = 0x0009;   // Image format, 9 = bitmap
4823         $Reserved3   = 0x0000;   // Reserved
4824         $cbPictFmla  = 0x0000;   // Length of FMLA structure
4825         $Reserved4   = 0x0000;   // Reserved
4826         $grbit2      = 0x0001;   // Option flags
4827         $Reserved5   = 0x0000;   // Reserved
4828     
4829     
4830         $header      = pack("vv", $record, $length);
4831         $data        = pack("V", $cObj);
4832         $data       .= pack("v", $OT);
4833         $data       .= pack("v", $id);
4834         $data       .= pack("v", $grbit);
4835         $data       .= pack("v", $colL);
4836         $data       .= pack("v", $dxL);
4837         $data       .= pack("v", $rwT);
4838         $data       .= pack("v", $dyT);
4839         $data       .= pack("v", $colR);
4840         $data       .= pack("v", $dxR);
4841         $data       .= pack("v", $rwB);
4842         $data       .= pack("v", $dyB);
4843         $data       .= pack("v", $cbMacro);
4844         $data       .= pack("V", $Reserved1);
4845         $data       .= pack("v", $Reserved2);
4846         $data       .= pack("C", $icvBack);
4847         $data       .= pack("C", $icvFore);
4848         $data       .= pack("C", $fls);
4849         $data       .= pack("C", $fAuto);
4850         $data       .= pack("C", $icv);
4851         $data       .= pack("C", $lns);
4852         $data       .= pack("C", $lnw);
4853         $data       .= pack("C", $fAutoB);
4854         $data       .= pack("v", $frs);
4855         $data       .= pack("V", $cf);
4856         $data       .= pack("v", $Reserved3);
4857         $data       .= pack("v", $cbPictFmla);
4858         $data       .= pack("v", $Reserved4);
4859         $data       .= pack("v", $grbit2);
4860         $data       .= pack("V", $Reserved5);
4861     
4862         $this->_append($header.$data);
4863     }
4864     
4865     /**
4866     * Convert a 24 bit bitmap into the modified internal format used by Windows.
4867     * This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
4868     * MSDN library.
4869     *
4870     * @param string $bitmap The bitmap to process
4871     * @return array Array with data and properties of the bitmap
4872     */
4873     function _process_bitmap($bitmap)
4874     {
4875         // Open file.
4876         $bmp_fd = fopen($bitmap,"rb");
4877         if (!$bmp_fd) {
4878             die("Couldn't import $bitmap");
4879         }
4880             
4881         // Slurp the file into a string.
4882         $data = fread($bmp_fd, filesize($bitmap));
4883     
4884         // Check that the file is big enough to be a bitmap.
4885         if (strlen($data) <= 0x36) {
4886             die("$bitmap doesn't contain enough data.\n");
4887         }
4888     
4889         // The first 2 bytes are used to identify the bitmap.
4890         $identity = unpack("A2", $data);
4891         if ($identity[''] != "BM") {
4892             die("$bitmap doesn't appear to be a valid bitmap image.\n");
4893         }
4894     
4895         // Remove bitmap data: ID.
4896         $data = substr($data, 2);
4897     
4898         // Read and remove the bitmap size. This is more reliable than reading
4899         // the data size at offset 0x22.
4900         //
4901         $size_array   = unpack("V", substr($data, 0, 4));
4902         $size   = $size_array[''];
4903         $data   = substr($data, 4);
4904         $size  -= 0x36; // Subtract size of bitmap header.
4905         $size  += 0x0C; // Add size of BIFF header.
4906     
4907         // Remove bitmap data: reserved, offset, header length.
4908         $data = substr($data, 12);
4909     
4910         // Read and remove the bitmap width and height. Verify the sizes.
4911         $width_and_height = unpack("V2", substr($data, 0, 8));
4912         $width  = $width_and_height[1];
4913         $height = $width_and_height[2];
4914         $data   = substr($data, 8);
4915         if ($width > 0xFFFF) { 
4916             die("$bitmap: largest image width supported is 65k.\n");
4917         }
4918         if ($height > 0xFFFF) { 
4919             die("$bitmap: largest image height supported is 65k.\n");
4920         }
4921     
4922         // Read and remove the bitmap planes and bpp data. Verify them.
4923         $planes_and_bitcount = unpack("v2", substr($data, 0, 4));
4924         $data = substr($data, 4);
4925         if ($planes_and_bitcount[2] != 24) { // Bitcount
4926             die("$bitmap isn't a 24bit true color bitmap.\n");
4927         }
4928         if ($planes_and_bitcount[1] != 1) {
4929             die("$bitmap: only 1 plane supported in bitmap image.\n");
4930         }
4931     
4932         // Read and remove the bitmap compression. Verify compression.
4933         $compression = unpack("V", substr($data, 0, 4));
4934         $data = substr($data, 4);
4935       
4936         //$compression = 0;
4937         if ($compression[""] != 0) {
4938             die("$bitmap: compression not supported in bitmap image.\n");
4939         }
4940     
4941         // Remove bitmap data: data size, hres, vres, colours, imp. colours.
4942         $data = substr($data, 20);
4943     
4944         // Add the BITMAPCOREHEADER data
4945         $header  = pack("Vvvvv", 0x000c, $width, $height, 0x01, 0x18);
4946         $data    = $header . $data;
4947     
4948         return (array($width, $height, $size, $data));
4949     }
4950     
4951     /**
4952     * Store the window zoom factor. This should be a reduced fraction but for
4953     * simplicity we will store all fractions with a numerator of 100.
4954     */
4955     function _store_zoom()
4956     {
4957         // If scale is 100 we don't need to write a record
4958         if ($this->_zoom == 100) {
4959             return;
4960         }
4961     
4962         $record      = 0x00A0;               // Record identifier
4963         $length      = 0x0004;               // Bytes to follow
4964     
4965         $header      = pack("vv", $record, $length);
4966         $data        = pack("vv", $this->_zoom, 100);
4967         $this->_append($header.$data);
4968     }
4969 }
4970
4971 /**
4972 * Class for generating Excel Spreadsheets
4973 *
4974 * @author Xavier Noguer <xnoguer@rezebra.com>
4975 * @package Spreadsheet_WriteExcel
4976 */
4977
4978 class Workbook extends BIFFwriter
4979 {
4980     /**
4981     * Class constructor
4982     *
4983     * @param string filename for storing the workbook. "-" for writing to stdout.
4984     */
4985     function Workbook($filename)
4986     {
4987         $this->BIFFwriter(); // It needs to call its parent's constructor explicitly
4988     
4989         $this->_filename         = $filename;
4990         $this->parser            = new Parser($this->_byte_order);
4991         $this->_1904             = 0;
4992         $this->activesheet       = 0;
4993         $this->firstsheet        = 0;
4994         $this->selected          = 0;
4995         $this->xf_index          = 16; // 15 style XF's and 1 cell XF.
4996         $this->_fileclosed       = 0;
4997         $this->_biffsize         = 0;
4998         $this->sheetname         = "Sheet";
4999         $this->tmp_format        = new Format();
5000         $this->worksheets        = array();
5001         $this->sheetnames        = array();
5002         $this->formats           = array();
5003         $this->palette           = array();
5004     
5005         // Add the default format for hyperlinks
5006         $this->url_format =& $this->add_format(array('color' => 'blue', 'underline' => 1));
5007     
5008         // Check for a filename
5009         //if ($this->_filename == '') {
5010         //    die('Filename required by Spreadsheet::WriteExcel->new()');
5011         //}
5012     
5013         # Warn if tmpfiles can't be used.
5014         //$this->tmpfile_warning();
5015         $this->_set_palette_xl97();
5016     }
5017     
5018     /**
5019     * Calls finalization methods and explicitly close the OLEwriter file
5020     * handle.
5021     */
5022     function close()
5023     {
5024         if ($this->_fileclosed) { // Prevent close() from being called twice.
5025             return;
5026         }
5027         $this->store_workbook();
5028         $this->_fileclosed = 1;
5029     }
5030     
5031     
5032     /**
5033     * An accessor for the _worksheets[] array
5034     * Returns an array of the worksheet objects in a workbook
5035     *
5036     * @return array
5037     */
5038     function sheets()
5039     {
5040         return($this->worksheets());
5041     }
5042     
5043     /**
5044     * An accessor for the _worksheets[] array.
5045     *
5046     * @return array
5047     */
5048     function worksheets()
5049     {
5050         return($this->worksheets);
5051     }
5052     
5053     /**
5054     * Add a new worksheet to the Excel workbook.
5055     * TODO: Add accessor for $this->{_sheetname} for international Excel versions.
5056     *
5057     * @access public
5058     * @param string $name the optional name of the worksheet
5059     * @return &object reference to a worksheet object
5060     */
5061     function &add_worksheet($name = '')
5062     {
5063         $index     = count($this->worksheets);
5064         $sheetname = $this->sheetname;
5065
5066         if($name == '') {
5067             $name = $sheetname.($index+1); 
5068         }
5069     
5070         // Check that sheetname is <= 31 chars (Excel limit).
5071         if(strlen($name) > 31) {
5072             die("Sheetname $name must be <= 31 chars");
5073         }
5074     
5075         // Check that the worksheet name doesn't already exist: a fatal Excel error.
5076         for($i=0; $i < count($this->worksheets); $i++)
5077         {
5078             if($name == $this->worksheets[$i]->get_name()) {
5079                 die("Worksheet '$name' already exists");
5080             }
5081         }
5082     
5083         $worksheet = new Worksheet($name,$index,$this->activesheet,
5084                                    $this->firstsheet,$this->url_format,
5085                                    $this->parser);
5086         $this->worksheets[$index] = &$worksheet;      // Store ref for iterator
5087         $this->sheetnames[$index] = $name;            // Store EXTERNSHEET names
5088         //$this->parser->set_ext_sheet($name,$index); // Store names in Formula.php
5089         return($worksheet);
5090     }
5091     
5092     /**
5093     * DEPRECATED!! Use add_worksheet instead
5094     *
5095     * @access public
5096     * @deprecated Use add_worksheet instead
5097     * @param string $name the optional name of the worksheet
5098     * @return &object reference to a worksheet object
5099     */
5100     function &addworksheet($name = '')
5101     {
5102         return($this->add_worksheet($name));
5103     }
5104     
5105     /**
5106     * Add a new format to the Excel workbook. This adds an XF record and
5107     * a FONT record. Also, pass any properties to the Format constructor.
5108     *
5109     * @access public
5110     * @param array $properties array with properties for initializing the format (see Format.php)
5111     * @return &object reference to an XF format
5112     */
5113     function &add_format($properties = array())
5114     {
5115         $format = new Format($this->xf_index,$properties);
5116         $this->xf_index += 1;
5117         $this->formats[] = &$format;
5118         return($format);
5119     }
5120     
5121     /**
5122     * DEPRECATED!! Use add_format instead
5123     *
5124     * @access public
5125     * @deprecated Use add_format instead
5126     * @param array $properties array with properties for initializing the format (see Format.php)
5127     * @return &object reference to an XF format
5128     */
5129     function &addformat($properties = array())
5130     {
5131          return($this->add_format($properties));
5132     }
5133     
5134     
5135     /**
5136     * Change the RGB components of the elements in the colour palette.
5137     *
5138     * @access public
5139     * @param integer $index colour index
5140     * @param integer $red   red RGB value [0-255]
5141     * @param integer $green green RGB value [0-255]
5142     * @param integer $blue  blue RGB value [0-255]
5143     * @return integer The palette index for the custom color
5144     */
5145     function set_custom_color($index,$red,$green,$blue)
5146     {
5147         // Match a HTML #xxyyzz style parameter
5148         /*if (defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) {
5149             @_ = ($_[0], hex $1, hex $2, hex $3);
5150         }*/
5151     
5152         // Check that the colour index is the right range
5153         if ($index < 8 or $index > 64) {
5154             die("Color index $index outside range: 8 <= index <= 64");
5155         }
5156     
5157         // Check that the colour components are in the right range
5158         if ( ($red   < 0 or $red   > 255) ||
5159              ($green < 0 or $green > 255) ||
5160              ($blue  < 0 or $blue  > 255) )  
5161         {
5162             die("Color component outside range: 0 <= color <= 255");
5163         }
5164
5165         $index -= 8; // Adjust colour index (wingless dragonfly)
5166         
5167         // Set the RGB value
5168         $this->palette[$index] = array($red, $green, $blue, 0);
5169         return($index + 8);
5170     }
5171     
5172     /**
5173     * Sets the colour palette to the Excel 97+ default.
5174     */
5175     function _set_palette_xl97()
5176     {
5177         $this->palette = array(
5178                            array(0x00, 0x00, 0x00, 0x00),   // 8
5179                            array(0xff, 0xff, 0xff, 0x00),   // 9
5180                            array(0xff, 0x00, 0x00, 0x00),   // 10
5181                            array(0x00, 0xff, 0x00, 0x00),   // 11
5182                            array(0x00, 0x00, 0xff, 0x00),   // 12
5183                            array(0xff, 0xff, 0x00, 0x00),   // 13
5184                            array(0xff, 0x00, 0xff, 0x00),   // 14
5185                            array(0x00, 0xff, 0xff, 0x00),   // 15
5186                            array(0x80, 0x00, 0x00, 0x00),   // 16
5187                            array(0x00, 0x80, 0x00, 0x00),   // 17
5188                            array(0x00, 0x00, 0x80, 0x00),   // 18
5189                            array(0x80, 0x80, 0x00, 0x00),   // 19
5190                            array(0x80, 0x00, 0x80, 0x00),   // 20
5191                            array(0x00, 0x80, 0x80, 0x00),   // 21
5192                            array(0xc0, 0xc0, 0xc0, 0x00),   // 22
5193                            array(0x80, 0x80, 0x80, 0x00),   // 23
5194                            array(0x99, 0x99, 0xff, 0x00),   // 24
5195                            array(0x99, 0x33, 0x66, 0x00),   // 25
5196                            array(0xff, 0xff, 0xcc, 0x00),   // 26
5197                            array(0xcc, 0xff, 0xff, 0x00),   // 27
5198                            array(0x66, 0x00, 0x66, 0x00),   // 28
5199                            array(0xff, 0x80, 0x80, 0x00),   // 29
5200                            array(0x00, 0x66, 0xcc, 0x00),   // 30
5201                            array(0xcc, 0xcc, 0xff, 0x00),   // 31
5202                            array(0x00, 0x00, 0x80, 0x00),   // 32
5203                            array(0xff, 0x00, 0xff, 0x00),   // 33
5204                            array(0xff, 0xff, 0x00, 0x00),   // 34
5205                            array(0x00, 0xff, 0xff, 0x00),   // 35
5206                            array(0x80, 0x00, 0x80, 0x00),   // 36
5207                            array(0x80, 0x00, 0x00, 0x00),   // 37
5208                            array(0x00, 0x80, 0x80, 0x00),   // 38
5209                            array(0x00, 0x00, 0xff, 0x00),   // 39
5210                            array(0x00, 0xcc, 0xff, 0x00),   // 40
5211                            array(0xcc, 0xff, 0xff, 0x00),   // 41
5212                            array(0xcc, 0xff, 0xcc, 0x00),   // 42
5213                            array(0xff, 0xff, 0x99, 0x00),   // 43
5214                            array(0x99, 0xcc, 0xff, 0x00),   // 44
5215                            array(0xff, 0x99, 0xcc, 0x00),   // 45
5216                            array(0xcc, 0x99, 0xff, 0x00),   // 46
5217                            array(0xff, 0xcc, 0x99, 0x00),   // 47
5218                            array(0x33, 0x66, 0xff, 0x00),   // 48
5219                            array(0x33, 0xcc, 0xcc, 0x00),   // 49
5220                            array(0x99, 0xcc, 0x00, 0x00),   // 50
5221                            array(0xff, 0xcc, 0x00, 0x00),   // 51
5222                            array(0xff, 0x99, 0x00, 0x00),   // 52
5223                            array(0xff, 0x66, 0x00, 0x00),   // 53
5224                            array(0x66, 0x66, 0x99, 0x00),   // 54
5225                            array(0x96, 0x96, 0x96, 0x00),   // 55
5226                            array(0x00, 0x33, 0x66, 0x00),   // 56
5227                            array(0x33, 0x99, 0x66, 0x00),   // 57
5228                            array(0x00, 0x33, 0x00, 0x00),   // 58
5229                            array(0x33, 0x33, 0x00, 0x00),   // 59
5230                            array(0x99, 0x33, 0x00, 0x00),   // 60
5231                            array(0x99, 0x33, 0x66, 0x00),   // 61
5232                            array(0x33, 0x33, 0x99, 0x00),   // 62
5233                            array(0x33, 0x33, 0x33, 0x00),   // 63
5234                          );
5235     }
5236     
5237     
5238     ###############################################################################
5239     #
5240     # _tmpfile_warning()
5241     #
5242     # Check that tmp files can be created for use in Worksheet.pm. A CGI, mod_perl
5243     # or IIS might not have permission to create tmp files. The test is here rather
5244     # than in Worksheet.pm so that only one warning is given.
5245     #
5246     /*sub _tmpfile_warning {
5247     
5248         my $fh = IO::File->new_tmpfile();
5249     
5250         if ((not defined $fh) && ($^W)) {
5251             carp("Unable to create tmp files via IO::File->new_tmpfile(). " .
5252                  "Storing data in memory")
5253     }
5254     }*/
5255     
5256     /**
5257     * Assemble worksheets into a workbook and send the BIFF data to an OLE
5258     * storage.
5259     */
5260     function store_workbook()
5261     {
5262         // Ensure that at least one worksheet has been selected.
5263         if ($this->activesheet == 0) {
5264             $this->worksheets[0]->selected = 1;
5265         }
5266     
5267         // Calculate the number of selected worksheet tabs and call the finalization
5268         // methods for each worksheet
5269         for($i=0; $i < count($this->worksheets); $i++)
5270         {
5271             if($this->worksheets[$i]->selected)
5272               $this->selected++;
5273             $this->worksheets[$i]->close($this->sheetnames);
5274         }
5275     
5276         // Add Workbook globals
5277         $this->_store_bof(0x0005);
5278         $this->_store_externs();    // For print area and repeat rows
5279         $this->_store_names();      // For print area and repeat rows
5280         $this->_store_window1();
5281         $this->_store_1904();
5282         $this->_store_all_fonts();
5283         $this->_store_all_num_formats();
5284         $this->_store_all_xfs();
5285         $this->_store_all_styles();
5286         $this->_store_palette();
5287         $this->_calc_sheet_offsets();
5288     
5289         // Add BOUNDSHEET records
5290         for($i=0; $i < count($this->worksheets); $i++) {
5291             $this->_store_boundsheet($this->worksheets[$i]->name,$this->worksheets[$i]->offset);
5292         }
5293     
5294         // End Workbook globals
5295         $this->_store_eof();
5296
5297         // Store the workbook in an OLE container
5298         $this->_store_OLE_file();
5299     }
5300     
5301     /**
5302     * Store the workbook in an OLE container if the total size of the workbook data
5303     * is less than ~ 7MB.
5304     */
5305     function _store_OLE_file()
5306     {
5307         $OLE  = new OLEwriter($this->_filename);
5308         // Write Worksheet data if data <~ 7MB
5309         if ($OLE->set_size($this->_biffsize))
5310         {
5311             $OLE->write_header();
5312             $OLE->write($this->_data);
5313             foreach($this->worksheets as $sheet) 
5314             {
5315                 while ($tmp = $sheet->get_data()) {
5316                     $OLE->write($tmp);
5317                 }
5318             }
5319         }
5320         $OLE->close();
5321     }
5322     
5323     /**
5324     * Calculate offsets for Worksheet BOF records.
5325     */
5326     function _calc_sheet_offsets()
5327     {
5328         $BOF     = 11;
5329         $EOF     = 4;
5330         $offset  = $this->_datasize;
5331         for($i=0; $i < count($this->worksheets); $i++) {
5332             $offset += $BOF + strlen($this->worksheets[$i]->name);
5333         }
5334         $offset += $EOF;
5335         for($i=0; $i < count($this->worksheets); $i++) {
5336             $this->worksheets[$i]->offset = $offset;
5337             $offset += $this->worksheets[$i]->_datasize;
5338         }
5339         $this->_biffsize = $offset;
5340     }
5341     
5342     /**
5343     * Store the Excel FONT records.
5344     */
5345     function _store_all_fonts()
5346     {
5347         // tmp_format is added by new(). We use this to write the default XF's
5348         $format = $this->tmp_format;
5349         $font   = $format->get_font();
5350     
5351         // Note: Fonts are 0-indexed. According to the SDK there is no index 4,
5352         // so the following fonts are 0, 1, 2, 3, 5
5353         //
5354         for($i=1; $i <= 5; $i++){
5355             $this->_append($font);
5356         }
5357     
5358         // Iterate through the XF objects and write a FONT record if it isn't the
5359         // same as the default FONT and if it hasn't already been used.
5360         //
5361         $fonts = array();
5362         $index = 6;                  // The first user defined FONT
5363     
5364         $key = $format->get_font_key(); // The default font from _tmp_format
5365         $fonts[$key] = 0;               // Index of the default font
5366     
5367         for($i=0; $i < count($this->formats); $i++) {
5368             $key = $this->formats[$i]->get_font_key();
5369             if (isset($fonts[$key])) {
5370                 // FONT has already been used
5371                 $this->formats[$i]->font_index = $fonts[$key];
5372             }
5373             else {
5374                 // Add a new FONT record
5375                 $fonts[$key]        = $index;
5376                 $this->formats[$i]->font_index = $index;
5377                 $index++;
5378                 $font = $this->formats[$i]->get_font();
5379                 $this->_append($font);
5380             }
5381         }
5382     }
5383     
5384     /**
5385     * Store user defined numerical formats i.e. FORMAT records
5386     */
5387     function _store_all_num_formats()
5388     {
5389         // Leaning num_format syndrome
5390         $hash_num_formats = array();
5391         $num_formats      = array();
5392         $index = 164;
5393     
5394         // Iterate through the XF objects and write a FORMAT record if it isn't a
5395         // built-in format type and if the FORMAT string hasn't already been used.
5396         //
5397         for($i=0; $i < count($this->formats); $i++)
5398         {
5399             $num_format = $this->formats[$i]->_num_format;
5400     
5401             // Check if $num_format is an index to a built-in format.
5402             // Also check for a string of zeros, which is a valid format string
5403             // but would evaluate to zero.
5404             //
5405             if (!preg_match("/^0+\d/",$num_format))
5406             {
5407                 if (preg_match("/^\d+$/",$num_format)) { // built-in format
5408                     continue;
5409                 }
5410             }
5411     
5412             if (isset($hash_num_formats[$num_format])) {
5413                 // FORMAT has already been used
5414                 $this->formats[$i]->_num_format = $hash_num_formats[$num_format];
5415             }
5416             else
5417             {
5418                 // Add a new FORMAT
5419                 $hash_num_formats[$num_format]  = $index;
5420                 $this->formats[$i]->_num_format = $index;
5421                 array_push($num_formats,$num_format);
5422                 $index++;
5423             }
5424         }
5425     
5426         // Write the new FORMAT records starting from 0xA4
5427         $index = 164;
5428         foreach ($num_formats as $num_format)
5429         {
5430             $this->_store_num_format($num_format,$index);
5431             $index++;
5432         }
5433     }
5434     
5435     /**
5436     * Write all XF records.
5437     */
5438     function _store_all_xfs()
5439     {
5440         // tmp_format is added by the constructor. We use this to write the default XF's
5441         // The default font index is 0
5442         //
5443         $format = $this->tmp_format;
5444         for ($i=0; $i <= 14; $i++)
5445         {
5446             $xf = $format->get_xf('style'); // Style XF
5447             $this->_append($xf);
5448         }
5449     
5450         $xf = $format->get_xf('cell');      // Cell XF
5451         $this->_append($xf);
5452     
5453         // User defined XFs
5454         for($i=0; $i < count($this->formats); $i++)
5455         {
5456             $xf = $this->formats[$i]->get_xf('cell');
5457             $this->_append($xf);
5458         }
5459     }
5460     
5461     /**
5462     * Write all STYLE records.
5463     */
5464     function _store_all_styles()
5465     {
5466         $this->_store_style();
5467     }
5468     
5469     /**
5470     * Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
5471     * the NAME records.
5472     */
5473     function _store_externs()
5474     {
5475         // Create EXTERNCOUNT with number of worksheets
5476         $this->_store_externcount(count($this->worksheets));
5477     
5478         // Create EXTERNSHEET for each worksheet
5479         foreach ($this->sheetnames as $sheetname) {
5480             $this->_store_externsheet($sheetname);
5481         }
5482     }
5483     
5484     /**
5485     * Write the NAME record to define the print area and the repeat rows and cols.
5486     */
5487     function _store_names()
5488     {
5489         // Create the print area NAME records
5490         foreach ($this->worksheets as $worksheet)
5491         {
5492             // Write a Name record if the print area has been defined
5493             if (isset($worksheet->_print_rowmin))
5494             {
5495                 $this->store_name_short(
5496                     $worksheet->index,
5497                     0x06, // NAME type
5498                     $worksheet->_print_rowmin,
5499                     $worksheet->_print_rowmax,
5500                     $worksheet->_print_colmin,
5501                     $worksheet->_print_colmax
5502                     );
5503             }
5504         }
5505     
5506         // Create the print title NAME records
5507         foreach ($this->worksheets as $worksheet)
5508         {
5509             $rowmin = $worksheet->_title_rowmin;
5510             $rowmax = $worksheet->_title_rowmax;
5511             $colmin = $worksheet->_title_colmin;
5512             $colmax = $worksheet->_title_colmax;
5513     
5514             // Determine if row + col, row, col or nothing has been defined
5515             // and write the appropriate record
5516             //
5517             if (isset($rowmin) && isset($colmin))
5518             {
5519                 // Row and column titles have been defined.
5520                 // Row title has been defined.
5521                 $this->store_name_long(
5522                     $worksheet->index,
5523                     0x07, // NAME type
5524                     $rowmin,
5525                     $rowmax,
5526                     $colmin,
5527                     $colmax
5528                     );
5529             }
5530             elseif (isset($rowmin))
5531             {
5532                 // Row title has been defined.
5533                 $this->store_name_short(
5534                     $worksheet->index,
5535                     0x07, // NAME type
5536                     $rowmin,
5537                     $rowmax,
5538                     0x00,
5539                     0xff
5540                     );
5541             }
5542             elseif (isset($colmin))
5543             {
5544                 // Column title has been defined.
5545                 $this->store_name_short(
5546                     $worksheet->index,
5547                     0x07, // NAME type
5548                     0x0000,
5549                     0x3fff,
5550                     $colmin,
5551                     $colmax
5552                     );
5553             }
5554             else {
5555                 // Print title hasn't been defined.
5556             }
5557         }
5558     }
5559     
5560
5561     
5562     
5563     /******************************************************************************
5564     *
5565     * BIFF RECORDS
5566     *
5567     */
5568     
5569     /**
5570     * Write Excel BIFF WINDOW1 record.
5571     */
5572     function _store_window1()
5573     {
5574         $record    = 0x003D;                 // Record identifier
5575         $length    = 0x0012;                 // Number of bytes to follow
5576     
5577         $xWn       = 0x0000;                 // Horizontal position of window
5578         $yWn       = 0x0000;                 // Vertical position of window
5579         $dxWn      = 0x25BC;                 // Width of window
5580         $dyWn      = 0x1572;                 // Height of window
5581     
5582         $grbit     = 0x0038;                 // Option flags
5583         $ctabsel   = $this->selected;        // Number of workbook tabs selected
5584         $wTabRatio = 0x0258;                 // Tab to scrollbar ratio
5585     
5586         $itabFirst = $this->firstsheet;   // 1st displayed worksheet
5587         $itabCur   = $this->activesheet;  // Active worksheet
5588     
5589         $header    = pack("vv",        $record, $length);
5590         $data      = pack("vvvvvvvvv", $xWn, $yWn, $dxWn, $dyWn,
5591                                        $grbit,
5592                                        $itabCur, $itabFirst,
5593                                        $ctabsel, $wTabRatio);
5594         $this->_append($header.$data);
5595     }
5596     
5597     /**
5598     * Writes Excel BIFF BOUNDSHEET record.
5599     *
5600     * @param string  $sheetname Worksheet name
5601     * @param integer $offset    Location of worksheet BOF
5602     */
5603     function _store_boundsheet($sheetname,$offset)
5604     {
5605         $record    = 0x0085;                    // Record identifier
5606         $length    = 0x07 + strlen($sheetname); // Number of bytes to follow
5607     
5608         $grbit     = 0x0000;                    // Sheet identifier
5609         $cch       = strlen($sheetname);        // Length of sheet name
5610     
5611         $header    = pack("vv",  $record, $length);
5612         $data      = pack("VvC", $offset, $grbit, $cch);
5613         $this->_append($header.$data.$sheetname);
5614     }
5615     
5616     /**
5617     * Write Excel BIFF STYLE records.
5618     */
5619     function _store_style()
5620     {
5621         $record    = 0x0293;   // Record identifier
5622         $length    = 0x0004;   // Bytes to follow
5623                                
5624         $ixfe      = 0x8000;   // Index to style XF
5625         $BuiltIn   = 0x00;     // Built-in style
5626         $iLevel    = 0xff;     // Outline style level
5627     
5628         $header    = pack("vv",  $record, $length);
5629         $data      = pack("vCC", $ixfe, $BuiltIn, $iLevel);
5630         $this->_append($header.$data);
5631     }
5632     
5633     
5634     /**
5635     * Writes Excel FORMAT record for non "built-in" numerical formats.
5636     *
5637     * @param string  $format Custom format string
5638     * @param integer $ifmt   Format index code
5639     */
5640     function _store_num_format($format,$ifmt)
5641     {
5642         $record    = 0x041E;                      // Record identifier
5643         $length    = 0x03 + strlen($format);      // Number of bytes to follow
5644     
5645         $cch       = strlen($format);             // Length of format string
5646     
5647         $header    = pack("vv", $record, $length);
5648         $data      = pack("vC", $ifmt, $cch);
5649         $this->_append($header.$data.$format);
5650     }
5651     
5652     /**
5653     * Write Excel 1904 record to indicate the date system in use.
5654     */
5655     function _store_1904()
5656     {
5657         $record    = 0x0022;         // Record identifier
5658         $length    = 0x0002;         // Bytes to follow
5659
5660         $f1904     = $this->_1904;   // Flag for 1904 date system
5661     
5662         $header    = pack("vv", $record, $length);
5663         $data      = pack("v", $f1904);
5664         $this->_append($header.$data);
5665     }
5666     
5667     
5668     /**
5669     * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
5670     * references in the workbook.
5671     *
5672     * Excel only stores references to external sheets that are used in NAME.
5673     * The workbook NAME record is required to define the print area and the repeat
5674     * rows and columns.
5675     *
5676     * A similar method is used in Worksheet.php for a slightly different purpose.
5677     *
5678     * @param integer $cxals Number of external references
5679     */
5680     function _store_externcount($cxals)
5681     {
5682         $record   = 0x0016;          // Record identifier
5683         $length   = 0x0002;          // Number of bytes to follow
5684     
5685         $header   = pack("vv", $record, $length);
5686         $data     = pack("v",  $cxals);
5687         $this->_append($header.$data);
5688     }
5689     
5690     
5691     /**
5692     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
5693     * formulas. NAME record is required to define the print area and the repeat
5694     * rows and columns.
5695     *
5696     * A similar method is used in Worksheet.php for a slightly different purpose.
5697     *
5698     * @param string $sheetname Worksheet name
5699     */
5700     function _store_externsheet($sheetname)
5701     {
5702         $record      = 0x0017;                     // Record identifier
5703         $length      = 0x02 + strlen($sheetname);  // Number of bytes to follow
5704                                                    
5705         $cch         = strlen($sheetname);         // Length of sheet name
5706         $rgch        = 0x03;                       // Filename encoding
5707     
5708         $header      = pack("vv",  $record, $length);
5709         $data        = pack("CC", $cch, $rgch);
5710         $this->_append($header.$data.$sheetname);
5711     }
5712     
5713     
5714     /**
5715     * Store the NAME record in the short format that is used for storing the print
5716     * area, repeat rows only and repeat columns only.
5717     *
5718     * @param integer $index  Sheet index
5719     * @param integer $type   Built-in name type
5720     * @param integer $rowmin Start row
5721     * @param integer $rowmax End row
5722     * @param integer $colmin Start colum
5723     * @param integer $colmax End column
5724     */
5725     function store_name_short($index,$type,$rowmin,$rowmax,$colmin,$colmax)
5726     {
5727         $record          = 0x0018;       // Record identifier
5728         $length          = 0x0024;       // Number of bytes to follow
5729     
5730         $grbit           = 0x0020;       // Option flags
5731         $chKey           = 0x00;         // Keyboard shortcut
5732         $cch             = 0x01;         // Length of text name
5733         $cce             = 0x0015;       // Length of text definition
5734         $ixals           = $index + 1;   // Sheet index
5735         $itab            = $ixals;       // Equal to ixals
5736         $cchCustMenu     = 0x00;         // Length of cust menu text
5737         $cchDescription  = 0x00;         // Length of description text
5738         $cchHelptopic    = 0x00;         // Length of help topic text
5739         $cchStatustext   = 0x00;         // Length of status bar text
5740         $rgch            = $type;        // Built-in name type
5741     
5742         $unknown03       = 0x3b;
5743         $unknown04       = 0xffff-$index;
5744         $unknown05       = 0x0000;
5745         $unknown06       = 0x0000;
5746         $unknown07       = 0x1087;
5747         $unknown08       = 0x8005;
5748     
5749         $header             = pack("vv", $record, $length);
5750         $data               = pack("v", $grbit);
5751         $data              .= pack("C", $chKey);
5752         $data              .= pack("C", $cch);
5753         $data              .= pack("v", $cce);
5754         $data              .= pack("v", $ixals);
5755         $data              .= pack("v", $itab);
5756         $data              .= pack("C", $cchCustMenu);
5757         $data              .= pack("C", $cchDescription);
5758         $data              .= pack("C", $cchHelptopic);
5759         $data              .= pack("C", $cchStatustext);
5760         $data              .= pack("C", $rgch);
5761         $data              .= pack("C", $unknown03);
5762         $data              .= pack("v", $unknown04);
5763         $data              .= pack("v", $unknown05);
5764         $data              .= pack("v", $unknown06);
5765         $data              .= pack("v", $unknown07);
5766         $data              .= pack("v", $unknown08);
5767         $data              .= pack("v", $index);
5768         $data              .= pack("v", $index);
5769         $data              .= pack("v", $rowmin);
5770         $data              .= pack("v", $rowmax);
5771         $data              .= pack("C", $colmin);
5772         $data              .= pack("C", $colmax);
5773         $this->_append($header.$data);
5774     }
5775     
5776     
5777     /**
5778     * Store the NAME record in the long format that is used for storing the repeat
5779     * rows and columns when both are specified. This share a lot of code with
5780     * _store_name_short() but we use a separate method to keep the code clean.
5781     * Code abstraction for reuse can be carried too far, and I should know. ;-)
5782     *
5783     * @param integer $index Sheet index
5784     * @param integer $type  Built-in name type
5785     * @param integer $rowmin Start row
5786     * @param integer $rowmax End row
5787     * @param integer $colmin Start colum
5788     * @param integer $colmax End column
5789     */
5790     function store_name_long($index,$type,$rowmin,$rowmax,$colmin,$colmax)
5791     {
5792         $record          = 0x0018;       // Record identifier
5793         $length          = 0x003d;       // Number of bytes to follow
5794         $grbit           = 0x0020;       // Option flags
5795         $chKey           = 0x00;         // Keyboard shortcut
5796         $cch             = 0x01;         // Length of text name
5797         $cce             = 0x002e;       // Length of text definition
5798         $ixals           = $index + 1;   // Sheet index
5799         $itab            = $ixals;       // Equal to ixals
5800         $cchCustMenu     = 0x00;         // Length of cust menu text
5801         $cchDescription  = 0x00;         // Length of description text
5802         $cchHelptopic    = 0x00;         // Length of help topic text
5803         $cchStatustext   = 0x00;         // Length of status bar text
5804         $rgch            = $type;        // Built-in name type
5805     
5806         $unknown01       = 0x29;
5807         $unknown02       = 0x002b;
5808         $unknown03       = 0x3b;
5809         $unknown04       = 0xffff-$index;
5810         $unknown05       = 0x0000;
5811         $unknown06       = 0x0000;
5812         $unknown07       = 0x1087;
5813         $unknown08       = 0x8008;
5814     
5815         $header             = pack("vv",  $record, $length);
5816         $data               = pack("v", $grbit);
5817         $data              .= pack("C", $chKey);
5818         $data              .= pack("C", $cch);
5819         $data              .= pack("v", $cce);
5820         $data              .= pack("v", $ixals);
5821         $data              .= pack("v", $itab);
5822         $data              .= pack("C", $cchCustMenu);
5823         $data              .= pack("C", $cchDescription);
5824         $data              .= pack("C", $cchHelptopic);
5825         $data              .= pack("C", $cchStatustext);
5826         $data              .= pack("C", $rgch);
5827         $data              .= pack("C", $unknown01);
5828         $data              .= pack("v", $unknown02);
5829         // Column definition
5830         $data              .= pack("C", $unknown03);
5831         $data              .= pack("v", $unknown04);
5832         $data              .= pack("v", $unknown05);
5833         $data              .= pack("v", $unknown06);
5834         $data              .= pack("v", $unknown07);
5835         $data              .= pack("v", $unknown08);
5836         $data              .= pack("v", $index);
5837         $data              .= pack("v", $index);
5838         $data              .= pack("v", 0x0000);
5839         $data              .= pack("v", 0x3fff);
5840         $data              .= pack("C", $colmin);
5841         $data              .= pack("C", $colmax);
5842         // Row definition
5843         $data              .= pack("C", $unknown03);
5844         $data              .= pack("v", $unknown04);
5845         $data              .= pack("v", $unknown05);
5846         $data              .= pack("v", $unknown06);
5847         $data              .= pack("v", $unknown07);
5848         $data              .= pack("v", $unknown08);
5849         $data              .= pack("v", $index);
5850         $data              .= pack("v", $index);
5851         $data              .= pack("v", $rowmin);
5852         $data              .= pack("v", $rowmax);
5853         $data              .= pack("C", 0x00);
5854         $data              .= pack("C", 0xff);
5855         // End of data
5856         $data              .= pack("C", 0x10);
5857         $this->_append($header.$data);
5858     }
5859     
5860     
5861     /**
5862     * Stores the PALETTE biff record.
5863     */
5864     function _store_palette()
5865     {
5866         $aref            = $this->palette;
5867     
5868         $record          = 0x0092;                 // Record identifier
5869         $length          = 2 + 4 * count($aref);   // Number of bytes to follow
5870         $ccv             =         count($aref);   // Number of RGB values to follow
5871         $data = '';                                // The RGB data
5872     
5873         // Pack the RGB data
5874         foreach($aref as $color)
5875         {
5876             foreach($color as $byte) {
5877                 $data .= pack("C",$byte);
5878             }
5879         }
5880     
5881         $header = pack("vvv",  $record, $length, $ccv);
5882         $this->_append($header.$data);
5883     }
5884 }
5885 ?>