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

Documentation generated on Mon, 27 Oct 2008 08:41:50 +0100 by phpDocumentor 1.4.1