Spreadsheet_Excel_Writer
[ class tree: Spreadsheet_Excel_Writer ] [ index: Spreadsheet_Excel_Writer ] [ all elements ]

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

Documentation generated on Mon, 11 Mar 2019 15:47:15 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.