Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/pear/Spreadsheet/Excel/Writer/Workbook.php
Go to the documentation of this file.
00001 <?php
00002 /*
00003 *  Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
00004 *
00005 *  The majority of this is _NOT_ my code.  I simply ported it from the
00006 *  PERL Spreadsheet::WriteExcel module.
00007 *
00008 *  The author of the Spreadsheet::WriteExcel module is John McNamara
00009 *  <jmcnamara@cpan.org>
00010 *
00011 *  I _DO_ maintain this code, and John McNamara has nothing to do with the
00012 *  porting of this code to PHP.  Any questions directly related to this
00013 *  class library should be directed to me.
00014 *
00015 *  License Information:
00016 *
00017 *    Spreadsheet_Excel_Writer:  A library for generating Excel Spreadsheets
00018 *    Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
00019 *
00020 *    This library is free software; you can redistribute it and/or
00021 *    modify it under the terms of the GNU Lesser General Public
00022 *    License as published by the Free Software Foundation; either
00023 *    version 2.1 of the License, or (at your option) any later version.
00024 *
00025 *    This library is distributed in the hope that it will be useful,
00026 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
00027 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00028 *    Lesser General Public License for more details.
00029 *
00030 *    You should have received a copy of the GNU Lesser General Public
00031 *    License along with this library; if not, write to the Free Software
00032 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00033 */
00034 
00035 require_once 'Spreadsheet/Excel/Writer/Format.php';
00036 require_once 'Spreadsheet/Excel/Writer/BIFFwriter.php';
00037 require_once 'Spreadsheet/Excel/Writer/Worksheet.php';
00038 require_once 'Spreadsheet/Excel/Writer/Parser.php';
00039 require_once 'OLE/PPS/Root.php';
00040 require_once 'OLE/PPS/File.php';
00041 
00050 class Spreadsheet_Excel_Writer_Workbook extends Spreadsheet_Excel_Writer_BIFFwriter
00051 {
00056     var $_filename;
00057 
00062     var $_parser;
00063 
00068     var $_1904;
00069 
00074     var $_activesheet;
00075 
00080     var $_firstsheet;
00081 
00086     var $_selected;
00087 
00092     var $_xf_index;
00093 
00099     var $_fileclosed;
00100 
00106     var $_biffsize;
00107 
00112     var $_sheetname;
00113 
00118     var $_tmp_format;
00119 
00124     var $_worksheets;
00125 
00130     var $_sheetnames;
00131 
00136     var $_formats;
00137 
00142     var $_palette;
00143 
00148     var $_url_format;
00149 
00154     var $_codepage;
00155 
00160     var $_country_code;
00161 
00166     var $_tmp_dir;
00167 
00172     var $_string_sizeinfo_size;
00173 
00180     function Spreadsheet_Excel_Writer_Workbook($filename)
00181     {
00182         // It needs to call its parent's constructor explicitly
00183         $this->Spreadsheet_Excel_Writer_BIFFwriter();
00184 
00185         $this->_filename         = $filename;
00186         $this->_parser           = new Spreadsheet_Excel_Writer_Parser($this->_byte_order, $this->_BIFF_version);
00187         $this->_1904             = 0;
00188         $this->_activesheet      = 0;
00189         $this->_firstsheet       = 0;
00190         $this->_selected         = 0;
00191         $this->_xf_index         = 16; // 15 style XF's and 1 cell XF.
00192         $this->_fileclosed       = 0;
00193         $this->_biffsize         = 0;
00194         $this->_sheetname        = 'Sheet';
00195         $this->_tmp_format       = new Spreadsheet_Excel_Writer_Format($this->_BIFF_version);
00196         $this->_worksheets       = array();
00197         $this->_sheetnames       = array();
00198         $this->_formats          = array();
00199         $this->_palette          = array();
00200         $this->_codepage         = 0x04E4; // FIXME: should change for BIFF8
00201         $this->_country_code     = -1;
00202         $this->_string_sizeinfo  = 3;
00203 
00204         // Add the default format for hyperlinks
00205         $this->_url_format =& $this->addFormat(array('color' => 'blue', 'underline' => 1));
00206         $this->_str_total       = 0;
00207         $this->_str_unique      = 0;
00208         $this->_str_table       = array();
00209         $this->_setPaletteXl97();
00210         $this->_tmp_dir         = '';
00211     }
00212 
00220     function close()
00221     {
00222         if ($this->_fileclosed) { // Prevent close() from being called twice.
00223             return true;
00224         }
00225         $res = $this->_storeWorkbook();
00226         if ($this->isError($res)) {
00227             return $this->raiseError($res->getMessage());
00228         }
00229         $this->_fileclosed = 1;
00230         return true;
00231     }
00232 
00242     function sheets()
00243     {
00244         return $this->worksheets();
00245     }
00246 
00254     function worksheets()
00255     {
00256         return $this->_worksheets;
00257     }
00258 
00269     function setVersion($version)
00270     {
00271         if ($version == 8) { // only accept version 8
00272             $version = 0x0600;
00273             $this->_BIFF_version = $version;
00274             // change BIFFwriter limit for CONTINUE records
00275             $this->_limit = 8228;
00276             $this->_tmp_format->_BIFF_version = $version;
00277             $this->_url_format->_BIFF_version = $version;
00278             $this->_parser->_BIFF_version = $version;
00279 
00280             $total_worksheets = count($this->_worksheets);
00281             // change version for all worksheets too
00282             for ($i = 0; $i < $total_worksheets; $i++) {
00283                 $this->_worksheets[$i]->_BIFF_version = $version;
00284             }
00285 
00286             $total_formats = count($this->_formats);
00287             // change version for all formats too
00288             for ($i = 0; $i < $total_formats; $i++) {
00289                 $this->_formats[$i]->_BIFF_version = $version;
00290             }
00291         }
00292     }
00293 
00301     function setCountry($code)
00302     {
00303         $this->_country_code = $code;
00304     }
00305 
00316     function &addWorksheet($name = '')
00317     {
00318         $index     = count($this->_worksheets);
00319         $sheetname = $this->_sheetname;
00320 
00321         if ($name == '') {
00322             $name = $sheetname.($index+1);
00323         }
00324 
00325         // Check that sheetname is <= 31 chars (Excel limit before BIFF8).
00326         if ($this->_BIFF_version != 0x0600)
00327         {
00328             if (strlen($name) > 31) {
00329                 return $this->raiseError("Sheetname $name must be <= 31 chars");
00330             }
00331         }
00332 
00333         // Check that the worksheet name doesn't already exist: a fatal Excel error.
00334         $total_worksheets = count($this->_worksheets);
00335         for ($i = 0; $i < $total_worksheets; $i++) {
00336             if ($this->_worksheets[$i]->getName() == $name) {
00337                 return $this->raiseError("Worksheet '$name' already exists");
00338             }
00339         }
00340 
00341         $worksheet = new Spreadsheet_Excel_Writer_Worksheet($this->_BIFF_version,
00342                                    $name, $index,
00343                                    $this->_activesheet, $this->_firstsheet,
00344                                    $this->_str_total, $this->_str_unique,
00345                                    $this->_str_table, $this->_url_format,
00346                                    $this->_parser);
00347 
00348         $this->_worksheets[$index] = &$worksheet;    // Store ref for iterator
00349         $this->_sheetnames[$index] = $name;          // Store EXTERNSHEET names
00350         $this->_parser->setExtSheet($name, $index);  // Register worksheet name with parser
00351         return $worksheet;
00352     }
00353 
00362     function &addFormat($properties = array())
00363     {
00364         $format = new Spreadsheet_Excel_Writer_Format($this->_BIFF_version, $this->_xf_index, $properties);
00365         $this->_xf_index += 1;
00366         $this->_formats[] = &$format;
00367         return $format;
00368     }
00369 
00376     function &addValidator()
00377     {
00378         include_once 'Spreadsheet/Excel/Writer/Validator.php';
00379         /* FIXME: check for successful inclusion*/
00380         $valid = new Spreadsheet_Excel_Writer_Validator($this->_parser);
00381         return $valid;
00382     }
00383 
00394     function setCustomColor($index, $red, $green, $blue)
00395     {
00396         // Match a HTML #xxyyzz style parameter
00397         /*if (defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) {
00398             @_ = ($_[0], hex $1, hex $2, hex $3);
00399         }*/
00400 
00401         // Check that the colour index is the right range
00402         if ($index < 8 or $index > 64) {
00403             // TODO: assign real error codes
00404             return $this->raiseError("Color index $index outside range: 8 <= index <= 64");
00405         }
00406 
00407         // Check that the colour components are in the right range
00408         if (($red   < 0 or $red   > 255) ||
00409             ($green < 0 or $green > 255) ||
00410             ($blue  < 0 or $blue  > 255))
00411         {
00412             return $this->raiseError("Color component outside range: 0 <= color <= 255");
00413         }
00414 
00415         $index -= 8; // Adjust colour index (wingless dragonfly)
00416 
00417         // Set the RGB value
00418         $this->_palette[$index] = array($red, $green, $blue, 0);
00419         return($index + 8);
00420     }
00421 
00427     function _setPaletteXl97()
00428     {
00429         $this->_palette = array(
00430                            array(0x00, 0x00, 0x00, 0x00),   // 8
00431                            array(0xff, 0xff, 0xff, 0x00),   // 9
00432                            array(0xff, 0x00, 0x00, 0x00),   // 10
00433                            array(0x00, 0xff, 0x00, 0x00),   // 11
00434                            array(0x00, 0x00, 0xff, 0x00),   // 12
00435                            array(0xff, 0xff, 0x00, 0x00),   // 13
00436                            array(0xff, 0x00, 0xff, 0x00),   // 14
00437                            array(0x00, 0xff, 0xff, 0x00),   // 15
00438                            array(0x80, 0x00, 0x00, 0x00),   // 16
00439                            array(0x00, 0x80, 0x00, 0x00),   // 17
00440                            array(0x00, 0x00, 0x80, 0x00),   // 18
00441                            array(0x80, 0x80, 0x00, 0x00),   // 19
00442                            array(0x80, 0x00, 0x80, 0x00),   // 20
00443                            array(0x00, 0x80, 0x80, 0x00),   // 21
00444                            array(0xc0, 0xc0, 0xc0, 0x00),   // 22
00445                            array(0x80, 0x80, 0x80, 0x00),   // 23
00446                            array(0x99, 0x99, 0xff, 0x00),   // 24
00447                            array(0x99, 0x33, 0x66, 0x00),   // 25
00448                            array(0xff, 0xff, 0xcc, 0x00),   // 26
00449                            array(0xcc, 0xff, 0xff, 0x00),   // 27
00450                            array(0x66, 0x00, 0x66, 0x00),   // 28
00451                            array(0xff, 0x80, 0x80, 0x00),   // 29
00452                            array(0x00, 0x66, 0xcc, 0x00),   // 30
00453                            array(0xcc, 0xcc, 0xff, 0x00),   // 31
00454                            array(0x00, 0x00, 0x80, 0x00),   // 32
00455                            array(0xff, 0x00, 0xff, 0x00),   // 33
00456                            array(0xff, 0xff, 0x00, 0x00),   // 34
00457                            array(0x00, 0xff, 0xff, 0x00),   // 35
00458                            array(0x80, 0x00, 0x80, 0x00),   // 36
00459                            array(0x80, 0x00, 0x00, 0x00),   // 37
00460                            array(0x00, 0x80, 0x80, 0x00),   // 38
00461                            array(0x00, 0x00, 0xff, 0x00),   // 39
00462                            array(0x00, 0xcc, 0xff, 0x00),   // 40
00463                            array(0xcc, 0xff, 0xff, 0x00),   // 41
00464                            array(0xcc, 0xff, 0xcc, 0x00),   // 42
00465                            array(0xff, 0xff, 0x99, 0x00),   // 43
00466                            array(0x99, 0xcc, 0xff, 0x00),   // 44
00467                            array(0xff, 0x99, 0xcc, 0x00),   // 45
00468                            array(0xcc, 0x99, 0xff, 0x00),   // 46
00469                            array(0xff, 0xcc, 0x99, 0x00),   // 47
00470                            array(0x33, 0x66, 0xff, 0x00),   // 48
00471                            array(0x33, 0xcc, 0xcc, 0x00),   // 49
00472                            array(0x99, 0xcc, 0x00, 0x00),   // 50
00473                            array(0xff, 0xcc, 0x00, 0x00),   // 51
00474                            array(0xff, 0x99, 0x00, 0x00),   // 52
00475                            array(0xff, 0x66, 0x00, 0x00),   // 53
00476                            array(0x66, 0x66, 0x99, 0x00),   // 54
00477                            array(0x96, 0x96, 0x96, 0x00),   // 55
00478                            array(0x00, 0x33, 0x66, 0x00),   // 56
00479                            array(0x33, 0x99, 0x66, 0x00),   // 57
00480                            array(0x00, 0x33, 0x00, 0x00),   // 58
00481                            array(0x33, 0x33, 0x00, 0x00),   // 59
00482                            array(0x99, 0x33, 0x00, 0x00),   // 60
00483                            array(0x99, 0x33, 0x66, 0x00),   // 61
00484                            array(0x33, 0x33, 0x99, 0x00),   // 62
00485                            array(0x33, 0x33, 0x33, 0x00),   // 63
00486                          );
00487     }
00488 
00496     function _storeWorkbook()
00497     {
00498         // Ensure that at least one worksheet has been selected.
00499         if ($this->_activesheet == 0) {
00500             $this->_worksheets[0]->selected = 1;
00501         }
00502 
00503         // Calculate the number of selected worksheet tabs and call the finalization
00504         // methods for each worksheet
00505         $total_worksheets = count($this->_worksheets);
00506         for ($i = 0; $i < $total_worksheets; $i++) {
00507             if ($this->_worksheets[$i]->selected) {
00508                 $this->_selected++;
00509             }
00510             $this->_worksheets[$i]->close($this->_sheetnames);
00511         }
00512 
00513         // Add Workbook globals
00514         $this->_storeBof(0x0005);
00515         $this->_storeCodepage();
00516         if ($this->_BIFF_version == 0x0600) {
00517             $this->_storeWindow1();
00518         }
00519         if ($this->_BIFF_version == 0x0500) {
00520             $this->_storeExterns();    // For print area and repeat rows
00521         }
00522         $this->_storeNames();      // For print area and repeat rows
00523         if ($this->_BIFF_version == 0x0500) {
00524             $this->_storeWindow1();
00525         }
00526         $this->_storeDatemode();
00527         $this->_storeAllFonts();
00528         $this->_storeAllNumFormats();
00529         $this->_storeAllXfs();
00530         $this->_storeAllStyles();
00531         $this->_storePalette();
00532         $this->_calcSheetOffsets();
00533 
00534         // Add BOUNDSHEET records
00535         for ($i = 0; $i < $total_worksheets; $i++) {
00536             $this->_storeBoundsheet($this->_worksheets[$i]->name,$this->_worksheets[$i]->offset);
00537         }
00538 
00539         if ($this->_country_code != -1) {
00540             $this->_storeCountry();
00541         }
00542 
00543         if ($this->_BIFF_version == 0x0600) {
00544             //$this->_storeSupbookInternal();
00545             /* TODO: store external SUPBOOK records and XCT and CRN records
00546             in case of external references for BIFF8 */
00547             //$this->_storeExternsheetBiff8();
00548             $this->_storeSharedStringsTable();
00549         }
00550 
00551         // End Workbook globals
00552         $this->_storeEof();
00553 
00554         // Store the workbook in an OLE container
00555         $res = $this->_storeOLEFile();
00556         if ($this->isError($res)) {
00557             return $this->raiseError($res->getMessage());
00558         }
00559         return true;
00560     }
00561 
00569     function setTempDir($dir)
00570     {
00571         if (is_dir($dir)) {
00572             $this->_tmp_dir = $dir;
00573             return true;
00574         }
00575         return false;
00576     }
00577 
00584     function _storeOLEFile()
00585     {
00586         $OLE = new OLE_PPS_File(OLE::Asc2Ucs('Book'));
00587         if ($this->_tmp_dir != '') {
00588             $OLE->setTempDir($this->_tmp_dir);
00589         }
00590         $res = $OLE->init();
00591         if ($this->isError($res)) {
00592             return $this->raiseError("OLE Error: ".$res->getMessage());
00593         }
00594         $OLE->append($this->_data);
00595 
00596         $total_worksheets = count($this->_worksheets);
00597         for ($i = 0; $i < $total_worksheets; $i++) {
00598             while ($tmp = $this->_worksheets[$i]->getData()) {
00599                 $OLE->append($tmp);
00600             }
00601         }
00602 
00603         $root = new OLE_PPS_Root(time(), time(), array($OLE));
00604         if ($this->_tmp_dir != '') {
00605             $root->setTempDir($this->_tmp_dir);
00606         }
00607 
00608         $res = $root->save($this->_filename);
00609         if ($this->isError($res)) {
00610             return $this->raiseError("OLE Error: ".$res->getMessage());
00611         }
00612         return true;
00613     }
00614 
00620     function _calcSheetOffsets()
00621     {
00622         if ($this->_BIFF_version == 0x0600) {
00623             $boundsheet_length = 12;  // fixed length for a BOUNDSHEET record
00624         } else {
00625             $boundsheet_length = 11;
00626         }
00627         $EOF               = 4;
00628         $offset            = $this->_datasize;
00629 
00630         if ($this->_BIFF_version == 0x0600) {
00631             // add the length of the SST
00632             /* TODO: check this works for a lot of strings (> 8224 bytes) */
00633             $offset += $this->_calculateSharedStringsSizes();
00634             if ($this->_country_code != -1) {
00635                 $offset += 8; // adding COUNTRY record
00636             }
00637             // add the lenght of SUPBOOK, EXTERNSHEET and NAME records
00638             //$offset += 8; // FIXME: calculate real value when storing the records
00639         }
00640         $total_worksheets = count($this->_worksheets);
00641         // add the length of the BOUNDSHEET records
00642         for ($i = 0; $i < $total_worksheets; $i++) {
00643             $offset += $boundsheet_length + strlen($this->_worksheets[$i]->name);
00644         }
00645         $offset += $EOF;
00646 
00647         for ($i = 0; $i < $total_worksheets; $i++) {
00648             $this->_worksheets[$i]->offset = $offset;
00649             $offset += $this->_worksheets[$i]->_datasize;
00650         }
00651         $this->_biffsize = $offset;
00652     }
00653 
00659     function _storeAllFonts()
00660     {
00661         // tmp_format is added by the constructor. We use this to write the default XF's
00662         $format = $this->_tmp_format;
00663         $font   = $format->getFont();
00664 
00665         // Note: Fonts are 0-indexed. According to the SDK there is no index 4,
00666         // so the following fonts are 0, 1, 2, 3, 5
00667         //
00668         for ($i = 1; $i <= 5; $i++){
00669             $this->_append($font);
00670         }
00671 
00672         // Iterate through the XF objects and write a FONT record if it isn't the
00673         // same as the default FONT and if it hasn't already been used.
00674         //
00675         $fonts = array();
00676         $index = 6;                  // The first user defined FONT
00677 
00678         $key = $format->getFontKey(); // The default font from _tmp_format
00679         $fonts[$key] = 0;             // Index of the default font
00680 
00681         $total_formats = count($this->_formats);
00682         for ($i = 0; $i < $total_formats; $i++) {
00683             $key = $this->_formats[$i]->getFontKey();
00684             if (isset($fonts[$key])) {
00685                 // FONT has already been used
00686                 $this->_formats[$i]->font_index = $fonts[$key];
00687             } else {
00688                 // Add a new FONT record
00689                 $fonts[$key]        = $index;
00690                 $this->_formats[$i]->font_index = $index;
00691                 $index++;
00692                 $font = $this->_formats[$i]->getFont();
00693                 $this->_append($font);
00694             }
00695         }
00696     }
00697 
00703     function _storeAllNumFormats()
00704     {
00705         // Leaning num_format syndrome
00706         $hash_num_formats = array();
00707         $num_formats      = array();
00708         $index = 164;
00709 
00710         // Iterate through the XF objects and write a FORMAT record if it isn't a
00711         // built-in format type and if the FORMAT string hasn't already been used.
00712         $total_formats = count($this->_formats);
00713         for ($i = 0; $i < $total_formats; $i++) {
00714             $num_format = $this->_formats[$i]->_num_format;
00715 
00716             // Check if $num_format is an index to a built-in format.
00717             // Also check for a string of zeros, which is a valid format string
00718             // but would evaluate to zero.
00719             //
00720             if (!preg_match("/^0+\d/", $num_format)) {
00721                 if (preg_match("/^\d+$/", $num_format)) { // built-in format
00722                     continue;
00723                 }
00724             }
00725 
00726             if (isset($hash_num_formats[$num_format])) {
00727                 // FORMAT has already been used
00728                 $this->_formats[$i]->_num_format = $hash_num_formats[$num_format];
00729             } else{
00730                 // Add a new FORMAT
00731                 $hash_num_formats[$num_format]  = $index;
00732                 $this->_formats[$i]->_num_format = $index;
00733                 array_push($num_formats,$num_format);
00734                 $index++;
00735             }
00736         }
00737 
00738         // Write the new FORMAT records starting from 0xA4
00739         $index = 164;
00740         foreach ($num_formats as $num_format) {
00741             $this->_storeNumFormat($num_format,$index);
00742             $index++;
00743         }
00744     }
00745 
00751     function _storeAllXfs()
00752     {
00753         // _tmp_format is added by the constructor. We use this to write the default XF's
00754         // The default font index is 0
00755         //
00756         $format = $this->_tmp_format;
00757         for ($i = 0; $i <= 14; $i++) {
00758             $xf = $format->getXf('style'); // Style XF
00759             $this->_append($xf);
00760         }
00761 
00762         $xf = $format->getXf('cell');      // Cell XF
00763         $this->_append($xf);
00764 
00765         // User defined XFs
00766         $total_formats = count($this->_formats);
00767         for ($i = 0; $i < $total_formats; $i++) {
00768             $xf = $this->_formats[$i]->getXf('cell');
00769             $this->_append($xf);
00770         }
00771     }
00772 
00778     function _storeAllStyles()
00779     {
00780         $this->_storeStyle();
00781     }
00782 
00789     function _storeExterns()
00790     {
00791         // Create EXTERNCOUNT with number of worksheets
00792         $this->_storeExterncount(count($this->_worksheets));
00793 
00794         // Create EXTERNSHEET for each worksheet
00795         foreach ($this->_sheetnames as $sheetname) {
00796             $this->_storeExternsheet($sheetname);
00797         }
00798     }
00799 
00805     function _storeNames()
00806     {
00807         // Create the print area NAME records
00808         $total_worksheets = count($this->_worksheets);
00809         for ($i = 0; $i < $total_worksheets; $i++) {
00810             // Write a Name record if the print area has been defined
00811             if (isset($this->_worksheets[$i]->print_rowmin)) {
00812                 $this->_storeNameShort(
00813                     $this->_worksheets[$i]->index,
00814                     0x06, // NAME type
00815                     $this->_worksheets[$i]->print_rowmin,
00816                     $this->_worksheets[$i]->print_rowmax,
00817                     $this->_worksheets[$i]->print_colmin,
00818                     $this->_worksheets[$i]->print_colmax
00819                     );
00820             }
00821         }
00822 
00823         // Create the print title NAME records
00824         $total_worksheets = count($this->_worksheets);
00825         for ($i = 0; $i < $total_worksheets; $i++) {
00826             $rowmin = $this->_worksheets[$i]->title_rowmin;
00827             $rowmax = $this->_worksheets[$i]->title_rowmax;
00828             $colmin = $this->_worksheets[$i]->title_colmin;
00829             $colmax = $this->_worksheets[$i]->title_colmax;
00830 
00831             // Determine if row + col, row, col or nothing has been defined
00832             // and write the appropriate record
00833             //
00834             if (isset($rowmin) && isset($colmin)) {
00835                 // Row and column titles have been defined.
00836                 // Row title has been defined.
00837                 $this->_storeNameLong(
00838                     $this->_worksheets[$i]->index,
00839                     0x07, // NAME type
00840                     $rowmin,
00841                     $rowmax,
00842                     $colmin,
00843                     $colmax
00844                     );
00845             } elseif (isset($rowmin)) {
00846                 // Row title has been defined.
00847                 $this->_storeNameShort(
00848                     $this->_worksheets[$i]->index,
00849                     0x07, // NAME type
00850                     $rowmin,
00851                     $rowmax,
00852                     0x00,
00853                     0xff
00854                     );
00855             } elseif (isset($colmin)) {
00856                 // Column title has been defined.
00857                 $this->_storeNameShort(
00858                     $this->_worksheets[$i]->index,
00859                     0x07, // NAME type
00860                     0x0000,
00861                     0x3fff,
00862                     $colmin,
00863                     $colmax
00864                     );
00865             } else {
00866                 // Print title hasn't been defined.
00867             }
00868         }
00869     }
00870 
00871 
00872 
00873 
00874     /******************************************************************************
00875     *
00876     * BIFF RECORDS
00877     *
00878     */
00879 
00885     function _storeCodepage()
00886     {
00887         $record          = 0x0042;             // Record identifier
00888         $length          = 0x0002;             // Number of bytes to follow
00889         $cv              = $this->_codepage;   // The code page
00890 
00891         $header          = pack('vv', $record, $length);
00892         $data            = pack('v',  $cv);
00893 
00894         $this->_append($header . $data);
00895     }
00896 
00902     function _storeWindow1()
00903     {
00904         $record    = 0x003D;                 // Record identifier
00905         $length    = 0x0012;                 // Number of bytes to follow
00906 
00907         $xWn       = 0x0000;                 // Horizontal position of window
00908         $yWn       = 0x0000;                 // Vertical position of window
00909         $dxWn      = 0x25BC;                 // Width of window
00910         $dyWn      = 0x1572;                 // Height of window
00911 
00912         $grbit     = 0x0038;                 // Option flags
00913         $ctabsel   = $this->_selected;       // Number of workbook tabs selected
00914         $wTabRatio = 0x0258;                 // Tab to scrollbar ratio
00915 
00916         $itabFirst = $this->_firstsheet;     // 1st displayed worksheet
00917         $itabCur   = $this->_activesheet;    // Active worksheet
00918 
00919         $header    = pack("vv",        $record, $length);
00920         $data      = pack("vvvvvvvvv", $xWn, $yWn, $dxWn, $dyWn,
00921                                        $grbit,
00922                                        $itabCur, $itabFirst,
00923                                        $ctabsel, $wTabRatio);
00924         $this->_append($header . $data);
00925     }
00926 
00935     function _storeBoundsheet($sheetname,$offset)
00936     {
00937         $record    = 0x0085;                    // Record identifier
00938         if ($this->_BIFF_version == 0x0600) {
00939             $length    = 0x08 + strlen($sheetname); // Number of bytes to follow
00940         } else {
00941             $length = 0x07 + strlen($sheetname); // Number of bytes to follow
00942         }
00943 
00944         $grbit     = 0x0000;                    // Visibility and sheet type
00945         $cch       = strlen($sheetname);        // Length of sheet name
00946 
00947         $header    = pack("vv",  $record, $length);
00948         if ($this->_BIFF_version == 0x0600) {
00949             $data      = pack("Vvv", $offset, $grbit, $cch);
00950         } else {
00951             $data      = pack("VvC", $offset, $grbit, $cch);
00952         }
00953         $this->_append($header.$data.$sheetname);
00954     }
00955 
00961     function _storeSupbookInternal()
00962     {
00963         $record    = 0x01AE;   // Record identifier
00964         $length    = 0x0004;   // Bytes to follow
00965 
00966         $header    = pack("vv", $record, $length);
00967         $data      = pack("vv", count($this->_worksheets), 0x0104);
00968         $this->_append($header . $data);
00969     }
00970 
00978     function _storeExternsheetBiff8()
00979     {
00980         $total_references = count($this->_parser->_references);
00981         $record   = 0x0017;                     // Record identifier
00982         $length   = 2 + 6 * $total_references;  // Number of bytes to follow
00983 
00984         $supbook_index = 0;           // FIXME: only using internal SUPBOOK record
00985         $header           = pack("vv",  $record, $length);
00986         $data             = pack('v', $total_references);
00987         for ($i = 0; $i < $total_references; $i++) {
00988             $data .= $this->_parser->_references[$i];
00989         }
00990         $this->_append($header . $data);
00991     }
00992 
00998     function _storeStyle()
00999     {
01000         $record    = 0x0293;   // Record identifier
01001         $length    = 0x0004;   // Bytes to follow
01002 
01003         $ixfe      = 0x8000;   // Index to style XF
01004         $BuiltIn   = 0x00;     // Built-in style
01005         $iLevel    = 0xff;     // Outline style level
01006 
01007         $header    = pack("vv",  $record, $length);
01008         $data      = pack("vCC", $ixfe, $BuiltIn, $iLevel);
01009         $this->_append($header . $data);
01010     }
01011 
01012 
01020     function _storeNumFormat($format, $ifmt)
01021     {
01022         $record    = 0x041E;                      // Record identifier
01023 
01024         if ($this->_BIFF_version == 0x0600) {
01025             $length    = 5 + strlen($format);      // Number of bytes to follow
01026             $encoding = 0x0;
01027         } elseif ($this->_BIFF_version == 0x0500) {
01028             $length    = 3 + strlen($format);      // Number of bytes to follow
01029         }
01030 
01031         $cch       = strlen($format);             // Length of format string
01032 
01033         $header    = pack("vv", $record, $length);
01034         if ($this->_BIFF_version == 0x0600) {
01035             $data      = pack("vvC", $ifmt, $cch, $encoding);
01036         } elseif ($this->_BIFF_version == 0x0500) {
01037             $data      = pack("vC", $ifmt, $cch);
01038         }
01039         $this->_append($header . $data . $format);
01040     }
01041 
01047     function _storeDatemode()
01048     {
01049         $record    = 0x0022;         // Record identifier
01050         $length    = 0x0002;         // Bytes to follow
01051 
01052         $f1904     = $this->_1904;   // Flag for 1904 date system
01053 
01054         $header    = pack("vv", $record, $length);
01055         $data      = pack("v", $f1904);
01056         $this->_append($header . $data);
01057     }
01058 
01059 
01073     function _storeExterncount($cxals)
01074     {
01075         $record   = 0x0016;          // Record identifier
01076         $length   = 0x0002;          // Number of bytes to follow
01077 
01078         $header   = pack("vv", $record, $length);
01079         $data     = pack("v",  $cxals);
01080         $this->_append($header . $data);
01081     }
01082 
01083 
01094     function _storeExternsheet($sheetname)
01095     {
01096         $record      = 0x0017;                     // Record identifier
01097         $length      = 0x02 + strlen($sheetname);  // Number of bytes to follow
01098 
01099         $cch         = strlen($sheetname);         // Length of sheet name
01100         $rgch        = 0x03;                       // Filename encoding
01101 
01102         $header      = pack("vv",  $record, $length);
01103         $data        = pack("CC", $cch, $rgch);
01104         $this->_append($header . $data . $sheetname);
01105     }
01106 
01107 
01120     function _storeNameShort($index, $type, $rowmin, $rowmax, $colmin, $colmax)
01121     {
01122         $record          = 0x0018;       // Record identifier
01123         $length          = 0x0024;       // Number of bytes to follow
01124 
01125         $grbit           = 0x0020;       // Option flags
01126         $chKey           = 0x00;         // Keyboard shortcut
01127         $cch             = 0x01;         // Length of text name
01128         $cce             = 0x0015;       // Length of text definition
01129         $ixals           = $index + 1;   // Sheet index
01130         $itab            = $ixals;       // Equal to ixals
01131         $cchCustMenu     = 0x00;         // Length of cust menu text
01132         $cchDescription  = 0x00;         // Length of description text
01133         $cchHelptopic    = 0x00;         // Length of help topic text
01134         $cchStatustext   = 0x00;         // Length of status bar text
01135         $rgch            = $type;        // Built-in name type
01136 
01137         $unknown03       = 0x3b;
01138         $unknown04       = 0xffff-$index;
01139         $unknown05       = 0x0000;
01140         $unknown06       = 0x0000;
01141         $unknown07       = 0x1087;
01142         $unknown08       = 0x8005;
01143 
01144         $header             = pack("vv", $record, $length);
01145         $data               = pack("v", $grbit);
01146         $data              .= pack("C", $chKey);
01147         $data              .= pack("C", $cch);
01148         $data              .= pack("v", $cce);
01149         $data              .= pack("v", $ixals);
01150         $data              .= pack("v", $itab);
01151         $data              .= pack("C", $cchCustMenu);
01152         $data              .= pack("C", $cchDescription);
01153         $data              .= pack("C", $cchHelptopic);
01154         $data              .= pack("C", $cchStatustext);
01155         $data              .= pack("C", $rgch);
01156         $data              .= pack("C", $unknown03);
01157         $data              .= pack("v", $unknown04);
01158         $data              .= pack("v", $unknown05);
01159         $data              .= pack("v", $unknown06);
01160         $data              .= pack("v", $unknown07);
01161         $data              .= pack("v", $unknown08);
01162         $data              .= pack("v", $index);
01163         $data              .= pack("v", $index);
01164         $data              .= pack("v", $rowmin);
01165         $data              .= pack("v", $rowmax);
01166         $data              .= pack("C", $colmin);
01167         $data              .= pack("C", $colmax);
01168         $this->_append($header . $data);
01169     }
01170 
01171 
01186     function _storeNameLong($index, $type, $rowmin, $rowmax, $colmin, $colmax)
01187     {
01188         $record          = 0x0018;       // Record identifier
01189         $length          = 0x003d;       // Number of bytes to follow
01190         $grbit           = 0x0020;       // Option flags
01191         $chKey           = 0x00;         // Keyboard shortcut
01192         $cch             = 0x01;         // Length of text name
01193         $cce             = 0x002e;       // Length of text definition
01194         $ixals           = $index + 1;   // Sheet index
01195         $itab            = $ixals;       // Equal to ixals
01196         $cchCustMenu     = 0x00;         // Length of cust menu text
01197         $cchDescription  = 0x00;         // Length of description text
01198         $cchHelptopic    = 0x00;         // Length of help topic text
01199         $cchStatustext   = 0x00;         // Length of status bar text
01200         $rgch            = $type;        // Built-in name type
01201 
01202         $unknown01       = 0x29;
01203         $unknown02       = 0x002b;
01204         $unknown03       = 0x3b;
01205         $unknown04       = 0xffff-$index;
01206         $unknown05       = 0x0000;
01207         $unknown06       = 0x0000;
01208         $unknown07       = 0x1087;
01209         $unknown08       = 0x8008;
01210 
01211         $header             = pack("vv",  $record, $length);
01212         $data               = pack("v", $grbit);
01213         $data              .= pack("C", $chKey);
01214         $data              .= pack("C", $cch);
01215         $data              .= pack("v", $cce);
01216         $data              .= pack("v", $ixals);
01217         $data              .= pack("v", $itab);
01218         $data              .= pack("C", $cchCustMenu);
01219         $data              .= pack("C", $cchDescription);
01220         $data              .= pack("C", $cchHelptopic);
01221         $data              .= pack("C", $cchStatustext);
01222         $data              .= pack("C", $rgch);
01223         $data              .= pack("C", $unknown01);
01224         $data              .= pack("v", $unknown02);
01225         // Column definition
01226         $data              .= pack("C", $unknown03);
01227         $data              .= pack("v", $unknown04);
01228         $data              .= pack("v", $unknown05);
01229         $data              .= pack("v", $unknown06);
01230         $data              .= pack("v", $unknown07);
01231         $data              .= pack("v", $unknown08);
01232         $data              .= pack("v", $index);
01233         $data              .= pack("v", $index);
01234         $data              .= pack("v", 0x0000);
01235         $data              .= pack("v", 0x3fff);
01236         $data              .= pack("C", $colmin);
01237         $data              .= pack("C", $colmax);
01238         // Row definition
01239         $data              .= pack("C", $unknown03);
01240         $data              .= pack("v", $unknown04);
01241         $data              .= pack("v", $unknown05);
01242         $data              .= pack("v", $unknown06);
01243         $data              .= pack("v", $unknown07);
01244         $data              .= pack("v", $unknown08);
01245         $data              .= pack("v", $index);
01246         $data              .= pack("v", $index);
01247         $data              .= pack("v", $rowmin);
01248         $data              .= pack("v", $rowmax);
01249         $data              .= pack("C", 0x00);
01250         $data              .= pack("C", 0xff);
01251         // End of data
01252         $data              .= pack("C", 0x10);
01253         $this->_append($header . $data);
01254     }
01255 
01261     function _storeCountry()
01262     {
01263         $record          = 0x008C;    // Record identifier
01264         $length          = 4;         // Number of bytes to follow
01265 
01266         $header = pack('vv',  $record, $length);
01267         /* using the same country code always for simplicity */
01268         $data = pack('vv', $this->_country_code, $this->_country_code);
01269         $this->_append($header . $data);
01270     }
01271 
01277     function _storePalette()
01278     {
01279         $aref            = $this->_palette;
01280 
01281         $record          = 0x0092;                 // Record identifier
01282         $length          = 2 + 4 * count($aref);   // Number of bytes to follow
01283         $ccv             =         count($aref);   // Number of RGB values to follow
01284         $data = '';                                // The RGB data
01285 
01286         // Pack the RGB data
01287         foreach ($aref as $color) {
01288             foreach ($color as $byte) {
01289                 $data .= pack("C",$byte);
01290             }
01291         }
01292 
01293         $header = pack("vvv",  $record, $length, $ccv);
01294         $this->_append($header . $data);
01295     }
01296 
01319     function _calculateSharedStringsSizes()
01320     {
01321         /* Iterate through the strings to calculate the CONTINUE block sizes.
01322            For simplicity we use the same size for the SST and CONTINUE records:
01323            8228 : Maximum Excel97 block size
01324              -4 : Length of block header
01325              -8 : Length of additional SST header information
01326              -8 : Arbitrary number to keep within _add_continue() limit
01327          = 8208
01328         */
01329         $continue_limit     = 8208;
01330         $block_length       = 0;
01331         $written            = 0;
01332         $this->_block_sizes = array();
01333         $continue           = 0;
01334 
01335         foreach (array_keys($this->_str_table) as $string) {
01336             $string_length = strlen($string);
01337             $headerinfo    = unpack("vlength/Cencoding", $string);
01338             $encoding      = $headerinfo["encoding"];
01339             $split_string  = 0;
01340 
01341             // Block length is the total length of the strings that will be
01342             // written out in a single SST or CONTINUE block.
01343             $block_length += $string_length;
01344 
01345             // We can write the string if it doesn't cross a CONTINUE boundary
01346             if ($block_length < $continue_limit) {
01347                 $written      += $string_length;
01348                 continue;
01349             }
01350 
01351             // Deal with the cases where the next string to be written will exceed
01352             // the CONTINUE boundary. If the string is very long it may need to be
01353             // written in more than one CONTINUE record.
01354             while ($block_length >= $continue_limit) {
01355 
01356                 // We need to avoid the case where a string is continued in the first
01357                 // n bytes that contain the string header information.
01358                 $header_length   = 3; // Min string + header size -1
01359                 $space_remaining = $continue_limit - $written - $continue;
01360 
01361 
01362                 /* TODO: Unicode data should only be split on char (2 byte)
01363                 boundaries. Therefore, in some cases we need to reduce the
01364                 amount of available
01365                 */
01366                 $align = 0;
01367 
01368                 # Only applies to Unicode strings
01369                 if ($encoding == 1) {
01370                     # Min string + header size -1
01371                     $header_length = 4;
01372 
01373                     if ($space_remaining > $header_length) {
01374                         # String contains 3 byte header => split on odd boundary
01375                         if (!$split_string && $space_remaining % 2 != 1) {
01376                             $space_remaining--;
01377                             $align = 1;
01378                         }
01379                         # Split section without header => split on even boundary
01380                         else if ($split_string && $space_remaining % 2 == 1) {
01381                             $space_remaining--;
01382                             $align = 1;
01383                         }
01384 
01385                         $split_string = 1;
01386                     }
01387                 }
01388 
01389 
01390                 if ($space_remaining > $header_length) {
01391                     // Write as much as possible of the string in the current block
01392                     $written      += $space_remaining;
01393 
01394                     // Reduce the current block length by the amount written
01395                     $block_length -= $continue_limit - $continue - $align;
01396 
01397                     // Store the max size for this block
01398                     $this->_block_sizes[] = $continue_limit - $align;
01399 
01400                     // If the current string was split then the next CONTINUE block
01401                     // should have the string continue flag (grbit) set unless the
01402                     // split string fits exactly into the remaining space.
01403                     if ($block_length > 0) {
01404                         $continue = 1;
01405                     } else {
01406                         $continue = 0;
01407                     }
01408                 } else {
01409                     // Store the max size for this block
01410                     $this->_block_sizes[] = $written + $continue;
01411 
01412                     // Not enough space to start the string in the current block
01413                     $block_length -= $continue_limit - $space_remaining - $continue;
01414                     $continue = 0;
01415 
01416                 }
01417 
01418                 // If the string (or substr) is small enough we can write it in the
01419                 // new CONTINUE block. Else, go through the loop again to write it in
01420                 // one or more CONTINUE blocks
01421                 if ($block_length < $continue_limit) {
01422                     $written = $block_length;
01423                 } else {
01424                     $written = 0;
01425                 }
01426             }
01427         }
01428 
01429         // Store the max size for the last block unless it is empty
01430         if ($written + $continue) {
01431             $this->_block_sizes[] = $written + $continue;
01432         }
01433 
01434 
01435         /* Calculate the total length of the SST and associated CONTINUEs (if any).
01436          The SST record will have a length even if it contains no strings.
01437          This length is required to set the offsets in the BOUNDSHEET records since
01438          they must be written before the SST records
01439         */
01440 
01441         $tmp_block_sizes = array();
01442         $tmp_block_sizes = $this->_block_sizes;
01443 
01444         $length  = 12;
01445         if (!empty($tmp_block_sizes)) {
01446             $length += array_shift($tmp_block_sizes); # SST
01447         }
01448         while (!empty($tmp_block_sizes)) {
01449             $length += 4 + array_shift($tmp_block_sizes); # CONTINUEs
01450         }
01451 
01452         return $length;
01453     }
01454 
01466     function _storeSharedStringsTable()
01467     {
01468         $record  = 0x00fc;  // Record identifier
01469         $length  = 0x0008;  // Number of bytes to follow
01470         $total   = 0x0000;
01471 
01472         // Iterate through the strings to calculate the CONTINUE block sizes
01473         $continue_limit = 8208;
01474         $block_length   = 0;
01475         $written        = 0;
01476         $continue       = 0;
01477 
01478         // sizes are upside down
01479         $tmp_block_sizes = $this->_block_sizes;
01480 //        $tmp_block_sizes = array_reverse($this->_block_sizes);
01481 
01482         # The SST record is required even if it contains no strings. Thus we will
01483         # always have a length
01484         #
01485         if (!empty($tmp_block_sizes)) {
01486             $length = 8 + array_shift($tmp_block_sizes);
01487         }
01488         else {
01489             # No strings
01490             $length = 8;
01491         }
01492 
01493 
01494 
01495         // Write the SST block header information
01496         $header      = pack("vv", $record, $length);
01497         $data        = pack("VV", $this->_str_total, $this->_str_unique);
01498         $this->_append($header . $data);
01499 
01500 
01501 
01502 
01503         /* TODO: not good for performance */
01504         foreach (array_keys($this->_str_table) as $string) {
01505 
01506             $string_length = strlen($string);
01507             $headerinfo    = unpack("vlength/Cencoding", $string);
01508             $encoding      = $headerinfo["encoding"];
01509             $split_string  = 0;
01510 
01511             // Block length is the total length of the strings that will be
01512             // written out in a single SST or CONTINUE block.
01513             //
01514             $block_length += $string_length;
01515 
01516 
01517             // We can write the string if it doesn't cross a CONTINUE boundary
01518             if ($block_length < $continue_limit) {
01519                 $this->_append($string);
01520                 $written += $string_length;
01521                 continue;
01522             }
01523 
01524             // Deal with the cases where the next string to be written will exceed
01525             // the CONTINUE boundary. If the string is very long it may need to be
01526             // written in more than one CONTINUE record.
01527             //
01528             while ($block_length >= $continue_limit) {
01529 
01530                 // We need to avoid the case where a string is continued in the first
01531                 // n bytes that contain the string header information.
01532                 //
01533                 $header_length   = 3; // Min string + header size -1
01534                 $space_remaining = $continue_limit - $written - $continue;
01535 
01536 
01537                 // Unicode data should only be split on char (2 byte) boundaries.
01538                 // Therefore, in some cases we need to reduce the amount of available
01539                 // space by 1 byte to ensure the correct alignment.
01540                 $align = 0;
01541 
01542                 // Only applies to Unicode strings
01543                 if ($encoding == 1) {
01544                     // Min string + header size -1
01545                     $header_length = 4;
01546 
01547                     if ($space_remaining > $header_length) {
01548                         // String contains 3 byte header => split on odd boundary
01549                         if (!$split_string && $space_remaining % 2 != 1) {
01550                             $space_remaining--;
01551                             $align = 1;
01552                         }
01553                         // Split section without header => split on even boundary
01554                         else if ($split_string && $space_remaining % 2 == 1) {
01555                             $space_remaining--;
01556                             $align = 1;
01557                         }
01558 
01559                         $split_string = 1;
01560                     }
01561                 }
01562 
01563 
01564                 if ($space_remaining > $header_length) {
01565                     // Write as much as possible of the string in the current block
01566                     $tmp = substr($string, 0, $space_remaining);
01567                     $this->_append($tmp);
01568 
01569                     // The remainder will be written in the next block(s)
01570                     $string = substr($string, $space_remaining);
01571 
01572                     // Reduce the current block length by the amount written
01573                     $block_length -= $continue_limit - $continue - $align;
01574 
01575                     // If the current string was split then the next CONTINUE block
01576                     // should have the string continue flag (grbit) set unless the
01577                     // split string fits exactly into the remaining space.
01578                     //
01579                     if ($block_length > 0) {
01580                         $continue = 1;
01581                     } else {
01582                         $continue = 0;
01583                     }
01584                 } else {
01585                     // Not enough space to start the string in the current block
01586                     $block_length -= $continue_limit - $space_remaining - $continue;
01587                     $continue = 0;
01588                 }
01589 
01590                 // Write the CONTINUE block header
01591                 if (!empty($this->_block_sizes)) {
01592                     $record  = 0x003C;
01593                     $length  = array_shift($tmp_block_sizes);
01594 
01595                     $header  = pack('vv', $record, $length);
01596                     if ($continue) {
01597                         $header .= pack('C', $encoding);
01598                     }
01599                     $this->_append($header);
01600                 }
01601 
01602                 // If the string (or substr) is small enough we can write it in the
01603                 // new CONTINUE block. Else, go through the loop again to write it in
01604                 // one or more CONTINUE blocks
01605                 //
01606                 if ($block_length < $continue_limit) {
01607                     $this->_append($string);
01608                     $written = $block_length;
01609                 } else {
01610                     $written = 0;
01611                 }
01612             }
01613         }
01614     }
01615 }
01616 ?>
 All Data Structures Namespaces Files Functions Variables Enumerations