Fixed broken excel reportsfor non-latin1 encoding.
[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_Excel_Writer:  A library for generating Excel Spreadsheets
18 *    Copyright (c) 2002-2003 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 SPREADSHEET_EXCEL_WRITER_ADD token identifier for character "+"
37 */
38 define('SPREADSHEET_EXCEL_WRITER_ADD', "+");
39
40 /**
41 * @const SPREADSHEET_EXCEL_WRITER_SUB token identifier for character "-"
42 */
43 define('SPREADSHEET_EXCEL_WRITER_SUB', "-");
44
45 /**
46 * @const SPREADSHEET_EXCEL_WRITER_MUL token identifier for character "*"
47 */
48 define('SPREADSHEET_EXCEL_WRITER_MUL', "*");
49
50 /**
51 * @const SPREADSHEET_EXCEL_WRITER_DIV token identifier for character "/"
52 */
53 define('SPREADSHEET_EXCEL_WRITER_DIV', "/");
54
55 /**
56 * @const SPREADSHEET_EXCEL_WRITER_OPEN token identifier for character "("
57 */
58 define('SPREADSHEET_EXCEL_WRITER_OPEN', "(");
59
60 /**
61 * @const SPREADSHEET_EXCEL_WRITER_CLOSE token identifier for character ")"
62 */
63 define('SPREADSHEET_EXCEL_WRITER_CLOSE', ")");
64
65 /**
66 * @const SPREADSHEET_EXCEL_WRITER_COMA token identifier for character ","
67 */
68 define('SPREADSHEET_EXCEL_WRITER_COMA', ",");
69
70 /**
71 * @const SPREADSHEET_EXCEL_WRITER_SEMICOLON token identifier for character ";"
72 */
73 define('SPREADSHEET_EXCEL_WRITER_SEMICOLON', ";");
74
75 /**
76 * @const SPREADSHEET_EXCEL_WRITER_GT token identifier for character ">"
77 */
78 define('SPREADSHEET_EXCEL_WRITER_GT', ">");
79
80 /**
81 * @const SPREADSHEET_EXCEL_WRITER_LT token identifier for character "<"
82 */
83 define('SPREADSHEET_EXCEL_WRITER_LT', "<");
84
85 /**
86 * @const SPREADSHEET_EXCEL_WRITER_LE token identifier for character "<="
87 */
88 define('SPREADSHEET_EXCEL_WRITER_LE', "<=");
89
90 /**
91 * @const SPREADSHEET_EXCEL_WRITER_GE token identifier for character ">="
92 */
93 define('SPREADSHEET_EXCEL_WRITER_GE', ">=");
94
95 /**
96 * @const SPREADSHEET_EXCEL_WRITER_EQ token identifier for character "="
97 */
98 define('SPREADSHEET_EXCEL_WRITER_EQ', "=");
99
100 /**
101 * @const SPREADSHEET_EXCEL_WRITER_NE token identifier for character "<>"
102 */
103 define('SPREADSHEET_EXCEL_WRITER_NE', "<>");
104
105 $encoding_string='';
106 /**
107 * Class for creating OLE streams for Excel Spreadsheets
108 *
109 * @author Xavier Noguer <xnoguer@rezebra.com>
110 * @package Spreadsheet_WriteExcel
111 */
112 class OLEwriter
113 {
114     /**
115     * Filename for the OLE stream
116     * @var string
117     * @see _initialize()
118     */
119     var $_OLEfilename;
120
121     /**
122     * Filehandle for the OLE stream
123     * @var resource
124     */
125     var $_filehandle;
126
127     /**
128     * Name of the temporal file in case OLE stream goes to stdout
129     * @var string
130     */
131     var $_tmp_filename;
132
133     /**
134     * Variable for preventing closing two times
135     * @var integer
136     */
137     var $_fileclosed;
138
139     /**
140     * Size of the data to be written to the OLE stream
141     * @var integer
142     */
143     var $_biffsize;
144
145     /**
146     * Real data size to be written to the OLE stream
147     * @var integer
148     */
149     var $_booksize;
150
151     /**
152     * Number of big blocks in the OLE stream
153     * @var integer
154     */
155     var $_big_blocks;
156
157     /**
158     * Number of list blocks in the OLE stream
159     * @var integer
160     */
161     var $_list_blocks;
162
163     /**
164     * Number of big blocks in the OLE stream
165     * @var integer
166     */
167     var $_root_start;
168
169     /**
170     * Constructor for the OLEwriter class
171     *
172     * @param string $OLEfilename the name of the file for the OLE stream
173     */
174     function OLEwriter($OLEfilename)
175     {
176         $this->_OLEfilename  = $OLEfilename;
177         $this->_filehandle   = '';
178         $this->_tmp_filename = '';
179         $this->_fileclosed   = 0;
180         $this->_biff_only    = 0;
181         //$this->_size_allowed = 0;
182         $this->_biffsize     = 0;
183         $this->_booksize     = 0;
184         $this->_big_blocks   = 0;
185         $this->_list_blocks  = 0;
186         $this->_root_start   = 0;
187         //$this->_block_count  = 4;
188         $this->_initialize();
189     }
190
191     /**
192     * Check for a valid filename and store the filehandle.
193     * Filehandle "-" writes to STDOUT
194     *
195     * @access private
196     */
197     function _initialize()
198     {
199         $OLEfile = $this->_OLEfilename;
200
201         if (($OLEfile == '-') or ($OLEfile == '')) {
202             $this->_tmp_filename = tempnam("/tmp", "OLEwriter");
203             $fh = fopen($this->_tmp_filename, "wb");
204             if ($fh == false) {
205                 die("Can't create temporary file.");
206             }
207         } else {
208             // Create a new file, open for writing (in binmode)
209             $fh = fopen($OLEfile, "wb");
210             if ($fh == false) {
211                 die("Can't open $OLEfile. It may be in use or protected.");
212             }
213         }
214
215         // Store filehandle
216         $this->_filehandle = $fh;
217     }
218
219
220     /**
221     * Set the size of the data to be written to the OLE stream.
222     * The maximun size comes from this:
223     *   $big_blocks = (109 depot block x (128 -1 marker word)
224     *                 - (1 x end words)) = 13842
225     *   $maxsize    = $big_blocks * 512 bytes = 7087104
226     *
227     * @access public
228     * @see Spreadsheet_Excel_Writer_Workbook::store_OLE_file()
229     * @param integer $biffsize The size of the data to be written to the OLE stream
230     * @return integer 1 for success
231     */
232     function setSize($biffsize)
233     {
234         $maxsize = 7087104; // TODO: extend max size
235
236         if ($biffsize > $maxsize) {
237             die("Maximum file size, $maxsize, exceeded.");
238         }
239
240         $this->_biffsize = $biffsize;
241         // Set the min file size to 4k to avoid having to use small blocks
242         if ($biffsize > 4096) {
243             $this->_booksize = $biffsize;
244         } else {
245             $this->_booksize = 4096;
246         }
247         //$this->_size_allowed = 1;
248         return(1);
249     }
250
251
252     /**
253     * Calculate various sizes needed for the OLE stream
254     *
255     * @access private
256     */
257     function _calculateSizes()
258     {
259         $datasize = $this->_booksize;
260         if ($datasize % 512 == 0) {
261             $this->_big_blocks = $datasize/512;
262         } else {
263             $this->_big_blocks = floor($datasize/512) + 1;
264         }
265         // There are 127 list blocks and 1 marker blocks for each big block
266         // depot + 1 end of chain block
267         $this->_list_blocks = floor(($this->_big_blocks)/127) + 1;
268         $this->_root_start  = $this->_big_blocks;
269     }
270
271     /**
272     * Write root entry, big block list and close the filehandle.
273     * This routine is used to explicitly close the open filehandle without
274     * having to wait for DESTROY.
275     *
276     * @access public
277     * @see Spreadsheet_Excel_Writer_Workbook::store_OLE_file()
278     */
279     function close()
280     {
281         //return if not $this->{_size_allowed};
282         $this->_writePadding();
283         $this->_writePropertyStorage();
284         $this->_writeBigBlockDepot();
285         // Close the filehandle
286         fclose($this->_filehandle);
287         if (($this->_OLEfilename == '-') or ($this->_OLEfilename == '')) {
288             $fh = fopen($this->_tmp_filename, "rb");
289             if ($fh == false) {
290                 die("Can't read temporary file.");
291             }
292             fpassthru($fh);
293             @unlink($this->_tmp_filename);
294         }
295         $this->_fileclosed = 1;
296     }
297
298
299     /**
300     * Write BIFF data to OLE file.
301     *
302     * @param string $data string of bytes to be written
303     */
304     function write($data)
305     {
306         fwrite($this->_filehandle, $data, strlen($data));
307     }
308
309
310     /**
311     * Write OLE header block.
312     */
313     function writeHeader()
314     {
315         $this->_calculateSizes();
316         $root_start      = $this->_root_start;
317         $num_lists       = $this->_list_blocks;
318         $id              = pack("nnnn", 0xD0CF, 0x11E0, 0xA1B1, 0x1AE1);
319         $unknown1        = pack("VVVV", 0x00, 0x00, 0x00, 0x00);
320         $unknown2        = pack("vv",   0x3E, 0x03);
321         $unknown3        = pack("v",    -2);
322         $unknown4        = pack("v",    0x09);
323         $unknown5        = pack("VVV",  0x06, 0x00, 0x00);
324         $num_bbd_blocks  = pack("V",    $num_lists);
325         $root_startblock = pack("V",    $root_start);
326         $unknown6        = pack("VV",   0x00, 0x1000);
327         $sbd_startblock  = pack("V",    -2);
328         $unknown7        = pack("VVV",  0x00, -2 ,0x00);
329         $unused          = pack("V",    -1);
330
331         fwrite($this->_filehandle, $id);
332         fwrite($this->_filehandle, $unknown1);
333         fwrite($this->_filehandle, $unknown2);
334         fwrite($this->_filehandle, $unknown3);
335         fwrite($this->_filehandle, $unknown4);
336         fwrite($this->_filehandle, $unknown5);
337         fwrite($this->_filehandle, $num_bbd_blocks);
338         fwrite($this->_filehandle, $root_startblock);
339         fwrite($this->_filehandle, $unknown6);
340         fwrite($this->_filehandle, $sbd_startblock);
341         fwrite($this->_filehandle, $unknown7);
342
343         for ($i=1; $i <= $num_lists; $i++) {
344             $root_start++;
345             fwrite($this->_filehandle, pack("V",$root_start));
346         }
347         for ($i = $num_lists; $i <=108; $i++) {
348             fwrite($this->_filehandle, $unused);
349         }
350     }
351
352
353     /**
354     * Write big block depot.
355     *
356     * @access private
357     */
358     function _writeBigBlockDepot()
359     {
360         $num_blocks   = $this->_big_blocks;
361         $num_lists    = $this->_list_blocks;
362         $total_blocks = $num_lists *128;
363         $used_blocks  = $num_blocks + $num_lists +2;
364
365         $marker       = pack("V", -3);
366         $end_of_chain = pack("V", -2);
367         $unused       = pack("V", -1);
368
369         for ($i = 1; $i < $num_blocks; $i++) {
370             fwrite($this->_filehandle, pack("V",$i));
371         }
372
373         fwrite($this->_filehandle, $end_of_chain);
374         fwrite($this->_filehandle, $end_of_chain);
375         for ($i = 0; $i < $num_lists; $i++) {
376             fwrite($this->_filehandle, $marker);
377         }
378
379         for ($i = $used_blocks; $i <= $total_blocks; $i++) {
380             fwrite($this->_filehandle, $unused);
381         }
382     }
383
384     /**
385     * Write property storage. TODO: add summary sheets
386     *
387     * @access private
388     */
389     function _writePropertyStorage()
390     {
391         //$rootsize = -2;
392         /***************  name         type   dir start size */
393         $this->_writePps("Root Entry", 0x05,   1,   -2, 0x00);
394         $this->_writePps("Book",       0x02,  -1, 0x00, $this->_booksize);
395         $this->_writePps('',           0x00,  -1, 0x00, 0x0000);
396         $this->_writePps('',           0x00,  -1, 0x00, 0x0000);
397     }
398
399 /**
400 * Write property sheet in property storage
401 *
402 * @param string  $name  name of the property storage.
403 * @param integer $type  type of the property storage.
404 * @param integer $dir   dir of the property storage.
405 * @param integer $start start of the property storage.
406 * @param integer $size  size of the property storage.
407 * @access private
408 */
409     function _writePps($name, $type, $dir, $start, $size)
410     {
411         $length  = 0;
412         $rawname = '';
413
414         if ($name != '') {
415             $name = $name . "\0";
416             $name_length = strlen($name);
417             for ($i = 0; $i < $name_length; $i++) {
418                 // Simulate a Unicode string
419                 $rawname .= pack("H*",dechex(ord($name{$i}))).pack("C",0);
420             }
421             $length = strlen($name) * 2;
422         }
423
424         $zero            = pack("C",  0);
425         $pps_sizeofname  = pack("v",  $length);    // 0x40
426         $pps_type        = pack("v",  $type);      // 0x42
427         $pps_prev        = pack("V",  -1);         // 0x44
428         $pps_next        = pack("V",  -1);         // 0x48
429         $pps_dir         = pack("V",  $dir);       // 0x4c
430
431         $unknown1        = pack("V",  0);
432
433         $pps_ts1s        = pack("V",  0);          // 0x64
434         $pps_ts1d        = pack("V",  0);          // 0x68
435         $pps_ts2s        = pack("V",  0);          // 0x6c
436         $pps_ts2d        = pack("V",  0);          // 0x70
437         $pps_sb          = pack("V",  $start);     // 0x74
438         $pps_size        = pack("V",  $size);      // 0x78
439
440
441         fwrite($this->_filehandle, $rawname);
442         for ($i = 0; $i < (64 -$length); $i++) {
443             fwrite($this->_filehandle, $zero);
444         }
445         fwrite($this->_filehandle, $pps_sizeofname);
446         fwrite($this->_filehandle, $pps_type);
447         fwrite($this->_filehandle, $pps_prev);
448         fwrite($this->_filehandle, $pps_next);
449         fwrite($this->_filehandle, $pps_dir);
450         for ($i = 0; $i < 5; $i++) {
451             fwrite($this->_filehandle, $unknown1);
452         }
453         fwrite($this->_filehandle, $pps_ts1s);
454         fwrite($this->_filehandle, $pps_ts1d);
455         fwrite($this->_filehandle, $pps_ts2d);
456         fwrite($this->_filehandle, $pps_ts2d);
457         fwrite($this->_filehandle, $pps_sb);
458         fwrite($this->_filehandle, $pps_size);
459         fwrite($this->_filehandle, $unknown1);
460     }
461
462     /**
463     * Pad the end of the file
464     *
465     * @access private
466     */
467     function _writePadding()
468     {
469         $biffsize = $this->_biffsize;
470         if ($biffsize < 4096) {
471             $min_size = 4096;
472         } else {
473             $min_size = 512;
474         }
475         if ($biffsize % $min_size != 0) {
476             $padding  = $min_size - ($biffsize % $min_size);
477             for ($i = 0; $i < $padding; $i++) {
478                 fwrite($this->_filehandle, "\0");
479             }
480         }
481     }
482 }
483
484 /**
485 * Class for writing Excel BIFF records.
486 *
487 * From "MICROSOFT EXCEL BINARY FILE FORMAT" by Mark O'Brien (Microsoft Corporation):
488 *
489 * BIFF (BInary File Format) is the file format in which Excel documents are
490 * saved on disk.  A BIFF file is a complete description of an Excel document.
491 * BIFF files consist of sequences of variable-length records. There are many
492 * different types of BIFF records.  For example, one record type describes a
493 * formula entered into a cell; one describes the size and location of a
494 * window into a document; another describes a picture format.
495 *
496 * @author   Xavier Noguer <xnoguer@php.net>
497 * @category FileFormats
498 * @package  Spreadsheet_Excel_Writer
499 */
500
501 class Spreadsheet_Excel_Writer_BIFFwriter
502 {
503     /**
504     * The BIFF/Excel version (5).
505     * @var integer
506     */
507     var $_BIFF_version = 0x0500;
508
509     /**
510     * The byte order of this architecture. 0 => little endian, 1 => big endian
511     * @var integer
512     */
513     var $_byte_order;
514
515     /**
516     * The string containing the data of the BIFF stream
517     * @var string
518     */
519     var $_data;
520
521     /**
522     * The size of the data in bytes. Should be the same as strlen($this->_data)
523     * @var integer
524     */
525     var $_datasize;
526
527     /**
528     * The maximun length for a BIFF record. See _addContinue()
529     * @var integer
530     * @see _addContinue()
531     */
532     var $_limit;
533
534     /**
535     * Constructor
536     *
537     * @access public
538     */
539     function Spreadsheet_Excel_Writer_BIFFwriter()
540     {
541         $this->_byte_order = '';
542         $this->_data       = '';
543         $this->_datasize   = 0;
544         $this->_limit      = 2080;
545         // Set the byte order
546         $this->_setByteOrder();
547     }
548
549     /**
550     * Determine the byte order and store it as class data to avoid
551     * recalculating it for each call to new().
552     *
553     * @access private
554     */
555     function _setByteOrder()
556     {
557         // Check if "pack" gives the required IEEE 64bit float
558         $teststr = pack("d", 1.2345);
559         $number  = pack("C8", 0x8D, 0x97, 0x6E, 0x12, 0x83, 0xC0, 0xF3, 0x3F);
560         if ($number == $teststr) {
561             $byte_order = 0;    // Little Endian
562         } elseif ($number == strrev($teststr)){
563             $byte_order = 1;    // Big Endian
564         } else {
565             // Give up. I'll fix this in a later version.
566             die("Required floating point format ".
567                                      "not supported on this platform.");
568         }
569         $this->_byte_order = $byte_order;
570     }
571
572     /**
573     * General storage function
574     *
575     * @param string $data binary data to prepend
576     * @access private
577     */
578     function _prepend($data)
579     {
580         if (strlen($data) > $this->_limit) {
581             $data = $this->_addContinue($data);
582         }
583         $this->_data      = $data.$this->_data;
584         $this->_datasize += strlen($data);
585     }
586
587     /**
588     * General storage function
589     *
590     * @param string $data binary data to append
591     * @access private
592     */
593     function _append($data)
594     {
595         if (strlen($data) > $this->_limit) {
596             $data = $this->_addContinue($data);
597         }
598         $this->_data      = $this->_data.$data;
599         $this->_datasize += strlen($data);
600     }
601
602     /**
603     * Writes Excel BOF record to indicate the beginning of a stream or
604     * sub-stream in the BIFF file.
605     *
606     * @param  integer $type Type of BIFF file to write: 0x0005 Workbook,
607     *                       0x0010 Worksheet.
608     * @access private
609     */
610     function _storeBof($type)
611     {
612         $record  = 0x0809;        // Record identifier
613
614         // According to the SDK $build and $year should be set to zero.
615         // However, this throws a warning in Excel 5. So, use magic numbers.
616         if ($this->_BIFF_version == 0x0500) {
617             $length  = 0x0008;
618             $unknown = '';
619             $build   = 0x096C;
620             $year    = 0x07C9;
621         } elseif ($this->_BIFF_version == 0x0600) {
622             $length  = 0x0010;
623             $unknown = pack("VV", 0x00000041, 0x00000006); //unknown last 8 bytes for BIFF8
624             $build   = 0x0DBB;
625             $year    = 0x07CC;
626         }
627         $version = $this->_BIFF_version;
628
629         $header  = pack("vv",   $record, $length);
630         $data    = pack("vvvv", $version, $type, $build, $year);
631         $this->_prepend($header . $data . $unknown);
632     }
633
634     /**
635     * Writes Excel EOF record to indicate the end of a BIFF stream.
636     *
637     * @access private
638     */
639     function _storeEof()
640     {
641         $record    = 0x000A;   // Record identifier
642         $length    = 0x0000;   // Number of bytes to follow
643         $header    = pack("vv", $record, $length);
644         $this->_append($header);
645     }
646
647     /**
648     * Excel limits the size of BIFF records. In Excel 5 the limit is 2084 bytes. In
649     * Excel 97 the limit is 8228 bytes. Records that are longer than these limits
650     * must be split up into CONTINUE blocks.
651     *
652     * This function takes a long BIFF record and inserts CONTINUE records as
653     * necessary.
654     *
655     * @param  string  $data The original binary data to be written
656     * @return string        A very convenient string of continue blocks
657     * @access private
658     */
659     function _addContinue($data)
660     {
661         $limit  = $this->_limit;
662         $record = 0x003C;         // Record identifier
663
664         // The first 2080/8224 bytes remain intact. However, we have to change
665         // the length field of the record.
666         $tmp = substr($data, 0, 2).pack("v", $limit-4).substr($data, 4, $limit - 4);
667
668         $header = pack("vv", $record, $limit);  // Headers for continue records
669
670         // Retrieve chunks of 2080/8224 bytes +4 for the header.
671         $data_length = strlen($data);
672         for ($i = $limit; $i <  ($data_length - $limit); $i += $limit) {
673             $tmp .= $header;
674             $tmp .= substr($data, $i, $limit);
675         }
676
677         // Retrieve the last chunk of data
678         $header  = pack("vv", $record, strlen($data) - $i);
679         $tmp    .= $header;
680         $tmp    .= substr($data, $i, strlen($data) - $i);
681
682         return $tmp;
683     }
684 }
685
686 /*
687 FIXME: change prefixes
688 */
689 define("OP_BETWEEN",    0x00);
690 define("OP_NOTBETWEEN", 0x01);
691 define("OP_EQUAL",      0x02);
692 define("OP_NOTEQUAL",   0x03);
693 define("OP_GT",         0x04);
694 define("OP_LT",         0x05);
695 define("OP_GTE",        0x06);
696 define("OP_LTE",        0x07);
697
698 /**
699 * Baseclass for generating Excel DV records (validations)
700 *
701 * @author   Herman Kuiper
702 * @category FileFormats
703 * @package  Spreadsheet_Excel_Writer
704 */
705 class Spreadsheet_Excel_Writer_Validator
706 {
707    var $_type;
708    var $_style;
709    var $_fixedList;
710    var $_blank;
711    var $_incell;
712    var $_showprompt;
713    var $_showerror;
714    var $_title_prompt;
715    var $_descr_prompt;
716    var $_title_error;
717    var $_descr_error;
718    var $_operator;
719    var $_formula1;
720    var $_formula2;
721     /**
722     * The parser from the workbook. Used to parse validation formulas also
723     * @var Spreadsheet_Excel_Writer_Parser
724     */
725     var $_parser;
726
727     function Spreadsheet_Excel_Writer_Validator(&$parser)
728     {
729         $this->_parser       = $parser;
730         $this->_type         = 0x01; // FIXME: add method for setting datatype
731         $this->_style        = 0x00;
732         $this->_fixedList    = false;
733         $this->_blank        = false;
734         $this->_incell       = false;
735         $this->_showprompt   = false;
736         $this->_showerror    = true;
737         $this->_title_prompt = "\x00";
738         $this->_descr_prompt = "\x00";
739         $this->_title_error  = "\x00";
740         $this->_descr_error  = "\x00";
741         $this->_operator     = 0x00; // default is equal
742         $this->_formula1    = '';
743         $this->_formula2    = '';
744     }
745
746    function setPrompt($promptTitle = "\x00", $promptDescription = "\x00", $showPrompt = true)
747    {
748       $this->_showprompt = $showPrompt;
749       $this->_title_prompt = $promptTitle;
750       $this->_descr_prompt = $promptDescription;
751    }
752
753    function setError($errorTitle = "\x00", $errorDescription = "\x00", $showError = true)
754    {
755       $this->_showerror = $showError;
756       $this->_title_error = $errorTitle;
757       $this->_descr_error = $errorDescription;
758    }
759
760    function allowBlank()
761    {
762       $this->_blank = true;
763    }
764
765    function onInvalidStop()
766    {
767       $this->_style = 0x00;
768    }
769
770     function onInvalidWarn()
771     {
772         $this->_style = 0x01;
773     }
774
775     function onInvalidInfo()
776     {
777         $this->_style = 0x02;
778     }
779
780     function setFormula1($formula)
781     {
782         // Parse the formula using the parser in Parser.php
783         $this->_parser->parse($formula);
784
785         $this->_formula1 = $this->_parser->toReversePolish();
786         return true;
787     }
788
789     function setFormula2($formula)
790     {
791         // Parse the formula using the parser in Parser.php
792         $this->_parser->parse($formula);
793
794         $this->_formula2 = $this->_parser->toReversePolish();
795         return true;
796     }
797
798     function _getOptions()
799     {
800         $options = $this->_type;
801         $options |= $this->_style << 3;
802         if ($this->_fixedList) {
803             $options |= 0x80;
804         }
805         if ($this->_blank) {
806             $options |= 0x100;
807         }
808         if (!$this->_incell) {
809             $options |= 0x200;
810         }
811         if ($this->_showprompt) {
812             $options |= 0x40000;
813         }
814         if ($this->_showerror) {
815             $options |= 0x80000;
816         }
817       $options |= $this->_operator << 20;
818
819       return $options;
820    }
821
822    function _getData()
823    {
824       $title_prompt_len = strlen($this->_title_prompt);
825       $descr_prompt_len = strlen($this->_descr_prompt);
826       $title_error_len = strlen($this->_title_error);
827       $descr_error_len = strlen($this->_descr_error);
828
829       $formula1_size = strlen($this->_formula1);
830       $formula2_size = strlen($this->_formula2);
831
832       $data  = pack("V", $this->_getOptions());
833       $data .= pack("vC", $title_prompt_len, 0x00) . $this->_title_prompt;
834       $data .= pack("vC", $title_error_len, 0x00) . $this->_title_error;
835       $data .= pack("vC", $descr_prompt_len, 0x00) . $this->_descr_prompt;
836       $data .= pack("vC", $descr_error_len, 0x00) . $this->_descr_error;
837
838       $data .= pack("vv", $formula1_size, 0x0000) . $this->_formula1;
839       $data .= pack("vv", $formula2_size, 0x0000) . $this->_formula2;
840
841       return $data;
842    }
843 }
844
845 /**
846 * Class for generating Excel XF records (formats)
847 *
848 * @author   Xavier Noguer <xnoguer@rezebra.com>
849 * @category FileFormats
850 * @package  Spreadsheet_Excel_Writer
851 */
852
853 class Spreadsheet_Excel_Writer_Format
854 {
855     /**
856     * The index given by the workbook when creating a new format.
857     * @var integer
858     */
859     var $_xf_index;
860
861     /**
862     * Index to the FONT record.
863     * @var integer
864     */
865     var $font_index;
866
867     /**
868     * The font name (ASCII).
869     * @var string
870     */
871     var $_font_name;
872
873     /**
874     * Height of font (1/20 of a point)
875     * @var integer
876     */
877     var $_size;
878
879     /**
880     * Bold style
881     * @var integer
882     */
883     var $_bold;
884
885     /**
886     * Bit specifiying if the font is italic.
887     * @var integer
888     */
889     var $_italic;
890
891     /**
892     * Index to the cell's color
893     * @var integer
894     */
895     var $_color;
896
897     /**
898     * The text underline property
899     * @var integer
900     */
901     var $_underline;
902
903     /**
904     * Bit specifiying if the font has strikeout.
905     * @var integer
906     */
907     var $_font_strikeout;
908
909     /**
910     * Bit specifiying if the font has outline.
911     * @var integer
912     */
913     var $_font_outline;
914
915     /**
916     * Bit specifiying if the font has shadow.
917     * @var integer
918     */
919     var $_font_shadow;
920
921     /**
922     * 2 bytes specifiying the script type for the font.
923     * @var integer
924     */
925     var $_font_script;
926
927     /**
928     * Byte specifiying the font family.
929     * @var integer
930     */
931     var $_font_family;
932
933     /**
934     * Byte specifiying the font charset.
935     * @var integer
936     */
937     var $_font_charset;
938
939     /**
940     * An index (2 bytes) to a FORMAT record (number format).
941     * @var integer
942     */
943     var $_num_format;
944
945     /**
946     * Bit specifying if formulas are hidden.
947     * @var integer
948     */
949     var $_hidden;
950
951     /**
952     * Bit specifying if the cell is locked.
953     * @var integer
954     */
955     var $_locked;
956
957     /**
958     * The three bits specifying the text horizontal alignment.
959     * @var integer
960     */
961     var $_text_h_align;
962
963     /**
964     * Bit specifying if the text is wrapped at the right border.
965     * @var integer
966     */
967     var $_text_wrap;
968
969     /**
970     * The three bits specifying the text vertical alignment.
971     * @var integer
972     */
973     var $_text_v_align;
974
975     /**
976     * 1 bit, apparently not used.
977     * @var integer
978     */
979     var $_text_justlast;
980
981     /**
982     * The two bits specifying the text rotation.
983     * @var integer
984     */
985     var $_rotation;
986
987     /**
988     * The cell's foreground color.
989     * @var integer
990     */
991     var $_fg_color;
992
993     /**
994     * The cell's background color.
995     * @var integer
996     */
997     var $_bg_color;
998
999     /**
1000     * The cell's background fill pattern.
1001     * @var integer
1002     */
1003     var $_pattern;
1004
1005     /**
1006     * Style of the bottom border of the cell
1007     * @var integer
1008     */
1009     var $_bottom;
1010
1011     /**
1012     * Color of the bottom border of the cell.
1013     * @var integer
1014     */
1015     var $_bottom_color;
1016
1017     /**
1018     * Style of the top border of the cell
1019     * @var integer
1020     */
1021     var $_top;
1022
1023     /**
1024     * Color of the top border of the cell.
1025     * @var integer
1026     */
1027     var $_top_color;
1028
1029     /**
1030     * Style of the left border of the cell
1031     * @var integer
1032     */
1033     var $_left;
1034
1035     /**
1036     * Color of the left border of the cell.
1037     * @var integer
1038     */
1039     var $_left_color;
1040
1041     /**
1042     * Style of the right border of the cell
1043     * @var integer
1044     */
1045     var $_right;
1046
1047     /**
1048     * Color of the right border of the cell.
1049     * @var integer
1050     */
1051     var $_right_color;
1052
1053     /**
1054     * Constructor
1055     *
1056     * @access private
1057     * @param integer $index the XF index for the format.
1058     * @param array   $properties array with properties to be set on initialization.
1059     */
1060     function Spreadsheet_Excel_Writer_Format($BIFF_version, $index = 0, $properties =  array())
1061     {
1062         $this->_xf_index       = $index;
1063         $this->_BIFF_version   = $BIFF_version;
1064         $this->font_index      = 0;
1065         $this->_font_name      = 'Arial';
1066         $this->_size           = 10;
1067         $this->_bold           = 0x0190;
1068         $this->_italic         = 0;
1069         $this->_color          = 0x7FFF;
1070         $this->_underline      = 0;
1071         $this->_font_strikeout = 0;
1072         $this->_font_outline   = 0;
1073         $this->_font_shadow    = 0;
1074         $this->_font_script    = 0;
1075         $this->_font_family    = 0;
1076         $this->_font_charset   = 0;
1077
1078         $this->_num_format     = 0;
1079
1080         $this->_hidden         = 0;
1081         $this->_locked         = 0;
1082
1083         $this->_text_h_align   = 0;
1084         $this->_text_wrap      = 0;
1085         $this->_text_v_align   = 2;
1086         $this->_text_justlast  = 0;
1087         $this->_rotation       = 0;
1088
1089         $this->_fg_color       = 0x40;
1090         $this->_bg_color       = 0x41;
1091
1092         $this->_pattern        = 0;
1093
1094         $this->_bottom         = 0;
1095         $this->_top            = 0;
1096         $this->_left           = 0;
1097         $this->_right          = 0;
1098         $this->_diag           = 0;
1099
1100         $this->_bottom_color   = 0x40;
1101         $this->_top_color      = 0x40;
1102         $this->_left_color     = 0x40;
1103         $this->_right_color    = 0x40;
1104         $this->_diag_color     = 0x40;
1105
1106         // Set properties passed to Spreadsheet_Excel_Writer_Workbook::addFormat()
1107         foreach ($properties as $property => $value)
1108         {
1109             if (method_exists($this, 'set'.ucwords($property))) {
1110                 $method_name = 'set'.ucwords($property);
1111                 $this->$method_name($value);
1112             }
1113         }
1114     }
1115
1116
1117     /**
1118     * Generate an Excel BIFF XF record (style or cell).
1119     *
1120     * @param string $style The type of the XF record ('style' or 'cell').
1121     * @return string The XF record
1122     */
1123     function getXf($style)
1124     {
1125         // Set the type of the XF record and some of the attributes.
1126         if ($style == 'style') {
1127             $style = 0xFFF5;
1128         } else {
1129             $style   = $this->_locked;
1130             $style  |= $this->_hidden << 1;
1131         }
1132
1133         // Flags to indicate if attributes have been set.
1134         $atr_num     = ($this->_num_format != 0)?1:0;
1135         $atr_fnt     = ($this->font_index != 0)?1:0;
1136         $atr_alc     = ($this->_text_wrap)?1:0;
1137         $atr_bdr     = ($this->_bottom   ||
1138                         $this->_top      ||
1139                         $this->_left     ||
1140                         $this->_right)?1:0;
1141         $atr_pat     = (($this->_fg_color != 0x40) ||
1142                         ($this->_bg_color != 0x41) ||
1143                         $this->_pattern)?1:0;
1144         $atr_prot    = $this->_locked | $this->_hidden;
1145
1146         // Zero the default border colour if the border has not been set.
1147         if ($this->_bottom == 0) {
1148             $this->_bottom_color = 0;
1149         }
1150         if ($this->_top  == 0) {
1151             $this->_top_color = 0;
1152         }
1153         if ($this->_right == 0) {
1154             $this->_right_color = 0;
1155         }
1156         if ($this->_left == 0) {
1157             $this->_left_color = 0;
1158         }
1159         if ($this->_diag == 0) {
1160             $this->_diag_color = 0;
1161         }
1162
1163         $record         = 0x00E0;              // Record identifier
1164         if ($this->_BIFF_version == 0x0500) {
1165             $length         = 0x0010;              // Number of bytes to follow
1166         }
1167         if ($this->_BIFF_version == 0x0600) {
1168             $length         = 0x0014;
1169         }
1170
1171         $ifnt           = $this->font_index;   // Index to FONT record
1172         $ifmt           = $this->_num_format;  // Index to FORMAT record
1173         if ($this->_BIFF_version == 0x0500) {
1174             $align          = $this->_text_h_align;       // Alignment
1175             $align         |= $this->_text_wrap     << 3;
1176             $align         |= $this->_text_v_align  << 4;
1177             $align         |= $this->_text_justlast << 7;
1178             $align         |= $this->_rotation      << 8;
1179             $align         |= $atr_num                << 10;
1180             $align         |= $atr_fnt                << 11;
1181             $align         |= $atr_alc                << 12;
1182             $align         |= $atr_bdr                << 13;
1183             $align         |= $atr_pat                << 14;
1184             $align         |= $atr_prot               << 15;
1185
1186             $icv            = $this->_fg_color;       // fg and bg pattern colors
1187             $icv           |= $this->_bg_color      << 7;
1188
1189             $fill           = $this->_pattern;        // Fill and border line style
1190             $fill          |= $this->_bottom        << 6;
1191             $fill          |= $this->_bottom_color  << 9;
1192
1193             $border1        = $this->_top;            // Border line style and color
1194             $border1       |= $this->_left          << 3;
1195             $border1       |= $this->_right         << 6;
1196             $border1       |= $this->_top_color     << 9;
1197
1198             $border2        = $this->_left_color;     // Border color
1199             $border2       |= $this->_right_color   << 7;
1200
1201             $header      = pack("vv",       $record, $length);
1202             $data        = pack("vvvvvvvv", $ifnt, $ifmt, $style, $align,
1203                                             $icv, $fill,
1204                                             $border1, $border2);
1205         } elseif ($this->_BIFF_version == 0x0600) {
1206             $align          = $this->_text_h_align;       // Alignment
1207             $align         |= $this->_text_wrap     << 3;
1208             $align         |= $this->_text_v_align  << 4;
1209             $align         |= $this->_text_justlast << 7;
1210
1211             $used_attrib    = $atr_num              << 2;
1212             $used_attrib   |= $atr_fnt              << 3;
1213             $used_attrib   |= $atr_alc              << 4;
1214             $used_attrib   |= $atr_bdr              << 5;
1215             $used_attrib   |= $atr_pat              << 6;
1216             $used_attrib   |= $atr_prot             << 7;
1217
1218             $icv            = $this->_fg_color;      // fg and bg pattern colors
1219             $icv           |= $this->_bg_color      << 7;
1220
1221             $border1        = $this->_left;          // Border line style and color
1222             $border1       |= $this->_right         << 4;
1223             $border1       |= $this->_top           << 8;
1224             $border1       |= $this->_bottom        << 12;
1225             $border1       |= $this->_left_color    << 16;
1226             $border1       |= $this->_right_color   << 23;
1227             $diag_tl_to_rb = 0; // FIXME: add method
1228             $diag_tr_to_lb = 0; // FIXME: add method
1229             $border1       |= $diag_tl_to_rb        << 30;
1230             $border1       |= $diag_tr_to_lb        << 31;
1231
1232             $border2        = $this->_top_color;    // Border color
1233             $border2       |= $this->_bottom_color   << 7;
1234             $border2       |= $this->_diag_color     << 14;
1235             $border2       |= $this->_diag           << 21;
1236             $border2       |= $this->_pattern        << 26;
1237
1238             $header      = pack("vv",       $record, $length);
1239
1240             $rotation      = 0x00;
1241             $biff8_options = 0x00;
1242             $data  = pack("vvvC", $ifnt, $ifmt, $style, $align);
1243             $data .= pack("CCC", $rotation, $biff8_options, $used_attrib);
1244             $data .= pack("VVv", $border1, $border2, $icv);
1245         }
1246
1247         return($header . $data);
1248     }
1249
1250     /**
1251     * Generate an Excel BIFF FONT record.
1252     *
1253     * @return string The FONT record
1254     */
1255     function getFont()
1256     {
1257         $dyHeight   = $this->_size * 20;    // Height of font (1/20 of a point)
1258         $icv        = $this->_color;        // Index to color palette
1259         $bls        = $this->_bold;         // Bold style
1260         $sss        = $this->_font_script;  // Superscript/subscript
1261         $uls        = $this->_underline;    // Underline
1262         $bFamily    = $this->_font_family;  // Font family
1263         $bCharSet   = $this->_font_charset; // Character set
1264         $encoding   = 0;                    // TODO: Unicode support
1265
1266         $cch        = strlen($this->_font_name); // Length of font name
1267         $record     = 0x31;                      // Record identifier
1268         if ($this->_BIFF_version == 0x0500) {
1269             $length     = 0x0F + $cch;            // Record length
1270         } elseif ($this->_BIFF_version == 0x0600) {
1271             $length     = 0x10 + $cch;
1272         }
1273         $reserved   = 0x00;                // Reserved
1274         $grbit      = 0x00;                // Font attributes
1275         if ($this->_italic) {
1276             $grbit     |= 0x02;
1277         }
1278         if ($this->_font_strikeout) {
1279             $grbit     |= 0x08;
1280         }
1281         if ($this->_font_outline) {
1282             $grbit     |= 0x10;
1283         }
1284         if ($this->_font_shadow) {
1285             $grbit     |= 0x20;
1286         }
1287
1288         $header  = pack("vv",         $record, $length);
1289         if ($this->_BIFF_version == 0x0500) {
1290             $data    = pack("vvvvvCCCCC", $dyHeight, $grbit, $icv, $bls,
1291                                           $sss, $uls, $bFamily,
1292                                           $bCharSet, $reserved, $cch);
1293         } elseif ($this->_BIFF_version == 0x0600) {
1294             $data    = pack("vvvvvCCCCCC", $dyHeight, $grbit, $icv, $bls,
1295                                            $sss, $uls, $bFamily,
1296                                            $bCharSet, $reserved, $cch, $encoding);
1297         }
1298         return($header . $data . $this->_font_name);
1299     }
1300
1301     /**
1302     * Returns a unique hash key for a font.
1303     * Used by Spreadsheet_Excel_Writer_Workbook::_storeAllFonts()
1304     *
1305     * The elements that form the key are arranged to increase the probability of
1306     * generating a unique key. Elements that hold a large range of numbers
1307     * (eg. _color) are placed between two binary elements such as _italic
1308     *
1309     * @return string A key for this font
1310     */
1311     function getFontKey()
1312     {
1313         $key  = "$this->_font_name$this->_size";
1314         $key .= "$this->_font_script$this->_underline";
1315         $key .= "$this->_font_strikeout$this->_bold$this->_font_outline";
1316         $key .= "$this->_font_family$this->_font_charset";
1317         $key .= "$this->_font_shadow$this->_color$this->_italic";
1318         $key  = str_replace(' ', '_', $key);
1319         return ($key);
1320     }
1321
1322     /**
1323     * Returns the index used by Spreadsheet_Excel_Writer_Worksheet::_XF()
1324     *
1325     * @return integer The index for the XF record
1326     */
1327     function getXfIndex()
1328     {
1329         return($this->_xf_index);
1330     }
1331
1332     /**
1333     * Used in conjunction with the set_xxx_color methods to convert a color
1334     * string into a number. Color range is 0..63 but we will restrict it
1335     * to 8..63 to comply with Gnumeric. Colors 0..7 are repeated in 8..15.
1336     *
1337     * @access private
1338     * @param string $name_color name of the color (i.e.: 'blue', 'red', etc..). Optional.
1339     * @return integer The color index
1340     */
1341     function _getColor($name_color = '')
1342     {
1343         $colors = array(
1344                         'aqua'    => 0x0F,
1345                         'cyan'    => 0x0F,
1346                         'black'   => 0x08,
1347                         'blue'    => 0x0C,
1348                         'brown'   => 0x10,
1349                         'magenta' => 0x0E,
1350                         'fuchsia' => 0x0E,
1351                         'gray'    => 0x17,
1352                         'grey'    => 0x17,
1353                         'green'   => 0x11,
1354                         'lime'    => 0x0B,
1355                         'navy'    => 0x12,
1356                         'orange'  => 0x35,
1357                         'purple'  => 0x14,
1358                         'red'     => 0x0A,
1359                         'silver'  => 0x16,
1360                         'white'   => 0x09,
1361                         'yellow'  => 0x0D
1362                        );
1363
1364         // Return the default color, 0x7FFF, if undef,
1365         if ($name_color == '') {
1366             return(0x7FFF);
1367         }
1368
1369         // or the color string converted to an integer,
1370         if (isset($colors[$name_color])) {
1371             return($colors[$name_color]);
1372         }
1373
1374         // or the default color if string is unrecognised,
1375         if (preg_match("/\D/",$name_color)) {
1376             return(0x7FFF);
1377         }
1378
1379         // or an index < 8 mapped into the correct range,
1380         if ($name_color < 8) {
1381             return($name_color + 8);
1382         }
1383
1384         // or the default color if arg is outside range,
1385         if ($name_color > 63) {
1386             return(0x7FFF);
1387         }
1388
1389         // or an integer in the valid range
1390         return($name_color);
1391     }
1392
1393     /**
1394     * Set cell alignment.
1395     *
1396     * @access public
1397     * @param string $location alignment for the cell ('left', 'right', etc...).
1398     */
1399     function setAlign($location)
1400     {
1401         if (preg_match("/\d/",$location)) {
1402             return;                      // Ignore numbers
1403         }
1404
1405         $location = strtolower($location);
1406
1407         if ($location == 'left') {
1408             $this->_text_h_align = 1;
1409         }
1410         if ($location == 'centre') {
1411             $this->_text_h_align = 2;
1412         }
1413         if ($location == 'center') {
1414             $this->_text_h_align = 2;
1415         }
1416         if ($location == 'right') {
1417             $this->_text_h_align = 3;
1418         }
1419         if ($location == 'fill') {
1420             $this->_text_h_align = 4;
1421         }
1422         if ($location == 'justify') {
1423             $this->_text_h_align = 5;
1424         }
1425         if ($location == 'merge') {
1426             $this->_text_h_align = 6;
1427         }
1428         if ($location == 'equal_space') { // For T.K.
1429             $this->_text_h_align = 7;
1430         }
1431         if ($location == 'top') {
1432             $this->_text_v_align = 0;
1433         }
1434         if ($location == 'vcentre') {
1435             $this->_text_v_align = 1;
1436         }
1437         if ($location == 'vcenter') {
1438             $this->_text_v_align = 1;
1439         }
1440         if ($location == 'bottom') {
1441             $this->_text_v_align = 2;
1442         }
1443         if ($location == 'vjustify') {
1444             $this->_text_v_align = 3;
1445         }
1446         if ($location == 'vequal_space') { // For T.K.
1447             $this->_text_v_align = 4;
1448         }
1449     }
1450
1451     /**
1452     * Set cell horizontal alignment.
1453     *
1454     * @access public
1455     * @param string $location alignment for the cell ('left', 'right', etc...).
1456     */
1457     function setHAlign($location)
1458     {
1459         if (preg_match("/\d/",$location)) {
1460             return;                      // Ignore numbers
1461         }
1462     
1463         $location = strtolower($location);
1464     
1465         if ($location == 'left') {
1466             $this->_text_h_align = 1;
1467         }
1468         if ($location == 'centre') {
1469             $this->_text_h_align = 2;
1470         }
1471         if ($location == 'center') {
1472             $this->_text_h_align = 2;
1473         }
1474         if ($location == 'right') {
1475             $this->_text_h_align = 3;
1476         }
1477         if ($location == 'fill') {
1478             $this->_text_h_align = 4;
1479         }
1480         if ($location == 'justify') {
1481             $this->_text_h_align = 5;
1482         }
1483         if ($location == 'merge') {
1484             $this->_text_h_align = 6;
1485         }
1486         if ($location == 'equal_space') { // For T.K.
1487             $this->_text_h_align = 7;
1488         }
1489     }
1490
1491     /**
1492     * Set cell vertical alignment.
1493     *
1494     * @access public
1495     * @param string $location alignment for the cell ('top', 'vleft', 'vright', etc...).
1496     */
1497     function setVAlign($location)
1498     {
1499         if (preg_match("/\d/",$location)) {
1500             return;                      // Ignore numbers
1501         }
1502     
1503         $location = strtolower($location);
1504  
1505         if ($location == 'top') {
1506             $this->_text_v_align = 0;
1507         }
1508         if ($location == 'vcentre') {
1509             $this->_text_v_align = 1;
1510         }
1511         if ($location == 'vcenter') {
1512             $this->_text_v_align = 1;
1513         }
1514         if ($location == 'bottom') {
1515             $this->_text_v_align = 2;
1516         }
1517         if ($location == 'vjustify') {
1518             $this->_text_v_align = 3;
1519         }
1520         if ($location == 'vequal_space') { // For T.K.
1521             $this->_text_v_align = 4;
1522         }
1523     }
1524
1525     /**
1526     * This is an alias for the unintuitive setAlign('merge')
1527     *
1528     * @access public
1529     */
1530     function setMerge()
1531     {
1532         $this->setAlign('merge');
1533     }
1534
1535     /**
1536     * Sets the boldness of the text.
1537     * Bold has a range 100..1000.
1538     * 0 (400) is normal. 1 (700) is bold.
1539     *
1540     * @access public
1541     * @param integer $weight Weight for the text, 0 maps to 400 (normal text),
1542                              1 maps to 700 (bold text). Valid range is: 100-1000.
1543                              It's Optional, default is 1 (bold).
1544     */
1545     function setBold($weight = 1)
1546     {
1547         if ($weight == 1) {
1548             $weight = 0x2BC;  // Bold text
1549         }
1550         if ($weight == 0) {
1551             $weight = 0x190;  // Normal text
1552         }
1553         if ($weight <  0x064) {
1554             $weight = 0x190;  // Lower bound
1555         }
1556         if ($weight >  0x3E8) {
1557             $weight = 0x190;  // Upper bound
1558         }
1559         $this->_bold = $weight;
1560     }
1561
1562
1563     /************************************
1564     * FUNCTIONS FOR SETTING CELLS BORDERS
1565     */
1566
1567     /**
1568     * Sets the width for the bottom border of the cell
1569     *
1570     * @access public
1571     * @param integer $style style of the cell border. 1 => thin, 2 => thick.
1572     */
1573     function setBottom($style)
1574     {
1575         $this->_bottom = $style;
1576     }
1577
1578     /**
1579     * Sets the width for the top border of the cell
1580     *
1581     * @access public
1582     * @param integer $style style of the cell top border. 1 => thin, 2 => thick.
1583     */
1584     function setTop($style)
1585     {
1586         $this->_top = $style;
1587     }
1588
1589     /**
1590     * Sets the width for the left border of the cell
1591     *
1592     * @access public
1593     * @param integer $style style of the cell left border. 1 => thin, 2 => thick.
1594     */
1595     function setLeft($style)
1596     {
1597         $this->_left = $style;
1598     }
1599
1600     /**
1601     * Sets the width for the right border of the cell
1602     *
1603     * @access public
1604     * @param integer $style style of the cell right border. 1 => thin, 2 => thick.
1605     */
1606     function setRight($style)
1607     {
1608         $this->_right = $style;
1609     }
1610
1611
1612     /**
1613     * Set cells borders to the same style
1614     *
1615     * @access public
1616     * @param integer $style style to apply for all cell borders. 1 => thin, 2 => thick.
1617     */
1618     function setBorder($style)
1619     {
1620         $this->setBottom($style);
1621         $this->setTop($style);
1622         $this->setLeft($style);
1623         $this->setRight($style);
1624     }
1625
1626
1627     /*******************************************
1628     * FUNCTIONS FOR SETTING CELLS BORDERS COLORS
1629     */
1630
1631     /**
1632     * Sets all the cell's borders to the same color
1633     *
1634     * @access public
1635     * @param mixed $color The color we are setting. Either a string (like 'blue'),
1636     *                     or an integer (range is [8...63]).
1637     */
1638     function setBorderColor($color)
1639     {
1640         $this->setBottomColor($color);
1641         $this->setTopColor($color);
1642         $this->setLeftColor($color);
1643         $this->setRightColor($color);
1644     }
1645
1646     /**
1647     * Sets the cell's bottom border color
1648     *
1649     * @access public
1650     * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63]).
1651     */
1652     function setBottomColor($color)
1653     {
1654         $value = $this->_getColor($color);
1655         $this->_bottom_color = $value;
1656     }
1657
1658     /**
1659     * Sets the cell's top border color
1660     *
1661     * @access public
1662     * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63]).
1663     */
1664     function setTopColor($color)
1665     {
1666         $value = $this->_getColor($color);
1667         $this->_top_color = $value;
1668     }
1669
1670     /**
1671     * Sets the cell's left border color
1672     *
1673     * @access public
1674     * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63]).
1675     */
1676     function setLeftColor($color)
1677     {
1678         $value = $this->_getColor($color);
1679         $this->_left_color = $value;
1680     }
1681
1682     /**
1683     * Sets the cell's right border color
1684     *
1685     * @access public
1686     * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63]).
1687     */
1688     function setRightColor($color)
1689     {
1690         $value = $this->_getColor($color);
1691         $this->_right_color = $value;
1692     }
1693
1694
1695     /**
1696     * Sets the cell's foreground color
1697     *
1698     * @access public
1699     * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63]).
1700     */
1701     function setFgColor($color)
1702     {
1703         $value = $this->_getColor($color);
1704         $this->_fg_color = $value;
1705         if ($this->_pattern == 0) { // force color to be seen
1706             $this->_pattern = 1;
1707         }
1708     }
1709
1710     /**
1711     * Sets the cell's background color
1712     *
1713     * @access public
1714     * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63]).
1715     */
1716     function setBgColor($color)
1717     {
1718         $value = $this->_getColor($color);
1719         $this->_bg_color = $value;
1720         if ($this->_pattern == 0) { // force color to be seen
1721             $this->_pattern = 1;
1722         }
1723     }
1724
1725     /**
1726     * Sets the cell's color
1727     *
1728     * @access public
1729     * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63]).
1730     */
1731     function setColor($color)
1732     {
1733         $value = $this->_getColor($color);
1734         $this->_color = $value;
1735     }
1736
1737     /**
1738     * Sets the fill pattern attribute of a cell
1739     *
1740     * @access public
1741     * @param integer $arg Optional. Defaults to 1. Meaningful values are: 0-18,
1742     *                     0 meaning no background.
1743     */
1744     function setPattern($arg = 1)
1745     {
1746         $this->_pattern = $arg;
1747     }
1748
1749     /**
1750     * Sets the underline of the text
1751     *
1752     * @access public
1753     * @param integer $underline The value for underline. Possible values are:
1754     *                          1 => underline, 2 => double underline.
1755     */
1756     function setUnderline($underline)
1757     {
1758         $this->_underline = $underline;
1759     }
1760
1761     /**
1762     * Sets the font style as italic
1763     *
1764     * @access public
1765     */
1766     function setItalic()
1767     {
1768         $this->_italic = 1;
1769     }
1770
1771     /**
1772     * Sets the font size
1773     *
1774     * @access public
1775     * @param integer $size The font size (in pixels I think).
1776     */
1777     function setSize($size)
1778     {
1779         $this->_size = $size;
1780     }
1781
1782     /**
1783     * Sets text wrapping
1784     *
1785     * @access public
1786     */
1787     function setTextWrap()
1788     {
1789         $this->_text_wrap = 1;
1790     }
1791
1792     /**
1793     * Sets the orientation of the text
1794     *
1795     * @access public
1796     * @param integer $angle The rotation angle for the text (clockwise). Possible
1797                             values are: 0, 90, 270 and -1 for stacking top-to-bottom.
1798     */
1799     function setTextRotation($angle)
1800     {
1801         switch ($angle)
1802         {
1803             case 0:
1804                 $this->_rotation = 0;
1805                 break;
1806             case 90:
1807                 $this->_rotation = 3;
1808                 break;
1809             case 270:
1810                 $this->_rotation = 2;
1811                 break;
1812             case -1:
1813                 $this->_rotation = 1;
1814                 break;
1815             default :
1816                 $this->_rotation = 0;
1817                 break;
1818         }
1819     }
1820
1821     /**
1822     * Sets the numeric format.
1823     * It can be date, time, currency, etc...
1824     *
1825     * @access public
1826     * @param integer $num_format The numeric format.
1827     */
1828     function setNumFormat($num_format)
1829     {
1830         $this->_num_format = $num_format;
1831     }
1832
1833     /**
1834     * Sets font as strikeout.
1835     *
1836     * @access public
1837     */
1838     function setStrikeOut()
1839     {
1840         $this->_font_strikeout = 1;
1841     }
1842
1843     /**
1844     * Sets outlining for a font.
1845     *
1846     * @access public
1847     */
1848     function setOutLine()
1849     {
1850         $this->_font_outline = 1;
1851     }
1852
1853     /**
1854     * Sets font as shadow.
1855     *
1856     * @access public
1857     */
1858     function setShadow()
1859     {
1860         $this->_font_shadow = 1;
1861     }
1862
1863     /**
1864     * Sets the script type of the text
1865     *
1866     * @access public
1867     * @param integer $script The value for script type. Possible values are:
1868     *                        1 => superscript, 2 => subscript.
1869     */
1870     function setScript($script)
1871     {
1872         $this->_font_script = $script;
1873     }
1874
1875      /**
1876      * Locks a cell.
1877      *
1878      * @access public
1879      */
1880      function setLocked()
1881      {
1882          $this->_locked = 1;
1883      }
1884
1885     /**
1886     * Unlocks a cell. Useful for unprotecting particular cells of a protected sheet.
1887     *
1888     * @access public
1889     */
1890     function setUnLocked()
1891     {
1892         $this->_locked = 0;
1893     }
1894
1895     /**
1896     * Sets the font family name.
1897     *
1898     * @access public
1899     * @param string $fontfamily The font family name. Possible values are:
1900     *                           'Times New Roman', 'Arial', 'Courier'.
1901     */
1902     function setFontFamily($font_family)
1903     {
1904         $this->_font_name = $font_family;
1905     }
1906 }
1907
1908 /**
1909 * Class for parsing Excel formulas
1910 *
1911 * @author   Xavier Noguer <xnoguer@rezebra.com>
1912 * @category FileFormats
1913 * @package  Spreadsheet_Excel_Writer
1914 */
1915
1916 class Spreadsheet_Excel_Writer_Parser
1917 {
1918     /**
1919     * The index of the character we are currently looking at
1920     * @var integer
1921     */
1922     var $_current_char;
1923
1924     /**
1925     * The token we are working on.
1926     * @var string
1927     */
1928     var $_current_token;
1929
1930     /**
1931     * The formula to parse
1932     * @var string
1933     */
1934     var $_formula;
1935
1936     /**
1937     * The character ahead of the current char
1938     * @var string
1939     */
1940     var $_lookahead;
1941
1942     /**
1943     * The parse tree to be generated
1944     * @var string
1945     */
1946     var $_parse_tree;
1947
1948     /**
1949     * The byte order. 1 => big endian, 0 => little endian.
1950     * @var integer
1951     */
1952     var $_byte_order;
1953
1954     /**
1955     * Array of external sheets
1956     * @var array
1957     */
1958     var $_ext_sheets;
1959
1960     /**
1961     * Array of sheet references in the form of REF structures
1962     * @var array
1963     */
1964     var $_references;
1965
1966     /**
1967     * The BIFF version for the workbook
1968     * @var integer
1969     */
1970     var $_BIFF_version;
1971
1972     /**
1973     * The class constructor
1974     *
1975     * @param integer $byte_order The byte order (Little endian or Big endian) of the architecture
1976                                  (optional). 1 => big endian, 0 (default) little endian.
1977     */
1978     function Spreadsheet_Excel_Writer_Parser($byte_order, $biff_version)
1979     {
1980         $this->_current_char  = 0;
1981         $this->_BIFF_version  = $biff_version;
1982         $this->_current_token = '';       // The token we are working on.
1983         $this->_formula       = '';       // The formula to parse.
1984         $this->_lookahead     = '';       // The character ahead of the current char.
1985         $this->_parse_tree    = '';       // The parse tree to be generated.
1986         $this->_initializeHashes();      // Initialize the hashes: ptg's and function's ptg's
1987         $this->_byte_order = $byte_order; // Little Endian or Big Endian
1988         $this->_ext_sheets = array();
1989         $this->_references = array();
1990     }
1991
1992     /**
1993     * Initialize the ptg and function hashes.
1994     *
1995     * @access private
1996     */
1997     function _initializeHashes()
1998     {
1999         // The Excel ptg indices
2000         $this->ptg = array(
2001             'ptgExp'       => 0x01,
2002             'ptgTbl'       => 0x02,
2003             'ptgAdd'       => 0x03,
2004             'ptgSub'       => 0x04,
2005             'ptgMul'       => 0x05,
2006             'ptgDiv'       => 0x06,
2007             'ptgPower'     => 0x07,
2008             'ptgConcat'    => 0x08,
2009             'ptgLT'        => 0x09,
2010             'ptgLE'        => 0x0A,
2011             'ptgEQ'        => 0x0B,
2012             'ptgGE'        => 0x0C,
2013             'ptgGT'        => 0x0D,
2014             'ptgNE'        => 0x0E,
2015             'ptgIsect'     => 0x0F,
2016             'ptgUnion'     => 0x10,
2017             'ptgRange'     => 0x11,
2018             'ptgUplus'     => 0x12,
2019             'ptgUminus'    => 0x13,
2020             'ptgPercent'   => 0x14,
2021             'ptgParen'     => 0x15,
2022             'ptgMissArg'   => 0x16,
2023             'ptgStr'       => 0x17,
2024             'ptgAttr'      => 0x19,
2025             'ptgSheet'     => 0x1A,
2026             'ptgEndSheet'  => 0x1B,
2027             'ptgErr'       => 0x1C,
2028             'ptgBool'      => 0x1D,
2029             'ptgInt'       => 0x1E,
2030             'ptgNum'       => 0x1F,
2031             'ptgArray'     => 0x20,
2032             'ptgFunc'      => 0x21,
2033             'ptgFuncVar'   => 0x22,
2034             'ptgName'      => 0x23,
2035             'ptgRef'       => 0x24,
2036             'ptgArea'      => 0x25,
2037             'ptgMemArea'   => 0x26,
2038             'ptgMemErr'    => 0x27,
2039             'ptgMemNoMem'  => 0x28,
2040             'ptgMemFunc'   => 0x29,
2041             'ptgRefErr'    => 0x2A,
2042             'ptgAreaErr'   => 0x2B,
2043             'ptgRefN'      => 0x2C,
2044             'ptgAreaN'     => 0x2D,
2045             'ptgMemAreaN'  => 0x2E,
2046             'ptgMemNoMemN' => 0x2F,
2047             'ptgNameX'     => 0x39,
2048             'ptgRef3d'     => 0x3A,
2049             'ptgArea3d'    => 0x3B,
2050             'ptgRefErr3d'  => 0x3C,
2051             'ptgAreaErr3d' => 0x3D,
2052             'ptgArrayV'    => 0x40,
2053             'ptgFuncV'     => 0x41,
2054             'ptgFuncVarV'  => 0x42,
2055             'ptgNameV'     => 0x43,
2056             'ptgRefV'      => 0x44,
2057             'ptgAreaV'     => 0x45,
2058             'ptgMemAreaV'  => 0x46,
2059             'ptgMemErrV'   => 0x47,
2060             'ptgMemNoMemV' => 0x48,
2061             'ptgMemFuncV'  => 0x49,
2062             'ptgRefErrV'   => 0x4A,
2063             'ptgAreaErrV'  => 0x4B,
2064             'ptgRefNV'     => 0x4C,
2065             'ptgAreaNV'    => 0x4D,
2066             'ptgMemAreaNV' => 0x4E,
2067             'ptgMemNoMemN' => 0x4F,
2068             'ptgFuncCEV'   => 0x58,
2069             'ptgNameXV'    => 0x59,
2070             'ptgRef3dV'    => 0x5A,
2071             'ptgArea3dV'   => 0x5B,
2072             'ptgRefErr3dV' => 0x5C,
2073             'ptgAreaErr3d' => 0x5D,
2074             'ptgArrayA'    => 0x60,
2075             'ptgFuncA'     => 0x61,
2076             'ptgFuncVarA'  => 0x62,
2077             'ptgNameA'     => 0x63,
2078             'ptgRefA'      => 0x64,
2079             'ptgAreaA'     => 0x65,
2080             'ptgMemAreaA'  => 0x66,
2081             'ptgMemErrA'   => 0x67,
2082             'ptgMemNoMemA' => 0x68,
2083             'ptgMemFuncA'  => 0x69,
2084             'ptgRefErrA'   => 0x6A,
2085             'ptgAreaErrA'  => 0x6B,
2086             'ptgRefNA'     => 0x6C,
2087             'ptgAreaNA'    => 0x6D,
2088             'ptgMemAreaNA' => 0x6E,
2089             'ptgMemNoMemN' => 0x6F,
2090             'ptgFuncCEA'   => 0x78,
2091             'ptgNameXA'    => 0x79,
2092             'ptgRef3dA'    => 0x7A,
2093             'ptgArea3dA'   => 0x7B,
2094             'ptgRefErr3dA' => 0x7C,
2095             'ptgAreaErr3d' => 0x7D
2096             );
2097
2098         // Thanks to Michael Meeks and Gnumeric for the initial arg values.
2099         //
2100         // The following hash was generated by "function_locale.pl" in the distro.
2101         // Refer to function_locale.pl for non-English function names.
2102         //
2103         // The array elements are as follow:
2104         // ptg:   The Excel function ptg code.
2105         // args:  The number of arguments that the function takes:
2106         //           >=0 is a fixed number of arguments.
2107         //           -1  is a variable  number of arguments.
2108         // class: The reference, value or array class of the function args.
2109         // vol:   The function is volatile.
2110         //
2111         $this->_functions = array(
2112               // function                  ptg  args  class  vol
2113               'COUNT'           => array(   0,   -1,    0,    0 ),
2114               'IF'              => array(   1,   -1,    1,    0 ),
2115               'ISNA'            => array(   2,    1,    1,    0 ),
2116               'ISERROR'         => array(   3,    1,    1,    0 ),
2117               'SUM'             => array(   4,   -1,    0,    0 ),
2118               'AVERAGE'         => array(   5,   -1,    0,    0 ),
2119               'MIN'             => array(   6,   -1,    0,    0 ),
2120               'MAX'             => array(   7,   -1,    0,    0 ),
2121               'ROW'             => array(   8,   -1,    0,    0 ),
2122               'COLUMN'          => array(   9,   -1,    0,    0 ),
2123               'NA'              => array(  10,    0,    0,    0 ),
2124               'NPV'             => array(  11,   -1,    1,    0 ),
2125               'STDEV'           => array(  12,   -1,    0,    0 ),
2126               'DOLLAR'          => array(  13,   -1,    1,    0 ),
2127               'FIXED'           => array(  14,   -1,    1,    0 ),
2128               'SIN'             => array(  15,    1,    1,    0 ),
2129               'COS'             => array(  16,    1,    1,    0 ),
2130               'TAN'             => array(  17,    1,    1,    0 ),
2131               'ATAN'            => array(  18,    1,    1,    0 ),
2132               'PI'              => array(  19,    0,    1,    0 ),
2133               'SQRT'            => array(  20,    1,    1,    0 ),
2134               'EXP'             => array(  21,    1,    1,    0 ),
2135               'LN'              => array(  22,    1,    1,    0 ),
2136               'LOG10'           => array(  23,    1,    1,    0 ),
2137               'ABS'             => array(  24,    1,    1,    0 ),
2138               'INT'             => array(  25,    1,    1,    0 ),
2139               'SIGN'            => array(  26,    1,    1,    0 ),
2140               'ROUND'           => array(  27,    2,    1,    0 ),
2141               'LOOKUP'          => array(  28,   -1,    0,    0 ),
2142               'INDEX'           => array(  29,   -1,    0,    1 ),
2143               'REPT'            => array(  30,    2,    1,    0 ),
2144               'MID'             => array(  31,    3,    1,    0 ),
2145               'LEN'             => array(  32,    1,    1,    0 ),
2146               'VALUE'           => array(  33,    1,    1,    0 ),
2147               'TRUE'            => array(  34,    0,    1,    0 ),
2148               'FALSE'           => array(  35,    0,    1,    0 ),
2149               'AND'             => array(  36,   -1,    0,    0 ),
2150               'OR'              => array(  37,   -1,    0,    0 ),
2151               'NOT'             => array(  38,    1,    1,    0 ),
2152               'MOD'             => array(  39,    2,    1,    0 ),
2153               'DCOUNT'          => array(  40,    3,    0,    0 ),
2154               'DSUM'            => array(  41,    3,    0,    0 ),
2155               'DAVERAGE'        => array(  42,    3,    0,    0 ),
2156               'DMIN'            => array(  43,    3,    0,    0 ),
2157               'DMAX'            => array(  44,    3,    0,    0 ),
2158               'DSTDEV'          => array(  45,    3,    0,    0 ),
2159               'VAR'             => array(  46,   -1,    0,    0 ),
2160               'DVAR'            => array(  47,    3,    0,    0 ),
2161               'TEXT'            => array(  48,    2,    1,    0 ),
2162               'LINEST'          => array(  49,   -1,    0,    0 ),
2163               'TREND'           => array(  50,   -1,    0,    0 ),
2164               'LOGEST'          => array(  51,   -1,    0,    0 ),
2165               'GROWTH'          => array(  52,   -1,    0,    0 ),
2166               'PV'              => array(  56,   -1,    1,    0 ),
2167               'FV'              => array(  57,   -1,    1,    0 ),
2168               'NPER'            => array(  58,   -1,    1,    0 ),
2169               'PMT'             => array(  59,   -1,    1,    0 ),
2170               'RATE'            => array(  60,   -1,    1,    0 ),
2171               'MIRR'            => array(  61,    3,    0,    0 ),
2172               'IRR'             => array(  62,   -1,    0,    0 ),
2173               'RAND'            => array(  63,    0,    1,    1 ),
2174               'MATCH'           => array(  64,   -1,    0,    0 ),
2175               'DATE'            => array(  65,    3,    1,    0 ),
2176               'TIME'            => array(  66,    3,    1,    0 ),
2177               'DAY'             => array(  67,    1,    1,    0 ),
2178               'MONTH'           => array(  68,    1,    1,    0 ),
2179               'YEAR'            => array(  69,    1,    1,    0 ),
2180               'WEEKDAY'         => array(  70,   -1,    1,    0 ),
2181               'HOUR'            => array(  71,    1,    1,    0 ),
2182               'MINUTE'          => array(  72,    1,    1,    0 ),
2183               'SECOND'          => array(  73,    1,    1,    0 ),
2184               'NOW'             => array(  74,    0,    1,    1 ),
2185               'AREAS'           => array(  75,    1,    0,    1 ),
2186               'ROWS'            => array(  76,    1,    0,    1 ),
2187               'COLUMNS'         => array(  77,    1,    0,    1 ),
2188               'OFFSET'          => array(  78,   -1,    0,    1 ),
2189               'SEARCH'          => array(  82,   -1,    1,    0 ),
2190               'TRANSPOSE'       => array(  83,    1,    1,    0 ),
2191               'TYPE'            => array(  86,    1,    1,    0 ),
2192               'ATAN2'           => array(  97,    2,    1,    0 ),
2193               'ASIN'            => array(  98,    1,    1,    0 ),
2194               'ACOS'            => array(  99,    1,    1,    0 ),
2195               'CHOOSE'          => array( 100,   -1,    1,    0 ),
2196               'HLOOKUP'         => array( 101,   -1,    0,    0 ),
2197               'VLOOKUP'         => array( 102,   -1,    0,    0 ),
2198               'ISREF'           => array( 105,    1,    0,    0 ),
2199               'LOG'             => array( 109,   -1,    1,    0 ),
2200               'CHAR'            => array( 111,    1,    1,    0 ),
2201               'LOWER'           => array( 112,    1,    1,    0 ),
2202               'UPPER'           => array( 113,    1,    1,    0 ),
2203               'PROPER'          => array( 114,    1,    1,    0 ),
2204               'LEFT'            => array( 115,   -1,    1,    0 ),
2205               'RIGHT'           => array( 116,   -1,    1,    0 ),
2206               'EXACT'           => array( 117,    2,    1,    0 ),
2207               'TRIM'            => array( 118,    1,    1,    0 ),
2208               'REPLACE'         => array( 119,    4,    1,    0 ),
2209               'SUBSTITUTE'      => array( 120,   -1,    1,    0 ),
2210               'CODE'            => array( 121,    1,    1,    0 ),
2211               'FIND'            => array( 124,   -1,    1,    0 ),
2212               'CELL'            => array( 125,   -1,    0,    1 ),
2213               'ISERR'           => array( 126,    1,    1,    0 ),
2214               'ISTEXT'          => array( 127,    1,    1,    0 ),
2215               'ISNUMBER'        => array( 128,    1,    1,    0 ),
2216               'ISBLANK'         => array( 129,    1,    1,    0 ),
2217               'T'               => array( 130,    1,    0,    0 ),
2218               'N'               => array( 131,    1,    0,    0 ),
2219               'DATEVALUE'       => array( 140,    1,    1,    0 ),
2220               'TIMEVALUE'       => array( 141,    1,    1,    0 ),
2221               'SLN'             => array( 142,    3,    1,    0 ),
2222               'SYD'             => array( 143,    4,    1,    0 ),
2223               'DDB'             => array( 144,   -1,    1,    0 ),
2224               'INDIRECT'        => array( 148,   -1,    1,    1 ),
2225               'CALL'            => array( 150,   -1,    1,    0 ),
2226               'CLEAN'           => array( 162,    1,    1,    0 ),
2227               'MDETERM'         => array( 163,    1,    2,    0 ),
2228               'MINVERSE'        => array( 164,    1,    2,    0 ),
2229               'MMULT'           => array( 165,    2,    2,    0 ),
2230               'IPMT'            => array( 167,   -1,    1,    0 ),
2231               'PPMT'            => array( 168,   -1,    1,    0 ),
2232               'COUNTA'          => array( 169,   -1,    0,    0 ),
2233               'PRODUCT'         => array( 183,   -1,    0,    0 ),
2234               'FACT'            => array( 184,    1,    1,    0 ),
2235               'DPRODUCT'        => array( 189,    3,    0,    0 ),
2236               'ISNONTEXT'       => array( 190,    1,    1,    0 ),
2237               'STDEVP'          => array( 193,   -1,    0,    0 ),
2238               'VARP'            => array( 194,   -1,    0,    0 ),
2239               'DSTDEVP'         => array( 195,    3,    0,    0 ),
2240               'DVARP'           => array( 196,    3,    0,    0 ),
2241               'TRUNC'           => array( 197,   -1,    1,    0 ),
2242               'ISLOGICAL'       => array( 198,    1,    1,    0 ),
2243               'DCOUNTA'         => array( 199,    3,    0,    0 ),
2244               'ROUNDUP'         => array( 212,    2,    1,    0 ),
2245               'ROUNDDOWN'       => array( 213,    2,    1,    0 ),
2246               'RANK'            => array( 216,   -1,    0,    0 ),
2247               'ADDRESS'         => array( 219,   -1,    1,    0 ),
2248               'DAYS360'         => array( 220,   -1,    1,    0 ),
2249               'TODAY'           => array( 221,    0,    1,    1 ),
2250               'VDB'             => array( 222,   -1,    1,    0 ),
2251               'MEDIAN'          => array( 227,   -1,    0,    0 ),
2252               'SUMPRODUCT'      => array( 228,   -1,    2,    0 ),
2253               'SINH'            => array( 229,    1,    1,    0 ),
2254               'COSH'            => array( 230,    1,    1,    0 ),
2255               'TANH'            => array( 231,    1,    1,    0 ),
2256               'ASINH'           => array( 232,    1,    1,    0 ),
2257               'ACOSH'           => array( 233,    1,    1,    0 ),
2258               'ATANH'           => array( 234,    1,    1,    0 ),
2259               'DGET'            => array( 235,    3,    0,    0 ),
2260               'INFO'            => array( 244,    1,    1,    1 ),
2261               'DB'              => array( 247,   -1,    1,    0 ),
2262               'FREQUENCY'       => array( 252,    2,    0,    0 ),
2263               'ERROR.TYPE'      => array( 261,    1,    1,    0 ),
2264               'REGISTER.ID'     => array( 267,   -1,    1,    0 ),
2265               'AVEDEV'          => array( 269,   -1,    0,    0 ),
2266               'BETADIST'        => array( 270,   -1,    1,    0 ),
2267               'GAMMALN'         => array( 271,    1,    1,    0 ),
2268               'BETAINV'         => array( 272,   -1,    1,    0 ),
2269               'BINOMDIST'       => array( 273,    4,    1,    0 ),
2270               'CHIDIST'         => array( 274,    2,    1,    0 ),
2271               'CHIINV'          => array( 275,    2,    1,    0 ),
2272               'COMBIN'          => array( 276,    2,    1,    0 ),
2273               'CONFIDENCE'      => array( 277,    3,    1,    0 ),
2274               'CRITBINOM'       => array( 278,    3,    1,    0 ),
2275               'EVEN'            => array( 279,    1,    1,    0 ),
2276               'EXPONDIST'       => array( 280,    3,    1,    0 ),
2277               'FDIST'           => array( 281,    3,    1,    0 ),
2278               'FINV'            => array( 282,    3,    1,    0 ),
2279               'FISHER'          => array( 283,    1,    1,    0 ),
2280               'FISHERINV'       => array( 284,    1,    1,    0 ),
2281               'FLOOR'           => array( 285,    2,    1,    0 ),
2282               'GAMMADIST'       => array( 286,    4,    1,    0 ),
2283               'GAMMAINV'        => array( 287,    3,    1,    0 ),
2284               'CEILING'         => array( 288,    2,    1,    0 ),
2285               'HYPGEOMDIST'     => array( 289,    4,    1,    0 ),
2286               'LOGNORMDIST'     => array( 290,    3,    1,    0 ),
2287               'LOGINV'          => array( 291,    3,    1,    0 ),
2288               'NEGBINOMDIST'    => array( 292,    3,    1,    0 ),
2289               'NORMDIST'        => array( 293,    4,    1,    0 ),
2290               'NORMSDIST'       => array( 294,    1,    1,    0 ),
2291               'NORMINV'         => array( 295,    3,    1,    0 ),
2292               'NORMSINV'        => array( 296,    1,    1,    0 ),
2293               'STANDARDIZE'     => array( 297,    3,    1,    0 ),
2294               'ODD'             => array( 298,    1,    1,    0 ),
2295               'PERMUT'          => array( 299,    2,    1,    0 ),
2296               'POISSON'         => array( 300,    3,    1,    0 ),
2297               'TDIST'           => array( 301,    3,    1,    0 ),
2298               'WEIBULL'         => array( 302,    4,    1,    0 ),
2299               'SUMXMY2'         => array( 303,    2,    2,    0 ),
2300               'SUMX2MY2'        => array( 304,    2,    2,    0 ),
2301               'SUMX2PY2'        => array( 305,    2,    2,    0 ),
2302               'CHITEST'         => array( 306,    2,    2,    0 ),
2303               'CORREL'          => array( 307,    2,    2,    0 ),
2304               'COVAR'           => array( 308,    2,    2,    0 ),
2305               'FORECAST'        => array( 309,    3,    2,    0 ),
2306               'FTEST'           => array( 310,    2,    2,    0 ),
2307               'INTERCEPT'       => array( 311,    2,    2,    0 ),
2308               'PEARSON'         => array( 312,    2,    2,    0 ),
2309               'RSQ'             => array( 313,    2,    2,    0 ),
2310               'STEYX'           => array( 314,    2,    2,    0 ),
2311               'SLOPE'           => array( 315,    2,    2,    0 ),
2312               'TTEST'           => array( 316,    4,    2,    0 ),
2313               'PROB'            => array( 317,   -1,    2,    0 ),
2314               'DEVSQ'           => array( 318,   -1,    0,    0 ),
2315               'GEOMEAN'         => array( 319,   -1,    0,    0 ),
2316               'HARMEAN'         => array( 320,   -1,    0,    0 ),
2317               'SUMSQ'           => array( 321,   -1,    0,    0 ),
2318               'KURT'            => array( 322,   -1,    0,    0 ),
2319               'SKEW'            => array( 323,   -1,    0,    0 ),
2320               'ZTEST'           => array( 324,   -1,    0,    0 ),
2321               'LARGE'           => array( 325,    2,    0,    0 ),
2322               'SMALL'           => array( 326,    2,    0,    0 ),
2323               'QUARTILE'        => array( 327,    2,    0,    0 ),
2324               'PERCENTILE'      => array( 328,    2,    0,    0 ),
2325               'PERCENTRANK'     => array( 329,   -1,    0,    0 ),
2326               'MODE'            => array( 330,   -1,    2,    0 ),
2327               'TRIMMEAN'        => array( 331,    2,    0,    0 ),
2328               'TINV'            => array( 332,    2,    1,    0 ),
2329               'CONCATENATE'     => array( 336,   -1,    1,    0 ),
2330               'POWER'           => array( 337,    2,    1,    0 ),
2331               'RADIANS'         => array( 342,    1,    1,    0 ),
2332               'DEGREES'         => array( 343,    1,    1,    0 ),
2333               'SUBTOTAL'        => array( 344,   -1,    0,    0 ),
2334               'SUMIF'           => array( 345,   -1,    0,    0 ),
2335               'COUNTIF'         => array( 346,    2,    0,    0 ),
2336               'COUNTBLANK'      => array( 347,    1,    0,    0 ),
2337               'ROMAN'           => array( 354,   -1,    1,    0 )
2338               );
2339     }
2340
2341     /**
2342     * Convert a token to the proper ptg value.
2343     *
2344     * @access private
2345     * @param mixed $token The token to convert.
2346     * @return mixed the converted token on success. Die if the token
2347     *               is not recognized
2348     */
2349     function _convert($token)
2350     {
2351         if (preg_match("/^\"[^\"]{0,255}\"$/", $token)) {
2352             return $this->_convertString($token);
2353
2354         } elseif (is_numeric($token)) {
2355             return $this->_convertNumber($token);
2356
2357         // match references like A1 or $A$1
2358         } elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/',$token)) {
2359             return $this->_convertRef2d($token);
2360
2361         // match external references like Sheet1!A1 or Sheet1:Sheet2!A1
2362         } elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z](\d+)$/u",$token)) {
2363             return $this->_convertRef3d($token);
2364
2365         // match external references like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1
2366         } elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\![A-Ia-i]?[A-Za-z](\d+)$/u",$token)) {
2367             return $this->_convertRef3d($token);
2368
2369         // match ranges like A1:B2
2370         } elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token)) {
2371             return $this->_convertRange2d($token);
2372
2373         // match ranges like A1..B2
2374         } elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token)) {
2375             return $this->_convertRange2d($token);
2376
2377         // match external ranges like Sheet1!A1 or Sheet1:Sheet2!A1:B2
2378         } elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/u",$token)) {
2379             return $this->_convertRange3d($token);
2380
2381         // match external ranges like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1:B2
2382         } elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/u",$token)) {
2383             return $this->_convertRange3d($token);
2384
2385         // operators (including parentheses)
2386         } elseif (isset($this->ptg[$token])) {
2387             return pack("C", $this->ptg[$token]);
2388
2389         // commented so argument number can be processed correctly. See toReversePolish().
2390         /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/",$token))
2391         {
2392             return($this->_convertFunction($token,$this->_func_args));
2393         }*/
2394
2395         // if it's an argument, ignore the token (the argument remains)
2396         } elseif ($token == 'arg') {
2397             return '';
2398         }
2399         // TODO: use real error codes
2400         die("Unknown token $token");
2401     }
2402
2403     /**
2404     * Convert a number token to ptgInt or ptgNum
2405     *
2406     * @access private
2407     * @param mixed $num an integer or double for conversion to its ptg value
2408     */
2409     function _convertNumber($num)
2410     {
2411         // Integer in the range 0..2**16-1
2412         if ((preg_match("/^\d+$/", $num)) and ($num <= 65535)) {
2413             return pack("Cv", $this->ptg['ptgInt'], $num);
2414         } else { // A float
2415             if ($this->_byte_order) { // if it's Big Endian
2416                 $num = strrev($num);
2417             }
2418             return pack("Cd", $this->ptg['ptgNum'], $num);
2419         }
2420     }
2421
2422     /**
2423     * Convert a string token to ptgStr
2424     *
2425     * @access private
2426     * @param string $string A string for conversion to its ptg value.
2427     * @return mixed the converted token on success. PEAR_Error if the string
2428     *               is longer than 255 characters.
2429     */
2430     function _convertString($string)
2431     {
2432         // chop away beggining and ending quotes
2433         $string = substr($string, 1, strlen($string) - 2);
2434         if (strlen($string) > 255) {
2435             die("String is too long");
2436         }
2437
2438         if ($this->_BIFF_version == 0x0500) {
2439             return pack("CC", $this->ptg['ptgStr'], strlen($string)).$string;
2440         } elseif ($this->_BIFF_version == 0x0600) {
2441             $encoding = 0;   // TODO: Unicode support
2442             return pack("CCC", $this->ptg['ptgStr'], strlen($string), $encoding).$string;
2443         }
2444     }
2445
2446     /**
2447     * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
2448     * args that it takes.
2449     *
2450     * @access private
2451     * @param string  $token    The name of the function for convertion to ptg value.
2452     * @param integer $num_args The number of arguments the function receives.
2453     * @return string The packed ptg for the function
2454     */
2455     function _convertFunction($token, $num_args)
2456     {
2457         $args     = $this->_functions[$token][1];
2458         $volatile = $this->_functions[$token][3];
2459
2460         // Fixed number of args eg. TIME($i,$j,$k).
2461         if ($args >= 0) {
2462             return pack("Cv", $this->ptg['ptgFuncV'], $this->_functions[$token][0]);
2463         }
2464         // Variable number of args eg. SUM($i,$j,$k, ..).
2465         if ($args == -1) {
2466             return pack("CCv", $this->ptg['ptgFuncVarV'], $num_args, $this->_functions[$token][0]);
2467         }
2468     }
2469
2470     /**
2471     * Convert an Excel range such as A1:D4 to a ptgRefV.
2472     *
2473     * @access private
2474     * @param string $range An Excel range in the A1:A2 or A1..A2 format.
2475     */
2476     function _convertRange2d($range)
2477     {
2478         $class = 2; // as far as I know, this is magick.
2479
2480         // Split the range into 2 cell refs
2481         if (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\:([A-Ia-i]?[A-Za-z])(\d+)$/", $range)) {
2482             list($cell1, $cell2) = preg_split('/:/', $range);
2483         } elseif (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\.\.([A-Ia-i]?[A-Za-z])(\d+)$/", $range)) {
2484             list($cell1, $cell2) = preg_split('/\.\./', $range);
2485
2486         } else {
2487             // TODO: use real error codes
2488             die("Unknown range separator");
2489         }
2490
2491         // Convert the cell references
2492         $cell_array1 = $this->_cellToPackedRowcol($cell1);
2493         list($row1, $col1) = $cell_array1;
2494         $cell_array2 = $this->_cellToPackedRowcol($cell2);
2495         list($row2, $col2) = $cell_array2;
2496
2497         // The ptg value depends on the class of the ptg.
2498         if ($class == 0) {
2499             $ptgArea = pack("C", $this->ptg['ptgArea']);
2500         } elseif ($class == 1) {
2501             $ptgArea = pack("C", $this->ptg['ptgAreaV']);
2502         } elseif ($class == 2) {
2503             $ptgArea = pack("C", $this->ptg['ptgAreaA']);
2504         } else {
2505             // TODO: use real error codes
2506             die("Unknown class $class");
2507         }
2508         return $ptgArea . $row1 . $row2 . $col1. $col2;
2509     }
2510
2511     /**
2512     * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
2513     * a ptgArea3d.
2514     *
2515     * @access private
2516     * @param string $token An Excel range in the Sheet1!A1:A2 format.
2517     * @return mixed The packed ptgArea3d token on success, PEAR_Error on failure.
2518     */
2519     function _convertRange3d($token)
2520     {
2521         $class = 2; // as far as I know, this is magick.
2522
2523         // Split the ref at the ! symbol
2524         list($ext_ref, $range) = preg_split('/!/', $token);
2525
2526         // Convert the external reference part (different for BIFF8)
2527         if ($this->_BIFF_version == 0x0500) {
2528             $ext_ref = $this->_packExtRef($ext_ref);
2529         } elseif ($this->_BIFF_version == 0x0600) {
2530              $ext_ref = $this->_getRefIndex($ext_ref);
2531         }
2532
2533         // Split the range into 2 cell refs
2534         list($cell1, $cell2) = preg_split('/:/', $range);
2535
2536         // Convert the cell references
2537         if (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/", $cell1)) {
2538             $cell_array1 = $this->_cellToPackedRowcol($cell1);
2539             list($row1, $col1) = $cell_array1;
2540             $cell_array2 = $this->_cellToPackedRowcol($cell2);
2541             list($row2, $col2) = $cell_array2;
2542         } else { // It's a rows range (like 26:27)
2543              $cells_array = $this->_rangeToPackedRange($cell1.':'.$cell2);
2544              list($row1, $col1, $row2, $col2) = $cells_array;
2545         }
2546
2547         // The ptg value depends on the class of the ptg.
2548         if ($class == 0) {
2549             $ptgArea = pack("C", $this->ptg['ptgArea3d']);
2550         } elseif ($class == 1) {
2551             $ptgArea = pack("C", $this->ptg['ptgArea3dV']);
2552         } elseif ($class == 2) {
2553             $ptgArea = pack("C", $this->ptg['ptgArea3dA']);
2554         } else {
2555             die("Unknown class $class");
2556         }
2557
2558         return $ptgArea . $ext_ref . $row1 . $row2 . $col1. $col2;
2559     }
2560
2561     /**
2562     * Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
2563     *
2564     * @access private
2565     * @param string $cell An Excel cell reference
2566     * @return string The cell in packed() format with the corresponding ptg
2567     */
2568     function _convertRef2d($cell)
2569     {
2570         $class = 2; // as far as I know, this is magick.
2571
2572         // Convert the cell reference
2573         $cell_array = $this->_cellToPackedRowcol($cell);
2574         list($row, $col) = $cell_array;
2575
2576         // The ptg value depends on the class of the ptg.
2577         if ($class == 0) {
2578             $ptgRef = pack("C", $this->ptg['ptgRef']);
2579         } elseif ($class == 1) {
2580             $ptgRef = pack("C", $this->ptg['ptgRefV']);
2581         } elseif ($class == 2) {
2582             $ptgRef = pack("C", $this->ptg['ptgRefA']);
2583         } else {
2584             // TODO: use real error codes
2585             die("Unknown class $class");
2586         }
2587         return $ptgRef.$row.$col;
2588     }
2589
2590     /**
2591     * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
2592     * ptgRef3d.
2593     *
2594     * @access private
2595     * @param string $cell An Excel cell reference
2596     * @return mixed The packed ptgRef3d token on success, PEAR_Error on failure.
2597     */
2598     function _convertRef3d($cell)
2599     {
2600         $class = 2; // as far as I know, this is magick.
2601
2602         // Split the ref at the ! symbol
2603         list($ext_ref, $cell) = preg_split('/!/', $cell);
2604
2605         // Convert the external reference part (different for BIFF8)
2606         if ($this->_BIFF_version == 0x0500) {
2607             $ext_ref = $this->_packExtRef($ext_ref);
2608         } elseif ($this->_BIFF_version == 0x0600) {
2609             $ext_ref = $this->_getRefIndex($ext_ref);
2610         }
2611
2612         // Convert the cell reference part
2613         list($row, $col) = $this->_cellToPackedRowcol($cell);
2614
2615         // The ptg value depends on the class of the ptg.
2616         if ($class == 0) {
2617             $ptgRef = pack("C", $this->ptg['ptgRef3d']);
2618         } elseif ($class == 1) {
2619             $ptgRef = pack("C", $this->ptg['ptgRef3dV']);
2620         } elseif ($class == 2) {
2621             $ptgRef = pack("C", $this->ptg['ptgRef3dA']);
2622         } else {
2623             die("Unknown class $class");
2624         }
2625
2626         return $ptgRef . $ext_ref. $row . $col;
2627     }
2628
2629     /**
2630     * Convert the sheet name part of an external reference, for example "Sheet1" or
2631     * "Sheet1:Sheet2", to a packed structure.
2632     *
2633     * @access private
2634     * @param string $ext_ref The name of the external reference
2635     * @return string The reference index in packed() format
2636     */
2637     function _packExtRef($ext_ref)
2638     {
2639         $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
2640         $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
2641
2642         // Check if there is a sheet range eg., Sheet1:Sheet2.
2643         if (preg_match("/:/", $ext_ref)) {
2644             list($sheet_name1, $sheet_name2) = preg_split('/:/', $ext_ref);
2645
2646             $sheet1 = $this->_getSheetIndex($sheet_name1);
2647             if ($sheet1 == -1) {
2648                 die("Unknown sheet name $sheet_name1 in formula");
2649             }
2650             $sheet2 = $this->_getSheetIndex($sheet_name2);
2651             if ($sheet2 == -1) {
2652                 die("Unknown sheet name $sheet_name2 in formula");
2653             }
2654
2655             // Reverse max and min sheet numbers if necessary
2656             if ($sheet1 > $sheet2) {
2657                 list($sheet1, $sheet2) = array($sheet2, $sheet1);
2658             }
2659         } else { // Single sheet name only.
2660             $sheet1 = $this->_getSheetIndex($ext_ref);
2661             if ($sheet1 == -1) {
2662                 die("Unknown sheet name $ext_ref in formula");
2663             }
2664             $sheet2 = $sheet1;
2665         }
2666
2667         // References are stored relative to 0xFFFF.
2668         $offset = -1 - $sheet1;
2669
2670         return pack('vdvv', $offset, 0x00, $sheet1, $sheet2);
2671     }
2672
2673     /**
2674     * Look up the REF index that corresponds to an external sheet name
2675     * (or range). If it doesn't exist yet add it to the workbook's references
2676     * array. It assumes all sheet names given must exist.
2677     *
2678     * @access private
2679     * @param string $ext_ref The name of the external reference
2680     * @return mixed The reference index in packed() format on success,
2681     *               PEAR_Error on failure
2682     */
2683     function _getRefIndex($ext_ref)
2684     {
2685         $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading  ' if any.
2686         $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
2687
2688         // Check if there is a sheet range eg., Sheet1:Sheet2.
2689         if (preg_match("/:/", $ext_ref)) {
2690             list($sheet_name1, $sheet_name2) = preg_split('/:/', $ext_ref);
2691
2692             $sheet1 = $this->_getSheetIndex($sheet_name1);
2693             if ($sheet1 == -1) {
2694                 die("Unknown sheet name $sheet_name1 in formula");
2695             }
2696             $sheet2 = $this->_getSheetIndex($sheet_name2);
2697             if ($sheet2 == -1) {
2698                 die("Unknown sheet name $sheet_name2 in formula");
2699             }
2700
2701             // Reverse max and min sheet numbers if necessary
2702             if ($sheet1 > $sheet2) {
2703                 list($sheet1, $sheet2) = array($sheet2, $sheet1);
2704             }
2705         } else { // Single sheet name only.
2706             $sheet1 = $this->_getSheetIndex($ext_ref);
2707             if ($sheet1 == -1) {
2708                 die("Unknown sheet name $ext_ref in formula");
2709             }
2710             $sheet2 = $sheet1;
2711         }
2712
2713         // assume all references belong to this document
2714         $supbook_index = 0x00;
2715         $ref = pack('vvv', $supbook_index, $sheet1, $sheet2);
2716         $total_references = count($this->_references);
2717         $index = -1;
2718         for ($i = 0; $i < $total_references; $i++) {
2719             if ($ref == $this->_references[$i]) {
2720                 $index = $i;
2721                 break;
2722             }
2723         }
2724         // if REF was not found add it to references array
2725         if ($index == -1) {
2726             $this->_references[$total_references] = $ref;
2727             $index = $total_references;
2728         }
2729
2730         return pack('v', $index);
2731     }
2732
2733     /**
2734     * Look up the index that corresponds to an external sheet name. The hash of
2735     * sheet names is updated by the addworksheet() method of the
2736     * Spreadsheet_Excel_Writer_Workbook class.
2737     *
2738     * @access private
2739     * @return integer The sheet index, -1 if the sheet was not found
2740     */
2741     function _getSheetIndex($sheet_name)
2742     {
2743         if (!isset($this->_ext_sheets[$sheet_name])) {
2744             return -1;
2745         } else {
2746             return $this->_ext_sheets[$sheet_name];
2747         }
2748     }
2749
2750     /**
2751     * This method is used to update the array of sheet names. It is
2752     * called by the addWorksheet() method of the
2753     * Spreadsheet_Excel_Writer_Workbook class.
2754     *
2755     * @access public
2756     * @see Spreadsheet_Excel_Writer_Workbook::addWorksheet()
2757     * @param string  $name  The name of the worksheet being added
2758     * @param integer $index The index of the worksheet being added
2759     */
2760     function setExtSheet($name, $index)
2761     {
2762         $this->_ext_sheets[$name] = $index;
2763     }
2764
2765     /**
2766     * pack() row and column into the required 3 or 4 byte format.
2767     *
2768     * @access private
2769     * @param string $cell The Excel cell reference to be packed
2770     * @return array Array containing the row and column in packed() format
2771     */
2772     function _cellToPackedRowcol($cell)
2773     {
2774         $cell = strtoupper($cell);
2775         list($row, $col, $row_rel, $col_rel) = $this->_cellToRowcol($cell);
2776         if ($col >= 256) {
2777             die("Column in: $cell greater than 255");
2778         }
2779         // FIXME: change for BIFF8
2780         if ($row >= 16384) {
2781             die("Row in: $cell greater than 16384 ");
2782         }
2783
2784         // Set the high bits to indicate if row or col are relative.
2785         if ($this->_BIFF_version == 0x0500) {
2786             $row    |= $col_rel << 14;
2787             $row    |= $row_rel << 15;
2788             $col     = pack('C', $col);
2789         } elseif ($this->_BIFF_version == 0x0600) {
2790             $col    |= $col_rel << 14;
2791             $col    |= $row_rel << 15;
2792             $col     = pack('v', $col);
2793         }
2794         $row     = pack('v', $row);
2795
2796         return array($row, $col);
2797     }
2798
2799     /**
2800     * pack() row range into the required 3 or 4 byte format.
2801     * Just using maximum col/rows, which is probably not the correct solution
2802     *
2803     * @access private
2804     * @param string $range The Excel range to be packed
2805     * @return array Array containing (row1,col1,row2,col2) in packed() format
2806     */
2807     function _rangeToPackedRange($range)
2808     {
2809         preg_match('/(\$)?(\d+)\:(\$)?(\d+)/', $range, $match);
2810         // return absolute rows if there is a $ in the ref
2811         $row1_rel = empty($match[1]) ? 1 : 0;
2812         $row1     = $match[2];
2813         $row2_rel = empty($match[3]) ? 1 : 0;
2814         $row2     = $match[4];
2815         // Convert 1-index to zero-index
2816         $row1--;
2817         $row2--;
2818         // Trick poor inocent Excel
2819         $col1 = 0;
2820         $col2 = 16383; // FIXME: maximum possible value for Excel 5 (change this!!!)
2821
2822         // FIXME: this changes for BIFF8
2823         if (($row1 >= 16384) or ($row2 >= 16384)) {
2824             die("Row in: $range greater than 16384 ");
2825         }
2826
2827         // Set the high bits to indicate if rows are relative.
2828         if ($this->_BIFF_version == 0x0500) {
2829             $row1    |= $row1_rel << 14; // FIXME: probably a bug
2830             $row2    |= $row2_rel << 15;
2831             $col1     = pack('C', $col1);
2832             $col2     = pack('C', $col2);
2833         } elseif ($this->_BIFF_version == 0x0600) {
2834             $col1    |= $row1_rel << 15;
2835             $col2    |= $row2_rel << 15;
2836             $col1     = pack('v', $col1);
2837             $col2     = pack('v', $col2);
2838         }
2839         $row1     = pack('v', $row1);
2840         $row2     = pack('v', $row2);
2841
2842         return array($row1, $col1, $row2, $col2);
2843     }
2844
2845     /**
2846     * Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
2847     * indexed row and column number. Also returns two (0,1) values to indicate
2848     * whether the row or column are relative references.
2849     *
2850     * @access private
2851     * @param string $cell The Excel cell reference in A1 format.
2852     * @return array
2853     */
2854     function _cellToRowcol($cell)
2855     {
2856         preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/',$cell,$match);
2857         // return absolute column if there is a $ in the ref
2858         $col_rel = empty($match[1]) ? 1 : 0;
2859         $col_ref = $match[2];
2860         $row_rel = empty($match[3]) ? 1 : 0;
2861         $row     = $match[4];
2862
2863         // Convert base26 column string to a number.
2864         $expn   = strlen($col_ref) - 1;
2865         $col    = 0;
2866         $col_ref_length = strlen($col_ref);
2867         for ($i = 0; $i < $col_ref_length; $i++) {
2868             $col += (ord($col_ref{$i}) - ord('A') + 1) * pow(26, $expn);
2869             $expn--;
2870         }
2871
2872         // Convert 1-index to zero-index
2873         $row--;
2874         $col--;
2875
2876         return array($row, $col, $row_rel, $col_rel);
2877     }
2878
2879     /**
2880     * Advance to the next valid token.
2881     *
2882     * @access private
2883     */
2884     function _advance()
2885     {
2886         $i = $this->_current_char;
2887         $formula_length = strlen($this->_formula);
2888         // eat up white spaces
2889         if ($i < $formula_length) {
2890             while ($this->_formula{$i} == " ") {
2891                 $i++;
2892             }
2893
2894             if ($i < ($formula_length - 1)) {
2895                 $this->_lookahead = $this->_formula{$i+1};
2896             }
2897             $token = '';
2898         }
2899
2900         while ($i < $formula_length) {
2901             $token .= $this->_formula{$i};
2902             if ($i < ($formula_length - 1)) {
2903                 $this->_lookahead = $this->_formula{$i+1};
2904             } else {
2905                 $this->_lookahead = '';
2906             }
2907
2908             if ($this->_match($token) != '') {
2909                 //if ($i < strlen($this->_formula) - 1) {
2910                 //    $this->_lookahead = $this->_formula{$i+1};
2911                 //}
2912                 $this->_current_char = $i + 1;
2913                 $this->_current_token = $token;
2914                 return 1;
2915             }
2916
2917             if ($i < ($formula_length - 2)) {
2918                 $this->_lookahead = $this->_formula{$i+2};
2919             } else { // if we run out of characters _lookahead becomes empty
2920                 $this->_lookahead = '';
2921             }
2922             $i++;
2923         }
2924         //die("Lexical error ".$this->_current_char);
2925     }
2926
2927     /**
2928     * Checks if it's a valid token.
2929     *
2930     * @access private
2931     * @param mixed $token The token to check.
2932     * @return mixed       The checked token or false on failure
2933     */
2934     function _match($token)
2935     {
2936         switch($token) {
2937             case SPREADSHEET_EXCEL_WRITER_ADD:
2938                 return $token;
2939                 break;
2940             case SPREADSHEET_EXCEL_WRITER_SUB:
2941                 return $token;
2942                 break;
2943             case SPREADSHEET_EXCEL_WRITER_MUL:
2944                 return $token;
2945                 break;
2946             case SPREADSHEET_EXCEL_WRITER_DIV:
2947                 return $token;
2948                 break;
2949             case SPREADSHEET_EXCEL_WRITER_OPEN:
2950                 return $token;
2951                 break;
2952             case SPREADSHEET_EXCEL_WRITER_CLOSE:
2953                 return $token;
2954                 break;
2955             case SPREADSHEET_EXCEL_WRITER_COMA:
2956                 return $token;
2957                 break;
2958             case SPREADSHEET_EXCEL_WRITER_SEMICOLON:
2959                 return $token;
2960                 break;
2961             case SPREADSHEET_EXCEL_WRITER_GT:
2962                 if ($this->_lookahead == '=') { // it's a GE token
2963                     break;
2964                 }
2965                 return $token;
2966                 break;
2967             case SPREADSHEET_EXCEL_WRITER_LT:
2968                 // it's a LE or a NE token
2969                 if (($this->_lookahead == '=') or ($this->_lookahead == '>')) {
2970                     break;
2971                 }
2972                 return $token;
2973                 break;
2974             case SPREADSHEET_EXCEL_WRITER_GE:
2975                 return $token;
2976                 break;
2977             case SPREADSHEET_EXCEL_WRITER_LE:
2978                 return $token;
2979                 break;
2980             case SPREADSHEET_EXCEL_WRITER_EQ:
2981                 return $token;
2982                 break;
2983             case SPREADSHEET_EXCEL_WRITER_NE:
2984                 return $token;
2985                 break;
2986             default:
2987                 // if it's a reference
2988                 if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$token) and
2989                    !preg_match("/[0-9]/",$this->_lookahead) and 
2990                    ($this->_lookahead != ':') and ($this->_lookahead != '.') and
2991                    ($this->_lookahead != '!'))
2992                 {
2993                     return $token;
2994                 }
2995                 // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
2996                 elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z][0-9]+$/u",$token) and
2997                        !preg_match("/[0-9]/",$this->_lookahead) and
2998                        ($this->_lookahead != ':') and ($this->_lookahead != '.'))
2999                 {
3000                     return $token;
3001                 }
3002                 // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1)
3003                 elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\![A-Ia-i]?[A-Za-z][0-9]+$/u",$token) and
3004                        !preg_match("/[0-9]/",$this->_lookahead) and
3005                        ($this->_lookahead != ':') and ($this->_lookahead != '.'))
3006                 {
3007                     return $token;
3008                 }
3009                 // if it's a range (A1:A2)
3010                 elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$token) and 
3011                        !preg_match("/[0-9]/",$this->_lookahead))
3012                 {
3013                     return $token;
3014                 }
3015                 // if it's a range (A1..A2)
3016                 elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$token) and 
3017                        !preg_match("/[0-9]/",$this->_lookahead))
3018                 {
3019                     return $token;
3020                 }
3021                 // If it's an external range like Sheet1!A1 or Sheet1:Sheet2!A1:B2
3022                 elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/u",$token) and
3023                        !preg_match("/[0-9]/",$this->_lookahead))
3024                 {
3025                     return $token;
3026                 }
3027                 // If it's an external range like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1:B2
3028                 elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/u",$token) and
3029                        !preg_match("/[0-9]/",$this->_lookahead))
3030                 {
3031                     return $token;
3032                 }
3033                 // If it's a number (check that it's not a sheet name or range)
3034                 elseif (is_numeric($token) and 
3035                         (!is_numeric($token.$this->_lookahead) or ($this->_lookahead == '')) and
3036                         ($this->_lookahead != '!') and ($this->_lookahead != ':'))
3037                 {
3038                     return $token;
3039                 }
3040                 // If it's a string (of maximum 255 characters)
3041                 elseif (preg_match("/^\"[^\"]{0,255}\"$/",$token))
3042                 {
3043                     return $token;
3044                 }
3045                 // if it's a function call
3046                 elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i",$token) and ($this->_lookahead == "("))
3047                 {
3048                     return $token;
3049                 }
3050                 return '';
3051         }
3052     }
3053
3054     /**
3055     * The parsing method. It parses a formula.
3056     *
3057     * @access public
3058     * @param string $formula The formula to parse, without the initial equal
3059     *                        sign (=).
3060     * @return mixed true on success, PEAR_Error on failure
3061     */
3062     function parse($formula)
3063     {
3064         $this->_current_char = 0;
3065         $this->_formula      = $formula;
3066         $this->_lookahead    = $formula{1};
3067         $this->_advance();
3068         $this->_parse_tree   = $this->_condition();
3069     }
3070
3071     /**
3072     * It parses a condition. It assumes the following rule:
3073     * Cond -> Expr [(">" | "<") Expr]
3074     *
3075     * @access private
3076     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
3077     */
3078     function _condition()
3079     {
3080         $result = $this->_expression();
3081         if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LT) {
3082             $this->_advance();
3083             $result2 = $this->_expression();
3084             $result = $this->_createTree('ptgLT', $result, $result2);
3085         } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GT) {
3086             $this->_advance();
3087             $result2 = $this->_expression();
3088             $result = $this->_createTree('ptgGT', $result, $result2);
3089         } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LE) {
3090             $this->_advance();
3091             $result2 = $this->_expression();
3092             $result = $this->_createTree('ptgLE', $result, $result2);
3093         } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GE) {
3094             $this->_advance();
3095             $result2 = $this->_expression();
3096             $result = $this->_createTree('ptgGE', $result, $result2);
3097         } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_EQ) {
3098             $this->_advance();
3099             $result2 = $this->_expression();
3100             $result = $this->_createTree('ptgEQ', $result, $result2);
3101         } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_NE) {
3102             $this->_advance();
3103             $result2 = $this->_expression();
3104             $result = $this->_createTree('ptgNE', $result, $result2);
3105         }
3106         return $result;
3107     }
3108
3109     /**
3110     * It parses a expression. It assumes the following rule:
3111     * Expr -> Term [("+" | "-") Term]
3112     *      -> "string"
3113     *      -> "-" Term
3114     *
3115     * @access private
3116     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
3117     */
3118     function _expression()
3119     {
3120         // If it's a string return a string node
3121         if (ereg("^\"[^\"]{0,255}\"$", $this->_current_token)) {
3122             $result = $this->_createTree($this->_current_token, '', '');
3123             $this->_advance();
3124             return $result;
3125         } elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB) {
3126             // catch "-" Term
3127             $this->_advance();
3128             $result2 = $this->_expression();
3129             $result = $this->_createTree('ptgUminus', $result2, '');
3130             return $result;
3131         }
3132         $result = $this->_term();
3133         while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD) or
3134                ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB)) {
3135         /**/
3136             if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD) {
3137                 $this->_advance();
3138                 $result2 = $this->_term();
3139                 $result = $this->_createTree('ptgAdd', $result, $result2);
3140             } else {
3141                 $this->_advance();
3142                 $result2 = $this->_term();
3143                 $result = $this->_createTree('ptgSub', $result, $result2);
3144             }
3145         }
3146         return $result;
3147     }
3148
3149     /**
3150     * This function just introduces a ptgParen element in the tree, so that Excel
3151     * doesn't get confused when working with a parenthesized formula afterwards.
3152     *
3153     * @access private
3154     * @see _fact()
3155     * @return array The parsed ptg'd tree
3156     */
3157     function _parenthesizedExpression()
3158     {
3159         $result = $this->_createTree('ptgParen', $this->_expression(), '');
3160         return $result;
3161     }
3162
3163     /**
3164     * It parses a term. It assumes the following rule:
3165     * Term -> Fact [("*" | "/") Fact]
3166     *
3167     * @access private
3168     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
3169     */
3170     function _term()
3171     {
3172         $result = $this->_fact();
3173         while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL) or
3174                ($this->_current_token == SPREADSHEET_EXCEL_WRITER_DIV)) {
3175         /**/
3176             if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL) {
3177                 $this->_advance();
3178                 $result2 = $this->_fact();
3179                 $result = $this->_createTree('ptgMul', $result, $result2);
3180             } else {
3181                 $this->_advance();
3182                 $result2 = $this->_fact();
3183                 $result = $this->_createTree('ptgDiv', $result, $result2);
3184             }
3185         }
3186         return $result;
3187     }
3188
3189     /**
3190     * It parses a factor. It assumes the following rule:
3191     * Fact -> ( Expr )
3192     *       | CellRef
3193     *       | CellRange
3194     *       | Number
3195     *       | Function
3196     *
3197     * @access private
3198     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
3199     */
3200     function _fact()
3201     {
3202         if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_OPEN) {
3203             $this->_advance();         // eat the "("
3204             $result = $this->_parenthesizedExpression();
3205             if ($this->_current_token != SPREADSHEET_EXCEL_WRITER_CLOSE) {
3206                 die("')' token expected.");
3207             }
3208             $this->_advance();         // eat the ")"
3209             return $result;
3210         }
3211         // if it's a reference
3212         if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$this->_current_token))
3213         {
3214             $result = $this->_createTree($this->_current_token, '', '');
3215             $this->_advance();
3216             return $result;
3217         }
3218         // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
3219         elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z][0-9]+$/u",$this->_current_token))
3220         {
3221             $result = $this->_createTree($this->_current_token, '', '');
3222             $this->_advance();
3223             return $result;
3224         }
3225         // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1)
3226         elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\![A-Ia-i]?[A-Za-z][0-9]+$/u",$this->_current_token))
3227         {
3228             $result = $this->_createTree($this->_current_token, '', '');
3229             $this->_advance();
3230             return $result;
3231         }
3232         // if it's a range
3233         elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token) or 
3234                 preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token))
3235         {
3236             $result = $this->_current_token;
3237             $this->_advance();
3238             return $result;
3239         }
3240         // If it's an external range (Sheet1!A1 or Sheet1!A1:B2)
3241         elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/u",$this->_current_token))
3242         {
3243             $result = $this->_current_token;
3244             $this->_advance();
3245             return $result;
3246         }
3247         // If it's an external range ('Sheet1'!A1 or 'Sheet1'!A1:B2)
3248         elseif (preg_match("/^'[\w -]+(\:[\w -]+)?'\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/u",$this->_current_token))
3249         {
3250             $result = $this->_current_token;
3251             $this->_advance();
3252             return $result;
3253         }
3254         elseif (is_numeric($this->_current_token))
3255         {
3256             $result = $this->_createTree($this->_current_token, '', '');
3257             $this->_advance();
3258             return $result;
3259         }
3260         // if it's a function call
3261         elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i",$this->_current_token))
3262         {
3263             $result = $this->_func();
3264             return $result;
3265         }
3266         die("Syntax error: ".$this->_current_token.
3267                                  ", lookahead: ".$this->_lookahead.
3268                                  ", current char: ".$this->_current_char);
3269     }
3270
3271     /**
3272     * It parses a function call. It assumes the following rule:
3273     * Func -> ( Expr [,Expr]* )
3274     *
3275     * @access private
3276     * @return mixed The parsed ptg'd tree on success, PEAR_Error on failure
3277     */
3278     function _func()
3279     {
3280         $num_args = 0; // number of arguments received
3281         $function = strtoupper($this->_current_token);
3282         $result   = ''; // initialize result
3283         $this->_advance();
3284         $this->_advance();         // eat the "("
3285         while ($this->_current_token != ')') {
3286         /**/
3287             if ($num_args > 0) {
3288                 if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_COMA or
3289                     $this->_current_token == SPREADSHEET_EXCEL_WRITER_SEMICOLON)
3290                 {
3291                     $this->_advance();  // eat the "," or ";"
3292                 } else {
3293                     die("Syntax error: comma expected in ".
3294                                       "function $function, arg #{$num_args}");
3295                 }
3296                 $result2 = $this->_condition();
3297                 $result = $this->_createTree('arg', $result, $result2);
3298             } else { // first argument
3299                 $result2 = $this->_condition();
3300                 $result = $this->_createTree('arg', '', $result2);
3301             }
3302             $num_args++;
3303         }
3304         if (!isset($this->_functions[$function])) {
3305             die("Function $function() doesn't exist");
3306         }
3307         $args = $this->_functions[$function][1];
3308         // If fixed number of args eg. TIME($i,$j,$k). Check that the number of args is valid.
3309         if (($args >= 0) and ($args != $num_args)) {
3310             die("Incorrect number of arguments in function $function() ");
3311         }
3312
3313         $result = $this->_createTree($function, $result, $num_args);
3314         $this->_advance();         // eat the ")"
3315         return $result;
3316     }
3317
3318     /**
3319     * Creates a tree. In fact an array which may have one or two arrays (sub-trees)
3320     * as elements.
3321     *
3322     * @access private
3323     * @param mixed $value The value of this node.
3324     * @param mixed $left  The left array (sub-tree) or a final node.
3325     * @param mixed $right The right array (sub-tree) or a final node.
3326     * @return array A tree
3327     */
3328     function _createTree($value, $left, $right)
3329     {
3330         return array('value' => $value, 'left' => $left, 'right' => $right);
3331     }
3332
3333     /**
3334     * Builds a string containing the tree in reverse polish notation (What you
3335     * would use in a HP calculator stack).
3336     * The following tree:
3337     *
3338     *    +
3339     *   / \
3340     *  2   3
3341     *
3342     * produces: "23+"
3343     *
3344     * The following tree:
3345     *
3346     *    +
3347     *   / \
3348     *  3   *
3349     *     / \
3350     *    6   A1
3351     *
3352     * produces: "36A1*+"
3353     *
3354     * In fact all operands, functions, references, etc... are written as ptg's
3355     *
3356     * @access public
3357     * @param array $tree The optional tree to convert.
3358     * @return string The tree in reverse polish notation
3359     */
3360     function toReversePolish($tree = array())
3361     {
3362         $polish = ""; // the string we are going to return
3363         if (empty($tree)) { // If it's the first call use _parse_tree
3364             $tree = $this->_parse_tree;
3365         }
3366         if (is_array($tree['left'])) {
3367             $converted_tree = $this->toReversePolish($tree['left']);
3368             $polish .= $converted_tree;
3369         } elseif ($tree['left'] != '') { // It's a final node
3370             $converted_tree = $this->_convert($tree['left']);
3371             $polish .= $converted_tree;
3372         }
3373         if (is_array($tree['right'])) {
3374             $converted_tree = $this->toReversePolish($tree['right']);
3375             $polish .= $converted_tree;
3376         } elseif ($tree['right'] != '') { // It's a final node
3377             $converted_tree = $this->_convert($tree['right']);
3378             $polish .= $converted_tree;
3379         }
3380         // if it's a function convert it here (so we can set it's arguments)
3381         if (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/",$tree['value']) and
3382             !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/',$tree['value']) and
3383             !preg_match("/^[A-Ia-i]?[A-Za-z](\d+)\.\.[A-Ia-i]?[A-Za-z](\d+)$/",$tree['value']) and
3384             !is_numeric($tree['value']) and
3385             !isset($this->ptg[$tree['value']]))
3386         {
3387             // left subtree for a function is always an array.
3388             if ($tree['left'] != '') {
3389                 $left_tree = $this->toReversePolish($tree['left']);
3390             } else {
3391                 $left_tree = '';
3392             }
3393             // add it's left subtree and return.
3394             return $left_tree.$this->_convertFunction($tree['value'], $tree['right']);
3395         } else {
3396             $converted_tree = $this->_convert($tree['value']);
3397         }
3398         $polish .= $converted_tree;
3399         return $polish;
3400     }
3401 }
3402
3403 /**
3404 * Class for generating Excel Spreadsheets
3405 *
3406 * @author   Xavier Noguer <xnoguer@rezebra.com>
3407 * @category FileFormats
3408 * @package  Spreadsheet_Excel_Writer
3409 */
3410
3411 class Spreadsheet_Excel_Writer_Worksheet extends Spreadsheet_Excel_Writer_BIFFwriter
3412 {
3413     /**
3414     * Name of the Worksheet
3415     * @var string
3416     */
3417     var $name;
3418
3419     /**
3420     * Index for the Worksheet
3421     * @var integer
3422     */
3423     var $index;
3424
3425     /**
3426     * Reference to the (default) Format object for URLs
3427     * @var object Format
3428     */
3429     var $_url_format;
3430
3431     /**
3432     * Reference to the parser used for parsing formulas
3433     * @var object Format
3434     */
3435     var $_parser;
3436
3437     /**
3438     * Filehandle to the temporary file for storing data
3439     * @var resource
3440     */
3441     var $_filehandle;
3442
3443     /**
3444     * Boolean indicating if we are using a temporary file for storing data
3445     * @var bool
3446     */
3447     var $_using_tmpfile;
3448
3449     /**
3450     * Maximum number of rows for an Excel spreadsheet (BIFF5)
3451     * @var integer
3452     */
3453     var $_xls_rowmax;
3454
3455     /**
3456     * Maximum number of columns for an Excel spreadsheet (BIFF5)
3457     * @var integer
3458     */
3459     var $_xls_colmax;
3460
3461     /**
3462     * Maximum number of characters for a string (LABEL record in BIFF5)
3463     * @var integer
3464     */
3465     var $_xls_strmax;
3466
3467     /**
3468     * First row for the DIMENSIONS record
3469     * @var integer
3470     * @see _storeDimensions()
3471     */
3472     var $_dim_rowmin;
3473
3474     /**
3475     * Last row for the DIMENSIONS record
3476     * @var integer
3477     * @see _storeDimensions()
3478     */
3479     var $_dim_rowmax;
3480
3481     /**
3482     * First column for the DIMENSIONS record
3483     * @var integer
3484     * @see _storeDimensions()
3485     */
3486     var $_dim_colmin;
3487
3488     /**
3489     * Last column for the DIMENSIONS record
3490     * @var integer
3491     * @see _storeDimensions()
3492     */
3493     var $_dim_colmax;
3494
3495     /**
3496     * Array containing format information for columns
3497     * @var array
3498     */
3499     var $_colinfo;
3500
3501     /**
3502     * Array containing the selected area for the worksheet
3503     * @var array
3504     */
3505     var $_selection;
3506
3507     /**
3508     * Array containing the panes for the worksheet
3509     * @var array
3510     */
3511     var $_panes;
3512
3513     /**
3514     * The active pane for the worksheet
3515     * @var integer
3516     */
3517     var $_active_pane;
3518
3519     /**
3520     * Bit specifying if panes are frozen
3521     * @var integer
3522     */
3523     var $_frozen;
3524
3525     /**
3526     * Bit specifying if the worksheet is selected
3527     * @var integer
3528     */
3529     var $selected;
3530
3531     /**
3532     * The paper size (for printing) (DOCUMENT!!!)
3533     * @var integer
3534     */
3535     var $_paper_size;
3536
3537     /**
3538     * Bit specifying paper orientation (for printing). 0 => landscape, 1 => portrait
3539     * @var integer
3540     */
3541     var $_orientation;
3542
3543     /**
3544     * The page header caption
3545     * @var string
3546     */
3547     var $_header;
3548
3549     /**
3550     * The page footer caption
3551     * @var string
3552     */
3553     var $_footer;
3554
3555     /**
3556     * The horizontal centering value for the page
3557     * @var integer
3558     */
3559     var $_hcenter;
3560
3561     /**
3562     * The vertical centering value for the page
3563     * @var integer
3564     */
3565     var $_vcenter;
3566
3567     /**
3568     * The margin for the header
3569     * @var float
3570     */
3571     var $_margin_head;
3572
3573     /**
3574     * The margin for the footer
3575     * @var float
3576     */
3577     var $_margin_foot;
3578
3579     /**
3580     * The left margin for the worksheet in inches
3581     * @var float
3582     */
3583     var $_margin_left;
3584
3585     /**
3586     * The right margin for the worksheet in inches
3587     * @var float
3588     */
3589     var $_margin_right;
3590
3591     /**
3592     * The top margin for the worksheet in inches
3593     * @var float
3594     */
3595     var $_margin_top;
3596
3597     /**
3598     * The bottom margin for the worksheet in inches
3599     * @var float
3600     */
3601     var $_margin_bottom;
3602
3603     /**
3604     * First row to reapeat on each printed page
3605     * @var integer
3606     */
3607     var $title_rowmin;
3608
3609     /**
3610     * Last row to reapeat on each printed page
3611     * @var integer
3612     */
3613     var $title_rowmax;
3614
3615     /**
3616     * First column to reapeat on each printed page
3617     * @var integer
3618     */
3619     var $title_colmin;
3620
3621     /**
3622     * First row of the area to print
3623     * @var integer
3624     */
3625     var $print_rowmin;
3626
3627     /**
3628     * Last row to of the area to print
3629     * @var integer
3630     */
3631     var $print_rowmax;
3632
3633     /**
3634     * First column of the area to print
3635     * @var integer
3636     */
3637     var $print_colmin;
3638
3639     /**
3640     * Last column of the area to print
3641     * @var integer
3642     */
3643     var $print_colmax;
3644
3645     /**
3646     * Whether to use outline.
3647     * @var integer
3648     */
3649     var $_outline_on;
3650
3651     /**
3652     * Auto outline styles.
3653     * @var bool
3654     */
3655     var $_outline_style;
3656
3657     /**
3658     * Whether to have outline summary below.
3659     * @var bool
3660     */
3661     var $_outline_below;
3662
3663     /**
3664     * Whether to have outline summary at the right.
3665     * @var bool
3666     */
3667     var $_outline_right;
3668
3669     /**
3670     * Outline row level.
3671     * @var integer
3672     */
3673     var $_outline_row_level;
3674
3675     /**
3676     * Whether to fit to page when printing or not.
3677     * @var bool
3678     */
3679     var $_fit_page;
3680
3681     /**
3682     * Number of pages to fit wide
3683     * @var integer
3684     */
3685     var $_fit_width;
3686
3687     /**
3688     * Number of pages to fit high
3689     * @var integer
3690     */
3691     var $_fit_height;
3692
3693     /**
3694     * Reference to the total number of strings in the workbook
3695     * @var integer
3696     */
3697     var $_str_total;
3698
3699     /**
3700     * Reference to the number of unique strings in the workbook
3701     * @var integer
3702     */
3703     var $_str_unique;
3704
3705     /**
3706     * Reference to the array containing all the unique strings in the workbook
3707     * @var array
3708     */
3709     var $_str_table;
3710
3711     /**
3712     * Merged cell ranges
3713     * @var array
3714     */
3715     var $_merged_ranges;
3716
3717     /**
3718     * Charset encoding currently used when calling writeString()
3719     * @var string
3720     */
3721     var $_input_encoding;
3722
3723     /**
3724     * Constructor
3725     *
3726     * @param string  $name         The name of the new worksheet
3727     * @param integer $index        The index of the new worksheet
3728     * @param mixed   &$activesheet The current activesheet of the workbook we belong to
3729     * @param mixed   &$firstsheet  The first worksheet in the workbook we belong to
3730     * @param mixed   &$url_format  The default format for hyperlinks
3731     * @param mixed   &$parser      The formula parser created for the Workbook
3732     * @access private
3733     */
3734     function Spreadsheet_Excel_Writer_Worksheet($BIFF_version, $name,
3735                                                 $index, &$activesheet,
3736                                                 &$firstsheet, &$str_total,
3737                                                 &$str_unique, &$str_table,
3738                                                 &$url_format, &$parser)
3739     {
3740         // It needs to call its parent's constructor explicitly
3741         $this->Spreadsheet_Excel_Writer_BIFFwriter();
3742         $this->_BIFF_version   = $BIFF_version;
3743         $rowmax                = 65536; // 16384 in Excel 5
3744         $colmax                = 256;
3745
3746         $this->name            = $name;
3747         $this->index           = $index;
3748         $this->activesheet     = &$activesheet;
3749         $this->firstsheet      = &$firstsheet;
3750         $this->_str_total      = &$str_total;
3751         $this->_str_unique     = &$str_unique;
3752         $this->_str_table      = &$str_table;
3753         $this->_url_format     = &$url_format;
3754         $this->_parser         = &$parser;
3755
3756         //$this->ext_sheets      = array();
3757         $this->_filehandle     = '';
3758         $this->_using_tmpfile  = true;
3759         //$this->fileclosed      = 0;
3760         //$this->offset          = 0;
3761         $this->_xls_rowmax     = $rowmax;
3762         $this->_xls_colmax     = $colmax;
3763         $this->_xls_strmax     = 255;
3764         $this->_dim_rowmin     = $rowmax + 1;
3765         $this->_dim_rowmax     = 0;
3766         $this->_dim_colmin     = $colmax + 1;
3767         $this->_dim_colmax     = 0;
3768         $this->_colinfo        = array();
3769         $this->_selection      = array(0,0,0,0);
3770         $this->_panes          = array();
3771         $this->_active_pane    = 3;
3772         $this->_frozen         = 0;
3773         $this->selected        = 0;
3774
3775         $this->_paper_size      = 0x0;
3776         $this->_orientation     = 0x1;
3777         $this->_header          = '';
3778         $this->_footer          = '';
3779         $this->_hcenter         = 0;
3780         $this->_vcenter         = 0;
3781         $this->_margin_head     = 0.50;
3782         $this->_margin_foot     = 0.50;
3783         $this->_margin_left     = 0.75;
3784         $this->_margin_right    = 0.75;
3785         $this->_margin_top      = 1.00;
3786         $this->_margin_bottom   = 1.00;
3787
3788         $this->title_rowmin     = null;
3789         $this->title_rowmax     = null;
3790         $this->title_colmin     = null;
3791         $this->title_colmax     = null;
3792         $this->print_rowmin     = null;
3793         $this->print_rowmax     = null;
3794         $this->print_colmin     = null;
3795         $this->print_colmax     = null;
3796
3797         $this->_print_gridlines  = 1;
3798         $this->_screen_gridlines = 1;
3799         $this->_print_headers    = 0;
3800
3801         $this->_fit_page        = 0;
3802         $this->_fit_width       = 0;
3803         $this->_fit_height      = 0;
3804
3805         $this->_hbreaks         = array();
3806         $this->_vbreaks         = array();
3807
3808         $this->_protect         = 0;
3809         $this->_password        = null;
3810
3811         $this->col_sizes        = array();
3812         $this->_row_sizes        = array();
3813
3814         $this->_zoom            = 100;
3815         $this->_print_scale     = 100;
3816
3817         $this->_outline_row_level = 0;
3818         $this->_outline_style     = 0;
3819         $this->_outline_below     = 1;
3820         $this->_outline_right     = 1;
3821         $this->_outline_on        = 1;
3822
3823         $this->_merged_ranges     = array();
3824         
3825                 $this->_rtl                               = 0;  // Added by Joe Hunt 2009-03-05 for arabic languages
3826         $this->_input_encoding    = '';
3827
3828         $this->_dv                = array();
3829
3830         $this->_initialize();
3831     }
3832
3833     /**
3834     * Open a tmp file to store the majority of the Worksheet data. If this fails,
3835     * for example due to write permissions, store the data in memory. This can be
3836     * slow for large files.
3837     *
3838     * @access private
3839     */
3840     function _initialize()
3841     {
3842         // Open tmp file for storing Worksheet data
3843         $fh = tmpfile();
3844         if ($fh) {
3845             // Store filehandle
3846             $this->_filehandle = $fh;
3847         } else {
3848             // If tmpfile() fails store data in memory
3849             $this->_using_tmpfile = false;
3850         }
3851     }
3852
3853     /**
3854     * Add data to the beginning of the workbook (note the reverse order)
3855     * and to the end of the workbook.
3856     *
3857     * @access public
3858     * @see Spreadsheet_Excel_Writer_Workbook::storeWorkbook()
3859     * @param array $sheetnames The array of sheetnames from the Workbook this
3860     *                          worksheet belongs to
3861     */
3862     function close($sheetnames)
3863     {
3864         $num_sheets = count($sheetnames);
3865
3866         /***********************************************
3867         * Prepend in reverse order!!
3868         */
3869
3870         // Prepend the sheet dimensions
3871         $this->_storeDimensions();
3872
3873         // Prepend the sheet password
3874         $this->_storePassword();
3875
3876         // Prepend the sheet protection
3877         $this->_storeProtect();
3878
3879         // Prepend the page setup
3880         $this->_storeSetup();
3881
3882         /* FIXME: margins are actually appended */
3883         // Prepend the bottom margin
3884         $this->_storeMarginBottom();
3885
3886         // Prepend the top margin
3887         $this->_storeMarginTop();
3888
3889         // Prepend the right margin
3890         $this->_storeMarginRight();
3891
3892         // Prepend the left margin
3893         $this->_storeMarginLeft();
3894
3895         // Prepend the page vertical centering
3896         $this->_storeVcenter();
3897
3898         // Prepend the page horizontal centering
3899         $this->_storeHcenter();
3900
3901         // Prepend the page footer
3902         $this->_storeFooter();
3903
3904         // Prepend the page header
3905         $this->_storeHeader();
3906
3907         // Prepend the vertical page breaks
3908         $this->_storeVbreak();
3909
3910         // Prepend the horizontal page breaks
3911         $this->_storeHbreak();
3912
3913         // Prepend WSBOOL
3914         $this->_storeWsbool();
3915
3916         // Prepend GRIDSET
3917         $this->_storeGridset();
3918
3919          //  Prepend GUTS
3920         if ($this->_BIFF_version == 0x0500) {
3921             $this->_storeGuts();
3922         }
3923
3924         // Prepend PRINTGRIDLINES
3925         $this->_storePrintGridlines();
3926
3927         // Prepend PRINTHEADERS
3928         $this->_storePrintHeaders();
3929
3930         // Prepend EXTERNSHEET references
3931         if ($this->_BIFF_version == 0x0500) {
3932             for ($i = $num_sheets; $i > 0; $i--) {
3933                 $sheetname = $sheetnames[$i-1];
3934                 $this->_storeExternsheet($sheetname);
3935             }
3936         }
3937
3938         // Prepend the EXTERNCOUNT of external references.
3939         if ($this->_BIFF_version == 0x0500) {
3940             $this->_storeExterncount($num_sheets);
3941         }
3942
3943         // Prepend the COLINFO records if they exist
3944         if (!empty($this->_colinfo)) {
3945             $colcount = count($this->_colinfo);
3946             for ($i = 0; $i < $colcount; $i++) {
3947                 $this->_storeColinfo($this->_colinfo[$i]);
3948             }
3949             $this->_storeDefcol();
3950         }
3951
3952         // Prepend the BOF record
3953         $this->_storeBof(0x0010);
3954
3955         /*
3956         * End of prepend. Read upwards from here.
3957         ***********************************************/
3958
3959         // Append
3960         $this->_storeWindow2();
3961         $this->_storeZoom();
3962         if (!empty($this->_panes)) {
3963             $this->_storePanes($this->_panes);
3964         }
3965         $this->_storeSelection($this->_selection);
3966         $this->_storeMergedCells();
3967         /* TODO: add data validity */
3968         /*if ($this->_BIFF_version == 0x0600) {
3969             $this->_storeDataValidity();
3970         }*/
3971         $this->_storeEof();
3972     }
3973
3974     /**
3975     * Retrieve the worksheet name.
3976     * This is usefull when creating worksheets without a name.
3977     *
3978     * @access public
3979     * @return string The worksheet's name
3980     */
3981     function getName()
3982     {
3983         return $this->name;
3984     }
3985
3986     /**
3987     * Retrieves data from memory in one chunk, or from disk in $buffer
3988     * sized chunks.
3989     *
3990     * @return string The data
3991     */
3992     function getData()
3993     {
3994         $buffer = 4096;
3995
3996         // Return data stored in memory
3997         if (isset($this->_data)) {
3998             $tmp   = $this->_data;
3999             unset($this->_data);
4000             $fh    = $this->_filehandle;
4001             if ($this->_using_tmpfile) {
4002                 fseek($fh, 0);
4003             }
4004             return $tmp;
4005         }
4006         // Return data stored on disk
4007         if ($this->_using_tmpfile) {
4008             if ($tmp = fread($this->_filehandle, $buffer)) {
4009                 return $tmp;
4010             }
4011         }
4012
4013         // No data to return
4014         return '';
4015     }
4016
4017     /**
4018     * Sets a merged cell range
4019     *
4020     * @access public
4021     * @param integer $first_row First row of the area to merge
4022     * @param integer $first_col First column of the area to merge
4023     * @param integer $last_row  Last row of the area to merge
4024     * @param integer $last_col  Last column of the area to merge
4025     */
4026     function setMerge($first_row, $first_col, $last_row, $last_col)
4027     {
4028         if (($last_row < $first_row) || ($last_col < $first_col)) {
4029             return;
4030         }
4031         // don't check rowmin, rowmax, etc... because we don't know when this
4032         // is going to be called
4033         $this->_merged_ranges[] = array($first_row, $first_col, $last_row, $last_col);
4034     }
4035
4036     /**
4037     * Set this worksheet as a selected worksheet,
4038     * i.e. the worksheet has its tab highlighted.
4039     *
4040     * @access public
4041     */
4042     function select()
4043     {
4044         $this->selected = 1;
4045     }
4046
4047     /**
4048     * Set this worksheet as the active worksheet,
4049     * i.e. the worksheet that is displayed when the workbook is opened.
4050     * Also set it as selected.
4051     *
4052     * @access public
4053     */
4054     function activate()
4055     {
4056         $this->selected = 1;
4057         $this->activesheet = $this->index;
4058     }
4059
4060     /**
4061     * Set this worksheet as the first visible sheet.
4062     * This is necessary when there are a large number of worksheets and the
4063     * activated worksheet is not visible on the screen.
4064     *
4065     * @access public
4066     */
4067     function setFirstSheet()
4068     {
4069         $this->firstsheet = $this->index;
4070     }
4071
4072     /**
4073     * Set the worksheet protection flag
4074     * to prevent accidental modification and to
4075     * hide formulas if the locked and hidden format properties have been set.
4076     *
4077     * @access public
4078     * @param string $password The password to use for protecting the sheet.
4079     */
4080     function protect($password)
4081     {
4082         $this->_protect   = 1;
4083         $this->_password  = $this->_encodePassword($password);
4084     }
4085
4086     /**
4087     * Set the width of a single column or a range of columns.
4088     *
4089     * @access public
4090     * @param integer $firstcol first column on the range
4091     * @param integer $lastcol  last column on the range
4092     * @param integer $width    width to set
4093     * @param mixed   $format   The optional XF format to apply to the columns
4094     * @param integer $hidden   The optional hidden atribute
4095     * @param integer $level    The optional outline level
4096     */
4097     function setColumn($firstcol, $lastcol, $width, $format = null, $hidden = 0, $level = 0)
4098     {
4099         $this->_colinfo[] = array($firstcol, $lastcol, $width, &$format, $hidden, $level);
4100
4101         // Set width to zero if column is hidden
4102         $width = ($hidden) ? 0 : $width;
4103
4104         for ($col = $firstcol; $col <= $lastcol; $col++) {
4105             $this->col_sizes[$col] = $width;
4106         }
4107     }
4108
4109     /**
4110     * Set which cell or cells are selected in a worksheet
4111     *
4112     * @access public
4113     * @param integer $first_row    first row in the selected quadrant
4114     * @param integer $first_column first column in the selected quadrant
4115     * @param integer $last_row     last row in the selected quadrant
4116     * @param integer $last_column  last column in the selected quadrant
4117     */
4118     function setSelection($first_row,$first_column,$last_row,$last_column)
4119     {
4120         $this->_selection = array($first_row,$first_column,$last_row,$last_column);
4121     }
4122
4123     /**
4124     * Set panes and mark them as frozen.
4125     *
4126     * @access public
4127     * @param array $panes This is the only parameter received and is composed of the following:
4128     *                     0 => Vertical split position,
4129     *                     1 => Horizontal split position
4130     *                     2 => Top row visible
4131     *                     3 => Leftmost column visible
4132     *                     4 => Active pane
4133     */
4134     function freezePanes($panes)
4135     {
4136         $this->_frozen = 1;
4137         $this->_panes  = $panes;
4138     }
4139
4140     /**
4141     * Set panes and mark them as unfrozen.
4142     *
4143     * @access public
4144     * @param array $panes This is the only parameter received and is composed of the following:
4145     *                     0 => Vertical split position,
4146     *                     1 => Horizontal split position
4147     *                     2 => Top row visible
4148     *                     3 => Leftmost column visible
4149     *                     4 => Active pane
4150     */
4151     function thawPanes($panes)
4152     {
4153         $this->_frozen = 0;
4154         $this->_panes  = $panes;
4155     }
4156
4157     /**
4158     * Set the page orientation as portrait.
4159     *
4160     * @access public
4161     */
4162     function setPortrait()
4163     {
4164         $this->_orientation = 1;
4165     }
4166
4167     /**
4168     * Set the page orientation as landscape.
4169     *
4170     * @access public
4171     */
4172     function setLandscape()
4173     {
4174         $this->_orientation = 0;
4175     }
4176
4177     /**
4178     * Set the paper type. Ex. 1 = US Letter, 9 = A4
4179     *
4180     * @access public
4181     * @param integer $size The type of paper size to use
4182     */
4183     function setPaper($size = 0)
4184     {
4185         $this->_paper_size = $size;
4186     }
4187
4188
4189     /**
4190     * Set the page header caption and optional margin.
4191     *
4192     * @access public
4193     * @param string $string The header text
4194     * @param float  $margin optional head margin in inches.
4195     */
4196     function setHeader($string,$margin = 0.50)
4197     {
4198         if (strlen($string) >= 255) {
4199             //carp 'Header string must be less than 255 characters';
4200             return;
4201         }
4202         $this->_header      = $string;
4203         $this->_margin_head = $margin;
4204     }
4205
4206     /**
4207     * Set the page footer caption and optional margin.
4208     *
4209     * @access public
4210     * @param string $string The footer text
4211     * @param float  $margin optional foot margin in inches.
4212     */
4213     function setFooter($string,$margin = 0.50)
4214     {
4215         if (strlen($string) >= 255) {
4216             //carp 'Footer string must be less than 255 characters';
4217             return;
4218         }
4219         $this->_footer      = $string;
4220         $this->_margin_foot = $margin;
4221     }
4222
4223     /**
4224     * Center the page horinzontally.
4225     *
4226     * @access public
4227     * @param integer $center the optional value for centering. Defaults to 1 (center).
4228     */
4229     function centerHorizontally($center = 1)
4230     {
4231         $this->_hcenter = $center;
4232     }
4233
4234     /**
4235     * Center the page vertically.
4236     *
4237     * @access public
4238     * @param integer $center the optional value for centering. Defaults to 1 (center).
4239     */
4240     function centerVertically($center = 1)
4241     {
4242         $this->_vcenter = $center;
4243     }
4244
4245     /**
4246     * Set all the page margins to the same value in inches.
4247     *
4248     * @access public
4249     * @param float $margin The margin to set in inches
4250     */
4251     function setMargins($margin)
4252     {
4253         $this->setMarginLeft($margin);
4254         $this->setMarginRight($margin);
4255         $this->setMarginTop($margin);
4256         $this->setMarginBottom($margin);
4257     }
4258
4259     /**
4260     * Set the left and right margins to the same value in inches.
4261     *
4262     * @access public
4263     * @param float $margin The margin to set in inches
4264     */
4265     function setMargins_LR($margin)
4266     {
4267         $this->setMarginLeft($margin);
4268         $this->setMarginRight($margin);
4269     }
4270
4271     /**
4272     * Set the top and bottom margins to the same value in inches.
4273     *
4274     * @access public
4275     * @param float $margin The margin to set in inches
4276     */
4277     function setMargins_TB($margin)
4278     {
4279         $this->setMarginTop($margin);
4280         $this->setMarginBottom($margin);
4281     }
4282
4283     /**
4284     * Set the left margin in inches.
4285     *
4286     * @access public
4287     * @param float $margin The margin to set in inches
4288     */
4289     function setMarginLeft($margin = 0.75)
4290     {
4291         $this->_margin_left = $margin;
4292     }
4293
4294     /**
4295     * Set the right margin in inches.
4296     *
4297     * @access public
4298     * @param float $margin The margin to set in inches
4299     */
4300     function setMarginRight($margin = 0.75)
4301     {
4302         $this->_margin_right = $margin;
4303     }
4304
4305     /**
4306     * Set the top margin in inches.
4307     *
4308     * @access public
4309     * @param float $margin The margin to set in inches
4310     */
4311     function setMarginTop($margin = 1.00)
4312     {
4313         $this->_margin_top = $margin;
4314     }
4315
4316     /**
4317     * Set the bottom margin in inches.
4318     *
4319     * @access public
4320     * @param float $margin The margin to set in inches
4321     */
4322     function setMarginBottom($margin = 1.00)
4323     {
4324         $this->_margin_bottom = $margin;
4325     }
4326
4327     /**
4328     * Set the rows to repeat at the top of each printed page.
4329     *
4330     * @access public
4331     * @param integer $first_row First row to repeat
4332     * @param integer $last_row  Last row to repeat. Optional.
4333     */
4334     function repeatRows($first_row, $last_row = null)
4335     {
4336         $this->title_rowmin  = $first_row;
4337         if (isset($last_row)) { //Second row is optional
4338             $this->title_rowmax  = $last_row;
4339         } else {
4340             $this->title_rowmax  = $first_row;
4341         }
4342     }
4343
4344     /**
4345     * Set the columns to repeat at the left hand side of each printed page.
4346     *
4347     * @access public
4348     * @param integer $first_col First column to repeat
4349     * @param integer $last_col  Last column to repeat. Optional.
4350     */
4351     function repeatColumns($first_col, $last_col = null)
4352     {
4353         $this->title_colmin  = $first_col;
4354         if (isset($last_col)) { // Second col is optional
4355             $this->title_colmax  = $last_col;
4356         } else {
4357             $this->title_colmax  = $first_col;
4358         }
4359     }
4360
4361     /**
4362     * Set the area of each worksheet that will be printed.
4363     *
4364     * @access public
4365     * @param integer $first_row First row of the area to print
4366     * @param integer $first_col First column of the area to print
4367     * @param integer $last_row  Last row of the area to print
4368     * @param integer $last_col  Last column of the area to print
4369     */
4370     function printArea($first_row, $first_col, $last_row, $last_col)
4371     {
4372         $this->print_rowmin  = $first_row;
4373         $this->print_colmin  = $first_col;
4374         $this->print_rowmax  = $last_row;
4375         $this->print_colmax  = $last_col;
4376     }
4377
4378
4379     /**
4380     * Set the option to hide gridlines on the printed page.
4381     *
4382     * @access public
4383     */
4384     function hideGridlines()
4385     {
4386         $this->_print_gridlines = 0;
4387     }
4388
4389     /**
4390     * Set the option to hide gridlines on the worksheet (as seen on the screen).
4391     *
4392     * @access public
4393     */
4394     function hideScreenGridlines()
4395     {
4396         $this->_screen_gridlines = 0;
4397     }
4398
4399     /**
4400     * Set the option to print the row and column headers on the printed page.
4401     *
4402     * @access public
4403     * @param integer $print Whether to print the headers or not. Defaults to 1 (print).
4404     */
4405     function printRowColHeaders($print = 1)
4406     {
4407         $this->_print_headers = $print;
4408     }
4409
4410     /**
4411     * Set the vertical and horizontal number of pages that will define the maximum area printed.
4412     * It doesn't seem to work with OpenOffice.
4413     *
4414     * @access public
4415     * @param  integer $width  Maximun width of printed area in pages
4416     * @param  integer $height Maximun heigth of printed area in pages
4417     * @see setPrintScale()
4418     */
4419     function fitToPages($width, $height)
4420     {
4421         $this->_fit_page      = 1;
4422         $this->_fit_width     = $width;
4423         $this->_fit_height    = $height;
4424     }
4425
4426     /**
4427     * Store the horizontal page breaks on a worksheet (for printing).
4428     * The breaks represent the row after which the break is inserted.
4429     *
4430     * @access public
4431     * @param array $breaks Array containing the horizontal page breaks
4432     */
4433     function setHPagebreaks($breaks)
4434     {
4435         foreach ($breaks as $break) {
4436             array_push($this->_hbreaks, $break);
4437         }
4438     }
4439
4440     /**
4441     * Store the vertical page breaks on a worksheet (for printing).
4442     * The breaks represent the column after which the break is inserted.
4443     *
4444     * @access public
4445     * @param array $breaks Array containing the vertical page breaks
4446     */
4447     function setVPagebreaks($breaks)
4448     {
4449         foreach ($breaks as $break) {
4450             array_push($this->_vbreaks, $break);
4451         }
4452     }
4453
4454
4455     /**
4456     * Set the worksheet zoom factor.
4457     *
4458     * @access public
4459     * @param integer $scale The zoom factor
4460     */
4461     function setZoom($scale = 100)
4462     {
4463         // Confine the scale to Excel's range
4464         if ($scale < 10 || $scale > 400) {
4465             $scale = 100;
4466         }
4467
4468         $this->_zoom = floor($scale);
4469     }
4470
4471     /**
4472     * Set the scale factor for the printed page.
4473     * It turns off the "fit to page" option
4474     *
4475     * @access public
4476     * @param integer $scale The optional scale factor. Defaults to 100
4477     */
4478     function setPrintScale($scale = 100)
4479     {
4480         // Confine the scale to Excel's range
4481         if ($scale < 10 || $scale > 400) {
4482             $scale = 100;
4483         }
4484
4485         // Turn off "fit to page" option
4486         $this->_fit_page = 0;
4487
4488         $this->_print_scale = floor($scale);
4489     }
4490
4491     /**
4492     * Map to the appropriate write method acording to the token recieved.
4493     *
4494     * @access public
4495     * @param integer $row    The row of the cell we are writing to
4496     * @param integer $col    The column of the cell we are writing to
4497     * @param mixed   $token  What we are writing
4498     * @param mixed   $format The optional format to apply to the cell
4499     */
4500     function write($row, $col, $token, $format = null)
4501     {
4502         // Check for a cell reference in A1 notation and substitute row and column
4503         /*if ($_[0] =~ /^\D/) {
4504             @_ = $this->_substituteCellref(@_);
4505     }*/
4506
4507         if (preg_match("/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/", $token)) {
4508             // Match number
4509             return $this->writeNumber($row, $col, $token, $format);
4510         } elseif (preg_match("/^[fh]tt?p:\/\//", $token)) {
4511             // Match http or ftp URL
4512             return $this->writeUrl($row, $col, $token, '', $format);
4513         } elseif (preg_match("/^mailto:/", $token)) {
4514             // Match mailto:
4515             return $this->writeUrl($row, $col, $token, '', $format);
4516         } elseif (preg_match("/^(?:in|ex)ternal:/", $token)) {
4517             // Match internal or external sheet link
4518             return $this->writeUrl($row, $col, $token, '', $format);
4519         } elseif (preg_match("/^=/", $token)) {
4520             // Match formula
4521             return $this->writeFormula($row, $col, $token, $format);
4522         } elseif (preg_match("/^@/", $token)) {
4523             // Match formula
4524             return $this->writeFormula($row, $col, $token, $format);
4525         } elseif ($token == '') {
4526             // Match blank
4527             return $this->writeBlank($row, $col, $format);
4528         } else {
4529             // Default: match string
4530             return $this->writeString($row, $col, $token, $format);
4531         }
4532     }
4533
4534     /**
4535     * Write an array of values as a row
4536     *
4537     * @access public
4538     * @param integer $row    The row we are writing to
4539     * @param integer $col    The first col (leftmost col) we are writing to
4540     * @param array   $val    The array of values to write
4541     * @param mixed   $format The optional format to apply to the cell
4542     * @return mixed PEAR_Error on failure
4543     */
4544
4545     function writeRow($row, $col, $val, $format = null)
4546     {
4547         $retval = '';
4548         if (is_array($val)) {
4549             foreach ($val as $v) {
4550                 if (is_array($v)) {
4551                     $this->writeCol($row, $col, $v, $format);
4552                 } else {
4553                     $this->write($row, $col, $v, $format);
4554                 }
4555                 $col++;
4556             }
4557         } else {
4558             die('$val needs to be an array');
4559         }
4560         return($retval);
4561     }
4562
4563     /**
4564     * Write an array of values as a column
4565     *
4566     * @access public
4567     * @param integer $row    The first row (uppermost row) we are writing to
4568     * @param integer $col    The col we are writing to
4569     * @param array   $val    The array of values to write
4570     * @param mixed   $format The optional format to apply to the cell
4571     * @return mixed PEAR_Error on failure
4572     */
4573
4574     function writeCol($row, $col, $val, $format = null)
4575     {
4576         $retval = '';
4577         if (is_array($val)) {
4578             foreach ($val as $v) {
4579                 $this->write($row, $col, $v, $format);
4580                 $row++;
4581             }
4582         } else {
4583             die('$val needs to be an array');
4584         }
4585         return($retval);
4586     }
4587
4588     /**
4589     * Returns an index to the XF record in the workbook
4590     *
4591     * @access private
4592     * @param mixed &$format The optional XF format
4593     * @return integer The XF record index
4594     */
4595     function _XF(&$format)
4596     {
4597         if ($format) {
4598             return($format->getXfIndex());
4599         } else {
4600             return(0x0F);
4601         }
4602     }
4603
4604
4605     /******************************************************************************
4606     *******************************************************************************
4607     *
4608     * Internal methods
4609     */
4610
4611
4612     /**
4613     * Store Worksheet data in memory using the parent's class append() or to a
4614     * temporary file, the default.
4615     *
4616     * @access private
4617     * @param string $data The binary data to append
4618     */
4619     function _append($data)
4620     {
4621         if ($this->_using_tmpfile) {
4622             // Add CONTINUE records if necessary
4623             if (strlen($data) > $this->_limit) {
4624                 $data = $this->_addContinue($data);
4625             }
4626             fwrite($this->_filehandle, $data);
4627             $this->_datasize += strlen($data);
4628         } else {
4629             parent::_append($data);
4630         }
4631     }
4632
4633     /**
4634     * Substitute an Excel cell reference in A1 notation for  zero based row and
4635     * column values in an argument list.
4636     *
4637     * Ex: ("A4", "Hello") is converted to (3, 0, "Hello").
4638     *
4639     * @access private
4640     * @param string $cell The cell reference. Or range of cells.
4641     * @return array
4642     */
4643     function _substituteCellref($cell)
4644     {
4645         $cell = strtoupper($cell);
4646
4647         // Convert a column range: 'A:A' or 'B:G'
4648         if (preg_match("/([A-I]?[A-Z]):([A-I]?[A-Z])/", $cell, $match)) {
4649             list($no_use, $col1) =  $this->_cellToRowcol($match[1] .'1'); // Add a dummy row
4650             list($no_use, $col2) =  $this->_cellToRowcol($match[2] .'1'); // Add a dummy row
4651             return(array($col1, $col2));
4652         }
4653
4654         // Convert a cell range: 'A1:B7'
4655         if (preg_match("/\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)/", $cell, $match)) {
4656             list($row1, $col1) =  $this->_cellToRowcol($match[1]);
4657             list($row2, $col2) =  $this->_cellToRowcol($match[2]);
4658             return(array($row1, $col1, $row2, $col2));
4659         }
4660
4661         // Convert a cell reference: 'A1' or 'AD2000'
4662         if (preg_match("/\$?([A-I]?[A-Z]\$?\d+)/", $cell)) {
4663             list($row1, $col1) =  $this->_cellToRowcol($match[1]);
4664             return(array($row1, $col1));
4665         }
4666
4667         // TODO use real error codes
4668         die("Unknown cell reference $cell");
4669     }
4670
4671     /**
4672     * Convert an Excel cell reference in A1 notation to a zero based row and column
4673     * reference; converts C1 to (0, 2).
4674     *
4675     * @access private
4676     * @param string $cell The cell reference.
4677     * @return array containing (row, column)
4678     */
4679     function _cellToRowcol($cell)
4680     {
4681         preg_match("/\$?([A-I]?[A-Z])\$?(\d+)/",$cell,$match);
4682         $col     = $match[1];
4683         $row     = $match[2];
4684
4685         // Convert base26 column string to number
4686         $chars = preg_split('//', $col);
4687         $expn  = 0;
4688         $col   = 0;
4689
4690         while ($chars) {
4691             $char = array_pop($chars);        // LS char first
4692             $col += (ord($char) -ord('A') +1) * pow(26,$expn);
4693             $expn++;
4694         }
4695
4696         // Convert 1-index to zero-index
4697         $row--;
4698         $col--;
4699
4700         return(array($row, $col));
4701     }
4702
4703     /**
4704     * Based on the algorithm provided by Daniel Rentz of OpenOffice.
4705     *
4706     * @access private
4707     * @param string $plaintext The password to be encoded in plaintext.
4708     * @return string The encoded password
4709     */
4710     function _encodePassword($plaintext)
4711     {
4712         $password = 0x0000;
4713         $i        = 1;       // char position
4714
4715         // split the plain text password in its component characters
4716         $chars = preg_split('//', $plaintext, -1, PREG_SPLIT_NO_EMPTY);
4717         foreach ($chars as $char) {
4718             $value        = ord($char) << $i;   // shifted ASCII value
4719             $rotated_bits = $value >> 15;       // rotated bits beyond bit 15
4720             $value       &= 0x7fff;             // first 15 bits
4721             $password    ^= ($value | $rotated_bits);
4722             $i++;
4723         }
4724
4725         $password ^= strlen($plaintext);
4726         $password ^= 0xCE4B;
4727
4728         return($password);
4729     }
4730
4731     /**
4732     * This method sets the properties for outlining and grouping. The defaults
4733     * correspond to Excel's defaults.
4734     *
4735     * @param bool $visible
4736     * @param bool $symbols_below
4737     * @param bool $symbols_right
4738     * @param bool $auto_style
4739     */
4740     function setOutline($visible = true, $symbols_below = true, $symbols_right = true, $auto_style = false)
4741     {
4742         $this->_outline_on    = $visible;
4743         $this->_outline_below = $symbols_below;
4744         $this->_outline_right = $symbols_right;
4745         $this->_outline_style = $auto_style;
4746
4747         // Ensure this is a boolean vale for Window2
4748         if ($this->_outline_on) {
4749             $this->_outline_on = 1;
4750         }
4751      }
4752
4753     /******************************************************************************
4754     *******************************************************************************
4755     *
4756     * BIFF RECORDS
4757     */
4758
4759
4760     /**
4761     * Write a double to the specified row and column (zero indexed).
4762     * An integer can be written as a double. Excel will display an
4763     * integer. $format is optional.
4764     *
4765     * Returns  0 : normal termination
4766     *         -2 : row or column out of range
4767     *
4768     * @access public
4769     * @param integer $row    Zero indexed row
4770     * @param integer $col    Zero indexed column
4771     * @param float   $num    The number to write
4772     * @param mixed   $format The optional XF format
4773     * @return integer
4774     */
4775     function writeNumber($row, $col, $num, $format = null)
4776     {
4777         $record    = 0x0203;                 // Record identifier
4778         $length    = 0x000E;                 // Number of bytes to follow
4779
4780         $xf        = $this->_XF($format);    // The cell format
4781
4782         // Check that row and col are valid and store max and min values
4783         if ($row >= $this->_xls_rowmax) {
4784             return(-2);
4785         }
4786         if ($col >= $this->_xls_colmax) {
4787             return(-2);
4788         }
4789         if ($row <  $this->_dim_rowmin)  {
4790             $this->_dim_rowmin = $row;
4791         }
4792         if ($row >  $this->_dim_rowmax)  {
4793             $this->_dim_rowmax = $row;
4794         }
4795         if ($col <  $this->_dim_colmin)  {
4796             $this->_dim_colmin = $col;
4797         }
4798         if ($col >  $this->_dim_colmax)  {
4799             $this->_dim_colmax = $col;
4800         }
4801
4802         $header    = pack("vv",  $record, $length);
4803         $data      = pack("vvv", $row, $col, $xf);
4804         $xl_double = pack("d",   $num);
4805         if ($this->_byte_order) { // if it's Big Endian
4806             $xl_double = strrev($xl_double);
4807         }
4808
4809         $this->_append($header.$data.$xl_double);
4810         return(0);
4811     }
4812
4813     /**
4814     * Write a string to the specified row and column (zero indexed).
4815     * NOTE: there is an Excel 5 defined limit of 255 characters.
4816     * $format is optional.
4817     * Returns  0 : normal termination
4818     *         -2 : row or column out of range
4819     *         -3 : long string truncated to 255 chars
4820     *
4821     * @access public
4822     * @param integer $row    Zero indexed row
4823     * @param integer $col    Zero indexed column
4824     * @param string  $str    The string to write
4825     * @param mixed   $format The XF format for the cell
4826     * @return integer
4827     */
4828     function writeString($row, $col, $str, $format = null)
4829     {
4830         if ($this->_BIFF_version == 0x0600) {
4831             return $this->writeStringBIFF8($row, $col, $str, $format);
4832         }
4833         $strlen    = strlen($str);
4834         $record    = 0x0204;                   // Record identifier
4835         $length    = 0x0008 + $strlen;         // Bytes to follow
4836         $xf        = $this->_XF($format);      // The cell format
4837
4838         $str_error = 0;
4839
4840         // Check that row and col are valid and store max and min values
4841         if ($row >= $this->_xls_rowmax) {
4842             return(-2);
4843         }
4844         if ($col >= $this->_xls_colmax) {
4845             return(-2);
4846         }
4847         if ($row <  $this->_dim_rowmin) {
4848             $this->_dim_rowmin = $row;
4849         }
4850         if ($row >  $this->_dim_rowmax) {
4851             $this->_dim_rowmax = $row;
4852         }
4853         if ($col <  $this->_dim_colmin) {
4854             $this->_dim_colmin = $col;
4855         }
4856         if ($col >  $this->_dim_colmax) {
4857             $this->_dim_colmax = $col;
4858         }
4859
4860         if ($strlen > $this->_xls_strmax) { // LABEL must be < 255 chars
4861             $str       = substr($str, 0, $this->_xls_strmax);
4862             $length    = 0x0008 + $this->_xls_strmax;
4863             $strlen    = $this->_xls_strmax;
4864             $str_error = -3;
4865         }
4866
4867         $header    = pack("vv",   $record, $length);
4868         $data      = pack("vvvv", $row, $col, $xf, $strlen);
4869         $this->_append($header . $data . $str);
4870         return($str_error);
4871     }
4872
4873     /**
4874     * Sets Input Encoding for writing strings
4875     *
4876     * @access public
4877     * @param string $encoding The encoding. Ex: 'UTF-16LE', 'utf-8', 'ISO-859-7'
4878     */
4879     function 
4880     setInputEncoding($encoding)
4881     {
4882         global $encoding_string; 
4883          if ($encoding != 'UTF-16LE' && !function_exists('iconv')) {
4884              die("Using an input encoding other than UTF-16LE requires PHP support for iconv");
4885          }
4886          $this->_input_encoding = $encoding_string = $encoding;
4887     }
4888
4889     /** added 2009-03-05 by Joe Hunt, FA for arabic languages */
4890     function setRTL()
4891     {
4892         $this->_rtl = 1;
4893     }   
4894
4895     /**
4896     * Write a string to the specified row and column (zero indexed).
4897     * This is the BIFF8 version (no 255 chars limit).
4898     * $format is optional.
4899     * Returns  0 : normal termination
4900     *         -2 : row or column out of range
4901     *         -3 : long string truncated to 255 chars
4902     *
4903     * @access public
4904     * @param integer $row    Zero indexed row
4905     * @param integer $col    Zero indexed column
4906     * @param string  $str    The string to write
4907     * @param mixed   $format The XF format for the cell
4908     * @return integer
4909     */
4910     function writeStringBIFF8($row, $col, $str, $format = null)
4911     {
4912         if ($this->_input_encoding == 'UTF-16LE')
4913         {
4914             $strlen = function_exists('mb_strlen') ? mb_strlen($str, 'UTF-16LE') : (strlen($str) / 2);
4915             $encoding  = 0x1;
4916         }
4917         elseif ($this->_input_encoding != '')
4918         {
4919                 $x = $str;
4920             $str = iconv($this->_input_encoding, 'UTF-16LE', $str);
4921             $strlen = function_exists('mb_strlen') ? mb_strlen($str, 'UTF-16LE') : (strlen($str) / 2);
4922             $encoding  = 0x1;
4923         }
4924         else
4925         {
4926             $strlen    = strlen($str);
4927             $encoding  = 0x0;
4928         }
4929         $record    = 0x00FD;                   // Record identifier
4930         $length    = 0x000A;                   // Bytes to follow
4931         $xf        = $this->_XF($format);      // The cell format
4932
4933         $str_error = 0;
4934
4935         // Check that row and col are valid and store max and min values
4936         if ($this->_checkRowCol($row, $col) == false) {
4937             return -2;
4938         }
4939
4940         $str = pack('vC', $strlen, $encoding).$str;
4941
4942         /* check if string is already present */
4943         if (!isset($this->_str_table[$str])) {
4944             $this->_str_table[$str] = $this->_str_unique++;
4945         }
4946         $this->_str_total++;
4947
4948         $header    = pack('vv',   $record, $length);
4949         $data      = pack('vvvV', $row, $col, $xf, $this->_str_table[$str]);
4950         $this->_append($header.$data);
4951         return $str_error;
4952     }
4953
4954     /**
4955     * Check row and col before writing to a cell, and update the sheet's
4956     * dimensions accordingly
4957     *
4958     * @access private
4959     * @param integer $row    Zero indexed row
4960     * @param integer $col    Zero indexed column
4961     * @return boolean true for success, false if row and/or col are grester
4962     *                 then maximums allowed.
4963     */
4964     function _checkRowCol($row, $col)
4965     {
4966         if ($row >= $this->_xls_rowmax) {
4967             return false;
4968         }
4969         if ($col >= $this->_xls_colmax) {
4970             return false;
4971         }
4972         if ($row <  $this->_dim_rowmin) {
4973             $this->_dim_rowmin = $row;
4974         }
4975         if ($row >  $this->_dim_rowmax) {
4976             $this->_dim_rowmax = $row;
4977         }
4978         if ($col <  $this->_dim_colmin) {
4979             $this->_dim_colmin = $col;
4980         }
4981         if ($col >  $this->_dim_colmax) {
4982             $this->_dim_colmax = $col;
4983         }
4984         return true;
4985     }
4986
4987     /**
4988     * Writes a note associated with the cell given by the row and column.
4989     * NOTE records don't have a length limit.
4990     *
4991     * @access public
4992     * @param integer $row    Zero indexed row
4993     * @param integer $col    Zero indexed column
4994     * @param string  $note   The note to write
4995     */
4996     function writeNote($row, $col, $note)
4997     {
4998         $note_length    = strlen($note);
4999         $record         = 0x001C;                // Record identifier
5000         $max_length     = 2048;                  // Maximun length for a NOTE record
5001         //$length      = 0x0006 + $note_length;    // Bytes to follow
5002
5003         // Check that row and col are valid and store max and min values
5004         if ($row >= $this->_xls_rowmax) {
5005             return(-2);
5006         }
5007         if ($col >= $this->_xls_colmax) {
5008             return(-2);
5009         }
5010         if ($row <  $this->_dim_rowmin) {
5011             $this->_dim_rowmin = $row;
5012         }
5013         if ($row >  $this->_dim_rowmax) {
5014             $this->_dim_rowmax = $row;
5015         }
5016         if ($col <  $this->_dim_colmin) {
5017             $this->_dim_colmin = $col;
5018         }
5019         if ($col >  $this->_dim_colmax) {
5020             $this->_dim_colmax = $col;
5021         }
5022
5023         // Length for this record is no more than 2048 + 6
5024         $length    = 0x0006 + min($note_length, 2048);
5025         $header    = pack("vv",   $record, $length);
5026         $data      = pack("vvv", $row, $col, $note_length);
5027         $this->_append($header . $data . substr($note, 0, 2048));
5028
5029         for ($i = $max_length; $i < $note_length; $i += $max_length) {
5030             $chunk  = substr($note, $i, $max_length);
5031             $length = 0x0006 + strlen($chunk);
5032             $header = pack("vv",   $record, $length);
5033             $data   = pack("vvv", -1, 0, strlen($chunk));
5034             $this->_append($header.$data.$chunk);
5035         }
5036         return(0);
5037     }
5038
5039     /**
5040     * Write a blank cell to the specified row and column (zero indexed).
5041     * A blank cell is used to specify formatting without adding a string
5042     * or a number.
5043     *
5044     * A blank cell without a format serves no purpose. Therefore, we don't write
5045     * a BLANK record unless a format is specified.
5046     *
5047     * Returns  0 : normal termination (including no format)
5048     *         -1 : insufficient number of arguments
5049     *         -2 : row or column out of range
5050     *
5051     * @access public
5052     * @param integer $row    Zero indexed row
5053     * @param integer $col    Zero indexed column
5054     * @param mixed   $format The XF format
5055     */
5056     function writeBlank($row, $col, $format)
5057     {
5058         // Don't write a blank cell unless it has a format
5059         if (!$format) {
5060             return(0);
5061         }
5062
5063         $record    = 0x0201;                 // Record identifier
5064         $length    = 0x0006;                 // Number of bytes to follow
5065         $xf        = $this->_XF($format);    // The cell format
5066
5067         // Check that row and col are valid and store max and min values
5068         if ($row >= $this->_xls_rowmax) {
5069             return(-2);
5070         }
5071         if ($col >= $this->_xls_colmax) {
5072             return(-2);
5073         }
5074         if ($row <  $this->_dim_rowmin) {
5075             $this->_dim_rowmin = $row;
5076         }
5077         if ($row >  $this->_dim_rowmax) {
5078             $this->_dim_rowmax = $row;
5079         }
5080         if ($col <  $this->_dim_colmin) {
5081             $this->_dim_colmin = $col;
5082         }
5083         if ($col >  $this->_dim_colmax) {
5084             $this->_dim_colmax = $col;
5085         }
5086
5087         $header    = pack("vv",  $record, $length);
5088         $data      = pack("vvv", $row, $col, $xf);
5089         $this->_append($header . $data);
5090         return 0;
5091     }
5092
5093     /**
5094     * Write a formula to the specified row and column (zero indexed).
5095     * The textual representation of the formula is passed to the parser in
5096     * Parser.php which returns a packed binary string.
5097     *
5098     * Returns  0 : normal termination
5099     *         -1 : formula errors (bad formula)
5100     *         -2 : row or column out of range
5101     *
5102     * @access public
5103     * @param integer $row     Zero indexed row
5104     * @param integer $col     Zero indexed column
5105     * @param string  $formula The formula text string
5106     * @param mixed   $format  The optional XF format
5107     * @return integer
5108     */
5109     function writeFormula($row, $col, $formula, $format = null)
5110     {
5111         $record    = 0x0006;     // Record identifier
5112
5113         // Excel normally stores the last calculated value of the formula in $num.
5114         // Clearly we are not in a position to calculate this a priori. Instead
5115         // we set $num to zero and set the option flags in $grbit to ensure
5116         // automatic calculation of the formula when the file is opened.
5117         //
5118         $xf        = $this->_XF($format); // The cell format
5119         $num       = 0x00;                // Current value of formula
5120         $grbit     = 0x03;                // Option flags
5121         $unknown   = 0x0000;              // Must be zero
5122
5123
5124         // Check that row and col are valid and store max and min values
5125         if ($this->_checkRowCol($row, $col) == false) {
5126             return -2;
5127         }
5128
5129         // Strip the '=' or '@' sign at the beginning of the formula string
5130         if (preg_match("/^=/", $formula)) {
5131             $formula = preg_replace("/(^=)/", "", $formula);
5132         } elseif (preg_match("/^@/", $formula)) {
5133             $formula = preg_replace("/(^@)/", "", $formula);
5134         } else {
5135             // Error handling
5136             $this->writeString($row, $col, 'Unrecognised character for formula');
5137             return -1;
5138         }
5139
5140         // Parse the formula using the parser in Parser.php
5141         $this->_parser->parse($formula);
5142  
5143         $formula = $this->_parser->toReversePolish();
5144  
5145         $formlen    = strlen($formula);    // Length of the binary string
5146         $length     = 0x16 + $formlen;     // Length of the record data
5147
5148         $header    = pack("vv",      $record, $length);
5149         $data      = pack("vvvdvVv", $row, $col, $xf, $num,
5150                                      $grbit, $unknown, $formlen);
5151
5152         $this->_append($header . $data . $formula);
5153         return 0;
5154     }
5155
5156     /**
5157     * Write a hyperlink.
5158     * This is comprised of two elements: the visible label and
5159     * the invisible link. The visible label is the same as the link unless an
5160     * alternative string is specified. The label is written using the
5161     * writeString() method. Therefore the 255 characters string limit applies.
5162     * $string and $format are optional.
5163     *
5164     * The hyperlink can be to a http, ftp, mail, internal sheet (not yet), or external
5165     * directory url.
5166     *
5167     * Returns  0 : normal termination
5168     *         -2 : row or column out of range
5169     *         -3 : long string truncated to 255 chars
5170     *
5171     * @access public
5172     * @param integer $row    Row
5173     * @param integer $col    Column
5174     * @param string  $url    URL string
5175     * @param string  $string Alternative label
5176     * @param mixed   $format The cell format
5177     * @return integer
5178     */
5179     function writeUrl($row, $col, $url, $string = '', $format = null)
5180     {
5181         // Add start row and col to arg list
5182         return($this->_writeUrlRange($row, $col, $row, $col, $url, $string, $format));
5183     }
5184
5185     /**
5186     * This is the more general form of writeUrl(). It allows a hyperlink to be
5187     * written to a range of cells. This function also decides the type of hyperlink
5188     * to be written. These are either, Web (http, ftp, mailto), Internal
5189     * (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1').
5190     *
5191     * @access private
5192     * @see writeUrl()
5193     * @param integer $row1   Start row
5194     * @param integer $col1   Start column
5195     * @param integer $row2   End row
5196     * @param integer $col2   End column
5197     * @param string  $url    URL string
5198     * @param string  $string Alternative label
5199     * @param mixed   $format The cell format
5200     * @return integer
5201     */
5202
5203     function _writeUrlRange($row1, $col1, $row2, $col2, $url, $string = '', $format = null)
5204     {
5205
5206         // Check for internal/external sheet links or default to web link
5207         if (preg_match('[^internal:]', $url)) {
5208             return($this->_writeUrlInternal($row1, $col1, $row2, $col2, $url, $string, $format));
5209         }
5210         if (preg_match('[^external:]', $url)) {
5211             return($this->_writeUrlExternal($row1, $col1, $row2, $col2, $url, $string, $format));
5212         }
5213         return($this->_writeUrlWeb($row1, $col1, $row2, $col2, $url, $string, $format));
5214     }
5215
5216
5217     /**
5218     * Used to write http, ftp and mailto hyperlinks.
5219     * The link type ($options) is 0x03 is the same as absolute dir ref without
5220     * sheet. However it is differentiated by the $unknown2 data stream.
5221     *
5222     * @access private
5223     * @see writeUrl()
5224     * @param integer $row1   Start row
5225     * @param integer $col1   Start column
5226     * @param integer $row2   End row
5227     * @param integer $col2   End column
5228     * @param string  $url    URL string
5229     * @param string  $str    Alternative label
5230     * @param mixed   $format The cell format
5231     * @return integer
5232     */
5233     function _writeUrlWeb($row1, $col1, $row2, $col2, $url, $str, $format = null)
5234     {
5235         $record      = 0x01B8;                       // Record identifier
5236         $length      = 0x00000;                      // Bytes to follow
5237
5238         if (!$format) {
5239             $format = $this->_url_format;
5240         }
5241
5242         // Write the visible label using the writeString() method.
5243         if ($str == '') {
5244             $str = $url;
5245         }
5246         $str_error = $this->writeString($row1, $col1, $str, $format);
5247         if (($str_error == -2) || ($str_error == -3)) {
5248             return $str_error;
5249         }
5250
5251         // Pack the undocumented parts of the hyperlink stream
5252         $unknown1    = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
5253         $unknown2    = pack("H*", "E0C9EA79F9BACE118C8200AA004BA90B");
5254
5255         // Pack the option flags
5256         $options     = pack("V", 0x03);
5257
5258         // Convert URL to a null terminated wchar string
5259         $url         = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
5260         $url         = $url . "\0\0\0";
5261
5262         // Pack the length of the URL
5263         $url_len     = pack("V", strlen($url));
5264
5265         // Calculate the data length
5266         $length      = 0x34 + strlen($url);
5267
5268         // Pack the header data
5269         $header      = pack("vv",   $record, $length);
5270         $data        = pack("vvvv", $row1, $row2, $col1, $col2);
5271
5272         // Write the packed data
5273         $this->_append($header . $data .
5274                        $unknown1 . $options .
5275                        $unknown2 . $url_len . $url);
5276         return($str_error);
5277     }
5278
5279     /**
5280     * Used to write internal reference hyperlinks such as "Sheet1!A1".
5281     *
5282     * @access private
5283     * @see writeUrl()
5284     * @param integer $row1   Start row
5285     * @param integer $col1   Start column
5286     * @param integer $row2   End row
5287     * @param integer $col2   End column
5288     * @param string  $url    URL string
5289     * @param string  $str    Alternative label
5290     * @param mixed   $format The cell format
5291     * @return integer
5292     */
5293     function _writeUrlInternal($row1, $col1, $row2, $col2, $url, $str, $format = null)
5294     {
5295         $record      = 0x01B8;                       // Record identifier
5296         $length      = 0x00000;                      // Bytes to follow
5297
5298         if (!$format) {
5299             $format = $this->_url_format;
5300         }
5301
5302         // Strip URL type
5303         $url = preg_replace('/^internal:/', '', $url);
5304
5305         // Write the visible label
5306         if ($str == '') {
5307             $str = $url;
5308         }
5309         $str_error = $this->writeString($row1, $col1, $str, $format);
5310         if (($str_error == -2) || ($str_error == -3)) {
5311             return $str_error;
5312         }
5313
5314         // Pack the undocumented parts of the hyperlink stream
5315         $unknown1    = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
5316
5317         // Pack the option flags
5318         $options     = pack("V", 0x08);
5319
5320         // Convert the URL type and to a null terminated wchar string
5321         $url         = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
5322         $url         = $url . "\0\0\0";
5323
5324         // Pack the length of the URL as chars (not wchars)
5325         $url_len     = pack("V", floor(strlen($url)/2));
5326
5327         // Calculate the data length
5328         $length      = 0x24 + strlen($url);
5329
5330         // Pack the header data
5331         $header      = pack("vv",   $record, $length);
5332         $data        = pack("vvvv", $row1, $row2, $col1, $col2);
5333
5334         // Write the packed data
5335         $this->_append($header . $data .
5336                        $unknown1 . $options .
5337                        $url_len . $url);
5338         return($str_error);
5339     }
5340
5341     /**
5342     * Write links to external directory names such as 'c:\foo.xls',
5343     * c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.
5344     *
5345     * Note: Excel writes some relative links with the $dir_long string. We ignore
5346     * these cases for the sake of simpler code.
5347     *
5348     * @access private
5349     * @see writeUrl()
5350     * @param integer $row1   Start row
5351     * @param integer $col1   Start column
5352     * @param integer $row2   End row
5353     * @param integer $col2   End column
5354     * @param string  $url    URL string
5355     * @param string  $str    Alternative label
5356     * @param mixed   $format The cell format
5357     * @return integer
5358     */
5359     function _writeUrlExternal($row1, $col1, $row2, $col2, $url, $str, $format = null)
5360     {
5361         // Network drives are different. We will handle them separately
5362         // MS/Novell network drives and shares start with \\
5363         if (preg_match('[^external:\\\\]', $url)) {
5364             return; //($this->_writeUrlExternal_net($row1, $col1, $row2, $col2, $url, $str, $format));
5365         }
5366     
5367         $record      = 0x01B8;                       // Record identifier
5368         $length      = 0x00000;                      // Bytes to follow
5369     
5370         if (!$format) {
5371             $format = $this->_url_format;
5372         }
5373     
5374         // Strip URL type and change Unix dir separator to Dos style (if needed)
5375         //
5376         $url = preg_replace('/^external:/', '', $url);
5377         $url = preg_replace('/\//', "\\", $url);
5378     
5379         // Write the visible label
5380         if ($str == '') {
5381             $str = preg_replace('/\#/', ' - ', $url);
5382         }
5383         $str_error = $this->writeString($row1, $col1, $str, $format);
5384         if (($str_error == -2) or ($str_error == -3)) {
5385             return $str_error;
5386         }
5387     
5388         // Determine if the link is relative or absolute:
5389         //   relative if link contains no dir separator, "somefile.xls"
5390         //   relative if link starts with up-dir, "..\..\somefile.xls"
5391         //   otherwise, absolute
5392         
5393         $absolute    = 0x02; // Bit mask
5394         if (!preg_match("/\\\/", $url)) {
5395             $absolute    = 0x00;
5396         }
5397         if (preg_match("/^\.\.\\\/", $url)) {
5398             $absolute    = 0x00;
5399         }
5400         $link_type               = 0x01 | $absolute;
5401     
5402         // Determine if the link contains a sheet reference and change some of the
5403         // parameters accordingly.
5404         // Split the dir name and sheet name (if it exists)
5405         /*if (preg_match("/\#/", $url)) {
5406             list($dir_long, $sheet) = preg_split("/\#/", $url);
5407         } else {
5408             $dir_long = $url;
5409         }
5410     
5411         if (isset($sheet)) {
5412             $link_type |= 0x08;
5413             $sheet_len  = pack("V", strlen($sheet) + 0x01);
5414             $sheet      = join("\0", preg_split('//', $sheet));
5415             $sheet     .= "\0\0\0";
5416         } else {
5417             $sheet_len   = '';
5418             $sheet       = '';
5419         }*/
5420         $dir_long = $url;
5421         if (preg_match("/\#/", $url)) {
5422             $link_type |= 0x08;
5423         }
5424
5425
5426     
5427         // Pack the link type
5428         $link_type   = pack("V", $link_type);
5429     
5430         // Calculate the up-level dir count e.g.. (..\..\..\ == 3)
5431         $up_count    = preg_match_all("/\.\.\\\/", $dir_long, $useless);
5432         $up_count    = pack("v", $up_count);
5433     
5434         // Store the short dos dir name (null terminated)
5435         $dir_short   = preg_replace("/\.\.\\\/", '', $dir_long) . "\0";
5436     
5437         // Store the long dir name as a wchar string (non-null terminated)
5438         //$dir_long       = join("\0", preg_split('//', $dir_long));
5439         $dir_long       = $dir_long . "\0";
5440     
5441         // Pack the lengths of the dir strings
5442         $dir_short_len = pack("V", strlen($dir_short)      );
5443         $dir_long_len  = pack("V", strlen($dir_long)       );
5444         $stream_len    = pack("V", 0);//strlen($dir_long) + 0x06);
5445     
5446         // Pack the undocumented parts of the hyperlink stream
5447         $unknown1 = pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000'       );
5448         $unknown2 = pack("H*",'0303000000000000C000000000000046'               );
5449         $unknown3 = pack("H*",'FFFFADDE000000000000000000000000000000000000000');
5450         $unknown4 = pack("v",  0x03                                            );
5451     
5452         // Pack the main data stream
5453         $data        = pack("vvvv", $row1, $row2, $col1, $col2) .
5454                           $unknown1     .
5455                           $link_type    .
5456                           $unknown2     .
5457                           $up_count     .
5458                           $dir_short_len.
5459                           $dir_short    .
5460                           $unknown3     .
5461                           $stream_len   .
5462                           $dir_long_len .
5463                           $unknown4     .
5464                           $dir_long     .
5465                           $sheet_len    .
5466                           $sheet        ;
5467     
5468         // Pack the header data
5469         $length   = strlen($data);
5470         $header   = pack("vv", $record, $length);
5471     
5472         // Write the packed data
5473         $this->_append($header. $data);
5474         return($str_error);
5475     }
5476
5477
5478     /**
5479     * This method is used to set the height and format for a row.
5480     *
5481     * @access public
5482     * @param integer $row    The row to set
5483     * @param integer $height Height we are giving to the row.
5484     *                        Use null to set XF without setting height
5485     * @param mixed   $format XF format we are giving to the row
5486     * @param bool    $hidden The optional hidden attribute
5487     * @param integer $level  The optional outline level for row, in range [0,7]
5488     */
5489     function setRow($row, $height, $format = null, $hidden = false, $level = 0)
5490     {
5491         $record      = 0x0208;               // Record identifier
5492         $length      = 0x0010;               // Number of bytes to follow
5493
5494         $colMic      = 0x0000;               // First defined column
5495         $colMac      = 0x0000;               // Last defined column
5496         $irwMac      = 0x0000;               // Used by Excel to optimise loading
5497         $reserved    = 0x0000;               // Reserved
5498         $grbit       = 0x0000;               // Option flags
5499         $ixfe        = $this->_XF($format);  // XF index
5500
5501         // set _row_sizes so _sizeRow() can use it
5502         $this->_row_sizes[$row] = $height;
5503
5504         // Use setRow($row, null, $XF) to set XF format without setting height
5505         if ($height != null) {
5506             $miyRw = $height * 20;  // row height
5507         } else {
5508             $miyRw = 0xff;          // default row height is 256
5509         }
5510
5511         $level = max(0, min($level, 7));  // level should be between 0 and 7
5512         $this->_outline_row_level = max($level, $this->_outline_row_level);
5513
5514
5515         // Set the options flags. fUnsynced is used to show that the font and row
5516         // heights are not compatible. This is usually the case for WriteExcel.
5517         // The collapsed flag 0x10 doesn't seem to be used to indicate that a row
5518         // is collapsed. Instead it is used to indicate that the previous row is
5519         // collapsed. The zero height flag, 0x20, is used to collapse a row.
5520
5521         $grbit |= $level;
5522         if ($hidden) {
5523             $grbit |= 0x0020;
5524         }
5525         $grbit |= 0x0040; // fUnsynced
5526         if ($format) {
5527             $grbit |= 0x0080;
5528         }
5529         $grbit |= 0x0100;
5530
5531         $header   = pack("vv",       $record, $length);
5532         $data     = pack("vvvvvvvv", $row, $colMic, $colMac, $miyRw,
5533                                      $irwMac,$reserved, $grbit, $ixfe);
5534         $this->_append($header.$data);
5535     }
5536
5537     /**
5538     * Writes Excel DIMENSIONS to define the area in which there is data.
5539     *
5540     * @access private
5541     */
5542     function _storeDimensions()
5543     {
5544         $record    = 0x0200;                 // Record identifier
5545         $row_min   = $this->_dim_rowmin;     // First row
5546         $row_max   = $this->_dim_rowmax + 1; // Last row plus 1
5547         $col_min   = $this->_dim_colmin;     // First column
5548         $col_max   = $this->_dim_colmax + 1; // Last column plus 1
5549         $reserved  = 0x0000;                 // Reserved by Excel
5550
5551         if ($this->_BIFF_version == 0x0500) {
5552             $length    = 0x000A;               // Number of bytes to follow
5553             $data      = pack("vvvvv", $row_min, $row_max,
5554                                        $col_min, $col_max, $reserved);
5555         } elseif ($this->_BIFF_version == 0x0600) {
5556             $length    = 0x000E;
5557             $data      = pack("VVvvv", $row_min, $row_max,
5558                                        $col_min, $col_max, $reserved);
5559         }
5560         $header = pack("vv", $record, $length);
5561         $this->_prepend($header.$data);
5562     }
5563
5564     /**
5565     * Write BIFF record Window2.
5566     *
5567     * @access private
5568     */
5569     function _storeWindow2()
5570     {
5571         $record         = 0x023E;     // Record identifier
5572         if ($this->_BIFF_version == 0x0500) {
5573             $length         = 0x000A;     // Number of bytes to follow
5574         } elseif ($this->_BIFF_version == 0x0600) {
5575             $length         = 0x0012;
5576         }
5577
5578         $grbit          = 0x00B6;     // Option flags
5579         $rwTop          = 0x0000;     // Top row visible in window
5580         $colLeft        = 0x0000;     // Leftmost column visible in window
5581
5582
5583         // The options flags that comprise $grbit
5584         $fDspFmla       = 0;                     // 0 - bit
5585         $fDspGrid       = $this->_screen_gridlines; // 1
5586         $fDspRwCol      = 1;                     // 2
5587         $fFrozen        = $this->_frozen;        // 3
5588         $fDspZeros      = 1;                     // 4
5589         $fDefaultHdr    = 1;                     // 5
5590         $fArabic        = $this->_rtl;           // 6
5591         $fDspGuts       = $this->_outline_on;    // 7
5592         $fFrozenNoSplit = 0;                     // 0 - bit
5593         $fSelected      = $this->selected;       // 1
5594         $fPaged         = 1;                     // 2
5595
5596         $grbit             = $fDspFmla;
5597         $grbit            |= $fDspGrid       << 1;
5598         $grbit            |= $fDspRwCol      << 2;
5599         $grbit            |= $fFrozen        << 3;
5600         $grbit            |= $fDspZeros      << 4;
5601         $grbit            |= $fDefaultHdr    << 5;
5602         $grbit            |= $fArabic        << 6;
5603         $grbit            |= $fDspGuts       << 7;
5604         $grbit            |= $fFrozenNoSplit << 8;
5605         $grbit            |= $fSelected      << 9;
5606         $grbit            |= $fPaged         << 10;
5607
5608         $header  = pack("vv",   $record, $length);
5609         $data    = pack("vvv", $grbit, $rwTop, $colLeft);
5610         // FIXME !!!
5611         if ($this->_BIFF_version == 0x0500) {
5612             $rgbHdr         = 0x00000000; // Row/column heading and gridline color
5613             $data .= pack("V", $rgbHdr);
5614         } elseif ($this->_BIFF_version == 0x0600) {
5615             $rgbHdr       = 0x0040; // Row/column heading and gridline color index
5616             $zoom_factor_page_break = 0x0000;
5617             $zoom_factor_normal     = 0x0000;
5618             $data .= pack("vvvvV", $rgbHdr, 0x0000, $zoom_factor_page_break, $zoom_factor_normal, 0x00000000);
5619         }
5620         $this->_append($header.$data);
5621     }
5622
5623     /**
5624     * Write BIFF record DEFCOLWIDTH if COLINFO records are in use.
5625     *
5626     * @access private
5627     */
5628     function _storeDefcol()
5629     {
5630         $record   = 0x0055;      // Record identifier
5631         $length   = 0x0002;      // Number of bytes to follow
5632         $colwidth = 0x0008;      // Default column width
5633
5634         $header   = pack("vv", $record, $length);
5635         $data     = pack("v",  $colwidth);
5636         $this->_prepend($header . $data);
5637     }
5638
5639     /**
5640     * Write BIFF record COLINFO to define column widths
5641     *
5642     * Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
5643     * length record.
5644     *
5645     * @access private
5646     * @param array $col_array This is the only parameter received and is composed of the following:
5647     *                0 => First formatted column,
5648     *                1 => Last formatted column,
5649     *                2 => Col width (8.43 is Excel default),
5650     *                3 => The optional XF format of the column,
5651     *                4 => Option flags.
5652     *                5 => Optional outline level
5653     */
5654     function _storeColinfo($col_array)
5655     {
5656         if (isset($col_array[0])) {
5657             $colFirst = $col_array[0];
5658         }
5659         if (isset($col_array[1])) {
5660             $colLast = $col_array[1];
5661         }
5662         if (isset($col_array[2])) {
5663             $coldx = $col_array[2];
5664         } else {
5665             $coldx = 8.43;
5666         }
5667         if (isset($col_array[3])) {
5668             $format = $col_array[3];
5669         } else {
5670             $format = 0;
5671         }
5672         if (isset($col_array[4])) {
5673             $grbit = $col_array[4];
5674         } else {
5675             $grbit = 0;
5676         }
5677         if (isset($col_array[5])) {
5678             $level = $col_array[5];
5679         } else {
5680             $level = 0;
5681         }
5682         $record   = 0x007D;          // Record identifier
5683         $length   = 0x000B;          // Number of bytes to follow
5684
5685         $coldx   += 0.72;            // Fudge. Excel subtracts 0.72 !?
5686         $coldx   *= 256;             // Convert to units of 1/256 of a char
5687
5688         $ixfe     = $this->_XF($format);
5689         $reserved = 0x00;            // Reserved
5690
5691         $level = max(0, min($level, 7));
5692         $grbit |= $level << 8;
5693
5694         $header   = pack("vv",     $record, $length);
5695         $data     = pack("vvvvvC", $colFirst, $colLast, $coldx,
5696                                    $ixfe, $grbit, $reserved);
5697         $this->_prepend($header.$data);
5698     }
5699
5700     /**
5701     * Write BIFF record SELECTION.
5702     *
5703     * @access private
5704     * @param array $array array containing ($rwFirst,$colFirst,$rwLast,$colLast)
5705     * @see setSelection()
5706     */
5707     function _storeSelection($array)
5708     {
5709         list($rwFirst,$colFirst,$rwLast,$colLast) = $array;
5710         $record   = 0x001D;                  // Record identifier
5711         $length   = 0x000F;                  // Number of bytes to follow
5712
5713         $pnn      = $this->_active_pane;     // Pane position
5714         $rwAct    = $rwFirst;                // Active row
5715         $colAct   = $colFirst;               // Active column
5716         $irefAct  = 0;                       // Active cell ref
5717         $cref     = 1;                       // Number of refs
5718
5719         if (!isset($rwLast)) {
5720             $rwLast   = $rwFirst;       // Last  row in reference
5721         }
5722         if (!isset($colLast)) {
5723             $colLast  = $colFirst;      // Last  col in reference
5724         }
5725
5726         // Swap last row/col for first row/col as necessary
5727         if ($rwFirst > $rwLast) {
5728             list($rwFirst, $rwLast) = array($rwLast, $rwFirst);
5729         }
5730
5731         if ($colFirst > $colLast) {
5732             list($colFirst, $colLast) = array($colLast, $colFirst);
5733         }
5734
5735         $header   = pack("vv",         $record, $length);
5736         $data     = pack("CvvvvvvCC",  $pnn, $rwAct, $colAct,
5737                                        $irefAct, $cref,
5738                                        $rwFirst, $rwLast,
5739                                        $colFirst, $colLast);
5740         $this->_append($header . $data);
5741     }
5742
5743     /**
5744     * Store the MERGEDCELLS record for all ranges of merged cells
5745     *
5746     * @access private
5747     */
5748     function _storeMergedCells()
5749     {
5750         // if there are no merged cell ranges set, return
5751         if (count($this->_merged_ranges) == 0) {
5752             return;
5753         }
5754         $record   = 0x00E5;
5755         $length   = 2 + count($this->_merged_ranges) * 8;
5756
5757         $header   = pack('vv', $record, $length);
5758         $data     = pack('v',  count($this->_merged_ranges));
5759         foreach ($this->_merged_ranges as $range) {
5760             $data .= pack('vvvv', $range[0], $range[2], $range[1], $range[3]);
5761         }
5762         $this->_append($header . $data);
5763     }
5764
5765     /**
5766     * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
5767     * references in a worksheet.
5768     *
5769     * Excel only stores references to external sheets that are used in formulas.
5770     * For simplicity we store references to all the sheets in the workbook
5771     * regardless of whether they are used or not. This reduces the overall
5772     * complexity and eliminates the need for a two way dialogue between the formula
5773     * parser the worksheet objects.
5774     *
5775     * @access private
5776     * @param integer $count The number of external sheet references in this worksheet
5777     */
5778     function _storeExterncount($count)
5779     {
5780         $record = 0x0016;          // Record identifier
5781         $length = 0x0002;          // Number of bytes to follow
5782
5783         $header = pack("vv", $record, $length);
5784         $data   = pack("v",  $count);
5785         $this->_prepend($header . $data);
5786     }
5787
5788     /**
5789     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
5790     * formulas. A formula references a sheet name via an index. Since we store a
5791     * reference to all of the external worksheets the EXTERNSHEET index is the same
5792     * as the worksheet index.
5793     *
5794     * @access private
5795     * @param string $sheetname The name of a external worksheet
5796     */
5797     function _storeExternsheet($sheetname)
5798     {
5799         $record    = 0x0017;         // Record identifier
5800
5801         // References to the current sheet are encoded differently to references to
5802         // external sheets.
5803         //
5804         if ($this->name == $sheetname) {
5805             $sheetname = '';
5806             $length    = 0x02;  // The following 2 bytes
5807             $cch       = 1;     // The following byte
5808             $rgch      = 0x02;  // Self reference
5809         } else {
5810             $length    = 0x02 + strlen($sheetname);
5811             $cch       = strlen($sheetname);
5812             $rgch      = 0x03;  // Reference to a sheet in the current workbook
5813         }
5814
5815         $header = pack("vv",  $record, $length);
5816         $data   = pack("CC", $cch, $rgch);
5817         $this->_prepend($header . $data . $sheetname);
5818     }
5819
5820     /**
5821     * Writes the Excel BIFF PANE record.
5822     * The panes can either be frozen or thawed (unfrozen).
5823     * Frozen panes are specified in terms of an integer number of rows and columns.
5824     * Thawed panes are specified in terms of Excel's units for rows and columns.
5825     *
5826     * @access private
5827     * @param array $panes This is the only parameter received and is composed of the following:
5828     *                     0 => Vertical split position,
5829     *                     1 => Horizontal split position
5830     *                     2 => Top row visible
5831     *                     3 => Leftmost column visible
5832     *                     4 => Active pane
5833     */
5834     function _storePanes($panes)
5835     {
5836         $y       = $panes[0];
5837         $x       = $panes[1];
5838         $rwTop   = $panes[2];
5839         $colLeft = $panes[3];
5840         if (count($panes) > 4) { // if Active pane was received
5841             $pnnAct = $panes[4];
5842         } else {
5843             $pnnAct = null;
5844         }
5845         $record  = 0x0041;       // Record identifier
5846         $length  = 0x000A;       // Number of bytes to follow
5847
5848         // Code specific to frozen or thawed panes.
5849         if ($this->_frozen) {
5850             // Set default values for $rwTop and $colLeft
5851             if (!isset($rwTop)) {
5852                 $rwTop   = $y;
5853             }
5854             if (!isset($colLeft)) {
5855                 $colLeft = $x;
5856             }
5857         } else {
5858             // Set default values for $rwTop and $colLeft
5859             if (!isset($rwTop)) {
5860                 $rwTop   = 0;
5861             }
5862             if (!isset($colLeft)) {
5863                 $colLeft = 0;
5864             }
5865
5866             // Convert Excel's row and column units to the internal units.
5867             // The default row height is 12.75
5868             // The default column width is 8.43
5869             // The following slope and intersection values were interpolated.
5870             //
5871             $y = 20*$y      + 255;
5872             $x = 113.879*$x + 390;
5873         }
5874
5875
5876         // Determine which pane should be active. There is also the undocumented
5877         // option to override this should it be necessary: may be removed later.
5878         //
5879         if (!isset($pnnAct)) {
5880             if ($x != 0 && $y != 0) {
5881                 $pnnAct = 0; // Bottom right
5882             }
5883             if ($x != 0 && $y == 0) {
5884                 $pnnAct = 1; // Top right
5885             }
5886             if ($x == 0 && $y != 0) {
5887                 $pnnAct = 2; // Bottom left
5888             }
5889             if ($x == 0 && $y == 0) {
5890                 $pnnAct = 3; // Top left
5891             }
5892         }
5893
5894         $this->_active_pane = $pnnAct; // Used in _storeSelection
5895
5896         $header     = pack("vv",    $record, $length);
5897         $data       = pack("vvvvv", $x, $y, $rwTop, $colLeft, $pnnAct);
5898         $this->_append($header . $data);
5899     }
5900
5901     /**
5902     * Store the page setup SETUP BIFF record.
5903     *
5904     * @access private
5905     */
5906     function _storeSetup()
5907     {
5908         $record       = 0x00A1;                  // Record identifier
5909         $length       = 0x0022;                  // Number of bytes to follow
5910
5911         $iPaperSize   = $this->_paper_size;    // Paper size
5912         $iScale       = $this->_print_scale;   // Print scaling factor
5913         $iPageStart   = 0x01;                 // Starting page number
5914         $iFitWidth    = $this->_fit_width;    // Fit to number of pages wide
5915         $iFitHeight   = $this->_fit_height;   // Fit to number of pages high
5916         $grbit        = 0x00;                 // Option flags
5917         $iRes         = 0x0258;               // Print resolution
5918         $iVRes        = 0x0258;               // Vertical print resolution
5919         $numHdr       = $this->_margin_head;  // Header Margin
5920         $numFtr       = $this->_margin_foot;   // Footer Margin
5921         $iCopies      = 0x01;                 // Number of copies
5922
5923         $fLeftToRight = 0x0;                     // Print over then down
5924         $fLandscape   = $this->_orientation;     // Page orientation
5925         $fNoPls       = 0x0;                     // Setup not read from printer
5926         $fNoColor     = 0x0;                     // Print black and white
5927         $fDraft       = 0x0;                     // Print draft quality
5928         $fNotes       = 0x0;                     // Print notes
5929         $fNoOrient    = 0x0;                     // Orientation not set
5930         $fUsePage     = 0x0;                     // Use custom starting page
5931
5932         $grbit           = $fLeftToRight;
5933         $grbit          |= $fLandscape    << 1;
5934         $grbit          |= $fNoPls        << 2;
5935         $grbit          |= $fNoColor      << 3;
5936         $grbit          |= $fDraft        << 4;
5937         $grbit          |= $fNotes        << 5;
5938         $grbit          |= $fNoOrient     << 6;
5939         $grbit          |= $fUsePage      << 7;
5940
5941         $numHdr = pack("d", $numHdr);
5942         $numFtr = pack("d", $numFtr);
5943         if ($this->_byte_order) { // if it's Big Endian
5944             $numHdr = strrev($numHdr);
5945             $numFtr = strrev($numFtr);
5946         }
5947
5948         $header = pack("vv", $record, $length);
5949         $data1  = pack("vvvvvvvv", $iPaperSize,
5950                                    $iScale,
5951                                    $iPageStart,
5952                                    $iFitWidth,
5953                                    $iFitHeight,
5954                                    $grbit,
5955                                    $iRes,
5956                                    $iVRes);
5957         $data2  = $numHdr.$numFtr;
5958         $data3  = pack("v", $iCopies);
5959         $this->_prepend($header . $data1 . $data2 . $data3);
5960     }
5961
5962     /**
5963     * Store the header caption BIFF record.
5964     *
5965     * @access private
5966     */
5967     function _storeHeader()
5968     {
5969         $record  = 0x0014;               // Record identifier
5970
5971         $str      = $this->_header;       // header string
5972         $cch      = strlen($str);         // Length of header string
5973         if ($this->_BIFF_version == 0x0600) {
5974             $encoding = 0x0;                  // TODO: Unicode support
5975             $length   = 3 + $cch;             // Bytes to follow
5976         } else {
5977             $length  = 1 + $cch;             // Bytes to follow
5978         }
5979
5980         $header   = pack("vv", $record, $length);
5981         if ($this->_BIFF_version == 0x0600) {
5982             $data     = pack("vC",  $cch, $encoding);
5983         } else {
5984             $data      = pack("C",  $cch);
5985         }
5986
5987         $this->_prepend($header.$data.$str);
5988     }
5989
5990     /**
5991     * Store the footer caption BIFF record.
5992     *
5993     * @access private
5994     */
5995     function _storeFooter()
5996     {
5997         $record  = 0x0015;               // Record identifier
5998
5999         $str      = $this->_footer;       // Footer string
6000         $cch      = strlen($str);         // Length of footer string
6001         if ($this->_BIFF_version == 0x0600) {
6002             $encoding = 0x0;                  // TODO: Unicode support
6003             $length   = 3 + $cch;             // Bytes to follow
6004         } else {
6005             $length  = 1 + $cch;
6006         }
6007
6008         $header    = pack("vv", $record, $length);
6009         if ($this->_BIFF_version == 0x0600) {
6010             $data      = pack("vC",  $cch, $encoding);
6011         } else {
6012             $data      = pack("C",  $cch);
6013         }
6014
6015         $this->_prepend($header . $data . $str);
6016     }
6017
6018     /**
6019     * Store the horizontal centering HCENTER BIFF record.
6020     *
6021     * @access private
6022     */
6023     function _storeHcenter()
6024     {
6025         $record   = 0x0083;              // Record identifier
6026         $length   = 0x0002;              // Bytes to follow
6027
6028         $fHCenter = $this->_hcenter;     // Horizontal centering
6029
6030         $header    = pack("vv", $record, $length);
6031         $data      = pack("v",  $fHCenter);
6032
6033         $this->_prepend($header.$data);
6034     }
6035
6036     /**
6037     * Store the vertical centering VCENTER BIFF record.
6038     *
6039     * @access private
6040     */
6041     function _storeVcenter()
6042     {
6043         $record   = 0x0084;              // Record identifier
6044         $length   = 0x0002;              // Bytes to follow
6045
6046         $fVCenter = $this->_vcenter;     // Horizontal centering
6047
6048         $header    = pack("vv", $record, $length);
6049         $data      = pack("v",  $fVCenter);
6050         $this->_prepend($header . $data);
6051     }
6052
6053     /**
6054     * Store the LEFTMARGIN BIFF record.
6055     *
6056     * @access private
6057     */
6058     function _storeMarginLeft()
6059     {
6060         $record  = 0x0026;                   // Record identifier
6061         $length  = 0x0008;                   // Bytes to follow
6062
6063         $margin  = $this->_margin_left;       // Margin in inches
6064
6065         $header    = pack("vv",  $record, $length);
6066         $data      = pack("d",   $margin);
6067         if ($this->_byte_order) { // if it's Big Endian
6068             $data = strrev($data);
6069         }
6070
6071         $this->_prepend($header . $data);
6072     }
6073
6074     /**
6075     * Store the RIGHTMARGIN BIFF record.
6076     *
6077     * @access private
6078     */
6079     function _storeMarginRight()
6080     {
6081         $record  = 0x0027;                   // Record identifier
6082         $length  = 0x0008;                   // Bytes to follow
6083
6084         $margin  = $this->_margin_right;      // Margin in inches
6085
6086         $header    = pack("vv",  $record, $length);
6087         $data      = pack("d",   $margin);
6088         if ($this->_byte_order) { // if it's Big Endian
6089             $data = strrev($data);
6090         }
6091
6092         $this->_prepend($header . $data);
6093     }
6094
6095     /**
6096     * Store the TOPMARGIN BIFF record.
6097     *
6098     * @access private
6099     */
6100     function _storeMarginTop()
6101     {
6102         $record  = 0x0028;                   // Record identifier
6103         $length  = 0x0008;                   // Bytes to follow
6104
6105         $margin  = $this->_margin_top;        // Margin in inches
6106
6107         $header    = pack("vv",  $record, $length);
6108         $data      = pack("d",   $margin);
6109         if ($this->_byte_order) { // if it's Big Endian
6110             $data = strrev($data);
6111         }
6112
6113         $this->_prepend($header . $data);
6114     }
6115
6116     /**
6117     * Store the BOTTOMMARGIN BIFF record.
6118     *
6119     * @access private
6120     */
6121     function _storeMarginBottom()
6122     {
6123         $record  = 0x0029;                   // Record identifier
6124         $length  = 0x0008;                   // Bytes to follow
6125
6126         $margin  = $this->_margin_bottom;     // Margin in inches
6127
6128         $header    = pack("vv",  $record, $length);
6129         $data      = pack("d",   $margin);
6130         if ($this->_byte_order) { // if it's Big Endian
6131             $data = strrev($data);
6132         }
6133
6134         $this->_prepend($header . $data);
6135     }
6136
6137     /**
6138     * Merges the area given by its arguments.
6139     * This is an Excel97/2000 method. It is required to perform more complicated
6140     * merging than the normal setAlign('merge').
6141     *
6142     * @access public
6143     * @param integer $first_row First row of the area to merge
6144     * @param integer $first_col First column of the area to merge
6145     * @param integer $last_row  Last row of the area to merge
6146     * @param integer $last_col  Last column of the area to merge
6147     */
6148     function mergeCells($first_row, $first_col, $last_row, $last_col)
6149     {
6150         $record  = 0x00E5;                   // Record identifier
6151         $length  = 0x000A;                   // Bytes to follow
6152         $cref     = 1;                       // Number of refs
6153
6154         // Swap last row/col for first row/col as necessary
6155         if ($first_row > $last_row) {
6156             list($first_row, $last_row) = array($last_row, $first_row);
6157         }
6158
6159         if ($first_col > $last_col) {
6160             list($first_col, $last_col) = array($last_col, $first_col);
6161         }
6162
6163         $header   = pack("vv",    $record, $length);
6164         $data     = pack("vvvvv", $cref, $first_row, $last_row,
6165                                   $first_col, $last_col);
6166
6167         $this->_append($header.$data);
6168     }
6169
6170     /**
6171     * Write the PRINTHEADERS BIFF record.
6172     *
6173     * @access private
6174     */
6175     function _storePrintHeaders()
6176     {
6177         $record      = 0x002a;                   // Record identifier
6178         $length      = 0x0002;                   // Bytes to follow
6179
6180         $fPrintRwCol = $this->_print_headers;     // Boolean flag
6181
6182         $header      = pack("vv", $record, $length);
6183         $data        = pack("v", $fPrintRwCol);
6184         $this->_prepend($header . $data);
6185     }
6186
6187     /**
6188     * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the
6189     * GRIDSET record.
6190     *
6191     * @access private
6192     */
6193     function _storePrintGridlines()
6194     {
6195         $record      = 0x002b;                    // Record identifier
6196         $length      = 0x0002;                    // Bytes to follow
6197
6198         $fPrintGrid  = $this->_print_gridlines;    // Boolean flag
6199
6200         $header      = pack("vv", $record, $length);
6201         $data        = pack("v", $fPrintGrid);
6202         $this->_prepend($header . $data);
6203     }
6204
6205     /**
6206     * Write the GRIDSET BIFF record. Must be used in conjunction with the
6207     * PRINTGRIDLINES record.
6208     *
6209     * @access private
6210     */
6211     function _storeGridset()
6212     {
6213         $record      = 0x0082;                        // Record identifier
6214         $length      = 0x0002;                        // Bytes to follow
6215
6216         $fGridSet    = !($this->_print_gridlines);     // Boolean flag
6217
6218         $header      = pack("vv",  $record, $length);
6219         $data        = pack("v",   $fGridSet);
6220         $this->_prepend($header . $data);
6221     }
6222
6223     /**
6224     * Write the GUTS BIFF record. This is used to configure the gutter margins
6225     * where Excel outline symbols are displayed. The visibility of the gutters is
6226     * controlled by a flag in WSBOOL.
6227     *
6228     * @see _storeWsbool()
6229     * @access private
6230     */
6231     function _storeGuts()
6232     {
6233         $record      = 0x0080;   // Record identifier
6234         $length      = 0x0008;   // Bytes to follow
6235
6236         $dxRwGut     = 0x0000;   // Size of row gutter
6237         $dxColGut    = 0x0000;   // Size of col gutter
6238
6239         $row_level   = $this->_outline_row_level;
6240         $col_level   = 0;
6241
6242         // Calculate the maximum column outline level. The equivalent calculation
6243         // for the row outline level is carried out in setRow().
6244         $colcount = count($this->_colinfo);
6245         for ($i = 0; $i < $colcount; $i++) {
6246            // Skip cols without outline level info.
6247            if (count($col_level) >= 6) {
6248               $col_level = max($this->_colinfo[$i][5], $col_level);
6249            }
6250         }
6251
6252         // Set the limits for the outline levels (0 <= x <= 7).
6253         $col_level = max(0, min($col_level, 7));
6254
6255         // The displayed level is one greater than the max outline levels
6256         if ($row_level) {
6257             $row_level++;
6258         }
6259         if ($col_level) {
6260             $col_level++;
6261         }
6262
6263         $header      = pack("vv",   $record, $length);
6264         $data        = pack("vvvv", $dxRwGut, $dxColGut, $row_level, $col_level);
6265
6266         $this->_prepend($header.$data);
6267     }
6268
6269
6270     /**
6271     * Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction
6272     * with the SETUP record.
6273     *
6274     * @access private
6275     */
6276     function _storeWsbool()
6277     {
6278         $record      = 0x0081;   // Record identifier
6279         $length      = 0x0002;   // Bytes to follow
6280         $grbit       = 0x0000;
6281
6282         // The only option that is of interest is the flag for fit to page. So we
6283         // set all the options in one go.
6284         //
6285         /*if ($this->_fit_page) {
6286             $grbit = 0x05c1;
6287         } else {
6288             $grbit = 0x04c1;
6289         }*/
6290         // Set the option flags
6291         $grbit |= 0x0001;                           // Auto page breaks visible
6292         if ($this->_outline_style) {
6293             $grbit |= 0x0020; // Auto outline styles
6294         }
6295         if ($this->_outline_below) {
6296             $grbit |= 0x0040; // Outline summary below
6297         }
6298         if ($this->_outline_right) {
6299             $grbit |= 0x0080; // Outline summary right
6300         }
6301         if ($this->_fit_page) {
6302             $grbit |= 0x0100; // Page setup fit to page
6303         }
6304         if ($this->_outline_on) {
6305             $grbit |= 0x0400; // Outline symbols displayed
6306         }
6307
6308         $header      = pack("vv", $record, $length);
6309         $data        = pack("v",  $grbit);
6310         $this->_prepend($header . $data);
6311     }
6312
6313     /**
6314     * Write the HORIZONTALPAGEBREAKS BIFF record.
6315     *
6316     * @access private
6317     */
6318     function _storeHbreak()
6319     {
6320         // Return if the user hasn't specified pagebreaks
6321         if (empty($this->_hbreaks)) {
6322             return;
6323         }
6324
6325         // Sort and filter array of page breaks
6326         $breaks = $this->_hbreaks;
6327         sort($breaks, SORT_NUMERIC);
6328         if ($breaks[0] == 0) { // don't use first break if it's 0
6329             array_shift($breaks);
6330         }
6331
6332         $record  = 0x001b;               // Record identifier
6333         $cbrk    = count($breaks);       // Number of page breaks
6334         if ($this->_BIFF_version == 0x0600) {
6335             $length  = 2 + 6*$cbrk;      // Bytes to follow
6336         } else {
6337             $length  = 2 + 2*$cbrk;      // Bytes to follow
6338         }
6339
6340         $header  = pack("vv", $record, $length);
6341         $data    = pack("v",  $cbrk);
6342
6343         // Append each page break
6344         foreach ($breaks as $break) {
6345             if ($this->_BIFF_version == 0x0600) {
6346                 $data .= pack("vvv", $break, 0x0000, 0x00ff);
6347             } else {
6348                 $data .= pack("v", $break);
6349             }
6350         }
6351
6352         $this->_prepend($header.$data);
6353     }
6354
6355
6356     /**
6357     * Write the VERTICALPAGEBREAKS BIFF record.
6358     *
6359     * @access private
6360     */
6361     function _storeVbreak()
6362     {
6363         // Return if the user hasn't specified pagebreaks
6364         if (empty($this->_vbreaks)) {
6365             return;
6366         }
6367
6368         // 1000 vertical pagebreaks appears to be an internal Excel 5 limit.
6369         // It is slightly higher in Excel 97/200, approx. 1026
6370         $breaks = array_slice($this->_vbreaks,0,1000);
6371
6372         // Sort and filter array of page breaks
6373         sort($breaks, SORT_NUMERIC);
6374         if ($breaks[0] == 0) { // don't use first break if it's 0
6375             array_shift($breaks);
6376         }
6377
6378         $record  = 0x001a;               // Record identifier
6379         $cbrk    = count($breaks);       // Number of page breaks
6380         if ($this->_BIFF_version == 0x0600) {
6381             $length  = 2 + 6*$cbrk;      // Bytes to follow
6382         } else {
6383             $length  = 2 + 2*$cbrk;      // Bytes to follow
6384         }
6385
6386         $header  = pack("vv",  $record, $length);
6387         $data    = pack("v",   $cbrk);
6388
6389         // Append each page break
6390         foreach ($breaks as $break) {
6391             if ($this->_BIFF_version == 0x0600) {
6392                 $data .= pack("vvv", $break, 0x0000, 0xffff);
6393             } else {
6394                 $data .= pack("v", $break);
6395             }
6396         }
6397
6398         $this->_prepend($header . $data);
6399     }
6400
6401     /**
6402     * Set the Biff PROTECT record to indicate that the worksheet is protected.
6403     *
6404     * @access private
6405     */
6406     function _storeProtect()
6407     {
6408         // Exit unless sheet protection has been specified
6409         if ($this->_protect == 0) {
6410             return;
6411         }
6412
6413         $record      = 0x0012;             // Record identifier
6414         $length      = 0x0002;             // Bytes to follow
6415
6416         $fLock       = $this->_protect;    // Worksheet is protected
6417
6418         $header      = pack("vv", $record, $length);
6419         $data        = pack("v",  $fLock);
6420
6421         $this->_prepend($header.$data);
6422     }
6423
6424     /**
6425     * Write the worksheet PASSWORD record.
6426     *
6427     * @access private
6428     */
6429     function _storePassword()
6430     {
6431         // Exit unless sheet protection and password have been specified
6432         if (($this->_protect == 0) || (!isset($this->_password))) {
6433             return;
6434         }
6435
6436         $record      = 0x0013;               // Record identifier
6437         $length      = 0x0002;               // Bytes to follow
6438
6439         $wPassword   = $this->_password;     // Encoded password
6440
6441         $header      = pack("vv", $record, $length);
6442         $data        = pack("v",  $wPassword);
6443
6444         $this->_prepend($header . $data);
6445     }
6446
6447
6448     /**
6449     * Insert a 24bit bitmap image in a worksheet.
6450     *
6451     * @access public
6452     * @param integer $row     The row we are going to insert the bitmap into
6453     * @param integer $col     The column we are going to insert the bitmap into
6454     * @param string  $bitmap  The bitmap filename
6455     * @param integer $x       The horizontal position (offset) of the image inside the cell.
6456     * @param integer $y       The vertical position (offset) of the image inside the cell.
6457     * @param integer $scale_x The horizontal scale
6458     * @param integer $scale_y The vertical scale
6459     */
6460     function insertBitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1)
6461     {
6462         $bitmap_array = $this->_processBitmap($bitmap);
6463         if ($this->isError($bitmap_array)) {
6464             $this->writeString($row, $col, $bitmap_array->getMessage());
6465             return;
6466         }
6467         list($width, $height, $size, $data) = $bitmap_array; //$this->_processBitmap($bitmap);
6468
6469         // Scale the frame of the image.
6470         $width  *= $scale_x;
6471         $height *= $scale_y;
6472
6473         // Calculate the vertices of the image and write the OBJ record
6474         $this->_positionImage($col, $row, $x, $y, $width, $height);
6475
6476         // Write the IMDATA record to store the bitmap data
6477         $record      = 0x007f;
6478         $length      = 8 + $size;
6479         $cf          = 0x09;
6480         $env         = 0x01;
6481         $lcb         = $size;
6482
6483         $header      = pack("vvvvV", $record, $length, $cf, $env, $lcb);
6484         $this->_append($header.$data);
6485     }
6486
6487     /**
6488     * Calculate the vertices that define the position of the image as required by
6489     * the OBJ record.
6490     *
6491     *         +------------+------------+
6492     *         |     A      |      B     |
6493     *   +-----+------------+------------+
6494     *   |     |(x1,y1)     |            |
6495     *   |  1  |(A1)._______|______      |
6496     *   |     |    |              |     |
6497     *   |     |    |              |     |
6498     *   +-----+----|    BITMAP    |-----+
6499     *   |     |    |              |     |
6500     *   |  2  |    |______________.     |
6501     *   |     |            |        (B2)|
6502     *   |     |            |     (x2,y2)|
6503     *   +---- +------------+------------+
6504     *
6505     * Example of a bitmap that covers some of the area from cell A1 to cell B2.
6506     *
6507     * Based on the width and height of the bitmap we need to calculate 8 vars:
6508     *     $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
6509     * The width and height of the cells are also variable and have to be taken into
6510     * account.
6511     * The values of $col_start and $row_start are passed in from the calling
6512     * function. The values of $col_end and $row_end are calculated by subtracting
6513     * the width and height of the bitmap from the width and height of the
6514     * underlying cells.
6515     * The vertices are expressed as a percentage of the underlying cell width as
6516     * follows (rhs values are in pixels):
6517     *
6518     *       x1 = X / W *1024
6519     *       y1 = Y / H *256
6520     *       x2 = (X-1) / W *1024
6521     *       y2 = (Y-1) / H *256
6522     *
6523     *       Where:  X is distance from the left side of the underlying cell
6524     *               Y is distance from the top of the underlying cell
6525     *               W is the width of the cell
6526     *               H is the height of the cell
6527     *
6528     * @access private
6529     * @note  the SDK incorrectly states that the height should be expressed as a
6530     *        percentage of 1024.
6531     * @param integer $col_start Col containing upper left corner of object
6532     * @param integer $row_start Row containing top left corner of object
6533     * @param integer $x1        Distance to left side of object
6534     * @param integer $y1        Distance to top of object
6535     * @param integer $width     Width of image frame
6536     * @param integer $height    Height of image frame
6537     */
6538     function _positionImage($col_start, $row_start, $x1, $y1, $width, $height)
6539     {
6540         // Initialise end cell to the same as the start cell
6541         $col_end    = $col_start;  // Col containing lower right corner of object
6542         $row_end    = $row_start;  // Row containing bottom right corner of object
6543
6544         // Zero the specified offset if greater than the cell dimensions
6545         if ($x1 >= $this->_sizeCol($col_start)) {
6546             $x1 = 0;
6547         }
6548         if ($y1 >= $this->_sizeRow($row_start)) {
6549             $y1 = 0;
6550         }
6551
6552         $width      = $width  + $x1 -1;
6553         $height     = $height + $y1 -1;
6554
6555         // Subtract the underlying cell widths to find the end cell of the image
6556         while ($width >= $this->_sizeCol($col_end)) {
6557             $width -= $this->_sizeCol($col_end);
6558             $col_end++;
6559         }
6560
6561         // Subtract the underlying cell heights to find the end cell of the image
6562         while ($height >= $this->_sizeRow($row_end)) {
6563             $height -= $this->_sizeRow($row_end);
6564             $row_end++;
6565         }
6566
6567         // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
6568         // with zero eight or width.
6569         //
6570         if ($this->_sizeCol($col_start) == 0) {
6571             return;
6572         }
6573         if ($this->_sizeCol($col_end)   == 0) {
6574             return;
6575         }
6576         if ($this->_sizeRow($row_start) == 0) {
6577             return;
6578         }
6579         if ($this->_sizeRow($row_end)   == 0) {
6580             return;
6581         }
6582
6583         // Convert the pixel values to the percentage value expected by Excel
6584         $x1 = $x1     / $this->_sizeCol($col_start)   * 1024;
6585         $y1 = $y1     / $this->_sizeRow($row_start)   *  256;
6586         $x2 = $width  / $this->_sizeCol($col_end)     * 1024; // Distance to right side of object
6587         $y2 = $height / $this->_sizeRow($row_end)     *  256; // Distance to bottom of object
6588
6589         $this->_storeObjPicture($col_start, $x1,
6590                                  $row_start, $y1,
6591                                  $col_end, $x2,
6592                                  $row_end, $y2);
6593     }
6594
6595     /**
6596     * Convert the width of a cell from user's units to pixels. By interpolation
6597     * the relationship is: y = 7x +5. If the width hasn't been set by the user we
6598     * use the default value. If the col is hidden we use a value of zero.
6599     *
6600     * @access private
6601     * @param integer $col The column
6602     * @return integer The width in pixels
6603     */
6604     function _sizeCol($col)
6605     {
6606         // Look up the cell value to see if it has been changed
6607         if (isset($this->col_sizes[$col])) {
6608             if ($this->col_sizes[$col] == 0) {
6609                 return(0);
6610             } else {
6611                 return(floor(7 * $this->col_sizes[$col] + 5));
6612             }
6613         } else {
6614             return(64);
6615         }
6616     }
6617
6618     /**
6619     * Convert the height of a cell from user's units to pixels. By interpolation
6620     * the relationship is: y = 4/3x. If the height hasn't been set by the user we
6621     * use the default value. If the row is hidden we use a value of zero. (Not
6622     * possible to hide row yet).
6623     *
6624     * @access private
6625     * @param integer $row The row
6626     * @return integer The width in pixels
6627     */
6628     function _sizeRow($row)
6629     {
6630         // Look up the cell value to see if it has been changed
6631         if (isset($this->_row_sizes[$row])) {
6632             if ($this->_row_sizes[$row] == 0) {
6633                 return(0);
6634             } else {
6635                 return(floor(4/3 * $this->_row_sizes[$row]));
6636             }
6637         } else {
6638             return(17);
6639         }
6640     }
6641
6642     /**
6643     * Store the OBJ record that precedes an IMDATA record. This could be generalise
6644     * to support other Excel objects.
6645     *
6646     * @access private
6647     * @param integer $colL Column containing upper left corner of object
6648     * @param integer $dxL  Distance from left side of cell
6649     * @param integer $rwT  Row containing top left corner of object
6650     * @param integer $dyT  Distance from top of cell
6651     * @param integer $colR Column containing lower right corner of object
6652     * @param integer $dxR  Distance from right of cell
6653     * @param integer $rwB  Row containing bottom right corner of object
6654     * @param integer $dyB  Distance from bottom of cell
6655     */
6656     function _storeObjPicture($colL,$dxL,$rwT,$dyT,$colR,$dxR,$rwB,$dyB)
6657     {
6658         $record      = 0x005d;   // Record identifier
6659         $length      = 0x003c;   // Bytes to follow
6660
6661         $cObj        = 0x0001;   // Count of objects in file (set to 1)
6662         $OT          = 0x0008;   // Object type. 8 = Picture
6663         $id          = 0x0001;   // Object ID
6664         $grbit       = 0x0614;   // Option flags
6665
6666         $cbMacro     = 0x0000;   // Length of FMLA structure
6667         $Reserved1   = 0x0000;   // Reserved
6668         $Reserved2   = 0x0000;   // Reserved
6669
6670         $icvBack     = 0x09;     // Background colour
6671         $icvFore     = 0x09;     // Foreground colour
6672         $fls         = 0x00;     // Fill pattern
6673         $fAuto       = 0x00;     // Automatic fill
6674         $icv         = 0x08;     // Line colour
6675         $lns         = 0xff;     // Line style
6676         $lnw         = 0x01;     // Line weight
6677         $fAutoB      = 0x00;     // Automatic border
6678         $frs         = 0x0000;   // Frame style
6679         $cf          = 0x0009;   // Image format, 9 = bitmap
6680         $Reserved3   = 0x0000;   // Reserved
6681         $cbPictFmla  = 0x0000;   // Length of FMLA structure
6682         $Reserved4   = 0x0000;   // Reserved
6683         $grbit2      = 0x0001;   // Option flags
6684         $Reserved5   = 0x0000;   // Reserved
6685
6686
6687         $header      = pack("vv", $record, $length);
6688         $data        = pack("V", $cObj);
6689         $data       .= pack("v", $OT);
6690         $data       .= pack("v", $id);
6691         $data       .= pack("v", $grbit);
6692         $data       .= pack("v", $colL);
6693         $data       .= pack("v", $dxL);
6694         $data       .= pack("v", $rwT);
6695         $data       .= pack("v", $dyT);
6696         $data       .= pack("v", $colR);
6697         $data       .= pack("v", $dxR);
6698         $data       .= pack("v", $rwB);
6699         $data       .= pack("v", $dyB);
6700         $data       .= pack("v", $cbMacro);
6701         $data       .= pack("V", $Reserved1);
6702         $data       .= pack("v", $Reserved2);
6703         $data       .= pack("C", $icvBack);
6704         $data       .= pack("C", $icvFore);
6705         $data       .= pack("C", $fls);
6706         $data       .= pack("C", $fAuto);
6707         $data       .= pack("C", $icv);
6708         $data       .= pack("C", $lns);
6709         $data       .= pack("C", $lnw);
6710         $data       .= pack("C", $fAutoB);
6711         $data       .= pack("v", $frs);
6712         $data       .= pack("V", $cf);
6713         $data       .= pack("v", $Reserved3);
6714         $data       .= pack("v", $cbPictFmla);
6715         $data       .= pack("v", $Reserved4);
6716         $data       .= pack("v", $grbit2);
6717         $data       .= pack("V", $Reserved5);
6718
6719         $this->_append($header . $data);
6720     }
6721
6722     /**
6723     * Convert a 24 bit bitmap into the modified internal format used by Windows.
6724     * This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
6725     * MSDN library.
6726     *
6727     * @access private
6728     * @param string $bitmap The bitmap to process
6729     * @return array Array with data and properties of the bitmap
6730     */
6731     function _processBitmap($bitmap)
6732     {
6733         // Open file.
6734         $bmp_fd = @fopen($bitmap,"rb");
6735         if (!$bmp_fd) {
6736             die("Couldn't import $bitmap");
6737         }
6738
6739         // Slurp the file into a string.
6740         $data = fread($bmp_fd, filesize($bitmap));
6741
6742         // Check that the file is big enough to be a bitmap.
6743         if (strlen($data) <= 0x36) {
6744             die("$bitmap doesn't contain enough data.\n");
6745         }
6746
6747         // The first 2 bytes are used to identify the bitmap.
6748         $identity = unpack("A2ident", $data);
6749         if ($identity['ident'] != "BM") {
6750             die("$bitmap doesn't appear to be a valid bitmap image.\n");
6751         }
6752
6753         // Remove bitmap data: ID.
6754         $data = substr($data, 2);
6755
6756         // Read and remove the bitmap size. This is more reliable than reading
6757         // the data size at offset 0x22.
6758         //
6759         $size_array   = unpack("Vsa", substr($data, 0, 4));
6760         $size   = $size_array['sa'];
6761         $data   = substr($data, 4);
6762         $size  -= 0x36; // Subtract size of bitmap header.
6763         $size  += 0x0C; // Add size of BIFF header.
6764
6765         // Remove bitmap data: reserved, offset, header length.
6766         $data = substr($data, 12);
6767
6768         // Read and remove the bitmap width and height. Verify the sizes.
6769         $width_and_height = unpack("V2", substr($data, 0, 8));
6770         $width  = $width_and_height[1];
6771         $height = $width_and_height[2];
6772         $data   = substr($data, 8);
6773         if ($width > 0xFFFF) {
6774             die("$bitmap: largest image width supported is 65k.\n");
6775         }
6776         if ($height > 0xFFFF) {
6777             die("$bitmap: largest image height supported is 65k.\n");
6778         }
6779
6780         // Read and remove the bitmap planes and bpp data. Verify them.
6781         $planes_and_bitcount = unpack("v2", substr($data, 0, 4));
6782         $data = substr($data, 4);
6783         if ($planes_and_bitcount[2] != 24) { // Bitcount
6784             die("$bitmap isn't a 24bit true color bitmap.\n");
6785         }
6786         if ($planes_and_bitcount[1] != 1) {
6787             die("$bitmap: only 1 plane supported in bitmap image.\n");
6788         }
6789
6790         // Read and remove the bitmap compression. Verify compression.
6791         $compression = unpack("Vcomp", substr($data, 0, 4));
6792         $data = substr($data, 4);
6793
6794         //$compression = 0;
6795         if ($compression['comp'] != 0) {
6796             die("$bitmap: compression not supported in bitmap image.\n");
6797         }
6798
6799         // Remove bitmap data: data size, hres, vres, colours, imp. colours.
6800         $data = substr($data, 20);
6801
6802         // Add the BITMAPCOREHEADER data
6803         $header  = pack("Vvvvv", 0x000c, $width, $height, 0x01, 0x18);
6804         $data    = $header . $data;
6805
6806         return (array($width, $height, $size, $data));
6807     }
6808
6809     /**
6810     * Store the window zoom factor. This should be a reduced fraction but for
6811     * simplicity we will store all fractions with a numerator of 100.
6812     *
6813     * @access private
6814     */
6815     function _storeZoom()
6816     {
6817         // If scale is 100 we don't need to write a record
6818         if ($this->_zoom == 100) {
6819             return;
6820         }
6821
6822         $record      = 0x00A0;               // Record identifier
6823         $length      = 0x0004;               // Bytes to follow
6824
6825         $header      = pack("vv", $record, $length);
6826         $data        = pack("vv", $this->_zoom, 100);
6827         $this->_append($header . $data);
6828     }
6829
6830     /**
6831     * FIXME: add comments
6832     */
6833     function setValidation($row1, $col1, $row2, $col2, &$validator)
6834     {
6835         $this->_dv[] = $validator->_getData() .
6836                        pack("vvvvv", 1, $row1, $row2, $col1, $col2);
6837     }
6838
6839     /**
6840     * Store the DVAL and DV records.
6841     *
6842     * @access private
6843     */
6844     function _storeDataValidity()
6845     {
6846         $record      = 0x01b2;      // Record identifier
6847         $length      = 0x0012;      // Bytes to follow
6848
6849         $grbit       = 0x0002;      // Prompt box at cell, no cached validity data at DV records
6850         $horPos      = 0x00000000;  // Horizontal position of prompt box, if fixed position
6851         $verPos      = 0x00000000;  // Vertical position of prompt box, if fixed position
6852         $objId       = 0xffffffff;  // Object identifier of drop down arrow object, or -1 if not visible
6853
6854         $header      = pack('vv', $record, $length);
6855         $data        = pack('vVVVV', $grbit, $horPos, $verPos, $objId,
6856                                      count($this->_dv));
6857         $this->_append($header.$data);
6858
6859         $record = 0x01be;              // Record identifier
6860         foreach ($this->_dv as $dv) {
6861             $length = strlen($dv);      // Bytes to follow
6862             $header = pack("vv", $record, $length);
6863             $this->_append($header . $dv);
6864         }
6865     }
6866 }
6867
6868 /**
6869 * Class for generating Excel Spreadsheets
6870 *
6871 * @author   Xavier Noguer <xnoguer@rezebra.com>
6872 * @category FileFormats
6873 * @package  Spreadsheet_Excel_Writer
6874 */
6875
6876 class Spreadsheet_Excel_Writer_Workbook extends Spreadsheet_Excel_Writer_BIFFwriter
6877 {
6878     /**
6879     * Filename for the Workbook
6880     * @var string
6881     */
6882     var $_filename;
6883
6884     /**
6885     * Formula parser
6886     * @var object Parser
6887     */
6888     var $_parser;
6889
6890     /**
6891     * Flag for 1904 date system (0 => base date is 1900, 1 => base date is 1904)
6892     * @var integer
6893     */
6894     var $_1904;
6895
6896     /**
6897     * The active worksheet of the workbook (0 indexed)
6898     * @var integer
6899     */
6900     var $_activesheet;
6901
6902     /**
6903     * 1st displayed worksheet in the workbook (0 indexed)
6904     * @var integer
6905     */
6906     var $_firstsheet;
6907
6908     /**
6909     * Number of workbook tabs selected
6910     * @var integer
6911     */
6912     var $_selected;
6913
6914     /**
6915     * Index for creating adding new formats to the workbook
6916     * @var integer
6917     */
6918     var $_xf_index;
6919
6920     /**
6921     * Flag for preventing close from being called twice.
6922     * @var integer
6923     * @see close()
6924     */
6925     var $_fileclosed;
6926
6927     /**
6928     * The BIFF file size for the workbook.
6929     * @var integer
6930     * @see _calcSheetOffsets()
6931     */
6932     var $_biffsize;
6933
6934     /**
6935     * The default sheetname for all sheets created.
6936     * @var string
6937     */
6938     var $_sheetname;
6939
6940     /**
6941     * The default XF format.
6942     * @var object Format
6943     */
6944     var $_tmp_format;
6945
6946     /**
6947     * Array containing references to all of this workbook's worksheets
6948     * @var array
6949     */
6950     var $_worksheets;
6951
6952     /**
6953     * Array of sheetnames for creating the EXTERNSHEET records
6954     * @var array
6955     */
6956     var $_sheetnames;
6957
6958     /**
6959     * Array containing references to all of this workbook's formats
6960     * @var array
6961     */
6962     var $_formats;
6963
6964     /**
6965     * Array containing the colour palette
6966     * @var array
6967     */
6968     var $_palette;
6969
6970     /**
6971     * The default format for URLs.
6972     * @var object Format
6973     */
6974     var $_url_format;
6975
6976     /**
6977     * The codepage indicates the text encoding used for strings
6978     * @var integer
6979     */
6980     var $_codepage;
6981
6982     /**
6983     * The country code used for localization
6984     * @var integer
6985     */
6986     var $_country_code;
6987
6988     /**
6989     * The temporary dir for storing the OLE file
6990     * @var string
6991     */
6992     var $_tmp_dir;
6993
6994     /**
6995     * number of bytes for sizeinfo of strings
6996     * @var integer
6997     */
6998     var $_string_sizeinfo_size;
6999
7000     /**
7001     * Class constructor
7002     *
7003     * @param string filename for storing the workbook. "-" for writing to stdout.
7004     * @access public
7005     */
7006     function Spreadsheet_Excel_Writer_Workbook($filename)
7007     {
7008         // It needs to call its parent's constructor explicitly
7009         $this->Spreadsheet_Excel_Writer_BIFFwriter();
7010
7011         $this->_filename         = $filename;
7012         $this->_parser           = new Spreadsheet_Excel_Writer_Parser($this->_byte_order, $this->_BIFF_version);
7013         $this->_1904             = 0;
7014         $this->_activesheet      = 0;
7015         $this->_firstsheet       = 0;
7016         $this->_selected         = 0;
7017         $this->_xf_index         = 16; // 15 style XF's and 1 cell XF.
7018         $this->_fileclosed       = 0;
7019         $this->_biffsize         = 0;
7020         $this->_sheetname        = 'Sheet';
7021         $this->_tmp_format       = new Spreadsheet_Excel_Writer_Format($this->_BIFF_version);
7022         $this->_worksheets       = array();
7023         $this->_sheetnames       = array();
7024         $this->_formats          = array();
7025         $this->_palette          = array();
7026         $this->_codepage         = 0x04E4; // FIXME: should change for BIFF8
7027         $this->_country_code     = -1;
7028         $this->_string_sizeinfo  = 3;
7029
7030         // Add the default format for hyperlinks
7031         $this->_url_format =& $this->addFormat(array('color' => 'blue', 'underline' => 1));
7032         $this->_str_total       = 0;
7033         $this->_str_unique      = 0;
7034         $this->_str_table       = array();
7035         $this->_setPaletteXl97();
7036         $this->_tmp_dir         = '';
7037     }
7038
7039     /**
7040     * Calls finalization methods.
7041     * This method should always be the last one to be called on every workbook
7042     *
7043     * @access public
7044     * @return mixed true on success. PEAR_Error on failure
7045     */
7046     function close()
7047     {
7048         if ($this->_fileclosed) { // Prevent close() from being called twice.
7049             return true;
7050         }
7051         $this->_storeWorkbook();
7052         $this->_fileclosed = 1;
7053         return true;
7054     }
7055
7056     /**
7057     * An accessor for the _worksheets[] array
7058     * Returns an array of the worksheet objects in a workbook
7059     * It actually calls to worksheets()
7060     *
7061     * @access public
7062     * @see worksheets()
7063     * @return array
7064     */
7065     function sheets()
7066     {
7067         return $this->worksheets();
7068     }
7069
7070     /**
7071     * An accessor for the _worksheets[] array.
7072     * Returns an array of the worksheet objects in a workbook
7073     *
7074     * @access public
7075     * @return array
7076     */
7077     function worksheets()
7078     {
7079         return $this->_worksheets;
7080     }
7081
7082     /**
7083     * Sets the BIFF version.
7084     * This method exists just to access experimental functionality
7085     * from BIFF8. It will be deprecated !
7086     * Only possible value is 8 (Excel 97/2000).
7087     * For any other value it fails silently.
7088     *
7089     * @access public
7090     * @param integer $version The BIFF version
7091     */
7092     function 
7093     setVersion($version)
7094     {
7095         if ($version == 8) { // only accept version 8
7096             $version = 0x0600;
7097             $this->_BIFF_version = $version;
7098             // change BIFFwriter limit for CONTINUE records
7099             $this->_limit = 8228;
7100             $this->_tmp_format->_BIFF_version = $version;
7101             $this->_url_format->_BIFF_version = $version;
7102             $this->_parser->_BIFF_version = $version;
7103
7104             $total_worksheets = count($this->_worksheets);
7105             // change version for all worksheets too
7106             for ($i = 0; $i < $total_worksheets; $i++) {
7107                 $this->_worksheets[$i]->_BIFF_version = $version;
7108             }
7109
7110             $total_formats = count($this->_formats);
7111             // change version for all formats too
7112             for ($i = 0; $i < $total_formats; $i++) {
7113                 $this->_formats[$i]->_BIFF_version = $version;
7114             }
7115         }
7116     }
7117
7118     /**
7119     * Set the country identifier for the workbook
7120     *
7121     * @access public
7122     * @param integer $code Is the international calling country code for the
7123     *                      chosen country.
7124     */
7125     function setCountry($code)
7126     {
7127         $this->_country_code = $code;
7128     }
7129
7130     /**
7131     * Add a new worksheet to the Excel workbook.
7132     * If no name is given the name of the worksheet will be Sheeti$i, with
7133     * $i in [1..].
7134     *
7135     * @access public
7136     * @param string $name the optional name of the worksheet
7137     * @return mixed reference to a worksheet object on success, PEAR_Error
7138     *               on failure
7139     */
7140     function &addWorksheet($name = '')
7141     {
7142         $index     = count($this->_worksheets);
7143         $sheetname = $this->_sheetname;
7144
7145         if ($name == '') {
7146             $name = $sheetname.($index+1);
7147         }
7148
7149         // Check that sheetname is <= 31 chars (Excel limit before BIFF8).
7150         if ($this->_BIFF_version != 0x0600)
7151         {
7152             if (strlen($name) > 31) {
7153                 die("Sheetname $name must be <= 31 chars");
7154             }
7155         }
7156
7157         // Check that the worksheet name doesn't already exist: a fatal Excel error.
7158         $total_worksheets = count($this->_worksheets);
7159         for ($i = 0; $i < $total_worksheets; $i++) {
7160             if ($this->_worksheets[$i]->getName() == $name) {
7161                 die("Worksheet '$name' already exists");
7162             }
7163         }
7164
7165         $worksheet = new Spreadsheet_Excel_Writer_Worksheet($this->_BIFF_version,
7166                                    $name, $index,
7167                                    $this->_activesheet, $this->_firstsheet,
7168                                    $this->_str_total, $this->_str_unique,
7169                                    $this->_str_table, $this->_url_format,
7170                                    $this->_parser);
7171
7172         $this->_worksheets[$index] = &$worksheet;    // Store ref for iterator
7173         $this->_sheetnames[$index] = $name;          // Store EXTERNSHEET names
7174         $this->_parser->setExtSheet($name, $index);  // Register worksheet name with parser
7175         return $worksheet;
7176     }
7177
7178     /**
7179     * Add a new format to the Excel workbook.
7180     * Also, pass any properties to the Format constructor.
7181     *
7182     * @access public
7183     * @param array $properties array with properties for initializing the format.
7184     * @return &Spreadsheet_Excel_Writer_Format reference to an Excel Format
7185     */
7186     function &addFormat($properties = array())
7187     {
7188         $format = new Spreadsheet_Excel_Writer_Format($this->_BIFF_version, $this->_xf_index, $properties);
7189         $this->_xf_index += 1;
7190         $this->_formats[] = &$format;
7191         return $format;
7192     }
7193
7194     /**
7195      * Create new validator.
7196      *
7197      * @access public
7198      * @return &Spreadsheet_Excel_Writer_Validator reference to a Validator
7199      */
7200     function &addValidator()
7201     {
7202         include_once 'Spreadsheet/Excel/Writer/Validator.php';
7203         /* FIXME: check for successful inclusion*/
7204         $valid = new Spreadsheet_Excel_Writer_Validator($this->_parser);
7205         return $valid;
7206     }
7207
7208     /**
7209     * Change the RGB components of the elements in the colour palette.
7210     *
7211     * @access public
7212     * @param integer $index colour index
7213     * @param integer $red   red RGB value [0-255]
7214     * @param integer $green green RGB value [0-255]
7215     * @param integer $blue  blue RGB value [0-255]
7216     * @return integer The palette index for the custom color
7217     */
7218     function setCustomColor($index, $red, $green, $blue)
7219     {
7220         // Match a HTML #xxyyzz style parameter
7221         /*if (defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) {
7222             @_ = ($_[0], hex $1, hex $2, hex $3);
7223         }*/
7224
7225         // Check that the colour index is the right range
7226         if ($index < 8 or $index > 64) {
7227             // TODO: assign real error codes
7228             die("Color index $index outside range: 8 <= index <= 64");
7229         }
7230
7231         // Check that the colour components are in the right range
7232         if (($red   < 0 or $red   > 255) ||
7233             ($green < 0 or $green > 255) ||
7234             ($blue  < 0 or $blue  > 255))
7235         {
7236             die("Color component outside range: 0 <= color <= 255");
7237         }
7238
7239         $index -= 8; // Adjust colour index (wingless dragonfly)
7240
7241         // Set the RGB value
7242         $this->_palette[$index] = array($red, $green, $blue, 0);
7243         return($index + 8);
7244     }
7245
7246     /**
7247     * Sets the colour palette to the Excel 97+ default.
7248     *
7249     * @access private
7250     */
7251     function _setPaletteXl97()
7252     {
7253         $this->_palette = array(
7254                            array(0x00, 0x00, 0x00, 0x00),   // 8
7255                            array(0xff, 0xff, 0xff, 0x00),   // 9
7256                            array(0xff, 0x00, 0x00, 0x00),   // 10
7257                            array(0x00, 0xff, 0x00, 0x00),   // 11
7258                            array(0x00, 0x00, 0xff, 0x00),   // 12
7259                            array(0xff, 0xff, 0x00, 0x00),   // 13
7260                            array(0xff, 0x00, 0xff, 0x00),   // 14
7261                            array(0x00, 0xff, 0xff, 0x00),   // 15
7262                            array(0x80, 0x00, 0x00, 0x00),   // 16
7263                            array(0x00, 0x80, 0x00, 0x00),   // 17
7264                            array(0x00, 0x00, 0x80, 0x00),   // 18
7265                            array(0x80, 0x80, 0x00, 0x00),   // 19
7266                            array(0x80, 0x00, 0x80, 0x00),   // 20
7267                            array(0x00, 0x80, 0x80, 0x00),   // 21
7268                            array(0xc0, 0xc0, 0xc0, 0x00),   // 22
7269                            array(0x80, 0x80, 0x80, 0x00),   // 23
7270                            array(0x99, 0x99, 0xff, 0x00),   // 24
7271                            array(0x99, 0x33, 0x66, 0x00),   // 25
7272                            array(0xff, 0xff, 0xcc, 0x00),   // 26
7273                            array(0xcc, 0xff, 0xff, 0x00),   // 27
7274                            array(0x66, 0x00, 0x66, 0x00),   // 28
7275                            array(0xff, 0x80, 0x80, 0x00),   // 29
7276                            array(0x00, 0x66, 0xcc, 0x00),   // 30
7277                            array(0xcc, 0xcc, 0xff, 0x00),   // 31
7278                            array(0x00, 0x00, 0x80, 0x00),   // 32
7279                            array(0xff, 0x00, 0xff, 0x00),   // 33
7280                            array(0xff, 0xff, 0x00, 0x00),   // 34
7281                            array(0x00, 0xff, 0xff, 0x00),   // 35
7282                            array(0x80, 0x00, 0x80, 0x00),   // 36
7283                            array(0x80, 0x00, 0x00, 0x00),   // 37
7284                            array(0x00, 0x80, 0x80, 0x00),   // 38
7285                            array(0x00, 0x00, 0xff, 0x00),   // 39
7286                            array(0x00, 0xcc, 0xff, 0x00),   // 40
7287                            array(0xcc, 0xff, 0xff, 0x00),   // 41
7288                            array(0xcc, 0xff, 0xcc, 0x00),   // 42
7289                            array(0xff, 0xff, 0x99, 0x00),   // 43
7290                            array(0x99, 0xcc, 0xff, 0x00),   // 44
7291                            array(0xff, 0x99, 0xcc, 0x00),   // 45
7292                            array(0xcc, 0x99, 0xff, 0x00),   // 46
7293                            array(0xff, 0xcc, 0x99, 0x00),   // 47
7294                            array(0x33, 0x66, 0xff, 0x00),   // 48
7295                            array(0x33, 0xcc, 0xcc, 0x00),   // 49
7296                            array(0x99, 0xcc, 0x00, 0x00),   // 50
7297                            array(0xff, 0xcc, 0x00, 0x00),   // 51
7298                            array(0xff, 0x99, 0x00, 0x00),   // 52
7299                            array(0xff, 0x66, 0x00, 0x00),   // 53
7300                            array(0x66, 0x66, 0x99, 0x00),   // 54
7301                            array(0x96, 0x96, 0x96, 0x00),   // 55
7302                            array(0x00, 0x33, 0x66, 0x00),   // 56
7303                            array(0x33, 0x99, 0x66, 0x00),   // 57
7304                            array(0x00, 0x33, 0x00, 0x00),   // 58
7305                            array(0x33, 0x33, 0x00, 0x00),   // 59
7306                            array(0x99, 0x33, 0x00, 0x00),   // 60
7307                            array(0x99, 0x33, 0x66, 0x00),   // 61
7308                            array(0x33, 0x33, 0x99, 0x00),   // 62
7309                            array(0x33, 0x33, 0x33, 0x00),   // 63
7310                          );
7311     }
7312
7313     /**
7314     * Assemble worksheets into a workbook and send the BIFF data to an OLE
7315     * storage.
7316     *
7317     * @access private
7318     * @return mixed true on success. PEAR_Error on failure
7319     */
7320     function _storeWorkbook()
7321     {
7322         // Ensure that at least one worksheet has been selected.
7323         if ($this->_activesheet == 0) {
7324             $this->_worksheets[0]->selected = 1;
7325         }
7326
7327         // Calculate the number of selected worksheet tabs and call the finalization
7328         // methods for each worksheet
7329         $total_worksheets = count($this->_worksheets);
7330         for ($i = 0; $i < $total_worksheets; $i++) {
7331             if ($this->_worksheets[$i]->selected) {
7332                 $this->_selected++;
7333             }
7334             $this->_worksheets[$i]->close($this->_sheetnames);
7335         }
7336
7337         // Add Workbook globals
7338         $this->_storeBof(0x0005);
7339         $this->_storeCodepage();
7340         if ($this->_BIFF_version == 0x0600) {
7341             $this->_storeWindow1();
7342         }
7343         if ($this->_BIFF_version == 0x0500) {
7344             $this->_storeExterns();    // For print area and repeat rows
7345         }
7346         $this->_storeNames();      // For print area and repeat rows
7347         if ($this->_BIFF_version == 0x0500) {
7348             $this->_storeWindow1();
7349         }
7350         $this->_storeDatemode();
7351         $this->_storeAllFonts();
7352         $this->_storeAllNumFormats();
7353         $this->_storeAllXfs();
7354         $this->_storeAllStyles();
7355         $this->_storePalette();
7356         $this->_calcSheetOffsets();
7357
7358         // Add BOUNDSHEET records
7359         for ($i = 0; $i < $total_worksheets; $i++) {
7360             $this->_storeBoundsheet($this->_worksheets[$i]->name,$this->_worksheets[$i]->offset);
7361         }
7362
7363         if ($this->_country_code != -1) {
7364             $this->_storeCountry();
7365         }
7366
7367         if ($this->_BIFF_version == 0x0600) {
7368             //$this->_storeSupbookInternal();
7369             /* TODO: store external SUPBOOK records and XCT and CRN records
7370             in case of external references for BIFF8 */
7371             //$this->_storeExternsheetBiff8();
7372             $this->_storeSharedStringsTable();
7373         }
7374
7375         // End Workbook globals
7376         $this->_storeEof();
7377
7378         // Store the workbook in an OLE container
7379         $res = $this->_storeOLEFile();
7380         return true;
7381     }
7382
7383     /**
7384     * Sets the temp dir used for storing the OLE file
7385     *
7386     * @access public
7387     * @param string $dir The dir to be used as temp dir
7388     * @return true if given dir is valid, false otherwise
7389     */
7390     function setTempDir($dir)
7391     {
7392         if (is_dir($dir)) {
7393             $this->_tmp_dir = $dir;
7394             return true;
7395         }
7396         return false;
7397     }
7398
7399     /**
7400     * Store the workbook in an OLE container
7401     *
7402     * @access private
7403     * @return mixed true on success. PEAR_Error on failure
7404     */
7405     function _storeOLEFile()
7406     {
7407         $OLE  = new OLEwriter($this->_filename);
7408         // Write Worksheet data if data <~ 7MB
7409         if ($OLE->setSize($this->_biffsize))
7410         {
7411             $OLE->writeHeader();
7412             $OLE->write($this->_data);
7413             foreach($this->_worksheets as $sheet) 
7414             {
7415                 while ($tmp = $sheet->getData()) {
7416                     $OLE->write($tmp);
7417                 }
7418             }
7419         }
7420         $OLE->close();
7421         return true;
7422     }
7423
7424     /**
7425     * Calculate offsets for Worksheet BOF records.
7426     *
7427     * @access private
7428     */
7429     function _calcSheetOffsets()
7430     {
7431         if ($this->_BIFF_version == 0x0600) {
7432             $boundsheet_length = 12;  // fixed length for a BOUNDSHEET record
7433         } else {
7434             $boundsheet_length = 11;
7435         }
7436         $EOF               = 4;
7437         $offset            = $this->_datasize;
7438
7439         if ($this->_BIFF_version == 0x0600) {
7440             // add the length of the SST
7441             /* TODO: check this works for a lot of strings (> 8224 bytes) */
7442             $offset += $this->_calculateSharedStringsSizes();
7443             if ($this->_country_code != -1) {
7444                 $offset += 8; // adding COUNTRY record
7445             }
7446             // add the lenght of SUPBOOK, EXTERNSHEET and NAME records
7447             //$offset += 8; // FIXME: calculate real value when storing the records
7448         }
7449         $total_worksheets = count($this->_worksheets);
7450         // add the length of the BOUNDSHEET records
7451         for ($i = 0; $i < $total_worksheets; $i++) {
7452             $offset += $boundsheet_length + strlen($this->_worksheets[$i]->name);
7453         }
7454         $offset += $EOF;
7455
7456         for ($i = 0; $i < $total_worksheets; $i++) {
7457             $this->_worksheets[$i]->offset = $offset;
7458             $offset += $this->_worksheets[$i]->_datasize;
7459         }
7460         $this->_biffsize = $offset;
7461     }
7462
7463     /**
7464     * Store the Excel FONT records.
7465     *
7466     * @access private
7467     */
7468     function _storeAllFonts()
7469     {
7470         // tmp_format is added by the constructor. We use this to write the default XF's
7471         $format = $this->_tmp_format;
7472         $font   = $format->getFont();
7473
7474         // Note: Fonts are 0-indexed. According to the SDK there is no index 4,
7475         // so the following fonts are 0, 1, 2, 3, 5
7476         //
7477         for ($i = 1; $i <= 5; $i++){
7478             $this->_append($font);
7479         }
7480
7481         // Iterate through the XF objects and write a FONT record if it isn't the
7482         // same as the default FONT and if it hasn't already been used.
7483         //
7484         $fonts = array();
7485         $index = 6;                  // The first user defined FONT
7486
7487         $key = $format->getFontKey(); // The default font from _tmp_format
7488         $fonts[$key] = 0;             // Index of the default font
7489
7490         $total_formats = count($this->_formats);
7491         for ($i = 0; $i < $total_formats; $i++) {
7492             $key = $this->_formats[$i]->getFontKey();
7493             if (isset($fonts[$key])) {
7494                 // FONT has already been used
7495                 $this->_formats[$i]->font_index = $fonts[$key];
7496             } else {
7497                 // Add a new FONT record
7498                 $fonts[$key]        = $index;
7499                 $this->_formats[$i]->font_index = $index;
7500                 $index++;
7501                 $font = $this->_formats[$i]->getFont();
7502                 $this->_append($font);
7503             }
7504         }
7505     }
7506
7507     /**
7508     * Store user defined numerical formats i.e. FORMAT records
7509     *
7510     * @access private
7511     */
7512     function _storeAllNumFormats()
7513     {
7514         // Leaning num_format syndrome
7515         $hash_num_formats = array();
7516         $num_formats      = array();
7517         $index = 164;
7518
7519         // Iterate through the XF objects and write a FORMAT record if it isn't a
7520         // built-in format type and if the FORMAT string hasn't already been used.
7521         $total_formats = count($this->_formats);
7522         for ($i = 0; $i < $total_formats; $i++) {
7523             $num_format = $this->_formats[$i]->_num_format;
7524
7525             // Check if $num_format is an index to a built-in format.
7526             // Also check for a string of zeros, which is a valid format string
7527             // but would evaluate to zero.
7528             //
7529             if (!preg_match("/^0+\d/", $num_format)) {
7530                 if (preg_match("/^\d+$/", $num_format)) { // built-in format
7531                     continue;
7532                 }
7533             }
7534
7535             if (isset($hash_num_formats[$num_format])) {
7536                 // FORMAT has already been used
7537                 $this->_formats[$i]->_num_format = $hash_num_formats[$num_format];
7538             } else{
7539                 // Add a new FORMAT
7540                 $hash_num_formats[$num_format]  = $index;
7541                 $this->_formats[$i]->_num_format = $index;
7542                 array_push($num_formats,$num_format);
7543                 $index++;
7544             }
7545         }
7546
7547         // Write the new FORMAT records starting from 0xA4
7548         $index = 164;
7549         foreach ($num_formats as $num_format) {
7550             $this->_storeNumFormat($num_format,$index);
7551             $index++;
7552         }
7553     }
7554
7555     /**
7556     * Write all XF records.
7557     *
7558     * @access private
7559     */
7560     function _storeAllXfs()
7561     {
7562         // _tmp_format is added by the constructor. We use this to write the default XF's
7563         // The default font index is 0
7564         //
7565         $format = $this->_tmp_format;
7566         for ($i = 0; $i <= 14; $i++) {
7567             $xf = $format->getXf('style'); // Style XF
7568             $this->_append($xf);
7569         }
7570
7571         $xf = $format->getXf('cell');      // Cell XF
7572         $this->_append($xf);
7573
7574         // User defined XFs
7575         $total_formats = count($this->_formats);
7576         for ($i = 0; $i < $total_formats; $i++) {
7577             $xf = $this->_formats[$i]->getXf('cell');
7578             $this->_append($xf);
7579         }
7580     }
7581
7582     /**
7583     * Write all STYLE records.
7584     *
7585     * @access private
7586     */
7587     function _storeAllStyles()
7588     {
7589         $this->_storeStyle();
7590     }
7591
7592     /**
7593     * Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
7594     * the NAME records.
7595     *
7596     * @access private
7597     */
7598     function _storeExterns()
7599     {
7600         // Create EXTERNCOUNT with number of worksheets
7601         $this->_storeExterncount(count($this->_worksheets));
7602
7603         // Create EXTERNSHEET for each worksheet
7604         foreach ($this->_sheetnames as $sheetname) {
7605             $this->_storeExternsheet($sheetname);
7606         }
7607     }
7608
7609     /**
7610     * Write the NAME record to define the print area and the repeat rows and cols.
7611     *
7612     * @access private
7613     */
7614     function _storeNames()
7615     {
7616         // Create the print area NAME records
7617         $total_worksheets = count($this->_worksheets);
7618         for ($i = 0; $i < $total_worksheets; $i++) {
7619             // Write a Name record if the print area has been defined
7620             if (isset($this->_worksheets[$i]->print_rowmin)) {
7621                 $this->_storeNameShort(
7622                     $this->_worksheets[$i]->index,
7623                     0x06, // NAME type
7624                     $this->_worksheets[$i]->print_rowmin,
7625                     $this->_worksheets[$i]->print_rowmax,
7626                     $this->_worksheets[$i]->print_colmin,
7627                     $this->_worksheets[$i]->print_colmax
7628                     );
7629             }
7630         }
7631
7632         // Create the print title NAME records
7633         $total_worksheets = count($this->_worksheets);
7634         for ($i = 0; $i < $total_worksheets; $i++) {
7635             $rowmin = $this->_worksheets[$i]->title_rowmin;
7636             $rowmax = $this->_worksheets[$i]->title_rowmax;
7637             $colmin = $this->_worksheets[$i]->title_colmin;
7638             $colmax = $this->_worksheets[$i]->title_colmax;
7639
7640             // Determine if row + col, row, col or nothing has been defined
7641             // and write the appropriate record
7642             //
7643             if (isset($rowmin) && isset($colmin)) {
7644                 // Row and column titles have been defined.
7645                 // Row title has been defined.
7646                 $this->_storeNameLong(
7647                     $this->_worksheets[$i]->index,
7648                     0x07, // NAME type
7649                     $rowmin,
7650                     $rowmax,
7651                     $colmin,
7652                     $colmax
7653                     );
7654             } elseif (isset($rowmin)) {
7655                 // Row title has been defined.
7656                 $this->_storeNameShort(
7657                     $this->_worksheets[$i]->index,
7658                     0x07, // NAME type
7659                     $rowmin,
7660                     $rowmax,
7661                     0x00,
7662                     0xff
7663                     );
7664             } elseif (isset($colmin)) {
7665                 // Column title has been defined.
7666                 $this->_storeNameShort(
7667                     $this->_worksheets[$i]->index,
7668                     0x07, // NAME type
7669                     0x0000,
7670                     0x3fff,
7671                     $colmin,
7672                     $colmax
7673                     );
7674             } else {
7675                 // Print title hasn't been defined.
7676             }
7677         }
7678     }
7679
7680
7681
7682
7683     /******************************************************************************
7684     *
7685     * BIFF RECORDS
7686     *
7687     */
7688
7689     /**
7690     * Stores the CODEPAGE biff record.
7691     *
7692     * @access private
7693     */
7694     function _storeCodepage()
7695     {
7696         $record          = 0x0042;             // Record identifier
7697         $length          = 0x0002;             // Number of bytes to follow
7698         $cv              = $this->_codepage;   // The code page
7699
7700         $header          = pack('vv', $record, $length);
7701         $data            = pack('v',  $cv);
7702
7703         $this->_append($header . $data);
7704     }
7705
7706     /**
7707     * Write Excel BIFF WINDOW1 record.
7708     *
7709     * @access private
7710     */
7711     function _storeWindow1()
7712     {
7713         $record    = 0x003D;                 // Record identifier
7714         $length    = 0x0012;                 // Number of bytes to follow
7715
7716         $xWn       = 0x0000;                 // Horizontal position of window
7717         $yWn       = 0x0000;                 // Vertical position of window
7718         $dxWn      = 0x25BC;                 // Width of window
7719         $dyWn      = 0x1572;                 // Height of window
7720
7721         $grbit     = 0x0038;                 // Option flags
7722         $ctabsel   = $this->_selected;       // Number of workbook tabs selected
7723         $wTabRatio = 0x0258;                 // Tab to scrollbar ratio
7724
7725         $itabFirst = $this->_firstsheet;     // 1st displayed worksheet
7726         $itabCur   = $this->_activesheet;    // Active worksheet
7727
7728         $header    = pack("vv",        $record, $length);
7729         $data      = pack("vvvvvvvvv", $xWn, $yWn, $dxWn, $dyWn,
7730                                        $grbit,
7731                                        $itabCur, $itabFirst,
7732                                        $ctabsel, $wTabRatio);
7733         $this->_append($header . $data);
7734     }
7735
7736     /**
7737     * Writes Excel BIFF BOUNDSHEET record.
7738     * FIXME: inconsistent with BIFF documentation
7739     *
7740     * @param string  $sheetname Worksheet name
7741     * @param integer $offset    Location of worksheet BOF
7742     * @access private
7743     */
7744     function _storeBoundsheet($sheetname,$offset)
7745     {
7746         $record    = 0x0085;                    // Record identifier
7747 /*        
7748         if ($this->_BIFF_version == 0x0600)     // Tried to fix the correct handling here, with the
7749         {                                                                               // corrected specification from M$ - Joe Hunt 2009-03-08
7750                 global $encoding_string;
7751                 if ($encoding_string == 'UTF-16LE')
7752                 {
7753                     $strlen = function_exists('mb_strlen') ? mb_strlen($sheetname, 'UTF-16LE') : (strlen($sheetname) / 2);
7754                     $encoding  = 0x1;
7755                 }
7756                 else if ($encoding_string != '')
7757                 {
7758                     $sheetname = iconv($encoding_string, 'UTF-16LE', $sheetname);
7759                     $strlen = function_exists('mb_strlen') ? mb_strlen($sheetname, 'UTF-16LE') : (strlen($sheetname) / 2);
7760                     $encoding  = 0x1;
7761                 }
7762                 if ($strlen % 2 != 0)
7763                         $strlen++;
7764                 $encoding  = 0x1;
7765                 
7766             //$strlen = strlen($sheetname);
7767             $length    = 0x08 + $strlen;                // Number of bytes to follow
7768         } else {
7769                 $strlen = strlen($sheetname);
7770             $length = 0x07 + $strlen;                   // Number of bytes to follow
7771         }
7772
7773         $grbit     = 0x0000;                    // Visibility and sheet type
7774         $cch       = $strlen;                           // Length of sheet name
7775
7776         $header    = pack("vv",  $record, $length);
7777         if ($this->_BIFF_version == 0x0600) {
7778             $data      = pack("VvCC", $offset, $grbit, $cch, $encoding);
7779         } else {
7780             $data      = pack("VvC", $offset, $grbit, $cch);
7781         }
7782 */        
7783         if ($this->_BIFF_version == 0x0600) 
7784         {
7785             $strlen = strlen($sheetname);
7786             $length    = 0x08 + $strlen;                // Number of bytes to follow
7787         } else {
7788                 $strlen = strlen($sheetname);
7789             $length = 0x07 + $strlen;                   // Number of bytes to follow
7790         }
7791
7792         $grbit     = 0x0000;                    // Visibility and sheet type
7793         $cch       = $strlen;                           // Length of sheet name
7794
7795         $header    = pack("vv",  $record, $length);
7796         if ($this->_BIFF_version == 0x0600) {
7797             $data      = pack("Vvv", $offset, $grbit, $cch);
7798         } else {
7799             $data      = pack("VvC", $offset, $grbit, $cch);
7800         }
7801         $this->_append($header.$data.$sheetname);
7802     }
7803
7804     /**
7805     * Write Internal SUPBOOK record
7806     *
7807     * @access private
7808     */
7809     function _storeSupbookInternal()
7810     {
7811         $record    = 0x01AE;   // Record identifier
7812         $length    = 0x0004;   // Bytes to follow
7813
7814         $header    = pack("vv", $record, $length);
7815         $data      = pack("vv", count($this->_worksheets), 0x0104);
7816         $this->_append($header . $data);
7817     }
7818
7819     /**
7820     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
7821     * formulas.
7822     *
7823     * @param string $sheetname Worksheet name
7824     * @access private
7825     */
7826     function _storeExternsheetBiff8()
7827     {
7828         $total_references = count($this->_parser->_references);
7829         $record   = 0x0017;                     // Record identifier
7830         $length   = 2 + 6 * $total_references;  // Number of bytes to follow
7831
7832         $supbook_index = 0;           // FIXME: only using internal SUPBOOK record
7833         $header           = pack("vv",  $record, $length);
7834         $data             = pack('v', $total_references);
7835         for ($i = 0; $i < $total_references; $i++) {
7836             $data .= $this->_parser->_references[$i];
7837         }
7838         $this->_append($header . $data);
7839     }
7840
7841     /**
7842     * Write Excel BIFF STYLE records.
7843     *
7844     * @access private
7845     */
7846     function _storeStyle()
7847     {
7848         $record    = 0x0293;   // Record identifier
7849         $length    = 0x0004;   // Bytes to follow
7850
7851         $ixfe      = 0x8000;   // Index to style XF
7852         $BuiltIn   = 0x00;     // Built-in style
7853         $iLevel    = 0xff;     // Outline style level
7854
7855         $header    = pack("vv",  $record, $length);
7856         $data      = pack("vCC", $ixfe, $BuiltIn, $iLevel);
7857         $this->_append($header . $data);
7858     }
7859
7860
7861     /**
7862     * Writes Excel FORMAT record for non "built-in" numerical formats.
7863     *
7864     * @param string  $format Custom format string
7865     * @param integer $ifmt   Format index code
7866     * @access private
7867     */
7868     function _storeNumFormat($format, $ifmt)
7869     {
7870         $record    = 0x041E;                      // Record identifier
7871
7872         if ($this->_BIFF_version == 0x0600) {
7873             $length    = 5 + strlen($format);      // Number of bytes to follow
7874             $encoding = 0x0;
7875         } elseif ($this->_BIFF_version == 0x0500) {
7876             $length    = 3 + strlen($format);      // Number of bytes to follow
7877         }
7878
7879         $cch       = strlen($format);             // Length of format string
7880
7881         $header    = pack("vv", $record, $length);
7882         if ($this->_BIFF_version == 0x0600) {
7883             $data      = pack("vvC", $ifmt, $cch, $encoding);
7884         } elseif ($this->_BIFF_version == 0x0500) {
7885             $data      = pack("vC", $ifmt, $cch);
7886         }
7887         $this->_append($header . $data . $format);
7888     }
7889
7890     /**
7891     * Write DATEMODE record to indicate the date system in use (1904 or 1900).
7892     *
7893     * @access private
7894     */
7895     function _storeDatemode()
7896     {
7897         $record    = 0x0022;         // Record identifier
7898         $length    = 0x0002;         // Bytes to follow
7899
7900         $f1904     = $this->_1904;   // Flag for 1904 date system
7901
7902         $header    = pack("vv", $record, $length);
7903         $data      = pack("v", $f1904);
7904         $this->_append($header . $data);
7905     }
7906
7907
7908     /**
7909     * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
7910     * references in the workbook.
7911     *
7912     * Excel only stores references to external sheets that are used in NAME.
7913     * The workbook NAME record is required to define the print area and the repeat
7914     * rows and columns.
7915     *
7916     * A similar method is used in Worksheet.php for a slightly different purpose.
7917     *
7918     * @param integer $cxals Number of external references
7919     * @access private
7920     */
7921     function _storeExterncount($cxals)
7922     {
7923         $record   = 0x0016;          // Record identifier
7924         $length   = 0x0002;          // Number of bytes to follow
7925
7926         $header   = pack("vv", $record, $length);
7927         $data     = pack("v",  $cxals);
7928         $this->_append($header . $data);
7929     }
7930
7931
7932     /**
7933     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
7934     * formulas. NAME record is required to define the print area and the repeat
7935     * rows and columns.
7936     *
7937     * A similar method is used in Worksheet.php for a slightly different purpose.
7938     *
7939     * @param string $sheetname Worksheet name
7940     * @access private
7941     */
7942     function _storeExternsheet($sheetname)
7943     {
7944         $record      = 0x0017;                     // Record identifier
7945         $length      = 0x02 + strlen($sheetname);  // Number of bytes to follow
7946
7947         $cch         = strlen($sheetname);         // Length of sheet name
7948         $rgch        = 0x03;                       // Filename encoding
7949
7950         $header      = pack("vv",  $record, $length);
7951         $data        = pack("CC", $cch, $rgch);
7952         $this->_append($header . $data . $sheetname);
7953     }
7954
7955
7956     /**
7957     * Store the NAME record in the short format that is used for storing the print
7958     * area, repeat rows only and repeat columns only.
7959     *
7960     * @param integer $index  Sheet index
7961     * @param integer $type   Built-in name type
7962     * @param integer $rowmin Start row
7963     * @param integer $rowmax End row
7964     * @param integer $colmin Start colum
7965     * @param integer $colmax End column
7966     * @access private
7967     */
7968     function _storeNameShort($index, $type, $rowmin, $rowmax, $colmin, $colmax)
7969     {
7970         $record          = 0x0018;       // Record identifier
7971         $length          = 0x0024;       // Number of bytes to follow
7972
7973         $grbit           = 0x0020;       // Option flags
7974         $chKey           = 0x00;         // Keyboard shortcut
7975         $cch             = 0x01;         // Length of text name
7976         $cce             = 0x0015;       // Length of text definition
7977         $ixals           = $index + 1;   // Sheet index
7978         $itab            = $ixals;       // Equal to ixals
7979         $cchCustMenu     = 0x00;         // Length of cust menu text
7980         $cchDescription  = 0x00;         // Length of description text
7981         $cchHelptopic    = 0x00;         // Length of help topic text
7982         $cchStatustext   = 0x00;         // Length of status bar text
7983         $rgch            = $type;        // Built-in name type
7984
7985         $unknown03       = 0x3b;
7986         $unknown04       = 0xffff-$index;
7987         $unknown05       = 0x0000;
7988         $unknown06       = 0x0000;
7989         $unknown07       = 0x1087;
7990         $unknown08       = 0x8005;
7991
7992         $header             = pack("vv", $record, $length);
7993         $data               = pack("v", $grbit);
7994         $data              .= pack("C", $chKey);
7995         $data              .= pack("C", $cch);
7996         $data              .= pack("v", $cce);
7997         $data              .= pack("v", $ixals);
7998         $data              .= pack("v", $itab);
7999         $data              .= pack("C", $cchCustMenu);
8000         $data              .= pack("C", $cchDescription);
8001         $data              .= pack("C", $cchHelptopic);
8002         $data              .= pack("C", $cchStatustext);
8003         $data              .= pack("C", $rgch);
8004         $data              .= pack("C", $unknown03);
8005         $data              .= pack("v", $unknown04);
8006         $data              .= pack("v", $unknown05);
8007         $data              .= pack("v", $unknown06);
8008         $data              .= pack("v", $unknown07);
8009         $data              .= pack("v", $unknown08);
8010         $data              .= pack("v", $index);
8011         $data              .= pack("v", $index);
8012         $data              .= pack("v", $rowmin);
8013         $data              .= pack("v", $rowmax);
8014         $data              .= pack("C", $colmin);
8015         $data              .= pack("C", $colmax);
8016         $this->_append($header . $data);
8017     }
8018
8019
8020     /**
8021     * Store the NAME record in the long format that is used for storing the repeat
8022     * rows and columns when both are specified. This shares a lot of code with
8023     * _storeNameShort() but we use a separate method to keep the code clean.
8024     * Code abstraction for reuse can be carried too far, and I should know. ;-)
8025     *
8026     * @param integer $index Sheet index
8027     * @param integer $type  Built-in name type
8028     * @param integer $rowmin Start row
8029     * @param integer $rowmax End row
8030     * @param integer $colmin Start colum
8031     * @param integer $colmax End column
8032     * @access private
8033     */
8034     function _storeNameLong($index, $type, $rowmin, $rowmax, $colmin, $colmax)
8035     {
8036         $record          = 0x0018;       // Record identifier
8037         $length          = 0x003d;       // Number of bytes to follow
8038         $grbit           = 0x0020;       // Option flags
8039         $chKey           = 0x00;         // Keyboard shortcut
8040         $cch             = 0x01;         // Length of text name
8041         $cce             = 0x002e;       // Length of text definition
8042         $ixals           = $index + 1;   // Sheet index
8043         $itab            = $ixals;       // Equal to ixals
8044         $cchCustMenu     = 0x00;         // Length of cust menu text
8045         $cchDescription  = 0x00;         // Length of description text
8046         $cchHelptopic    = 0x00;         // Length of help topic text
8047         $cchStatustext   = 0x00;         // Length of status bar text
8048         $rgch            = $type;        // Built-in name type
8049
8050         $unknown01       = 0x29;
8051         $unknown02       = 0x002b;
8052         $unknown03       = 0x3b;
8053         $unknown04       = 0xffff-$index;
8054         $unknown05       = 0x0000;
8055         $unknown06       = 0x0000;
8056         $unknown07       = 0x1087;
8057         $unknown08       = 0x8008;
8058
8059         $header             = pack("vv",  $record, $length);
8060         $data               = pack("v", $grbit);
8061         $data              .= pack("C", $chKey);
8062         $data              .= pack("C", $cch);
8063         $data              .= pack("v", $cce);
8064         $data              .= pack("v", $ixals);
8065         $data              .= pack("v", $itab);
8066         $data              .= pack("C", $cchCustMenu);
8067         $data              .= pack("C", $cchDescription);
8068         $data              .= pack("C", $cchHelptopic);
8069         $data              .= pack("C", $cchStatustext);
8070         $data              .= pack("C", $rgch);
8071         $data              .= pack("C", $unknown01);
8072         $data              .= pack("v", $unknown02);
8073         // Column definition
8074         $data              .= pack("C", $unknown03);
8075         $data              .= pack("v", $unknown04);
8076         $data              .= pack("v", $unknown05);
8077         $data              .= pack("v", $unknown06);
8078         $data              .= pack("v", $unknown07);
8079         $data              .= pack("v", $unknown08);
8080         $data              .= pack("v", $index);
8081         $data              .= pack("v", $index);
8082         $data              .= pack("v", 0x0000);
8083         $data              .= pack("v", 0x3fff);
8084         $data              .= pack("C", $colmin);
8085         $data              .= pack("C", $colmax);
8086         // Row definition
8087         $data              .= pack("C", $unknown03);
8088         $data              .= pack("v", $unknown04);
8089         $data              .= pack("v", $unknown05);
8090         $data              .= pack("v", $unknown06);
8091         $data              .= pack("v", $unknown07);
8092         $data              .= pack("v", $unknown08);
8093         $data              .= pack("v", $index);
8094         $data              .= pack("v", $index);
8095         $data              .= pack("v", $rowmin);
8096         $data              .= pack("v", $rowmax);
8097         $data              .= pack("C", 0x00);
8098         $data              .= pack("C", 0xff);
8099         // End of data
8100         $data              .= pack("C", 0x10);
8101         $this->_append($header . $data);
8102     }
8103
8104     /**
8105     * Stores the COUNTRY record for localization
8106     *
8107     * @access private
8108     */
8109     function _storeCountry()
8110     {
8111         $record          = 0x008C;    // Record identifier
8112         $length          = 4;         // Number of bytes to follow
8113
8114         $header = pack('vv',  $record, $length);
8115         /* using the same country code always for simplicity */
8116         $data = pack('vv', $this->_country_code, $this->_country_code);
8117         $this->_append($header . $data);
8118     }
8119
8120     /**
8121     * Stores the PALETTE biff record.
8122     *
8123     * @access private
8124     */
8125     function _storePalette()
8126     {
8127         $aref            = $this->_palette;
8128
8129         $record          = 0x0092;                 // Record identifier
8130         $length          = 2 + 4 * count($aref);   // Number of bytes to follow
8131         $ccv             =         count($aref);   // Number of RGB values to follow
8132         $data = '';                                // The RGB data
8133
8134         // Pack the RGB data
8135         foreach ($aref as $color) {
8136             foreach ($color as $byte) {
8137                 $data .= pack("C",$byte);
8138             }
8139         }
8140
8141         $header = pack("vvv",  $record, $length, $ccv);
8142         $this->_append($header . $data);
8143     }
8144
8145     /**
8146     * Calculate
8147     * Handling of the SST continue blocks is complicated by the need to include an
8148     * additional continuation byte depending on whether the string is split between
8149     * blocks or whether it starts at the beginning of the block. (There are also
8150     * additional complications that will arise later when/if Rich Strings are
8151     * supported).
8152     *
8153     * @access private
8154     */
8155     function _calculateSharedStringsSizes()
8156     {
8157         /* Iterate through the strings to calculate the CONTINUE block sizes.
8158            For simplicity we use the same size for the SST and CONTINUE records:
8159            8228 : Maximum Excel97 block size
8160              -4 : Length of block header
8161              -8 : Length of additional SST header information
8162          = 8216
8163         */
8164         $continue_limit     = 8216;
8165         $block_length       = 0;
8166         $written            = 0;
8167         $this->_block_sizes = array();
8168         $continue           = 0;
8169
8170         foreach (array_keys($this->_str_table) as $string) {
8171             $string_length = strlen($string);
8172
8173             // Block length is the total length of the strings that will be
8174             // written out in a single SST or CONTINUE block.
8175             $block_length += $string_length;
8176
8177             // We can write the string if it doesn't cross a CONTINUE boundary
8178             if ($block_length < $continue_limit) {
8179                 $written      += $string_length;
8180                 continue;
8181             }
8182
8183             // Deal with the cases where the next string to be written will exceed
8184             // the CONTINUE boundary. If the string is very long it may need to be
8185             // written in more than one CONTINUE record.
8186             while ($block_length >= $continue_limit) {
8187
8188                 // We need to avoid the case where a string is continued in the first
8189                 // n bytes that contain the string header information.
8190                 $header_length   = 3; // Min string + header size -1
8191                 $space_remaining = $continue_limit - $written - $continue;
8192
8193
8194                 /* TODO: Unicode data should only be split on char (2 byte)
8195                 boundaries. Therefore, in some cases we need to reduce the
8196                 amount of available
8197                 */
8198
8199                 if ($space_remaining > $header_length) {
8200                     // Write as much as possible of the string in the current block
8201                     $written      += $space_remaining;
8202
8203                     // Reduce the current block length by the amount written
8204                     $block_length -= $continue_limit - $continue;
8205
8206                     // Store the max size for this block
8207                     $this->_block_sizes[] = $continue_limit;
8208
8209                     // If the current string was split then the next CONTINUE block
8210                     // should have the string continue flag (grbit) set unless the
8211                     // split string fits exactly into the remaining space.
8212                     if ($block_length > 0) {
8213                         $continue = 1;
8214                     } else {
8215                         $continue = 0;
8216                     }
8217                 } else {
8218                     // Store the max size for this block
8219                     $this->_block_sizes[] = $written + $continue;
8220
8221                     // Not enough space to start the string in the current block
8222                     $block_length -= $continue_limit - $space_remaining - $continue;
8223                     $continue = 0;
8224
8225                 }
8226
8227                 // If the string (or substr) is small enough we can write it in the
8228                 // new CONTINUE block. Else, go through the loop again to write it in
8229                 // one or more CONTINUE blocks
8230                 if ($block_length < $continue_limit) {
8231                     $written = $block_length;
8232                 } else {
8233                     $written = 0;
8234                 }
8235             }
8236         }
8237
8238         // Store the max size for the last block unless it is empty
8239         if ($written + $continue) {
8240             $this->_block_sizes[] = $written + $continue;
8241         }
8242
8243
8244         /* Calculate the total length of the SST and associated CONTINUEs (if any).
8245          The SST record will have a length even if it contains no strings.
8246          This length is required to set the offsets in the BOUNDSHEET records since
8247          they must be written before the SST records
8248         */
8249         $total_offset = array_sum($this->_block_sizes);
8250         // SST information
8251         $total_offset += 8;
8252         if (!empty($this->_block_sizes)) {
8253             $total_offset += (count($this->_block_sizes)) * 4; // add CONTINUE headers
8254         }
8255         return $total_offset;
8256     }
8257
8258     /**
8259     * Write all of the workbooks strings into an indexed array.
8260     * See the comments in _calculate_shared_string_sizes() for more information.
8261     *
8262     * The Excel documentation says that the SST record should be followed by an
8263     * EXTSST record. The EXTSST record is a hash table that is used to optimise
8264     * access to SST. However, despite the documentation it doesn't seem to be
8265     * required so we will ignore it.
8266     *
8267     * @access private
8268     */
8269     function _storeSharedStringsTable()
8270     {
8271         global $encoding_string;
8272
8273         $record  = 0x00fc;  // Record identifier
8274         // sizes are upside down
8275         $this->_block_sizes = array_reverse($this->_block_sizes);
8276         $length = array_pop($this->_block_sizes) + 8; // First block size plus SST information
8277
8278         // Write the SST block header information
8279         $header      = pack("vv", $record, $length);
8280         $data        = pack("VV", $this->_str_total, $this->_str_unique);
8281         $this->_append($header . $data);
8282
8283
8284         // Iterate through the strings to calculate the CONTINUE block sizes
8285         $continue_limit = 8216;
8286         $block_length   = 0;
8287         $written        = 0;
8288         $continue       = 0;
8289
8290
8291         /* TODO: not good for performance */
8292         foreach (array_keys($this->_str_table) as $string) {
8293
8294             $string_length = strlen($string);
8295             $encoding      = $encoding_string ? 1:0; // this is FA specific assumption
8296             $split_string  = 0;
8297
8298             // Block length is the total length of the strings that will be
8299             // written out in a single SST or CONTINUE block.
8300             //
8301             $block_length += $string_length;
8302
8303
8304             // We can write the string if it doesn't cross a CONTINUE boundary
8305             if ($block_length < $continue_limit) {
8306                 $this->_append($string);
8307                 $written += $string_length;
8308                 continue;
8309             }
8310
8311             // Deal with the cases where the next string to be written will exceed
8312             // the CONTINUE boundary. If the string is very long it may need to be
8313             // written in more than one CONTINUE record.
8314             //
8315             while ($block_length >= $continue_limit) {
8316
8317                 // We need to avoid the case where a string is continued in the first
8318                 // n bytes that contain the string header information.
8319                 //
8320                 $header_length   = 3; // Min string + header size -1
8321                 $space_remaining = $continue_limit - $written - $continue;
8322
8323
8324                 // Unicode data should only be split on char (2 byte) boundaries.
8325                 // Therefore, in some cases we need to reduce the amount of available
8326
8327                 if ($space_remaining > $header_length) {
8328                     // Write as much as possible of the string in the current block
8329                     $tmp = substr($string, 0, $space_remaining);
8330                     $this->_append($tmp);
8331
8332                     // The remainder will be written in the next block(s)
8333                     $string = substr($string, $space_remaining);
8334
8335                     // Reduce the current block length by the amount written
8336                     $block_length -= $continue_limit - $continue;
8337
8338                     // If the current string was split then the next CONTINUE block
8339                     // should have the string continue flag (grbit) set unless the
8340                     // split string fits exactly into the remaining space.
8341                     //
8342                     if ($block_length > 0) {
8343                         $continue = 1;
8344                     } else {
8345                         $continue = 0;
8346                     }
8347                 } else {
8348                     // Not enough space to start the string in the current block
8349                     $block_length -= $continue_limit - $space_remaining - $continue;
8350                     $continue = 0;
8351                 }
8352
8353                 // Write the CONTINUE block header
8354                 if (!empty($this->_block_sizes)) {
8355                     $record  = 0x003C;
8356                     $length  = array_pop($this->_block_sizes);
8357                     $header  = pack('vv', $record, $length);
8358                     if ($continue) {
8359                         $header .= pack('C', $encoding);
8360                     }
8361                     $this->_append($header);
8362                 }
8363
8364                 // If the string (or substr) is small enough we can write it in the
8365                 // new CONTINUE block. Else, go through the loop again to write it in
8366                 // one or more CONTINUE blocks
8367                 //
8368                 if ($block_length < $continue_limit) {
8369                     $this->_append($string);
8370                     $written = $block_length;
8371                 } else {
8372                     $written = 0;
8373                 }
8374             }
8375         }
8376     }
8377 }
8378 ?>