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

Documentation generated on Mon, 05 Jan 2009 20:38:48 +0100 by phpDocumentor 1.4.1