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