• Main Page
  • Related Pages
  • Modules
  • Namespaces
  • Data Structures
  • Files
  • File List
  • Globals

classes/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('Format.php');
00036 require_once('classes/Spreadsheet/Excel/Writer/BIFFwriter.php');
00037 require_once('classes/Spreadsheet/Excel/Writer/Worksheet.php');
00038 require_once('classes/Spreadsheet/Excel/Writer/Parser.php');
00039 require_once('classes/OLE/PPS/Root.php');
00040 require_once('classes/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 
00174     function Spreadsheet_Excel_Writer_Workbook($filename)
00175     {
00176         // It needs to call its parent's constructor explicitly
00177         $this->Spreadsheet_Excel_Writer_BIFFwriter();
00178     
00179         $this->_filename         = $filename;
00180         $this->_parser           =& new Spreadsheet_Excel_Writer_Parser($this->_byte_order, $this->_BIFF_version);
00181         $this->_1904             = 0;
00182         $this->_activesheet      = 0;
00183         $this->_firstsheet       = 0;
00184         $this->_selected         = 0;
00185         $this->_xf_index         = 16; // 15 style XF's and 1 cell XF.
00186         $this->_fileclosed       = 0;
00187         $this->_biffsize         = 0;
00188         $this->_sheetname        = "Sheet";
00189         $this->_tmp_format       =& new Spreadsheet_Excel_Writer_Format($this->_BIFF_version);
00190         $this->_worksheets       = array();
00191         $this->_sheetnames       = array();
00192         $this->_formats          = array();
00193         $this->_palette          = array();
00194         $this->_codepage         = 0x04E4; // FIXME: should change for BIFF8
00195         $this->_country_code     = -1;
00196     
00197         // Add the default format for hyperlinks
00198         $this->_url_format =& $this->addFormat(array('color' => 'blue', 'underline' => 1));
00199         $this->_str_total       = 0;
00200         $this->_str_unique      = 0;
00201         $this->_str_table       = array();
00202         $this->_setPaletteXl97();
00203         $this->_tmp_dir         = '';
00204     }
00205     
00213     function close()
00214     {
00215         if ($this->_fileclosed) { // Prevent close() from being called twice.
00216             return true;
00217         }
00218         $res = $this->_storeWorkbook();
00219         if ($this->isError($res)) {
00220             return $this->raiseError($res->getMessage());
00221         }
00222         $this->_fileclosed = 1;
00223         return true;
00224     }
00225  
00235     function sheets()
00236     {
00237         return $this->worksheets();
00238     }
00239     
00247     function worksheets()
00248     {
00249         return $this->_worksheets;
00250     }
00251  
00262     function setVersion($version)
00263     {
00264         if ($version == 8) { // only accept version 8
00265             $version = 0x0600;
00266             $this->_BIFF_version = $version;
00267             // change BIFFwriter limit for CONTINUE records
00268             $this->_limit = 8224;
00269             $this->_tmp_format->_BIFF_version = $version;
00270             $this->_url_format->_BIFF_version = $version;
00271             $this->_parser->_BIFF_version = $version;
00272             $total_worksheets = count($this->_worksheets);
00273             // change version for all worksheets too
00274             for ($i = 0; $i < $total_worksheets; $i++) {
00275                 $this->_worksheets[$i]->_BIFF_version = $version;
00276             }
00277             $total_formats = count($this->_formats);
00278             // change version for all formats too
00279             for ($i = 0; $i < $total_formats; $i++) {
00280                 $this->_formats[$i]->_BIFF_version = $version;
00281             }
00282         }
00283     }
00284 
00292     function setCountry($code)
00293     {
00294         $this->_country_code = $code;
00295     }
00296 
00307     function &addWorksheet($name = '')
00308     {
00309         $index     = count($this->_worksheets);
00310         $sheetname = $this->_sheetname;
00311     
00312         if ($name == '') {
00313             $name = $sheetname.($index+1); 
00314         }
00315     
00316         // Check that sheetname is <= 31 chars (Excel limit).
00317         if (strlen($name) > 31) {
00318             return $this->raiseError("Sheetname $name must be <= 31 chars");
00319         }
00320     
00321         // Check that the worksheet name doesn't already exist: a fatal Excel error.
00322         $total_worksheets = count($this->_worksheets);
00323         for ($i=0; $i < $total_worksheets; $i++)
00324         {
00325             if ($name == $this->_worksheets[$i]->getName()) {
00326                 return $this->raiseError("Worksheet '$name' already exists");
00327             }
00328         }
00329     
00330         $worksheet = new Spreadsheet_Excel_Writer_Worksheet($this->_BIFF_version,
00331                                    $name, $index,
00332                                    $this->_activesheet, $this->_firstsheet,
00333                                    $this->_str_total, $this->_str_unique,
00334                                    $this->_str_table, $this->_url_format,
00335                                    $this->_parser);
00336                                                                                                                                          
00337         $this->_worksheets[$index] = &$worksheet;    // Store ref for iterator
00338         $this->_sheetnames[$index] = $name;          // Store EXTERNSHEET names
00339         $this->_parser->setExtSheet($name, $index);  // Register worksheet name with parser
00340         return $worksheet;
00341     }
00342     
00351     function &addFormat($properties = array())
00352     {
00353         $format = new Spreadsheet_Excel_Writer_Format($this->_BIFF_version, $this->_xf_index,$properties);
00354         $this->_xf_index += 1;
00355         $this->_formats[] = &$format;
00356         return $format;
00357     }
00358     
00365     function &addValidator()
00366     {
00367         include_once('Validator.php');
00368         /* FIXME: check for successful inclusion*/
00369         $valid = new Spreadsheet_Excel_Writer_Validator($this->_parser);
00370         return $valid;
00371     }
00372 
00383     function setCustomColor($index,$red,$green,$blue)
00384     {
00385         // Match a HTML #xxyyzz style parameter
00386         /*if (defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) {
00387             @_ = ($_[0], hex $1, hex $2, hex $3);
00388         }*/
00389     
00390         // Check that the colour index is the right range
00391         if ($index < 8 or $index > 64) {
00392             // TODO: assign real error codes
00393             return $this->raiseError("Color index $index outside range: 8 <= index <= 64");
00394         }
00395     
00396         // Check that the colour components are in the right range
00397         if ( ($red   < 0 or $red   > 255) or
00398              ($green < 0 or $green > 255) or
00399              ($blue  < 0 or $blue  > 255) )  
00400         {
00401             return $this->raiseError("Color component outside range: 0 <= color <= 255");
00402         }
00403     
00404         $index -= 8; // Adjust colour index (wingless dragonfly)
00405         
00406         // Set the RGB value
00407         $this->_palette[$index] = array($red, $green, $blue, 0);
00408         return($index + 8);
00409     }
00410     
00416     function _setPaletteXl97()
00417     {
00418         $this->_palette = array(
00419                            array(0x00, 0x00, 0x00, 0x00),   // 8
00420                            array(0xff, 0xff, 0xff, 0x00),   // 9
00421                            array(0xff, 0x00, 0x00, 0x00),   // 10
00422                            array(0x00, 0xff, 0x00, 0x00),   // 11
00423                            array(0x00, 0x00, 0xff, 0x00),   // 12
00424                            array(0xff, 0xff, 0x00, 0x00),   // 13
00425                            array(0xff, 0x00, 0xff, 0x00),   // 14
00426                            array(0x00, 0xff, 0xff, 0x00),   // 15
00427                            array(0x80, 0x00, 0x00, 0x00),   // 16
00428                            array(0x00, 0x80, 0x00, 0x00),   // 17
00429                            array(0x00, 0x00, 0x80, 0x00),   // 18
00430                            array(0x80, 0x80, 0x00, 0x00),   // 19
00431                            array(0x80, 0x00, 0x80, 0x00),   // 20
00432                            array(0x00, 0x80, 0x80, 0x00),   // 21
00433                            array(0xc0, 0xc0, 0xc0, 0x00),   // 22
00434                            array(0x80, 0x80, 0x80, 0x00),   // 23
00435                            array(0x99, 0x99, 0xff, 0x00),   // 24
00436                            array(0x99, 0x33, 0x66, 0x00),   // 25
00437                            array(0xff, 0xff, 0xcc, 0x00),   // 26
00438                            array(0xcc, 0xff, 0xff, 0x00),   // 27
00439                            array(0x66, 0x00, 0x66, 0x00),   // 28
00440                            array(0xff, 0x80, 0x80, 0x00),   // 29
00441                            array(0x00, 0x66, 0xcc, 0x00),   // 30
00442                            array(0xcc, 0xcc, 0xff, 0x00),   // 31
00443                            array(0x00, 0x00, 0x80, 0x00),   // 32
00444                            array(0xff, 0x00, 0xff, 0x00),   // 33
00445                            array(0xff, 0xff, 0x00, 0x00),   // 34
00446                            array(0x00, 0xff, 0xff, 0x00),   // 35
00447                            array(0x80, 0x00, 0x80, 0x00),   // 36
00448                            array(0x80, 0x00, 0x00, 0x00),   // 37
00449                            array(0x00, 0x80, 0x80, 0x00),   // 38
00450                            array(0x00, 0x00, 0xff, 0x00),   // 39
00451                            array(0x00, 0xcc, 0xff, 0x00),   // 40
00452                            array(0xcc, 0xff, 0xff, 0x00),   // 41
00453                            array(0xcc, 0xff, 0xcc, 0x00),   // 42
00454                            array(0xff, 0xff, 0x99, 0x00),   // 43
00455                            array(0x99, 0xcc, 0xff, 0x00),   // 44
00456                            array(0xff, 0x99, 0xcc, 0x00),   // 45
00457                            array(0xcc, 0x99, 0xff, 0x00),   // 46
00458                            array(0xff, 0xcc, 0x99, 0x00),   // 47
00459                            array(0x33, 0x66, 0xff, 0x00),   // 48
00460                            array(0x33, 0xcc, 0xcc, 0x00),   // 49
00461                            array(0x99, 0xcc, 0x00, 0x00),   // 50
00462                            array(0xff, 0xcc, 0x00, 0x00),   // 51
00463                            array(0xff, 0x99, 0x00, 0x00),   // 52
00464                            array(0xff, 0x66, 0x00, 0x00),   // 53
00465                            array(0x66, 0x66, 0x99, 0x00),   // 54
00466                            array(0x96, 0x96, 0x96, 0x00),   // 55
00467                            array(0x00, 0x33, 0x66, 0x00),   // 56
00468                            array(0x33, 0x99, 0x66, 0x00),   // 57
00469                            array(0x00, 0x33, 0x00, 0x00),   // 58
00470                            array(0x33, 0x33, 0x00, 0x00),   // 59
00471                            array(0x99, 0x33, 0x00, 0x00),   // 60
00472                            array(0x99, 0x33, 0x66, 0x00),   // 61
00473                            array(0x33, 0x33, 0x99, 0x00),   // 62
00474                            array(0x33, 0x33, 0x33, 0x00),   // 63
00475                          );
00476     }
00477     
00485     function _storeWorkbook()
00486     {
00487         // Ensure that at least one worksheet has been selected.
00488         if ($this->_activesheet == 0) {
00489             $this->_worksheets[0]->selected = 1;
00490         }
00491     
00492         // Calculate the number of selected worksheet tabs and call the finalization
00493         // methods for each worksheet
00494         $total_worksheets = count($this->_worksheets);
00495         for ($i=0; $i < $total_worksheets; $i++) {
00496             if ($this->_worksheets[$i]->selected) {
00497                 $this->_selected++;
00498             }
00499             $this->_worksheets[$i]->close($this->_sheetnames);
00500         }
00501     
00502         // Add Workbook globals
00503         $this->_storeBof(0x0005);
00504         if ($this->_BIFF_version == 0x0600) {
00505             $this->_storeCodepage();
00506             $this->_storeWindow1();
00507         }
00508         if ($this->_BIFF_version == 0x0500) {
00509             $this->_storeExterns();    // For print area and repeat rows
00510         }
00511         $this->_storeNames();      // For print area and repeat rows
00512         if ($this->_BIFF_version == 0x0500) {
00513             $this->_storeWindow1();
00514         }
00515         $this->_storeDatemode();
00516         $this->_storeAllFonts();
00517         $this->_storeAllNumFormats();
00518         $this->_storeAllXfs();
00519         $this->_storeAllStyles();
00520         $this->_storePalette();
00521         $this->_calcSheetOffsets();
00522     
00523         // Add BOUNDSHEET records
00524         for ($i=0; $i < $total_worksheets; $i++) {
00525             $this->_storeBoundsheet($this->_worksheets[$i]->name,$this->_worksheets[$i]->offset);
00526         }
00527  
00528         if ($this->_country_code != -1) {
00529             $this->_storeCountry();
00530         }
00531 
00532         if ($this->_BIFF_version == 0x0600) {
00533             //$this->_storeSupbookInternal();
00534             /* TODO: store external SUPBOOK records and XCT and CRN records 
00535             in case of external references for BIFF8 */
00536             //$this->_storeExternsheetBiff8();
00537             $this->_storeSharedStringsTable();
00538         }
00539 
00540         // End Workbook globals
00541         $this->_storeEof();
00542     
00543         // Store the workbook in an OLE container
00544         $res = $this->_storeOLEFile();
00545         if ($this->isError($res)) {
00546             return $this->raiseError($res->getMessage());
00547         }
00548         return true;
00549     }
00550  
00558     function setTempDir($dir)
00559     {
00560         if (is_dir($dir)) {
00561             $this->_tmp_dir = $dir;
00562             return true;
00563         }
00564         return false;
00565     }
00566 
00573     function _storeOLEFile()
00574     {
00575         $OLE = new OLE_PPS_File(OLE::Asc2Ucs('Book'));
00576         if ($this->_tmp_dir != '') {
00577             $OLE->setTempDir($this->_tmp_dir);
00578         }
00579         $res = $OLE->init();
00580         if ($this->isError($res)) {
00581             return $this->raiseError("OLE Error: ".$res->getMessage());
00582         }
00583         $OLE->append($this->_data);
00584         $total_worksheets = count($this->_worksheets);
00585         for ($i = 0; $i < $total_worksheets; $i++)
00586         {
00587             while ($tmp = $this->_worksheets[$i]->getData()) {
00588                 $OLE->append($tmp);
00589             }
00590         }
00591         $root = new OLE_PPS_Root(time(), time(), array($OLE));
00592         if ($this->_tmp_dir != '') {
00593             $root->setTempDir($this->_tmp_dir);
00594         }
00595         $res = $root->save($this->_filename);
00596         if ($this->isError($res)) {
00597             return $this->raiseError("OLE Error: ".$res->getMessage());
00598         }
00599         return true;
00600     }
00601  
00607     function _calcSheetOffsets()
00608     {
00609         if ($this->_BIFF_version == 0x0600) {
00610             $boundsheet_length = 12;  // fixed length for a BOUNDSHEET record
00611         }
00612         else {
00613             $boundsheet_length = 11;
00614         }
00615         $EOF               = 4;
00616         $offset            = $this->_datasize;
00617 
00618         if ($this->_BIFF_version == 0x0600) {
00619             // add the length of the SST
00620             /* TODO: check this works for a lot of strings (> 8224 bytes) */
00621             $offset += $this->_calculateSharedStringsSizes();
00622             if ($this->_country_code != -1) {
00623                 $offset += 8; // adding COUNTRY record
00624             }
00625             // add the lenght of SUPBOOK, EXTERNSHEET and NAME records
00626             //$offset += 8; // FIXME: calculate real value when storing the records
00627         }
00628         $total_worksheets = count($this->_worksheets);
00629         // add the length of the BOUNDSHEET records 
00630         for ($i=0; $i < $total_worksheets; $i++) {
00631             $offset += $boundsheet_length + strlen($this->_worksheets[$i]->name);
00632         }
00633         $offset += $EOF;
00634 
00635         for ($i=0; $i < $total_worksheets; $i++) {
00636             $this->_worksheets[$i]->offset = $offset;
00637             $offset += $this->_worksheets[$i]->_datasize;
00638         }
00639         $this->_biffsize = $offset;
00640     }
00641     
00647     function _storeAllFonts()
00648     {
00649         // tmp_format is added by the constructor. We use this to write the default XF's
00650         $format = $this->_tmp_format;
00651         $font   = $format->getFont();
00652     
00653         // Note: Fonts are 0-indexed. According to the SDK there is no index 4,
00654         // so the following fonts are 0, 1, 2, 3, 5
00655         //
00656         for ($i=1; $i <= 5; $i++){
00657             $this->_append($font);
00658         }
00659     
00660         // Iterate through the XF objects and write a FONT record if it isn't the
00661         // same as the default FONT and if it hasn't already been used.
00662         //
00663         $fonts = array();
00664         $index = 6;                  // The first user defined FONT
00665     
00666         $key = $format->getFontKey(); // The default font from _tmp_format
00667         $fonts[$key] = 0;             // Index of the default font
00668 
00669         $total_formats = count($this->_formats);
00670         for ($i=0; $i < $total_formats; $i++)
00671         {
00672             $key = $this->_formats[$i]->getFontKey();
00673             if (isset($fonts[$key])) {
00674                 // FONT has already been used
00675                 $this->_formats[$i]->font_index = $fonts[$key];
00676             }
00677             else {
00678                 // Add a new FONT record
00679                 $fonts[$key]        = $index;
00680                 $this->_formats[$i]->font_index = $index;
00681                 $index++;
00682                 $font = $this->_formats[$i]->getFont();
00683                 $this->_append($font);
00684             }
00685         }
00686     }
00687     
00693     function _storeAllNumFormats()
00694     {
00695         // Leaning num_format syndrome
00696         $hash_num_formats = array();
00697         $num_formats      = array();
00698         $index = 164;
00699     
00700         // Iterate through the XF objects and write a FORMAT record if it isn't a
00701         // built-in format type and if the FORMAT string hasn't already been used.
00702         $total_formats = count($this->_formats);
00703         for ($i=0; $i < $total_formats; $i++)
00704         {
00705             $num_format = $this->_formats[$i]->_num_format;
00706     
00707             // Check if $num_format is an index to a built-in format.
00708             // Also check for a string of zeros, which is a valid format string
00709             // but would evaluate to zero.
00710             //
00711             if (!preg_match("/^0+\d/",$num_format))
00712             {
00713                 if (preg_match("/^\d+$/",$num_format)) { // built-in format
00714                     continue;
00715                 }
00716             }
00717     
00718             if (isset($hash_num_formats[$num_format])) {
00719                 // FORMAT has already been used
00720                 $this->_formats[$i]->_num_format = $hash_num_formats[$num_format];
00721             }
00722             else{
00723                 // Add a new FORMAT
00724                 $hash_num_formats[$num_format]  = $index;
00725                 $this->_formats[$i]->_num_format = $index;
00726                 array_push($num_formats,$num_format);
00727                 $index++;
00728             }
00729         }
00730     
00731         // Write the new FORMAT records starting from 0xA4
00732         $index = 164;
00733         foreach ($num_formats as $num_format) {
00734             $this->_storeNumFormat($num_format,$index);
00735             $index++;
00736         }
00737     }
00738     
00744     function _storeAllXfs()
00745     {
00746         // _tmp_format is added by the constructor. We use this to write the default XF's
00747         // The default font index is 0
00748         //
00749         $format = $this->_tmp_format;
00750         for ($i=0; $i <= 14; $i++) {
00751             $xf = $format->getXf('style'); // Style XF
00752             $this->_append($xf);
00753         }
00754     
00755         $xf = $format->getXf('cell');      // Cell XF
00756         $this->_append($xf);
00757     
00758         // User defined XFs
00759         $total_formats = count($this->_formats);
00760         for ($i=0; $i < $total_formats; $i++) {
00761             $xf = $this->_formats[$i]->getXf('cell');
00762             $this->_append($xf);
00763         }
00764     }
00765     
00771     function _storeAllStyles()
00772     {
00773         $this->_storeStyle();
00774     }
00775     
00782     function _storeExterns()
00783     {
00784         // Create EXTERNCOUNT with number of worksheets
00785         $this->_storeExterncount(count($this->_worksheets));
00786     
00787         // Create EXTERNSHEET for each worksheet
00788         foreach ($this->_sheetnames as $sheetname) {
00789             $this->_storeExternsheet($sheetname);
00790         }
00791     }
00792     
00798     function _storeNames()
00799     {
00800         // Create the print area NAME records
00801         $total_worksheets = count($this->_worksheets);
00802         for ($i = 0; $i < $total_worksheets; $i++) {
00803             // Write a Name record if the print area has been defined
00804             if (isset($this->_worksheets[$i]->print_rowmin))
00805             {
00806                 $this->_storeNameShort(
00807                     $this->_worksheets[$i]->index,
00808                     0x06, // NAME type
00809                     $this->_worksheets[$i]->print_rowmin,
00810                     $this->_worksheets[$i]->print_rowmax,
00811                     $this->_worksheets[$i]->print_colmin,
00812                     $this->_worksheets[$i]->print_colmax
00813                     );
00814             }
00815         }
00816     
00817         // Create the print title NAME records
00818         $total_worksheets = count($this->_worksheets);
00819         for ($i = 0; $i < $total_worksheets; $i++) {
00820             $rowmin = $this->_worksheets[$i]->title_rowmin;
00821             $rowmax = $this->_worksheets[$i]->title_rowmax;
00822             $colmin = $this->_worksheets[$i]->title_colmin;
00823             $colmax = $this->_worksheets[$i]->title_colmax;
00824     
00825             // Determine if row + col, row, col or nothing has been defined
00826             // and write the appropriate record
00827             //
00828             if (isset($rowmin) and isset($colmin)) {
00829                 // Row and column titles have been defined.
00830                 // Row title has been defined.
00831                 $this->_storeNameLong(
00832                     $this->_worksheets[$i]->index,
00833                     0x07, // NAME type
00834                     $rowmin,
00835                     $rowmax,
00836                     $colmin,
00837                     $colmax
00838                     );
00839             }
00840             elseif (isset($rowmin)) {
00841                 // Row title has been defined.
00842                 $this->_storeNameShort(
00843                     $this->_worksheets[$i]->index,
00844                     0x07, // NAME type
00845                     $rowmin,
00846                     $rowmax,
00847                     0x00,
00848                     0xff
00849                     );
00850             }
00851             elseif (isset($colmin)) {
00852                 // Column title has been defined.
00853                 $this->_storeNameShort(
00854                     $this->_worksheets[$i]->index,
00855                     0x07, // NAME type
00856                     0x0000,
00857                     0x3fff,
00858                     $colmin,
00859                     $colmax
00860                     );
00861             }
00862             else {
00863                 // Print title hasn't been defined.
00864             }
00865         }
00866     }
00867     
00868     
00869     
00870     
00871     /******************************************************************************
00872     *
00873     * BIFF RECORDS
00874     *
00875     */
00876     
00882     function _storeCodepage()
00883     {
00884         $record          = 0x0042;             // Record identifier
00885         $length          = 0x0002;             // Number of bytes to follow
00886         $cv              = $this->_codepage;   // The code page
00887  
00888         $header          = pack('vv', $record, $length);
00889         $data            = pack('v',  $cv);
00890  
00891         $this->_append($header.$data);
00892     }
00893 
00899     function _storeWindow1()
00900     {
00901         $record    = 0x003D;                 // Record identifier
00902         $length    = 0x0012;                 // Number of bytes to follow
00903     
00904         $xWn       = 0x0000;                 // Horizontal position of window
00905         $yWn       = 0x0000;                 // Vertical position of window
00906         $dxWn      = 0x25BC;                 // Width of window
00907         $dyWn      = 0x1572;                 // Height of window
00908     
00909         $grbit     = 0x0038;                 // Option flags
00910         $ctabsel   = $this->_selected;       // Number of workbook tabs selected
00911         $wTabRatio = 0x0258;                 // Tab to scrollbar ratio
00912     
00913         $itabFirst = $this->_firstsheet;     // 1st displayed worksheet
00914         $itabCur   = $this->_activesheet;    // Active worksheet
00915     
00916         $header    = pack("vv",        $record, $length);
00917         $data      = pack("vvvvvvvvv", $xWn, $yWn, $dxWn, $dyWn,
00918                                        $grbit,
00919                                        $itabCur, $itabFirst,
00920                                        $ctabsel, $wTabRatio);
00921         $this->_append($header.$data);
00922     }
00923     
00932     function _storeBoundsheet($sheetname,$offset)
00933     {
00934         $record    = 0x0085;                    // Record identifier
00935         if ($this->_BIFF_version == 0x0600) {
00936             $length    = 0x08 + strlen($sheetname); // Number of bytes to follow
00937         }
00938         else {
00939             $length = 0x07 + strlen($sheetname); // Number of bytes to follow
00940         }
00941     
00942         $grbit     = 0x0000;                    // Visibility and sheet type
00943         $cch       = strlen($sheetname);        // Length of sheet name
00944     
00945         $header    = pack("vv",  $record, $length);
00946         if ($this->_BIFF_version == 0x0600) {
00947             $data      = pack("Vvv", $offset, $grbit, $cch);
00948         }
00949         else {
00950             $data      = pack("VvC", $offset, $grbit, $cch);
00951         }
00952         $this->_append($header.$data.$sheetname);
00953     }
00954  
00960     function _storeSupbookInternal()
00961     {
00962         $record    = 0x01AE;   // Record identifier
00963         $length    = 0x0004;   // Bytes to follow
00964                                
00965         $header    = pack("vv", $record, $length);
00966         $data      = pack("vv", count($this->_worksheets), 0x0104);
00967         $this->_append($header.$data);
00968     }
00969 
00977     function _storeExternsheetBiff8()
00978     {
00979         $total_references = count($this->_parser->_references);
00980         $record   = 0x0017;                     // Record identifier
00981         $length   = 2 + 6 * $total_references;  // Number of bytes to follow
00982 
00983         $supbook_index = 0;           // FIXME: only using internal SUPBOOK record
00984         $header           = pack("vv",  $record, $length);
00985         $data             = pack('v', $total_references);
00986         for ($i = 0; $i < $total_references; $i++) {
00987             $data .= $this->_parser->_references[$i];
00988         }
00989         $this->_append($header.$data);
00990     }
00991 
00997     function _storeStyle()
00998     {
00999         $record    = 0x0293;   // Record identifier
01000         $length    = 0x0004;   // Bytes to follow
01001                                
01002         $ixfe      = 0x8000;   // Index to style XF
01003         $BuiltIn   = 0x00;     // Built-in style
01004         $iLevel    = 0xff;     // Outline style level
01005     
01006         $header    = pack("vv",  $record, $length);
01007         $data      = pack("vCC", $ixfe, $BuiltIn, $iLevel);
01008         $this->_append($header.$data);
01009     }
01010     
01011     
01019     function _storeNumFormat($format,$ifmt)
01020     {
01021         $record    = 0x041E;                      // Record identifier
01022 
01023         if ($this->_BIFF_version == 0x0600) {
01024             $length    = 5 + strlen($format);      // Number of bytes to follow
01025             $encoding = 0x0;
01026         }
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         }
01037         elseif ($this->_BIFF_version == 0x0500) {
01038             $data      = pack("vC", $ifmt, $cch);
01039         }
01040         $this->_append($header.$data.$format);
01041     }
01042     
01048     function _storeDatemode()
01049     {
01050         $record    = 0x0022;         // Record identifier
01051         $length    = 0x0002;         // Bytes to follow
01052     
01053         $f1904     = $this->_1904;   // Flag for 1904 date system
01054     
01055         $header    = pack("vv", $record, $length);
01056         $data      = pack("v", $f1904);
01057         $this->_append($header.$data);
01058     }
01059     
01060     
01074     function _storeExterncount($cxals)
01075     {
01076         $record   = 0x0016;          // Record identifier
01077         $length   = 0x0002;          // Number of bytes to follow
01078     
01079         $header   = pack("vv", $record, $length);
01080         $data     = pack("v",  $cxals);
01081         $this->_append($header.$data);
01082     }
01083     
01084     
01095     function _storeExternsheet($sheetname)
01096     {
01097         $record      = 0x0017;                     // Record identifier
01098         $length      = 0x02 + strlen($sheetname);  // Number of bytes to follow
01099                                                    
01100         $cch         = strlen($sheetname);         // Length of sheet name
01101         $rgch        = 0x03;                       // Filename encoding
01102     
01103         $header      = pack("vv",  $record, $length);
01104         $data        = pack("CC", $cch, $rgch);
01105         $this->_append($header.$data.$sheetname);
01106     }
01107     
01108     
01121     function _storeNameShort($index,$type,$rowmin,$rowmax,$colmin,$colmax)
01122     {
01123         $record          = 0x0018;       // Record identifier
01124         $length          = 0x0024;       // Number of bytes to follow
01125     
01126         $grbit           = 0x0020;       // Option flags
01127         $chKey           = 0x00;         // Keyboard shortcut
01128         $cch             = 0x01;         // Length of text name
01129         $cce             = 0x0015;       // Length of text definition
01130         $ixals           = $index + 1;   // Sheet index
01131         $itab            = $ixals;       // Equal to ixals
01132         $cchCustMenu     = 0x00;         // Length of cust menu text
01133         $cchDescription  = 0x00;         // Length of description text
01134         $cchHelptopic    = 0x00;         // Length of help topic text
01135         $cchStatustext   = 0x00;         // Length of status bar text
01136         $rgch            = $type;        // Built-in name type
01137     
01138         $unknown03       = 0x3b;
01139         $unknown04       = 0xffff-$index;
01140         $unknown05       = 0x0000;
01141         $unknown06       = 0x0000;
01142         $unknown07       = 0x1087;
01143         $unknown08       = 0x8005;
01144     
01145         $header             = pack("vv", $record, $length);
01146         $data               = pack("v", $grbit);
01147         $data              .= pack("C", $chKey);
01148         $data              .= pack("C", $cch);
01149         $data              .= pack("v", $cce);
01150         $data              .= pack("v", $ixals);
01151         $data              .= pack("v", $itab);
01152         $data              .= pack("C", $cchCustMenu);
01153         $data              .= pack("C", $cchDescription);
01154         $data              .= pack("C", $cchHelptopic);
01155         $data              .= pack("C", $cchStatustext);
01156         $data              .= pack("C", $rgch);
01157         $data              .= pack("C", $unknown03);
01158         $data              .= pack("v", $unknown04);
01159         $data              .= pack("v", $unknown05);
01160         $data              .= pack("v", $unknown06);
01161         $data              .= pack("v", $unknown07);
01162         $data              .= pack("v", $unknown08);
01163         $data              .= pack("v", $index);
01164         $data              .= pack("v", $index);
01165         $data              .= pack("v", $rowmin);
01166         $data              .= pack("v", $rowmax);
01167         $data              .= pack("C", $colmin);
01168         $data              .= pack("C", $colmax);
01169         $this->_append($header.$data);
01170     }
01171     
01172     
01187     function _storeNameLong($index,$type,$rowmin,$rowmax,$colmin,$colmax)
01188     {
01189         $record          = 0x0018;       // Record identifier
01190         $length          = 0x003d;       // Number of bytes to follow
01191         $grbit           = 0x0020;       // Option flags
01192         $chKey           = 0x00;         // Keyboard shortcut
01193         $cch             = 0x01;         // Length of text name
01194         $cce             = 0x002e;       // Length of text definition
01195         $ixals           = $index + 1;   // Sheet index
01196         $itab            = $ixals;       // Equal to ixals
01197         $cchCustMenu     = 0x00;         // Length of cust menu text
01198         $cchDescription  = 0x00;         // Length of description text
01199         $cchHelptopic    = 0x00;         // Length of help topic text
01200         $cchStatustext   = 0x00;         // Length of status bar text
01201         $rgch            = $type;        // Built-in name type
01202     
01203         $unknown01       = 0x29;
01204         $unknown02       = 0x002b;
01205         $unknown03       = 0x3b;
01206         $unknown04       = 0xffff-$index;
01207         $unknown05       = 0x0000;
01208         $unknown06       = 0x0000;
01209         $unknown07       = 0x1087;
01210         $unknown08       = 0x8008;
01211     
01212         $header             = pack("vv",  $record, $length);
01213         $data               = pack("v", $grbit);
01214         $data              .= pack("C", $chKey);
01215         $data              .= pack("C", $cch);
01216         $data              .= pack("v", $cce);
01217         $data              .= pack("v", $ixals);
01218         $data              .= pack("v", $itab);
01219         $data              .= pack("C", $cchCustMenu);
01220         $data              .= pack("C", $cchDescription);
01221         $data              .= pack("C", $cchHelptopic);
01222         $data              .= pack("C", $cchStatustext);
01223         $data              .= pack("C", $rgch);
01224         $data              .= pack("C", $unknown01);
01225         $data              .= pack("v", $unknown02);
01226         // Column definition
01227         $data              .= pack("C", $unknown03);
01228         $data              .= pack("v", $unknown04);
01229         $data              .= pack("v", $unknown05);
01230         $data              .= pack("v", $unknown06);
01231         $data              .= pack("v", $unknown07);
01232         $data              .= pack("v", $unknown08);
01233         $data              .= pack("v", $index);
01234         $data              .= pack("v", $index);
01235         $data              .= pack("v", 0x0000);
01236         $data              .= pack("v", 0x3fff);
01237         $data              .= pack("C", $colmin);
01238         $data              .= pack("C", $colmax);
01239         // Row definition
01240         $data              .= pack("C", $unknown03);
01241         $data              .= pack("v", $unknown04);
01242         $data              .= pack("v", $unknown05);
01243         $data              .= pack("v", $unknown06);
01244         $data              .= pack("v", $unknown07);
01245         $data              .= pack("v", $unknown08);
01246         $data              .= pack("v", $index);
01247         $data              .= pack("v", $index);
01248         $data              .= pack("v", $rowmin);
01249         $data              .= pack("v", $rowmax);
01250         $data              .= pack("C", 0x00);
01251         $data              .= pack("C", 0xff);
01252         // End of data
01253         $data              .= pack("C", 0x10);
01254         $this->_append($header.$data);
01255     }
01256 
01262     function _storeCountry()
01263     {
01264         $record          = 0x008C;    // Record identifier
01265         $length          = 4;         // Number of bytes to follow
01266 
01267         $header = pack('vv',  $record, $length);
01268         /* using the same country code always for simplicity */
01269         $data = pack('vv', $this->_country_code, $this->_country_code);
01270         $this->_append($header.$data);
01271     }
01272 
01278     function _storePalette()
01279     {
01280         $aref            = $this->_palette;
01281     
01282         $record          = 0x0092;                 // Record identifier
01283         $length          = 2 + 4 * count($aref);   // Number of bytes to follow
01284         $ccv             =         count($aref);   // Number of RGB values to follow
01285         $data = '';                                // The RGB data
01286     
01287         // Pack the RGB data
01288         foreach($aref as $color)
01289         {
01290             foreach($color as $byte) {
01291                 $data .= pack("C",$byte);
01292             }
01293         }
01294     
01295         $header = pack("vvv",  $record, $length, $ccv);
01296         $this->_append($header.$data);
01297     }
01298 
01309     function _calculateSharedStringsSizes()
01310     {
01311         /* Iterate through the strings to calculate the CONTINUE block sizes.
01312          The SST blocks requires a specialised CONTINUE block, so we have to
01313          ensure that the maximum data block size is less than the limit used by
01314          _add_continue() in BIFFwriter.pm. For simplicity we use the same size
01315          for the SST and CONTINUE records:
01316            8228 : Maximum Excel97 block size
01317              -4 : Length of block header
01318              -8 : Length of additional SST header information
01319              -8 : Arbitrary number to keep within _add_continue() limit
01320          = 8208
01321         */
01322         $total_offset       = 12;
01323         $continue_limit     = 8208;
01324         $block_length       = 0;
01325         $written            = 0;
01326         $this->_block_sizes = array();
01327         $continue           = 0;
01328 
01329         foreach (array_keys($this->_str_table) as $string) {
01330             $string_length = strlen($string);
01331  
01332             // Block length is the total length of the strings that will be
01333             // written out in a single SST or CONTINUE block.
01334             $block_length += $string_length;
01335  
01336             // We can write the string if it doesn't cross a CONTINUE boundary
01337             if ($block_length < $continue_limit) {
01338                 $written      += $string_length;
01339                 $total_offset += $string_length;
01340                 continue;
01341             }
01342  
01343             // Deal with the cases where the next string to be written will exceed
01344             // the CONTINUE boundary. If the string is very long it may need to be
01345             // written in more than one CONTINUE record.
01346             while ($block_length >= $continue_limit) {
01347  
01348                 // We need to avoid the case where a string is continued in the first
01349                 // n bytes that contain the string header information.
01350                 $header_length   = 3; // Min string + header size -1
01351                 $space_remaining = $continue_limit - $written - $continue;
01352  
01353  
01354                 /* TODO: Unicode data should only be split on char (2 byte)
01355                 boundaries. Therefore, in some cases we need to reduce the
01356                 amount of available
01357                 */
01358  
01359                 if ($space_remaining > $header_length) {
01360                     // Write as much as possible of the string in the current block
01361                     $written      += $space_remaining;
01362  
01363                     // Reduce the current block length by the amount written
01364                     $block_length -= $continue_limit + $continue;
01365  
01366                     // Store the max size for this block
01367                     $this->_block_sizes[] = $continue_limit;
01368  
01369                     // If the current string was split then the next CONTINUE block
01370                     // should have the string continue flag (grbit) set unless the
01371                     // split string fits exactly into the remaining space.
01372                     if ($block_length > 0) {
01373                         $continue = 1;
01374                     }
01375                     else {
01376                         $continue = 0;
01377                     }
01378  
01379                 }
01380                 else {
01381                     // Store the max size for this block
01382                     $this->_block_sizes[] = $written + $continue;
01383  
01384                     // Not enough space to start the string in the current block
01385                     $block_length -= $continue_limit - $space_remaining - $continue;
01386                     $continue = 0;
01387  
01388                 }
01389  
01390                 // If the string (or substr) is small enough we can write it in the
01391                 // new CONTINUE block. Else, go through the loop again to write it in
01392                 // one or more CONTINUE blocks
01393                 if ($block_length < $continue_limit) {
01394                     $written = $block_length;
01395                 }
01396                 else {
01397                     $written = 0;
01398                 }
01399             }
01400         }
01401 
01402         // Store the max size for the last block unless it is empty
01403         if ($written + $continue) {
01404             $this->_block_sizes[] = $written + $continue;
01405         }
01406  
01407  
01408         /* Calculate the total length of the SST and associated CONTINUEs (if any).
01409          The SST record will have a length even if it contains no strings.
01410          This length is required to set the offsets in the BOUNDSHEET records since
01411          they must be written before the SST records
01412         */
01413         if (!empty($this->_block_sizes)) {
01414             $total_offset += (count($this->_block_sizes) - 1) * 4; // add CONTINUE headers
01415         }
01416         return $total_offset;
01417     }
01418 
01430     /* FIXME: update _calcSheetOffsets() when updating this method */
01431     function _storeSharedStringsTable()
01432     {
01433         $record  = 0x00fc;  // Record identifier
01434         $length  = 8 + array_sum($this->_block_sizes); // Number of bytes to follow
01435 
01436         // Write the SST block header information
01437         $header      = pack("vv", $record, $length);
01438         $data        = pack("VV", $this->_str_total, $this->_str_unique);
01439         $this->_append($header.$data);
01440 
01441 
01442         // Iterate through the strings to calculate the CONTINUE block sizes
01443         $continue_limit = 8208;
01444         $block_length   = 0;
01445         $written        = 0;
01446         $continue       = 0;
01447 
01448 
01449         /* TODO: not good for performance */
01450         foreach (array_keys($this->_str_table) as $string) {
01451 
01452             $string_length = strlen($string);
01453             $encoding      = 0; // assume there are no Unicode strings
01454             $split_string  = 0;
01455  
01456             // Block length is the total length of the strings that will be
01457             // written out in a single SST or CONTINUE block.
01458             //
01459             $block_length += $string_length;
01460  
01461  
01462             // We can write the string if it doesn't cross a CONTINUE boundary
01463             if ($block_length < $continue_limit) {
01464                 $this->_append($string);
01465                 $written += $string_length;
01466                 continue;
01467             }
01468  
01469             // Deal with the cases where the next string to be written will exceed
01470             // the CONTINUE boundary. If the string is very long it may need to be
01471             // written in more than one CONTINUE record.
01472             // 
01473             while ($block_length >= $continue_limit) {
01474  
01475                 // We need to avoid the case where a string is continued in the first
01476                 // n bytes that contain the string header information.
01477                 //
01478                 $header_length   = 3; // Min string + header size -1
01479                 $space_remaining = $continue_limit - $written - $continue;
01480  
01481  
01482                 // Unicode data should only be split on char (2 byte) boundaries.
01483                 // Therefore, in some cases we need to reduce the amount of available
01484  
01485                 if ($space_remaining > $header_length) {
01486                     // Write as much as possible of the string in the current block
01487                     $tmp = substr($string, 0, $space_remaining);
01488                     $this->_append($tmp);
01489  
01490                     // The remainder will be written in the next block(s)
01491                     $string = substr($string, $space_remaining);
01492  
01493                     // Reduce the current block length by the amount written
01494                     $block_length -= $continue_limit - $continue;
01495  
01496                     // If the current string was split then the next CONTINUE block
01497                     // should have the string continue flag (grbit) set unless the
01498                     // split string fits exactly into the remaining space.
01499                     //
01500                     if ($block_length > 0) {
01501                         $continue = 1;
01502                     }
01503                     else {
01504                         $continue = 0;
01505                     }
01506                 }
01507                 else {
01508                     // Not enough space to start the string in the current block
01509                     $block_length -= $continue_limit - $space_remaining - $continue;
01510                     $continue = 0;
01511                 }
01512  
01513                 // Write the CONTINUE block header
01514                 if (!empty($this->_block_sizes)) {
01515                     $record  = 0x003C;
01516                     $length  = array_pop($this->_block_sizes);
01517  
01518                     $header  = pack('vv', $record, $length);
01519                     if ($continue) {
01520                         $header .= pack('C', $encoding);
01521                     }
01522                     $this->_append($header);
01523                 }
01524  
01525                 // If the string (or substr) is small enough we can write it in the
01526                 // new CONTINUE block. Else, go through the loop again to write it in
01527                 // one or more CONTINUE blocks
01528                 //
01529                 if ($block_length < $continue_limit) {
01530                     $this->_append($string);
01531  
01532                     $written = $block_length;
01533                 }
01534                 else {
01535                     $written = 0;
01536                 }
01537             }
01538         }
01539     }
01540 }
01541 ?>

Generated on Fri Dec 13 2013 13:52:09 for ILIAS Release_3_7_x_branch .rev 46817 by  doxygen 1.7.1