Source for file Workbook.php

Documentation is available at Workbook.php

  1. <?php
  2. /*
  3. *  Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
  4. *
  5. *  The majority of this is _NOT_ my code.  I simply ported it from the
  6. *  PERL Spreadsheet::WriteExcel module.
  7. *
  8. *  The author of the Spreadsheet::WriteExcel module is John McNamara
  9. *  <jmcnamara@cpan.org>
  10. *
  11. *  I _DO_ maintain this code, and John McNamara has nothing to do with the
  12. *  porting of this code to PHP.  Any questions directly related to this
  13. *  class library should be directed to me.
  14. *
  15. *  License Information:
  16. *
  17. *    Spreadsheet_Excel_Writer:  A library for generating Excel Spreadsheets
  18. *    Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
  19. *
  20. *    This library is free software; you can redistribute it and/or
  21. *    modify it under the terms of the GNU Lesser General Public
  22. *    License as published by the Free Software Foundation; either
  23. *    version 2.1 of the License, or (at your option) any later version.
  24. *
  25. *    This library is distributed in the hope that it will be useful,
  26. *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  27. *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  28. *    Lesser General Public License for more details.
  29. *
  30. *    You should have received a copy of the GNU Lesser General Public
  31. *    License along with this library; if not, write to the Free Software
  32. *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  33. */
  34.  
  35. require_once 'Spreadsheet/Excel/Writer/Format.php';
  36. require_once 'Spreadsheet/Excel/Writer/BIFFwriter.php';
  37. require_once 'Spreadsheet/Excel/Writer/Worksheet.php';
  38. require_once 'Spreadsheet/Excel/Writer/Parser.php';
  39. require_once 'OLE/PPS/Root.php';
  40. require_once 'OLE/PPS/File.php';
  41.  
  42. /**
  43. * Class for generating Excel Spreadsheets
  44. *
  45. @author   Xavier Noguer <xnoguer@rezebra.com>
  46. @category FileFormats
  47. @package  Spreadsheet_Excel_Writer
  48. */
  49.  
  50. {
  51.     /**
  52.     * Filename for the Workbook
  53.     * @var string 
  54.     */
  55.     var $_filename;
  56.  
  57.     /**
  58.     * Formula parser
  59.     * @var object Parser 
  60.     */
  61.     var $_parser;
  62.  
  63.     /**
  64.     * Flag for 1904 date system (0 => base date is 1900, 1 => base date is 1904)
  65.     * @var integer 
  66.     */
  67.     var $_1904;
  68.  
  69.     /**
  70.     * The active worksheet of the workbook (0 indexed)
  71.     * @var integer 
  72.     */
  73.     var $_activesheet;
  74.  
  75.     /**
  76.     * 1st displayed worksheet in the workbook (0 indexed)
  77.     * @var integer 
  78.     */
  79.     var $_firstsheet;
  80.  
  81.     /**
  82.     * Number of workbook tabs selected
  83.     * @var integer 
  84.     */
  85.     var $_selected;
  86.  
  87.     /**
  88.     * Index for creating adding new formats to the workbook
  89.     * @var integer 
  90.     */
  91.     var $_xf_index;
  92.  
  93.     /**
  94.     * Flag for preventing close from being called twice.
  95.     * @var integer 
  96.     * @see close()
  97.     */
  98.     var $_fileclosed;
  99.  
  100.     /**
  101.     * The BIFF file size for the workbook.
  102.     * @var integer 
  103.     * @see _calcSheetOffsets()
  104.     */
  105.     var $_biffsize;
  106.  
  107.     /**
  108.     * The default sheetname for all sheets created.
  109.     * @var string 
  110.     */
  111.     var $_sheetname;
  112.  
  113.     /**
  114.     * The default XF format.
  115.     * @var object Format 
  116.     */
  117.     var $_tmp_format;
  118.  
  119.     /**
  120.     * Array containing references to all of this workbook's worksheets
  121.     * @var array 
  122.     */
  123.     var $_worksheets;
  124.  
  125.     /**
  126.     * Array of sheetnames for creating the EXTERNSHEET records
  127.     * @var array 
  128.     */
  129.     var $_sheetnames;
  130.  
  131.     /**
  132.     * Array containing references to all of this workbook's formats
  133.     * @var array 
  134.     */
  135.     var $_formats;
  136.  
  137.     /**
  138.     * Array containing the colour palette
  139.     * @var array 
  140.     */
  141.     var $_palette;
  142.  
  143.     /**
  144.     * The default format for URLs.
  145.     * @var object Format 
  146.     */
  147.     var $_url_format;
  148.  
  149.     /**
  150.     * The codepage indicates the text encoding used for strings
  151.     * @var integer 
  152.     */
  153.     var $_codepage;
  154.  
  155.     /**
  156.     * The country code used for localization
  157.     * @var integer 
  158.     */
  159.     var $_country_code;
  160.  
  161.     /**
  162.     * number of bytes for sizeinfo of strings
  163.     * @var integer 
  164.     */
  165.  
  166.     /**
  167.     * Class constructor
  168.     *
  169.     * @param string filename for storing the workbook. "-" for writing to stdout.
  170.     * @access public
  171.     */
  172.     function Spreadsheet_Excel_Writer_Workbook($filename)
  173.     {
  174.         // It needs to call its parent's constructor explicitly
  175.         $this->Spreadsheet_Excel_Writer_BIFFwriter();
  176.  
  177.         $this->_filename         = $filename;
  178.         $this->_parser           =new Spreadsheet_Excel_Writer_Parser($this->_byte_order$this->_BIFF_version);
  179.         $this->_1904             = 0;
  180.         $this->_activesheet      = 0;
  181.         $this->_firstsheet       = 0;
  182.         $this->_selected         = 0;
  183.         $this->_xf_index         = 16// 15 style XF's and 1 cell XF.
  184.         $this->_fileclosed       = 0;
  185.         $this->_biffsize         = 0;
  186.         $this->_sheetname        = 'Sheet';
  187.         $this->_tmp_format       =new Spreadsheet_Excel_Writer_Format($this->_BIFF_version);
  188.         $this->_worksheets       = array();
  189.         $this->_sheetnames       = array();
  190.         $this->_formats          = array();
  191.         $this->_palette          = array();
  192.         $this->_codepage         = 0x04E4// FIXME: should change for BIFF8
  193.         $this->_country_code     = -1;
  194.         $this->_string_sizeinfo  3;
  195.  
  196.         // Add the default format for hyperlinks
  197.         $this->_url_format =$this->addFormat(array('color' => 'blue''underline' => 1));
  198.         $this->_str_total       0;
  199.         $this->_str_unique      0;
  200.         $this->_str_table       array();
  201.         $this->_setPaletteXl97();
  202.     }
  203.  
  204.     /**
  205.     * Calls finalization methods.
  206.     * This method should always be the last one to be called on every workbook
  207.     *
  208.     * @access public
  209.     * @return mixed true on success. PEAR_Error on failure
  210.     */
  211.     function close()
  212.     {
  213.         if ($this->_fileclosed// Prevent close() from being called twice.
  214.             return true;
  215.         }
  216.         $res $this->_storeWorkbook();
  217.         if ($this->isError($res)) {
  218.             return $this->raiseError($res->getMessage());
  219.         }
  220.         $this->_fileclosed = 1;
  221.         return true;
  222.     }
  223.  
  224.     /**
  225.     * An accessor for the _worksheets[] array
  226.     * Returns an array of the worksheet objects in a workbook
  227.     * It actually calls to worksheets()
  228.     *
  229.     * @access public
  230.     * @see worksheets()
  231.     * @return array 
  232.     */
  233.     function sheets()
  234.     {
  235.         return $this->worksheets();
  236.     }
  237.  
  238.     /**
  239.     * An accessor for the _worksheets[] array.
  240.     * Returns an array of the worksheet objects in a workbook
  241.     *
  242.     * @access public
  243.     * @return array 
  244.     */
  245.     function worksheets()
  246.     {
  247.         return $this->_worksheets;
  248.     }
  249.  
  250.     /**
  251.     * Sets the BIFF version.
  252.     * This method exists just to access experimental functionality
  253.     * from BIFF8. It will be deprecated !
  254.     * Only possible value is 8 (Excel 97/2000).
  255.     * For any other value it fails silently.
  256.     *
  257.     * @access public
  258.     * @param integer $version The BIFF version
  259.     */
  260.     function setVersion($version)
  261.     {
  262.         if ($version == 8// only accept version 8
  263.             $version 0x0600;
  264.             $this->_BIFF_version = $version;
  265.             // change BIFFwriter limit for CONTINUE records
  266.             $this->_limit = 8228;
  267.             $this->_tmp_format->_BIFF_version $version;
  268.             $this->_url_format->_BIFF_version $version;
  269.             $this->_parser->_BIFF_version $version;
  270.             $this->_codepage = 0x04B0;
  271.  
  272.             $total_worksheets count($this->_worksheets);
  273.             // change version for all worksheets too
  274.             for ($i 0$i $total_worksheets$i++{
  275.                 $this->_worksheets[$i]->_BIFF_version $version;
  276.             }
  277.  
  278.             $total_formats count($this->_formats);
  279.             // change version for all formats too
  280.             for ($i 0$i $total_formats$i++{
  281.                 $this->_formats[$i]->_BIFF_version $version;
  282.             }
  283.         }
  284.     }
  285.  
  286.     /**
  287.     * Set the country identifier for the workbook
  288.     *
  289.     * @access public
  290.     * @param integer $code Is the international calling country code for the
  291.     *                       chosen country.
  292.     */
  293.     function setCountry($code)
  294.     {
  295.         $this->_country_code = $code;
  296.     }
  297.  
  298.     /**
  299.     * Add a new worksheet to the Excel workbook.
  300.     * If no name is given the name of the worksheet will be Sheeti$i, with
  301.     * $i in [1..].
  302.     *
  303.     * @access public
  304.     * @param string $name the optional name of the worksheet
  305.     * @return mixed reference to a worksheet object on success, PEAR_Error
  306.     *                on failure
  307.     */
  308.     function &addWorksheet($name '')
  309.     {
  310.         $index     count($this->_worksheets);
  311.         $sheetname $this->_sheetname;
  312.  
  313.         if ($name == ''{
  314.             $name $sheetname.($index+1);
  315.         }
  316.  
  317.         // Check that sheetname is <= 31 chars (Excel limit before BIFF8).
  318.         if ($this->_BIFF_version != 0x0600)
  319.         {
  320.             if (strlen($name31{
  321.                 return $this->raiseError("Sheetname $name must be <= 31 chars");
  322.             }
  323.         }
  324.  
  325.         // Check that the worksheet name doesn't already exist: a fatal Excel error.
  326.         $total_worksheets count($this->_worksheets);
  327.         for ($i 0$i $total_worksheets$i++{
  328.             if ($this->_worksheets[$i]->getName(== $name{
  329.                 return $this->raiseError("Worksheet '$name' already exists");
  330.             }
  331.         }
  332.  
  333.         $worksheet new Spreadsheet_Excel_Writer_Worksheet($this->_BIFF_version,
  334.                                    $name$index,
  335.                                    $this->_activesheet$this->_firstsheet,
  336.                                    $this->_str_total$this->_str_unique,
  337.                                    $this->_str_table$this->_url_format,
  338.                                    $this->_parser$this->_tmp_dir);
  339.         
  340.         $this->_worksheets[$index&$worksheet;    // Store ref for iterator
  341.         $this->_sheetnames[$index$name;          // Store EXTERNSHEET names
  342.         $this->_parser->setExtSheet($name$index);  // Register worksheet name with parser
  343.         return $worksheet;
  344.     }
  345.  
  346.     /**
  347.     * Add a new format to the Excel workbook.
  348.     * Also, pass any properties to the Format constructor.
  349.     *
  350.     * @access public
  351.     * @param array $properties array with properties for initializing the format.
  352.     * @return &Spreadsheet_Excel_Writer_Format reference to an Excel Format
  353.     */
  354.     function &addFormat($properties array())
  355.     {
  356.         $format new Spreadsheet_Excel_Writer_Format($this->_BIFF_version$this->_xf_index$properties);
  357.         $this->_xf_index += 1;
  358.         $this->_formats[&$format;
  359.         return $format;
  360.     }
  361.  
  362.     /**
  363.      * Create new validator.
  364.      *
  365.      * @access public
  366.      * @return &Spreadsheet_Excel_Writer_Validator reference to a Validator
  367.      */
  368.     function &addValidator()
  369.     {
  370.         include_once 'Spreadsheet/Excel/Writer/Validator.php';
  371.         /* FIXME: check for successful inclusion*/
  372.         $valid new Spreadsheet_Excel_Writer_Validator($this->_parser);
  373.         return $valid;
  374.     }
  375.  
  376.     /**
  377.     * Change the RGB components of the elements in the colour palette.
  378.     *
  379.     * @access public
  380.     * @param integer $index colour index
  381.     * @param integer $red   red RGB value [0-255]
  382.     * @param integer $green green RGB value [0-255]
  383.     * @param integer $blue  blue RGB value [0-255]
  384.     * @return integer The palette index for the custom color
  385.     */
  386.     function setCustomColor($index$red$green$blue)
  387.     {
  388.         // Match a HTML #xxyyzz style parameter
  389.         /*if (defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) {
  390.             @_ = ($_[0], hex $1, hex $2, hex $3);
  391.         }*/
  392.  
  393.         // Check that the colour index is the right range
  394.         if ($index or $index 64{
  395.             // TODO: assign real error codes
  396.             return $this->raiseError("Color index $index outside range: 8 <= index <= 64");
  397.         }
  398.  
  399.         // Check that the colour components are in the right range
  400.         if (($red   or $red   255||
  401.             ($green or $green 255||
  402.             ($blue  or $blue  255))
  403.         {
  404.             return $this->raiseError("Color component outside range: 0 <= color <= 255");
  405.         }
  406.  
  407.         $index -= 8// Adjust colour index (wingless dragonfly)
  408.  
  409.         // Set the RGB value
  410.         $this->_palette[$indexarray($red$green$blue0);
  411.         return($index 8);
  412.     }
  413.  
  414.     /**
  415.     * Sets the colour palette to the Excel 97+ default.
  416.     *
  417.     * @access private
  418.     */
  419.     function _setPaletteXl97()
  420.     {
  421.         $this->_palette = array(
  422.                            array(0x000x000x000x00),   // 8
  423.                            array(0xff0xff0xff0x00),   // 9
  424.                            array(0xff0x000x000x00),   // 10
  425.                            array(0x000xff0x000x00),   // 11
  426.                            array(0x000x000xff0x00),   // 12
  427.                            array(0xff0xff0x000x00),   // 13
  428.                            array(0xff0x000xff0x00),   // 14
  429.                            array(0x000xff0xff0x00),   // 15
  430.                            array(0x800x000x000x00),   // 16
  431.                            array(0x000x800x000x00),   // 17
  432.                            array(0x000x000x800x00),   // 18
  433.                            array(0x800x800x000x00),   // 19
  434.                            array(0x800x000x800x00),   // 20
  435.                            array(0x000x800x800x00),   // 21
  436.                            array(0xc00xc00xc00x00),   // 22
  437.                            array(0x800x800x800x00),   // 23
  438.                            array(0x990x990xff0x00),   // 24
  439.                            array(0x990x330x660x00),   // 25
  440.                            array(0xff0xff0xcc0x00),   // 26
  441.                            array(0xcc0xff0xff0x00),   // 27
  442.                            array(0x660x000x660x00),   // 28
  443.                            array(0xff0x800x800x00),   // 29
  444.                            array(0x000x660xcc0x00),   // 30
  445.                            array(0xcc0xcc0xff0x00),   // 31
  446.                            array(0x000x000x800x00),   // 32
  447.                            array(0xff0x000xff0x00),   // 33
  448.                            array(0xff0xff0x000x00),   // 34
  449.                            array(0x000xff0xff0x00),   // 35
  450.                            array(0x800x000x800x00),   // 36
  451.                            array(0x800x000x000x00),   // 37
  452.                            array(0x000x800x800x00),   // 38
  453.                            array(0x000x000xff0x00),   // 39
  454.                            array(0x000xcc0xff0x00),   // 40
  455.                            array(0xcc0xff0xff0x00),   // 41
  456.                            array(0xcc0xff0xcc0x00),   // 42
  457.                            array(0xff0xff0x990x00),   // 43
  458.                            array(0x990xcc0xff0x00),   // 44
  459.                            array(0xff0x990xcc0x00),   // 45
  460.                            array(0xcc0x990xff0x00),   // 46
  461.                            array(0xff0xcc0x990x00),   // 47
  462.                            array(0x330x660xff0x00),   // 48
  463.                            array(0x330xcc0xcc0x00),   // 49
  464.                            array(0x990xcc0x000x00),   // 50
  465.                            array(0xff0xcc0x000x00),   // 51
  466.                            array(0xff0x990x000x00),   // 52
  467.                            array(0xff0x660x000x00),   // 53
  468.                            array(0x660x660x990x00),   // 54
  469.                            array(0x960x960x960x00),   // 55
  470.                            array(0x000x330x660x00),   // 56
  471.                            array(0x330x990x660x00),   // 57
  472.                            array(0x000x330x000x00),   // 58
  473.                            array(0x330x330x000x00),   // 59
  474.                            array(0x990x330x000x00),   // 60
  475.                            array(0x990x330x660x00),   // 61
  476.                            array(0x330x330x990x00),   // 62
  477.                            array(0x330x330x330x00),   // 63
  478.                          );
  479.     }
  480.  
  481.     /**
  482.     * Assemble worksheets into a workbook and send the BIFF data to an OLE
  483.     * storage.
  484.     *
  485.     * @access private
  486.     * @return mixed true on success. PEAR_Error on failure
  487.     */
  488.     function _storeWorkbook()
  489.     {
  490.         if (count($this->_worksheets== 0{
  491.             return true;
  492.         }
  493.  
  494.         // Ensure that at least one worksheet has been selected.
  495.         if ($this->_activesheet == 0{
  496.             $this->_worksheets[0]->selected 1;
  497.         }
  498.  
  499.         // Calculate the number of selected worksheet tabs and call the finalization
  500.         // methods for each worksheet
  501.         $total_worksheets count($this->_worksheets);
  502.         for ($i 0$i $total_worksheets$i++{
  503.             if ($this->_worksheets[$i]->selected{
  504.                 $this->_selected++;
  505.             }
  506.             $this->_worksheets[$i]->close($this->_sheetnames);
  507.         }
  508.  
  509.         // Add Workbook globals
  510.         $this->_storeBof(0x0005);
  511.         $this->_storeCodepage();
  512.         if ($this->_BIFF_version == 0x0600{
  513.             $this->_storeWindow1();
  514.         }
  515.         if ($this->_BIFF_version == 0x0500{
  516.             $this->_storeExterns();    // For print area and repeat rows
  517.         }
  518.         $this->_storeNames();      // For print area and repeat rows
  519.         if ($this->_BIFF_version == 0x0500{
  520.             $this->_storeWindow1();
  521.         }
  522.         $this->_storeDatemode();
  523.         $this->_storeAllFonts();
  524.         $this->_storeAllNumFormats();
  525.         $this->_storeAllXfs();
  526.         $this->_storeAllStyles();
  527.         $this->_storePalette();
  528.         $this->_calcSheetOffsets();
  529.  
  530.         // Add BOUNDSHEET records
  531.         for ($i 0$i $total_worksheets$i++{
  532.             $this->_storeBoundsheet($this->_worksheets[$i]->name,$this->_worksheets[$i]->offset);
  533.         }
  534.  
  535.         if ($this->_country_code != -1{
  536.             $this->_storeCountry();
  537.         }
  538.  
  539.         if ($this->_BIFF_version == 0x0600{
  540.             //$this->_storeSupbookInternal();
  541.             /* TODO: store external SUPBOOK records and XCT and CRN records
  542.             in case of external references for BIFF8 */
  543.             //$this->_storeExternsheetBiff8();
  544.             $this->_storeSharedStringsTable();
  545.         }
  546.  
  547.         // End Workbook globals
  548.         $this->_storeEof();
  549.  
  550.         // Store the workbook in an OLE container
  551.         $res $this->_storeOLEFile();
  552.         if ($this->isError($res)) {
  553.             return $this->raiseError($res->getMessage());
  554.         }
  555.         return true;
  556.     }
  557.  
  558.     /**
  559.     * Store the workbook in an OLE container
  560.     *
  561.     * @access private
  562.     * @return mixed true on success. PEAR_Error on failure
  563.     */
  564.     function _storeOLEFile()
  565.     {
  566.         if($this->_BIFF_version == 0x0600{
  567.             $OLE new OLE_PPS_File(OLE::Asc2Ucs('Workbook'));
  568.         else {
  569.             $OLE new OLE_PPS_File(OLE::Asc2Ucs('Book'));
  570.         }
  571.         if ($this->_tmp_dir != ''{
  572.             $OLE->setTempDir($this->_tmp_dir);
  573.         }
  574.         $res $OLE->init();
  575.         if ($this->isError($res)) {
  576.             return $this->raiseError("OLE Error: ".$res->getMessage());
  577.         }
  578.         $OLE->append($this->_data);
  579.  
  580.         $total_worksheets count($this->_worksheets);
  581.         for ($i 0$i $total_worksheets$i++{
  582.             while ($tmp $this->_worksheets[$i]->getData()) {
  583.                 $OLE->append($tmp);
  584.             }
  585.         }
  586.  
  587.         $root new OLE_PPS_Root(time()time()array($OLE));
  588.         if ($this->_tmp_dir != ''{
  589.             $root->setTempDir($this->_tmp_dir);
  590.         }
  591.  
  592.         $res $root->save($this->_filename);
  593.         if ($this->isError($res)) {
  594.             return $this->raiseError("OLE Error: ".$res->getMessage());
  595.         }
  596.         return true;
  597.     }
  598.  
  599.     /**
  600.     * Calculate offsets for Worksheet BOF records.
  601.     *
  602.     * @access private
  603.     */
  604.     function _calcSheetOffsets()
  605.     {
  606.         if ($this->_BIFF_version == 0x0600{
  607.             $boundsheet_length 12;  // fixed length for a BOUNDSHEET record
  608.         else {
  609.             $boundsheet_length 11;
  610.         }
  611.         $EOF               4;
  612.         $offset            $this->_datasize;
  613.  
  614.         if ($this->_BIFF_version == 0x0600{
  615.             // add the length of the SST
  616.             /* TODO: check this works for a lot of strings (> 8224 bytes) */
  617.             $offset += $this->_calculateSharedStringsSizes();
  618.             if ($this->_country_code != -1{
  619.                 $offset += 8// adding COUNTRY record
  620.             }
  621.             // add the lenght of SUPBOOK, EXTERNSHEET and NAME records
  622.             //$offset += 8; // FIXME: calculate real value when storing the records
  623.         }
  624.         $total_worksheets count($this->_worksheets);
  625.         // add the length of the BOUNDSHEET records
  626.         for ($i 0$i $total_worksheets$i++{
  627.             $offset += $boundsheet_length strlen($this->_worksheets[$i]->name);
  628.         }
  629.         $offset += $EOF;
  630.  
  631.         for ($i 0$i $total_worksheets$i++{
  632.             $this->_worksheets[$i]->offset $offset;
  633.             $offset += $this->_worksheets[$i]->_datasize;
  634.         }
  635.         $this->_biffsize = $offset;
  636.     }
  637.  
  638.     /**
  639.     * Store the Excel FONT records.
  640.     *
  641.     * @access private
  642.     */
  643.     function _storeAllFonts()
  644.     {
  645.         // tmp_format is added by the constructor. We use this to write the default XF's
  646.         $format $this->_tmp_format;
  647.         $font   $format->getFont();
  648.  
  649.         // Note: Fonts are 0-indexed. According to the SDK there is no index 4,
  650.         // so the following fonts are 0, 1, 2, 3, 5
  651.         //
  652.         for ($i 1$i <= 5$i++){
  653.             $this->_append($font);
  654.         }
  655.  
  656.         // Iterate through the XF objects and write a FONT record if it isn't the
  657.         // same as the default FONT and if it hasn't already been used.
  658.         //
  659.         $fonts array();
  660.         $index 6;                  // The first user defined FONT
  661.  
  662.         $key $format->getFontKey()// The default font from _tmp_format
  663.         $fonts[$key0;             // Index of the default font
  664.  
  665.         $total_formats count($this->_formats);
  666.         for ($i 0$i $total_formats$i++{
  667.             $key $this->_formats[$i]->getFontKey();
  668.             if (isset($fonts[$key])) {
  669.                 // FONT has already been used
  670.                 $this->_formats[$i]->font_index $fonts[$key];
  671.             else {
  672.                 // Add a new FONT record
  673.                 $fonts[$key]        $index;
  674.                 $this->_formats[$i]->font_index $index;
  675.                 $index++;
  676.                 $font $this->_formats[$i]->getFont();
  677.                 $this->_append($font);
  678.             }
  679.         }
  680.     }
  681.  
  682.     /**
  683.     * Store user defined numerical formats i.e. FORMAT records
  684.     *
  685.     * @access private
  686.     */
  687.     function _storeAllNumFormats()
  688.     {
  689.         // Leaning num_format syndrome
  690.         $hash_num_formats array();
  691.         $num_formats      array();
  692.         $index 164;
  693.  
  694.         // Iterate through the XF objects and write a FORMAT record if it isn't a
  695.         // built-in format type and if the FORMAT string hasn't already been used.
  696.         $total_formats count($this->_formats);
  697.         for ($i 0$i $total_formats$i++{
  698.             $num_format $this->_formats[$i]->_num_format;
  699.  
  700.             // Check if $num_format is an index to a built-in format.
  701.             // Also check for a string of zeros, which is a valid format string
  702.             // but would evaluate to zero.
  703.             //
  704.             if (!preg_match("/^0+\d/"$num_format)) {
  705.                 if (preg_match("/^\d+$/"$num_format)) // built-in format
  706.                     continue;
  707.                 }
  708.             }
  709.  
  710.             if (isset($hash_num_formats[$num_format])) {
  711.                 // FORMAT has already been used
  712.                 $this->_formats[$i]->_num_format $hash_num_formats[$num_format];
  713.             else{
  714.                 // Add a new FORMAT
  715.                 $hash_num_formats[$num_format]  $index;
  716.                 $this->_formats[$i]->_num_format $index;
  717.                 array_push($num_formats,$num_format);
  718.                 $index++;
  719.             }
  720.         }
  721.  
  722.         // Write the new FORMAT records starting from 0xA4
  723.         $index 164;
  724.         foreach ($num_formats as $num_format{
  725.             $this->_storeNumFormat($num_format,$index);
  726.             $index++;
  727.         }
  728.     }
  729.  
  730.     /**
  731.     * Write all XF records.
  732.     *
  733.     * @access private
  734.     */
  735.     function _storeAllXfs()
  736.     {
  737.         // _tmp_format is added by the constructor. We use this to write the default XF's
  738.         // The default font index is 0
  739.         //
  740.         $format $this->_tmp_format;
  741.         for ($i 0$i <= 14$i++{
  742.             $xf $format->getXf('style')// Style XF
  743.             $this->_append($xf);
  744.         }
  745.  
  746.         $xf $format->getXf('cell');      // Cell XF
  747.         $this->_append($xf);
  748.  
  749.         // User defined XFs
  750.         $total_formats count($this->_formats);
  751.         for ($i 0$i $total_formats$i++{
  752.             $xf $this->_formats[$i]->getXf('cell');
  753.             $this->_append($xf);
  754.         }
  755.     }
  756.  
  757.     /**
  758.     * Write all STYLE records.
  759.     *
  760.     * @access private
  761.     */
  762.     function _storeAllStyles()
  763.     {
  764.         $this->_storeStyle();
  765.     }
  766.  
  767.     /**
  768.     * Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
  769.     * the NAME records.
  770.     *
  771.     * @access private
  772.     */
  773.     function _storeExterns()
  774.     {
  775.         // Create EXTERNCOUNT with number of worksheets
  776.         $this->_storeExterncount(count($this->_worksheets));
  777.  
  778.         // Create EXTERNSHEET for each worksheet
  779.         foreach ($this->_sheetnames as $sheetname{
  780.             $this->_storeExternsheet($sheetname);
  781.         }
  782.     }
  783.  
  784.     /**
  785.     * Write the NAME record to define the print area and the repeat rows and cols.
  786.     *
  787.     * @access private
  788.     */
  789.     function _storeNames()
  790.     {
  791.         // Create the print area NAME records
  792.         $total_worksheets count($this->_worksheets);
  793.         for ($i 0$i $total_worksheets$i++{
  794.             // Write a Name record if the print area has been defined
  795.             if (isset($this->_worksheets[$i]->print_rowmin)) {
  796.                 $this->_storeNameShort(
  797.                     $this->_worksheets[$i]->index,
  798.                     0x06// NAME type
  799.                     $this->_worksheets[$i]->print_rowmin,
  800.                     $this->_worksheets[$i]->print_rowmax,
  801.                     $this->_worksheets[$i]->print_colmin,
  802.                     $this->_worksheets[$i]->print_colmax
  803.                     );
  804.             }
  805.         }
  806.  
  807.         // Create the print title NAME records
  808.         $total_worksheets count($this->_worksheets);
  809.         for ($i 0$i $total_worksheets$i++{
  810.             $rowmin $this->_worksheets[$i]->title_rowmin;
  811.             $rowmax $this->_worksheets[$i]->title_rowmax;
  812.             $colmin $this->_worksheets[$i]->title_colmin;
  813.             $colmax $this->_worksheets[$i]->title_colmax;
  814.  
  815.             // Determine if row + col, row, col or nothing has been defined
  816.             // and write the appropriate record
  817.             //
  818.             if (isset($rowmin&& isset($colmin)) {
  819.                 // Row and column titles have been defined.
  820.                 // Row title has been defined.
  821.                 $this->_storeNameLong(
  822.                     $this->_worksheets[$i]->index,
  823.                     0x07// NAME type
  824.                     $rowmin,
  825.                     $rowmax,
  826.                     $colmin,
  827.                     $colmax
  828.                     );
  829.             elseif (isset($rowmin)) {
  830.                 // Row title has been defined.
  831.                 $this->_storeNameShort(
  832.                     $this->_worksheets[$i]->index,
  833.                     0x07// NAME type
  834.                     $rowmin,
  835.                     $rowmax,
  836.                     0x00,
  837.                     0xff
  838.                     );
  839.             elseif (isset($colmin)) {
  840.                 // Column title has been defined.
  841.                 $this->_storeNameShort(
  842.                     $this->_worksheets[$i]->index,
  843.                     0x07// NAME type
  844.                     0x0000,
  845.                     0x3fff,
  846.                     $colmin,
  847.                     $colmax
  848.                     );
  849.             else {
  850.                 // Print title hasn't been defined.
  851.             }
  852.         }
  853.     }
  854.  
  855.  
  856.  
  857.  
  858.     /******************************************************************************
  859.     *
  860.     * BIFF RECORDS
  861.     *
  862.     */
  863.  
  864.     /**
  865.     * Stores the CODEPAGE biff record.
  866.     *
  867.     * @access private
  868.     */
  869.     function _storeCodepage()
  870.     {
  871.         $record          0x0042;             // Record identifier
  872.         $length          0x0002;             // Number of bytes to follow
  873.         $cv              $this->_codepage;   // The code page
  874.  
  875.         $header          pack('vv'$record$length);
  876.         $data            pack('v',  $cv);
  877.  
  878.         $this->_append($header $data);
  879.     }
  880.  
  881.     /**
  882.     * Write Excel BIFF WINDOW1 record.
  883.     *
  884.     * @access private
  885.     */
  886.     function _storeWindow1()
  887.     {
  888.         $record    0x003D;                 // Record identifier
  889.         $length    0x0012;                 // Number of bytes to follow
  890.  
  891.         $xWn       0x0000;                 // Horizontal position of window
  892.         $yWn       0x0000;                 // Vertical position of window
  893.         $dxWn      0x25BC;                 // Width of window
  894.         $dyWn      0x1572;                 // Height of window
  895.  
  896.         $grbit     0x0038;                 // Option flags
  897.         $ctabsel   $this->_selected;       // Number of workbook tabs selected
  898.         $wTabRatio 0x0258;                 // Tab to scrollbar ratio
  899.  
  900.         $itabFirst $this->_firstsheet;     // 1st displayed worksheet
  901.         $itabCur   $this->_activesheet;    // Active worksheet
  902.  
  903.         $header    pack("vv",        $record$length);
  904.         $data      pack("vvvvvvvvv"$xWn$yWn$dxWn$dyWn,
  905.                                        $grbit,
  906.                                        $itabCur$itabFirst,
  907.                                        $ctabsel$wTabRatio);
  908.         $this->_append($header $data);
  909.     }
  910.  
  911.     /**
  912.     * Writes Excel BIFF BOUNDSHEET record.
  913.     * FIXME: inconsistent with BIFF documentation
  914.     *
  915.     * @param string  $sheetname Worksheet name
  916.     * @param integer $offset    Location of worksheet BOF
  917.     * @access private
  918.     */
  919.     function _storeBoundsheet($sheetname,$offset)
  920.     {
  921.         $record    0x0085;                    // Record identifier
  922.         if ($this->_BIFF_version == 0x0600{
  923.             $length    0x08 strlen($sheetname)// Number of bytes to follow
  924.         else {
  925.             $length 0x07 strlen($sheetname)// Number of bytes to follow
  926.         }
  927.  
  928.         $grbit     0x0000;                    // Visibility and sheet type
  929.         $cch       strlen($sheetname);        // Length of sheet name
  930.  
  931.         $header    pack("vv",  $record$length);
  932.         if ($this->_BIFF_version == 0x0600{
  933.             $data      pack("Vvv"$offset$grbit$cch);
  934.         else {
  935.             $data      pack("VvC"$offset$grbit$cch);
  936.         }
  937.         $this->_append($header.$data.$sheetname);
  938.     }
  939.  
  940.     /**
  941.     * Write Internal SUPBOOK record
  942.     *
  943.     * @access private
  944.     */
  945.     function _storeSupbookInternal()
  946.     {
  947.         $record    0x01AE;   // Record identifier
  948.         $length    0x0004;   // Bytes to follow
  949.  
  950.         $header    pack("vv"$record$length);
  951.         $data      pack("vv"count($this->_worksheets)0x0104);
  952.         $this->_append($header $data);
  953.     }
  954.  
  955.     /**
  956.     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
  957.     * formulas.
  958.     *
  959.     * @param string $sheetname Worksheet name
  960.     * @access private
  961.     */
  962.     function _storeExternsheetBiff8()
  963.     {
  964.         $total_references count($this->_parser->_references);
  965.         $record   0x0017;                     // Record identifier
  966.         $length   $total_references;  // Number of bytes to follow
  967.  
  968.         $supbook_index 0;           // FIXME: only using internal SUPBOOK record
  969.         $header           pack("vv",  $record$length);
  970.         $data             pack('v'$total_references);
  971.         for ($i 0$i $total_references$i++{
  972.             $data .= $this->_parser->_references[$i];
  973.         }
  974.         $this->_append($header $data);
  975.     }
  976.  
  977.     /**
  978.     * Write Excel BIFF STYLE records.
  979.     *
  980.     * @access private
  981.     */
  982.     function _storeStyle()
  983.     {
  984.         $record    0x0293;   // Record identifier
  985.         $length    0x0004;   // Bytes to follow
  986.  
  987.         $ixfe      0x8000;   // Index to style XF
  988.         $BuiltIn   0x00;     // Built-in style
  989.         $iLevel    0xff;     // Outline style level
  990.  
  991.         $header    pack("vv",  $record$length);
  992.         $data      pack("vCC"$ixfe$BuiltIn$iLevel);
  993.         $this->_append($header $data);
  994.     }
  995.  
  996.  
  997.     /**
  998.     * Writes Excel FORMAT record for non "built-in" numerical formats.
  999.     *
  1000.     * @param string  $format Custom format string
  1001.     * @param integer $ifmt   Format index code
  1002.     * @access private
  1003.     */
  1004.     function _storeNumFormat($format$ifmt)
  1005.     {
  1006.         $record    0x041E;                      // Record identifier
  1007.  
  1008.         if ($this->_BIFF_version == 0x0600{
  1009.             $length    strlen($format);      // Number of bytes to follow
  1010.             $encoding 0x0;
  1011.         elseif ($this->_BIFF_version == 0x0500{
  1012.             $length    strlen($format);      // Number of bytes to follow
  1013.         }
  1014.  
  1015.         $cch       strlen($format);             // Length of format string
  1016.  
  1017.         $header    pack("vv"$record$length);
  1018.         if ($this->_BIFF_version == 0x0600{
  1019.             $data      pack("vvC"$ifmt$cch$encoding);
  1020.         elseif ($this->_BIFF_version == 0x0500{
  1021.             $data      pack("vC"$ifmt$cch);
  1022.         }
  1023.         $this->_append($header $data $format);
  1024.     }
  1025.  
  1026.     /**
  1027.     * Write DATEMODE record to indicate the date system in use (1904 or 1900).
  1028.     *
  1029.     * @access private
  1030.     */
  1031.     function _storeDatemode()
  1032.     {
  1033.         $record    0x0022;         // Record identifier
  1034.         $length    0x0002;         // Bytes to follow
  1035.  
  1036.         $f1904     $this->_1904;   // Flag for 1904 date system
  1037.  
  1038.         $header    pack("vv"$record$length);
  1039.         $data      pack("v"$f1904);
  1040.         $this->_append($header $data);
  1041.     }
  1042.  
  1043.  
  1044.     /**
  1045.     * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
  1046.     * references in the workbook.
  1047.     *
  1048.     * Excel only stores references to external sheets that are used in NAME.
  1049.     * The workbook NAME record is required to define the print area and the repeat
  1050.     * rows and columns.
  1051.     *
  1052.     * A similar method is used in Worksheet.php for a slightly different purpose.
  1053.     *
  1054.     * @param integer $cxals Number of external references
  1055.     * @access private
  1056.     */
  1057.     function _storeExterncount($cxals)
  1058.     {
  1059.         $record   0x0016;          // Record identifier
  1060.         $length   0x0002;          // Number of bytes to follow
  1061.  
  1062.         $header   pack("vv"$record$length);
  1063.         $data     pack("v",  $cxals);
  1064.         $this->_append($header $data);
  1065.     }
  1066.  
  1067.  
  1068.     /**
  1069.     * Writes the Excel BIFF EXTERNSHEET record. These references are used by
  1070.     * formulas. NAME record is required to define the print area and the repeat
  1071.     * rows and columns.
  1072.     *
  1073.     * A similar method is used in Worksheet.php for a slightly different purpose.
  1074.     *
  1075.     * @param string $sheetname Worksheet name
  1076.     * @access private
  1077.     */
  1078.     function _storeExternsheet($sheetname)
  1079.     {
  1080.         $record      0x0017;                     // Record identifier
  1081.         $length      0x02 strlen($sheetname);  // Number of bytes to follow
  1082.  
  1083.         $cch         strlen($sheetname);         // Length of sheet name
  1084.         $rgch        0x03;                       // Filename encoding
  1085.  
  1086.         $header      pack("vv",  $record$length);
  1087.         $data        pack("CC"$cch$rgch);
  1088.         $this->_append($header $data $sheetname);
  1089.     }
  1090.  
  1091.  
  1092.     /**
  1093.     * Store the NAME record in the short format that is used for storing the print
  1094.     * area, repeat rows only and repeat columns only.
  1095.     *
  1096.     * @param integer $index  Sheet index
  1097.     * @param integer $type   Built-in name type
  1098.     * @param integer $rowmin Start row
  1099.     * @param integer $rowmax End row
  1100.     * @param integer $colmin Start colum
  1101.     * @param integer $colmax End column
  1102.     * @access private
  1103.     */
  1104.     function _storeNameShort($index$type$rowmin$rowmax$colmin$colmax)
  1105.     {
  1106.         $record          0x0018;       // Record identifier
  1107.         $length          0x0024;       // Number of bytes to follow
  1108.  
  1109.         $grbit           0x0020;       // Option flags
  1110.         $chKey           0x00;         // Keyboard shortcut
  1111.         $cch             0x01;         // Length of text name
  1112.         $cce             0x0015;       // Length of text definition
  1113.         $ixals           $index 1;   // Sheet index
  1114.         $itab            $ixals;       // Equal to ixals
  1115.         $cchCustMenu     0x00;         // Length of cust menu text
  1116.         $cchDescription  0x00;         // Length of description text
  1117.         $cchHelptopic    0x00;         // Length of help topic text
  1118.         $cchStatustext   0x00;         // Length of status bar text
  1119.         $rgch            $type;        // Built-in name type
  1120.  
  1121.         $unknown03       0x3b;
  1122.         $unknown04       0xffff-$index;
  1123.         $unknown05       0x0000;
  1124.         $unknown06       0x0000;
  1125.         $unknown07       0x1087;
  1126.         $unknown08       0x8005;
  1127.  
  1128.         $header             pack("vv"$record$length);
  1129.         $data               pack("v"$grbit);
  1130.         $data              .= pack("C"$chKey);
  1131.         $data              .= pack("C"$cch);
  1132.         $data              .= pack("v"$cce);
  1133.         $data              .= pack("v"$ixals);
  1134.         $data              .= pack("v"$itab);
  1135.         $data              .= pack("C"$cchCustMenu);
  1136.         $data              .= pack("C"$cchDescription);
  1137.         $data              .= pack("C"$cchHelptopic);
  1138.         $data              .= pack("C"$cchStatustext);
  1139.         $data              .= pack("C"$rgch);
  1140.         $data              .= pack("C"$unknown03);
  1141.         $data              .= pack("v"$unknown04);
  1142.         $data              .= pack("v"$unknown05);
  1143.         $data              .= pack("v"$unknown06);
  1144.         $data              .= pack("v"$unknown07);
  1145.         $data              .= pack("v"$unknown08);
  1146.         $data              .= pack("v"$index);
  1147.         $data              .= pack("v"$index);
  1148.         $data              .= pack("v"$rowmin);
  1149.         $data              .= pack("v"$rowmax);
  1150.         $data              .= pack("C"$colmin);
  1151.         $data              .= pack("C"$colmax);
  1152.         $this->_append($header $data);
  1153.     }
  1154.  
  1155.  
  1156.     /**
  1157.     * Store the NAME record in the long format that is used for storing the repeat
  1158.     * rows and columns when both are specified. This shares a lot of code with
  1159.     * _storeNameShort() but we use a separate method to keep the code clean.
  1160.     * Code abstraction for reuse can be carried too far, and I should know. ;-)
  1161.     *
  1162.     * @param integer $index Sheet index
  1163.     * @param integer $type  Built-in name type
  1164.     * @param integer $rowmin Start row
  1165.     * @param integer $rowmax End row
  1166.     * @param integer $colmin Start colum
  1167.     * @param integer $colmax End column
  1168.     * @access private
  1169.     */
  1170.     function _storeNameLong($index$type$rowmin$rowmax$colmin$colmax)
  1171.     {
  1172.         $record          0x0018;       // Record identifier
  1173.         $length          0x003d;       // Number of bytes to follow
  1174.         $grbit           0x0020;       // Option flags
  1175.         $chKey           0x00;         // Keyboard shortcut
  1176.         $cch             0x01;         // Length of text name
  1177.         $cce             0x002e;       // Length of text definition
  1178.         $ixals           $index 1;   // Sheet index
  1179.         $itab            $ixals;       // Equal to ixals
  1180.         $cchCustMenu     0x00;         // Length of cust menu text
  1181.         $cchDescription  0x00;         // Length of description text
  1182.         $cchHelptopic    0x00;         // Length of help topic text
  1183.         $cchStatustext   0x00;         // Length of status bar text
  1184.         $rgch            $type;        // Built-in name type
  1185.  
  1186.         $unknown01       0x29;
  1187.         $unknown02       0x002b;
  1188.         $unknown03       0x3b;
  1189.         $unknown04       0xffff-$index;
  1190.         $unknown05       0x0000;
  1191.         $unknown06       0x0000;
  1192.         $unknown07       0x1087;
  1193.         $unknown08       0x8008;
  1194.  
  1195.         $header             pack("vv",  $record$length);
  1196.         $data               pack("v"$grbit);
  1197.         $data              .= pack("C"$chKey);
  1198.         $data              .= pack("C"$cch);
  1199.         $data              .= pack("v"$cce);
  1200.         $data              .= pack("v"$ixals);
  1201.         $data              .= pack("v"$itab);
  1202.         $data              .= pack("C"$cchCustMenu);
  1203.         $data              .= pack("C"$cchDescription);
  1204.         $data              .= pack("C"$cchHelptopic);
  1205.         $data              .= pack("C"$cchStatustext);
  1206.         $data              .= pack("C"$rgch);
  1207.         $data              .= pack("C"$unknown01);
  1208.         $data              .= pack("v"$unknown02);
  1209.         // Column definition
  1210.         $data              .= pack("C"$unknown03);
  1211.         $data              .= pack("v"$unknown04);
  1212.         $data              .= pack("v"$unknown05);
  1213.         $data              .= pack("v"$unknown06);
  1214.         $data              .= pack("v"$unknown07);
  1215.         $data              .= pack("v"$unknown08);
  1216.         $data              .= pack("v"$index);
  1217.         $data              .= pack("v"$index);
  1218.         $data              .= pack("v"0x0000);
  1219.         $data              .= pack("v"0x3fff);
  1220.         $data              .= pack("C"$colmin);
  1221.         $data              .= pack("C"$colmax);
  1222.         // Row definition
  1223.         $data              .= pack("C"$unknown03);
  1224.         $data              .= pack("v"$unknown04);
  1225.         $data              .= pack("v"$unknown05);
  1226.         $data              .= pack("v"$unknown06);
  1227.         $data              .= pack("v"$unknown07);
  1228.         $data              .= pack("v"$unknown08);
  1229.         $data              .= pack("v"$index);
  1230.         $data              .= pack("v"$index);
  1231.         $data              .= pack("v"$rowmin);
  1232.         $data              .= pack("v"$rowmax);
  1233.         $data              .= pack("C"0x00);
  1234.         $data              .= pack("C"0xff);
  1235.         // End of data
  1236.         $data              .= pack("C"0x10);
  1237.         $this->_append($header $data);
  1238.     }
  1239.  
  1240.     /**
  1241.     * Stores the COUNTRY record for localization
  1242.     *
  1243.     * @access private
  1244.     */
  1245.     function _storeCountry()
  1246.     {
  1247.         $record          0x008C;    // Record identifier
  1248.         $length          4;         // Number of bytes to follow
  1249.  
  1250.         $header pack('vv',  $record$length);
  1251.         /* using the same country code always for simplicity */
  1252.         $data pack('vv'$this->_country_code$this->_country_code);
  1253.         $this->_append($header $data);
  1254.     }
  1255.  
  1256.     /**
  1257.     * Stores the PALETTE biff record.
  1258.     *
  1259.     * @access private
  1260.     */
  1261.     function _storePalette()
  1262.     {
  1263.         $aref            $this->_palette;
  1264.  
  1265.         $record          0x0092;                 // Record identifier
  1266.         $length          count($aref);   // Number of bytes to follow
  1267.         $ccv             =         count($aref);   // Number of RGB values to follow
  1268.         $data '';                                // The RGB data
  1269.  
  1270.         // Pack the RGB data
  1271.         foreach ($aref as $color{
  1272.             foreach ($color as $byte{
  1273.                 $data .= pack("C",$byte);
  1274.             }
  1275.         }
  1276.  
  1277.         $header pack("vvv",  $record$length$ccv);
  1278.         $this->_append($header $data);
  1279.     }
  1280.   
  1281.     /**
  1282.     * Calculate
  1283.     * Handling of the SST continue blocks is complicated by the need to include an
  1284.     * additional continuation byte depending on whether the string is split between
  1285.     * blocks or whether it starts at the beginning of the block. (There are also
  1286.     * additional complications that will arise later when/if Rich Strings are
  1287.     * supported).
  1288.     *
  1289.     * @access private
  1290.     */
  1291.     function _calculateSharedStringsSizes()
  1292.     {
  1293.         /* Iterate through the strings to calculate the CONTINUE block sizes.
  1294.            For simplicity we use the same size for the SST and CONTINUE records:
  1295.            8228 : Maximum Excel97 block size
  1296.              -4 : Length of block header
  1297.              -8 : Length of additional SST header information
  1298.              -8 : Arbitrary number to keep within _add_continue() limit = 8208
  1299.         */
  1300.         $continue_limit     8208;
  1301.         $block_length       0;
  1302.         $written            0;
  1303.         $this->_block_sizes array();
  1304.         $continue           0;
  1305.  
  1306.         foreach (array_keys($this->_str_tableas $string{
  1307.             $string_length strlen($string);
  1308.             $headerinfo    unpack("vlength/Cencoding"$string);
  1309.             $encoding      $headerinfo["encoding"];
  1310.             $split_string  0;
  1311.  
  1312.             // Block length is the total length of the strings that will be
  1313.             // written out in a single SST or CONTINUE block.
  1314.             $block_length += $string_length;
  1315.  
  1316.             // We can write the string if it doesn't cross a CONTINUE boundary
  1317.             if ($block_length $continue_limit{
  1318.                 $written      += $string_length;
  1319.                 continue;
  1320.             }
  1321.  
  1322.             // Deal with the cases where the next string to be written will exceed
  1323.             // the CONTINUE boundary. If the string is very long it may need to be
  1324.             // written in more than one CONTINUE record.
  1325.             while ($block_length >= $continue_limit{
  1326.  
  1327.                 // We need to avoid the case where a string is continued in the first
  1328.                 // n bytes that contain the string header information.
  1329.                 $header_length   3// Min string + header size -1
  1330.                 $space_remaining $continue_limit $written $continue;
  1331.  
  1332.  
  1333.                 /* TODO: Unicode data should only be split on char (2 byte)
  1334.                 boundaries. Therefore, in some cases we need to reduce the
  1335.                 amount of available
  1336.                 */
  1337.                 $align 0;
  1338.  
  1339.                 // Only applies to Unicode strings
  1340.                 if ($encoding == 1{
  1341.                     // Min string + header size -1
  1342.                     $header_length 4;
  1343.  
  1344.                     if ($space_remaining $header_length{
  1345.                         // String contains 3 byte header => split on odd boundary
  1346.                         if (!$split_string && $space_remaining != 1{
  1347.                             $space_remaining--;
  1348.                             $align 1;
  1349.                         }
  1350.                         // Split section without header => split on even boundary
  1351.                         else if ($split_string && $space_remaining == 1{
  1352.                             $space_remaining--;
  1353.                             $align 1;
  1354.                         }
  1355.  
  1356.                         $split_string 1;
  1357.                     }
  1358.                 }
  1359.  
  1360.  
  1361.                 if ($space_remaining $header_length{
  1362.                     // Write as much as possible of the string in the current block
  1363.                     $written      += $space_remaining;
  1364.  
  1365.                     // Reduce the current block length by the amount written
  1366.                     $block_length -= $continue_limit $continue $align;
  1367.  
  1368.                     // Store the max size for this block
  1369.                     $this->_block_sizes[$continue_limit $align;
  1370.  
  1371.                     // If the current string was split then the next CONTINUE block
  1372.                     // should have the string continue flag (grbit) set unless the
  1373.                     // split string fits exactly into the remaining space.
  1374.                     if ($block_length 0{
  1375.                         $continue 1;
  1376.                     else {
  1377.                         $continue 0;
  1378.                     }
  1379.                 else {
  1380.                     // Store the max size for this block
  1381.                     $this->_block_sizes[$written $continue;
  1382.  
  1383.                     // Not enough space to start the string in the current block
  1384.                     $block_length -= $continue_limit $space_remaining $continue;
  1385.                     $continue 0;
  1386.  
  1387.                 }
  1388.  
  1389.                 // If the string (or substr) is small enough we can write it in the
  1390.                 // new CONTINUE block. Else, go through the loop again to write it in
  1391.                 // one or more CONTINUE blocks
  1392.                 if ($block_length $continue_limit{
  1393.                     $written $block_length;
  1394.                 else {
  1395.                     $written 0;
  1396.                 }
  1397.             }
  1398.         }
  1399.  
  1400.         // Store the max size for the last block unless it is empty
  1401.         if ($written $continue{
  1402.             $this->_block_sizes[$written $continue;
  1403.         }
  1404.  
  1405.  
  1406.         /* Calculate the total length of the SST and associated CONTINUEs (if any).
  1407.          The SST record will have a length even if it contains no strings.
  1408.          This length is required to set the offsets in the BOUNDSHEET records since
  1409.          they must be written before the SST records
  1410.         */
  1411.  
  1412.         $tmp_block_sizes array();
  1413.         $tmp_block_sizes $this->_block_sizes;
  1414.  
  1415.         $length  12;
  1416.         if (!empty($tmp_block_sizes)) {
  1417.             $length += array_shift($tmp_block_sizes)// SST
  1418.         }
  1419.         while (!empty($tmp_block_sizes)) {
  1420.             $length += array_shift($tmp_block_sizes)// CONTINUEs
  1421.         }
  1422.  
  1423.         return $length;
  1424.     }
  1425.  
  1426.     /**
  1427.     * Write all of the workbooks strings into an indexed array.
  1428.     * See the comments in _calculate_shared_string_sizes() for more information.
  1429.     *
  1430.     * The Excel documentation says that the SST record should be followed by an
  1431.     * EXTSST record. The EXTSST record is a hash table that is used to optimise
  1432.     * access to SST. However, despite the documentation it doesn't seem to be
  1433.     * required so we will ignore it.
  1434.     *
  1435.     * @access private
  1436.     */
  1437.     function _storeSharedStringsTable()
  1438.     {
  1439.         $record  0x00fc;  // Record identifier
  1440.         $length  0x0008;  // Number of bytes to follow
  1441.         $total   0x0000;
  1442.  
  1443.         // Iterate through the strings to calculate the CONTINUE block sizes
  1444.         $continue_limit 8208;
  1445.         $block_length   0;
  1446.         $written        0;
  1447.         $continue       0;
  1448.  
  1449.         // sizes are upside down
  1450.         $tmp_block_sizes $this->_block_sizes;
  1451.         // $tmp_block_sizes = array_reverse($this->_block_sizes);
  1452.  
  1453.         // The SST record is required even if it contains no strings. Thus we will
  1454.         // always have a length
  1455.         //
  1456.         if (!empty($tmp_block_sizes)) {
  1457.             $length array_shift($tmp_block_sizes);
  1458.         }
  1459.         else {
  1460.             // No strings
  1461.             $length 8;
  1462.         }
  1463.  
  1464.  
  1465.  
  1466.         // Write the SST block header information
  1467.         $header      pack("vv"$record$length);
  1468.         $data        pack("VV"$this->_str_total$this->_str_unique);
  1469.         $this->_append($header $data);
  1470.  
  1471.  
  1472.  
  1473.  
  1474.         /* TODO: not good for performance */
  1475.         foreach (array_keys($this->_str_tableas $string{
  1476.  
  1477.             $string_length strlen($string);
  1478.             $headerinfo    unpack("vlength/Cencoding"$string);
  1479.             $encoding      $headerinfo["encoding"];
  1480.             $split_string  0;
  1481.  
  1482.             // Block length is the total length of the strings that will be
  1483.             // written out in a single SST or CONTINUE block.
  1484.             //
  1485.             $block_length += $string_length;
  1486.  
  1487.  
  1488.             // We can write the string if it doesn't cross a CONTINUE boundary
  1489.             if ($block_length $continue_limit{
  1490.                 $this->_append($string);
  1491.                 $written += $string_length;
  1492.                 continue;
  1493.             }
  1494.  
  1495.             // Deal with the cases where the next string to be written will exceed
  1496.             // the CONTINUE boundary. If the string is very long it may need to be
  1497.             // written in more than one CONTINUE record.
  1498.             //
  1499.             while ($block_length >= $continue_limit{
  1500.  
  1501.                 // We need to avoid the case where a string is continued in the first
  1502.                 // n bytes that contain the string header information.
  1503.                 //
  1504.                 $header_length   3// Min string + header size -1
  1505.                 $space_remaining $continue_limit $written $continue;
  1506.  
  1507.  
  1508.                 // Unicode data should only be split on char (2 byte) boundaries.
  1509.                 // Therefore, in some cases we need to reduce the amount of available
  1510.                 // space by 1 byte to ensure the correct alignment.
  1511.                 $align 0;
  1512.  
  1513.                 // Only applies to Unicode strings
  1514.                 if ($encoding == 1{
  1515.                     // Min string + header size -1
  1516.                     $header_length 4;
  1517.  
  1518.                     if ($space_remaining $header_length{
  1519.                         // String contains 3 byte header => split on odd boundary
  1520.                         if (!$split_string && $space_remaining != 1{
  1521.                             $space_remaining--;
  1522.                             $align 1;
  1523.                         }
  1524.                         // Split section without header => split on even boundary
  1525.                         else if ($split_string && $space_remaining == 1{
  1526.                             $space_remaining--;
  1527.                             $align 1;
  1528.                         }
  1529.  
  1530.                         $split_string 1;
  1531.                     }
  1532.                 }
  1533.  
  1534.  
  1535.                 if ($space_remaining $header_length{
  1536.                     // Write as much as possible of the string in the current block
  1537.                     $tmp substr($string0$space_remaining);
  1538.                     $this->_append($tmp);
  1539.  
  1540.                     // The remainder will be written in the next block(s)
  1541.                     $string substr($string$space_remaining);
  1542.  
  1543.                     // Reduce the current block length by the amount written
  1544.                     $block_length -= $continue_limit $continue $align;
  1545.  
  1546.                     // If the current string was split then the next CONTINUE block
  1547.                     // should have the string continue flag (grbit) set unless the
  1548.                     // split string fits exactly into the remaining space.
  1549.                     //
  1550.                     if ($block_length 0{
  1551.                         $continue 1;
  1552.                     else {
  1553.                         $continue 0;
  1554.                     }
  1555.                 else {
  1556.                     // Not enough space to start the string in the current block
  1557.                     $block_length -= $continue_limit $space_remaining $continue;
  1558.                     $continue 0;
  1559.                 }
  1560.  
  1561.                 // Write the CONTINUE block header
  1562.                 if (!empty($this->_block_sizes)) {
  1563.                     $record  0x003C;
  1564.                     $length  array_shift($tmp_block_sizes);
  1565.  
  1566.                     $header  pack('vv'$record$length);
  1567.                     if ($continue{
  1568.                         $header .= pack('C'$encoding);
  1569.                     }
  1570.                     $this->_append($header);
  1571.                 }
  1572.  
  1573.                 // If the string (or substr) is small enough we can write it in the
  1574.                 // new CONTINUE block. Else, go through the loop again to write it in
  1575.                 // one or more CONTINUE blocks
  1576.                 //
  1577.                 if ($block_length $continue_limit{
  1578.                     $this->_append($string);
  1579.                     $written $block_length;
  1580.                 else {
  1581.                     $written 0;
  1582.                 }
  1583.             }
  1584.         }
  1585.     }
  1586.  
  1587.  
  1588. }

Documentation generated on Wed, 09 Feb 2011 09:05:19 +0700 by phpDocumentor 1.4.2