]> git.mjollnir.org Git - moodle.git/commitdiff
changed the excel writing library to a more fully-featured, less buggy
authorjungwirr <jungwirr>
Wed, 24 Sep 2003 19:10:14 +0000 (19:10 +0000)
committerjungwirr <jungwirr>
Wed, 24 Sep 2003 19:10:14 +0000 (19:10 +0000)
one.

mod/attendance/viewall.php
mod/attendance/viewweek.php
mod/attendance/write_excel/BIFFwriter.php [new file with mode: 0644]
mod/attendance/write_excel/Format.php [new file with mode: 0644]
mod/attendance/write_excel/OLEwriter.php [new file with mode: 0644]
mod/attendance/write_excel/Parser.php [new file with mode: 0644]
mod/attendance/write_excel/Workbook.php [new file with mode: 0644]
mod/attendance/write_excel/Worksheet.php [new file with mode: 0644]
mod/attendance/write_excel/test.php [new file with mode: 0644]

index c5d235e1e9082f27951dba371c832879e71d5524..ed5e0bc5de704d16445365a07b59bff12386e1bd 100644 (file)
@@ -77,40 +77,37 @@ if ($attendances) {
 //
 //
 
-
 if ($download == "xls") {
-    require_once("../../lib/psxlsgen.php");
+    require_once("write_excel/Worksheet.php");
+    require_once("write_excel/Workbook.php");
+  // HTTP headers
+  attendance_HeaderingExcel($course->shortname."_Attendance.xls");
+  // Creating a workbook
+  $workbook = new Workbook("-");
+  // Creating the first worksheet
+  $myxls =& $workbook->add_worksheet('Grades');
 
-// add up the total columns that are required for the whole report
-    $myxls = new PhpSimpleXlsGen();
-if ($dlsub== "all") {
-    $myxls->totalcol=4+$numatt; // first,last,id ---...---> total
-} else {
-    $myxls->totalcol=4; // first,last,id ---...---> total
-}
     // print the date headings at the top of the table
     // for each day of attendance
-    $myxls->ChangePos(2,0);
-    $myxls->InsertText(get_string("lastname"));
-    $myxls->InsertText(get_string("firstname"));
-    $myxls->InsertText(get_string("idnumber"));
+    $myxls->write_string(3,0,get_string("lastname"));
+    $myxls->write_string(3,1,get_string("firstname"));
+    $myxls->write_string(3,2,get_string("idnumber"));
     $pos=3;
 if ($dlsub== "all") {
     for($k=0;$k<$numatt;$k++)  {
     // put notes for the date in the date heading
-           $myxls->ChangePos(0,$pos);
-           $myxls->InsertText(userdate($atts[$k]->attendance->day,"%m/%0d"));
-           $myxls->ChangePos(1,$pos);
-           $myxls->InsertText($atts[$k]->attendance->notes);
-                       $myxls->ChangePos(2,$pos);
+           $myxls->write_string(1,$pos,userdate($atts[$k]->attendance->day,"%m/%0d"));
+           $myxls->set_column($pos,$pos,5);
+           $myxls->write_string(2,$pos,$atts[$k]->attendance->notes);
                        for ($i=1;$i<=$atts[$k]->attendance->hours;$i++) {
-                               $myxls->InsertText($i);
+                               $myxls->write_number(3,$pos,$i);
+           $myxls->set_column($pos,$pos,1);
                                $pos++;
                        }
     }
 }  // if dlsub==all
-               $myxls->ChangePos(2,$pos);
-               $myxls->InsertText(get_string("total"));
+               $myxls->write_string(3,$pos,get_string("total"));
+           $myxls->set_column($pos,$pos,5);
                
 /// generate the attendance rolls for the body of the spreadsheet
   if (isstudent($course->id)) { 
@@ -122,13 +119,13 @@ if ($dlsub== "all") {
   $A = get_string("absentshort","attendance");
   $T = get_string("tardyshort","attendance");
   $P = get_string("presentshort","attendance");  
-  $row=3;
+  $row=4;
   foreach ($students as $student) {
-    $myxls->ChangePos($row,0);
-    $myxls->InsertText($student->lastname);
-    $myxls->InsertText($student->firstname);
+    $myxls->write_string($row,0,$student->lastname);
+    $myxls->write_string($row,1,$student->firstname);
     $studentid=(($student->idnumber != "") ? $student->idnumber : " ");
-    $myxls->InsertText($studentid);
+    $myxls->write_string($row,2,$studentid);
+    $pos=3;
     if ($dlsub== "all") {
            for($k=0;$k<$numatt;$k++)  { // for each day of attendance for the student
                  for($j=1;$j<=$atts[$k]->attendance->hours;$j++) {
@@ -136,7 +133,8 @@ if ($dlsub== "all") {
                    if ($atts[$k]->sroll[$student->id][$j]->status == 1) {$status=$T;}
                      elseif ($atts[$k]->sroll[$student->id][$j]->status == 2) {$status=$A;}
                      else {$status=$P;}
-               $myxls->InsertText($status);
+               $myxls->write_string($row,$pos,$status);
+               $pos++;
                    } /// for loop
            }
     }
@@ -149,14 +147,15 @@ if ($dlsub== "all") {
                  } /// for loop
          } // outer for for each day of attendance
     $tot=tally_overall_absences_decimal($abs,$tar);
-    $myxls->InsertNumber($tot);
+    $myxls->write_number($row,$pos,$tot);
                $row++;
   }
-  $myxls->SendFileName($course->shortname."_Attendance");
+  $workbook->close();
 
   exit;
 }
 
+
 if ($download == "txt") {
 
         header("Content-Type: application/download\n"); 
@@ -513,4 +512,13 @@ function attendance_print_pagenav() {
                echo "</tr></table></td></tr></table></center>\n";
   }
 }
+
+function attendance_HeaderingExcel($filename) {
+  header("Content-type: application/vnd.ms-excel");
+  header("Content-Disposition: attachment; filename=$filename" );
+  header("Expires: 0");
+  header("Cache-Control: must-revalidate, post-check=0,pre-check=0");
+  header("Pragma: public");
+}
+
 ?>
index 084ab63b0deb7939230b63bec7e682c6aba51863..c8f11e493545e8290f3b4cbd0d1acb25c2325ece 100644 (file)
@@ -79,38 +79,36 @@ if ($attendances) {
 
 
 if ($download == "xls") {
-    require_once("../../lib/psxlsgen.php");
+    require_once("write_excel/Worksheet.php");
+    require_once("write_excel/Workbook.php");
+  // HTTP headers
+  attendance_HeaderingExcel($course->shortname."_Attendance_Week.xls");
+  // Creating a workbook
+  $workbook = new Workbook("-");
+  // Creating the first worksheet
+  $myxls =& $workbook->add_worksheet('Grades');
 
-// add up the total columns that are required for the whole report
-    $myxls = new PhpSimpleXlsGen();
-if ($dlsub== "all") {
-    $myxls->totalcol=4+$numatt; // first,last,id ---...---> total
-} else {
-    $myxls->totalcol=4; // first,last,id ---...---> total
-}
     // print the date headings at the top of the table
     // for each day of attendance
-    $myxls->ChangePos(2,0);
-    $myxls->InsertText(get_string("lastname"));
-    $myxls->InsertText(get_string("firstname"));
-    $myxls->InsertText(get_string("idnumber"));
+    $myxls->write_string(3,0,get_string("lastname"));
+    $myxls->write_string(3,1,get_string("firstname"));
+    $myxls->write_string(3,2,get_string("idnumber"));
     $pos=3;
 if ($dlsub== "all") {
     for($k=0;$k<$numatt;$k++)  {
     // put notes for the date in the date heading
-           $myxls->ChangePos(0,$pos);
-           $myxls->InsertText(userdate($atts[$k]->attendance->day,"%m/%0d"));
-           $myxls->ChangePos(1,$pos);
-           $myxls->InsertText($atts[$k]->attendance->notes);
-                       $myxls->ChangePos(2,$pos);
+           $myxls->write_string(1,$pos,userdate($atts[$k]->attendance->day,"%m/%0d"));
+           $myxls->set_column($pos,$pos,5);
+           $myxls->write_string(2,$pos,$atts[$k]->attendance->notes);
                        for ($i=1;$i<=$atts[$k]->attendance->hours;$i++) {
-                               $myxls->InsertText($i);
+                               $myxls->write_number(3,$pos,$i);
+           $myxls->set_column($pos,$pos,1);
                                $pos++;
                        }
     }
 }  // if dlsub==all
-               $myxls->ChangePos(2,$pos);
-               $myxls->InsertText(get_string("total"));
+               $myxls->write_string(3,$pos,get_string("total"));
+               $myxls->set_column($pos,$pos,5);
                
 /// generate the attendance rolls for the body of the spreadsheet
   if (isstudent($course->id)) { 
@@ -122,13 +120,13 @@ if ($dlsub== "all") {
   $A = get_string("absentshort","attendance");
   $T = get_string("tardyshort","attendance");
   $P = get_string("presentshort","attendance");  
-  $row=3;
+  $row=4;
   foreach ($students as $student) {
-    $myxls->ChangePos($row,0);
-    $myxls->InsertText($student->lastname);
-    $myxls->InsertText($student->firstname);
+    $myxls->write_string($row,0,$student->lastname);
+    $myxls->write_string($row,1,$student->firstname);
     $studentid=(($student->idnumber != "") ? $student->idnumber : " ");
-    $myxls->InsertText($studentid);
+    $myxls->write_string($row,2,$studentid);
+    $pos=3;
     if ($dlsub== "all") {
            for($k=0;$k<$numatt;$k++)  { // for each day of attendance for the student
                  for($j=1;$j<=$atts[$k]->attendance->hours;$j++) {
@@ -136,7 +134,8 @@ if ($dlsub== "all") {
                    if ($atts[$k]->sroll[$student->id][$j]->status == 1) {$status=$T;}
                      elseif ($atts[$k]->sroll[$student->id][$j]->status == 2) {$status=$A;}
                      else {$status=$P;}
-               $myxls->InsertText($status);
+               $myxls->write_string($row,$pos,$status);
+               $pos++;
                    } /// for loop
            }
     }
@@ -149,10 +148,10 @@ if ($dlsub== "all") {
                  } /// for loop
          } // outer for for each day of attendance
     $tot=tally_overall_absences_decimal($abs,$tar);
-    $myxls->InsertNumber($tot);
+    $myxls->write_number($row,$pos,$tot);
                $row++;
   }
-  $myxls->SendFileName($course->shortname."_Attendance_Week");
+  $workbook->close();
 
   exit;
 }
@@ -522,4 +521,12 @@ function attendance_print_header()  {
                   navmenu($course, $cm));
 }
 
+function attendance_HeaderingExcel($filename) {
+  header("Content-type: application/vnd.ms-excel");
+  header("Content-Disposition: attachment; filename=$filename" );
+  header("Expires: 0");
+  header("Cache-Control: must-revalidate, post-check=0,pre-check=0");
+  header("Pragma: public");
+}
+
 ?>
diff --git a/mod/attendance/write_excel/BIFFwriter.php b/mod/attendance/write_excel/BIFFwriter.php
new file mode 100644 (file)
index 0000000..be24506
--- /dev/null
@@ -0,0 +1,209 @@
+<?php\r
+/*\r
+*  Module written/ported by Xavier Noguer <xnoguer@rezebra.com>\r
+*\r
+*  The majority of this is _NOT_ my code.  I simply ported it from the\r
+*  PERL Spreadsheet::WriteExcel module.\r
+*\r
+*  The author of the Spreadsheet::WriteExcel module is John McNamara \r
+*  <jmcnamara@cpan.org>\r
+*\r
+*  I _DO_ maintain this code, and John McNamara has nothing to do with the\r
+*  porting of this code to PHP.  Any questions directly related to this\r
+*  class library should be directed to me.\r
+*\r
+*  License Information:\r
+*\r
+*    Spreadsheet::WriteExcel:  A library for generating Excel Spreadsheets\r
+*    Copyright (C) 2002 Xavier Noguer xnoguer@rezebra.com\r
+*\r
+*    This library is free software; you can redistribute it and/or\r
+*    modify it under the terms of the GNU Lesser General Public\r
+*    License as published by the Free Software Foundation; either\r
+*    version 2.1 of the License, or (at your option) any later version.\r
+*\r
+*    This library is distributed in the hope that it will be useful,\r
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+*    Lesser General Public License for more details.\r
+*\r
+*    You should have received a copy of the GNU Lesser General Public\r
+*    License along with this library; if not, write to the Free Software\r
+*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
+*/\r
+\r
+/**\r
+* Class for writing Excel BIFF records.\r
+* \r
+* From "MICROSOFT EXCEL BINARY FILE FORMAT" by Mark O'Brien (Microsoft Corporation):\r
+*\r
+* BIFF (BInary File Format) is the file format in which Excel documents are \r
+* saved on disk.  A BIFF file is a complete description of an Excel document.\r
+* BIFF files consist of sequences of variable-length records. There are many \r
+* different types of BIFF records.  For example, one record type describes a \r
+* formula entered into a cell; one describes the size and location of a \r
+* window into a document; another describes a picture format.\r
+*\r
+* @author Xavier Noguer <xnoguer@rezebra.com>\r
+* @package Spreadsheet_WriteExcel\r
+*/\r
+\r
+class BIFFWriter\r
+{\r
+    var $_BIFF_version = 0x0500;\r
+\r
+/**\r
+* Constructor\r
+*\r
+* @access public\r
+*/\r
+    function BIFFwriter()\r
+    {\r
+        // The byte order of this architecture. 0 => little endian, 1 => big endian\r
+        $this->_byte_order = '';\r
+        // The string containing the data of the BIFF stream\r
+        $this->_data       = '';\r
+        // Should be the same as strlen($this->_data)\r
+        $this->_datasize   = 0;\r
+        // The maximun length for a BIFF record. See _add_continue()\r
+        $this->_limit      = 2080;   \r
+        // Set the byte order\r
+        $this->_set_byte_order();\r
+    }\r
+\r
+/**\r
+* Determine the byte order and store it as class data to avoid\r
+* recalculating it for each call to new().\r
+*\r
+* @access private\r
+*/\r
+    function _set_byte_order()\r
+    {\r
+        if ($this->_byte_order == '')\r
+        {\r
+            // Check if "pack" gives the required IEEE 64bit float\r
+            $teststr = pack("d", 1.2345);\r
+            $number  = pack("C8", 0x8D, 0x97, 0x6E, 0x12, 0x83, 0xC0, 0xF3, 0x3F);\r
+            if ($number == $teststr) {\r
+                $byte_order = 0;    // Little Endian\r
+            }\r
+            elseif ($number == strrev($teststr)){\r
+                $byte_order = 1;    // Big Endian\r
+            }\r
+            else {\r
+                // Give up. I'll fix this in a later version.\r
+                die("Required floating point format not supported ".\r
+                    "on this platform. See the portability section ".\r
+                    "of the documentation."\r
+                   );\r
+            }\r
+        }\r
+        $this->_byte_order = $byte_order;\r
+    }\r
+\r
+/**\r
+* General storage function\r
+*\r
+* @param string $data binary data to prepend\r
+* @access private\r
+*/\r
+    function _prepend($data)\r
+    {\r
+        if (strlen($data) > $this->_limit) {\r
+            $data = $this->_add_continue($data);\r
+        }\r
+        $this->_data      = $data.$this->_data;\r
+        $this->_datasize += strlen($data);\r
+    }\r
+\r
+/**\r
+* General storage function\r
+*\r
+* @param string $data binary data to append\r
+* @access private\r
+*/\r
+    function _append($data)\r
+    {\r
+        if (strlen($data) > $this->_limit) {\r
+            $data = $this->_add_continue($data);\r
+        }\r
+        $this->_data      = $this->_data.$data;\r
+        $this->_datasize += strlen($data);\r
+    }\r
+\r
+/**\r
+* Writes Excel BOF record to indicate the beginning of a stream or\r
+* sub-stream in the BIFF file.\r
+*\r
+* @param  integer $type type of BIFF file to write: 0x0005 Workbook, 0x0010 Worksheet.\r
+* @access private\r
+*/\r
+    function _store_bof($type)\r
+    {\r
+        $record  = 0x0809;        // Record identifier\r
+        $length  = 0x0008;        // Number of bytes to follow\r
+        $version = $this->_BIFF_version;\r
+   \r
+        // According to the SDK $build and $year should be set to zero.\r
+        // However, this throws a warning in Excel 5. So, use these\r
+        // magic numbers.\r
+        $build   = 0x096C;\r
+        $year    = 0x07C9;\r
+   \r
+        $header  = pack("vv",   $record, $length);\r
+        $data    = pack("vvvv", $version, $type, $build, $year);\r
+        $this->_prepend($header.$data);\r
+    }\r
+\r
+/**\r
+* Writes Excel EOF record to indicate the end of a BIFF stream.\r
+*\r
+* @access private\r
+*/\r
+    function _store_eof() \r
+    {\r
+        $record    = 0x000A;   // Record identifier\r
+        $length    = 0x0000;   // Number of bytes to follow\r
+        $header    = pack("vv", $record, $length);\r
+        $this->_append($header);\r
+    }\r
+\r
+/**\r
+* Excel limits the size of BIFF records. In Excel 5 the limit is 2084 bytes. In\r
+* Excel 97 the limit is 8228 bytes. Records that are longer than these limits\r
+* must be split up into CONTINUE blocks.\r
+*\r
+* This function takes a long BIFF record and inserts CONTINUE records as\r
+* necessary.\r
+*\r
+* @param  string  $data The original binary data to be written\r
+* @return string        A very convenient string of continue blocks\r
+* @access private\r
+*/\r
+    function _add_continue($data)\r
+    {\r
+        $limit      = $this->_limit;\r
+        $record     = 0x003C;         // Record identifier\r
\r
+        // The first 2080/8224 bytes remain intact. However, we have to change\r
+        // the length field of the record.\r
+        $tmp = substr($data, 0, 2).pack("v", $limit-4).substr($data, 4, $limit - 4);\r
+        \r
+        $header = pack("vv", $record, $limit);  // Headers for continue records\r
\r
+        // Retrieve chunks of 2080/8224 bytes +4 for the header.\r
+        for($i = $limit; $i < strlen($data) - $limit; $i += $limit)\r
+        {\r
+            $tmp .= $header;\r
+            $tmp .= substr($data, $i, $limit);\r
+        }\r
+\r
+        // Retrieve the last chunk of data\r
+        $header  = pack("vv", $record, strlen($data) - $i);\r
+        $tmp    .= $header;\r
+        $tmp    .= substr($data,$i,strlen($data) - $i);\r
\r
+        return($tmp);\r
+    }\r
+}\r
+?>
\ No newline at end of file
diff --git a/mod/attendance/write_excel/Format.php b/mod/attendance/write_excel/Format.php
new file mode 100644 (file)
index 0000000..ff8cc7e
--- /dev/null
@@ -0,0 +1,637 @@
+<?php\r
+/*\r
+*  Module written/ported by Xavier Noguer <xnoguer@rezebra.com>\r
+*\r
+*  The majority of this is _NOT_ my code.  I simply ported it from the\r
+*  PERL Spreadsheet::WriteExcel module.\r
+*\r
+*  The author of the Spreadsheet::WriteExcel module is John McNamara\r
+*  <jmcnamara@cpan.org>\r
+*\r
+*  I _DO_ maintain this code, and John McNamara has nothing to do with the\r
+*  porting of this code to PHP.  Any questions directly related to this\r
+*  class library should be directed to me.\r
+*\r
+*  License Information:\r
+*\r
+*    Spreadsheet::WriteExcel:  A library for generating Excel Spreadsheets\r
+*    Copyright (C) 2002 Xavier Noguer xnoguer@rezebra.com\r
+*\r
+*    This library is free software; you can redistribute it and/or\r
+*    modify it under the terms of the GNU Lesser General Public\r
+*    License as published by the Free Software Foundation; either\r
+*    version 2.1 of the License, or (at your option) any later version.\r
+*\r
+*    This library is distributed in the hope that it will be useful,\r
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+*    Lesser General Public License for more details.\r
+*\r
+*    You should have received a copy of the GNU Lesser General Public\r
+*    License along with this library; if not, write to the Free Software\r
+*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
+*/\r
+\r
+/**\r
+* Class for generating Excel XF records (formats)\r
+*\r
+* @author Xavier Noguer <xnoguer@rezebra.com>\r
+* @package Spreadsheet_WriteExcel\r
+*/\r
+\r
+class Format\r
+{\r
+  /**\r
+  * Constructor\r
+  *\r
+  * @access public\r
+  * @param integer $index the XF index for the format.\r
+  * @param array   $properties array with properties to be set on initialization.\r
+  */\r
+    function Format($index = 0,$properties =  array())\r
+    {\r
+        $this->xf_index       = $index;\r
+    \r
+        $this->font_index     = 0;\r
+        $this->font           = 'Arial';\r
+        $this->size           = 10;\r
+        $this->bold           = 0x0190;\r
+        $this->_italic        = 0;\r
+        $this->color          = 0x7FFF;\r
+        $this->_underline     = 0;\r
+        $this->font_strikeout = 0;\r
+        $this->font_outline   = 0;\r
+        $this->font_shadow    = 0;\r
+        $this->font_script    = 0;\r
+        $this->font_family    = 0;\r
+        $this->font_charset   = 0;\r
+    \r
+        $this->_num_format    = 0;\r
+    \r
+        $this->hidden         = 0;\r
+        $this->locked         = 1;\r
+    \r
+        $this->_text_h_align  = 0;\r
+        $this->_text_wrap     = 0;\r
+        $this->text_v_align   = 2;\r
+        $this->text_justlast  = 0;\r
+        $this->rotation       = 0;\r
+    \r
+        $this->fg_color       = 0x40;\r
+        $this->bg_color       = 0x41;\r
+    \r
+        $this->pattern        = 0;\r
+    \r
+        $this->bottom         = 0;\r
+        $this->top            = 0;\r
+        $this->left           = 0;\r
+        $this->right          = 0;\r
+    \r
+        $this->bottom_color   = 0x40;\r
+        $this->top_color      = 0x40;\r
+        $this->left_color     = 0x40;\r
+        $this->right_color    = 0x40;\r
+    \r
+        // Set properties passed to Workbook::add_format()\r
+        foreach($properties as $property => $value)\r
+        {\r
+            if(method_exists($this,"set_$property"))\r
+            {\r
+                $aux = 'set_'.$property;\r
+                $this->$aux($value);\r
+            }\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Generate an Excel BIFF XF record (style or cell).\r
+    *\r
+    * @param string $style The type of the XF record ('style' or 'cell').\r
+    * @return string The XF record\r
+    */\r
+    function get_xf($style)\r
+    {\r
+        // Set the type of the XF record and some of the attributes.\r
+        if ($style == "style") {\r
+            $style = 0xFFF5;\r
+        }\r
+        else {\r
+            $style   = $this->locked;\r
+            $style  |= $this->hidden << 1;\r
+        }\r
+    \r
+        // Flags to indicate if attributes have been set.\r
+        $atr_num     = ($this->_num_format != 0)?1:0;\r
+        $atr_fnt     = ($this->font_index != 0)?1:0;\r
+        $atr_alc     = ($this->_text_wrap)?1:0;\r
+        $atr_bdr     = ($this->bottom   ||\r
+                        $this->top      ||\r
+                        $this->left     ||\r
+                        $this->right)?1:0;\r
+        $atr_pat     = (($this->fg_color != 0x40) ||\r
+                        ($this->bg_color != 0x41) ||\r
+                        $this->pattern)?1:0;\r
+        $atr_prot    = 0;\r
+    \r
+        // Zero the default border colour if the border has not been set.\r
+        if ($this->bottom == 0) {\r
+            $this->bottom_color = 0;\r
+            }\r
+        if ($this->top  == 0) {\r
+            $this->top_color = 0;\r
+            }\r
+        if ($this->right == 0) {\r
+            $this->right_color = 0;\r
+            }\r
+        if ($this->left == 0) {\r
+            $this->left_color = 0;\r
+            }\r
+    \r
+        $record         = 0x00E0;              // Record identifier\r
+        $length         = 0x0010;              // Number of bytes to follow\r
+                                               \r
+        $ifnt           = $this->font_index;   // Index to FONT record\r
+        $ifmt           = $this->_num_format;  // Index to FORMAT record\r
+    \r
+        $align          = $this->_text_h_align;       // Alignment\r
+        $align         |= $this->_text_wrap    << 3;\r
+        $align         |= $this->text_v_align  << 4;\r
+        $align         |= $this->text_justlast << 7;\r
+        $align         |= $this->rotation      << 8;\r
+        $align         |= $atr_num                << 10;\r
+        $align         |= $atr_fnt                << 11;\r
+        $align         |= $atr_alc                << 12;\r
+        $align         |= $atr_bdr                << 13;\r
+        $align         |= $atr_pat                << 14;\r
+        $align         |= $atr_prot               << 15;\r
+    \r
+        $icv            = $this->fg_color;           // fg and bg pattern colors\r
+        $icv           |= $this->bg_color      << 7;\r
+    \r
+        $fill           = $this->pattern;            // Fill and border line style\r
+        $fill          |= $this->bottom        << 6;\r
+        $fill          |= $this->bottom_color  << 9;\r
+    \r
+        $border1        = $this->top;                // Border line style and color\r
+        $border1       |= $this->left          << 3;\r
+        $border1       |= $this->right         << 6;\r
+        $border1       |= $this->top_color     << 9;\r
+\r
+        $border2        = $this->left_color;         // Border color\r
+        $border2       |= $this->right_color   << 7;\r
+    \r
+        $header      = pack("vv",       $record, $length);\r
+        $data        = pack("vvvvvvvv", $ifnt, $ifmt, $style, $align,\r
+                                        $icv, $fill,\r
+                                        $border1, $border2);\r
+        return($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Generate an Excel BIFF FONT record.\r
+    *\r
+    * @see Workbook::_store_all_fonts()\r
+    * @return string The FONT record\r
+    */\r
+    function get_font()\r
+    {\r
+        $dyHeight   = $this->size * 20;    // Height of font (1/20 of a point)\r
+        $icv        = $this->color;        // Index to color palette\r
+        $bls        = $this->bold;         // Bold style\r
+        $sss        = $this->font_script;  // Superscript/subscript\r
+        $uls        = $this->_underline;   // Underline\r
+        $bFamily    = $this->font_family;  // Font family\r
+        $bCharSet   = $this->font_charset; // Character set\r
+        $rgch       = $this->font;         // Font name\r
+    \r
+        $cch        = strlen($rgch);       // Length of font name\r
+        $record     = 0x31;                // Record identifier\r
+        $length     = 0x0F + $cch;         // Record length\r
+        $reserved   = 0x00;                // Reserved\r
+        $grbit      = 0x00;                // Font attributes\r
+        if ($this->_italic) {\r
+            $grbit     |= 0x02;\r
+        }\r
+        if ($this->font_strikeout) {\r
+            $grbit     |= 0x08;\r
+        }\r
+        if ($this->font_outline) {\r
+            $grbit     |= 0x10;\r
+        }\r
+        if ($this->font_shadow) {\r
+            $grbit     |= 0x20;\r
+        }\r
+    \r
+        $header  = pack("vv",         $record, $length);\r
+        $data    = pack("vvvvvCCCCC", $dyHeight, $grbit, $icv, $bls,\r
+                                      $sss, $uls, $bFamily,\r
+                                      $bCharSet, $reserved, $cch);\r
+        return($header . $data. $this->font);\r
+    }\r
+    \r
+    /**\r
+    * Returns a unique hash key for a font. Used by Workbook->_store_all_fonts()\r
+    *\r
+    * The elements that form the key are arranged to increase the probability of\r
+    * generating a unique key. Elements that hold a large range of numbers\r
+    * (eg. _color) are placed between two binary elements such as _italic\r
+    *\r
+    * @return string A key for this font\r
+    */\r
+    function get_font_key()\r
+    {\r
+        $key  = "$this->font$this->size";\r
+        $key .= "$this->font_script$this->_underline";\r
+        $key .= "$this->font_strikeout$this->bold$this->font_outline";\r
+        $key .= "$this->font_family$this->font_charset";\r
+        $key .= "$this->font_shadow$this->color$this->_italic";\r
+        $key  = str_replace(" ","_",$key);\r
+        return ($key);\r
+    }\r
+    \r
+    /**\r
+    * Returns the index used by Worksheet->_XF()\r
+    *\r
+    * @return integer The index for the XF record\r
+    */\r
+    function get_xf_index()\r
+    {\r
+        return($this->xf_index);\r
+    }\r
+    \r
+    /**\r
+    * Used in conjunction with the set_xxx_color methods to convert a color\r
+    * string into a number. Color range is 0..63 but we will restrict it\r
+    * to 8..63 to comply with Gnumeric. Colors 0..7 are repeated in 8..15.\r
+    *\r
+    * @param string $name_color name of the color (i.e.: 'blue', 'red', etc..). Optional.\r
+    * @return integer The color index\r
+    */\r
+    function _get_color($name_color = '')\r
+    {\r
+        $colors = array(\r
+                        'aqua'    => 0x0F,\r
+                        'cyan'    => 0x0F,\r
+                        'black'   => 0x08,\r
+                        'blue'    => 0x0C,\r
+                        'brown'   => 0x10,\r
+                        'magenta' => 0x0E,\r
+                        'fuchsia' => 0x0E,\r
+                        'gray'    => 0x17,\r
+                        'grey'    => 0x17,\r
+                        'green'   => 0x11,\r
+                        'lime'    => 0x0B,\r
+                        'navy'    => 0x12,\r
+                        'orange'  => 0x35,\r
+                        'purple'  => 0x14,\r
+                        'red'     => 0x0A,\r
+                        'silver'  => 0x16,\r
+                        'white'   => 0x09,\r
+                        'yellow'  => 0x0D\r
+                       );\r
+    \r
+        // Return the default color, 0x7FFF, if undef,\r
+        if($name_color == '') {\r
+            return(0x7FFF);\r
+        }\r
+    \r
+        // or the color string converted to an integer,\r
+        if(isset($colors[$name_color])) {\r
+            return($colors[$name_color]);\r
+        }\r
+    \r
+        // or the default color if string is unrecognised,\r
+        if(preg_match("/\D/",$name_color)) {\r
+            return(0x7FFF);\r
+        }\r
+    \r
+        // or an index < 8 mapped into the correct range,\r
+        if($name_color < 8) {\r
+            return($name_color + 8);\r
+        }\r
+    \r
+        // or the default color if arg is outside range,\r
+        if($name_color > 63) {\r
+            return(0x7FFF);\r
+        }\r
+    \r
+        // or an integer in the valid range\r
+        return($name_color);\r
+    }\r
+    \r
+    /**\r
+    * Set cell alignment.\r
+    *\r
+    * @access public\r
+    * @param string $location alignment for the cell ('left', 'right', etc...).\r
+    */\r
+    function set_align($location)\r
+    {\r
+        if (preg_match("/\d/",$location)) {\r
+            return;                      // Ignore numbers\r
+        }\r
+    \r
+        $location = strtolower($location);\r
+    \r
+        if ($location == 'left')\r
+            $this->_text_h_align = 1; \r
+        if ($location == 'centre')\r
+            $this->_text_h_align = 2; \r
+        if ($location == 'center')\r
+            $this->_text_h_align = 2; \r
+        if ($location == 'right')\r
+            $this->_text_h_align = 3; \r
+        if ($location == 'fill')\r
+            $this->_text_h_align = 4; \r
+        if ($location == 'justify')\r
+            $this->_text_h_align = 5;\r
+        if ($location == 'merge')\r
+            $this->_text_h_align = 6;\r
+        if ($location == 'equal_space') // For T.K.\r
+            $this->_text_h_align = 7; \r
+        if ($location == 'top')\r
+            $this->text_v_align = 0; \r
+        if ($location == 'vcentre')\r
+            $this->text_v_align = 1; \r
+        if ($location == 'vcenter')\r
+            $this->text_v_align = 1; \r
+        if ($location == 'bottom')\r
+            $this->text_v_align = 2; \r
+        if ($location == 'vjustify')\r
+            $this->text_v_align = 3; \r
+        if ($location == 'vequal_space') // For T.K.\r
+            $this->text_v_align = 4; \r
+    }\r
+    \r
+    /**\r
+    * This is an alias for the unintuitive set_align('merge')\r
+    *\r
+    * @access public\r
+    */\r
+    function set_merge()\r
+    {\r
+        $this->set_align('merge');\r
+    }\r
+    \r
+    /**\r
+    * Bold has a range 0x64..0x3E8.\r
+    * 0x190 is normal. 0x2BC is bold.\r
+    *\r
+    * @access public\r
+    * @param integer $weight Weight for the text, 0 maps to 0x190, 1 maps to 0x2BC. \r
+                             It's Optional, default is 1 (bold).\r
+    */\r
+    function set_bold($weight = 1)\r
+    {\r
+        if($weight == 1) {\r
+            $weight = 0x2BC;  // Bold text\r
+        }\r
+        if($weight == 0) {\r
+            $weight = 0x190;  // Normal text\r
+        }\r
+        if($weight <  0x064) {\r
+            $weight = 0x190;  // Lower bound\r
+        }\r
+        if($weight >  0x3E8) {\r
+            $weight = 0x190;  // Upper bound\r
+        }\r
+        $this->bold = $weight;\r
+    }\r
+    \r
+    \r
+    /************************************\r
+    * FUNCTIONS FOR SETTING CELLS BORDERS\r
+    */\r
+    \r
+    /**\r
+    * Sets the bottom border of the cell\r
+    *\r
+    * @access public\r
+    * @param integer $style style of the cell border. 1 => thin, 2 => thick.\r
+    */\r
+    function set_bottom($style)\r
+    {\r
+        $this->bottom = $style;\r
+    }\r
+    \r
+    /**\r
+    * Sets the top border of the cell\r
+    *\r
+    * @access public\r
+    * @param integer $style style of the cell top border. 1 => thin, 2 => thick.\r
+    */\r
+    function set_top($style)\r
+    {\r
+        $this->top = $style;\r
+    }\r
+    \r
+    /**\r
+    * Sets the left border of the cell\r
+    *\r
+    * @access public\r
+    * @param integer $style style of the cell left border. 1 => thin, 2 => thick.\r
+    */\r
+    function set_left($style)\r
+    {\r
+        $this->left = $style;\r
+    }\r
+    \r
+    /**\r
+    * Sets the right border of the cell\r
+    *\r
+    * @access public\r
+    * @param integer $style style of the cell right border. 1 => thin, 2 => thick.\r
+    */\r
+    function set_right($style)\r
+    {\r
+        $this->right = $style;\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Set cells borders to the same style\r
+    *\r
+    * @access public\r
+    * @param integer $style style to apply for all cell borders. 1 => thin, 2 => thick.\r
+    */\r
+    function set_border($style)\r
+    {\r
+        $this->set_bottom($style);\r
+        $this->set_top($style);\r
+        $this->set_left($style);\r
+        $this->set_right($style);\r
+    }\r
+    \r
+    \r
+    /*******************************************\r
+    * FUNCTIONS FOR SETTING CELLS BORDERS COLORS\r
+    */\r
+    \r
+    /**\r
+    * Sets all the cell's borders to the same color\r
+    *\r
+    * @access public\r
+    * @param mixed $color The color we are setting. Either a string (like 'blue'), \r
+    *                     or an integer (like 0x41).\r
+    */\r
+    function set_border_color($color)\r
+    {\r
+        $this->set_bottom_color($color);\r
+        $this->set_top_color($color);\r
+        $this->set_left_color($color);\r
+        $this->set_right_color($color);\r
+    }\r
+    \r
+    /**\r
+    * Sets the cell's bottom border color\r
+    *\r
+    * @access public\r
+    * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63]).\r
+    */\r
+    function set_bottom_color($color)\r
+    {\r
+        $value = $this->_get_color($color);\r
+        $this->bottom_color = $value;\r
+    }\r
+    \r
+    /**\r
+    * Sets the cell's top border color\r
+    *\r
+    * @access public\r
+    * @param mixed $color either a string (like 'blue'), or an integer (range is [8...63]).\r
+    */\r
+    function set_top_color($color)\r
+    {\r
+        $value = $this->_get_color($color);\r
+        $this->top_color = $value;\r
+    }\r
+    \r
+    /**\r
+    * Sets the cell's left border color\r
+    *\r
+    * @access public\r
+    * @param mixed $color either a string (like 'blue'), or an integer (like 0x41).\r
+    */\r
+    function set_left_color($color)\r
+    {\r
+        $value = $this->_get_color($color);\r
+        $this->left_color = $value;\r
+    }\r
+    \r
+    /**\r
+    * Sets the cell's right border color\r
+    *\r
+    * @access public\r
+    * @param mixed $color either a string (like 'blue'), or an integer (like 0x41).\r
+    */\r
+    function set_right_color($color)\r
+    {\r
+        $value = $this->_get_color($color);\r
+        $this->right_color = $value;\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Sets the cell's foreground color\r
+    *\r
+    * @access public\r
+    * @param mixed $color either a string (like 'blue'), or an integer (like 0x41).\r
+    */\r
+    function set_fg_color($color)\r
+    {\r
+        $value = $this->_get_color($color);\r
+        $this->fg_color = $value;\r
+    }\r
+      \r
+    /**\r
+    * Sets the cell's background color\r
+    *\r
+    * @access public\r
+    * @param mixed $color either a string (like 'blue'), or an integer (like 0x41).\r
+    */\r
+    function set_bg_color($color)\r
+    {\r
+        $value = $this->_get_color($color);\r
+        $this->bg_color = $value;\r
+    }\r
+    \r
+    /**\r
+    * Sets the cell's color\r
+    *\r
+    * @access public\r
+    * @param mixed $color either a string (like 'blue'), or an integer (like 0x41).\r
+    */\r
+    function set_color($color)\r
+    {\r
+        $value = $this->_get_color($color);\r
+        $this->color = $value;\r
+    }\r
+    \r
+    /**\r
+    * Sets the pattern attribute of a cell\r
+    *\r
+    * @access public\r
+    * @param integer $arg Optional. Defaults to 1.\r
+    */\r
+    function set_pattern($arg = 1)\r
+    {\r
+        $this->pattern = $arg;\r
+    }\r
+    \r
+    /**\r
+    * Sets the underline of the text\r
+    *\r
+    * @access public\r
+    * @param integer $underline The value for underline. Possible values are:\r
+    *                          1 => underline, 2 => double underline.\r
+    */\r
+    function set_underline($underline)\r
+    {\r
+        $this->_underline = $underline;\r
+    }\r
\r
+    /**\r
+    * Sets the font style as italic\r
+    *\r
+    * @access public\r
+    */\r
+    function set_italic()\r
+    {\r
+        $this->_italic = 1;\r
+    }\r
+\r
+    /**\r
+    * Sets the font size \r
+    *\r
+    * @access public\r
+    * @param integer $size The font size (in pixels I think).\r
+    */\r
+    function set_size($size)\r
+    {\r
+        $this->size = $size;\r
+    }\r
+    \r
+    /**\r
+    * Sets the num format\r
+    *\r
+    * @access public\r
+    * @param integer $num_format The num format.\r
+    */\r
+    function set_num_format($num_format)\r
+    {\r
+        $this->_num_format = $num_format;\r
+    }\r
+    \r
+    /**\r
+    * Sets text wrapping\r
+    *\r
+    * @access public\r
+    * @param integer $text_wrap Optional. 0 => no text wrapping, 1 => text wrapping. \r
+    *                           Defaults to 1.\r
+    */\r
+    function set_text_wrap($text_wrap = 1)\r
+    {\r
+        $this->_text_wrap = $text_wrap;\r
+    }\r
+}\r
+?>
\ No newline at end of file
diff --git a/mod/attendance/write_excel/OLEwriter.php b/mod/attendance/write_excel/OLEwriter.php
new file mode 100644 (file)
index 0000000..6af35d2
--- /dev/null
@@ -0,0 +1,414 @@
+<?php\r
+/*\r
+*  Module written/ported by Xavier Noguer <xnoguer@rezebra.com>\r
+*\r
+*  The majority of this is _NOT_ my code.  I simply ported it from the\r
+*  PERL Spreadsheet::WriteExcel module.\r
+*\r
+*  The author of the Spreadsheet::WriteExcel module is John McNamara\r
+*  <jmcnamara@cpan.org>\r
+*\r
+*  I _DO_ maintain this code, and John McNamara has nothing to do with the\r
+*  porting of this code to PHP.  Any questions directly related to this\r
+*  class library should be directed to me.\r
+*\r
+*  License Information:\r
+*\r
+*    Spreadsheet::WriteExcel:  A library for generating Excel Spreadsheets\r
+*    Copyright (C) 2002 Xavier Noguer xnoguer@rezebra.com\r
+*\r
+*    This library is free software; you can redistribute it and/or\r
+*    modify it under the terms of the GNU Lesser General Public\r
+*    License as published by the Free Software Foundation; either\r
+*    version 2.1 of the License, or (at your option) any later version.\r
+*\r
+*    This library is distributed in the hope that it will be useful,\r
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+*    Lesser General Public License for more details.\r
+*\r
+*    You should have received a copy of the GNU Lesser General Public\r
+*    License along with this library; if not, write to the Free Software\r
+*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
+*/\r
+\r
+/**\r
+* Class for creating OLE streams for Excel Spreadsheets\r
+*\r
+* @author Xavier Noguer <xnoguer@rezebra.com>\r
+* @package Spreadsheet_WriteExcel\r
+*/\r
+class OLEwriter\r
+{\r
+    /**\r
+    * Filename for the OLE stream\r
+    * @var string\r
+    * @see _initialize()\r
+    */\r
+    var $_OLEfilename;\r
+\r
+    /**\r
+    * Filehandle for the OLE stream\r
+    * @var resource\r
+    */\r
+    var $_filehandle;\r
+\r
+    /**\r
+    * Name of the temporal file in case OLE stream goes to stdout\r
+    * @var string\r
+    */\r
+    var $_tmp_filename;\r
+\r
+    /**\r
+    * Variable for preventing closing two times\r
+    * @var integer\r
+    */\r
+    var $_fileclosed;\r
+\r
+    /**\r
+    * Size of the data to be written to the OLE stream\r
+    * @var integer\r
+    */\r
+    var $_biffsize;\r
+\r
+    /**\r
+    * Real data size to be written to the OLE stream\r
+    * @var integer\r
+    */\r
+    var $_booksize;\r
+\r
+    /**\r
+    * Number of big blocks in the OLE stream\r
+    * @var integer\r
+    */\r
+    var $_big_blocks;\r
+\r
+    /**\r
+    * Number of list blocks in the OLE stream\r
+    * @var integer\r
+    */\r
+    var $_list_blocks;\r
+\r
+    /**\r
+    * Number of big blocks in the OLE stream\r
+    * @var integer\r
+    */\r
+    var $_root_start;\r
+\r
+    /**\r
+    * Class for creating an OLEwriter\r
+    *\r
+    * @param string $OLEfilename the name of the file for the OLE stream\r
+    */\r
+    function OLEwriter($OLEfilename)\r
+    {\r
+        $this->_OLEfilename  = $OLEfilename;\r
+        $this->_filehandle   = "";\r
+        $this->_tmp_filename = "";\r
+        $this->_fileclosed   = 0;\r
+        //$this->_size_allowed = 0;\r
+        $this->_biffsize     = 0;\r
+        $this->_booksize     = 0;\r
+        $this->_big_blocks   = 0;\r
+        $this->_list_blocks  = 0;\r
+        $this->_root_start   = 0;\r
+        //$this->_block_count  = 4;\r
+        $this->_initialize();\r
+    }\r
+\r
+/**\r
+* Check for a valid filename and store the filehandle.\r
+* Filehandle "-" writes to STDOUT\r
+*/\r
+    function _initialize()\r
+    {\r
+        $OLEfile = $this->_OLEfilename;\r
\r
+        if(($OLEfile == '-') or ($OLEfile == ''))\r
+        {\r
+            $this->_tmp_filename = tempnam("/tmp", "OLEwriter");\r
+            $fh = fopen($this->_tmp_filename,"wb");\r
+            if ($fh == false) {\r
+                die("Can't create temporary file.");\r
+            }\r
+        }\r
+        else\r
+        {\r
+            // Create a new file, open for writing (in binmode)\r
+            $fh = fopen($OLEfile,"wb");\r
+            if ($fh == false) {\r
+                die("Can't open $OLEfile. It may be in use or protected.");\r
+            }\r
+        }\r
+\r
+        // Store filehandle\r
+        $this->_filehandle = $fh;\r
+    }\r
+\r
+\r
+    /**\r
+    * Set the size of the data to be written to the OLE stream.\r
+    * The maximun size comes from this:\r
+    *   $big_blocks = (109 depot block x (128 -1 marker word)\r
+    *                 - (1 x end words)) = 13842\r
+    *   $maxsize    = $big_blocks * 512 bytes = 7087104\r
+    *\r
+    * @access public\r
+    * @see Workbook::store_OLE_file()\r
+    * @param integer $biffsize The size of the data to be written to the OLE stream\r
+    * @return integer 1 for success\r
+    */\r
+    function set_size($biffsize)\r
+    {\r
+        $maxsize = 7087104; // TODO: extend max size\r
\r
+        if ($biffsize > $maxsize) {\r
+            die("Maximum file size, $maxsize, exceeded.");\r
+        }\r
\r
+        $this->_biffsize = $biffsize;\r
+        // Set the min file size to 4k to avoid having to use small blocks\r
+        if ($biffsize > 4096) {\r
+            $this->_booksize = $biffsize;\r
+        }\r
+        else {\r
+            $this->_booksize = 4096;\r
+        }\r
+        //$this->_size_allowed = 1;\r
+        return(1);\r
+    }\r
+\r
+\r
+    /**\r
+    * Calculate various sizes needed for the OLE stream\r
+    */\r
+    function _calculate_sizes()\r
+    {\r
+        $datasize = $this->_booksize;\r
+        if ($datasize % 512 == 0) {\r
+            $this->_big_blocks = $datasize/512;\r
+        }\r
+        else {\r
+            $this->_big_blocks = floor($datasize/512) + 1;\r
+        }\r
+        // There are 127 list blocks and 1 marker blocks for each big block\r
+        // depot + 1 end of chain block\r
+        $this->_list_blocks = floor(($this->_big_blocks)/127) + 1;\r
+        $this->_root_start  = $this->_big_blocks;\r
+    }\r
+\r
+    /**\r
+    * Write root entry, big block list and close the filehandle.\r
+    * This routine is used to explicitly close the open filehandle without\r
+    * having to wait for DESTROY.\r
+    *\r
+    * @access public\r
+    * @see Workbook::store_OLE_file()\r
+    */\r
+    function close() \r
+    {\r
+        //return if not $this->{_size_allowed};\r
+        $this->_write_padding();\r
+        $this->_write_property_storage();\r
+        $this->_write_big_block_depot();\r
+        // Close the filehandle \r
+        fclose($this->_filehandle);\r
+        if(($this->_OLEfilename == '-') or ($this->_OLEfilename == ''))\r
+        {\r
+            $fh = fopen($this->_tmp_filename, "rb");\r
+            if ($fh == false) {\r
+                die("Can't read temporary file.");\r
+            }\r
+            fpassthru($fh);\r
+            // Delete the temporary file.\r
+            @unlink($this->_tmp_filename);\r
+        }\r
+        $this->_fileclosed = 1;\r
+    }\r
+\r
+\r
+    /**\r
+    * Write BIFF data to OLE file.\r
+    *\r
+    * @param string $data string of bytes to be written\r
+    */\r
+    function write($data) //por ahora sólo a STDOUT\r
+    {\r
+        fwrite($this->_filehandle,$data,strlen($data));\r
+    }\r
+\r
+\r
+    /**\r
+    * Write OLE header block.\r
+    */\r
+    function write_header()\r
+    {\r
+        $this->_calculate_sizes();\r
+        $root_start      = $this->_root_start;\r
+        $num_lists       = $this->_list_blocks;\r
+        $id              = pack("nnnn", 0xD0CF, 0x11E0, 0xA1B1, 0x1AE1);\r
+        $unknown1        = pack("VVVV", 0x00, 0x00, 0x00, 0x00);\r
+        $unknown2        = pack("vv",   0x3E, 0x03);\r
+        $unknown3        = pack("v",    -2);\r
+        $unknown4        = pack("v",    0x09);\r
+        $unknown5        = pack("VVV",  0x06, 0x00, 0x00);\r
+        $num_bbd_blocks  = pack("V",    $num_lists);\r
+        $root_startblock = pack("V",    $root_start);\r
+        $unknown6        = pack("VV",   0x00, 0x1000);\r
+        $sbd_startblock  = pack("V",    -2);\r
+        $unknown7        = pack("VVV",  0x00, -2 ,0x00);\r
+        $unused          = pack("V",    -1);\r
\r
+        fwrite($this->_filehandle,$id);\r
+        fwrite($this->_filehandle,$unknown1);\r
+        fwrite($this->_filehandle,$unknown2);\r
+        fwrite($this->_filehandle,$unknown3);\r
+        fwrite($this->_filehandle,$unknown4);\r
+        fwrite($this->_filehandle,$unknown5);\r
+        fwrite($this->_filehandle,$num_bbd_blocks);\r
+        fwrite($this->_filehandle,$root_startblock);\r
+        fwrite($this->_filehandle,$unknown6);\r
+        fwrite($this->_filehandle,$sbd_startblock);\r
+        fwrite($this->_filehandle,$unknown7);\r
\r
+        for($i=1; $i <= $num_lists; $i++)\r
+        {\r
+            $root_start++;\r
+            fwrite($this->_filehandle,pack("V",$root_start));\r
+        }\r
+        for($i = $num_lists; $i <=108; $i++)\r
+        {\r
+            fwrite($this->_filehandle,$unused);\r
+        }\r
+    }\r
+\r
+\r
+    /**\r
+    * Write big block depot.\r
+    */\r
+    function _write_big_block_depot()\r
+    {\r
+        $num_blocks   = $this->_big_blocks;\r
+        $num_lists    = $this->_list_blocks;\r
+        $total_blocks = $num_lists *128;\r
+        $used_blocks  = $num_blocks + $num_lists +2;\r
\r
+        $marker       = pack("V", -3);\r
+        $end_of_chain = pack("V", -2);\r
+        $unused       = pack("V", -1);\r
\r
+        for($i=1; $i < $num_blocks; $i++)\r
+        {\r
+            fwrite($this->_filehandle,pack("V",$i));\r
+        }\r
+        fwrite($this->_filehandle,$end_of_chain);\r
+        fwrite($this->_filehandle,$end_of_chain);\r
+        for($i=0; $i < $num_lists; $i++)\r
+        {\r
+            fwrite($this->_filehandle,$marker);\r
+        }\r
+        for($i=$used_blocks; $i <= $total_blocks; $i++)\r
+        {\r
+            fwrite($this->_filehandle,$unused);\r
+        }\r
+    }\r
+\r
+/**\r
+* Write property storage. TODO: add summary sheets\r
+*/\r
+    function _write_property_storage()\r
+    {\r
+        //$rootsize = -2;\r
+        /***************  name         type   dir start size */\r
+        $this->_write_pps("Root Entry", 0x05,   1,   -2, 0x00);\r
+        $this->_write_pps("Book",       0x02,  -1, 0x00, $this->_booksize);\r
+        $this->_write_pps('',           0x00,  -1, 0x00, 0x0000);\r
+        $this->_write_pps('',           0x00,  -1, 0x00, 0x0000);\r
+    }\r
+\r
+/**\r
+* Write property sheet in property storage\r
+*\r
+* @param string  $name  name of the property storage.\r
+* @param integer $type  type of the property storage.\r
+* @param integer $dir   dir of the property storage.\r
+* @param integer $start start of the property storage.\r
+* @param integer $size  size of the property storage.\r
+* @access private\r
+*/\r
+    function _write_pps($name,$type,$dir,$start,$size)\r
+    {\r
+        $length  = 0;\r
+        $rawname = '';\r
\r
+        if ($name != '')\r
+        {\r
+            $name = $name . "\0";\r
+            for($i=0;$i<strlen($name);$i++)\r
+            {\r
+                // Simulate a Unicode string\r
+                $rawname .= pack("H*",dechex(ord($name{$i}))).pack("C",0);\r
+            }\r
+            $length = strlen($name) * 2;\r
+        }\r
+       \r
+        $zero            = pack("C",  0);\r
+        $pps_sizeofname  = pack("v",  $length);    // 0x40\r
+        $pps_type        = pack("v",  $type);      // 0x42\r
+        $pps_prev        = pack("V",  -1);         // 0x44\r
+        $pps_next        = pack("V",  -1);         // 0x48\r
+        $pps_dir         = pack("V",  $dir);       // 0x4c\r
+       \r
+        $unknown1        = pack("V",  0);\r
+       \r
+        $pps_ts1s        = pack("V",  0);          // 0x64\r
+        $pps_ts1d        = pack("V",  0);          // 0x68\r
+        $pps_ts2s        = pack("V",  0);          // 0x6c\r
+        $pps_ts2d        = pack("V",  0);          // 0x70\r
+        $pps_sb          = pack("V",  $start);     // 0x74\r
+        $pps_size        = pack("V",  $size);      // 0x78\r
+       \r
+       \r
+        fwrite($this->_filehandle,$rawname);\r
+        for($i=0; $i < (64 -$length); $i++) {\r
+            fwrite($this->_filehandle,$zero);\r
+        }\r
+        fwrite($this->_filehandle,$pps_sizeofname);\r
+        fwrite($this->_filehandle,$pps_type);\r
+        fwrite($this->_filehandle,$pps_prev);\r
+        fwrite($this->_filehandle,$pps_next);\r
+        fwrite($this->_filehandle,$pps_dir);\r
+        for($i=0; $i < 5; $i++) {\r
+            fwrite($this->_filehandle,$unknown1);\r
+        }\r
+        fwrite($this->_filehandle,$pps_ts1s);\r
+        fwrite($this->_filehandle,$pps_ts1d);\r
+        fwrite($this->_filehandle,$pps_ts2d);\r
+        fwrite($this->_filehandle,$pps_ts2d);\r
+        fwrite($this->_filehandle,$pps_sb);\r
+        fwrite($this->_filehandle,$pps_size);\r
+        fwrite($this->_filehandle,$unknown1);\r
+    }\r
+\r
+    /**\r
+    * Pad the end of the file\r
+    */\r
+    function _write_padding()\r
+    {\r
+        $biffsize = $this->_biffsize;\r
+        if ($biffsize < 4096) {\r
+           $min_size = 4096;\r
+        }\r
+       else {    \r
+            $min_size = 512;\r
+        }\r
+       if ($biffsize % $min_size != 0)\r
+        {\r
+            $padding  = $min_size - ($biffsize % $min_size);\r
+            for($i=0; $i < $padding; $i++) {\r
+                fwrite($this->_filehandle,"\0");\r
+            }\r
+        }\r
+    }\r
+}\r
+?>
\ No newline at end of file
diff --git a/mod/attendance/write_excel/Parser.php b/mod/attendance/write_excel/Parser.php
new file mode 100644 (file)
index 0000000..e1882e7
--- /dev/null
@@ -0,0 +1,996 @@
+<?php\r
+/**\r
+*  Class for parsing Excel formulas\r
+*\r
+*  License Information:\r
+*\r
+*    Spreadsheet::WriteExcel:  A library for generating Excel Spreadsheets\r
+*    Copyright (C) 2002 Xavier Noguer xnoguer@rezebra.com\r
+*\r
+*    This library is free software; you can redistribute it and/or\r
+*    modify it under the terms of the GNU Lesser General Public\r
+*    License as published by the Free Software Foundation; either\r
+*    version 2.1 of the License, or (at your option) any later version.\r
+*\r
+*    This library is distributed in the hope that it will be useful,\r
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+*    Lesser General Public License for more details.\r
+*\r
+*    You should have received a copy of the GNU Lesser General Public\r
+*    License along with this library; if not, write to the Free Software\r
+*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
+*/\r
+\r
+/**\r
+* @const ADD token identifier for character "+"\r
+*/\r
+define('ADD',"+");\r
+\r
+/**\r
+* @const SUB token identifier for character "-"\r
+*/\r
+define('SUB',"-");\r
+\r
+/**\r
+* @const EQUAL token identifier for character "="\r
+*/\r
+define('EQUAL',"=");\r
+\r
+/**\r
+* @const MUL token identifier for character "*"\r
+*/\r
+define('MUL',"*");\r
+\r
+/**\r
+* @const DIV token identifier for character "/"\r
+*/\r
+define('DIV',"/");\r
+\r
+/**\r
+* @const OPEN token identifier for character "("\r
+*/\r
+define('OPEN',"(");\r
+\r
+/**\r
+* @const CLOSE token identifier for character ")"\r
+*/\r
+define('CLOSE',")");\r
+\r
+/**\r
+* @const COMA token identifier for character ","\r
+*/\r
+define('COMA',",");\r
+\r
+/**\r
+* Class for parsing Excel formulas\r
+*\r
+* @author Xavier Noguer <xnoguer@rezebra.com>\r
+* @package Spreadsheet_WriteExcel\r
+*/\r
+class Parser\r
+  {\r
+/**\r
+* The class constructor\r
+*\r
+* @param integer $byte_order The byte order (Little endian or Big endian) of the architecture\r
+                             (optional). 1 => big endian, 0 (default) => little endian. \r
+*/\r
+  function Parser($byte_order = 0)\r
+    {\r
+    $this->_current_char  = 0;        // The index of the character we are currently looking at.\r
+    $this->_current_token = '';       // The token we are working on.\r
+    $this->_formula       = "";       // The formula to parse.\r
+    $this->_lookahead     = '';       // The character ahead of the current char.\r
+    $this->_parse_tree    = '';       // The parse tree to be generated.\r
+    $this->_initialize_hashes();      // Initialize the hashes: ptg's and function's ptg's\r
+    $this->_byte_order = $byte_order; // Little Endian or Big Endian\r
+    $this->_func_args  = 0;           // Number of arguments for the current function\r
+    $this->_volatile   = 0;\r
+    }\r
+\r
+/**\r
+* Initialize the ptg and function hashes. \r
+*/\r
+  function _initialize_hashes()\r
+    {\r
+    // The Excel ptg indices\r
+    $this->ptg = array(\r
+        'ptgExp'       => 0x01,\r
+        'ptgTbl'       => 0x02,\r
+        'ptgAdd'       => 0x03,\r
+        'ptgSub'       => 0x04,\r
+        'ptgMul'       => 0x05,\r
+        'ptgDiv'       => 0x06,\r
+        'ptgPower'     => 0x07,\r
+        'ptgConcat'    => 0x08,\r
+        'ptgLT'        => 0x09,\r
+        'ptgLE'        => 0x0A,\r
+        'ptgEQ'        => 0x0B,\r
+        'ptgGE'        => 0x0C,\r
+        'ptgGT'        => 0x0D,\r
+        'ptgNE'        => 0x0E,\r
+        'ptgIsect'     => 0x0F,\r
+        'ptgUnion'     => 0x10,\r
+        'ptgRange'     => 0x11,\r
+        'ptgUplus'     => 0x12,\r
+        'ptgUminus'    => 0x13,\r
+        'ptgPercent'   => 0x14,\r
+        'ptgParen'     => 0x15,\r
+        'ptgMissArg'   => 0x16,\r
+        'ptgStr'       => 0x17,\r
+        'ptgAttr'      => 0x19,\r
+        'ptgSheet'     => 0x1A,\r
+        'ptgEndSheet'  => 0x1B,\r
+        'ptgErr'       => 0x1C,\r
+        'ptgBool'      => 0x1D,\r
+        'ptgInt'       => 0x1E,\r
+        'ptgNum'       => 0x1F,\r
+        'ptgArray'     => 0x20,\r
+        'ptgFunc'      => 0x21,\r
+        'ptgFuncVar'   => 0x22,\r
+        'ptgName'      => 0x23,\r
+        'ptgRef'       => 0x24,\r
+        'ptgArea'      => 0x25,\r
+        'ptgMemArea'   => 0x26,\r
+        'ptgMemErr'    => 0x27,\r
+        'ptgMemNoMem'  => 0x28,\r
+        'ptgMemFunc'   => 0x29,\r
+        'ptgRefErr'    => 0x2A,\r
+        'ptgAreaErr'   => 0x2B,\r
+        'ptgRefN'      => 0x2C,\r
+        'ptgAreaN'     => 0x2D,\r
+        'ptgMemAreaN'  => 0x2E,\r
+        'ptgMemNoMemN' => 0x2F,\r
+        'ptgNameX'     => 0x39,\r
+        'ptgRef3d'     => 0x3A,\r
+        'ptgArea3d'    => 0x3B,\r
+        'ptgRefErr3d'  => 0x3C,\r
+        'ptgAreaErr3d' => 0x3D,\r
+        'ptgArrayV'    => 0x40,\r
+        'ptgFuncV'     => 0x41,\r
+        'ptgFuncVarV'  => 0x42,\r
+        'ptgNameV'     => 0x43,\r
+        'ptgRefV'      => 0x44,\r
+        'ptgAreaV'     => 0x45,\r
+        'ptgMemAreaV'  => 0x46,\r
+        'ptgMemErrV'   => 0x47,\r
+        'ptgMemNoMemV' => 0x48,\r
+        'ptgMemFuncV'  => 0x49,\r
+        'ptgRefErrV'   => 0x4A,\r
+        'ptgAreaErrV'  => 0x4B,\r
+        'ptgRefNV'     => 0x4C,\r
+        'ptgAreaNV'    => 0x4D,\r
+        'ptgMemAreaNV' => 0x4E,\r
+        'ptgMemNoMemN' => 0x4F,\r
+        'ptgFuncCEV'   => 0x58,\r
+        'ptgNameXV'    => 0x59,\r
+        'ptgRef3dV'    => 0x5A,\r
+        'ptgArea3dV'   => 0x5B,\r
+        'ptgRefErr3dV' => 0x5C,\r
+        'ptgAreaErr3d' => 0x5D,\r
+        'ptgArrayA'    => 0x60,\r
+        'ptgFuncA'     => 0x61,\r
+        'ptgFuncVarA'  => 0x62,\r
+        'ptgNameA'     => 0x63,\r
+        'ptgRefA'      => 0x64,\r
+        'ptgAreaA'     => 0x65,\r
+        'ptgMemAreaA'  => 0x66,\r
+        'ptgMemErrA'   => 0x67,\r
+        'ptgMemNoMemA' => 0x68,\r
+        'ptgMemFuncA'  => 0x69,\r
+        'ptgRefErrA'   => 0x6A,\r
+        'ptgAreaErrA'  => 0x6B,\r
+        'ptgRefNA'     => 0x6C,\r
+        'ptgAreaNA'    => 0x6D,\r
+        'ptgMemAreaNA' => 0x6E,\r
+        'ptgMemNoMemN' => 0x6F,\r
+        'ptgFuncCEA'   => 0x78,\r
+        'ptgNameXA'    => 0x79,\r
+        'ptgRef3dA'    => 0x7A,\r
+        'ptgArea3dA'   => 0x7B,\r
+        'ptgRefErr3dA' => 0x7C,\r
+        'ptgAreaErr3d' => 0x7D\r
+        );\r
+\r
+    // Thanks to Michael Meeks and Gnumeric for the initial arg values.\r
+    //\r
+    // The following hash was generated by "function_locale.pl" in the distro.\r
+    // Refer to function_locale.pl for non-English function names.\r
+    //\r
+    // The array elements are as follow:\r
+    // ptg:   The Excel function ptg code.\r
+    // args:  The number of arguments that the function takes:\r
+    //           >=0 is a fixed number of arguments.\r
+    //           -1  is a variable  number of arguments.\r
+    // class: The reference, value or array class of the function args.\r
+    // vol:   The function is volatile.\r
+    //\r
+    $this->_functions = array(\r
+          // function                  ptg  args  class  vol\r
+          'COUNT'           => array(   0,   -1,    0,    0 ),\r
+          'IF'              => array(   1,   -1,    1,    0 ),\r
+          'ISNA'            => array(   2,    1,    1,    0 ),\r
+          'ISERROR'         => array(   3,    1,    1,    0 ),\r
+          'SUM'             => array(   4,   -1,    0,    0 ),\r
+          'AVERAGE'         => array(   5,   -1,    0,    0 ),\r
+          'MIN'             => array(   6,   -1,    0,    0 ),\r
+          'MAX'             => array(   7,   -1,    0,    0 ),\r
+          'ROW'             => array(   8,   -1,    0,    0 ),\r
+          'COLUMN'          => array(   9,   -1,    0,    0 ),\r
+          'NA'              => array(  10,    0,    0,    0 ),\r
+          'NPV'             => array(  11,   -1,    1,    0 ),\r
+          'STDEV'           => array(  12,   -1,    0,    0 ),\r
+          'DOLLAR'          => array(  13,   -1,    1,    0 ),\r
+          'FIXED'           => array(  14,   -1,    1,    0 ),\r
+          'SIN'             => array(  15,    1,    1,    0 ),\r
+          'COS'             => array(  16,    1,    1,    0 ),\r
+          'TAN'             => array(  17,    1,    1,    0 ),\r
+          'ATAN'            => array(  18,    1,    1,    0 ),\r
+          'PI'              => array(  19,    0,    1,    0 ),\r
+          'SQRT'            => array(  20,    1,    1,    0 ),\r
+          'EXP'             => array(  21,    1,    1,    0 ),\r
+          'LN'              => array(  22,    1,    1,    0 ),\r
+          'LOG10'           => array(  23,    1,    1,    0 ),\r
+          'ABS'             => array(  24,    1,    1,    0 ),\r
+          'INT'             => array(  25,    1,    1,    0 ),\r
+          'SIGN'            => array(  26,    1,    1,    0 ),\r
+          'ROUND'           => array(  27,    2,    1,    0 ),\r
+          'LOOKUP'          => array(  28,   -1,    0,    0 ),\r
+          'INDEX'           => array(  29,   -1,    0,    1 ),\r
+          'REPT'            => array(  30,    2,    1,    0 ),\r
+          'MID'             => array(  31,    3,    1,    0 ),\r
+          'LEN'             => array(  32,    1,    1,    0 ),\r
+          'VALUE'           => array(  33,    1,    1,    0 ),\r
+          'TRUE'            => array(  34,    0,    1,    0 ),\r
+          'FALSE'           => array(  35,    0,    1,    0 ),\r
+          'AND'             => array(  36,   -1,    0,    0 ),\r
+          'OR'              => array(  37,   -1,    0,    0 ),\r
+          'NOT'             => array(  38,    1,    1,    0 ),\r
+          'MOD'             => array(  39,    2,    1,    0 ),\r
+          'DCOUNT'          => array(  40,    3,    0,    0 ),\r
+          'DSUM'            => array(  41,    3,    0,    0 ),\r
+          'DAVERAGE'        => array(  42,    3,    0,    0 ),\r
+          'DMIN'            => array(  43,    3,    0,    0 ),\r
+          'DMAX'            => array(  44,    3,    0,    0 ),\r
+          'DSTDEV'          => array(  45,    3,    0,    0 ),\r
+          'VAR'             => array(  46,   -1,    0,    0 ),\r
+          'DVAR'            => array(  47,    3,    0,    0 ),\r
+          'TEXT'            => array(  48,    2,    1,    0 ),\r
+          'LINEST'          => array(  49,   -1,    0,    0 ),\r
+          'TREND'           => array(  50,   -1,    0,    0 ),\r
+          'LOGEST'          => array(  51,   -1,    0,    0 ),\r
+          'GROWTH'          => array(  52,   -1,    0,    0 ),\r
+          'PV'              => array(  56,   -1,    1,    0 ),\r
+          'FV'              => array(  57,   -1,    1,    0 ),\r
+          'NPER'            => array(  58,   -1,    1,    0 ),\r
+          'PMT'             => array(  59,   -1,    1,    0 ),\r
+          'RATE'            => array(  60,   -1,    1,    0 ),\r
+          'MIRR'            => array(  61,    3,    0,    0 ),\r
+          'IRR'             => array(  62,   -1,    0,    0 ),\r
+          'RAND'            => array(  63,    0,    1,    1 ),\r
+          'MATCH'           => array(  64,   -1,    0,    0 ),\r
+          'DATE'            => array(  65,    3,    1,    0 ),\r
+          'TIME'            => array(  66,    3,    1,    0 ),\r
+          'DAY'             => array(  67,    1,    1,    0 ),\r
+          'MONTH'           => array(  68,    1,    1,    0 ),\r
+          'YEAR'            => array(  69,    1,    1,    0 ),\r
+          'WEEKDAY'         => array(  70,   -1,    1,    0 ),\r
+          'HOUR'            => array(  71,    1,    1,    0 ),\r
+          'MINUTE'          => array(  72,    1,    1,    0 ),\r
+          'SECOND'          => array(  73,    1,    1,    0 ),\r
+          'NOW'             => array(  74,    0,    1,    1 ),\r
+          'AREAS'           => array(  75,    1,    0,    1 ),\r
+          'ROWS'            => array(  76,    1,    0,    1 ),\r
+          'COLUMNS'         => array(  77,    1,    0,    1 ),\r
+          'OFFSET'          => array(  78,   -1,    0,    1 ),\r
+          'SEARCH'          => array(  82,   -1,    1,    0 ),\r
+          'TRANSPOSE'       => array(  83,    1,    1,    0 ),\r
+          'TYPE'            => array(  86,    1,    1,    0 ),\r
+          'ATAN2'           => array(  97,    2,    1,    0 ),\r
+          'ASIN'            => array(  98,    1,    1,    0 ),\r
+          'ACOS'            => array(  99,    1,    1,    0 ),\r
+          'CHOOSE'          => array( 100,   -1,    1,    0 ),\r
+          'HLOOKUP'         => array( 101,   -1,    0,    0 ),\r
+          'VLOOKUP'         => array( 102,   -1,    0,    0 ),\r
+          'ISREF'           => array( 105,    1,    0,    0 ),\r
+          'LOG'             => array( 109,   -1,    1,    0 ),\r
+          'CHAR'            => array( 111,    1,    1,    0 ),\r
+          'LOWER'           => array( 112,    1,    1,    0 ),\r
+          'UPPER'           => array( 113,    1,    1,    0 ),\r
+          'PROPER'          => array( 114,    1,    1,    0 ),\r
+          'LEFT'            => array( 115,   -1,    1,    0 ),\r
+          'RIGHT'           => array( 116,   -1,    1,    0 ),\r
+          'EXACT'           => array( 117,    2,    1,    0 ),\r
+          'TRIM'            => array( 118,    1,    1,    0 ),\r
+          'REPLACE'         => array( 119,    4,    1,    0 ),\r
+          'SUBSTITUTE'      => array( 120,   -1,    1,    0 ),\r
+          'CODE'            => array( 121,    1,    1,    0 ),\r
+          'FIND'            => array( 124,   -1,    1,    0 ),\r
+          'CELL'            => array( 125,   -1,    0,    1 ),\r
+          'ISERR'           => array( 126,    1,    1,    0 ),\r
+          'ISTEXT'          => array( 127,    1,    1,    0 ),\r
+          'ISNUMBER'        => array( 128,    1,    1,    0 ),\r
+          'ISBLANK'         => array( 129,    1,    1,    0 ),\r
+          'T'               => array( 130,    1,    0,    0 ),\r
+          'N'               => array( 131,    1,    0,    0 ),\r
+          'DATEVALUE'       => array( 140,    1,    1,    0 ),\r
+          'TIMEVALUE'       => array( 141,    1,    1,    0 ),\r
+          'SLN'             => array( 142,    3,    1,    0 ),\r
+          'SYD'             => array( 143,    4,    1,    0 ),\r
+          'DDB'             => array( 144,   -1,    1,    0 ),\r
+          'INDIRECT'        => array( 148,   -1,    1,    1 ),\r
+          'CALL'            => array( 150,   -1,    1,    0 ),\r
+          'CLEAN'           => array( 162,    1,    1,    0 ),\r
+          'MDETERM'         => array( 163,    1,    2,    0 ),\r
+          'MINVERSE'        => array( 164,    1,    2,    0 ),\r
+          'MMULT'           => array( 165,    2,    2,    0 ),\r
+          'IPMT'            => array( 167,   -1,    1,    0 ),\r
+          'PPMT'            => array( 168,   -1,    1,    0 ),\r
+          'COUNTA'          => array( 169,   -1,    0,    0 ),\r
+          'PRODUCT'         => array( 183,   -1,    0,    0 ),\r
+          'FACT'            => array( 184,    1,    1,    0 ),\r
+          'DPRODUCT'        => array( 189,    3,    0,    0 ),\r
+          'ISNONTEXT'       => array( 190,    1,    1,    0 ),\r
+          'STDEVP'          => array( 193,   -1,    0,    0 ),\r
+          'VARP'            => array( 194,   -1,    0,    0 ),\r
+          'DSTDEVP'         => array( 195,    3,    0,    0 ),\r
+          'DVARP'           => array( 196,    3,    0,    0 ),\r
+          'TRUNC'           => array( 197,   -1,    1,    0 ),\r
+          'ISLOGICAL'       => array( 198,    1,    1,    0 ),\r
+          'DCOUNTA'         => array( 199,    3,    0,    0 ),\r
+          'ROUNDUP'         => array( 212,    2,    1,    0 ),\r
+          'ROUNDDOWN'       => array( 213,    2,    1,    0 ),\r
+          'RANK'            => array( 216,   -1,    0,    0 ),\r
+          'ADDRESS'         => array( 219,   -1,    1,    0 ),\r
+          'DAYS360'         => array( 220,   -1,    1,    0 ),\r
+          'TODAY'           => array( 221,    0,    1,    1 ),\r
+          'VDB'             => array( 222,   -1,    1,    0 ),\r
+          'MEDIAN'          => array( 227,   -1,    0,    0 ),\r
+          'SUMPRODUCT'      => array( 228,   -1,    2,    0 ),\r
+          'SINH'            => array( 229,    1,    1,    0 ),\r
+          'COSH'            => array( 230,    1,    1,    0 ),\r
+          'TANH'            => array( 231,    1,    1,    0 ),\r
+          'ASINH'           => array( 232,    1,    1,    0 ),\r
+          'ACOSH'           => array( 233,    1,    1,    0 ),\r
+          'ATANH'           => array( 234,    1,    1,    0 ),\r
+          'DGET'            => array( 235,    3,    0,    0 ),\r
+          'INFO'            => array( 244,    1,    1,    1 ),\r
+          'DB'              => array( 247,   -1,    1,    0 ),\r
+          'FREQUENCY'       => array( 252,    2,    0,    0 ),\r
+          'ERROR.TYPE'      => array( 261,    1,    1,    0 ),\r
+          'REGISTER.ID'     => array( 267,   -1,    1,    0 ),\r
+          'AVEDEV'          => array( 269,   -1,    0,    0 ),\r
+          'BETADIST'        => array( 270,   -1,    1,    0 ),\r
+          'GAMMALN'         => array( 271,    1,    1,    0 ),\r
+          'BETAINV'         => array( 272,   -1,    1,    0 ),\r
+          'BINOMDIST'       => array( 273,    4,    1,    0 ),\r
+          'CHIDIST'         => array( 274,    2,    1,    0 ),\r
+          'CHIINV'          => array( 275,    2,    1,    0 ),\r
+          'COMBIN'          => array( 276,    2,    1,    0 ),\r
+          'CONFIDENCE'      => array( 277,    3,    1,    0 ),\r
+          'CRITBINOM'       => array( 278,    3,    1,    0 ),\r
+          'EVEN'            => array( 279,    1,    1,    0 ),\r
+          'EXPONDIST'       => array( 280,    3,    1,    0 ),\r
+          'FDIST'           => array( 281,    3,    1,    0 ),\r
+          'FINV'            => array( 282,    3,    1,    0 ),\r
+          'FISHER'          => array( 283,    1,    1,    0 ),\r
+          'FISHERINV'       => array( 284,    1,    1,    0 ),\r
+          'FLOOR'           => array( 285,    2,    1,    0 ),\r
+          'GAMMADIST'       => array( 286,    4,    1,    0 ),\r
+          'GAMMAINV'        => array( 287,    3,    1,    0 ),\r
+          'CEILING'         => array( 288,    2,    1,    0 ),\r
+          'HYPGEOMDIST'     => array( 289,    4,    1,    0 ),\r
+          'LOGNORMDIST'     => array( 290,    3,    1,    0 ),\r
+          'LOGINV'          => array( 291,    3,    1,    0 ),\r
+          'NEGBINOMDIST'    => array( 292,    3,    1,    0 ),\r
+          'NORMDIST'        => array( 293,    4,    1,    0 ),\r
+          'NORMSDIST'       => array( 294,    1,    1,    0 ),\r
+          'NORMINV'         => array( 295,    3,    1,    0 ),\r
+          'NORMSINV'        => array( 296,    1,    1,    0 ),\r
+          'STANDARDIZE'     => array( 297,    3,    1,    0 ),\r
+          'ODD'             => array( 298,    1,    1,    0 ),\r
+          'PERMUT'          => array( 299,    2,    1,    0 ),\r
+          'POISSON'         => array( 300,    3,    1,    0 ),\r
+          'TDIST'           => array( 301,    3,    1,    0 ),\r
+          'WEIBULL'         => array( 302,    4,    1,    0 ),\r
+          'SUMXMY2'         => array( 303,    2,    2,    0 ),\r
+          'SUMX2MY2'        => array( 304,    2,    2,    0 ),\r
+          'SUMX2PY2'        => array( 305,    2,    2,    0 ),\r
+          'CHITEST'         => array( 306,    2,    2,    0 ),\r
+          'CORREL'          => array( 307,    2,    2,    0 ),\r
+          'COVAR'           => array( 308,    2,    2,    0 ),\r
+          'FORECAST'        => array( 309,    3,    2,    0 ),\r
+          'FTEST'           => array( 310,    2,    2,    0 ),\r
+          'INTERCEPT'       => array( 311,    2,    2,    0 ),\r
+          'PEARSON'         => array( 312,    2,    2,    0 ),\r
+          'RSQ'             => array( 313,    2,    2,    0 ),\r
+          'STEYX'           => array( 314,    2,    2,    0 ),\r
+          'SLOPE'           => array( 315,    2,    2,    0 ),\r
+          'TTEST'           => array( 316,    4,    2,    0 ),\r
+          'PROB'            => array( 317,   -1,    2,    0 ),\r
+          'DEVSQ'           => array( 318,   -1,    0,    0 ),\r
+          'GEOMEAN'         => array( 319,   -1,    0,    0 ),\r
+          'HARMEAN'         => array( 320,   -1,    0,    0 ),\r
+          'SUMSQ'           => array( 321,   -1,    0,    0 ),\r
+          'KURT'            => array( 322,   -1,    0,    0 ),\r
+          'SKEW'            => array( 323,   -1,    0,    0 ),\r
+          'ZTEST'           => array( 324,   -1,    0,    0 ),\r
+          'LARGE'           => array( 325,    2,    0,    0 ),\r
+          'SMALL'           => array( 326,    2,    0,    0 ),\r
+          'QUARTILE'        => array( 327,    2,    0,    0 ),\r
+          'PERCENTILE'      => array( 328,    2,    0,    0 ),\r
+          'PERCENTRANK'     => array( 329,   -1,    0,    0 ),\r
+          'MODE'            => array( 330,   -1,    2,    0 ),\r
+          'TRIMMEAN'        => array( 331,    2,    0,    0 ),\r
+          'TINV'            => array( 332,    2,    1,    0 ),\r
+          'CONCATENATE'     => array( 336,   -1,    1,    0 ),\r
+          'POWER'           => array( 337,    2,    1,    0 ),\r
+          'RADIANS'         => array( 342,    1,    1,    0 ),\r
+          'DEGREES'         => array( 343,    1,    1,    0 ),\r
+          'SUBTOTAL'        => array( 344,   -1,    0,    0 ),\r
+          'SUMIF'           => array( 345,   -1,    0,    0 ),\r
+          'COUNTIF'         => array( 346,    2,    0,    0 ),\r
+          'COUNTBLANK'      => array( 347,    1,    0,    0 ),\r
+          'ROMAN'           => array( 354,   -1,    1,    0 )\r
+          );\r
+    }\r
+\r
+/**\r
+* Convert a token to the proper ptg value.\r
+*\r
+* @param mixed $token The token to convert.\r
+*/\r
+  function _convert($token)\r
+    {\r
+    if(is_numeric($token))\r
+        {\r
+        return($this->_convert_number($token));\r
+        }\r
+    // match references like A1\r
+    elseif(preg_match("/^([A-I]?[A-Z])(\d+)$/",$token))\r
+        {\r
+        return($this->_convert_ref2d($token));\r
+        }\r
+    // match ranges like A1:B2\r
+    elseif(preg_match("/^([A-I]?[A-Z])(\d+)\:([A-I]?[A-Z])(\d+)$/",$token))\r
+        {\r
+        return($this->_convert_range2d($token));\r
+        }\r
+    // match ranges like A1..B2\r
+    elseif(preg_match("/^([A-I]?[A-Z])(\d+)\.\.([A-I]?[A-Z])(\d+)$/",$token))\r
+        {\r
+        return($this->_convert_range2d($token));\r
+        }\r
+    elseif(isset($this->ptg[$token])) // operators (including parentheses)\r
+        {\r
+        return(pack("C", $this->ptg[$token]));\r
+        }\r
+    elseif(preg_match("/[A-Z0-9À-Ü\.]+/",$token))\r
+        {\r
+        return($this->_convert_function($token,$this->_func_args));\r
+        }\r
+    // if it's an argument, ignore the token (the argument remains)\r
+    elseif($token == 'arg')\r
+        {\r
+        $this->_func_args++;\r
+        return('');\r
+        }\r
+    die("Unknown token $token");\r
+    }\r
+\r
+/**\r
+* Convert a number token to ptgInt or ptgNum\r
+*\r
+* @param mixed $num an integer or double for conersion to its ptg value\r
+*/\r
+  function _convert_number($num)\r
+    {\r
+    // Integer in the range 0..2**16-1\r
+    if ((preg_match("/^\d+$/",$num)) and ($num <= 65535)) {\r
+        return pack("Cv", $this->ptg['ptgInt'], $num);\r
+        }\r
+    else // A float\r
+        {\r
+        if($this->_byte_order) // if it's Big Endian\r
+            {\r
+            $num = strrev($num);\r
+            }\r
+        return pack("Cd", $this->ptg['ptgNum'], $num);\r
+        }\r
+    }\r
+\r
+/**\r
+* Convert a function to a ptgFunc or ptgFuncVarV depending on the number of\r
+* args that it takes.\r
+*\r
+* @param string  $token    The name of the function for convertion to ptg value.\r
+* @param integer $num_args The number of arguments the function recieves.\r
+*/\r
+  function _convert_function($token, $num_args)\r
+    {\r
+    $this->_func_args = 0; // re initialize the number of arguments\r
+    $args     = $this->_functions[$token][1];\r
+    $volatile = $this->_functions[$token][3];\r
+\r
+    if($volatile) {\r
+        $this->_volatile = 1;\r
+        }\r
+    // Fixed number of args eg. TIME($i,$j,$k).\r
+    if ($args >= 0)\r
+        {\r
+        return(pack("Cv", $this->ptg['ptgFuncV'], $this->_functions[$token][0]));\r
+        }\r
+    // Variable number of args eg. SUM($i,$j,$k, ..).\r
+    if ($args == -1) {\r
+        return(pack("CCv", $this->ptg['ptgFuncVarV'], $num_args, $this->_functions[$token][0]));\r
+        }\r
+    }\r
+\r
+/**\r
+* Convert an Excel range such as A1:D4 to a ptgRefV.\r
+*\r
+* @param string $range An Excel range in the A1:A2 or A1..A2 format.\r
+*/\r
+  function _convert_range2d($range)\r
+    {\r
+    $class = 2; // as far as I know, this is magick.\r
+\r
+    // Split the range into 2 cell refs\r
+    if(preg_match("/^([A-I]?[A-Z])(\d+)\:([A-I]?[A-Z])(\d+)$/",$range)) {\r
+        list($cell1, $cell2) = split(':', $range);\r
+        }\r
+    elseif(preg_match("/^([A-I]?[A-Z])(\d+)\.\.([A-I]?[A-Z])(\d+)$/",$range)) {\r
+        list($cell1, $cell2) = split('\.\.', $range);\r
+        }\r
+    else {\r
+        die("Unknown range separator");\r
+        }\r
+\r
+    // Convert the cell references\r
+    list($row1, $col1) = $this->_cell_to_packed_rowcol($cell1);\r
+    list($row2, $col2) = $this->_cell_to_packed_rowcol($cell2);\r
+\r
+    // The ptg value depends on the class of the ptg.\r
+    if ($class == 0) {\r
+        $ptgArea = pack("C", $this->ptg['ptgArea']);\r
+        }\r
+    elseif ($class == 1) {\r
+        $ptgArea = pack("C", $this->ptg['ptgAreaV']);\r
+        }\r
+    elseif ($class == 2) {\r
+        $ptgArea = pack("C", $this->ptg['ptgAreaA']);\r
+        }\r
+    else{\r
+        die("Unknown class ");\r
+        }\r
+\r
+    return($ptgArea . $row1 . $row2 . $col1. $col2);\r
+    }\r
+\r
+/**\r
+* Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.\r
+*\r
+* @param string $cell An Excel cell reference\r
+*/\r
+  function _convert_ref2d($cell)\r
+    {\r
+    $class = 2; // as far as I know, this is magick.\r
+\r
+    // Convert the cell reference\r
+    list($row, $col) = $this->_cell_to_packed_rowcol($cell);\r
+\r
+    // The ptg value depends on the class of the ptg.\r
+    if ($class == 0) {\r
+        $ptgRef = pack("C", $this->ptg['ptgRef']);\r
+        }\r
+    elseif ($class == 1) {\r
+        $ptgRef = pack("C", $this->ptg['ptgRefV']);\r
+        }\r
+    elseif ($class == 2) {\r
+        $ptgRef = pack("C", $this->ptg['ptgRefA']);\r
+        }\r
+    else{\r
+        die("Unknown class ");\r
+        }\r
+    return $ptgRef.$row.$col;\r
+    }\r
+\r
+/**\r
+* pack() row and column into the required 3 byte format.\r
+*\r
+* @param string $cell The Excel cell reference to be packed\r
+*/\r
+  function _cell_to_packed_rowcol($cell)\r
+    {\r
+    list($row, $col, $row_rel, $col_rel) = $this->_cell_to_rowcol($cell);\r
+    if ($col >= 256) {\r
+        die("Column in: $cell greater than 255 ");\r
+        }\r
+    if ($row >= 16384) {\r
+        die("Row in: $cell greater than 16384 ");\r
+        }\r
+\r
+    // Set the high bits to indicate if row or col are relative.\r
+    $row    |= $col_rel << 14;\r
+    $row    |= $row_rel << 15;\r
+\r
+    $row     = pack('v', $row);\r
+    $col     = pack('C', $col);\r
+\r
+    return (array($row, $col));\r
+    }\r
+\r
+/**\r
+* Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero\r
+* indexed row and column number. Also returns two boolean values to indicate\r
+* whether the row or column are relative references.\r
+*\r
+* @param string $cell The Excel cell reference in A1 format.\r
+*/\r
+  function _cell_to_rowcol($cell)\r
+    {\r
+    preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/',$cell,$match);\r
+    // return absolute column if there is a $ in the ref\r
+    $col_rel = empty($match[1]) ? 1 : 0;\r
+    $col_ref = $match[2];\r
+    $row_rel = empty($match[3]) ? 1 : 0;\r
+    $row     = $match[4];\r
+\r
+    // Convert base26 column string to a number.\r
+    $expn   = strlen($col_ref) - 1;\r
+    $col    = 0;\r
+    for($i=0; $i < strlen($col_ref); $i++)\r
+    {\r
+        $col += (ord($col_ref{$i}) - ord('A') + 1) * pow(26, $expn);\r
+        $expn--;\r
+    }\r
+\r
+    // Convert 1-index to zero-index\r
+    $row--;\r
+    $col--;\r
+\r
+    return(array($row, $col, $row_rel, $col_rel));\r
+    }\r
+\r
+/**\r
+* Advance to the next valid token.\r
+*/\r
+  function _advance()\r
+    {\r
+    $i = $this->_current_char;\r
+    // eat up white spaces\r
+    if($i < strlen($this->_formula))\r
+        {\r
+        while($this->_formula{$i} == " ")\r
+            {\r
+            $i++;\r
+            }\r
+        if($i < strlen($this->_formula) - 1)\r
+            {\r
+            $this->_lookahead = $this->_formula{$i+1};\r
+            }\r
+        $token = "";\r
+        }\r
+    while($i < strlen($this->_formula))\r
+        {\r
+        $token .= $this->_formula{$i};\r
+        if($this->_match($token) != '')\r
+            {\r
+            if($i < strlen($this->_formula) - 1)\r
+                {\r
+                $this->_lookahead = $this->_formula{$i+1};\r
+                }\r
+            $this->_current_char = $i + 1;\r
+            $this->_current_token = $token;\r
+            return(1);\r
+            }\r
+        $this->_lookahead = $this->_formula{$i+2};\r
+        $i++;\r
+        }\r
+    //die("Lexical error ".$this->_current_char);\r
+    }\r
+\r
+/**\r
+* Checks if it's a valid token.\r
+*\r
+* @param mixed $token The token to check.\r
+*/\r
+  function _match($token)\r
+    {\r
+    switch($token)\r
+        {\r
+        case ADD:\r
+            return($token);\r
+            break;\r
+        case SUB:\r
+            return($token);\r
+            break;\r
+        case MUL:\r
+            return($token);\r
+            break;\r
+        case DIV:\r
+            return($token);\r
+            break;\r
+        case OPEN:\r
+            return($token);\r
+            break;\r
+        case CLOSE:\r
+            return($token);\r
+            break;\r
+        case COMA:\r
+            return($token);\r
+            break;\r
+        default:\r
+           // if it's a reference\r
+            if(eregi("^[A-I]?[A-Z][0-9]+$",$token) and \r
+              !ereg("[0-9]",$this->_lookahead) and \r
+               ($this->_lookahead != ':') and ($this->_lookahead != '.'))\r
+                {\r
+                return($token);\r
+                }\r
+            // if it's a range (A1:A2)\r
+            elseif(eregi("^[A-I]?[A-Z][0-9]+:[A-I]?[A-Z][0-9]+$",$token) and \r
+                  !ereg("[0-9]",$this->_lookahead))\r
+               {\r
+               return($token);\r
+               }\r
+            // if it's a range (A1..A2)\r
+            elseif(eregi("^[A-I]?[A-Z][0-9]+\.\.[A-I]?[A-Z][0-9]+$",$token) and \r
+                  !ereg("[0-9]",$this->_lookahead))\r
+               {\r
+               return($token);\r
+               }\r
+            elseif(is_numeric($token) and !is_numeric($token.$this->_lookahead))\r
+                {\r
+                return($token);\r
+                }\r
+            // if it's a function call\r
+            elseif(eregi("^[A-Z0-9À-Ü\.]+$",$token) and ($this->_lookahead == "("))\r
+\r
+               {\r
+               return($token);\r
+               }\r
+            return '';\r
+        }\r
+    }\r
+\r
+/**\r
+* The parsing method. It parses a formula.\r
+*\r
+* @access public\r
+* @param string $formula The formula to parse, without the initial equal sign (=).\r
+*/\r
+  function parse($formula)\r
+    {\r
+    $this->_current_char = 0;\r
+    $this->_formula      = $formula;\r
+    $this->_lookahead    = $formula{1};\r
+    $this->_advance();\r
+    $this->_parse_tree   = $this->_expression();\r
+    }\r
+\r
+/**\r
+* It parses a expression. It assumes the following rule:\r
+* Expr -> Term [("+" | "-") Term]\r
+*\r
+* @return mixed The parsed ptg'd tree\r
+*/\r
+  function _expression()\r
+    {\r
+    $result = $this->_term();\r
+    while ($this->_current_token == ADD or $this->_current_token == SUB)\r
+        {\r
+        if ($this->_current_token == ADD)\r
+            {\r
+            $this->_advance();\r
+            $result = $this->_create_tree('ptgAdd', $result, $this->_term());\r
+            }\r
+        else \r
+            {\r
+            $this->_advance();\r
+            $result = $this->_create_tree('ptgSub', $result, $this->_term());\r
+            }\r
+        }\r
+    return $result;\r
+    }\r
+\r
+/**\r
+* This function just introduces a ptgParen element in the tree, so that Excel\r
+* doesn't get confused when working with a parenthesized formula afterwards.\r
+*\r
+* @see _fact\r
+* @return mixed The parsed ptg'd tree\r
+*/\r
+  function _parenthesized_expression()\r
+    {\r
+    $result = $this->_create_tree('ptgParen', $this->_expression(), '');\r
+    return($result);\r
+    }\r
+\r
+/**\r
+* It parses a term. It assumes the following rule:\r
+* Term -> Fact [("*" | "/") Fact]\r
+*\r
+* @return mixed The parsed ptg'd tree\r
+*/\r
+  function _term()\r
+    {\r
+    $result = $this->_fact();\r
+    while ($this->_current_token == MUL || $this->_current_token == DIV)\r
+        {\r
+        if ($this->_current_token == MUL)\r
+            {\r
+            $this->_advance();\r
+            $result = $this->_create_tree('ptgMul', $result, $this->_fact());\r
+            }\r
+        else \r
+            {\r
+            $this->_advance();\r
+            $result = $this->_create_tree('ptgDiv', $result, $this->_fact());\r
+            }\r
+        }\r
+    return($result);\r
+    }\r
+\r
+/**\r
+* It parses a factor. It assumes the following rule:\r
+* Fact -> ( Expr )\r
+*       | CellRef\r
+*       | CellRange\r
+*       | Number\r
+*       | Function\r
+*\r
+* @return mixed The parsed ptg'd tree\r
+*/\r
+  function _fact()\r
+    {\r
+    if ($this->_current_token == OPEN)\r
+        {\r
+        $this->_advance();         // eat the "("\r
+        $result = $this->_parenthesized_expression();//$this->_expression();\r
+\r
+        if ($this->_current_token != CLOSE) {\r
+            die("')' token expected.");\r
+            }\r
+        $this->_advance();         // eat the ")"\r
+        return($result);\r
+        }\r
+    // if it's a reference\r
+    if (eregi("^[A-I]?[A-Z][0-9]+$",$this->_current_token))\r
+        {\r
+        $result = $this->_create_tree($this->_current_token, '', '');\r
+        $this->_advance();\r
+        return($result);\r
+        }\r
+    // if it's a range\r
+    elseif (eregi("^[A-I]?[A-Z][0-9]+:[A-I]?[A-Z][0-9]+$",$this->_current_token) or \r
+            eregi("^[A-I]?[A-Z][0-9]+\.\.[A-I]?[A-Z][0-9]+$",$this->_current_token)) \r
+        {\r
+        $result = $this->_current_token;\r
+        $this->_advance();\r
+        return($result);\r
+        }\r
+    elseif (is_numeric($this->_current_token))\r
+        {\r
+        $result = $this->_create_tree($this->_current_token, '', '');\r
+        $this->_advance();\r
+        return($result);\r
+        }\r
+    // if it's a function call\r
+    elseif (eregi("^[A-Z0-9À-Ü\.]+$",$this->_current_token))\r
+        {\r
+        $result = $this->_func();\r
+        return($result);\r
+        }\r
+    die("Sintactic error: ".$this->_current_token.", lookahead: ".\r
+        $this->_lookahead.", current char: ".$this->_current_char);\r
+    }\r
+\r
+/**\r
+* It parses a function call. It assumes the following rule:\r
+* Func -> ( Expr [,Expr]* )\r
+*\r
+*/\r
+  function _func()\r
+    {\r
+    $num_args = 0; // number of arguments received\r
+    $function = $this->_current_token;\r
+    $this->_advance();\r
+    $this->_advance();         // eat the "("\r
+    while($this->_current_token != ')')\r
+        {\r
+        if($num_args > 0)\r
+            {\r
+            if($this->_current_token == COMA) {\r
+                $this->_advance();  // eat the ","\r
+                }\r
+            else {\r
+                die("Sintactic error: coma expected $num_args");\r
+                }\r
+            $result = $this->_create_tree('arg', $result, $this->_expression());\r
+            }\r
+        else {\r
+            $result = $this->_create_tree('arg', '', $this->_expression());\r
+            }\r
+        $num_args++;\r
+        }\r
+    $args = $this->_functions[$function][1];\r
+    // If fixed number of args eg. TIME($i,$j,$k). Check that the number of args is valid.\r
+    if (($args >= 0) and ($args != $num_args))\r
+        {\r
+        die("Incorrect number of arguments in function $function() ");\r
+        }\r
+\r
+    $result = $this->_create_tree($function, $result, '');\r
+    $this->_advance();         // eat the ")"\r
+    return($result);\r
+    }\r
+\r
+/**\r
+* Creates a tree. In fact an array which may have one or two arrays (sub-trees)\r
+* as elements.\r
+*\r
+* @param mixed $value The value of this node.\r
+* @param mixed $left  The left array (sub-tree) or a final node.\r
+* @param mixed $right The right array (sub-tree) or a final node.\r
+*/\r
+  function _create_tree($value, $left, $right)\r
+    {\r
+    return array('value' => $value, 'left' => $left, 'right' => $right);\r
+    }\r
+\r
+/**\r
+* Builds a string containing the tree in reverse polish notation (What you \r
+* would use in a HP calculator stack).\r
+* The following tree:\r
+* \r
+*    +\r
+*   / \\r
+*  2   3\r
+*\r
+* produces: "23+"\r
+*\r
+* The following tree:\r
+*\r
+*    +\r
+*   / \\r
+*  3   *\r
+*     / \\r
+*    6   A1\r
+*\r
+* produces: "36A1*+"\r
+*\r
+* In fact all operands, functions, references, etc... are written as ptg's\r
+*\r
+* @access public\r
+* @param array $tree The optional tree to convert.\r
+*/\r
+  function to_reverse_polish($tree = array())\r
+    {\r
+    $polish = ""; // the string we are going to return\r
+    if (empty($tree)) // If it's the first call use _parse_tree\r
+        {\r
+        $tree = $this->_parse_tree;\r
+        }\r
+    if (is_array($tree['left']))\r
+        {\r
+        $polish .= $this->to_reverse_polish($tree['left']);\r
+        }\r
+    elseif($tree['left'] != '') // It's a final node\r
+        {\r
+        $polish .= $this->_convert($tree['left']); //$tree['left'];\r
+        }\r
+    if (is_array($tree['right']))\r
+        {\r
+        $polish .= $this->to_reverse_polish($tree['right']);\r
+        }\r
+    elseif($tree['right'] != '') // It's a final node\r
+        {\r
+        $polish .= $this->_convert($tree['right']);\r
+        }\r
+    $polish .= $this->_convert($tree['value']);\r
+    return $polish;\r
+    }\r
+  }\r
+?>
\ No newline at end of file
diff --git a/mod/attendance/write_excel/Workbook.php b/mod/attendance/write_excel/Workbook.php
new file mode 100644 (file)
index 0000000..4d6cbcc
--- /dev/null
@@ -0,0 +1,953 @@
+<?php\r
+/*\r
+*  Module written/ported by Xavier Noguer <xnoguer@rezebra.com>\r
+*\r
+*  The majority of this is _NOT_ my code.  I simply ported it from the\r
+*  PERL Spreadsheet::WriteExcel module.\r
+*\r
+*  The author of the Spreadsheet::WriteExcel module is John McNamara \r
+*  <jmcnamara@cpan.org>\r
+*\r
+*  I _DO_ maintain this code, and John McNamara has nothing to do with the\r
+*  porting of this code to PHP.  Any questions directly related to this\r
+*  class library should be directed to me.\r
+*\r
+*  License Information:\r
+*\r
+*    Spreadsheet::WriteExcel:  A library for generating Excel Spreadsheets\r
+*    Copyright (C) 2002 Xavier Noguer xnoguer@rezebra.com\r
+*\r
+*    This library is free software; you can redistribute it and/or\r
+*    modify it under the terms of the GNU Lesser General Public\r
+*    License as published by the Free Software Foundation; either\r
+*    version 2.1 of the License, or (at your option) any later version.\r
+*\r
+*    This library is distributed in the hope that it will be useful,\r
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+*    Lesser General Public License for more details.\r
+*\r
+*    You should have received a copy of the GNU Lesser General Public\r
+*    License along with this library; if not, write to the Free Software\r
+*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
+*/\r
+\r
+require_once('Format.php');\r
+require_once('OLEwriter.php');\r
+require_once('BIFFwriter.php');\r
+\r
+/**\r
+* Class for generating Excel Spreadsheets\r
+*\r
+* @author Xavier Noguer <xnoguer@rezebra.com>\r
+* @package Spreadsheet_WriteExcel\r
+*/\r
+\r
+class Workbook extends BIFFwriter\r
+{\r
+    /**\r
+    * Class constructor\r
+    *\r
+    * @param string filename for storing the workbook. "-" for writing to stdout.\r
+    */\r
+    function Workbook($filename)\r
+    {\r
+        $this->BIFFwriter(); // It needs to call its parent's constructor explicitly\r
+    \r
+        $this->_filename         = $filename;\r
+        $this->parser            = new Parser($this->_byte_order);\r
+        $this->_1904             = 0;\r
+        $this->activesheet       = 0;\r
+        $this->firstsheet        = 0;\r
+        $this->selected          = 0;\r
+        $this->xf_index          = 16; // 15 style XF's and 1 cell XF.\r
+        $this->_fileclosed       = 0;\r
+        $this->_biffsize         = 0;\r
+        $this->sheetname         = "Sheet";\r
+        $this->tmp_format        = new Format();\r
+        $this->worksheets        = array();\r
+        $this->sheetnames        = array();\r
+        $this->formats           = array();\r
+        $this->palette           = array();\r
+    \r
+        // Add the default format for hyperlinks\r
+        $this->url_format =& $this->add_format(array('color' => 'blue', 'underline' => 1));\r
+    \r
+        // Check for a filename\r
+        //if ($this->_filename == '') {\r
+        //    die('Filename required by Spreadsheet::WriteExcel->new()');\r
+        //}\r
+    \r
+        # Warn if tmpfiles can't be used.\r
+        //$this->tmpfile_warning();\r
+        $this->_set_palette_xl97();\r
+    }\r
+    \r
+    /**\r
+    * Calls finalization methods and explicitly close the OLEwriter file\r
+    * handle.\r
+    */\r
+    function close()\r
+    {\r
+        if ($this->_fileclosed) { // Prevent close() from being called twice.\r
+            return;\r
+        }\r
+        $this->store_workbook();\r
+        $this->_fileclosed = 1;\r
+    }\r
+    \r
+    \r
+    /**\r
+    * An accessor for the _worksheets[] array\r
+    * Returns an array of the worksheet objects in a workbook\r
+    *\r
+    * @return array\r
+    */\r
+    function sheets()\r
+    {\r
+        return($this->worksheets());\r
+    }\r
+    \r
+    /**\r
+    * An accessor for the _worksheets[] array.\r
+    *\r
+    * @return array\r
+    */\r
+    function worksheets()\r
+    {\r
+        return($this->worksheets);\r
+    }\r
+    \r
+    /**\r
+    * Add a new worksheet to the Excel workbook.\r
+    * TODO: Add accessor for $this->{_sheetname} for international Excel versions.\r
+    *\r
+    * @access public\r
+    * @param string $name the optional name of the worksheet\r
+    * @return &object reference to a worksheet object\r
+    */\r
+    function &add_worksheet($name = '')\r
+    {\r
+        $index     = count($this->worksheets);\r
+        $sheetname = $this->sheetname;\r
+\r
+        if($name == '') {\r
+            $name = $sheetname.($index+1); \r
+        }\r
+    \r
+        // Check that sheetname is <= 31 chars (Excel limit).\r
+        if(strlen($name) > 31) {\r
+            die("Sheetname $name must be <= 31 chars");\r
+        }\r
+    \r
+        // Check that the worksheet name doesn't already exist: a fatal Excel error.\r
+        for($i=0; $i < count($this->worksheets); $i++)\r
+        {\r
+            if($name == $this->worksheets[$i]->get_name()) {\r
+                die("Worksheet '$name' already exists");\r
+            }\r
+        }\r
+    \r
+        $worksheet = new Worksheet($name,$index,$this->activesheet,\r
+                                   $this->firstsheet,$this->url_format,\r
+                                   $this->parser);\r
+        $this->worksheets[$index] = &$worksheet;      // Store ref for iterator\r
+        $this->sheetnames[$index] = $name;            // Store EXTERNSHEET names\r
+        //$this->parser->set_ext_sheet($name,$index); // Store names in Formula.php\r
+        return($worksheet);\r
+    }\r
+    \r
+    /**\r
+    * DEPRECATED!! Use add_worksheet instead\r
+    *\r
+    * @access public\r
+    * @deprecated Use add_worksheet instead\r
+    * @param string $name the optional name of the worksheet\r
+    * @return &object reference to a worksheet object\r
+    */\r
+    function &addworksheet($name = '')\r
+    {\r
+        return($this->add_worksheet($name));\r
+    }\r
+    \r
+    /**\r
+    * Add a new format to the Excel workbook. This adds an XF record and\r
+    * a FONT record. Also, pass any properties to the Format constructor.\r
+    *\r
+    * @access public\r
+    * @param array $properties array with properties for initializing the format (see Format.php)\r
+    * @return &object reference to an XF format\r
+    */\r
+    function &add_format($properties = array())\r
+    {\r
+        $format = new Format($this->xf_index,$properties);\r
+        $this->xf_index += 1;\r
+        $this->formats[] = &$format;\r
+        return($format);\r
+    }\r
+    \r
+    /**\r
+    * DEPRECATED!! Use add_format instead\r
+    *\r
+    * @access public\r
+    * @deprecated Use add_format instead\r
+    * @param array $properties array with properties for initializing the format (see Format.php)\r
+    * @return &object reference to an XF format\r
+    */\r
+    function &addformat($properties = array())\r
+    {\r
+         return($this->add_format($properties));\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Change the RGB components of the elements in the colour palette.\r
+    *\r
+    * @access public\r
+    * @param integer $index colour index\r
+    * @param integer $red   red RGB value [0-255]\r
+    * @param integer $green green RGB value [0-255]\r
+    * @param integer $blue  blue RGB value [0-255]\r
+    * @return integer The palette index for the custom color\r
+    */\r
+    function set_custom_color($index,$red,$green,$blue)\r
+    {\r
+        // Match a HTML #xxyyzz style parameter\r
+        /*if (defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) {\r
+            @_ = ($_[0], hex $1, hex $2, hex $3);\r
+        }*/\r
+    \r
+        // Check that the colour index is the right range\r
+        if ($index < 8 or $index > 64) {\r
+            die("Color index $index outside range: 8 <= index <= 64");\r
+        }\r
+    \r
+        // Check that the colour components are in the right range\r
+        if ( ($red   < 0 or $red   > 255) ||\r
+             ($green < 0 or $green > 255) ||\r
+             ($blue  < 0 or $blue  > 255) )  \r
+        {\r
+            die("Color component outside range: 0 <= color <= 255");\r
+        }\r
+\r
+        $index -= 8; // Adjust colour index (wingless dragonfly)\r
+        \r
+        // Set the RGB value\r
+        $this->palette[$index] = array($red, $green, $blue, 0);\r
+        return($index + 8);\r
+    }\r
+    \r
+    /**\r
+    * Sets the colour palette to the Excel 97+ default.\r
+    */\r
+    function _set_palette_xl97()\r
+    {\r
+        $this->palette = array(\r
+                           array(0x00, 0x00, 0x00, 0x00),   // 8\r
+                           array(0xff, 0xff, 0xff, 0x00),   // 9\r
+                           array(0xff, 0x00, 0x00, 0x00),   // 10\r
+                           array(0x00, 0xff, 0x00, 0x00),   // 11\r
+                           array(0x00, 0x00, 0xff, 0x00),   // 12\r
+                           array(0xff, 0xff, 0x00, 0x00),   // 13\r
+                           array(0xff, 0x00, 0xff, 0x00),   // 14\r
+                           array(0x00, 0xff, 0xff, 0x00),   // 15\r
+                           array(0x80, 0x00, 0x00, 0x00),   // 16\r
+                           array(0x00, 0x80, 0x00, 0x00),   // 17\r
+                           array(0x00, 0x00, 0x80, 0x00),   // 18\r
+                           array(0x80, 0x80, 0x00, 0x00),   // 19\r
+                           array(0x80, 0x00, 0x80, 0x00),   // 20\r
+                           array(0x00, 0x80, 0x80, 0x00),   // 21\r
+                           array(0xc0, 0xc0, 0xc0, 0x00),   // 22\r
+                           array(0x80, 0x80, 0x80, 0x00),   // 23\r
+                           array(0x99, 0x99, 0xff, 0x00),   // 24\r
+                           array(0x99, 0x33, 0x66, 0x00),   // 25\r
+                           array(0xff, 0xff, 0xcc, 0x00),   // 26\r
+                           array(0xcc, 0xff, 0xff, 0x00),   // 27\r
+                           array(0x66, 0x00, 0x66, 0x00),   // 28\r
+                           array(0xff, 0x80, 0x80, 0x00),   // 29\r
+                           array(0x00, 0x66, 0xcc, 0x00),   // 30\r
+                           array(0xcc, 0xcc, 0xff, 0x00),   // 31\r
+                           array(0x00, 0x00, 0x80, 0x00),   // 32\r
+                           array(0xff, 0x00, 0xff, 0x00),   // 33\r
+                           array(0xff, 0xff, 0x00, 0x00),   // 34\r
+                           array(0x00, 0xff, 0xff, 0x00),   // 35\r
+                           array(0x80, 0x00, 0x80, 0x00),   // 36\r
+                           array(0x80, 0x00, 0x00, 0x00),   // 37\r
+                           array(0x00, 0x80, 0x80, 0x00),   // 38\r
+                           array(0x00, 0x00, 0xff, 0x00),   // 39\r
+                           array(0x00, 0xcc, 0xff, 0x00),   // 40\r
+                           array(0xcc, 0xff, 0xff, 0x00),   // 41\r
+                           array(0xcc, 0xff, 0xcc, 0x00),   // 42\r
+                           array(0xff, 0xff, 0x99, 0x00),   // 43\r
+                           array(0x99, 0xcc, 0xff, 0x00),   // 44\r
+                           array(0xff, 0x99, 0xcc, 0x00),   // 45\r
+                           array(0xcc, 0x99, 0xff, 0x00),   // 46\r
+                           array(0xff, 0xcc, 0x99, 0x00),   // 47\r
+                           array(0x33, 0x66, 0xff, 0x00),   // 48\r
+                           array(0x33, 0xcc, 0xcc, 0x00),   // 49\r
+                           array(0x99, 0xcc, 0x00, 0x00),   // 50\r
+                           array(0xff, 0xcc, 0x00, 0x00),   // 51\r
+                           array(0xff, 0x99, 0x00, 0x00),   // 52\r
+                           array(0xff, 0x66, 0x00, 0x00),   // 53\r
+                           array(0x66, 0x66, 0x99, 0x00),   // 54\r
+                           array(0x96, 0x96, 0x96, 0x00),   // 55\r
+                           array(0x00, 0x33, 0x66, 0x00),   // 56\r
+                           array(0x33, 0x99, 0x66, 0x00),   // 57\r
+                           array(0x00, 0x33, 0x00, 0x00),   // 58\r
+                           array(0x33, 0x33, 0x00, 0x00),   // 59\r
+                           array(0x99, 0x33, 0x00, 0x00),   // 60\r
+                           array(0x99, 0x33, 0x66, 0x00),   // 61\r
+                           array(0x33, 0x33, 0x99, 0x00),   // 62\r
+                           array(0x33, 0x33, 0x33, 0x00),   // 63\r
+                         );\r
+    }\r
+    \r
+    \r
+    ###############################################################################\r
+    #\r
+    # _tmpfile_warning()\r
+    #\r
+    # Check that tmp files can be created for use in Worksheet.pm. A CGI, mod_perl\r
+    # or IIS might not have permission to create tmp files. The test is here rather\r
+    # than in Worksheet.pm so that only one warning is given.\r
+    #\r
+    /*sub _tmpfile_warning {\r
+    \r
+        my $fh = IO::File->new_tmpfile();\r
+    \r
+        if ((not defined $fh) && ($^W)) {\r
+            carp("Unable to create tmp files via IO::File->new_tmpfile(). " .\r
+                 "Storing data in memory")\r
+    }\r
+    }*/\r
+    \r
+    /**\r
+    * Assemble worksheets into a workbook and send the BIFF data to an OLE\r
+    * storage.\r
+    */\r
+    function store_workbook()\r
+    {\r
+        // Ensure that at least one worksheet has been selected.\r
+        if ($this->activesheet == 0) {\r
+            $this->worksheets[0]->selected = 1;\r
+        }\r
+    \r
+        // Calculate the number of selected worksheet tabs and call the finalization\r
+        // methods for each worksheet\r
+        for($i=0; $i < count($this->worksheets); $i++)\r
+        {\r
+            if($this->worksheets[$i]->selected)\r
+              $this->selected++;\r
+            $this->worksheets[$i]->close($this->sheetnames);\r
+        }\r
+    \r
+        // Add Workbook globals\r
+        $this->_store_bof(0x0005);\r
+        $this->_store_externs();    // For print area and repeat rows\r
+        $this->_store_names();      // For print area and repeat rows\r
+        $this->_store_window1();\r
+        $this->_store_1904();\r
+        $this->_store_all_fonts();\r
+        $this->_store_all_num_formats();\r
+        $this->_store_all_xfs();\r
+        $this->_store_all_styles();\r
+        $this->_store_palette();\r
+        $this->_calc_sheet_offsets();\r
+    \r
+        // Add BOUNDSHEET records\r
+        for($i=0; $i < count($this->worksheets); $i++) {\r
+            $this->_store_boundsheet($this->worksheets[$i]->name,$this->worksheets[$i]->offset);\r
+        }\r
+    \r
+        // End Workbook globals\r
+        $this->_store_eof();\r
+\r
+        // Store the workbook in an OLE container\r
+        $this->_store_OLE_file();\r
+    }\r
+    \r
+    /**\r
+    * Store the workbook in an OLE container if the total size of the workbook data\r
+    * is less than ~ 7MB.\r
+    */\r
+    function _store_OLE_file()\r
+    {\r
+        $OLE  = new OLEwriter($this->_filename);\r
+        // Write Worksheet data if data <~ 7MB\r
+        if ($OLE->set_size($this->_biffsize))\r
+        {\r
+            $OLE->write_header();\r
+            $OLE->write($this->_data);\r
+            foreach($this->worksheets as $sheet) \r
+            {\r
+                while ($tmp = $sheet->get_data()) {\r
+                    $OLE->write($tmp);\r
+                }\r
+            }\r
+        }\r
+        $OLE->close();\r
+    }\r
+    \r
+    /**\r
+    * Calculate offsets for Worksheet BOF records.\r
+    */\r
+    function _calc_sheet_offsets()\r
+    {\r
+        $BOF     = 11;\r
+        $EOF     = 4;\r
+        $offset  = $this->_datasize;\r
+        for($i=0; $i < count($this->worksheets); $i++) {\r
+            $offset += $BOF + strlen($this->worksheets[$i]->name);\r
+        }\r
+        $offset += $EOF;\r
+        for($i=0; $i < count($this->worksheets); $i++) {\r
+            $this->worksheets[$i]->offset = $offset;\r
+            $offset += $this->worksheets[$i]->_datasize;\r
+        }\r
+        $this->_biffsize = $offset;\r
+    }\r
+    \r
+    /**\r
+    * Store the Excel FONT records.\r
+    */\r
+    function _store_all_fonts()\r
+    {\r
+        // tmp_format is added by new(). We use this to write the default XF's\r
+        $format = $this->tmp_format;\r
+        $font   = $format->get_font();\r
+    \r
+        // Note: Fonts are 0-indexed. According to the SDK there is no index 4,\r
+        // so the following fonts are 0, 1, 2, 3, 5\r
+        //\r
+        for($i=1; $i <= 5; $i++){\r
+            $this->_append($font);\r
+        }\r
+    \r
+        // Iterate through the XF objects and write a FONT record if it isn't the\r
+        // same as the default FONT and if it hasn't already been used.\r
+        //\r
+        $fonts = array();\r
+        $index = 6;                  // The first user defined FONT\r
+    \r
+        $key = $format->get_font_key(); // The default font from _tmp_format\r
+        $fonts[$key] = 0;               // Index of the default font\r
+    \r
+        for($i=0; $i < count($this->formats); $i++) {\r
+            $key = $this->formats[$i]->get_font_key();\r
+            if (isset($fonts[$key])) {\r
+                // FONT has already been used\r
+                $this->formats[$i]->font_index = $fonts[$key];\r
+            }\r
+            else {\r
+                // Add a new FONT record\r
+                $fonts[$key]        = $index;\r
+                $this->formats[$i]->font_index = $index;\r
+                $index++;\r
+                $font = $this->formats[$i]->get_font();\r
+                $this->_append($font);\r
+            }\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Store user defined numerical formats i.e. FORMAT records\r
+    */\r
+    function _store_all_num_formats()\r
+    {\r
+        // Leaning num_format syndrome\r
+        $hash_num_formats = array();\r
+        $num_formats      = array();\r
+        $index = 164;\r
+    \r
+        // Iterate through the XF objects and write a FORMAT record if it isn't a\r
+        // built-in format type and if the FORMAT string hasn't already been used.\r
+        //\r
+        for($i=0; $i < count($this->formats); $i++)\r
+        {\r
+            $num_format = $this->formats[$i]->_num_format;\r
+    \r
+            // Check if $num_format is an index to a built-in format.\r
+            // Also check for a string of zeros, which is a valid format string\r
+            // but would evaluate to zero.\r
+            //\r
+            if (!preg_match("/^0+\d/",$num_format))\r
+            {\r
+                if (preg_match("/^\d+$/",$num_format)) { // built-in format\r
+                    continue;\r
+                }\r
+            }\r
+    \r
+            if (isset($hash_num_formats[$num_format])) {\r
+                // FORMAT has already been used\r
+                $this->formats[$i]->_num_format = $hash_num_formats[$num_format];\r
+            }\r
+            else\r
+            {\r
+                // Add a new FORMAT\r
+                $hash_num_formats[$num_format]  = $index;\r
+                $this->formats[$i]->_num_format = $index;\r
+                array_push($num_formats,$num_format);\r
+                $index++;\r
+            }\r
+        }\r
+    \r
+        // Write the new FORMAT records starting from 0xA4\r
+        $index = 164;\r
+        foreach ($num_formats as $num_format)\r
+        {\r
+            $this->_store_num_format($num_format,$index);\r
+            $index++;\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Write all XF records.\r
+    */\r
+    function _store_all_xfs()\r
+    {\r
+        // tmp_format is added by the constructor. We use this to write the default XF's\r
+        // The default font index is 0\r
+        //\r
+        $format = $this->tmp_format;\r
+        for ($i=0; $i <= 14; $i++)\r
+        {\r
+            $xf = $format->get_xf('style'); // Style XF\r
+            $this->_append($xf);\r
+        }\r
+    \r
+        $xf = $format->get_xf('cell');      // Cell XF\r
+        $this->_append($xf);\r
+    \r
+        // User defined XFs\r
+        for($i=0; $i < count($this->formats); $i++)\r
+        {\r
+            $xf = $this->formats[$i]->get_xf('cell');\r
+            $this->_append($xf);\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Write all STYLE records.\r
+    */\r
+    function _store_all_styles()\r
+    {\r
+        $this->_store_style();\r
+    }\r
+    \r
+    /**\r
+    * Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for\r
+    * the NAME records.\r
+    */\r
+    function _store_externs()\r
+    {\r
+        // Create EXTERNCOUNT with number of worksheets\r
+        $this->_store_externcount(count($this->worksheets));\r
+    \r
+        // Create EXTERNSHEET for each worksheet\r
+        foreach ($this->sheetnames as $sheetname) {\r
+            $this->_store_externsheet($sheetname);\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Write the NAME record to define the print area and the repeat rows and cols.\r
+    */\r
+    function _store_names()\r
+    {\r
+        // Create the print area NAME records\r
+        foreach ($this->worksheets as $worksheet)\r
+        {\r
+            // Write a Name record if the print area has been defined\r
+            if (isset($worksheet->_print_rowmin))\r
+            {\r
+                $this->store_name_short(\r
+                    $worksheet->index,\r
+                    0x06, // NAME type\r
+                    $worksheet->_print_rowmin,\r
+                    $worksheet->_print_rowmax,\r
+                    $worksheet->_print_colmin,\r
+                    $worksheet->_print_colmax\r
+                    );\r
+            }\r
+        }\r
+    \r
+        // Create the print title NAME records\r
+        foreach ($this->worksheets as $worksheet)\r
+        {\r
+            $rowmin = $worksheet->_title_rowmin;\r
+            $rowmax = $worksheet->_title_rowmax;\r
+            $colmin = $worksheet->_title_colmin;\r
+            $colmax = $worksheet->_title_colmax;\r
+    \r
+            // Determine if row + col, row, col or nothing has been defined\r
+            // and write the appropriate record\r
+            //\r
+            if (isset($rowmin) && isset($colmin))\r
+            {\r
+                // Row and column titles have been defined.\r
+                // Row title has been defined.\r
+                $this->store_name_long(\r
+                    $worksheet->index,\r
+                    0x07, // NAME type\r
+                    $rowmin,\r
+                    $rowmax,\r
+                    $colmin,\r
+                    $colmax\r
+                    );\r
+            }\r
+            elseif (isset($rowmin))\r
+            {\r
+                // Row title has been defined.\r
+                $this->store_name_short(\r
+                    $worksheet->index,\r
+                    0x07, // NAME type\r
+                    $rowmin,\r
+                    $rowmax,\r
+                    0x00,\r
+                    0xff\r
+                    );\r
+            }\r
+            elseif (isset($colmin))\r
+            {\r
+                // Column title has been defined.\r
+                $this->store_name_short(\r
+                    $worksheet->index,\r
+                    0x07, // NAME type\r
+                    0x0000,\r
+                    0x3fff,\r
+                    $colmin,\r
+                    $colmax\r
+                    );\r
+            }\r
+            else {\r
+                // Print title hasn't been defined.\r
+            }\r
+        }\r
+    }\r
+    \r
+\r
+    \r
+    \r
+    /******************************************************************************\r
+    *\r
+    * BIFF RECORDS\r
+    *\r
+    */\r
+    \r
+    /**\r
+    * Write Excel BIFF WINDOW1 record.\r
+    */\r
+    function _store_window1()\r
+    {\r
+        $record    = 0x003D;                 // Record identifier\r
+        $length    = 0x0012;                 // Number of bytes to follow\r
+    \r
+        $xWn       = 0x0000;                 // Horizontal position of window\r
+        $yWn       = 0x0000;                 // Vertical position of window\r
+        $dxWn      = 0x25BC;                 // Width of window\r
+        $dyWn      = 0x1572;                 // Height of window\r
+    \r
+        $grbit     = 0x0038;                 // Option flags\r
+        $ctabsel   = $this->selected;        // Number of workbook tabs selected\r
+        $wTabRatio = 0x0258;                 // Tab to scrollbar ratio\r
+    \r
+        $itabFirst = $this->firstsheet;   // 1st displayed worksheet\r
+        $itabCur   = $this->activesheet;  // Active worksheet\r
+    \r
+        $header    = pack("vv",        $record, $length);\r
+        $data      = pack("vvvvvvvvv", $xWn, $yWn, $dxWn, $dyWn,\r
+                                       $grbit,\r
+                                       $itabCur, $itabFirst,\r
+                                       $ctabsel, $wTabRatio);\r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Writes Excel BIFF BOUNDSHEET record.\r
+    *\r
+    * @param string  $sheetname Worksheet name\r
+    * @param integer $offset    Location of worksheet BOF\r
+    */\r
+    function _store_boundsheet($sheetname,$offset)\r
+    {\r
+        $record    = 0x0085;                    // Record identifier\r
+        $length    = 0x07 + strlen($sheetname); // Number of bytes to follow\r
+    \r
+        $grbit     = 0x0000;                    // Sheet identifier\r
+        $cch       = strlen($sheetname);        // Length of sheet name\r
+    \r
+        $header    = pack("vv",  $record, $length);\r
+        $data      = pack("VvC", $offset, $grbit, $cch);\r
+        $this->_append($header.$data.$sheetname);\r
+    }\r
+    \r
+    /**\r
+    * Write Excel BIFF STYLE records.\r
+    */\r
+    function _store_style()\r
+    {\r
+        $record    = 0x0293;   // Record identifier\r
+        $length    = 0x0004;   // Bytes to follow\r
+                               \r
+        $ixfe      = 0x8000;   // Index to style XF\r
+        $BuiltIn   = 0x00;     // Built-in style\r
+        $iLevel    = 0xff;     // Outline style level\r
+    \r
+        $header    = pack("vv",  $record, $length);\r
+        $data      = pack("vCC", $ixfe, $BuiltIn, $iLevel);\r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Writes Excel FORMAT record for non "built-in" numerical formats.\r
+    *\r
+    * @param string  $format Custom format string\r
+    * @param integer $ifmt   Format index code\r
+    */\r
+    function _store_num_format($format,$ifmt)\r
+    {\r
+        $record    = 0x041E;                      // Record identifier\r
+        $length    = 0x03 + strlen($format);      // Number of bytes to follow\r
+    \r
+        $cch       = strlen($format);             // Length of format string\r
+    \r
+        $header    = pack("vv", $record, $length);\r
+        $data      = pack("vC", $ifmt, $cch);\r
+        $this->_append($header.$data.$format);\r
+    }\r
+    \r
+    /**\r
+    * Write Excel 1904 record to indicate the date system in use.\r
+    */\r
+    function _store_1904()\r
+    {\r
+        $record    = 0x0022;         // Record identifier\r
+        $length    = 0x0002;         // Bytes to follow\r
+\r
+        $f1904     = $this->_1904;   // Flag for 1904 date system\r
+    \r
+        $header    = pack("vv", $record, $length);\r
+        $data      = pack("v", $f1904);\r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Write BIFF record EXTERNCOUNT to indicate the number of external sheet\r
+    * references in the workbook.\r
+    *\r
+    * Excel only stores references to external sheets that are used in NAME.\r
+    * The workbook NAME record is required to define the print area and the repeat\r
+    * rows and columns.\r
+    *\r
+    * A similar method is used in Worksheet.php for a slightly different purpose.\r
+    *\r
+    * @param integer $cxals Number of external references\r
+    */\r
+    function _store_externcount($cxals)\r
+    {\r
+        $record   = 0x0016;          // Record identifier\r
+        $length   = 0x0002;          // Number of bytes to follow\r
+    \r
+        $header   = pack("vv", $record, $length);\r
+        $data     = pack("v",  $cxals);\r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Writes the Excel BIFF EXTERNSHEET record. These references are used by\r
+    * formulas. NAME record is required to define the print area and the repeat\r
+    * rows and columns.\r
+    *\r
+    * A similar method is used in Worksheet.php for a slightly different purpose.\r
+    *\r
+    * @param string $sheetname Worksheet name\r
+    */\r
+    function _store_externsheet($sheetname)\r
+    {\r
+        $record      = 0x0017;                     // Record identifier\r
+        $length      = 0x02 + strlen($sheetname);  // Number of bytes to follow\r
+                                                   \r
+        $cch         = strlen($sheetname);         // Length of sheet name\r
+        $rgch        = 0x03;                       // Filename encoding\r
+    \r
+        $header      = pack("vv",  $record, $length);\r
+        $data        = pack("CC", $cch, $rgch);\r
+        $this->_append($header.$data.$sheetname);\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Store the NAME record in the short format that is used for storing the print\r
+    * area, repeat rows only and repeat columns only.\r
+    *\r
+    * @param integer $index  Sheet index\r
+    * @param integer $type   Built-in name type\r
+    * @param integer $rowmin Start row\r
+    * @param integer $rowmax End row\r
+    * @param integer $colmin Start colum\r
+    * @param integer $colmax End column\r
+    */\r
+    function store_name_short($index,$type,$rowmin,$rowmax,$colmin,$colmax)\r
+    {\r
+        $record          = 0x0018;       // Record identifier\r
+        $length          = 0x0024;       // Number of bytes to follow\r
+    \r
+        $grbit           = 0x0020;       // Option flags\r
+        $chKey           = 0x00;         // Keyboard shortcut\r
+        $cch             = 0x01;         // Length of text name\r
+        $cce             = 0x0015;       // Length of text definition\r
+        $ixals           = $index + 1;   // Sheet index\r
+        $itab            = $ixals;       // Equal to ixals\r
+        $cchCustMenu     = 0x00;         // Length of cust menu text\r
+        $cchDescription  = 0x00;         // Length of description text\r
+        $cchHelptopic    = 0x00;         // Length of help topic text\r
+        $cchStatustext   = 0x00;         // Length of status bar text\r
+        $rgch            = $type;        // Built-in name type\r
+    \r
+        $unknown03       = 0x3b;\r
+        $unknown04       = 0xffff-$index;\r
+        $unknown05       = 0x0000;\r
+        $unknown06       = 0x0000;\r
+        $unknown07       = 0x1087;\r
+        $unknown08       = 0x8005;\r
+    \r
+        $header             = pack("vv", $record, $length);\r
+        $data               = pack("v", $grbit);\r
+        $data              .= pack("C", $chKey);\r
+        $data              .= pack("C", $cch);\r
+        $data              .= pack("v", $cce);\r
+        $data              .= pack("v", $ixals);\r
+        $data              .= pack("v", $itab);\r
+        $data              .= pack("C", $cchCustMenu);\r
+        $data              .= pack("C", $cchDescription);\r
+        $data              .= pack("C", $cchHelptopic);\r
+        $data              .= pack("C", $cchStatustext);\r
+        $data              .= pack("C", $rgch);\r
+        $data              .= pack("C", $unknown03);\r
+        $data              .= pack("v", $unknown04);\r
+        $data              .= pack("v", $unknown05);\r
+        $data              .= pack("v", $unknown06);\r
+        $data              .= pack("v", $unknown07);\r
+        $data              .= pack("v", $unknown08);\r
+        $data              .= pack("v", $index);\r
+        $data              .= pack("v", $index);\r
+        $data              .= pack("v", $rowmin);\r
+        $data              .= pack("v", $rowmax);\r
+        $data              .= pack("C", $colmin);\r
+        $data              .= pack("C", $colmax);\r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Store the NAME record in the long format that is used for storing the repeat\r
+    * rows and columns when both are specified. This share a lot of code with\r
+    * _store_name_short() but we use a separate method to keep the code clean.\r
+    * Code abstraction for reuse can be carried too far, and I should know. ;-)\r
+    *\r
+    * @param integer $index Sheet index\r
+    * @param integer $type  Built-in name type\r
+    * @param integer $rowmin Start row\r
+    * @param integer $rowmax End row\r
+    * @param integer $colmin Start colum\r
+    * @param integer $colmax End column\r
+    */\r
+    function store_name_long($index,$type,$rowmin,$rowmax,$colmin,$colmax)\r
+    {\r
+        $record          = 0x0018;       // Record identifier\r
+        $length          = 0x003d;       // Number of bytes to follow\r
+        $grbit           = 0x0020;       // Option flags\r
+        $chKey           = 0x00;         // Keyboard shortcut\r
+        $cch             = 0x01;         // Length of text name\r
+        $cce             = 0x002e;       // Length of text definition\r
+        $ixals           = $index + 1;   // Sheet index\r
+        $itab            = $ixals;       // Equal to ixals\r
+        $cchCustMenu     = 0x00;         // Length of cust menu text\r
+        $cchDescription  = 0x00;         // Length of description text\r
+        $cchHelptopic    = 0x00;         // Length of help topic text\r
+        $cchStatustext   = 0x00;         // Length of status bar text\r
+        $rgch            = $type;        // Built-in name type\r
+    \r
+        $unknown01       = 0x29;\r
+        $unknown02       = 0x002b;\r
+        $unknown03       = 0x3b;\r
+        $unknown04       = 0xffff-$index;\r
+        $unknown05       = 0x0000;\r
+        $unknown06       = 0x0000;\r
+        $unknown07       = 0x1087;\r
+        $unknown08       = 0x8008;\r
+    \r
+        $header             = pack("vv",  $record, $length);\r
+        $data               = pack("v", $grbit);\r
+        $data              .= pack("C", $chKey);\r
+        $data              .= pack("C", $cch);\r
+        $data              .= pack("v", $cce);\r
+        $data              .= pack("v", $ixals);\r
+        $data              .= pack("v", $itab);\r
+        $data              .= pack("C", $cchCustMenu);\r
+        $data              .= pack("C", $cchDescription);\r
+        $data              .= pack("C", $cchHelptopic);\r
+        $data              .= pack("C", $cchStatustext);\r
+        $data              .= pack("C", $rgch);\r
+        $data              .= pack("C", $unknown01);\r
+        $data              .= pack("v", $unknown02);\r
+        // Column definition\r
+        $data              .= pack("C", $unknown03);\r
+        $data              .= pack("v", $unknown04);\r
+        $data              .= pack("v", $unknown05);\r
+        $data              .= pack("v", $unknown06);\r
+        $data              .= pack("v", $unknown07);\r
+        $data              .= pack("v", $unknown08);\r
+        $data              .= pack("v", $index);\r
+        $data              .= pack("v", $index);\r
+        $data              .= pack("v", 0x0000);\r
+        $data              .= pack("v", 0x3fff);\r
+        $data              .= pack("C", $colmin);\r
+        $data              .= pack("C", $colmax);\r
+        // Row definition\r
+        $data              .= pack("C", $unknown03);\r
+        $data              .= pack("v", $unknown04);\r
+        $data              .= pack("v", $unknown05);\r
+        $data              .= pack("v", $unknown06);\r
+        $data              .= pack("v", $unknown07);\r
+        $data              .= pack("v", $unknown08);\r
+        $data              .= pack("v", $index);\r
+        $data              .= pack("v", $index);\r
+        $data              .= pack("v", $rowmin);\r
+        $data              .= pack("v", $rowmax);\r
+        $data              .= pack("C", 0x00);\r
+        $data              .= pack("C", 0xff);\r
+        // End of data\r
+        $data              .= pack("C", 0x10);\r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Stores the PALETTE biff record.\r
+    */\r
+    function _store_palette()\r
+    {\r
+        $aref            = $this->palette;\r
+    \r
+        $record          = 0x0092;                 // Record identifier\r
+        $length          = 2 + 4 * count($aref);   // Number of bytes to follow\r
+        $ccv             =         count($aref);   // Number of RGB values to follow\r
+        $data = '';                                // The RGB data\r
+    \r
+        // Pack the RGB data\r
+        foreach($aref as $color)\r
+        {\r
+            foreach($color as $byte) {\r
+                $data .= pack("C",$byte);\r
+            }\r
+        }\r
+    \r
+        $header = pack("vvv",  $record, $length, $ccv);\r
+        $this->_append($header.$data);\r
+    }\r
+}\r
+?>
\ No newline at end of file
diff --git a/mod/attendance/write_excel/Worksheet.php b/mod/attendance/write_excel/Worksheet.php
new file mode 100644 (file)
index 0000000..f512fb8
--- /dev/null
@@ -0,0 +1,2835 @@
+<?php\r
+/*\r
+*  Module written/ported by Xavier Noguer <xnoguer@rezebra.com>\r
+*\r
+*  The majority of this is _NOT_ my code.  I simply ported it from the\r
+*  PERL Spreadsheet::WriteExcel module.\r
+*\r
+*  The author of the Spreadsheet::WriteExcel module is John McNamara \r
+*  <jmcnamara@cpan.org>\r
+*\r
+*  I _DO_ maintain this code, and John McNamara has nothing to do with the\r
+*  porting of this code to PHP.  Any questions directly related to this\r
+*  class library should be directed to me.\r
+*\r
+*  License Information:\r
+*\r
+*    Spreadsheet::WriteExcel:  A library for generating Excel Spreadsheets\r
+*    Copyright (C) 2002 Xavier Noguer xnoguer@rezebra.com\r
+*\r
+*    This library is free software; you can redistribute it and/or\r
+*    modify it under the terms of the GNU Lesser General Public\r
+*    License as published by the Free Software Foundation; either\r
+*    version 2.1 of the License, or (at your option) any later version.\r
+*\r
+*    This library is distributed in the hope that it will be useful,\r
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+*    Lesser General Public License for more details.\r
+*\r
+*    You should have received a copy of the GNU Lesser General Public\r
+*    License along with this library; if not, write to the Free Software\r
+*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
+*/\r
+\r
+require_once('Parser.php');\r
+require_once('BIFFwriter.php');\r
+\r
+/**\r
+* Class for generating Excel Spreadsheets\r
+*\r
+* @author Xavier Noguer <xnoguer@rezebra.com>\r
+* @package Spreadsheet_WriteExcel\r
+*/\r
+\r
+class Worksheet extends BIFFwriter\r
+{\r
+\r
+    /**\r
+    * Constructor\r
+    *\r
+    * @param string  $name         The name of the new worksheet\r
+    * @param integer $index        The index of the new worksheet\r
+    * @param mixed   &$activesheet The current activesheet of the workbook we belong to\r
+    * @param mixed   &$firstsheet  The first worksheet in the workbook we belong to \r
+    * @param mixed   &$url_format  The default format for hyperlinks\r
+    * @param mixed   &$parser      The formula parser created for the Workbook\r
+    */\r
+    function Worksheet($name,$index,&$activesheet,&$firstsheet,&$url_format,&$parser)\r
+    {\r
+        $this->BIFFwriter();     // It needs to call its parent's constructor explicitly\r
+        $rowmax                = 65536; // 16384 in Excel 5\r
+        $colmax                = 256;\r
+        $strmax                = 255;\r
+    \r
+        $this->name            = $name;\r
+        $this->index           = $index;\r
+        $this->activesheet     = &$activesheet;\r
+        $this->firstsheet      = &$firstsheet;\r
+        $this->_url_format     = $url_format;\r
+        $this->_parser         = &$parser;\r
+    \r
+        $this->ext_sheets      = array();\r
+        $this->_using_tmpfile  = 1;\r
+        $this->_filehandle     = "";\r
+        $this->fileclosed      = 0;\r
+        $this->offset          = 0;\r
+        $this->xls_rowmax      = $rowmax;\r
+        $this->xls_colmax      = $colmax;\r
+        $this->xls_strmax      = $strmax;\r
+        $this->dim_rowmin      = $rowmax +1;\r
+        $this->dim_rowmax      = 0;\r
+        $this->dim_colmin      = $colmax +1;\r
+        $this->dim_colmax      = 0;\r
+        $this->colinfo         = array();\r
+        $this->_selection      = array(0,0,0,0);\r
+        $this->_panes          = array();\r
+        $this->_active_pane    = 3;\r
+        $this->_frozen         = 0;\r
+        $this->selected        = 0;\r
+    \r
+        $this->_paper_size      = 0x0;\r
+        $this->_orientation     = 0x1;\r
+        $this->_header          = '';\r
+        $this->_footer          = '';\r
+        $this->_hcenter         = 0;\r
+        $this->_vcenter         = 0;\r
+        $this->_margin_head     = 0.50;\r
+        $this->_margin_foot     = 0.50;\r
+        $this->_margin_left     = 0.75;\r
+        $this->_margin_right    = 0.75;\r
+        $this->_margin_top      = 1.00;\r
+        $this->_margin_bottom   = 1.00;\r
+    \r
+        $this->_title_rowmin    = NULL;\r
+        $this->_title_rowmax    = NULL;\r
+        $this->_title_colmin    = NULL;\r
+        $this->_title_colmax    = NULL;\r
+        $this->_print_rowmin    = NULL;\r
+        $this->_print_rowmax    = NULL;\r
+        $this->_print_colmin    = NULL;\r
+        $this->_print_colmax    = NULL;\r
+    \r
+        $this->_print_gridlines = 1;\r
+        $this->_print_headers   = 0;\r
+    \r
+        $this->_fit_page        = 0;\r
+        $this->_fit_width       = 0;\r
+        $this->_fit_height      = 0;\r
+    \r
+        $this->_hbreaks         = array();\r
+        $this->_vbreaks         = array();\r
+    \r
+        $this->_protect         = 0;\r
+        $this->_password        = NULL;\r
+    \r
+        $this->col_sizes        = array();\r
+        $this->row_sizes        = array();\r
+    \r
+        $this->_zoom            = 100;\r
+        $this->_print_scale     = 100;\r
+    \r
+        $this->_initialize();\r
+    }\r
+    \r
+    /**\r
+    * Open a tmp file to store the majority of the Worksheet data. If this fails,\r
+    * for example due to write permissions, store the data in memory. This can be\r
+    * slow for large files.\r
+    */\r
+    function _initialize()\r
+    {\r
+        // Open tmp file for storing Worksheet data\r
+        $fh = tmpfile();\r
+        if ( $fh) {\r
+            // Store filehandle\r
+            $this->_filehandle = $fh;\r
+        }\r
+        else {\r
+            // If tmpfile() fails store data in memory\r
+            $this->_using_tmpfile = 0;\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Add data to the beginning of the workbook (note the reverse order)\r
+    * and to the end of the workbook.\r
+    *\r
+    * @access public \r
+    * @see Workbook::store_workbook()\r
+    * @param array $sheetnames The array of sheetnames from the Workbook this \r
+    *                          worksheet belongs to\r
+    */\r
+    function close($sheetnames)\r
+    {\r
+        $num_sheets = count($sheetnames);\r
+\r
+        /***********************************************\r
+        * Prepend in reverse order!!\r
+        */\r
+    \r
+        // Prepend the sheet dimensions\r
+        $this->_store_dimensions();\r
+    \r
+        // Prepend the sheet password\r
+        $this->_store_password();\r
+    \r
+        // Prepend the sheet protection\r
+        $this->_store_protect();\r
+    \r
+        // Prepend the page setup\r
+        $this->_store_setup();\r
+    \r
+        // Prepend the bottom margin\r
+        $this->_store_margin_bottom();\r
+    \r
+        // Prepend the top margin\r
+        $this->_store_margin_top();\r
+    \r
+        // Prepend the right margin\r
+        $this->_store_margin_right();\r
+    \r
+        // Prepend the left margin\r
+        $this->_store_margin_left();\r
+    \r
+        // Prepend the page vertical centering\r
+        $this->store_vcenter();\r
+    \r
+        // Prepend the page horizontal centering\r
+        $this->store_hcenter();\r
+    \r
+        // Prepend the page footer\r
+        $this->store_footer();\r
+    \r
+        // Prepend the page header\r
+        $this->store_header();\r
+    \r
+        // Prepend the vertical page breaks\r
+        $this->_store_vbreak();\r
+    \r
+        // Prepend the horizontal page breaks\r
+        $this->_store_hbreak();\r
+    \r
+        // Prepend WSBOOL\r
+        $this->_store_wsbool();\r
+    \r
+        // Prepend GRIDSET\r
+        $this->_store_gridset();\r
+    \r
+        // Prepend PRINTGRIDLINES\r
+        $this->_store_print_gridlines();\r
+    \r
+        // Prepend PRINTHEADERS\r
+        $this->_store_print_headers();\r
+    \r
+        // Prepend EXTERNSHEET references\r
+        for ($i = $num_sheets; $i > 0; $i--) {\r
+            $sheetname = $sheetnames[$i-1];\r
+            $this->_store_externsheet($sheetname);\r
+        }\r
+    \r
+        // Prepend the EXTERNCOUNT of external references.\r
+        $this->_store_externcount($num_sheets);\r
+    \r
+        // Prepend the COLINFO records if they exist\r
+        if (!empty($this->colinfo)){\r
+            for($i=0; $i < count($this->colinfo); $i++)\r
+            {\r
+                $this->_store_colinfo($this->colinfo[$i]);\r
+            }\r
+            $this->_store_defcol();\r
+        }\r
+    \r
+        // Prepend the BOF record\r
+        $this->_store_bof(0x0010);\r
+    \r
+        /*\r
+        * End of prepend. Read upwards from here.\r
+        ***********************************************/\r
+    \r
+        // Append\r
+        $this->_store_window2();\r
+        $this->_store_zoom();\r
+        if(!empty($this->_panes))\r
+          $this->_store_panes($this->_panes);\r
+        $this->_store_selection($this->_selection);\r
+        $this->_store_eof();\r
+    }\r
+    \r
+    /**\r
+    * Retrieve the worksheet name. This is usefull when creating worksheets\r
+    * without a name.\r
+    *\r
+    * @access public\r
+    * @return string The worksheet's name\r
+    */\r
+    function get_name()\r
+    {\r
+        return($this->name);\r
+    }\r
+    \r
+    /**\r
+    * Retrieves data from memory in one chunk, or from disk in $buffer\r
+    * sized chunks.\r
+    *\r
+    * @return string The data\r
+    */\r
+    function get_data()\r
+    {\r
+        $buffer = 4096;\r
+    \r
+        // Return data stored in memory\r
+        if (isset($this->_data)) {\r
+            $tmp   = $this->_data;\r
+            unset($this->_data);\r
+            $fh    = $this->_filehandle;\r
+            if ($this->_using_tmpfile) {\r
+                fseek($fh, 0);\r
+            }\r
+            return($tmp);\r
+        }\r
+        // Return data stored on disk\r
+        if ($this->_using_tmpfile) {\r
+            if ($tmp = fread($this->_filehandle, $buffer)) {\r
+                return($tmp);\r
+            }\r
+        }\r
+    \r
+        // No data to return\r
+        return('');\r
+    }\r
+    \r
+    /**\r
+    * Set this worksheet as a selected worksheet, i.e. the worksheet has its tab\r
+    * highlighted.\r
+    *\r
+    * @access public\r
+    */\r
+    function select()\r
+    {\r
+        $this->selected = 1;\r
+    }\r
+    \r
+    /**\r
+    * Set this worksheet as the active worksheet, i.e. the worksheet that is\r
+    * displayed when the workbook is opened. Also set it as selected.\r
+    *\r
+    * @access public\r
+    */\r
+    function activate()\r
+    {\r
+        $this->selected = 1;\r
+        $this->activesheet =& $this->index;\r
+    }\r
+    \r
+    /**\r
+    * Set this worksheet as the first visible sheet. This is necessary\r
+    * when there are a large number of worksheets and the activated\r
+    * worksheet is not visible on the screen.\r
+    *\r
+    * @access public\r
+    */\r
+    function set_first_sheet()\r
+    {\r
+        $this->firstsheet = $this->index;\r
+    }\r
+    \r
+    /**\r
+    * Set the worksheet protection flag to prevent accidental modification and to\r
+    * hide formulas if the locked and hidden format properties have been set.\r
+    *\r
+    * @access public\r
+    * @param string $password The password to use for protecting the sheet.\r
+    */\r
+    function protect($password)\r
+    {\r
+        $this->_protect   = 1;\r
+        $this->_password  = $this->_encode_password($password);\r
+    }\r
+    \r
+    /**\r
+    * Set the width of a single column or a range of columns.\r
+    *\r
+    * @access public\r
+    * @see _store_colinfo()\r
+    * @param integer $firstcol first column on the range\r
+    * @param integer $lastcol  last column on the range\r
+    * @param integer $width    width to set\r
+    * @param mixed   $format   The optional XF format to apply to the columns\r
+    * @param integer $hidden   The optional hidden atribute\r
+    */\r
+    function set_column($firstcol, $lastcol, $width, $format = 0, $hidden = 0)\r
+    {\r
+        $this->colinfo[] = array($firstcol, $lastcol, $width, $format, $hidden);\r
+\r
+        // Set width to zero if column is hidden\r
+        $width = ($hidden) ? 0 : $width;\r
+    \r
+        for($col = $firstcol; $col <= $lastcol; $col++) {\r
+            $this->col_sizes[$col] = $width;\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Set which cell or cells are selected in a worksheet\r
+    *\r
+    * @access public\r
+    * @param integer $first_row    first row in the selected quadrant\r
+    * @param integer $first_column first column in the selected quadrant\r
+    * @param integer $last_row     last row in the selected quadrant\r
+    * @param integer $last_column  last column in the selected quadrant\r
+    * @see _store_selection()\r
+    */\r
+    function set_selection($first_row,$first_column,$last_row,$last_column)\r
+    {\r
+        $this->_selection = array($first_row,$first_column,$last_row,$last_column);\r
+    }\r
+    \r
+    /**\r
+    * Set panes and mark them as frozen.\r
+    *\r
+    * @access public\r
+    * @param array $panes This is the only parameter received and is composed of the following:\r
+    *                     0 => Vertical split position,\r
+    *                     1 => Horizontal split position\r
+    *                     2 => Top row visible\r
+    *                     3 => Leftmost column visible\r
+    *                     4 => Active pane\r
+    */\r
+    function freeze_panes($panes)\r
+    {\r
+        $this->_frozen = 1;\r
+        $this->_panes  = $panes;\r
+    }\r
+    \r
+    /**\r
+    * Set panes and mark them as unfrozen.\r
+    *\r
+    * @access public\r
+    * @param array $panes This is the only parameter received and is composed of the following:\r
+    *                     0 => Vertical split position,\r
+    *                     1 => Horizontal split position\r
+    *                     2 => Top row visible\r
+    *                     3 => Leftmost column visible\r
+    *                     4 => Active pane\r
+    */\r
+    function thaw_panes($panes)\r
+    {\r
+        $this->_frozen = 0;\r
+        $this->_panes  = $panes;\r
+    }\r
+    \r
+    /**\r
+    * Set the page orientation as portrait.\r
+    *\r
+    * @access public\r
+    */\r
+    function set_portrait()\r
+    {\r
+        $this->_orientation = 1;\r
+    }\r
+    \r
+    /**\r
+    * Set the page orientation as landscape.\r
+    *\r
+    * @access public\r
+    */\r
+    function set_landscape()\r
+    {\r
+        $this->_orientation = 0;\r
+    }\r
+    \r
+    /**\r
+    * Set the paper type. Ex. 1 = US Letter, 9 = A4\r
+    *\r
+    * @access public\r
+    * @param integer $size The type of paper size to use\r
+    */\r
+    function set_paper($size = 0)\r
+    {\r
+        $this->_paper_size = $size;\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Set the page header caption and optional margin.\r
+    *\r
+    * @access public\r
+    * @param string $string The header text\r
+    * @param float  $margin optional head margin in inches.\r
+    */\r
+    function set_header($string,$margin = 0.50)\r
+    {\r
+        if (strlen($string) >= 255) {\r
+            //carp 'Header string must be less than 255 characters';\r
+            return;\r
+        }\r
+        $this->_header      = $string;\r
+        $this->_margin_head = $margin;\r
+    }\r
+    \r
+    /**\r
+    * Set the page footer caption and optional margin.\r
+    *\r
+    * @access public\r
+    * @param string $string The footer text\r
+    * @param float  $margin optional foot margin in inches.\r
+    */\r
+    function set_footer($string,$margin = 0.50)\r
+    {\r
+        if (strlen($string) >= 255) {\r
+            //carp 'Footer string must be less than 255 characters';\r
+            return;\r
+        }\r
+        $this->_footer      = $string;\r
+        $this->_margin_foot = $margin;\r
+    }\r
+    \r
+    /**\r
+    * Center the page horinzontally.\r
+    *\r
+    * @access public\r
+    * @param integer $center the optional value for centering. Defaults to 1 (center).\r
+    */\r
+    function center_horizontally($center = 1)\r
+    {\r
+        $this->_hcenter = $center;\r
+    }\r
+    \r
+    /**\r
+    * Center the page horinzontally.\r
+    *\r
+    * @access public\r
+    * @param integer $center the optional value for centering. Defaults to 1 (center).\r
+    */\r
+    function center_vertically($center = 1)\r
+    {\r
+        $this->_vcenter = $center;\r
+    }\r
+    \r
+    /**\r
+    * Set all the page margins to the same value in inches.\r
+    *\r
+    * @access public\r
+    * @param float $margin The margin to set in inches\r
+    */\r
+    function set_margins($margin)\r
+    {\r
+        $this->set_margin_left($margin);\r
+        $this->set_margin_right($margin);\r
+        $this->set_margin_top($margin);\r
+        $this->set_margin_bottom($margin);\r
+    }\r
+    \r
+    /**\r
+    * Set the left and right margins to the same value in inches.\r
+    *\r
+    * @access public\r
+    * @param float $margin The margin to set in inches\r
+    */\r
+    function set_margins_LR($margin)\r
+    {\r
+        $this->set_margin_left($margin);\r
+        $this->set_margin_right($margin);\r
+    }\r
+    \r
+    /**\r
+    * Set the top and bottom margins to the same value in inches.\r
+    *\r
+    * @access public\r
+    * @param float $margin The margin to set in inches\r
+    */\r
+    function set_margins_TB($margin)\r
+    {\r
+        $this->set_margin_top($margin);\r
+        $this->set_margin_bottom($margin);\r
+    }\r
+    \r
+    /**\r
+    * Set the left margin in inches.\r
+    *\r
+    * @access public\r
+    * @param float $margin The margin to set in inches\r
+    */\r
+    function set_margin_left($margin = 0.75)\r
+    {\r
+        $this->_margin_left = $margin;\r
+    }\r
+    \r
+    /**\r
+    * Set the right margin in inches.\r
+    *\r
+    * @access public\r
+    * @param float $margin The margin to set in inches\r
+    */\r
+    function set_margin_right($margin = 0.75)\r
+    {\r
+        $this->_margin_right = $margin;\r
+    }\r
+    \r
+    /**\r
+    * Set the top margin in inches.\r
+    *\r
+    * @access public\r
+    * @param float $margin The margin to set in inches\r
+    */\r
+    function set_margin_top($margin = 1.00)\r
+    {\r
+        $this->_margin_top = $margin;\r
+    }\r
+    \r
+    /**\r
+    * Set the bottom margin in inches.\r
+    *\r
+    * @access public\r
+    * @param float $margin The margin to set in inches\r
+    */\r
+    function set_margin_bottom($margin = 1.00)\r
+    {\r
+        $this->_margin_bottom = $margin;\r
+    }\r
+    \r
+    /**\r
+    * Set the rows to repeat at the top of each printed page. See also the\r
+    * _store_name_xxxx() methods in Workbook.php\r
+    *\r
+    * @access public\r
+    * @param integer $first_row First row to repeat\r
+    * @param integer $last_row  Last row to repeat. Optional.\r
+    */\r
+    function repeat_rows($first_row, $last_row = NULL)\r
+    {\r
+        $this->_title_rowmin  = $first_row;\r
+        if(isset($last_row)) { //Second row is optional\r
+            $this->_title_rowmax  = $last_row;\r
+        }\r
+        else {\r
+            $this->_title_rowmax  = $first_row;\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Set the columns to repeat at the left hand side of each printed page.\r
+    * See also the _store_names() methods in Workbook.php\r
+    *\r
+    * @access public\r
+    * @param integer $first_col First column to repeat\r
+    * @param integer $last_col  Last column to repeat. Optional.\r
+    */\r
+    function repeat_columns($first_col, $last_col = NULL)\r
+    {\r
+        $this->_title_colmin  = $first_col;\r
+        if(isset($last_col)) { // Second col is optional\r
+            $this->_title_colmax  = $last_col;\r
+        }\r
+        else {\r
+            $this->_title_colmax  = $first_col;\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Set the area of each worksheet that will be printed.\r
+    *\r
+    * @access public\r
+    * @see Workbook::_store_names()\r
+    * @param integer $first_row First row of the area to print\r
+    * @param integer $first_col First column of the area to print\r
+    * @param integer $last_row  Last row of the area to print\r
+    * @param integer $last_col  Last column of the area to print\r
+    */\r
+    function print_area($first_row, $first_col, $last_row, $last_col)\r
+    {\r
+        $this->_print_rowmin  = $first_row;\r
+        $this->_print_colmin  = $first_col;\r
+        $this->_print_rowmax  = $last_row;\r
+        $this->_print_colmax  = $last_col;\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Set the option to hide gridlines on the printed page. \r
+    *\r
+    * @access public\r
+    * @see _store_print_gridlines(), _store_gridset()\r
+    */\r
+    function hide_gridlines()\r
+    {\r
+        $this->_print_gridlines = 0;\r
+    }\r
+    \r
+    /**\r
+    * Set the option to print the row and column headers on the printed page.\r
+    * See also the _store_print_headers() method below.\r
+    *\r
+    * @access public\r
+    * @see _store_print_headers()\r
+    * @param integer $print Whether to print the headers or not. Defaults to 1 (print).\r
+    */\r
+    function print_row_col_headers($print = 1)\r
+    {\r
+        $this->_print_headers = $print;\r
+    }\r
+    \r
+    /**\r
+    * Store the vertical and horizontal number of pages that will define the\r
+    * maximum area printed. It doesn't seem to work with OpenOffice.\r
+    *\r
+    * @access public\r
+    * @param  integer $width  Maximun width of printed area in pages\r
+    * @param  integer $heigth Maximun heigth of printed area in pages\r
+    * @see set_print_scale()\r
+    */\r
+    function fit_to_pages($width, $height)\r
+    {\r
+        $this->_fit_page      = 1;\r
+        $this->_fit_width     = $width;\r
+        $this->_fit_height    = $height;\r
+    }\r
+    \r
+    /**\r
+    * Store the horizontal page breaks on a worksheet (for printing).\r
+    * The breaks represent the row after which the break is inserted.\r
+    *\r
+    * @access public\r
+    * @param array $breaks Array containing the horizontal page breaks\r
+    */\r
+    function set_h_pagebreaks($breaks)\r
+    {\r
+        foreach($breaks as $break) {\r
+            array_push($this->_hbreaks,$break);\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Store the vertical page breaks on a worksheet (for printing).\r
+    * The breaks represent the column after which the break is inserted.\r
+    *\r
+    * @access public\r
+    * @param array $breaks Array containing the vertical page breaks\r
+    */\r
+    function set_v_pagebreaks($breaks)\r
+    {\r
+        foreach($breaks as $break) {\r
+            array_push($this->_vbreaks,$break);\r
+        }\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Set the worksheet zoom factor.\r
+    *\r
+    * @access public\r
+    * @param integer $scale The zoom factor\r
+    */\r
+    function set_zoom($scale = 100)\r
+    {\r
+        // Confine the scale to Excel's range\r
+        if ($scale < 10 or $scale > 400) {\r
+            //carp "Zoom factor $scale outside range: 10 <= zoom <= 400";\r
+            $scale = 100;\r
+        }\r
+    \r
+        $this->_zoom = floor($scale);\r
+    }\r
+    \r
+    /**\r
+    * Set the scale factor for the printed page. \r
+    * It turns off the "fit to page" option\r
+    *\r
+    * @access public\r
+    * @param integer $scale The optional scale factor. Defaults to 100\r
+    */\r
+    function set_print_scale($scale = 100)\r
+    {\r
+        // Confine the scale to Excel's range\r
+        if ($scale < 10 or $scale > 400)\r
+        {\r
+            // REPLACE THIS FOR A WARNING\r
+            die("Print scale $scale outside range: 10 <= zoom <= 400");\r
+            $scale = 100;\r
+        }\r
+    \r
+        // Turn off "fit to page" option\r
+        $this->_fit_page    = 0;\r
+    \r
+        $this->_print_scale = floor($scale);\r
+    }\r
+    \r
+    /**\r
+    * Map to the appropriate write method acording to the token recieved.\r
+    *\r
+    * @access public\r
+    * @param integer $row    The row of the cell we are writing to\r
+    * @param integer $col    The column of the cell we are writing to\r
+    * @param mixed   $token  What we are writing\r
+    * @param mixed   $format The optional format to apply to the cell\r
+    */\r
+    function write($row, $col, $token, $format = 0)\r
+    {\r
+        // Check for a cell reference in A1 notation and substitute row and column\r
+        /*if ($_[0] =~ /^\D/) {\r
+            @_ = $this->_substitute_cellref(@_);\r
+    }*/\r
+    \r
+        /*\r
+        # Match an array ref.\r
+        if (ref $token eq "ARRAY") {\r
+            return $this->write_row(@_);\r
+    }*/\r
+    \r
+        // Match number\r
+        if (preg_match("/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/",$token)) {\r
+            return $this->write_number($row,$col,$token,$format);\r
+        }\r
+        // Match http or ftp URL\r
+        elseif (preg_match("/^[fh]tt?p:\/\//",$token)) {\r
+            return $this->write_url($row, $col, $token, $format);\r
+        }\r
+        // Match mailto:\r
+        elseif (preg_match("/^mailto:/",$token)) {\r
+            return $this->write_url($row, $col, $token, $format);\r
+        }\r
+        // Match internal or external sheet link\r
+        elseif (preg_match("/^(?:in|ex)ternal:/",$token)) {\r
+            return $this->write_url($row, $col, $token, $format);\r
+        }\r
+        // Match formula\r
+        elseif (preg_match("/^=/",$token)) {\r
+            return $this->write_formula($row, $col, $token, $format);\r
+        }\r
+        // Match formula\r
+        elseif (preg_match("/^@/",$token)) {\r
+            return $this->write_formula($row, $col, $token, $format);\r
+        }\r
+        // Match blank\r
+        elseif ($token == '') {\r
+            return $this->write_blank($row,$col,$format);\r
+        }\r
+        // Default: match string\r
+        else {\r
+            return $this->write_string($row,$col,$token,$format);\r
+        }\r
+    }\r
\r
+    /**\r
+    * Returns an index to the XF record in the workbook\r
+    *\r
+    * @param mixed $format The optional XF format\r
+    * @return integer The XF record index\r
+    */\r
+    function _XF(&$format)\r
+    {\r
+        if($format != 0)\r
+        {\r
+            return($format->get_xf_index());\r
+        }\r
+        else\r
+        {\r
+            return(0x0F);\r
+        }\r
+    }\r
+    \r
+    \r
+    /******************************************************************************\r
+    *******************************************************************************\r
+    *\r
+    * Internal methods\r
+    */\r
+    \r
+    \r
+    /**\r
+    * Store Worksheet data in memory using the parent's class append() or to a\r
+    * temporary file, the default.\r
+    *\r
+    * @param string $data The binary data to append\r
+    */\r
+    function _append($data)\r
+    {\r
+        if ($this->_using_tmpfile)\r
+        {\r
+            // Add CONTINUE records if necessary\r
+            if (strlen($data) > $this->_limit) {\r
+                $data = $this->_add_continue($data);\r
+            }\r
+            fwrite($this->_filehandle,$data);\r
+            $this->_datasize += strlen($data);\r
+        }\r
+        else {\r
+            parent::_append($data);\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Substitute an Excel cell reference in A1 notation for  zero based row and\r
+    * column values in an argument list.\r
+    *\r
+    * Ex: ("A4", "Hello") is converted to (3, 0, "Hello").\r
+    *\r
+    * @param string $cell The cell reference. Or range of cells.\r
+    * @return array\r
+    */\r
+    function _substitute_cellref($cell)\r
+    {\r
+        $cell = strtoupper($cell);\r
+    \r
+        // Convert a column range: 'A:A' or 'B:G'\r
+        if (preg_match("/([A-I]?[A-Z]):([A-I]?[A-Z])/",$cell,$match)) {\r
+            list($no_use, $col1) =  $this->_cell_to_rowcol($match[1] .'1'); // Add a dummy row\r
+            list($no_use, $col2) =  $this->_cell_to_rowcol($match[2] .'1'); // Add a dummy row\r
+            return(array($col1, $col2));\r
+        }\r
+    \r
+        // Convert a cell range: 'A1:B7'\r
+        if (preg_match("/\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)/",$cell,$match)) {\r
+            list($row1, $col1) =  $this->_cell_to_rowcol($match[1]);\r
+            list($row2, $col2) =  $this->_cell_to_rowcol($match[2]);\r
+            return(array($row1, $col1, $row2, $col2));\r
+        }\r
+    \r
+        // Convert a cell reference: 'A1' or 'AD2000'\r
+        if (preg_match("/\$?([A-I]?[A-Z]\$?\d+)/",$cell)) {\r
+            list($row1, $col1) =  $this->_cell_to_rowcol($match[1]);\r
+            return(array($row1, $col1));\r
+        }\r
+    \r
+        die("Unknown cell reference $cell ");\r
+    }\r
+    \r
+    /**\r
+    * Convert an Excel cell reference in A1 notation to a zero based row and column\r
+    * reference; converts C1 to (0, 2).\r
+    *\r
+    * @param string $cell The cell reference.\r
+    * @return array containing (row, column)\r
+    */\r
+    function _cell_to_rowcol($cell)\r
+    {\r
+        preg_match("/\$?([A-I]?[A-Z])\$?(\d+)/",$cell,$match);\r
+        $col     = $match[1];\r
+        $row     = $match[2];\r
+    \r
+        // Convert base26 column string to number\r
+        $chars = split('', $col);\r
+        $expn  = 0;\r
+        $col   = 0;\r
+    \r
+        while ($chars) {\r
+            $char = array_pop($chars);        // LS char first\r
+            $col += (ord($char) -ord('A') +1) * pow(26,$expn);\r
+            $expn++;\r
+        }\r
+    \r
+        // Convert 1-index to zero-index\r
+        $row--;\r
+        $col--;\r
+    \r
+        return(array($row, $col));\r
+    }\r
+    \r
+    /**\r
+    * Based on the algorithm provided by Daniel Rentz of OpenOffice.\r
+    *\r
+    * @param string $plaintext The password to be encoded in plaintext.\r
+    * @return string The encoded password\r
+    */\r
+    function _encode_password($plaintext)\r
+    {\r
+        $password = 0x0000;\r
+        $i        = 1;       // char position\r
\r
+        // split the plain text password in its component characters\r
+        $chars = preg_split('//', $plaintext, -1, PREG_SPLIT_NO_EMPTY);\r
+        foreach($chars as $char)\r
+        {\r
+            $value     = ord($char) << $i;   // shifted ASCII value \r
+            $bit_16    = $value & 0x8000;    // the bit 16\r
+            $bit_16  >>= 15;                 // 0x0000 or 0x0001\r
+            //$bit_17    = $value & 0x00010000;\r
+            //$bit_17  >>= 15;\r
+            $value    &= 0x7fff;             // first 15 bits\r
+            $password ^= ($value | $bit_16);\r
+            //$password ^= ($value | $bit_16 | $bit_17);\r
+            $i++;\r
+        }\r
+    \r
+        $password ^= strlen($plaintext);\r
+        $password ^= 0xCE4B;\r
+\r
+        return($password);\r
+    }\r
+    \r
+    /******************************************************************************\r
+    *******************************************************************************\r
+    *\r
+    * BIFF RECORDS\r
+    */\r
+    \r
+    \r
+    /**\r
+    * Write a double to the specified row and column (zero indexed).\r
+    * An integer can be written as a double. Excel will display an\r
+    * integer. $format is optional.\r
+    *\r
+    * Returns  0 : normal termination\r
+    *         -2 : row or column out of range\r
+    *\r
+    * @access public\r
+    * @param integer $row    Zero indexed row\r
+    * @param integer $col    Zero indexed column\r
+    * @param float   $num    The number to write\r
+    * @param mixed   $format The optional XF format\r
+    */\r
+    function write_number($row, $col, $num, $format = 0)\r
+    {\r
+        $record    = 0x0203;                 // Record identifier\r
+        $length    = 0x000E;                 // Number of bytes to follow\r
+        $xf        = $this->_XF($format);    // The cell format\r
+    \r
+        // Check that row and col are valid and store max and min values\r
+        if ($row >= $this->xls_rowmax)\r
+        {\r
+            return(-2);\r
+        }\r
+        if ($col >= $this->xls_colmax)\r
+        {\r
+            return(-2);\r
+        }\r
+        if ($row <  $this->dim_rowmin) \r
+        {\r
+            $this->dim_rowmin = $row;\r
+        }\r
+        if ($row >  $this->dim_rowmax) \r
+        {\r
+            $this->dim_rowmax = $row;\r
+        }\r
+        if ($col <  $this->dim_colmin) \r
+        {\r
+            $this->dim_colmin = $col;\r
+        }\r
+        if ($col >  $this->dim_colmax) \r
+        {\r
+            $this->dim_colmax = $col;\r
+        }\r
+    \r
+        $header    = pack("vv",  $record, $length);\r
+        $data      = pack("vvv", $row, $col, $xf);\r
+        $xl_double = pack("d",   $num);\r
+        if ($this->_byte_order) // if it's Big Endian\r
+        {\r
+            $xl_double = strrev($xl_double);\r
+        }\r
+    \r
+        $this->_append($header.$data.$xl_double);\r
+        return(0);\r
+    }\r
+    \r
+    /**\r
+    * Write a string to the specified row and column (zero indexed).\r
+    * NOTE: there is an Excel 5 defined limit of 255 characters.\r
+    * $format is optional.\r
+    * Returns  0 : normal termination\r
+    *         -1 : insufficient number of arguments\r
+    *         -2 : row or column out of range\r
+    *         -3 : long string truncated to 255 chars\r
+    *\r
+    * @access public\r
+    * @param integer $row    Zero indexed row\r
+    * @param integer $col    Zero indexed column\r
+    * @param string  $str    The string to write\r
+    * @param mixed   $format The XF format for the cell\r
+    */\r
+    function write_string($row, $col, $str, $format = 0)\r
+    {\r
+        $strlen    = strlen($str);\r
+        $record    = 0x0204;                   // Record identifier\r
+        $length    = 0x0008 + $strlen;         // Bytes to follow\r
+        $xf        = $this->_XF($format);      // The cell format\r
+        \r
+        $str_error = 0;\r
+    \r
+        // Check that row and col are valid and store max and min values\r
+        if ($row >= $this->xls_rowmax) \r
+        {\r
+            return(-2);\r
+        }\r
+        if ($col >= $this->xls_colmax) \r
+        {\r
+            return(-2);\r
+        }\r
+        if ($row <  $this->dim_rowmin) \r
+        {\r
+            $this->dim_rowmin = $row;\r
+        }\r
+        if ($row >  $this->dim_rowmax) \r
+        {\r
+            $this->dim_rowmax = $row;\r
+        }\r
+        if ($col <  $this->dim_colmin) \r
+        {\r
+            $this->dim_colmin = $col;\r
+        }\r
+        if ($col >  $this->dim_colmax) \r
+        {\r
+            $this->dim_colmax = $col;\r
+        }\r
+    \r
+        if ($strlen > $this->xls_strmax)  // LABEL must be < 255 chars\r
+        {\r
+            $str       = substr($str, 0, $this->xls_strmax);\r
+            $length    = 0x0008 + $this->xls_strmax;\r
+            $strlen    = $this->xls_strmax;\r
+            $str_error = -3;\r
+        }\r
+    \r
+        $header    = pack("vv",   $record, $length);\r
+        $data      = pack("vvvv", $row, $col, $xf, $strlen);\r
+        $this->_append($header.$data.$str);\r
+        return($str_error);\r
+    }\r
\r
+    /**\r
+    * Writes a note associated with the cell given by the row and column.\r
+    * NOTE records don't have a length limit.\r
+    *\r
+    * @access public\r
+    * @param integer $row    Zero indexed row\r
+    * @param integer $col    Zero indexed column\r
+    * @param string  $note   The note to write\r
+    */\r
+    function write_note($row, $col, $note)\r
+    {\r
+        $note_length    = strlen($note);\r
+        $record         = 0x001C;                // Record identifier\r
+        $max_length     = 2048;                  // Maximun length for a NOTE record\r
+        //$length      = 0x0006 + $note_length;    // Bytes to follow\r
+\r
+        // Check that row and col are valid and store max and min values\r
+        if ($row >= $this->xls_rowmax) \r
+        {\r
+            return(-2);\r
+        }\r
+        if ($col >= $this->xls_colmax) \r
+        {\r
+            return(-2);\r
+        }\r
+        if ($row <  $this->dim_rowmin) \r
+        {\r
+            $this->dim_rowmin = $row;\r
+        }\r
+        if ($row >  $this->dim_rowmax) \r
+        {\r
+            $this->dim_rowmax = $row;\r
+        }\r
+        if ($col <  $this->dim_colmin) \r
+        {\r
+            $this->dim_colmin = $col;\r
+        }\r
+        if ($col >  $this->dim_colmax) \r
+        {\r
+            $this->dim_colmax = $col;\r
+        }\r
\r
+        // Length for this record is no more than 2048 + 6\r
+        $length    = 0x0006 + min($note_length, 2048);\r
+        $header    = pack("vv",   $record, $length);\r
+        $data      = pack("vvv", $row, $col, $note_length);\r
+        $this->_append($header.$data.substr($note, 0, 2048));\r
+\r
+        for($i = $max_length; $i < $note_length; $i += $max_length)\r
+        {\r
+            $chunk  = substr($note, $i, $max_length);\r
+            $length = 0x0006 + strlen($chunk);\r
+            $header = pack("vv",   $record, $length);\r
+            $data   = pack("vvv", -1, 0, strlen($chunk));\r
+            $this->_append($header.$data.$chunk);\r
+        }\r
+        return(0);\r
+    }\r
\r
+    /**\r
+    * Write a blank cell to the specified row and column (zero indexed).\r
+    * A blank cell is used to specify formatting without adding a string\r
+    * or a number.\r
+    *\r
+    * A blank cell without a format serves no purpose. Therefore, we don't write\r
+    * a BLANK record unless a format is specified. This is mainly an optimisation\r
+    * for the write_row() and write_col() methods.\r
+    *\r
+    * Returns  0 : normal termination (including no format)\r
+    *         -1 : insufficient number of arguments\r
+    *         -2 : row or column out of range\r
+    *\r
+    * @access public\r
+    * @param integer $row    Zero indexed row\r
+    * @param integer $col    Zero indexed column\r
+    * @param mixed   $format The XF format\r
+    */\r
+    function write_blank($row, $col, $format = 0)\r
+    {\r
+        // Don't write a blank cell unless it has a format\r
+        if ($format == 0)\r
+        {\r
+            return(0);\r
+        }\r
+    \r
+        $record    = 0x0201;                 // Record identifier\r
+        $length    = 0x0006;                 // Number of bytes to follow\r
+        $xf        = $this->_XF($format);    // The cell format\r
+    \r
+        // Check that row and col are valid and store max and min values\r
+        if ($row >= $this->xls_rowmax) \r
+        {\r
+            return(-2);\r
+        }\r
+        if ($col >= $this->xls_colmax) \r
+        {\r
+            return(-2);\r
+        }\r
+        if ($row <  $this->dim_rowmin) \r
+        {\r
+            $this->dim_rowmin = $row;\r
+        }\r
+        if ($row >  $this->dim_rowmax) \r
+        {\r
+            $this->dim_rowmax = $row;\r
+        }\r
+        if ($col <  $this->dim_colmin) \r
+        {\r
+            $this->dim_colmin = $col;\r
+        }\r
+        if ($col >  $this->dim_colmax) \r
+        {\r
+            $this->dim_colmax = $col;\r
+        }\r
+    \r
+        $header    = pack("vv",  $record, $length);\r
+        $data      = pack("vvv", $row, $col, $xf);\r
+        $this->_append($header.$data);\r
+        return 0;\r
+    }\r
\r
+    /**\r
+    * Write a formula to the specified row and column (zero indexed).\r
+    * The textual representation of the formula is passed to the parser in\r
+    * Parser.php which returns a packed binary string.\r
+    *\r
+    * Returns  0 : normal termination\r
+    *         -2 : row or column out of range\r
+    *\r
+    * @access public\r
+    * @param integer $row     Zero indexed row\r
+    * @param integer $col     Zero indexed column\r
+    * @param string  $formula The formula text string\r
+    * @param mixed   $format  The optional XF format\r
+    */\r
+    function write_formula($row, $col, $formula, $format = 0)\r
+    {\r
+        $record    = 0x0006;     // Record identifier\r
+    \r
+        // Excel normally stores the last calculated value of the formula in $num.\r
+        // Clearly we are not in a position to calculate this a priori. Instead\r
+        // we set $num to zero and set the option flags in $grbit to ensure\r
+        // automatic calculation of the formula when the file is opened.\r
+        //\r
+        $xf        = $this->_XF($format); // The cell format\r
+        $num       = 0x00;                // Current value of formula\r
+        $grbit     = 0x03;                // Option flags\r
+        $chn       = 0x0000;              // Must be zero\r
+    \r
+    \r
+        // Check that row and col are valid and store max and min values\r
+        if ($row >= $this->xls_rowmax)\r
+        {\r
+            return(-2);\r
+        }\r
+        if ($col >= $this->xls_colmax)\r
+        {\r
+            return(-2);\r
+        }\r
+        if ($row <  $this->dim_rowmin) \r
+        {\r
+            $this->dim_rowmin = $row;\r
+        }\r
+        if ($row >  $this->dim_rowmax) \r
+        {\r
+            $this->dim_rowmax = $row;\r
+        }\r
+        if ($col <  $this->dim_colmin) \r
+        {\r
+            $this->dim_colmin = $col;\r
+        }\r
+        if ($col >  $this->dim_colmax) \r
+        {\r
+            $this->dim_colmax = $col;\r
+        }\r
+    \r
+        // Strip the '=' or '@' sign at the beginning of the formula string\r
+        if (ereg("^=",$formula)) {\r
+            $formula = preg_replace("/(^=)/","",$formula);\r
+        }\r
+        elseif(ereg("^@",$formula)) {\r
+            $formula = preg_replace("/(^@)/","",$formula);\r
+        }\r
+        else {\r
+            die("Unrecognised character for formula");\r
+        }\r
+    \r
+        // Parse the formula using the parser in Parser.php\r
+        //$tree      = new Parser($this->_byte_order);\r
+        $this->_parser->parse($formula);\r
+        //$tree->parse($formula);\r
+        $formula = $this->_parser->to_reverse_polish();\r
+    \r
+        $formlen    = strlen($formula);    // Length of the binary string\r
+        $length     = 0x16 + $formlen;     // Length of the record data\r
+    \r
+        $header    = pack("vv",      $record, $length);\r
+        $data      = pack("vvvdvVv", $row, $col, $xf, $num,\r
+                                     $grbit, $chn, $formlen);\r
+    \r
+        $this->_append($header.$data.$formula);\r
+        return 0;\r
+    }\r
+    \r
+    /**\r
+    * Write a hyperlink. This is comprised of two elements: the visible label and\r
+    * the invisible link. The visible label is the same as the link unless an\r
+    * alternative string is specified. The label is written using the\r
+    * write_string() method. Therefore the 255 characters string limit applies.\r
+    * $string and $format are optional and their order is interchangeable.\r
+    *\r
+    * The hyperlink can be to a http, ftp, mail, internal sheet, or external\r
+    * directory url.\r
+    *\r
+    * Returns  0 : normal termination\r
+    *         -1 : insufficient number of arguments\r
+    *         -2 : row or column out of range\r
+    *         -3 : long string truncated to 255 chars\r
+    *\r
+    * @access public\r
+    * @param integer $row    Row\r
+    * @param integer $col    Column\r
+    * @param string  $url    URL string\r
+    * @param string  $string Alternative label\r
+    * @param mixed   $format The cell format\r
+    */\r
+    function write_url($row, $col, $url, $string = '', $format = 0)\r
+    {\r
+        // Add start row and col to arg list\r
+        return($this->_write_url_range($row, $col, $row, $col, $url, $string, $format));\r
+    }\r
+    \r
+    /**\r
+    * This is the more general form of write_url(). It allows a hyperlink to be\r
+    * written to a range of cells. This function also decides the type of hyperlink\r
+    * to be written. These are either, Web (http, ftp, mailto), Internal\r
+    * (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1').\r
+    *\r
+    * See also write_url() above for a general description and return values.\r
+    *\r
+    * @param integer $row1   Start row\r
+    * @param integer $col1   Start column\r
+    * @param integer $row2   End row\r
+    * @param integer $col2   End column\r
+    * @param string  $url    URL string\r
+    * @param string  $string Alternative label\r
+    * @param mixed   $format The cell format\r
+    */\r
+    \r
+    function _write_url_range($row1, $col1, $row2, $col2, $url, $string = '', $format = 0)\r
+    {\r
+        // Check for internal/external sheet links or default to web link\r
+        if (preg_match('[^internal:]', $url)) {\r
+            return($this->_write_url_internal($row1, $col1, $row2, $col2, $url, $string, $format));\r
+        }\r
+        if (preg_match('[^external:]', $url)) {\r
+            return($this->_write_url_external($row1, $col1, $row2, $col2, $url, $string, $format));\r
+        }\r
+        return($this->_write_url_web($row1, $col1, $row2, $col2, $url, $string, $format));\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Used to write http, ftp and mailto hyperlinks.\r
+    * The link type ($options) is 0x03 is the same as absolute dir ref without\r
+    * sheet. However it is differentiated by the $unknown2 data stream.\r
+    *\r
+    * @see write_url()\r
+    * @param integer $row1   Start row\r
+    * @param integer $col1   Start column\r
+    * @param integer $row2   End row\r
+    * @param integer $col2   End column\r
+    * @param string  $url    URL string\r
+    * @param string  $str    Alternative label\r
+    * @param mixed   $format The cell format\r
+    */\r
+    function _write_url_web($row1, $col1, $row2, $col2, $url, $str, $format = 0)\r
+    {\r
+        $record      = 0x01B8;                       // Record identifier\r
+        $length      = 0x00000;                      // Bytes to follow\r
+    \r
+        if($format == 0) {\r
+            $format = $this->_url_format;\r
+        }\r
+    \r
+        // Write the visible label using the write_string() method.\r
+        if($str == '') {\r
+            $str = $url;\r
+        }\r
+        $str_error = $this->write_string($row1, $col1, $str, $format);\r
+        if ($str_error == -2) {\r
+            return($str_error);\r
+        }\r
+    \r
+        // Pack the undocumented parts of the hyperlink stream\r
+        $unknown1    = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");\r
+        $unknown2    = pack("H*", "E0C9EA79F9BACE118C8200AA004BA90B");\r
+    \r
+        // Pack the option flags\r
+        $options     = pack("V", 0x03);\r
+    \r
+        // Convert URL to a null terminated wchar string\r
+        $url         = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));\r
+        $url         = $url . "\0\0\0";\r
+    \r
+        // Pack the length of the URL\r
+        $url_len     = pack("V", strlen($url));\r
+    \r
+        // Calculate the data length\r
+        $length      = 0x34 + strlen($url);\r
+    \r
+        // Pack the header data\r
+        $header      = pack("vv",   $record, $length);\r
+        $data        = pack("vvvv", $row1, $row2, $col1, $col2);\r
+    \r
+        // Write the packed data\r
+        $this->_append( $header. $data.\r
+                        $unknown1. $options.\r
+                        $unknown2. $url_len. $url);\r
+        return($str_error);\r
+    }\r
+    \r
+    /**\r
+    * Used to write internal reference hyperlinks such as "Sheet1!A1".\r
+    *\r
+    * @see write_url()\r
+    * @param integer $row1   Start row\r
+    * @param integer $col1   Start column\r
+    * @param integer $row2   End row\r
+    * @param integer $col2   End column\r
+    * @param string  $url    URL string\r
+    * @param string  $str    Alternative label\r
+    * @param mixed   $format The cell format\r
+    */\r
+    function _write_url_internal($row1, $col1, $row2, $col2, $url, $str, $format = 0)\r
+    {\r
+        $record      = 0x01B8;                       // Record identifier\r
+        $length      = 0x00000;                      // Bytes to follow\r
+    \r
+        if ($format == 0) {\r
+            $format = $this->_url_format;\r
+        }\r
+    \r
+        // Strip URL type\r
+        $url = preg_replace('s[^internal:]', '', $url);\r
+    \r
+        // Write the visible label\r
+        if($str == '') {\r
+            $str = $url;\r
+        }\r
+        $str_error = $this->write_string($row1, $col1, $str, $format);\r
+        if ($str_error == -2) {\r
+            return($str_error);\r
+        }\r
+    \r
+        // Pack the undocumented parts of the hyperlink stream\r
+        $unknown1    = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");\r
+    \r
+        // Pack the option flags\r
+        $options     = pack("V", 0x08);\r
+    \r
+        // Convert the URL type and to a null terminated wchar string\r
+        $url         = join("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));\r
+        $url         = $url . "\0\0\0";\r
+    \r
+        // Pack the length of the URL as chars (not wchars)\r
+        $url_len     = pack("V", floor(strlen($url)/2));\r
+    \r
+        // Calculate the data length\r
+        $length      = 0x24 + strlen($url);\r
+    \r
+        // Pack the header data\r
+        $header      = pack("vv",   $record, $length);\r
+        $data        = pack("vvvv", $row1, $row2, $col1, $col2);\r
+    \r
+        // Write the packed data\r
+        $this->_append($header. $data.\r
+                       $unknown1. $options.\r
+                       $url_len. $url);\r
+        return($str_error);\r
+    }\r
+    \r
+    /**\r
+    * Write links to external directory names such as 'c:\foo.xls',\r
+    * c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.\r
+    *\r
+    * Note: Excel writes some relative links with the $dir_long string. We ignore\r
+    * these cases for the sake of simpler code.\r
+    *\r
+    * @see write_url()\r
+    * @param integer $row1   Start row\r
+    * @param integer $col1   Start column\r
+    * @param integer $row2   End row\r
+    * @param integer $col2   End column\r
+    * @param string  $url    URL string\r
+    * @param string  $str    Alternative label\r
+    * @param mixed   $format The cell format\r
+    */\r
+    function _write_url_external($row1, $col1, $row2, $col2, $url, $str, $format = 0)\r
+    {\r
+        // Network drives are different. We will handle them separately\r
+        // MS/Novell network drives and shares start with \\\r
+        if (preg_match('[^external:\\\\]', $url)) {\r
+            return($this->_write_url_external_net($row1, $col1, $row2, $col2, $url, $str, $format));\r
+        }\r
+    \r
+        $record      = 0x01B8;                       // Record identifier\r
+        $length      = 0x00000;                      // Bytes to follow\r
+    \r
+        if ($format == 0) {\r
+            $format = $this->_url_format;\r
+        }\r
+    \r
+        // Strip URL type and change Unix dir separator to Dos style (if needed)\r
+        //\r
+        $url = preg_replace('[^external:]', '', $url);\r
+        $url = preg_replace('[/]', "\\", $url);\r
+    \r
+        // Write the visible label\r
+        if ($str == '') {\r
+            $str = preg_replace('[\#]', ' - ', $url);\r
+        }\r
+        $str_error = $this->write_string($row1, $col1, $str, $format);\r
+        if ($str_error == -2) {\r
+            return($str_error);\r
+        }\r
+    \r
+        // Determine if the link is relative or absolute:\r
+        //   relative if link contains no dir separator, "somefile.xls"\r
+        //   relative if link starts with up-dir, "..\..\somefile.xls"\r
+        //   otherwise, absolute\r
+        \r
+        $absolute    = 0x02; // Bit mask\r
+        if (!preg_match('[\\]', $url)) {\r
+            $absolute    = 0x00;\r
+        }\r
+        if (preg_match('[^\.\.\\]', $url)) {\r
+            $absolute    = 0x00;\r
+        }\r
+    \r
+        // Determine if the link contains a sheet reference and change some of the\r
+        // parameters accordingly.\r
+        // Split the dir name and sheet name (if it exists)\r
+        list($dir_long , $sheet) = split('/\#/', $url);\r
+        $link_type               = 0x01 | $absolute;\r
+    \r
+        if (isset($sheet)) {\r
+            $link_type |= 0x08;\r
+            $sheet_len  = pack("V", strlen($sheet) + 0x01);\r
+            $sheet      = join("\0", split('', $sheet));\r
+            $sheet     .= "\0\0\0";\r
+        }\r
+        else {\r
+            $sheet_len   = '';\r
+            $sheet       = '';\r
+        }\r
+    \r
+        // Pack the link type\r
+        $link_type   = pack("V", $link_type);\r
+    \r
+        // Calculate the up-level dir count e.g.. (..\..\..\ == 3)\r
+        $up_count    = preg_match_all("/\.\.\\/", $dir_long, $useless);\r
+        $up_count    = pack("v", $up_count);\r
+    \r
+        // Store the short dos dir name (null terminated)\r
+        $dir_short   = preg_replace('/\.\.\\/', '', $dir_long) . "\0";\r
+    \r
+        // Store the long dir name as a wchar string (non-null terminated)\r
+        $dir_long       = join("\0", split('', $dir_long));\r
+        $dir_long       = $dir_long . "\0";\r
+    \r
+        // Pack the lengths of the dir strings\r
+        $dir_short_len = pack("V", strlen($dir_short)      );\r
+        $dir_long_len  = pack("V", strlen($dir_long)       );\r
+        $stream_len    = pack("V", strlen($dir_long) + 0x06);\r
+    \r
+        // Pack the undocumented parts of the hyperlink stream\r
+        $unknown1 = pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000'       );\r
+        $unknown2 = pack("H*",'0303000000000000C000000000000046'               );\r
+        $unknown3 = pack("H*",'FFFFADDE000000000000000000000000000000000000000');\r
+        $unknown4 = pack("v",  0x03                                            );\r
+    \r
+        // Pack the main data stream\r
+        $data        = pack("vvvv", $row1, $row2, $col1, $col2) .\r
+                          $unknown1     .\r
+                          $link_type    .\r
+                          $unknown2     .\r
+                          $up_count     .\r
+                          $dir_short_len.\r
+                          $dir_short    .\r
+                          $unknown3     .\r
+                          $stream_len   .\r
+                          $dir_long_len .\r
+                          $unknown4     .\r
+                          $dir_long     .\r
+                          $sheet_len    .\r
+                          $sheet        ;\r
+    \r
+        // Pack the header data\r
+        $length   = strlen($data);\r
+        $header   = pack("vv", $record, $length);\r
+    \r
+        // Write the packed data\r
+        $this->_append($header. $data);\r
+        return($str_error);\r
+    }\r
+    \r
+    \r
+    /*\r
+    ###############################################################################\r
+    #\r
+    # write_url_xxx($row1, $col1, $row2, $col2, $url, $string, $format)\r
+    #\r
+    # Write links to external MS/Novell network drives and shares such as\r
+    # '//NETWORK/share/foo.xls' and '//NETWORK/share/foo.xls#Sheet1!A1'.\r
+    #\r
+    # See also write_url() above for a general description and return values.\r
+    #\r
+    sub _write_url_external_net {\r
+    \r
+        my $this    = shift;\r
+    \r
+        my $record      = 0x01B8;                       # Record identifier\r
+        my $length      = 0x00000;                      # Bytes to follow\r
+    \r
+        my $row1        = $_[0];                        # Start row\r
+        my $col1        = $_[1];                        # Start column\r
+        my $row2        = $_[2];                        # End row\r
+        my $col2        = $_[3];                        # End column\r
+        my $url         = $_[4];                        # URL string\r
+        my $str         = $_[5];                        # Alternative label\r
+        my $xf          = $_[6] || $this->{_url_format};# The cell format\r
+    \r
+    \r
+        # Strip URL type and change Unix dir separator to Dos style (if needed)\r
+        #\r
+        $url            =~ s[^external:][];\r
+        $url            =~ s[/][\\]g;\r
+    \r
+    \r
+        # Write the visible label\r
+        ($str = $url)   =~ s[\#][ - ] unless defined $str;\r
+        my $str_error   = $this->write_string($row1, $col1, $str, $xf);\r
+        return $str_error if $str_error == -2;\r
+    \r
+    \r
+        # Determine if the link contains a sheet reference and change some of the\r
+        # parameters accordingly.\r
+        # Split the dir name and sheet name (if it exists)\r
+        #\r
+        my ($dir_long , $sheet) = split /\#/, $url;\r
+        my $link_type           = 0x0103; # Always absolute\r
+        my $sheet_len;\r
+    \r
+        if (defined $sheet) {\r
+            $link_type |= 0x08;\r
+            $sheet_len  = pack("V", length($sheet) + 0x01);\r
+            $sheet      = join("\0", split('', $sheet));\r
+            $sheet     .= "\0\0\0";\r
+    }\r
+        else {\r
+            $sheet_len   = '';\r
+            $sheet       = '';\r
+    }\r
+    \r
+        # Pack the link type\r
+        $link_type      = pack("V", $link_type);\r
+    \r
+    \r
+        # Make the string null terminated\r
+        $dir_long       = $dir_long . "\0";\r
+    \r
+    \r
+        # Pack the lengths of the dir string\r
+        my $dir_long_len  = pack("V", length $dir_long);\r
+    \r
+    \r
+        # Store the long dir name as a wchar string (non-null terminated)\r
+        $dir_long       = join("\0", split('', $dir_long));\r
+        $dir_long       = $dir_long . "\0";\r
+    \r
+    \r
+        # Pack the undocumented part of the hyperlink stream\r
+        my $unknown1    = pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000');\r
+    \r
+    \r
+        # Pack the main data stream\r
+        my $data        = pack("vvvv", $row1, $row2, $col1, $col2) .\r
+                          $unknown1     .\r
+                          $link_type    .\r
+                          $dir_long_len .\r
+                          $dir_long     .\r
+                          $sheet_len    .\r
+                          $sheet        ;\r
+    \r
+    \r
+        # Pack the header data\r
+        $length         = length $data;\r
+        my $header      = pack("vv",   $record, $length);\r
+    \r
+    \r
+        # Write the packed data\r
+        $this->_append( $header, $data);\r
+    \r
+        return $str_error;\r
+}*/\r
+    \r
+    /**\r
+    * This method is used to set the height and XF format for a row.\r
+    * Writes the  BIFF record ROW.\r
+    *\r
+    * @access public\r
+    * @param integer $row    The row to set\r
+    * @param integer $height Height we are giving to the row. \r
+    *                        Use NULL to set XF without setting height\r
+    * @param mixed   $format XF format we are giving to the row\r
+    */\r
+    function set_row($row, $height, $format = 0)\r
+    {\r
+        $record      = 0x0208;               // Record identifier\r
+        $length      = 0x0010;               // Number of bytes to follow\r
+    \r
+        $colMic      = 0x0000;               // First defined column\r
+        $colMac      = 0x0000;               // Last defined column\r
+        $irwMac      = 0x0000;               // Used by Excel to optimise loading\r
+        $reserved    = 0x0000;               // Reserved\r
+        $grbit       = 0x01C0;               // Option flags. (monkey) see $1 do\r
+        $ixfe        = $this->_XF($format); // XF index\r
+    \r
+        // Use set_row($row, NULL, $XF) to set XF without setting height\r
+        if ($height != NULL) {\r
+            $miyRw = $height * 20;  // row height\r
+        }\r
+        else {\r
+            $miyRw = 0xff;          // default row height is 256\r
+        }\r
+    \r
+        $header   = pack("vv",       $record, $length);\r
+        $data     = pack("vvvvvvvv", $row, $colMic, $colMac, $miyRw,\r
+                                     $irwMac,$reserved, $grbit, $ixfe);\r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Writes Excel DIMENSIONS to define the area in which there is data.\r
+    */\r
+    function _store_dimensions()\r
+    {\r
+        $record    = 0x0000;               // Record identifier\r
+        $length    = 0x000A;               // Number of bytes to follow\r
+        $row_min   = $this->dim_rowmin;    // First row\r
+        $row_max   = $this->dim_rowmax;    // Last row plus 1\r
+        $col_min   = $this->dim_colmin;    // First column\r
+        $col_max   = $this->dim_colmax;    // Last column plus 1\r
+        $reserved  = 0x0000;               // Reserved by Excel\r
+    \r
+        $header    = pack("vv",    $record, $length);\r
+        $data      = pack("vvvvv", $row_min, $row_max,\r
+                                   $col_min, $col_max, $reserved);\r
+        $this->_prepend($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Write BIFF record Window2.\r
+    */\r
+    function _store_window2()\r
+    {\r
+        $record         = 0x023E;     // Record identifier\r
+        $length         = 0x000A;     // Number of bytes to follow\r
+    \r
+        $grbit          = 0x00B6;     // Option flags\r
+        $rwTop          = 0x0000;     // Top row visible in window\r
+        $colLeft        = 0x0000;     // Leftmost column visible in window\r
+        $rgbHdr         = 0x00000000; // Row/column heading and gridline color\r
+    \r
+        // The options flags that comprise $grbit\r
+        $fDspFmla       = 0;                     // 0 - bit\r
+        $fDspGrid       = 1;                     // 1\r
+        $fDspRwCol      = 1;                     // 2\r
+        $fFrozen        = $this->_frozen;        // 3\r
+        $fDspZeros      = 1;                     // 4\r
+        $fDefaultHdr    = 1;                     // 5\r
+        $fArabic        = 0;                     // 6\r
+        $fDspGuts       = 1;                     // 7\r
+        $fFrozenNoSplit = 0;                     // 0 - bit\r
+        $fSelected      = $this->selected;       // 1\r
+        $fPaged         = 1;                     // 2\r
+    \r
+        $grbit             = $fDspFmla;\r
+        $grbit            |= $fDspGrid       << 1;\r
+        $grbit            |= $fDspRwCol      << 2;\r
+        $grbit            |= $fFrozen        << 3;\r
+        $grbit            |= $fDspZeros      << 4;\r
+        $grbit            |= $fDefaultHdr    << 5;\r
+        $grbit            |= $fArabic        << 6;\r
+        $grbit            |= $fDspGuts       << 7;\r
+        $grbit            |= $fFrozenNoSplit << 8;\r
+        $grbit            |= $fSelected      << 9;\r
+        $grbit            |= $fPaged         << 10;\r
+    \r
+        $header  = pack("vv",   $record, $length);\r
+        $data    = pack("vvvV", $grbit, $rwTop, $colLeft, $rgbHdr);\r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Write BIFF record DEFCOLWIDTH if COLINFO records are in use.\r
+    */\r
+    function _store_defcol()\r
+    {\r
+        $record   = 0x0055;      // Record identifier\r
+        $length   = 0x0002;      // Number of bytes to follow\r
+        $colwidth = 0x0008;      // Default column width\r
+    \r
+        $header   = pack("vv", $record, $length);\r
+        $data     = pack("v",  $colwidth);\r
+        $this->_prepend($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Write BIFF record COLINFO to define column widths\r
+    *\r
+    * Note: The SDK says the record length is 0x0B but Excel writes a 0x0C\r
+    * length record.\r
+    *\r
+    * @param array $col_array This is the only parameter received and is composed of the following:\r
+    *                0 => First formatted column,\r
+    *                1 => Last formatted column,\r
+    *                2 => Col width (8.43 is Excel default),\r
+    *                3 => The optional XF format of the column,\r
+    *                4 => Option flags.\r
+    */\r
+    function _store_colinfo($col_array)\r
+    {\r
+        if(isset($col_array[0])) {\r
+            $colFirst = $col_array[0];\r
+        }\r
+        if(isset($col_array[1])) {\r
+            $colLast = $col_array[1];\r
+        }\r
+        if(isset($col_array[2])) {\r
+            $coldx = $col_array[2];\r
+        }\r
+        else {\r
+            $coldx = 8.43;\r
+        }\r
+        if(isset($col_array[3])) {\r
+            $format = $col_array[3];\r
+        }\r
+        else {\r
+            $format = 0;\r
+        }\r
+        if(isset($col_array[4])) {\r
+            $grbit = $col_array[4];\r
+        }\r
+        else {\r
+            $grbit = 0;\r
+        }\r
+        $record   = 0x007D;          // Record identifier\r
+        $length   = 0x000B;          // Number of bytes to follow\r
+    \r
+        $coldx   += 0.72;            // Fudge. Excel subtracts 0.72 !?\r
+        $coldx   *= 256;             // Convert to units of 1/256 of a char\r
+    \r
+        $ixfe     = $this->_XF($format);\r
+        $reserved = 0x00;            // Reserved\r
+    \r
+        $header   = pack("vv",     $record, $length);\r
+        $data     = pack("vvvvvC", $colFirst, $colLast, $coldx,\r
+                                   $ixfe, $grbit, $reserved);\r
+        $this->_prepend($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Write BIFF record SELECTION.\r
+    *\r
+    * @param array $array array containing ($rwFirst,$colFirst,$rwLast,$colLast)\r
+    * @see set_selection()\r
+    */\r
+    function _store_selection($array)\r
+    {\r
+        list($rwFirst,$colFirst,$rwLast,$colLast) = $array;\r
+        $record   = 0x001D;                  // Record identifier\r
+        $length   = 0x000F;                  // Number of bytes to follow\r
+    \r
+        $pnn      = $this->_active_pane;     // Pane position\r
+        $rwAct    = $rwFirst;                // Active row\r
+        $colAct   = $colFirst;               // Active column\r
+        $irefAct  = 0;                       // Active cell ref\r
+        $cref     = 1;                       // Number of refs\r
+    \r
+        if (!isset($rwLast)) {\r
+            $rwLast   = $rwFirst;       // Last  row in reference\r
+        }\r
+        if (!isset($colLast)) {\r
+            $colLast  = $colFirst;      // Last  col in reference\r
+        }\r
+    \r
+        // Swap last row/col for first row/col as necessary\r
+        if ($rwFirst > $rwLast)\r
+        {\r
+            list($rwFirst, $rwLast) = array($rwLast, $rwFirst);\r
+        }\r
+    \r
+        if ($colFirst > $colLast)\r
+        {\r
+            list($colFirst, $colLast) = array($colLast, $colFirst);\r
+        }\r
+    \r
+        $header   = pack("vv",         $record, $length);\r
+        $data     = pack("CvvvvvvCC",  $pnn, $rwAct, $colAct,\r
+                                       $irefAct, $cref,\r
+                                       $rwFirst, $rwLast,\r
+                                       $colFirst, $colLast);\r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Write BIFF record EXTERNCOUNT to indicate the number of external sheet\r
+    * references in a worksheet.\r
+    *\r
+    * Excel only stores references to external sheets that are used in formulas.\r
+    * For simplicity we store references to all the sheets in the workbook\r
+    * regardless of whether they are used or not. This reduces the overall\r
+    * complexity and eliminates the need for a two way dialogue between the formula\r
+    * parser the worksheet objects.\r
+    *\r
+    * @param integer $count The number of external sheet references in this worksheet\r
+    */\r
+    function _store_externcount($count)\r
+    {\r
+        $record   = 0x0016;          // Record identifier\r
+        $length   = 0x0002;          // Number of bytes to follow\r
+    \r
+        $header   = pack("vv", $record, $length);\r
+        $data     = pack("v",  $count);\r
+        $this->_prepend($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Writes the Excel BIFF EXTERNSHEET record. These references are used by\r
+    * formulas. A formula references a sheet name via an index. Since we store a\r
+    * reference to all of the external worksheets the EXTERNSHEET index is the same\r
+    * as the worksheet index.\r
+    *\r
+    * @param string $sheetname The name of a external worksheet\r
+    */\r
+    function _store_externsheet($sheetname)\r
+    {\r
+        $record    = 0x0017;         // Record identifier\r
+    \r
+        // References to the current sheet are encoded differently to references to\r
+        // external sheets.\r
+        //\r
+        if ($this->name == $sheetname) {\r
+            $sheetname = '';\r
+            $length    = 0x02;  // The following 2 bytes\r
+            $cch       = 1;     // The following byte\r
+            $rgch      = 0x02;  // Self reference\r
+        }\r
+        else {\r
+            $length    = 0x02 + strlen($sheetname);\r
+            $cch       = strlen($sheetname);\r
+            $rgch      = 0x03;  // Reference to a sheet in the current workbook\r
+        }\r
+    \r
+        $header     = pack("vv",  $record, $length);\r
+        $data       = pack("CC", $cch, $rgch);\r
+        $this->_prepend($header.$data.$sheetname);\r
+    }\r
+    \r
+    /**\r
+    * Writes the Excel BIFF PANE record.\r
+    * The panes can either be frozen or thawed (unfrozen).\r
+    * Frozen panes are specified in terms of an integer number of rows and columns.\r
+    * Thawed panes are specified in terms of Excel's units for rows and columns.\r
+    *\r
+    * @param array $panes This is the only parameter received and is composed of the following:\r
+    *                     0 => Vertical split position,\r
+    *                     1 => Horizontal split position\r
+    *                     2 => Top row visible\r
+    *                     3 => Leftmost column visible\r
+    *                     4 => Active pane\r
+    */\r
+    function _store_panes($panes)\r
+    {\r
+        $y       = $panes[0];\r
+        $x       = $panes[1];\r
+        $rwTop   = $panes[2];\r
+        $colLeft = $panes[3];\r
+        if(count($panes) > 4) { // if Active pane was received\r
+            $pnnAct = $panes[4];\r
+        }\r
+        else {\r
+            $pnnAct = NULL;\r
+        }\r
+        $record  = 0x0041;       // Record identifier\r
+        $length  = 0x000A;       // Number of bytes to follow\r
+    \r
+        // Code specific to frozen or thawed panes.\r
+        if ($this->_frozen) {\r
+            // Set default values for $rwTop and $colLeft\r
+            if(!isset($rwTop)) {\r
+                $rwTop   = $y;\r
+            }\r
+            if(!isset($colLeft)) {\r
+                $colLeft = $x;\r
+            }\r
+        }\r
+        else {\r
+            // Set default values for $rwTop and $colLeft\r
+            if(!isset($rwTop)) {\r
+                $rwTop   = 0;\r
+            }\r
+            if(!isset($colLeft)) {\r
+                $colLeft = 0;\r
+            }\r
+    \r
+            // Convert Excel's row and column units to the internal units.\r
+            // The default row height is 12.75\r
+            // The default column width is 8.43\r
+            // The following slope and intersection values were interpolated.\r
+            //\r
+            $y = 20*$y      + 255;\r
+            $x = 113.879*$x + 390;\r
+        }\r
+    \r
+    \r
+        // Determine which pane should be active. There is also the undocumented\r
+        // option to override this should it be necessary: may be removed later.\r
+        //\r
+        if (!isset($pnnAct))\r
+        {\r
+            if ($x != 0 and $y != 0)\r
+                $pnnAct = 0; // Bottom right\r
+            if ($x != 0 and $y == 0)\r
+                $pnnAct = 1; // Top right\r
+            if ($x == 0 and $y != 0)\r
+                $pnnAct = 2; // Bottom left\r
+            if ($x == 0 and $y == 0)\r
+                $pnnAct = 3; // Top left\r
+        }\r
+    \r
+        $this->_active_pane = $pnnAct; // Used in _store_selection\r
+    \r
+        $header     = pack("vv",    $record, $length);\r
+        $data       = pack("vvvvv", $x, $y, $rwTop, $colLeft, $pnnAct);\r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Store the page setup SETUP BIFF record.\r
+    */\r
+    function _store_setup()\r
+    {\r
+        $record       = 0x00A1;                  // Record identifier\r
+        $length       = 0x0022;                  // Number of bytes to follow\r
+    \r
+        $iPaperSize   = $this->_paper_size;    // Paper size\r
+        $iScale       = $this->_print_scale;   // Print scaling factor\r
+        $iPageStart   = 0x01;                 // Starting page number\r
+        $iFitWidth    = $this->_fit_width;    // Fit to number of pages wide\r
+        $iFitHeight   = $this->_fit_height;   // Fit to number of pages high\r
+        $grbit        = 0x00;                 // Option flags\r
+        $iRes         = 0x0258;               // Print resolution\r
+        $iVRes        = 0x0258;               // Vertical print resolution\r
+        $numHdr       = $this->_margin_head;  // Header Margin\r
+        $numFtr       = $this->_margin_foot;   // Footer Margin\r
+        $iCopies      = 0x01;                 // Number of copies\r
+    \r
+        $fLeftToRight = 0x0;                     // Print over then down\r
+        $fLandscape   = $this->_orientation;     // Page orientation\r
+        $fNoPls       = 0x0;                     // Setup not read from printer\r
+        $fNoColor     = 0x0;                     // Print black and white\r
+        $fDraft       = 0x0;                     // Print draft quality\r
+        $fNotes       = 0x0;                     // Print notes\r
+        $fNoOrient    = 0x0;                     // Orientation not set\r
+        $fUsePage     = 0x0;                     // Use custom starting page\r
+    \r
+        $grbit           = $fLeftToRight;\r
+        $grbit          |= $fLandscape    << 1;\r
+        $grbit          |= $fNoPls        << 2;\r
+        $grbit          |= $fNoColor      << 3;\r
+        $grbit          |= $fDraft        << 4;\r
+        $grbit          |= $fNotes        << 5;\r
+        $grbit          |= $fNoOrient     << 6;\r
+        $grbit          |= $fUsePage      << 7;\r
+    \r
+        $numHdr = pack("d", $numHdr);\r
+        $numFtr = pack("d", $numFtr);\r
+        if ($this->_byte_order) // if it's Big Endian\r
+        {\r
+            $numHdr = strrev($numHdr);\r
+            $numFtr = strrev($numFtr);\r
+        }\r
+    \r
+        $header = pack("vv", $record, $length);\r
+        $data1  = pack("vvvvvvvv", $iPaperSize,\r
+                                   $iScale,\r
+                                   $iPageStart,\r
+                                   $iFitWidth,\r
+                                   $iFitHeight,\r
+                                   $grbit,\r
+                                   $iRes,\r
+                                   $iVRes);\r
+        $data2  = $numHdr .$numFtr;\r
+        $data3  = pack("v", $iCopies);\r
+        $this->_prepend($header.$data1.$data2.$data3);\r
+    }\r
+    \r
+    /**\r
+    * Store the header caption BIFF record.\r
+    */\r
+    function store_header()\r
+    {\r
+        $record  = 0x0014;               // Record identifier\r
+    \r
+        $str     = $this->_header;        // header string\r
+        $cch     = strlen($str);         // Length of header string\r
+        $length  = 1 + $cch;             // Bytes to follow\r
+    \r
+        $header    = pack("vv",  $record, $length);\r
+        $data      = pack("C",   $cch);\r
+    \r
+        $this->_append($header.$data.$str);\r
+    }\r
+    \r
+    /**\r
+    * Store the footer caption BIFF record.\r
+    */\r
+    function store_footer()\r
+    {\r
+        $record  = 0x0015;               // Record identifier\r
+    \r
+        $str     = $this->_footer;       // Footer string\r
+        $cch     = strlen($str);         // Length of footer string\r
+        $length  = 1 + $cch;             // Bytes to follow\r
+    \r
+        $header    = pack("vv",  $record, $length);\r
+        $data      = pack("C",   $cch);\r
+    \r
+        $this->_append($header.$data.$str);\r
+    }\r
+    \r
+    /**\r
+    * Store the horizontal centering HCENTER BIFF record.\r
+    */\r
+    function store_hcenter()\r
+    {\r
+        $record   = 0x0083;              // Record identifier\r
+        $length   = 0x0002;              // Bytes to follow\r
+    \r
+        $fHCenter = $this->_hcenter;      // Horizontal centering\r
+    \r
+        $header    = pack("vv",  $record, $length);\r
+        $data      = pack("v",   $fHCenter);\r
+    \r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Store the vertical centering VCENTER BIFF record.\r
+    */\r
+    function store_vcenter()\r
+    {\r
+        $record   = 0x0084;              // Record identifier\r
+        $length   = 0x0002;              // Bytes to follow\r
+    \r
+        $fVCenter = $this->_vcenter;      // Horizontal centering\r
+    \r
+        $header    = pack("vv", $record, $length);\r
+        $data      = pack("v", $fVCenter);\r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Store the LEFTMARGIN BIFF record.\r
+    */\r
+    function _store_margin_left()\r
+    {\r
+        $record  = 0x0026;                   // Record identifier\r
+        $length  = 0x0008;                   // Bytes to follow\r
+    \r
+        $margin  = $this->_margin_left;       // Margin in inches\r
+    \r
+        $header    = pack("vv",  $record, $length);\r
+        $data      = pack("d",   $margin);\r
+        if ($this->_byte_order) // if it's Big Endian\r
+        { \r
+            $data = strrev($data);\r
+        }\r
+    \r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Store the RIGHTMARGIN BIFF record.\r
+    */\r
+    function _store_margin_right()\r
+    {\r
+        $record  = 0x0027;                   // Record identifier\r
+        $length  = 0x0008;                   // Bytes to follow\r
+    \r
+        $margin  = $this->_margin_right;      // Margin in inches\r
+    \r
+        $header    = pack("vv",  $record, $length);\r
+        $data      = pack("d",   $margin);\r
+        if ($this->_byte_order) // if it's Big Endian\r
+        { \r
+            $data = strrev($data);\r
+        }\r
+    \r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Store the TOPMARGIN BIFF record.\r
+    */\r
+    function _store_margin_top()\r
+    {\r
+        $record  = 0x0028;                   // Record identifier\r
+        $length  = 0x0008;                   // Bytes to follow\r
+    \r
+        $margin  = $this->_margin_top;        // Margin in inches\r
+    \r
+        $header    = pack("vv",  $record, $length);\r
+        $data      = pack("d",   $margin);\r
+        if ($this->_byte_order) // if it's Big Endian\r
+        { \r
+            $data = strrev($data);\r
+        }\r
+    \r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Store the BOTTOMMARGIN BIFF record.\r
+    */\r
+    function _store_margin_bottom()\r
+    {\r
+        $record  = 0x0029;                   // Record identifier\r
+        $length  = 0x0008;                   // Bytes to follow\r
+    \r
+        $margin  = $this->_margin_bottom;     // Margin in inches\r
+    \r
+        $header    = pack("vv",  $record, $length);\r
+        $data      = pack("d",   $margin);\r
+        if ($this->_byte_order) // if it's Big Endian\r
+        { \r
+            $data = strrev($data);\r
+        }\r
+    \r
+        $this->_append($header.$data);\r
+    }\r
+\r
+    /**\r
+    * This is an Excel97/2000 method. It is required to perform more complicated\r
+    * merging than the normal set_align('merge'). It merges the area given by \r
+    * its arguments.\r
+    *\r
+    * @access public\r
+    * @param integer $first_row First row of the area to merge\r
+    * @param integer $first_col First column of the area to merge\r
+    * @param integer $last_row  Last row of the area to merge\r
+    * @param integer $last_col  Last column of the area to merge\r
+    */\r
+    function merge_cells($first_row, $first_col, $last_row, $last_col)\r
+    {\r
+        $record  = 0x00E5;                   // Record identifier\r
+        $length  = 0x000A;                   // Bytes to follow\r
+        $cref     = 1;                       // Number of refs\r
+\r
+        // Swap last row/col for first row/col as necessary\r
+        if ($first_row > $last_row) {\r
+            list($first_row, $last_row) = array($last_row, $first_row);\r
+        }\r
+    \r
+        if ($first_col > $last_col) {\r
+            list($first_col, $last_col) = array($last_col, $first_col);\r
+        }\r
+    \r
+        $header   = pack("vv",    $record, $length);\r
+        $data     = pack("vvvvv", $cref, $first_row, $last_row,\r
+                                  $first_col, $last_col);\r
+    \r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Write the PRINTHEADERS BIFF record.\r
+    */\r
+    function _store_print_headers()\r
+    {\r
+        $record      = 0x002a;                   // Record identifier\r
+        $length      = 0x0002;                   // Bytes to follow\r
+    \r
+        $fPrintRwCol = $this->_print_headers;     // Boolean flag\r
+    \r
+        $header      = pack("vv", $record, $length);\r
+        $data        = pack("v", $fPrintRwCol);\r
+        $this->_prepend($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the\r
+    * GRIDSET record.\r
+    */\r
+    function _store_print_gridlines()\r
+    {\r
+        $record      = 0x002b;                    // Record identifier\r
+        $length      = 0x0002;                    // Bytes to follow\r
+    \r
+        $fPrintGrid  = $this->_print_gridlines;    // Boolean flag\r
+    \r
+        $header      = pack("vv", $record, $length);\r
+        $data        = pack("v", $fPrintGrid);\r
+        $this->_prepend($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Write the GRIDSET BIFF record. Must be used in conjunction with the\r
+    * PRINTGRIDLINES record.\r
+    */\r
+    function _store_gridset()\r
+    {\r
+        $record      = 0x0082;                        // Record identifier\r
+        $length      = 0x0002;                        // Bytes to follow\r
+    \r
+        $fGridSet    = !($this->_print_gridlines);     // Boolean flag\r
+    \r
+        $header      = pack("vv",  $record, $length);\r
+        $data        = pack("v",   $fGridSet);\r
+        $this->_prepend($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction\r
+    * with the SETUP record.\r
+    */\r
+    function _store_wsbool()\r
+    {\r
+        $record      = 0x0081;   // Record identifier\r
+        $length      = 0x0002;   // Bytes to follow\r
+    \r
+        // The only option that is of interest is the flag for fit to page. So we\r
+        // set all the options in one go.\r
+        //\r
+        if ($this->_fit_page) {\r
+            $grbit = 0x05c1;\r
+        }\r
+        else {\r
+            $grbit = 0x04c1;\r
+        }\r
+    \r
+        $header      = pack("vv", $record, $length);\r
+        $data        = pack("v",  $grbit);\r
+        $this->_prepend($header.$data);\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Write the HORIZONTALPAGEBREAKS BIFF record.\r
+    */\r
+    function _store_hbreak()\r
+    {\r
+        // Return if the user hasn't specified pagebreaks\r
+        if(empty($this->_hbreaks)) {\r
+            return;\r
+        }\r
+    \r
+        // Sort and filter array of page breaks\r
+        $breaks = $this->_hbreaks;\r
+        sort($breaks,SORT_NUMERIC);\r
+        if($breaks[0] == 0) { // don't use first break if it's 0\r
+            array_shift($breaks);\r
+        }\r
+    \r
+        $record  = 0x001b;               // Record identifier\r
+        $cbrk    = count($breaks);       // Number of page breaks\r
+        $length  = ($cbrk + 1) * 2;      // Bytes to follow\r
+    \r
+        $header  = pack("vv", $record, $length);\r
+        $data    = pack("v",  $cbrk);\r
+    \r
+        // Append each page break\r
+        foreach($breaks as $break) {\r
+            $data .= pack("v", $break);\r
+        }\r
+    \r
+        $this->_prepend($header.$data);\r
+    }\r
+    \r
+    \r
+    /**\r
+    * Write the VERTICALPAGEBREAKS BIFF record.\r
+    */\r
+    function _store_vbreak()\r
+    {\r
+        // Return if the user hasn't specified pagebreaks\r
+        if(empty($this->_vbreaks)) {\r
+            return;\r
+        }\r
+    \r
+        // 1000 vertical pagebreaks appears to be an internal Excel 5 limit.\r
+        // It is slightly higher in Excel 97/200, approx. 1026\r
+        $breaks = array_slice($this->_vbreaks,0,1000);\r
+    \r
+        // Sort and filter array of page breaks\r
+        sort($breaks,SORT_NUMERIC);\r
+        if($breaks[0] == 0) { // don't use first break if it's 0\r
+            array_shift($breaks);\r
+        }\r
+    \r
+        $record  = 0x001a;               // Record identifier\r
+        $cbrk    = count($breaks);       // Number of page breaks\r
+        $length  = ($cbrk + 1) * 2;      // Bytes to follow\r
+    \r
+        $header  = pack("vv",  $record, $length);\r
+        $data    = pack("v",   $cbrk);\r
+    \r
+        // Append each page break\r
+        foreach ($breaks as $break) {\r
+            $data .= pack("v", $break);\r
+        }\r
+    \r
+        $this->_prepend($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Set the Biff PROTECT record to indicate that the worksheet is protected.\r
+    */\r
+    function _store_protect()\r
+    {\r
+        // Exit unless sheet protection has been specified\r
+        if($this->_protect == 0) {\r
+            return;\r
+        }\r
+    \r
+        $record      = 0x0012;             // Record identifier\r
+        $length      = 0x0002;             // Bytes to follow\r
+    \r
+        $fLock       = $this->_protect;    // Worksheet is protected\r
+    \r
+        $header      = pack("vv", $record, $length);\r
+        $data        = pack("v",  $fLock);\r
+    \r
+        $this->_prepend($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Write the worksheet PASSWORD record.\r
+    */\r
+    function _store_password()\r
+    {\r
+        // Exit unless sheet protection and password have been specified\r
+        if(($this->_protect == 0) or (!isset($this->_password))) {\r
+            return;\r
+        }\r
+    \r
+        $record      = 0x0013;               // Record identifier\r
+        $length      = 0x0002;               // Bytes to follow\r
+    \r
+        $wPassword   = $this->_password;     // Encoded password\r
+    \r
+        $header      = pack("vv", $record, $length);\r
+        $data        = pack("v",  $wPassword);\r
+    \r
+        $this->_prepend($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Insert a 24bit bitmap image in a worksheet. The main record required is\r
+    * IMDATA but it must be proceeded by a OBJ record to define its position.\r
+    *\r
+    * @access public\r
+    * @param integer $row     The row we are going to insert the bitmap into\r
+    * @param integer $col     The column we are going to insert the bitmap into\r
+    * @param string  $bitmap  The bitmap filename\r
+    * @param integer $x       The horizontal position (offset) of the image inside the cell.\r
+    * @param integer $y       The vertical position (offset) of the image inside the cell.\r
+    * @param integer $scale_x The horizontal scale\r
+    * @param integer $scale_y The vertical scale\r
+    */\r
+    function insert_bitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1)\r
+    {\r
+        list($width, $height, $size, $data) = $this->_process_bitmap($bitmap);\r
+    \r
+        // Scale the frame of the image.\r
+        $width  *= $scale_x;\r
+        $height *= $scale_y;\r
+    \r
+        // Calculate the vertices of the image and write the OBJ record\r
+        $this->_position_image($col, $row, $x, $y, $width, $height);\r
+    \r
+        // Write the IMDATA record to store the bitmap data\r
+        $record      = 0x007f;\r
+        $length      = 8 + $size;\r
+        $cf          = 0x09;\r
+        $env         = 0x01;\r
+        $lcb         = $size;\r
+    \r
+        $header      = pack("vvvvV", $record, $length, $cf, $env, $lcb);\r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Calculate the vertices that define the position of the image as required by\r
+    * the OBJ record.\r
+    *\r
+    *         +------------+------------+\r
+    *         |     A      |      B     |\r
+    *   +-----+------------+------------+\r
+    *   |     |(x1,y1)     |            |\r
+    *   |  1  |(A1)._______|______      |\r
+    *   |     |    |              |     |\r
+    *   |     |    |              |     |\r
+    *   +-----+----|    BITMAP    |-----+\r
+    *   |     |    |              |     |\r
+    *   |  2  |    |______________.     |\r
+    *   |     |            |        (B2)|\r
+    *   |     |            |     (x2,y2)|\r
+    *   +---- +------------+------------+\r
+    *\r
+    * Example of a bitmap that covers some of the area from cell A1 to cell B2.\r
+    *\r
+    * Based on the width and height of the bitmap we need to calculate 8 vars:\r
+    *     $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.\r
+    * The width and height of the cells are also variable and have to be taken into\r
+    * account.\r
+    * The values of $col_start and $row_start are passed in from the calling\r
+    * function. The values of $col_end and $row_end are calculated by subtracting\r
+    * the width and height of the bitmap from the width and height of the\r
+    * underlying cells.\r
+    * The vertices are expressed as a percentage of the underlying cell width as\r
+    * follows (rhs values are in pixels):\r
+    *\r
+    *       x1 = X / W *1024\r
+    *       y1 = Y / H *256\r
+    *       x2 = (X-1) / W *1024\r
+    *       y2 = (Y-1) / H *256\r
+    *\r
+    *       Where:  X is distance from the left side of the underlying cell\r
+    *               Y is distance from the top of the underlying cell\r
+    *               W is the width of the cell\r
+    *               H is the height of the cell\r
+    *\r
+    * @note  the SDK incorrectly states that the height should be expressed as a\r
+    *        percentage of 1024.\r
+    * @param integer $col_start Col containing upper left corner of object\r
+    * @param integer $row_start Row containing top left corner of object\r
+    * @param integer $x1        Distance to left side of object\r
+    * @param integer $y1        Distance to top of object\r
+    * @param integer $width     Width of image frame\r
+    * @param integer $height    Height of image frame\r
+    */\r
+    function _position_image($col_start, $row_start, $x1, $y1, $width, $height)\r
+    {\r
+        // Initialise end cell to the same as the start cell\r
+        $col_end    = $col_start;  // Col containing lower right corner of object\r
+        $row_end    = $row_start;  // Row containing bottom right corner of object\r
+    \r
+        // Zero the specified offset if greater than the cell dimensions\r
+        if ($x1 >= $this->size_col($col_start))\r
+        {\r
+            $x1 = 0;\r
+        }\r
+        if ($y1 >= $this->size_row($row_start))\r
+        {\r
+            $y1 = 0;\r
+        }\r
+    \r
+        $width      = $width  + $x1 -1;\r
+        $height     = $height + $y1 -1;\r
+    \r
+        // Subtract the underlying cell widths to find the end cell of the image\r
+        while ($width >= $this->size_col($col_end)) {\r
+            $width -= $this->size_col($col_end);\r
+            $col_end++;\r
+        }\r
+    \r
+        // Subtract the underlying cell heights to find the end cell of the image\r
+        while ($height >= $this->size_row($row_end)) {\r
+            $height -= $this->size_row($row_end);\r
+            $row_end++;\r
+        }\r
+    \r
+        // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell\r
+        // with zero eight or width.\r
+        //\r
+        if ($this->size_col($col_start) == 0)\r
+            return;\r
+        if ($this->size_col($col_end)   == 0)\r
+            return;\r
+        if ($this->size_row($row_start) == 0)\r
+            return;\r
+        if ($this->size_row($row_end)   == 0)\r
+            return;\r
+    \r
+        // Convert the pixel values to the percentage value expected by Excel\r
+        $x1 = $x1     / $this->size_col($col_start)   * 1024;\r
+        $y1 = $y1     / $this->size_row($row_start)   *  256;\r
+        $x2 = $width  / $this->size_col($col_end)     * 1024; // Distance to right side of object\r
+        $y2 = $height / $this->size_row($row_end)     *  256; // Distance to bottom of object\r
+    \r
+        $this->_store_obj_picture( $col_start, $x1,\r
+                                  $row_start, $y1,\r
+                                  $col_end, $x2,\r
+                                  $row_end, $y2\r
+                                );\r
+    }\r
+    \r
+    /**\r
+    * Convert the width of a cell from user's units to pixels. By interpolation\r
+    * the relationship is: y = 7x +5. If the width hasn't been set by the user we\r
+    * use the default value. If the col is hidden we use a value of zero.\r
+    *\r
+    * @param integer  $col The column \r
+    * @return integer The width in pixels\r
+    */\r
+    function size_col($col)\r
+    {\r
+        // Look up the cell value to see if it has been changed\r
+        if (isset($this->col_sizes[$col])) {\r
+            if ($this->col_sizes[$col] == 0) {\r
+                return(0);\r
+            }\r
+            else {\r
+                return(floor(7 * $this->col_sizes[$col] + 5));\r
+            }\r
+        }\r
+        else {\r
+            return(64);\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Convert the height of a cell from user's units to pixels. By interpolation\r
+    * the relationship is: y = 4/3x. If the height hasn't been set by the user we\r
+    * use the default value. If the row is hidden we use a value of zero. (Not\r
+    * possible to hide row yet).\r
+    *\r
+    * @param integer $row The row\r
+    * @return integer The width in pixels\r
+    */\r
+    function size_row($row)\r
+    {\r
+        // Look up the cell value to see if it has been changed\r
+        if (isset($this->row_sizes[$row])) {\r
+            if ($this->row_sizes[$row] == 0) {\r
+                return(0);\r
+            }\r
+            else {\r
+                return(floor(4/3 * $this->row_sizes[$row]));\r
+            }\r
+        }\r
+        else {\r
+            return(17);\r
+        }\r
+    }\r
+    \r
+    /**\r
+    * Store the OBJ record that precedes an IMDATA record. This could be generalise\r
+    * to support other Excel objects.\r
+    *\r
+    * @param integer $colL Column containing upper left corner of object\r
+    * @param integer $dxL  Distance from left side of cell\r
+    * @param integer $rwT  Row containing top left corner of object\r
+    * @param integer $dyT  Distance from top of cell\r
+    * @param integer $colR Column containing lower right corner of object\r
+    * @param integer $dxR  Distance from right of cell\r
+    * @param integer $rwB  Row containing bottom right corner of object\r
+    * @param integer $dyB  Distance from bottom of cell\r
+    */\r
+    function _store_obj_picture($colL,$dxL,$rwT,$dyT,$colR,$dxR,$rwB,$dyB)\r
+    {\r
+        $record      = 0x005d;   // Record identifier\r
+        $length      = 0x003c;   // Bytes to follow\r
+    \r
+        $cObj        = 0x0001;   // Count of objects in file (set to 1)\r
+        $OT          = 0x0008;   // Object type. 8 = Picture\r
+        $id          = 0x0001;   // Object ID\r
+        $grbit       = 0x0614;   // Option flags\r
+    \r
+        $cbMacro     = 0x0000;   // Length of FMLA structure\r
+        $Reserved1   = 0x0000;   // Reserved\r
+        $Reserved2   = 0x0000;   // Reserved\r
+    \r
+        $icvBack     = 0x09;     // Background colour\r
+        $icvFore     = 0x09;     // Foreground colour\r
+        $fls         = 0x00;     // Fill pattern\r
+        $fAuto       = 0x00;     // Automatic fill\r
+        $icv         = 0x08;     // Line colour\r
+        $lns         = 0xff;     // Line style\r
+        $lnw         = 0x01;     // Line weight\r
+        $fAutoB      = 0x00;     // Automatic border\r
+        $frs         = 0x0000;   // Frame style\r
+        $cf          = 0x0009;   // Image format, 9 = bitmap\r
+        $Reserved3   = 0x0000;   // Reserved\r
+        $cbPictFmla  = 0x0000;   // Length of FMLA structure\r
+        $Reserved4   = 0x0000;   // Reserved\r
+        $grbit2      = 0x0001;   // Option flags\r
+        $Reserved5   = 0x0000;   // Reserved\r
+    \r
+    \r
+        $header      = pack("vv", $record, $length);\r
+        $data        = pack("V", $cObj);\r
+        $data       .= pack("v", $OT);\r
+        $data       .= pack("v", $id);\r
+        $data       .= pack("v", $grbit);\r
+        $data       .= pack("v", $colL);\r
+        $data       .= pack("v", $dxL);\r
+        $data       .= pack("v", $rwT);\r
+        $data       .= pack("v", $dyT);\r
+        $data       .= pack("v", $colR);\r
+        $data       .= pack("v", $dxR);\r
+        $data       .= pack("v", $rwB);\r
+        $data       .= pack("v", $dyB);\r
+        $data       .= pack("v", $cbMacro);\r
+        $data       .= pack("V", $Reserved1);\r
+        $data       .= pack("v", $Reserved2);\r
+        $data       .= pack("C", $icvBack);\r
+        $data       .= pack("C", $icvFore);\r
+        $data       .= pack("C", $fls);\r
+        $data       .= pack("C", $fAuto);\r
+        $data       .= pack("C", $icv);\r
+        $data       .= pack("C", $lns);\r
+        $data       .= pack("C", $lnw);\r
+        $data       .= pack("C", $fAutoB);\r
+        $data       .= pack("v", $frs);\r
+        $data       .= pack("V", $cf);\r
+        $data       .= pack("v", $Reserved3);\r
+        $data       .= pack("v", $cbPictFmla);\r
+        $data       .= pack("v", $Reserved4);\r
+        $data       .= pack("v", $grbit2);\r
+        $data       .= pack("V", $Reserved5);\r
+    \r
+        $this->_append($header.$data);\r
+    }\r
+    \r
+    /**\r
+    * Convert a 24 bit bitmap into the modified internal format used by Windows.\r
+    * This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the\r
+    * MSDN library.\r
+    *\r
+    * @param string $bitmap The bitmap to process\r
+    * @return array Array with data and properties of the bitmap\r
+    */\r
+    function _process_bitmap($bitmap)\r
+    {\r
+        // Open file.\r
+        $bmp_fd = fopen($bitmap,"rb");\r
+        if (!$bmp_fd) {\r
+            die("Couldn't import $bitmap");\r
+        }\r
+            \r
+        // Slurp the file into a string.\r
+        $data = fread($bmp_fd, filesize($bitmap));\r
+    \r
+        // Check that the file is big enough to be a bitmap.\r
+        if (strlen($data) <= 0x36) {\r
+            die("$bitmap doesn't contain enough data.\n");\r
+        }\r
+    \r
+        // The first 2 bytes are used to identify the bitmap.\r
+        $identity = unpack("A2", $data);\r
+        if ($identity[''] != "BM") {\r
+            die("$bitmap doesn't appear to be a valid bitmap image.\n");\r
+        }\r
+    \r
+        // Remove bitmap data: ID.\r
+        $data = substr($data, 2);\r
+    \r
+        // Read and remove the bitmap size. This is more reliable than reading\r
+        // the data size at offset 0x22.\r
+        //\r
+        $size_array   = unpack("V", substr($data, 0, 4));\r
+        $size   = $size_array[''];\r
+        $data   = substr($data, 4);\r
+        $size  -= 0x36; // Subtract size of bitmap header.\r
+        $size  += 0x0C; // Add size of BIFF header.\r
+    \r
+        // Remove bitmap data: reserved, offset, header length.\r
+        $data = substr($data, 12);\r
+    \r
+        // Read and remove the bitmap width and height. Verify the sizes.\r
+        $width_and_height = unpack("V2", substr($data, 0, 8));\r
+        $width  = $width_and_height[1];\r
+        $height = $width_and_height[2];\r
+        $data   = substr($data, 8);\r
+        if ($width > 0xFFFF) { \r
+            die("$bitmap: largest image width supported is 65k.\n");\r
+        }\r
+        if ($height > 0xFFFF) { \r
+            die("$bitmap: largest image height supported is 65k.\n");\r
+        }\r
+    \r
+        // Read and remove the bitmap planes and bpp data. Verify them.\r
+        $planes_and_bitcount = unpack("v2", substr($data, 0, 4));\r
+        $data = substr($data, 4);\r
+        if ($planes_and_bitcount[2] != 24) { // Bitcount\r
+            die("$bitmap isn't a 24bit true color bitmap.\n");\r
+        }\r
+        if ($planes_and_bitcount[1] != 1) {\r
+            die("$bitmap: only 1 plane supported in bitmap image.\n");\r
+        }\r
+    \r
+        // Read and remove the bitmap compression. Verify compression.\r
+        $compression = unpack("V", substr($data, 0, 4));\r
+        $data = substr($data, 4);\r
+      \r
+        //$compression = 0;\r
+        if ($compression[""] != 0) {\r
+            die("$bitmap: compression not supported in bitmap image.\n");\r
+        }\r
+    \r
+        // Remove bitmap data: data size, hres, vres, colours, imp. colours.\r
+        $data = substr($data, 20);\r
+    \r
+        // Add the BITMAPCOREHEADER data\r
+        $header  = pack("Vvvvv", 0x000c, $width, $height, 0x01, 0x18);\r
+        $data    = $header . $data;\r
+    \r
+        return (array($width, $height, $size, $data));\r
+    }\r
+    \r
+    /**\r
+    * Store the window zoom factor. This should be a reduced fraction but for\r
+    * simplicity we will store all fractions with a numerator of 100.\r
+    */\r
+    function _store_zoom()\r
+    {\r
+        // If scale is 100 we don't need to write a record\r
+        if ($this->_zoom == 100) {\r
+            return;\r
+        }\r
+    \r
+        $record      = 0x00A0;               // Record identifier\r
+        $length      = 0x0004;               // Bytes to follow\r
+    \r
+        $header      = pack("vv", $record, $length);\r
+        $data        = pack("vv", $this->_zoom, 100);\r
+        $this->_append($header.$data);\r
+    }\r
+}\r
+?>
\ No newline at end of file
diff --git a/mod/attendance/write_excel/test.php b/mod/attendance/write_excel/test.php
new file mode 100644 (file)
index 0000000..25a4f11
--- /dev/null
@@ -0,0 +1,90 @@
+<?php\r
+  //require_once('OLEwriter.php');\r
+  //require_once('BIFFwriter.php');\r
+  require_once('Worksheet.php');\r
+  require_once('Workbook.php');\r
+\r
+  function HeaderingExcel($filename) {\r
+      header("Content-type: application/vnd.ms-excel");\r
+      header("Content-Disposition: attachment; filename=$filename" );\r
+      header("Expires: 0");\r
+      header("Cache-Control: must-revalidate, post-check=0,pre-check=0");\r
+      header("Pragma: public");\r
+      }\r
+\r
+  // HTTP headers\r
+  HeaderingExcel('test.xls');\r
+\r
+  // Creating a workbook\r
+  $workbook = new Workbook("-");\r
+  // Creating the first worksheet\r
+  $worksheet1 =& $workbook->add_worksheet('First One');\r
+// set the column width\r
+  $worksheet1->set_column(1, 1, 40);\r
+// set the row height\r
+  $worksheet1->set_row(1, 20);\r
+  $worksheet1->write_string(1, 1, "This worksheet's name is ".$worksheet1->get_name());\r
+  $worksheet1->write(2,1,"http://www.phpclasses.org/browse.html/package/767.html");\r
+  $worksheet1->write_number(3, 0, 11);\r
+  $worksheet1->write_number(3, 1, 1);\r
+  $worksheet1->write_string(3, 2, "by four is");\r
+  $worksheet1->write_formula(3, 3, "=A4 * (2 + 2)");\r
+  //$worksheet1->write_formula(3, 3, "= SUM(A4:B4)");\r
+  $worksheet1->write(5, 4, "= POWER(2,3)");\r
+  $worksheet1->write(4, 4, "= SUM(5, 5, 5)");\r
+  //$worksheet1->write_formula(4, 4, "= LN(2.71428)");\r
+  //$worksheet1->write_formula(5, 4, "= SIN(PI()/2)");\r
+\r
+  // Creating the second worksheet\r
+  $worksheet2 =& $workbook->add_worksheet();\r
+\r
+  // Format for the headings\r
+  $formatot =& $workbook->add_format();\r
+  $formatot->set_size(10);\r
+  $formatot->set_align('center');\r
+  $formatot->set_color('white');\r
+  $formatot->set_pattern();\r
+  $formatot->set_fg_color('magenta');\r
+\r
+  $worksheet2->set_column(0,0,15);\r
+  $worksheet2->set_column(1,2,30);\r
+  $worksheet2->set_column(3,3,15);\r
+  $worksheet2->set_column(4,4,10);\r
+\r
+  $worksheet2->write_string(1,0,"Id",$formatot);\r
+  $worksheet2->write_string(1,1,"Name",$formatot);\r
+  $worksheet2->write_string(1,2,"Adress",$formatot);\r
+  $worksheet2->write_string(1,3,"Phone Number",$formatot);\r
+  $worksheet2->write_string(1,4,"Salary",$formatot);\r
+\r
+  $worksheet2->write(3,0,"22222222-2");\r
+  $worksheet2->write(3,1,"John Smith");\r
+  $worksheet2->write(3,2,"Main Street 100");\r
+  $worksheet2->write(3,3,"02-5551234");\r
+  $worksheet2->write(3,4,100);\r
+  $worksheet2->write(4,0,"11111111-1");\r
+  $worksheet2->write(4,1,"Juan Perez");\r
+  $worksheet2->write(4,2,"Los Paltos 200");\r
+  $worksheet2->write(4,3,"03-5552345");\r
+  $worksheet2->write(4,4,110);\r
+  // if you are writing a very long worksheet, you may want to use\r
+  // write_xxx() functions, instead of write() for performance reasons.\r
+  $worksheet2->write_string(5,0,"11111111-1");\r
+  $worksheet2->write_string(5,1,"Another Guy");\r
+  $worksheet2->write_string(5,2,"Somewhere 300");\r
+  $worksheet2->write_string(5,3,"03-5553456");\r
+  $worksheet2->write(5,4,108);\r
+\r
+\r
+  // Calculate some statistics\r
+  $worksheet2->write(7, 0, "Average Salary:");\r
+  $worksheet2->write_formula(7, 4, "= AVERAGE(E4:E6)");\r
+  $worksheet2->write(8, 0, "Minimum Salary:");\r
+  $worksheet2->write_formula(8, 4, "= MIN(E4:E6)");\r
+  $worksheet2->write(9, 0, "Maximum Salary:");\r
+  $worksheet2->write_formula(9, 4, "= MAX(E4:E6)");\r
+\r
+  //$worksheet2->insert_bitmap(0, 0, "some.bmp",10,10);\r
+\r
+  $workbook->close();\r
+?>
\ No newline at end of file