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

Source for file Excel5.php

Documentation is available at Excel5.php

  1. <?php
  2. /**
  3.  * PHPExcel
  4.  *
  5.  * Copyright (c) 2006 - 2009 PHPExcel
  6.  *
  7.  * This library is free software; you can redistribute it and/or
  8.  * modify it under the terms of the GNU Lesser General Public
  9.  * License as published by the Free Software Foundation; either
  10.  * version 2.1 of the License, or (at your option) any later version.
  11.  *
  12.  * This library is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15.  * Lesser General Public License for more details.
  16.  *
  17.  * You should have received a copy of tshhe GNU Lesser General Public
  18.  * License along with this library; if not, write to the Free Software
  19.  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  20.  *
  21.  * @category   PHPExcel
  22.  * @package    PHPExcel_Reader_Excel5
  23.  * @copyright  Copyright (c) 2006 - 2009 PHPExcel (http://www.codeplex.com/PHPExcel)
  24.  * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
  25.  * @version    1.6.5, 2009-01-05
  26.  */
  27.  
  28. // Original file header of ParseXL (used as the base for this class):
  29. // --------------------------------------------------------------------------------
  30. // Adapted from Excel_Spreadsheet_Reader developed by users bizon153,
  31. // trex005, and mmp11 (SourceForge.net)
  32. // http://sourceforge.net/projects/phpexcelreader/
  33. // Primary changes made by canyoncasa (dvc) for ParseXL 1.00 ...
  34. //     Modelled moreso after Perl Excel Parse/Write modules
  35. //     Added Parse_Excel_Spreadsheet object
  36. //         Reads a whole worksheet or tab as row,column array or as
  37. //         associated hash of indexed rows and named column fields
  38. //     Added variables for worksheet (tab) indexes and names
  39. //     Added an object call for loading individual woorksheets
  40. //     Changed default indexing defaults to 0 based arrays
  41. //     Fixed date/time and percent formats
  42. //     Includes patches found at SourceForge...
  43. //         unicode patch by nobody
  44. //         unpack("d") machine depedency patch by matchy
  45. //         boundsheet utf16 patch by bjaenichen
  46. //     Renamed functions for shorter names
  47. //     General code cleanup and rigor, including <80 column width
  48. //     Included a testcase Excel file and PHP example calls
  49. //     Code works for PHP 5.x
  50.  
  51. // Primary changes made by canyoncasa (dvc) for ParseXL 1.10 ...
  52. // http://sourceforge.net/tracker/index.php?func=detail&aid=1466964&group_id=99160&atid=623334
  53. //     Decoding of formula conditions, results, and tokens.
  54. //     Support for user-defined named cells added as an array "namedcells"
  55. //         Patch code for user-defined named cells supports single cells only.
  56. //         NOTE: this patch only works for BIFF8 as BIFF5-7 use a different
  57. //         external sheet reference structure
  58.  
  59.  
  60. /** PHPExcel */
  61. require_once 'PHPExcel.php';
  62.  
  63. /** PHPExcel_Reader_IReader */
  64. require_once 'PHPExcel/Reader/IReader.php';
  65.  
  66. /** PHPExcel_Reader_Excel5_Escher */
  67. require_once 'PHPExcel/Reader/Excel5/Escher.php';
  68.  
  69. /** PHPExcel_Shared_Date */
  70. require_once 'PHPExcel/Shared/Date.php';
  71.  
  72. /** PHPExcel_Shared_Escher */
  73. require_once 'PHPExcel/Shared/Escher.php';
  74.  
  75. /** PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE */
  76. require_once 'PHPExcel/Shared/Escher/DggContainer/BstoreContainer/BSE.php';
  77.  
  78. /** PHPExcel_Shared_OLERead */
  79. require_once 'PHPExcel/Shared/OLERead.php';
  80.  
  81. /** PHPExcel_Cell */
  82. require_once 'PHPExcel/Cell.php';
  83.  
  84. /** PHPExcel_NamedRange */
  85. require_once 'PHPExcel/NamedRange.php';
  86.  
  87. /** PHPExcel_Reader_IReadFilter */
  88. require_once 'PHPExcel/Reader/IReadFilter.php';
  89.  
  90. /** PHPExcel_Reader_DefaultReadFilter */
  91. require_once 'PHPExcel/Reader/DefaultReadFilter.php';
  92.  
  93. /** PHPExcel_Worksheet_MemoryDrawing */
  94. require_once 'PHPExcel/Worksheet/MemoryDrawing.php';
  95.  
  96.  
  97. /**
  98.  * PHPExcel_Reader_Excel5
  99.  *
  100.  * This class uses {@link http://sourceforge.net/projects/phpexcelreader/parseXL}
  101.  *
  102.  * @category   PHPExcel
  103.  * @package    PHPExcel_Reader_Excel5
  104.  * @copyright  Copyright (c) 2006 - 2009 PHPExcel (http://www.codeplex.com/PHPExcel)
  105.  */
  106. class PHPExcel_Reader_Excel5 implements PHPExcel_Reader_IReader
  107. {
  108.     // ParseXL definitions
  109.     const XLS_BIFF8                        = 0x0600;
  110.     const XLS_BIFF7                        = 0x0500;
  111.     const XLS_WorkbookGlobals            = 0x0005;
  112.     const XLS_Worksheet                    = 0x0010;
  113.  
  114.     // record identifiers
  115.     const XLS_Type_FORMULA                = 0x0006;
  116.     const XLS_Type_EOF                    = 0x000a;
  117.     const XLS_Type_PROTECT                = 0x0012;
  118.     const XLS_Type_PASSWORD                = 0x0013;
  119.     const XLS_Type_HEADER                = 0x0014;
  120.     const XLS_Type_FOOTER                = 0x0015;
  121.     const XLS_Type_EXTERNSHEET            = 0x0017;
  122.     const XLS_Type_DEFINEDNAME            = 0x0018;
  123.     const XLS_Type_VERTICALPAGEBREAKS    = 0x001a;
  124.     const XLS_Type_HORIZONTALPAGEBREAKS    = 0x001b;
  125.     const XLS_Type_NOTE                    = 0x001c;
  126.     const XLS_Type_DATEMODE                = 0x0022;
  127.     const XLS_Type_LEFTMARGIN            = 0x0026;
  128.     const XLS_Type_RIGHTMARGIN            = 0x0027;
  129.     const XLS_Type_TOPMARGIN            = 0x0028;
  130.     const XLS_Type_BOTTOMMARGIN            = 0x0029;
  131.     const XLS_Type_PRINTGRIDLINES        = 0x002b;
  132.     const XLS_Type_FILEPASS                = 0x002f;
  133.     const XLS_Type_FONT                    = 0x0031;
  134.     const XLS_Type_CONTINUE                = 0x003c;
  135.     const XLS_Type_PANE                    = 0x0041;
  136.     const XLS_Type_CODEPAGE                = 0x0042;
  137.     const XLS_Type_DEFCOLWIDTH             = 0x0055;
  138.     const XLS_Type_OBJ                    = 0x005d;
  139.     const XLS_Type_COLINFO                = 0x007d;
  140.     const XLS_Type_IMDATA                = 0x007f;
  141.     const XLS_Type_SHEETPR                = 0x0081;
  142.     const XLS_Type_HCENTER                = 0x0083;
  143.     const XLS_Type_VCENTER                = 0x0084;
  144.     const XLS_Type_SHEET                = 0x0085;
  145.     const XLS_Type_PALETTE                = 0x0092;
  146.     const XLS_Type_SCL                    = 0x00a0;
  147.     const XLS_Type_PAGESETUP            = 0x00a1;
  148.     const XLS_Type_MULRK                = 0x00bd;
  149.     const XLS_Type_MULBLANK                = 0x00be;
  150.     const XLS_Type_DBCELL                = 0x00d7;
  151.     const XLS_Type_XF                    = 0x00e0;
  152.     const XLS_Type_MERGEDCELLS            = 0x00e5;
  153.     const XLS_Type_MSODRAWINGGROUP        = 0x00eb;
  154.     const XLS_Type_MSODRAWING            = 0x00ec;
  155.     const XLS_Type_SST                    = 0x00fc;
  156.     const XLS_Type_LABELSST                = 0x00fd;
  157.     const XLS_Type_EXTSST                = 0x00ff;
  158.     const XLS_Type_EXTERNALBOOK            = 0x01ae;
  159.     const XLS_Type_TXO                    = 0x01b6;
  160.     const XLS_Type_HYPERLINK            = 0x01b8;
  161.     const XLS_Type_DIMENSION            = 0x0200;
  162.     const XLS_Type_BLANK                = 0x0201;
  163.     const XLS_Type_NUMBER                = 0x0203;
  164.     const XLS_Type_LABEL                = 0x0204;
  165.     const XLS_Type_BOOLERR                = 0x0205;
  166.     const XLS_Type_STRING                = 0x0207;
  167.     const XLS_Type_ROW                    = 0x0208;
  168.     const XLS_Type_INDEX                = 0x020b;
  169.     const XLS_Type_ARRAY                = 0x0221;
  170.     const XLS_Type_DEFAULTROWHEIGHT     = 0x0225;
  171.     const XLS_Type_WINDOW2                = 0x023e;
  172.     const XLS_Type_RK                    = 0x027e;
  173.     const XLS_Type_STYLE                = 0x0293;
  174.     const XLS_Type_FORMAT                = 0x041e;
  175.     const XLS_Type_BOF                    = 0x0809;
  176.     const XLS_Type_RANGEPROTECTION        = 0x0868;
  177.     const XLS_Type_UNKNOWN                = 0xffff;
  178.  
  179.     /**
  180.      * Read data only?
  181.      *
  182.      * @var boolean 
  183.      */
  184.     private $_readDataOnly = false;
  185.  
  186.     /**
  187.      * Restict which sheets should be loaded?
  188.      *
  189.      * @var array 
  190.      */
  191.     private $_loadSheetsOnly = null;
  192.  
  193.     /**
  194.      * PHPExcel_Reader_IReadFilter instance
  195.      *
  196.      * @var PHPExcel_Reader_IReadFilter 
  197.      */
  198.     private $_readFilter = null;
  199.  
  200.     /**
  201.      * OLE reader
  202.      *
  203.      * @var PHPExcel_Shared_OLERead 
  204.      */
  205.     private $_ole;
  206.  
  207.     /**
  208.      * Stream data that is read. Includes workbook globals substream as well as sheet substreams
  209.      *
  210.      * @var string 
  211.      */
  212.     private $_data;
  213.  
  214.     /**
  215.      * Size in bytes of $this->_data
  216.      *
  217.      * @var int 
  218.      */
  219.     private $_dataSize;
  220.  
  221.     /**
  222.      * Current position in stream
  223.      *
  224.      * @var integer 
  225.      */
  226.     private $_pos;
  227.  
  228.     /**
  229.      * Workbook to be returned by the reader.
  230.      *
  231.      * @var PHPExcel 
  232.      */
  233.     private $_phpExcel;
  234.  
  235.     /**
  236.      * Worksheet that is currently being built by the reader.
  237.      *
  238.      * @var PHPExcel_Worksheet 
  239.      */
  240.     private $_phpSheet;
  241.  
  242.     /**
  243.      * BIFF version
  244.      *
  245.      * @var int 
  246.      */
  247.     private $_version;
  248.  
  249.     /**
  250.      * Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95)
  251.      * For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'
  252.      *
  253.      * @var string 
  254.      */
  255.     private $_codepage;
  256.  
  257.     /**
  258.      * Shared fonts
  259.      *
  260.      * @var array 
  261.      */
  262.     private $_fonts = array();
  263.  
  264.     /**
  265.      * Shared formats
  266.      *
  267.      * @var array 
  268.      */
  269.     private $_formats = array();
  270.  
  271.     /**
  272.      * Shared styles
  273.      *
  274.      * @var array 
  275.      */
  276.     private $_xf = array();
  277.  
  278.     /**
  279.      * Built-in styles
  280.      *
  281.      * @var array 
  282.      */
  283.     private $_builtInStyles = array();
  284.  
  285.     /**
  286.      * Color palette
  287.      *
  288.      * @var array 
  289.      */
  290.     private $_palette;
  291.  
  292.     /**
  293.      * Worksheets
  294.      *
  295.      * @var array 
  296.      */
  297.     private $_sheets = array();
  298.  
  299.     /**
  300.      * External books
  301.      *
  302.      * @var array 
  303.      */
  304.     private $_externalBooks = array();
  305.  
  306.     /**
  307.      * REF structures. Only applies to BIFF8.
  308.      *
  309.      * @var array 
  310.      */
  311.     private $_ref = array();
  312.  
  313.     /**
  314.      * Defined names
  315.      *
  316.      * @var array 
  317.      */
  318.     private $_definedname = array();
  319.  
  320.     /**
  321.      * Shared strings. Only applies to BIFF8.
  322.      *
  323.      * @var array 
  324.      */
  325.     private $_sst = array();
  326.  
  327.     /**
  328.      * Panes are frozen? (in sheet currently being read). See WINDOW2 record.
  329.      *
  330.      * @var boolean 
  331.      */
  332.     private $_frozen = false;
  333.  
  334.     /**
  335.      * Fit printout to number of pages? (in sheet currently being read). See SHEETPR record.
  336.      *
  337.      * @var boolean 
  338.      */
  339.     private $_isFitToPages;
  340.  
  341.     /**
  342.      * Objects. One OBJ record contributes with one entry.
  343.      *
  344.      * @var array 
  345.      */
  346.     private $_objs = array();
  347.  
  348.     /**
  349.      * The combined MSODRAWINGGROUP data
  350.      *
  351.      * @var string 
  352.      */
  353.     private $_drawingGroupData = '';
  354.  
  355.     /**
  356.      * The combined MSODRAWING data (per sheet)
  357.      *
  358.      * @var string 
  359.      */
  360.     private $_drawingData = '';
  361.  
  362.     /**
  363.      * Read data only?
  364.      *
  365.      * @return boolean 
  366.      */
  367.     public function getReadDataOnly()
  368.     {
  369.         return $this->_readDataOnly;
  370.     }
  371.  
  372.     /**
  373.      * Set read data only
  374.      *
  375.      * @param boolean $pValue 
  376.      */
  377.     public function setReadDataOnly($pValue = false)
  378.     {
  379.         $this->_readDataOnly = $pValue;
  380.     }
  381.  
  382.     /**
  383.      * Get which sheets to load
  384.      *
  385.      * @return mixed 
  386.      */
  387.     public function getLoadSheetsOnly()
  388.     {
  389.         return $this->_loadSheetsOnly;
  390.     }
  391.  
  392.     /**
  393.      * Set which sheets to load
  394.      *
  395.      * @param mixed $value 
  396.      */
  397.     public function setLoadSheetsOnly($value = null)
  398.     {
  399.         $this->_loadSheetsOnly = is_array($value) ?
  400.             $value : array($value);
  401.     }
  402.  
  403.     /**
  404.      * Set all sheets to load
  405.      */
  406.     public function setLoadAllSheets()
  407.     {
  408.         $this->_loadSheetsOnly = null;
  409.     }
  410.  
  411.     /**
  412.      * Read filter
  413.      *
  414.      * @return PHPExcel_Reader_IReadFilter 
  415.      */
  416.     public function getReadFilter() {
  417.         return $this->_readFilter;
  418.     }
  419.  
  420.     /**
  421.      * Set read filter
  422.      *
  423.      * @param PHPExcel_Reader_IReadFilter $pValue 
  424.      */
  425.     public function setReadFilter(PHPExcel_Reader_IReadFilter $pValue) {
  426.         $this->_readFilter = $pValue;
  427.     }
  428.  
  429.     /**
  430.      * Create a new PHPExcel_Reader_Excel5 instance
  431.      */
  432.     public function __construct() {
  433.         $this->_readFilter = new PHPExcel_Reader_DefaultReadFilter();
  434.     }
  435.  
  436.     /**
  437.      * Loads PHPExcel from file
  438.      *
  439.      * @param     string         $pFilename 
  440.      * @throws     Exception
  441.      */
  442.     public function load($pFilename)
  443.     {
  444.         // Check if file exists
  445.         if (!file_exists($pFilename)) {
  446.             throw new Exception("Could not open " . $pFilename . " for reading! File does not exist.");
  447.         }
  448.  
  449.         // Initialisations
  450.         $this->_phpExcel = new PHPExcel;
  451.         $this->_phpExcel->removeSheetByIndex(0);
  452.  
  453.         // Use ParseXL for the hard work.
  454.         $this->_ole = new PHPExcel_Shared_OLERead();
  455.  
  456.         $this->_encoderFunction = function_exists('mb_convert_encoding') ?
  457.             'mb_convert_encoding' : 'iconv';
  458.  
  459.         // get excel data
  460.         $res = $this->_ole->read($pFilename);
  461.  
  462.         // oops, something goes wrong (Darko Miljanovic)
  463.         if ($res === false) { // check error code
  464.             if($this->_ole->error == 1) { // bad file
  465.                 throw new Exception('The filename ' . $pFilename . ' is not readable');
  466.             } elseif($this->_ole->error == 2) {
  467.                 throw new Exception('The filename ' . $pFilename . ' is not recognised as an Excel file');
  468.             }
  469.             // check other error codes here (eg bad fileformat, etc...)
  470.         }
  471.  
  472.         $this->_data = $this->_ole->getWorkBook();
  473.  
  474.         // total byte size of Excel data (workbook global substream + sheet substreams)
  475.         $this->_dataSize = strlen($this->_data);
  476.  
  477.         $this->_pos = 0;
  478.  
  479.         // Parse Workbook Global Substream
  480.         while ($this->_pos < $this->_dataSize) {
  481.             $code = $this->_GetInt2d($this->_data, $this->_pos);
  482.  
  483.             switch ($code) {
  484.                 case self::XLS_Type_BOF:
  485.                     $pos = $this->_pos;
  486.                     $length = $this->_GetInt2d($this->_data, $pos + 2);
  487.                     $recordData = substr($this->_data, $pos + 4, $length);
  488.  
  489.                     // offset: 0; size: 2; BIFF version
  490.                     $this->_version = $this->_GetInt2d($this->_data, $pos + 4);
  491.  
  492.                     if (($this->_version != self::XLS_BIFF8) && ($this->_version != self::XLS_BIFF7)) {
  493.                         return false;
  494.                     }
  495.  
  496.                     // offset: 2; size: 2; type of stream
  497.                     $substreamType = $this->_GetInt2d($this->_data, $pos + 6);
  498.                     if ($substreamType != self::XLS_WorkbookGlobals) {
  499.                         return false;
  500.                     }
  501.                     $this->_pos += 4 + $length;
  502.                     break;
  503.  
  504.                 case self::XLS_Type_FILEPASS:        $this->_readFilepass();            break;
  505.                 case self::XLS_Type_CODEPAGE:        $this->_readCodepage();            break;
  506.                 case self::XLS_Type_DATEMODE:        $this->_readDateMode();            break;
  507.                 case self::XLS_Type_FONT:            $this->_readFont();                break;
  508.                 case self::XLS_Type_FORMAT:            $this->_readFormat();            break;
  509.                 case self::XLS_Type_XF:                $this->_readXf();                break;
  510.                 case self::XLS_Type_STYLE:            $this->_readStyle();            break;
  511.                 case self::XLS_Type_PALETTE:        $this->_readPalette();            break;
  512.                 case self::XLS_Type_SHEET:            $this->_readSheet();            break;
  513.                 case self::XLS_Type_EXTERNALBOOK:    $this->_readExternalBook();        break;
  514.                 case self::XLS_Type_EXTERNSHEET:    $this->_readExternSheet();        break;
  515.                 case self::XLS_Type_DEFINEDNAME:    $this->_readDefinedName();        break;
  516.                 case self::XLS_Type_MSODRAWINGGROUP:    $this->_readMsoDrawingGroup();    break;
  517.                 case self::XLS_Type_SST:            $this->_readSst();                break;
  518.                 case self::XLS_Type_EOF:            $this->_readDefault();            break 2;
  519.                 default:                            $this->_readDefault();            break;
  520.             }
  521.         }
  522.  
  523.         // Resolve indexed colors for font, fill, and border colors
  524.         // Cannot be resolved already in XF record, because PALETTE record comes afterwards
  525.         if (!$this->_readDataOnly) {
  526.             foreach ($this->_fonts as &$font) {
  527.                 $font['color'] = $this->_readColor($font['colorIndex']);
  528.             }
  529.             
  530.             foreach ($this->_xf as &$xf) {
  531.                 // fonts
  532.                 $xf['font']['color'] = $this->_readColor($xf['font']['colorIndex']);
  533.  
  534.                 // fill start and end color
  535.                 $xf['fill']['startcolor'] = $this->_readColor($xf['fill']['startcolorIndex']);
  536.                 $xf['fill']['endcolor'] = $this->_readColor($xf['fill']['endcolorIndex']);
  537.  
  538.                 // border colors
  539.                 $xf['borders']['top']['color']    = $this->_readColor($xf['borders']['top']['colorIndex']);
  540.                 $xf['borders']['right']['color']  = $this->_readColor($xf['borders']['right']['colorIndex']);
  541.                 $xf['borders']['bottom']['color'] = $this->_readColor($xf['borders']['bottom']['colorIndex']);
  542.                 $xf['borders']['left']['color']   = $this->_readColor($xf['borders']['left']['colorIndex']);
  543.             }
  544.  
  545.             foreach ($this->_builtInStyles as &$builtInStyle) {
  546.                 // fonts
  547.                 $builtInStyle['font']['color'] = $this->_readColor($builtInStyle['font']['colorIndex']);
  548.  
  549.                 // fill start and end color
  550.                 $builtInStyle['fill']['startcolor'] = $this->_readColor($builtInStyle['fill']['startcolorIndex']);
  551.                 $builtInStyle['fill']['endcolor'] = $this->_readColor($builtInStyle['fill']['endcolorIndex']);
  552.  
  553.                 // border colors
  554.                 $builtInStyle['borders']['top']['color']    = $this->_readColor($builtInStyle['borders']['top']['colorIndex']);
  555.                 $builtInStyle['borders']['right']['color']  = $this->_readColor($builtInStyle['borders']['right']['colorIndex']);
  556.                 $builtInStyle['borders']['bottom']['color'] = $this->_readColor($builtInStyle['borders']['bottom']['colorIndex']);
  557.                 $builtInStyle['borders']['left']['color']   = $this->_readColor($builtInStyle['borders']['left']['colorIndex']);
  558.             }
  559.         }
  560.  
  561.         // treat MSODRAWINGGROUP records, workbook-level Escher
  562.         if (!$this->_readDataOnly && $this->_drawingGroupData) {
  563.             $escherWorkbook = new PHPExcel_Shared_Escher();
  564.             $reader = new PHPExcel_Reader_Excel5_Escher($escherWorkbook);
  565.             $escherWorkbook = $reader->load($this->_drawingGroupData);
  566.  
  567.             // debug Escher stream
  568.             //$debug = new Debug_Escher(new PHPExcel_Shared_Escher());
  569.             //$debug->load($this->_drawingGroupData);
  570.         }
  571.  
  572.         // Parse the individual sheets
  573.         foreach ($this->_sheets as $sheet) {
  574.  
  575.             // check if sheet should be skipped
  576.             if (isset($this->_loadSheetsOnly) && !in_array($sheet['name'], $this->_loadSheetsOnly)) {
  577.                 continue;
  578.             }
  579.  
  580.             // add sheet to PHPExcel object
  581.             $this->_phpSheet = $this->_phpExcel->createSheet();
  582.             $this->_phpSheet->setTitle($sheet['name']);
  583.  
  584.             // default style
  585.             if (!$this->_readDataOnly && isset($this->_builtInStyles[0])) {
  586.                 $this->_phpSheet->getDefaultStyle()->applyFromArray($this->_builtInStyles[0]);
  587.             }
  588.  
  589.             $this->_pos = $sheet['offset'];
  590.  
  591.             // Initialize isFitToPages. May change after reading SHEETPR record.
  592.             $this->_isFitToPages = false;
  593.  
  594.             // Initialize drawingData
  595.             $this->_drawingData = '';
  596.  
  597.             // Initialize objs
  598.             $this->_objs = array();
  599.  
  600.             while ($this->_pos < $this->_dataSize) {
  601.                 $code = $this->_GetInt2d($this->_data, $this->_pos);
  602.  
  603.                 switch ($code) {
  604.                     case self::XLS_Type_BOF:
  605.                         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  606.                         $recordData = substr($this->_data, $this->_pos + 4, $length);
  607.  
  608.                         // move stream pointer to next record
  609.                         $this->_pos += 4 + $length;
  610.  
  611.                         // do not use this version information for anything
  612.                         // it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream
  613.  
  614.                         // offset: 2; size: 2; type of the following data
  615.                         $substreamType = $this->_GetInt2d($recordData, 2);
  616.                         if ($substreamType != self::XLS_Worksheet) {
  617.                             break 2;
  618.                         }
  619.                         break;
  620.  
  621.                     case self::XLS_Type_PRINTGRIDLINES:            $this->_readPrintGridlines();            break;
  622.                     case self::XLS_Type_DEFAULTROWHEIGHT:        $this->_readDefaultRowHeight();            break;
  623.                     case self::XLS_Type_SHEETPR:                $this->_readSheetPr();                    break;
  624.                     case self::XLS_Type_HORIZONTALPAGEBREAKS:    $this->_readHorizontalPageBreaks();        break;
  625.                     case self::XLS_Type_VERTICALPAGEBREAKS:        $this->_readVerticalPageBreaks();        break;
  626.                     case self::XLS_Type_HEADER:                    $this->_readHeader();                    break;
  627.                     case self::XLS_Type_FOOTER:                    $this->_readFooter();                    break;
  628.                     case self::XLS_Type_HCENTER:                $this->_readHcenter();                    break;
  629.                     case self::XLS_Type_VCENTER:                $this->_readVcenter();                    break;
  630.                     case self::XLS_Type_LEFTMARGIN:                $this->_readLeftMargin();                break;
  631.                     case self::XLS_Type_RIGHTMARGIN:            $this->_readRightMargin();                break;
  632.                     case self::XLS_Type_TOPMARGIN:                $this->_readTopMargin();                break;
  633.                     case self::XLS_Type_BOTTOMMARGIN:            $this->_readBottomMargin();                break;
  634.                     case self::XLS_Type_PAGESETUP:                $this->_readPageSetup();                break;
  635.                     case self::XLS_Type_PROTECT:                $this->_readProtect();                    break;
  636.                     case self::XLS_Type_PASSWORD:                $this->_readPassword();                    break;
  637.                     case self::XLS_Type_DEFCOLWIDTH:            $this->_readDefColWidth();                break;
  638.                     case self::XLS_Type_COLINFO:                $this->_readColInfo();                    break;
  639.                     case self::XLS_Type_DIMENSION:                $this->_readDefault();                    break;
  640.                     case self::XLS_Type_ROW:                    $this->_readRow();                        break;
  641.                     case self::XLS_Type_DBCELL:                    $this->_readDefault();                    break;
  642.                     case self::XLS_Type_RK:                        $this->_readRk();                        break;
  643.                     case self::XLS_Type_LABELSST:                $this->_readLabelSst();                    break;
  644.                     case self::XLS_Type_MULRK:                    $this->_readMulRk();                    break;
  645.                     case self::XLS_Type_NUMBER:                    $this->_readNumber();                    break;
  646.                     case self::XLS_Type_FORMULA:                $this->_readFormula();                    break;
  647.                     case self::XLS_Type_BOOLERR:                $this->_readBoolErr();                    break;
  648.                     case self::XLS_Type_MULBLANK:                $this->_readMulBlank();                    break;
  649.                     case self::XLS_Type_LABEL:                    $this->_readLabel();                    break;
  650.                     case self::XLS_Type_BLANK:                    $this->_readBlank();                    break;
  651.                     case self::XLS_Type_MSODRAWING:                $this->_readMsoDrawing();                break;
  652.                     case self::XLS_Type_OBJ:                    $this->_readObj();                        break;
  653.                     case self::XLS_Type_WINDOW2:                $this->_readWindow2();                    break;
  654.                     case self::XLS_Type_SCL:                    $this->_readScl();                        break;
  655.                     case self::XLS_Type_PANE:                    $this->_readPane();                        break;
  656.                     case self::XLS_Type_MERGEDCELLS:            $this->_readMergedCells();                break;
  657.                     case self::XLS_Type_HYPERLINK:                $this->_readHyperLink();                break;
  658.                     case self::XLS_Type_RANGEPROTECTION:        $this->_readRangeProtection();            break;
  659.                     //case self::XLS_Type_IMDATA:                $this->_readImData();                    break;
  660.                     case self::XLS_Type_EOF:                    $this->_readDefault();                    break 2;
  661.                     default:                                    $this->_readDefault();                    break;
  662.                 }
  663.  
  664.             }
  665.  
  666.             // treat MSODRAWING records, sheet-level Escher
  667.             if (!$this->_readDataOnly && $this->_drawingData) {
  668.                 $escherWorksheet = new PHPExcel_Shared_Escher();
  669.                 $reader = new PHPExcel_Reader_Excel5_Escher($escherWorksheet);
  670.                 $escherWorksheet = $reader->load($this->_drawingData);
  671.  
  672.                 // debug Escher stream
  673.                 //$debug = new Debug_Escher(new PHPExcel_Shared_Escher());
  674.                 //$debug->load($this->_drawingData);
  675.  
  676.                 $spContainerCollection = $escherWorksheet->getDgContainer()->getSpgrContainer()->getSpContainerCollection();
  677.             }
  678.  
  679.             // treat OBJ records
  680.             foreach ($this->_objs as $n => $obj) {
  681.  
  682.                 // skip first shape container which holds the shape group, hence $n + 1
  683.                 $spContainer = $spContainerCollection[$n + 1];
  684.  
  685.                 switch ($obj['type']) {
  686.                 
  687.                 case 0x08:
  688.                     // picture
  689.  
  690.                     // get index to BSE entry (1-based)
  691.                     $BSEindex = $spContainer->getOPT(0x0104);
  692.                     $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection();
  693.                     $BSE = $BSECollection[$BSEindex - 1];
  694.                     $blipType = $BSE->getBlipType();
  695.                     $blip = $BSE->getBlip();
  696.                     
  697.                     $ih = imagecreatefromstring($blip->getData());
  698.                     $drawing = new PHPExcel_Worksheet_MemoryDrawing();
  699.                     $drawing->setImageResource($ih);
  700.  
  701.                     switch ($blipType) {
  702.  
  703.                     case PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_JPEG:
  704.                         $drawing->setRenderingFunction(PHPExcel_Worksheet_MemoryDrawing::RENDERING_JPEG);
  705.                         $drawing->setMimeType(PHPExcel_Worksheet_MemoryDrawing::MIMETYPE_JPEG);
  706.                         break;
  707.  
  708.                     case PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG:
  709.                         $drawing->setRenderingFunction(PHPExcel_Worksheet_MemoryDrawing::RENDERING_PNG);
  710.                         $drawing->setMimeType(PHPExcel_Worksheet_MemoryDrawing::MIMETYPE_PNG);
  711.                         break;
  712.                     }
  713.  
  714.                     $drawing->setWorksheet($this->_phpSheet);
  715.                     $drawing->setCoordinates($spContainer->getStartCoordinates());
  716.  
  717.                     break;
  718.  
  719.                 default:
  720.                     // other object type
  721.                     break;
  722.  
  723.                 }
  724.             }
  725.  
  726.         }
  727.  
  728.         // add the named ranges (defined names)
  729.         foreach ($this->_definedname as $definedName) {
  730.             if ($definedName['isBuiltInName']) {
  731.                 switch ($definedName['name']) {
  732.  
  733.                 case pack('C', 0x06):
  734.                     // print area
  735.                     //    in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2
  736.  
  737.                     $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
  738.  
  739.                     foreach ($ranges as $range) {
  740.                         // $range should look like this one of these
  741.                         //        Foo!$C$7:$J$66
  742.                         //        Bar!$A$1:$IV$2
  743.  
  744.                         $explodes = explode('!', $range);
  745.  
  746.                         if (count($explodes) == 2) {
  747.                             if ($docSheet = $this->_phpExcel->getSheetByName($explodes[0])) {
  748.                                 $extractedRange = $explodes[1];
  749.                                 $extractedRange = str_replace('$', '', $extractedRange);
  750.                                 $docSheet->getPageSetup()->setPrintArea($extractedRange);
  751.                             }
  752.                         }
  753.                     }
  754.                     break;
  755.  
  756.                 case pack('C', 0x07):
  757.                     // print titles (repeating rows)
  758.                     // Assuming BIFF8, there are 3 cases
  759.                     // 1. repeating rows
  760.                     //        formula looks like this: Sheet!$A$1:$IV$2
  761.                     //        rows 1-2 repeat
  762.                     // 2. repeating columns
  763.                     //        formula looks like this: Sheet!$A$1:$B$65536
  764.                     //        columns A-B repeat
  765.                     // 3. both repeating rows and repeating columns
  766.                     //        formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2
  767.  
  768.                     $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
  769.  
  770.                     foreach ($ranges as $range) {
  771.                         // $range should look like this one of these
  772.                         //        Sheet!$A$1:$B$65536
  773.                         //        Sheet!$A$1:$IV$2
  774.  
  775.                         $explodes = explode('!', $range);
  776.  
  777.                         if (count($explodes) == 2) {
  778.                             if ($docSheet = $this->_phpExcel->getSheetByName($explodes[0])) {
  779.  
  780.                                 $extractedRange = $explodes[1];
  781.                                 $extractedRange = str_replace('$', '', $extractedRange);
  782.  
  783.                                 $coordinateStrings = explode(':', $extractedRange);
  784.                                 if (count($coordinateStrings) == 2) {
  785.                                     list($firstColumn, $firstRow) = PHPExcel_Cell::coordinateFromString($coordinateStrings[0]);
  786.                                     list($lastColumn, $lastRow) = PHPExcel_Cell::coordinateFromString($coordinateStrings[1]);
  787.  
  788.                                     if ($firstColumn == 'A' and $lastColumn == 'IV') {
  789.                                         // then we have repeating rows
  790.                                         $docSheet->getPageSetup()->setRowsToRepeatAtTop(array($firstRow, $lastRow));
  791.                                     } elseif ($firstRow == 1 and $lastRow == 65536) {
  792.                                         // then we have repeating columns
  793.                                         $docSheet->getPageSetup()->setColumnsToRepeatAtLeft(array($firstColumn, $lastColumn));
  794.                                     }
  795.                                 }
  796.                             }
  797.                         }
  798.                     }
  799.                     break;
  800.  
  801.                 }
  802.             } else {
  803.                 // Extract range
  804.                 $explodes = explode('!', $definedName['formula']);
  805.  
  806.                 if (count($explodes) == 2) {
  807.                     if ($docSheet = $this->_phpExcel->getSheetByName($explodes[0])) {
  808.                         $extractedRange = $explodes[1];
  809.                         $extractedRange = str_replace('$', '', $extractedRange);
  810.  
  811.                         $this->_phpExcel->addNamedRange( new PHPExcel_NamedRange((string)$definedName['name'], $docSheet, $extractedRange, true) );
  812.                     }
  813.                 }
  814.             }
  815.         }
  816.  
  817.         return $this->_phpExcel;
  818.     }
  819.  
  820.     /**
  821.      * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record.
  822.      */
  823.     private function _readDefault()
  824.     {
  825.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  826.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  827.  
  828.         // move stream pointer to next record
  829.         $this->_pos += 4 + $length;
  830.     }
  831.     
  832.     /**
  833.      * FILEPASS
  834.      *
  835.      * This record is part of the File Protection Block. It
  836.      * contains information about the read/write password of the
  837.      * file. All record contents following this record will be
  838.      * encrypted.
  839.      *
  840.      * --    "OpenOffice.org's Documentation of the Microsoft
  841.      *         Excel File Format"
  842.      */
  843.     private function _readFilepass()
  844.     {
  845.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  846.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  847.  
  848.         // move stream pointer to next record
  849.         $this->_pos += 4 + $length;
  850.  
  851.         throw new Exception('Cannot read encrypted file');
  852.     }
  853.  
  854.     /**
  855.      * CODEPAGE
  856.      *
  857.      * This record stores the text encoding used to write byte
  858.      * strings, stored as MS Windows code page identifier.
  859.      *
  860.      * --    "OpenOffice.org's Documentation of the Microsoft
  861.      *         Excel File Format"
  862.      */
  863.     private function _readCodepage()
  864.     {
  865.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  866.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  867.  
  868.         // move stream pointer to next record
  869.         $this->_pos += 4 + $length;
  870.  
  871.         // offset: 0; size: 2; code page identifier
  872.         $codepage = $this->_GetInt2d($recordData, 0);
  873.  
  874.         switch ($codepage) {
  875.  
  876.         case 367: // ASCII
  877.             $this->_codepage ="ASCII";
  878.             break;
  879.  
  880.         case 437: //OEM US
  881.             $this->_codepage ="CP437";
  882.             break;
  883.  
  884.         case 720: //OEM Arabic
  885.             // currently not supported by libiconv
  886.             $this->_codepage = "";
  887.             break;
  888.  
  889.         case 737: //OEM Greek
  890.             $this->_codepage ="CP737";
  891.             break;
  892.  
  893.         case 775: //OEM Baltic
  894.             $this->_codepage ="CP775";
  895.             break;
  896.  
  897.         case 850: //OEM Latin I
  898.             $this->_codepage ="CP850";
  899.             break;
  900.  
  901.         case 852: //OEM Latin II (Central European)
  902.             $this->_codepage ="CP852";
  903.             break;
  904.  
  905.         case 855: //OEM Cyrillic
  906.             $this->_codepage ="CP855";
  907.             break;
  908.  
  909.         case 857: //OEM Turkish
  910.             $this->_codepage ="CP857";
  911.             break;
  912.  
  913.         case 858: //OEM Multilingual Latin I with Euro
  914.             $this->_codepage ="CP858";
  915.             break;
  916.  
  917.         case 860: //OEM Portugese
  918.             $this->_codepage ="CP860";
  919.             break;
  920.  
  921.         case 861: //OEM Icelandic
  922.             $this->_codepage ="CP861";
  923.             break;
  924.  
  925.         case 862: //OEM Hebrew
  926.             $this->_codepage ="CP862";
  927.             break;
  928.  
  929.         case 863: //OEM Canadian (French)
  930.             $this->_codepage ="CP863";
  931.             break;
  932.  
  933.         case 864: //OEM Arabic
  934.             $this->_codepage ="CP864";
  935.             break;
  936.  
  937.         case 865: //OEM Nordic
  938.             $this->_codepage ="CP865";
  939.             break;
  940.  
  941.         case 866: //OEM Cyrillic (Russian)
  942.             $this->_codepage ="CP866";
  943.             break;
  944.  
  945.         case 869: //OEM Greek (Modern)
  946.             $this->_codepage ="CP869";
  947.             break;
  948.  
  949.         case 874: //ANSI Thai
  950.             $this->_codepage ="CP874";
  951.             break;
  952.  
  953.         case 932: //ANSI Japanese Shift-JIS
  954.             $this->_codepage ="CP932";
  955.             break;
  956.  
  957.         case 936: //ANSI Chinese Simplified GBK
  958.             $this->_codepage ="CP936";
  959.             break;
  960.  
  961.         case 949: //ANSI Korean (Wansung)
  962.             $this->_codepage ="CP949";
  963.             break;
  964.  
  965.         case 950: //ANSI Chinese Traditional BIG5
  966.             $this->_codepage ="CP950";
  967.             break;
  968.  
  969.         case 1200: //UTF-16 (BIFF8)
  970.             $this->_codepage ="UTF-16LE";
  971.             break;
  972.  
  973.         case 1250:// ANSI Latin II (Central European)
  974.             $this->_codepage ="CP1250";
  975.             break;
  976.  
  977.         case 1251: //ANSI Cyrillic
  978.             $this->_codepage ="CP1251";
  979.             break;
  980.  
  981.         case 1252: //ANSI Latin I (BIFF4-BIFF7)
  982.             $this->_codepage ="CP1252";
  983.             break;
  984.  
  985.         case 1253: //ANSI Greek
  986.             $this->_codepage ="CP1253";
  987.             break;
  988.  
  989.         case 1254: //ANSI Turkish
  990.             $this->_codepage ="CP1254";
  991.             break;
  992.  
  993.         case 1255: //ANSI Hebrew
  994.             $this->_codepage ="CP1255";
  995.             break;
  996.  
  997.         case 1256: //ANSI Arabic
  998.             $this->_codepage ="CP1256";
  999.             break;
  1000.  
  1001.         case 1257: //ANSI Baltic
  1002.             $this->_codepage ="CP1257";
  1003.             break;
  1004.  
  1005.         case 1258: //ANSI Vietnamese
  1006.             $this->_codepage ="CP1258";
  1007.             break;
  1008.  
  1009.         case 1361: //ANSI Korean (Johab)
  1010.             $this->_codepage ="CP1361";
  1011.             break;
  1012.  
  1013.         case 10000: //Apple Roman
  1014.             // currently not supported by libiconv
  1015.             $this->_codepage = "";
  1016.             break;
  1017.  
  1018.         case 32768: //Apple Roman
  1019.             // currently not supported by libiconv
  1020.             $this->_codepage = "";
  1021.             break;
  1022.  
  1023.         case 32769: //ANSI Latin I (BIFF2-BIFF3)
  1024.             // currently not supported by libiconv
  1025.             $this->_codepage = "";
  1026.             break;
  1027.  
  1028.         }
  1029.     }
  1030.  
  1031.     /**
  1032.      * DATEMODE
  1033.      *
  1034.      * This record specifies the base date for displaying date
  1035.      * values. All dates are stored as count of days past this
  1036.      * base date. In BIFF2-BIFF4 this record is part of the
  1037.      * Calculation Settings Block. In BIFF5-BIFF8 it is
  1038.      * stored in the Workbook Globals Substream.
  1039.      *
  1040.      * --    "OpenOffice.org's Documentation of the Microsoft
  1041.      *         Excel File Format"
  1042.      */
  1043.     private function _readDateMode()
  1044.     {
  1045.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  1046.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  1047.  
  1048.         // move stream pointer to next record
  1049.         $this->_pos += 4 + $length;
  1050.  
  1051.         // offset: 0; size: 2; 0 = base 1900, 1 = base 1904
  1052.         if (ord($recordData{0}) == 1) {
  1053.             PHPExcel_Shared_Date::setExcelCalendar(PHPExcel_Shared_Date::CALENDAR_MAC_1904);
  1054.         }
  1055.     }
  1056.  
  1057.     /**
  1058.      * Read a FONT record
  1059.      */
  1060.     private function _readFont()
  1061.     {
  1062.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  1063.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  1064.  
  1065.         // move stream pointer to next record
  1066.         $this->_pos += 4 + $length;
  1067.  
  1068.         if (!$this->_readDataOnly) {
  1069.             $font = array();
  1070.             // offset: 0; size: 2; height of the font (in twips = 1/20 of a point)
  1071.             $size = $this->_GetInt2d($recordData, 0);
  1072.             $font['size'] = $size / 20;
  1073.  
  1074.             // offset: 2; size: 2; option flags
  1075.                 // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8)
  1076.                 // bit: 1; mask 0x0002; italic
  1077.                 $isItalic = (0x0002 & $this->_GetInt2d($recordData, 2)) >> 1;
  1078.                 if ($isItalic) $font['italic'] = true;
  1079.  
  1080.                 // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8)
  1081.                 // bit: 3; mask 0x0008; strike
  1082.                 $isStrike = (0x0008 & $this->_GetInt2d($recordData, 2)) >> 3;
  1083.                 if ($isStrike) $font['strike'] = true;
  1084.  
  1085.             // offset: 4; size: 2; colour index
  1086.             $colorIndex = $this->_GetInt2d($recordData, 4);
  1087.             $font['colorIndex'] = $colorIndex;
  1088.  
  1089.             // offset: 6; size: 2; font weight
  1090.             $weight = $this->_GetInt2d($recordData, 6);
  1091.             switch ($weight) {
  1092.                 case 0x02BC: $font['bold'] = true;
  1093.             }
  1094.  
  1095.             // offset: 8; size: 2; escapement type
  1096.             $escapement = $this->_GetInt2d($recordData, 8);
  1097.             switch ($escapement) {
  1098.                 case 0x0001: $font['superScript'] = true; break;
  1099.                 case 0x0002: $font['subScript'] = true; break;
  1100.             }
  1101.  
  1102.             // offset: 10; size: 1; underline type
  1103.             $underlineType = ord($recordData[10]);
  1104.             switch ($underlineType) {
  1105.                 case 0x00: break; // no underline
  1106.                 case 0x01: $font['underline'] = PHPExcel_Style_Font::UNDERLINE_SINGLE; break;
  1107.                 case 0x02: $font['underline'] = PHPExcel_Style_Font::UNDERLINE_DOUBLE; break;
  1108.                 case 0x21: $font['underline'] = PHPExcel_Style_Font::UNDERLINE_SINGLEACCOUNTING; break;
  1109.                 case 0x22: $font['underline'] = PHPExcel_Style_Font::UNDERLINE_DOUBLEACCOUNTING; break;
  1110.             }
  1111.  
  1112.             // offset: 11; size: 1; font family
  1113.             // offset: 12; size: 1; character set
  1114.             // offset: 13; size: 1; not used
  1115.             // offset: 14; size: var; font name
  1116.             if ($this->_version == self::XLS_BIFF8) {
  1117.                 $string = $this->_readUnicodeStringShort(substr($recordData, 14));
  1118.             } else {
  1119.                 $string = $this->_readByteStringShort(substr($recordData, 14));
  1120.             }
  1121.             $font['name'] = $string['value'];
  1122.  
  1123.             $this->_fonts[] = $font;
  1124.         }
  1125.     }
  1126.  
  1127.     /**
  1128.      * FORMAT
  1129.      *
  1130.      * This record contains information about a number format.
  1131.      * All FORMAT records occur together in a sequential list.
  1132.      *
  1133.      * In BIFF2-BIFF4 other records referencing a FORMAT record
  1134.      * contain a zero-based index into this list. From BIFF5 on
  1135.      * the FORMAT record contains the index itself that will be
  1136.      * used by other records.
  1137.      *
  1138.      * --    "OpenOffice.org's Documentation of the Microsoft
  1139.      *         Excel File Format"
  1140.      */
  1141.     private function _readFormat()
  1142.     {
  1143.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  1144.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  1145.  
  1146.         // move stream pointer to next record
  1147.         $this->_pos += 4 + $length;
  1148.  
  1149.         if (!$this->_readDataOnly) {
  1150.             $indexCode = $this->_GetInt2d($recordData, 0);
  1151.             
  1152.             if ($this->_version == self::XLS_BIFF8) {
  1153.                 $string = $this->_readUnicodeStringLong(substr($recordData, 2));
  1154.             } else {
  1155.                 // BIFF7
  1156.                 $string = $this->_readByteStringShort(substr($recordData, 2));
  1157.             }
  1158.             
  1159.             $formatString = $string['value'];
  1160.             $this->_formats[$indexCode] = $formatString;
  1161.         }
  1162.     }
  1163.  
  1164.     /**
  1165.      * XF - Extended Format
  1166.      *
  1167.      * This record contains formatting information for cells,
  1168.      * rows, columns or styles.
  1169.      *
  1170.      * --    "OpenOffice.org's Documentation of the Microsoft
  1171.      *         Excel File Format"
  1172.      */
  1173.     private function _readXf()
  1174.     {
  1175.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  1176.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  1177.  
  1178.         // move stream pointer to next record
  1179.         $this->_pos += 4 + $length;
  1180.  
  1181.         $style = array();
  1182.         if (!$this->_readDataOnly) {
  1183.             // offset:  0; size: 2; Index to FONT record
  1184.             if ($this->_GetInt2d($recordData, 0) < 4) {
  1185.                 $fontIndex = $this->_GetInt2d($recordData, 0);
  1186.             } else {
  1187.                 // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
  1188.                 // check the OpenOffice documentation of the FONT record
  1189.                 $fontIndex = $this->_GetInt2d($recordData, 0) - 1;
  1190.             }
  1191.             $style['font'] = $this->_fonts[$fontIndex];
  1192.  
  1193.             // offset:  2; size: 2; Index to FORMAT record
  1194.             $numberFormatIndex = $this->_GetInt2d($recordData, 2);
  1195.             if (isset($this->_formats[$numberFormatIndex])) {
  1196.                 // then we have user-defined format code
  1197.                 $numberformat = array('code' => $this->_formats[$numberFormatIndex]);
  1198.             } elseif ($code = PHPExcel_Style_NumberFormat::builtInFormatCode($numberFormatIndex)) {
  1199.                 // then we have built-in format code
  1200.                 $numberformat = array('code' => $code);
  1201.             } else {
  1202.                 // we set the general format code
  1203.                 $numberformat = array('code' => 'General');
  1204.             }
  1205.             $style['numberformat'] = $numberformat;
  1206.  
  1207.             $style['protection'] = array();
  1208.             // offset:  4; size: 2; XF type, cell protection, and parent style XF
  1209.             // bit 2-0; mask 0x0007; XF_TYPE_PROT
  1210.             $xfTypeProt = $this->_GetInt2d($recordData, 4);
  1211.             // bit 0; mask 0x01; 1 = cell is locked
  1212.             $isLocked = (0x01 & $xfTypeProt) >> 0;
  1213.             $style['protection']['locked'] = $isLocked ?
  1214.                 PHPExcel_Style_Protection::PROTECTION_INHERIT : PHPExcel_Style_Protection::PROTECTION_UNPROTECTED;
  1215.             // bit 1; mask 0x02; 1 = Formula is hidden
  1216.             $isHidden = (0x02 & $xfTypeProt) >> 1;
  1217.             $style['protection']['hidden'] = $isHidden ?
  1218.                 PHPExcel_Style_Protection::PROTECTION_PROTECTED : PHPExcel_Style_Protection::PROTECTION_UNPROTECTED;
  1219.             // bit 2;
  1220.  
  1221.             $style['alignment'] = array();
  1222.             // offset:  6; size: 1; Alignment and text break
  1223.             // bit 2-0, mask 0x07; horizontal alignment
  1224.             $horAlign = (0x07 & ord($recordData[6])) >> 0;
  1225.             switch ($horAlign) {
  1226.                 case 0: $style['alignment']['horizontal'] = PHPExcel_Style_Alignment::HORIZONTAL_GENERAL; break;
  1227.                 case 1: $style['alignment']['horizontal'] = PHPExcel_Style_Alignment::HORIZONTAL_LEFT; break;
  1228.                 case 2: $style['alignment']['horizontal'] = PHPExcel_Style_Alignment::HORIZONTAL_CENTER; break;
  1229.                 case 3: $style['alignment']['horizontal'] = PHPExcel_Style_Alignment::HORIZONTAL_RIGHT; break;
  1230.                 case 5: $style['alignment']['horizontal'] = PHPExcel_Style_Alignment::HORIZONTAL_JUSTIFY; break;
  1231.             }
  1232.             // bit 3, mask 0x08; wrap text
  1233.             $wrapText = (0x08 & ord($recordData[6])) >> 3;
  1234.             switch ($wrapText) {
  1235.                 case 0: $style['alignment']['wrap'] = false; break;
  1236.                 case 1: $style['alignment']['wrap'] = true; break;
  1237.             }
  1238.             // bit 6-4, mask 0x70; vertical alignment
  1239.             $vertAlign = (0x70 & ord($recordData[6])) >> 4;
  1240.             switch ($vertAlign) {
  1241.                 case 0: $style['alignment']['vertical'] = PHPExcel_Style_Alignment::VERTICAL_TOP; break;
  1242.                 case 1: $style['alignment']['vertical'] = PHPExcel_Style_Alignment::VERTICAL_CENTER; break;
  1243.                 case 2: $style['alignment']['vertical'] = PHPExcel_Style_Alignment::VERTICAL_BOTTOM; break;
  1244.                 case 3: $style['alignment']['vertical'] = PHPExcel_Style_Alignment::VERTICAL_JUSTIFY; break;
  1245.             }
  1246.  
  1247.             // initialize fill
  1248.             $style['fill'] = array();
  1249.  
  1250.             // initialize borders
  1251.             $style['borders'] = array(
  1252.                 'left' => array(),
  1253.                 'right' => array(),
  1254.                 'top' => array(),
  1255.                 'bottom' => array(),
  1256.             );
  1257.  
  1258.             if ($this->_version == self::XLS_BIFF8) {
  1259.                 // offset:  7; size: 1; XF_ROTATION: Text rotation angle
  1260.                     $angle = ord($recordData[7]);
  1261.                     $rotation = 0;
  1262.                     if ($angle <= 90) {
  1263.                         $rotation = $angle;
  1264.                     } else if ($angle <= 180) {
  1265.                         $rotation = 90 - $angle;
  1266.                     } else if ($angle == 255) {
  1267.                         $rotation = -165;
  1268.                     }
  1269.                     $style['alignment']['rotation'] = $rotation;
  1270.  
  1271.                 // offset:  8; size: 1; Indentation, shrink to cell size, and text direction
  1272.                     // bit: 3-0; mask: 0x0F; indent level
  1273.                     $indent = (0x0F & ord($recordData[8])) >> 0;
  1274.                     $style['alignment']['indent'] = $indent;
  1275.                     
  1276.                     // bit: 4; mask: 0x10; 1 = shrink content to fit into cell
  1277.                     $shrinkToFit = (0x10 & ord($recordData[8])) >> 4;
  1278.                     switch ($shrinkToFit) {
  1279.                         case 0: $style['alignment']['shrinkToFit'] = false; break;
  1280.                         case 1: $style['alignment']['shrinkToFit'] = true; break;
  1281.                     }
  1282.  
  1283.                 // offset:  9; size: 1; Flags used for attribute groups
  1284.  
  1285.                 // offset: 10; size: 4; Cell border lines and background area
  1286.                     // bit: 3-0; mask: 0x0000000F; left style
  1287.                     if ($bordersLeftStyle = $this->_mapBorderStyle((0x0000000F & $this->_GetInt4d($recordData, 10)) >> 0)) {
  1288.                         $style['borders']['left']['style'] = $bordersLeftStyle;
  1289.                     }
  1290.                     // bit: 7-4; mask: 0x000000F0; right style
  1291.                     if ($bordersRightStyle = $this->_mapBorderStyle((0x000000F0 & $this->_GetInt4d($recordData, 10)) >> 4)) {
  1292.                         $style['borders']['right']['style'] = $bordersRightStyle;
  1293.                     }
  1294.                     // bit: 11-8; mask: 0x00000F00; top style
  1295.                     if ($bordersTopStyle = $this->_mapBorderStyle((0x00000F00 & $this->_GetInt4d($recordData, 10)) >> 8)) {
  1296.                         $style['borders']['top']['style'] = $bordersTopStyle;
  1297.                     }
  1298.                     // bit: 15-12; mask: 0x0000F000; bottom style
  1299.                     if ($bordersBottomStyle = $this->_mapBorderStyle((0x0000F000 & $this->_GetInt4d($recordData, 10)) >> 12)) {
  1300.                         $style['borders']['bottom']['style'] = $bordersBottomStyle;
  1301.                     }
  1302.                     // bit: 22-16; mask: 0x007F0000; left color
  1303.                     $style['borders']['left']['colorIndex'] = (0x007F0000 & $this->_GetInt4d($recordData, 10)) >> 16;
  1304.  
  1305.                     // bit: 29-23; mask: 0x3F800000; right color
  1306.                     $style['borders']['right']['colorIndex'] = (0x3F800000 & $this->_GetInt4d($recordData, 10)) >> 23;
  1307.  
  1308.                 // offset: 14; size: 4;
  1309.                     // bit: 6-0; mask: 0x0000007F; top color
  1310.                     $style['borders']['top']['colorIndex'] = (0x0000007F & $this->_GetInt4d($recordData, 14)) >> 0;
  1311.  
  1312.                     // bit: 13-7; mask: 0x00003F80; bottom color
  1313.                     $style['borders']['bottom']['colorIndex'] = (0x00003F80 & $this->_GetInt4d($recordData, 14)) >> 7;
  1314.  
  1315.                     // bit: 31-26; mask: 0xFC000000 fill pattern
  1316.                     if ($fillType = $this->_mapFillPattern((0xFC000000 & $this->_GetInt4d($recordData, 14)) >> 26)) {
  1317.                         $style['fill']['type'] = $fillType;
  1318.                     }
  1319.                 // offset: 18; size: 2; pattern and background colour
  1320.                     // bit: 6-0; mask: 0x007F; color index for pattern color
  1321.                     $style['fill']['startcolorIndex'] = (0x007F & $this->_GetInt2d($recordData, 18)) >> 0;
  1322.                     
  1323.                     // bit: 13-7; mask: 0x3F80; color index for pattern background
  1324.                     $style['fill']['endcolorIndex'] = (0x3F80 & $this->_GetInt2d($recordData, 18)) >> 7;
  1325.             } else {
  1326.                 // BIFF5
  1327.  
  1328.                 // offset: 7; size: 1; Text orientation and flags
  1329.                 $orientationAndFlags = ord($recordData[7]);
  1330.  
  1331.                 // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation 
  1332.                 $xfOrientation = (0x03 & $orientationAndFlags) >> 0;
  1333.                 switch ($xfOrientation) {
  1334.                     case 0: $style['alignment']['rotation'] = 0; break;
  1335.                     case 1: $style['alignment']['rotation'] = -165; break;
  1336.                     case 2: $style['alignment']['rotation'] = 90; break;
  1337.                     case 3: $style['alignment']['rotation'] = -90; break;
  1338.                 }
  1339.  
  1340.                 // offset: 8; size: 4; cell border lines and background area
  1341.                 $borderAndBackground = $this->_GetInt4d($recordData, 8);
  1342.  
  1343.                 // bit: 6-0; mask: 0x0000007F; color index for pattern color
  1344.                 $style['fill']['startcolorIndex'] = (0x0000007F & $borderAndBackground) >> 0;
  1345.                 
  1346.                 // bit: 13-7; mask: 0x00003F80; color index for pattern background
  1347.                 $style['fill']['endcolorIndex'] = (0x00003F80 & $borderAndBackground) >> 7;
  1348.                 
  1349.                 // bit: 21-16; mask: 0x003F0000; fill pattern
  1350.                 $style['fill']['type'] = $this->_mapFillPattern((0x003F0000 & $borderAndBackground) >> 16);
  1351.                 
  1352.                 // bit: 24-22; mask: 0x01C00000; bottom line style
  1353.                 $style['borders']['bottom']['style'] = $this->_mapBorderStyle((0x01C00000 & $borderAndBackground) >> 22);
  1354.                 
  1355.                 // bit: 31-25; mask: 0xFE000000; bottom line color
  1356.                 $style['borders']['bottom']['colorIndex'] = (0xFE000000 & $borderAndBackground) >> 25;
  1357.                 
  1358.                 // offset: 12; size: 4; cell border lines
  1359.                 $borderLines = $this->_GetInt4d($recordData, 12);
  1360.                 
  1361.                 // bit: 2-0; mask: 0x00000007; top line style
  1362.                 $style['borders']['top']['style'] = $this->_mapBorderStyle((0x00000007 & $borderLines) >> 0);
  1363.                 
  1364.                 // bit: 5-3; mask: 0x00000038; left line style
  1365.                 $style['borders']['left']['style'] = $this->_mapBorderStyle((0x00000038 & $borderLines) >> 3);
  1366.                 
  1367.                 // bit: 8-6; mask: 0x000001C0; right line style
  1368.                 $style['borders']['right']['style'] = $this->_mapBorderStyle((0x000001C0 & $borderLines) >> 6);
  1369.                 
  1370.                 // bit: 15-9; mask: 0x0000FE00; top line color index
  1371.                 $style['borders']['top']['colorIndex'] = (0x0000FE00 & $borderLines) >> 9;
  1372.                 
  1373.                 // bit: 22-16; mask: 0x007F0000; left line color index
  1374.                 $style['borders']['left']['colorIndex'] = (0x007F0000 & $borderLines) >> 16;
  1375.                 
  1376.                 // bit: 29-23; mask: 0x3F800000; right line color index
  1377.                 $style['borders']['right']['colorIndex'] = (0x3F800000 & $borderLines) >> 23;
  1378.             }
  1379.             $this->_xf[] = $style;
  1380.         }
  1381.     }
  1382.  
  1383.     /**
  1384.      * Read STYLE record
  1385.      */
  1386.     private function _readStyle()
  1387.     {
  1388.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  1389.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  1390.  
  1391.         // move stream pointer to next record
  1392.         $this->_pos += 4 + $length;
  1393.  
  1394.         if (!$this->_readDataOnly) {
  1395.             // offset: 0; size: 2; index to XF record and flag for built-in style
  1396.             $ixfe = $this->_GetInt2d($recordData, 0);
  1397.  
  1398.             // bit: 11-0; mask 0x0FFF; index to XF record
  1399.             $xfIndex = (0x0FFF & $ixfe) >> 0;
  1400.  
  1401.             // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style
  1402.             $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15);
  1403.  
  1404.             if ($isBuiltIn) {
  1405.                 // offset: 2; size: 1; identifier for built-in style
  1406.                 $builtInId = ord($recordData{2});
  1407.  
  1408.                 $this->_builtInStyles[$builtInId] = $this->_xf[$xfIndex];
  1409.  
  1410.             } else {
  1411.                 // user-defined; not supported by PHPExcel
  1412.             }
  1413.         }
  1414.     }
  1415.  
  1416.     /**
  1417.      * Read PALETTE record
  1418.      */
  1419.     private function _readPalette()
  1420.     {
  1421.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  1422.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  1423.  
  1424.         // move stream pointer to next record
  1425.         $this->_pos += 4 + $length;
  1426.  
  1427.         if (!$this->_readDataOnly) {
  1428.             // offset: 0; size: 2; number of following colors
  1429.             $nm = $this->_GetInt2d($recordData, 0);
  1430.  
  1431.             // list of RGB colors
  1432.             for ($i = 0; $i < $nm; ++$i) {
  1433.                 $rgb = substr($recordData, 2 + 4 * $i, 4);
  1434.                 $this->_palette[] = $this->_readRGB($rgb);
  1435.             }
  1436.         }
  1437.     }
  1438.  
  1439.     /**
  1440.      * SHEET
  1441.      *
  1442.      * This record is  located in the  Workbook Globals
  1443.      * Substream  and represents a sheet inside the workbook.
  1444.      * One SHEET record is written for each sheet. It stores the
  1445.      * sheet name and a stream offset to the BOF record of the
  1446.      * respective Sheet Substream within the Workbook Stream.
  1447.      *
  1448.      * --    "OpenOffice.org's Documentation of the Microsoft
  1449.      *         Excel File Format"
  1450.      */
  1451.     private function _readSheet()
  1452.     {
  1453.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  1454.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  1455.  
  1456.         // move stream pointer to next record
  1457.         $this->_pos += 4 + $length;
  1458.  
  1459.         // offset: 0; size: 4; absolute stream position of the BOF record of the sheet
  1460.         $rec_offset = $this->_GetInt4d($recordData, 0);
  1461.  
  1462.         // offset: 4; size: 1; sheet state
  1463.         $rec_typeFlag = ord($recordData{4});
  1464.  
  1465.         // offset: 5; size: 1; sheet type
  1466.         $rec_visibilityFlag = ord($recordData{5});
  1467.  
  1468.         // offset: 6; size: var; sheet name
  1469.         if ($this->_version == self::XLS_BIFF8) {
  1470.             $string = $this->_readUnicodeStringShort(substr($recordData, 6));
  1471.             $rec_name = $string['value'];
  1472.         } elseif ($this->_version == self::XLS_BIFF7) {
  1473.             $string = $this->_readByteStringShort(substr($recordData, 6));
  1474.             $rec_name = $string['value'];
  1475.         }
  1476.         $this->_sheets[] = array(
  1477.             'name' => $rec_name,
  1478.             'offset' => $rec_offset
  1479.         );
  1480.     }
  1481.  
  1482.     /**
  1483.      * Read EXTERNALBOOK record
  1484.      */
  1485.     private function _readExternalBook()
  1486.     {
  1487.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  1488.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  1489.  
  1490.         // move stream pointer to next record
  1491.         $this->_pos += 4 + $length;
  1492.  
  1493.         // offset within record data
  1494.         $offset = 0;
  1495.  
  1496.         // there are 4 types of records
  1497.         if (strlen($recordData) > 4) {
  1498.             // external reference
  1499.             // offset: 0; size: 2; number of sheet names ($nm)
  1500.             $nm = $this->_GetInt2d($recordData, 0);
  1501.             $offset += 2;
  1502.  
  1503.             // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length)
  1504.             $encodedUrlString = $this->_readUnicodeStringLong(substr($recordData, 2));
  1505.             $offset += $encodedUrlString['size'];
  1506.  
  1507.             // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length)
  1508.             $externalSheetNames = array();
  1509.             for ($i = 0; $i < $nm; ++$i) {
  1510.                 $externalSheetNameString = $this->_readUnicodeStringLong(substr($recordData, $offset));
  1511.                 $externalSheetNames[] = $externalSheetNameString['value'];
  1512.                 $offset += $externalSheetNameString['size'];
  1513.             }
  1514.  
  1515.             // store the record data
  1516.             $this->_externalBooks[] = array(
  1517.                 'type' => 'external',
  1518.                 'encodedUrl' => $encodedUrlString['value'],
  1519.                 'externalSheetNames' => $externalSheetNames,
  1520.             );
  1521.  
  1522.         } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) {
  1523.             // internal reference
  1524.             // offset: 0; size: 2; number of sheet in this document
  1525.             // offset: 2; size: 2; 0x01 0x04
  1526.             $this->_externalBooks[] = array(
  1527.                 'type' => 'internal',
  1528.             );
  1529.         } elseif (substr($recordData, 0, 4) == pack('VCC', 0x0001, 0x01, 0x3A)) {
  1530.             // add-in function
  1531.             // offset: 0; size: 2; 0x0001
  1532.             $this->_externalBooks[] = array(
  1533.                 'type' => 'addInFunction',
  1534.             );
  1535.         } elseif (substr($recordData, 0, 2) == pack('V', 0x0000)) {
  1536.             // DDE links, OLE links
  1537.             // offset: 0; size: 2; 0x0000
  1538.             // offset: 2; size: var; encoded source document name
  1539.             $this->_externalBooks[] = array(
  1540.                 'type' => 'DDEorOLE',
  1541.             );
  1542.         }
  1543.     }
  1544.  
  1545.     /**
  1546.      * Read EXTERNSHEET record
  1547.      */
  1548.     private function _readExternSheet()
  1549.     {
  1550.         $this->_pos = $this->_pos;
  1551.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  1552.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  1553.  
  1554.         // move stream pointer to next record
  1555.         $this->_pos += 4 + $length;
  1556.  
  1557.         // external sheet references provided for named cells
  1558.         if ($this->_version == self::XLS_BIFF8) {
  1559.             // offset: 0; size: 2; number of following ref structures
  1560.             $nm = $this->_GetInt2d($recordData, 0);
  1561.             for ($i = 0; $i < $nm; ++$i) {
  1562.                 $this->_ref[] = array(
  1563.                     // offset: 2 + 6 * $i; index to EXTERNALBOOK record
  1564.                     'externalBookIndex' => $this->_GetInt2d($recordData, 2 + 6 * $i),
  1565.                     // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record
  1566.                     'firstSheetIndex' => $this->_GetInt2d($recordData, 4 + 6 * $i),
  1567.                     // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record
  1568.                     'lastSheetIndex' => $this->_GetInt2d($recordData, 6 + 6 * $i),
  1569.                 );
  1570.             }
  1571.         }
  1572.     }
  1573.  
  1574.     /**
  1575.      * DEFINEDNAME
  1576.      *
  1577.      * This record is part of a Link Table. It contains the name
  1578.      * and the token array of an internal defined name. Token
  1579.      * arrays of defined names contain tokens with aberrant
  1580.      * token classes.
  1581.      *
  1582.      * --    "OpenOffice.org's Documentation of the Microsoft
  1583.      *         Excel File Format"
  1584.      */
  1585.     private function _readDefinedName()
  1586.     {
  1587.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  1588.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  1589.  
  1590.         // move stream pointer to next record
  1591.         $this->_pos += 4 + $length;
  1592.  
  1593.         if ($this->_version == self::XLS_BIFF8) {
  1594.             // retrieves named cells
  1595.  
  1596.             // offset: 0; size: 2; option flags
  1597.             $opts = $this->_GetInt2d($recordData, 0);
  1598.  
  1599.                 // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name
  1600.                 $isBuiltInName = (0x0020 & $opts) >> 5;
  1601.  
  1602.             // offset: 2; size: 1; keyboard shortcut
  1603.  
  1604.             // offset: 3; size: 1; length of the name (character count)
  1605.             $nlen = ord($recordData{3});
  1606.  
  1607.             // offset: 4; size: 2; size of the formula data
  1608.             $flen = $this->_GetInt2d($recordData, 4);
  1609.  
  1610.             // offset: 14; size: var; Name (Unicode string without length field)
  1611.             $string = $this->_readUnicodeString(substr($recordData, 14), $nlen);
  1612.  
  1613.             // offset: var; size: $flen; formula data
  1614.             $offset = 14 + $string['size'];
  1615.             $formulaStructure = pack('v', $flen) . substr($recordData, $offset, $flen);
  1616.  
  1617.             try {
  1618.                 $formula = $this->_getFormulaFromStructure($formulaStructure);
  1619.             } catch (Exception $e) {
  1620.                 $formula = '';
  1621.             }
  1622.  
  1623.             $this->_definedname[] = array(
  1624.                 'isBuiltInName' => $isBuiltInName,
  1625.                 'name' => $string['value'],
  1626.                 'formula' => $formula,
  1627.             );
  1628.         }
  1629.     }
  1630.  
  1631.     /**
  1632.      * Read MSODRAWINGGROUP record
  1633.      */
  1634.     private function _readMsoDrawingGroup()
  1635.     {
  1636.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  1637.  
  1638.         // get spliced record data
  1639.         $splicedRecordData = $this->_getSplicedRecordData();
  1640.         $recordData = $splicedRecordData['recordData'];
  1641.  
  1642.         $this->_drawingGroupData .= $recordData;
  1643.     }
  1644.  
  1645.     /**
  1646.      * SST - Shared String Table
  1647.      *
  1648.      * This record contains a list of all strings used anywhere
  1649.      * in the workbook. Each string occurs only once. The
  1650.      * workbook uses indexes into the list to reference the
  1651.      * strings.
  1652.      *
  1653.      * --    "OpenOffice.org's Documentation of the Microsoft
  1654.      *         Excel File Format"
  1655.      **/
  1656.  
  1657.     private function _readSst()
  1658.     {
  1659.         // offset within (spliced) record data
  1660.         $pos = 0;
  1661.  
  1662.         // get spliced record data
  1663.         $splicedRecordData = $this->_getSplicedRecordData();
  1664.  
  1665.         $recordData = $splicedRecordData['recordData'];
  1666.         $spliceOffsets = $splicedRecordData['spliceOffsets'];
  1667.  
  1668.         // offset: 0; size: 4; total number of strings in the workbook
  1669.         $pos += 4;
  1670.  
  1671.         // offset: 4; size: 4; number of following strings ($nm)
  1672.         $nm = $this->_GetInt4d($recordData, 4);
  1673.         $pos += 4;
  1674.  
  1675.         // loop through the Unicode strings (16-bit length)
  1676.         for ($i = 0; $i < $nm; ++$i) {
  1677.  
  1678.             // number of characters in the Unicode string
  1679.             $numChars = $this->_GetInt2d($recordData, $pos);
  1680.             $pos += 2;
  1681.  
  1682.             // option flags
  1683.             $optionFlags = ord($recordData[$pos]);
  1684.             ++$pos;
  1685.  
  1686.             // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed
  1687.             $isCompressed = (($optionFlags & 0x01) == 0) ;
  1688.  
  1689.             // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic
  1690.             $hasAsian = (($optionFlags & 0x04) != 0);
  1691.  
  1692.             // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text
  1693.             $hasRichText = (($optionFlags & 0x08) != 0);
  1694.  
  1695.             if ($hasRichText) {
  1696.                 // number of Rich-Text formatting runs
  1697.                 $formattingRuns = $this->_GetInt2d($recordData, $pos);
  1698.                 $pos += 2;
  1699.             }
  1700.  
  1701.             if ($hasAsian) {
  1702.                 // size of Asian phonetic setting
  1703.                 $extendedRunLength = $this->_GetInt4d($recordData, $pos);
  1704.                 $pos += 4;
  1705.             }
  1706.  
  1707.             // expected byte length of character array if not split
  1708.             $len = ($isCompressed) ? $numChars : $numChars * 2;
  1709.  
  1710.             // look up limit position
  1711.             foreach ($spliceOffsets as $spliceOffset) {
  1712.                 if ($pos < $spliceOffset) {
  1713.                     $limitpos = $spliceOffset;
  1714.                     break;
  1715.                 }
  1716.             }
  1717.  
  1718.             if ($pos + $len <= $limitpos) {
  1719.                 // character array is not split between records
  1720.  
  1721.                 $retstr = substr($recordData, $pos, $len);
  1722.                 $pos += $len;
  1723.  
  1724.             } else {
  1725.                 // character array is split between records
  1726.  
  1727.                 // first part of character array
  1728.                 $retstr = substr($recordData, $pos, $limitpos - $pos);
  1729.  
  1730.                 $bytesRead = $limitpos - $pos;
  1731.  
  1732.                 // remaining characters in Unicode string
  1733.                 $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));
  1734.  
  1735.                 $pos = $limitpos;
  1736.  
  1737.                 // keep reading the characters
  1738.                 while ($charsLeft > 0) {
  1739.  
  1740.                     // look up next limit position, in case the string span more than one continue record
  1741.                     foreach ($spliceOffsets as $spliceOffset) {
  1742.                         if ($pos < $spliceOffset) {
  1743.                             $limitpos = $spliceOffset;
  1744.                             break;
  1745.                         }
  1746.                     }
  1747.  
  1748.                     // repeated option flags
  1749.                     // OpenOffice.org documentation 5.21
  1750.                     $option = ord($recordData[$pos]);
  1751.                     ++$pos;
  1752.  
  1753.                     if ($isCompressed && ($option == 0)) {
  1754.                         // 1st fragment compressed
  1755.                         // this fragment compressed
  1756.                         $len = min($charsLeft, $limitpos - $pos);
  1757.                         $retstr .= substr($recordData, $pos, $len);
  1758.                         $charsLeft -= $len;
  1759.                         $isCompressed = true;
  1760.  
  1761.                     } elseif (!$isCompressed && ($option != 0)) {
  1762.                         // 1st fragment uncompressed
  1763.                         // this fragment uncompressed
  1764.                         $len = min($charsLeft * 2, $limitpos - $pos);
  1765.                         $retstr .= substr($recordData, $pos, $len);
  1766.                         $charsLeft -= $len / 2;
  1767.                         $isCompressed = false;
  1768.  
  1769.                     } elseif (!$isCompressed && ($option == 0)) {
  1770.                         // 1st fragment uncompressed
  1771.                         // this fragment compressed
  1772.                         $len = min($charsLeft, $limitpos - $pos);
  1773.                         for ($j = 0; $j < $len; ++$j) {
  1774.                             $retstr .= $recordData[$pos + $j] . chr(0);
  1775.                         }
  1776.                         $charsLeft -= $len;
  1777.                         $isCompressed = false;
  1778.  
  1779.                     } else {
  1780.                         // 1st fragment compressed
  1781.                         // this fragment uncompressed
  1782.                         $newstr = '';
  1783.                         for ($j = 0; $j < strlen($retstr); ++$j) {
  1784.                             $newstr .= $retstr[$j] . chr(0);
  1785.                         }
  1786.                         $retstr = $newstr;
  1787.                         $len = min($charsLeft * 2, $limitpos - $pos);
  1788.                         $retstr .= substr($recordData, $pos, $len);
  1789.                         $charsLeft -= $len / 2;
  1790.                         $isCompressed = false;
  1791.                     }
  1792.  
  1793.                     $pos += $len;
  1794.                 }
  1795.             }
  1796.  
  1797.             // convert to UTF-8
  1798.             $retstr = $this->_encodeUTF16($retstr, $isCompressed);
  1799.  
  1800.             // read additional Rich-Text information, if any
  1801.             $fmtRuns = array();
  1802.             if ($hasRichText) {
  1803.                 // list of formatting runs
  1804.                 for ($j = 0; $j < $formattingRuns; ++$j) {
  1805.                     // first formatted character; zero-based
  1806.                     $charPos = $this->_GetInt2d($recordData, $pos + $j * 4);
  1807.  
  1808.                     // index to font record
  1809.                     $fontIndex = $this->_GetInt2d($recordData, $pos + 2 + $j * 4);
  1810.  
  1811.                     $fmtRuns[] = array(
  1812.                         'charPos' => $charPos,
  1813.                         'fontIndex' => $fontIndex,
  1814.                     );
  1815.                 }
  1816.                 $pos += 4 * $formattingRuns;
  1817.             }
  1818.  
  1819.             // read additional Asian phonetics information, if any
  1820.             if ($hasAsian) {
  1821.                 // For Asian phonetic settings, we skip the extended string data
  1822.                 $pos += $extendedRunLength;
  1823.             }
  1824.  
  1825.             // store the shared sting
  1826.             $this->_sst[] = array(
  1827.                 'value' => $retstr,
  1828.                 'fmtRuns' => $fmtRuns,
  1829.             );
  1830.         }
  1831.         
  1832.         // _getSplicedRecordData() takes care of moving current position in data stream
  1833.     }
  1834.     
  1835.     /**
  1836.      * Read PRINTGRIDLINES record
  1837.      */
  1838.     private function _readPrintGridlines()
  1839.     {
  1840.         $pos = $this->_pos;
  1841.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  1842.  
  1843.         if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) {
  1844.             $recordData = substr($this->_data, $pos + 4, $length);
  1845.  
  1846.             // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines
  1847.             $printGridlines = (bool) $this->_GetInt2d($recordData, 0);
  1848.             $this->_phpSheet->setPrintGridlines($printGridlines);
  1849.         }
  1850.  
  1851.         // move stream pointer to next record
  1852.         $this->_pos += 4 + $length;
  1853.     }
  1854.  
  1855.     /**
  1856.      * Read DEFAULTROWHEIGHT record
  1857.      */
  1858.     private function _readDefaultRowHeight()
  1859.     {
  1860.         $spos = $this->_pos;
  1861.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  1862.         $recordData = substr($this->_data, $spos + 4, $length);
  1863.         $spos += 4;
  1864.         // offset: 0; size: 2; option flags
  1865.         // offset: 2; size: 2; default height for unused rows, (twips 1/20 point)
  1866.         $height = $this->_GetInt2d($recordData, 2);
  1867.         $this->_phpSheet->getDefaultRowDimension()->setRowHeight($height / 20);
  1868.  
  1869.         $this->_pos += 4 + $length;
  1870.     }
  1871.  
  1872.     /**
  1873.      * Read SHEETPR record
  1874.      */
  1875.     private function _readSheetPr()
  1876.     {
  1877.         $spos = $this->_pos;
  1878.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  1879.         $recordData = substr($this->_data, $spos + 4, $length);
  1880.         $spos += 4;
  1881.  
  1882.         // offset: 0; size: 2
  1883.  
  1884.         // bit: 6; mask: 0x0040; 0 = outline buttons above outline group
  1885.         $isSummaryBelow = (0x0040 & $this->_GetInt2d($recordData, 0)) >> 6;
  1886.         $this->_phpSheet->setShowSummaryBelow($isSummaryBelow);
  1887.  
  1888.         // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group
  1889.         $isSummaryRight = (0x0080 & $this->_GetInt2d($recordData, 0)) >> 7;
  1890.         $this->_phpSheet->setShowSummaryRight($isSummaryRight);
  1891.  
  1892.         // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages
  1893.         // this corresponds to radio button setting in page setup dialog in Excel
  1894.         $this->_isFitToPages = (bool) ((0x0100 & $this->_GetInt2d($recordData, 0)) >> 8);
  1895.  
  1896.         $this->_pos += 4 + $length;
  1897.     }
  1898.  
  1899.     /**
  1900.      * Read HORIZONTALPAGEBREAKS record
  1901.      */
  1902.     private function _readHorizontalPageBreaks()
  1903.     {
  1904.         $spos = $this->_pos;
  1905.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  1906.  
  1907.         if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) {
  1908.             $recordData = substr($this->_data, $spos + 4, $length);
  1909.             // offset: 0; size: 2; number of the following row index structures
  1910.             $nm = $this->_GetInt2d($recordData, 0);
  1911.             // offset: 2; size: 6 * $nm; list of $nm row index structures
  1912.             for ($i = 0; $i < $nm; ++$i) {
  1913.                 $r = $this->_GetInt2d($recordData, 2 + 6 * $i);
  1914.                 $cf = $this->_GetInt2d($recordData, 2 + 6 * $i + 2);
  1915.                 $cl = $this->_GetInt2d($recordData, 2 + 6 * $i + 4);
  1916.                 // not sure why two column indexes are necessary?
  1917.                 $this->_phpSheet->setBreakByColumnAndRow($cf, $r, PHPExcel_Worksheet::BREAK_ROW);
  1918.             }
  1919.         }
  1920.  
  1921.         $this->_pos += 4 + $length;
  1922.     }
  1923.  
  1924.     /**
  1925.      * Read VERTICALPAGEBREAKS record
  1926.      */
  1927.     private function _readVerticalPageBreaks()
  1928.     {
  1929.         $spos = $this->_pos;
  1930.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  1931.  
  1932.         if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) {
  1933.             $recordData = substr($this->_data, $spos + 4, $length);
  1934.             // offset: 0; size: 2; number of the following column index structures
  1935.             $nm = $this->_GetInt2d($recordData, 0);
  1936.             // offset: 2; size: 6 * $nm; list of $nm row index structures
  1937.             for ($i = 0; $i < $nm; ++$i) {
  1938.                 $c = $this->_GetInt2d($recordData, 2 + 6 * $i);
  1939.                 $rf = $this->_GetInt2d($recordData, 2 + 6 * $i + 2);
  1940.                 $rl = $this->_GetInt2d($recordData, 2 + 6 * $i + 4);
  1941.                 // not sure why two row indexes are necessary?
  1942.                 $this->_phpSheet->setBreakByColumnAndRow($c, $rf, PHPExcel_Worksheet::BREAK_COLUMN);
  1943.             }
  1944.         }
  1945.  
  1946.         $this->_pos += 4 + $length;
  1947.     }
  1948.  
  1949.     /**
  1950.      * Read HEADER record
  1951.      */
  1952.     private function _readHeader()
  1953.     {
  1954.         $spos = $this->_pos;
  1955.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  1956.         $recordData = substr($this->_data, $spos + 4, $length);
  1957.         $spos += 4;
  1958.         if (!$this->_readDataOnly) {
  1959.             // offset: 0; size: var
  1960.             // realized that $recordData can be empty even when record exists
  1961.             if ($recordData) {
  1962.                 $string = $this->_readUnicodeStringLong($recordData);
  1963.                 $this->_phpSheet->getHeaderFooter()->setOddHeader($string['value']);
  1964.                 $this->_phpSheet->getHeaderFooter()->setEvenHeader($string['value']);
  1965.             }
  1966.         }
  1967.  
  1968.         $this->_pos += 4 + $length;
  1969.     }
  1970.  
  1971.     /**
  1972.      * Read FOOTER record
  1973.      */
  1974.     private function _readFooter()
  1975.     {
  1976.         $spos = $this->_pos;
  1977.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  1978.         $recordData = substr($this->_data, $spos + 4, $length);
  1979.         $spos += 4;
  1980.         if (!$this->_readDataOnly) {
  1981.             // offset: 0; size: var
  1982.             // realized that $recordData can be empty even when record exists
  1983.             if ($recordData) {
  1984.                 $string = $this->_readUnicodeStringLong($recordData);
  1985.                 $this->_phpSheet->getHeaderFooter()->setOddFooter($string['value']);
  1986.                 $this->_phpSheet->getHeaderFooter()->setEvenFooter($string['value']);
  1987.             }
  1988.         }
  1989.  
  1990.         $this->_pos += 4 + $length;
  1991.     }
  1992.  
  1993.     /**
  1994.      * Read HCENTER record
  1995.      */
  1996.     private function _readHcenter()
  1997.     {
  1998.         $pos = $this->_pos;
  1999.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2000.  
  2001.         if (!$this->_readDataOnly) {
  2002.             $recordData = substr($this->_data, $pos + 4, $length);
  2003.  
  2004.             // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally
  2005.             $isHorizontalCentered = (bool) $this->_GetInt2d($recordData, 0);
  2006.  
  2007.             $this->_phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered);
  2008.         }
  2009.  
  2010.         $this->_pos += 4 + $length;
  2011.     }
  2012.  
  2013.     /**
  2014.      * Read VCENTER record
  2015.      */
  2016.     private function _readVcenter()
  2017.     {
  2018.         $pos = $this->_pos;
  2019.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2020.  
  2021.         if (!$this->_readDataOnly) {
  2022.             $recordData = substr($this->_data, $pos + 4, $length);
  2023.  
  2024.             // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered
  2025.             $isVerticalCentered = (bool) $this->_GetInt2d($recordData, 0);
  2026.  
  2027.             $this->_phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered);
  2028.         }
  2029.  
  2030.         $this->_pos += 4 + $length;
  2031.     }
  2032.  
  2033.     /**
  2034.      * Read LEFTMARGIN record
  2035.      */
  2036.     private function _readLeftMargin()
  2037.     {
  2038.         $spos = $this->_pos;
  2039.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  2040.         $recordData = substr($this->_data, $spos + 4, $length);
  2041.         $spos += 4;
  2042.         if (!$this->_readDataOnly) {
  2043.             // offset: 0; size: 8
  2044.             $this->_phpSheet->getPageMargins()->setLeft($this->_extractNumber($recordData));
  2045.         }
  2046.  
  2047.         $this->_pos += 4 + $length;
  2048.     }
  2049.  
  2050.     /**
  2051.      * Read RIGHTMARGIN record
  2052.      */
  2053.     private function _readRightMargin()
  2054.     {
  2055.         $spos = $this->_pos;
  2056.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  2057.         $recordData = substr($this->_data, $spos + 4, $length);
  2058.         $spos += 4;
  2059.         if (!$this->_readDataOnly) {
  2060.             // offset: 0; size: 8
  2061.             $this->_phpSheet->getPageMargins()->setRight($this->_extractNumber($recordData));
  2062.         }
  2063.  
  2064.         $this->_pos += 4 + $length;
  2065.     }
  2066.  
  2067.     /**
  2068.      * Read TOPMARGIN record
  2069.      */
  2070.     private function _readTopMargin()
  2071.     {
  2072.         $spos = $this->_pos;
  2073.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  2074.         $recordData = substr($this->_data, $spos + 4, $length);
  2075.         $spos += 4;
  2076.         if (!$this->_readDataOnly) {
  2077.             // offset: 0; size: 8
  2078.             $this->_phpSheet->getPageMargins()->setTop($this->_extractNumber($recordData));
  2079.         }
  2080.  
  2081.         $this->_pos += 4 + $length;
  2082.     }
  2083.  
  2084.     /**
  2085.      * Read BOTTOMMARGIN record
  2086.      */
  2087.     private function _readBottomMargin()
  2088.     {
  2089.         $spos = $this->_pos;
  2090.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  2091.         $recordData = substr($this->_data, $spos + 4, $length);
  2092.         $spos += 4;
  2093.         if (!$this->_readDataOnly) {
  2094.             // offset: 0; size: 8
  2095.             $this->_phpSheet->getPageMargins()->setBottom($this->_extractNumber($recordData));
  2096.         }
  2097.  
  2098.         $this->_pos += 4 + $length;
  2099.     }
  2100.  
  2101.     /**
  2102.      * Read PAGESETUP record
  2103.      */
  2104.     private function _readPageSetup()
  2105.     {
  2106.         $spos = $this->_pos;
  2107.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  2108.         $recordData = substr($this->_data, $spos + 4, $length);
  2109.         $spos += 4;
  2110.         if (!$this->_readDataOnly) {
  2111.             // offset: 0; size: 2; paper size
  2112.             $paperSize = $this->_GetInt2d($recordData, 0);
  2113.  
  2114.             // offset: 2; size: 2; scaling factor
  2115.             $scale = $this->_GetInt2d($recordData, 2);
  2116.  
  2117.             // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed
  2118.             $fitToWidth = $this->_GetInt2d($recordData, 6);
  2119.  
  2120.             // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed
  2121.             $fitToHeight = $this->_GetInt2d($recordData, 8);
  2122.  
  2123.             // offset: 10; size: 2; option flags
  2124.                 // bit: 1; mask: 0x0002; 0=landscape, 1=portrait
  2125.                 $isPortrait = (0x0002 & $this->_GetInt2d($recordData, 10)) >> 1;
  2126.                 // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init
  2127.                 // when this bit is set, do not use flags for those properties
  2128.                 $isNotInit = (0x0004 & $this->_GetInt2d($recordData, 10)) >> 2;
  2129.  
  2130.             if (!$isNotInit) {
  2131.                 $this->_phpSheet->getPageSetup()->setPaperSize($paperSize);
  2132.                 switch ($isPortrait) {
  2133.                 case 0: $this->_phpSheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_LANDSCAPE); break;
  2134.                 case 1: $this->_phpSheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_PORTRAIT); break;
  2135.                 }
  2136.  
  2137.                 if (!$this->_isFitToPages) {
  2138.                     $this->_phpSheet->getPageSetup()->setScale($scale);
  2139.                 } else {
  2140.                     $this->_phpSheet->getPageSetup()->setFitToWidth($fitToWidth);
  2141.                     $this->_phpSheet->getPageSetup()->setFitToHeight($fitToHeight);
  2142.                 }
  2143.             }
  2144.  
  2145.             // offset: 16; size: 8; header margin (IEEE 754 floating-point value)
  2146.             $marginHeader = $this->_extractNumber(substr($recordData, 16, 8));
  2147.             $this->_phpSheet->getPageMargins()->setHeader($marginHeader);
  2148.  
  2149.             // offset: 24; size: 8; footer margin (IEEE 754 floating-point value)
  2150.             $marginFooter = $this->_extractNumber(substr($recordData, 24, 8));
  2151.             $this->_phpSheet->getPageMargins()->setFooter($marginFooter);
  2152.         }
  2153.  
  2154.         $this->_pos += 4 + $length;
  2155.     }
  2156.  
  2157.     /**
  2158.      * PROTECT - Sheet protection (BIFF2 through BIFF8)
  2159.      *   if this record is omitted, then it also means no sheet protection
  2160.      */
  2161.     private function _readProtect()
  2162.     {
  2163.         $spos = $this->_pos;
  2164.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  2165.         $recordData = substr($this->_data, $spos + 4, $length);
  2166.         $spos += 4;
  2167.         if (!$this->_readDataOnly) {
  2168.             // offset: 0; size: 2;
  2169.             // bit 0, mask 0x01; sheet protection
  2170.             $isSheetProtected = (0x01 & $this->_GetInt2d($recordData, 0)) >> 0;
  2171.             switch ($isSheetProtected) {
  2172.                 case 0: break;
  2173.                 case 1: $this->_phpSheet->getProtection()->setSheet(true); break;
  2174.             }
  2175.         }
  2176.  
  2177.         $this->_pos += 4 + $length;
  2178.     }
  2179.  
  2180.     /**
  2181.      * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8)
  2182.      */
  2183.     private function _readPassword()
  2184.     {
  2185.         $spos = $this->_pos;
  2186.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  2187.         $recordData = substr($this->_data, $spos + 4, $length);
  2188.         $spos += 4;
  2189.         if (!$this->_readDataOnly) {
  2190.             // offset: 0; size: 2; 16-bit hash value of password
  2191.             $password = strtoupper(dechex($this->_GetInt2d($recordData, 0))); // the hashed password
  2192.             $this->_phpSheet->getProtection()->setPassword($password, true);
  2193.         }
  2194.  
  2195.         $this->_pos += 4 + $length;
  2196.     }
  2197.  
  2198.     /**
  2199.      * Read DEFCOLWIDTH record
  2200.      */
  2201.     private function _readDefColWidth()
  2202.     {
  2203.         $spos = $this->_pos;
  2204.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  2205.         $recordData = substr($this->_data, $spos + 4, $length);
  2206.         $spos += 4;
  2207.         // offset: 0; size: 2; row index
  2208.         $width = $this->_GetInt2d($recordData, 0);
  2209.         $this->_phpSheet->getDefaultColumnDimension()->setWidth($width);
  2210.  
  2211.         $this->_pos += 4 + $length;
  2212.     }
  2213.  
  2214.     /**
  2215.      * Read COLINFO record
  2216.      */
  2217.     private function _readColInfo()
  2218.     {
  2219.         $spos = $this->_pos;
  2220.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  2221.         $recordData = substr($this->_data, $spos + 4, $length);
  2222.         $spos += 4;
  2223.         if (!$this->_readDataOnly) {
  2224.             // offset: 0; size: 2; index to first column in range
  2225.             $fc = $this->_GetInt2d($recordData, 0); // first column index
  2226.  
  2227.             // offset: 2; size: 2; index to last column in range
  2228.             $lc = $this->_GetInt2d($recordData, 2); // first column index
  2229.  
  2230.             // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character
  2231.             $width = $this->_GetInt2d($recordData, 4);
  2232.  
  2233.             // offset: 6; size: 2; index to XF record for default column formatting
  2234.  
  2235.             // offset: 8; size: 2; option flags
  2236.                 // bit: 0; mask: 0x0001; 1= columns are hidden
  2237.                 $isHidden = (0x0001 & $this->_GetInt2d($recordData, 8)) >> 0;
  2238.                 // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline)
  2239.                 $level = (0x0700 & $this->_GetInt2d($recordData, 8)) >> 8;
  2240.                 // bit: 12; mask: 0x1000; 1 = collapsed
  2241.                 $isCollapsed = (0x1000 & $this->_GetInt2d($recordData, 8)) >> 12;
  2242.  
  2243.             // offset: 10; size: 2; not used
  2244.  
  2245.             for ($i = $fc; $i <= $lc; ++$i) {
  2246.                 $this->_phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256);
  2247.                 $this->_phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden);
  2248.                 $this->_phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level);
  2249.                 $this->_phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed);
  2250.             }
  2251.         }
  2252.  
  2253.         $this->_pos += 4 + $length;
  2254.     }
  2255.  
  2256.     /**
  2257.      * ROW
  2258.      *
  2259.      * This record contains the properties of a single row in a
  2260.      * sheet. Rows and cells in a sheet are divided into blocks
  2261.      * of 32 rows.
  2262.      *
  2263.      * --    "OpenOffice.org's Documentation of the Microsoft
  2264.      *         Excel File Format"
  2265.      */
  2266.     private function _readRow()
  2267.     {
  2268.         $spos = $this->_pos;
  2269.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  2270.         $recordData = substr($this->_data, $spos + 4, $length);
  2271.         $spos += 4;
  2272.         if (!$this->_readDataOnly) {
  2273.             // offset: 0; size: 2; index of this row
  2274.             $r = $this->_GetInt2d($recordData, 0);
  2275.             // offset: 2; size: 2; index to column of the first cell which is described by a cell record
  2276.             // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1
  2277.             // offset: 6; size: 2;
  2278.                 // bit: 14-0; mask: 0x7FF; height of the row, in twips = 1/20 of a point
  2279.                 $height = (0x7FF & $this->_GetInt2d($recordData, 6)) >> 0;
  2280.                 // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height
  2281.                 $useDefaultHeight = (0x8000 & $this->_GetInt2d($recordData, 6)) >> 15;
  2282.  
  2283.                 if (!$useDefaultHeight) {
  2284.                     $this->_phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
  2285.                 }
  2286.             // offset: 8; size: 2; not used
  2287.             // offset: 10; size: 2; not used in BIFF5-BIFF8
  2288.             // offset: 12; size: 4; option flags and default row formatting
  2289.                 // bit: 2-0: mask: 0x00000007; outline level of the row
  2290.                 $level = (0x00000007 & $this->_GetInt4d($recordData, 12)) >> 0;
  2291.                 $this->_phpSheet->getRowDimension($r + 1)->setOutlineLevel($level);
  2292.                 // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed
  2293.                 $isCollapsed = (0x00000010 & $this->_GetInt4d($recordData, 12)) >> 4;
  2294.                 $this->_phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed);
  2295.                 // bit: 5; mask: 0x00000020; 1 = row is hidden
  2296.                 $isHidden = (0x00000020 & $this->_GetInt4d($recordData, 12)) >> 5;
  2297.                 $this->_phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden);
  2298.         }
  2299.         $this->_pos += 4 + $length;
  2300.     }
  2301.  
  2302.     /**
  2303.      * Read RK record
  2304.      * This record represents a cell that contains an RK value
  2305.      * (encoded integer or floating-point value). If a
  2306.      * floating-point value cannot be encoded to an RK value,
  2307.      * a NUMBER record will be written. This record replaces the
  2308.      * record INTEGER written in BIFF2.
  2309.      *
  2310.      * --    "OpenOffice.org's Documentation of the Microsoft
  2311.      *         Excel File Format"
  2312.      */
  2313.     private function _readRk()
  2314.     {
  2315.         $pos = $this->_pos;
  2316.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2317.         $recordData = substr($this->_data, $pos + 4, $length);
  2318.         $pos += 4;
  2319.  
  2320.         // offset: 0; size: 2; index to row
  2321.         $row = $this->_GetInt2d($this->_data, $pos);
  2322.  
  2323.         // offset: 2; size: 2; index to column
  2324.         $column = $this->_GetInt2d($this->_data, $pos + 2);
  2325.         $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
  2326.  
  2327.         // Read cell?
  2328.         if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
  2329.             // offset: 4; size: 2; index to XF record
  2330.             $xfindex = $this->_GetInt2d($recordData, 4);
  2331.  
  2332.             // offset: 6; size: 4; RK value
  2333.             $rknum = $this->_GetInt4d($this->_data, $pos + 6);
  2334.             $numValue = $this->_GetIEEE754($rknum);
  2335.  
  2336.             // add style information
  2337.             if (!$this->_readDataOnly) {
  2338.                 $this->_phpSheet->getStyle($columnString . ($row + 1))->applyFromArray($this->_xf[$xfindex]);
  2339.  
  2340.                 if (PHPExcel_Shared_Date::isDateTimeFormatCode($this->_xf[$xfindex]['numberformat']['code'])) {
  2341.                     $numValue = PHPExcel_Shared_Date::ExcelToPHP($numValue);
  2342.                 }
  2343.             }
  2344.  
  2345.             // add cell
  2346.             $this->_phpSheet->setCellValueExplicit($columnString . ($row + 1), $numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC);
  2347.         }
  2348.  
  2349.         // move stream pointer to next record
  2350.         $this->_pos += 4 + $length;
  2351.     }
  2352.  
  2353.     /**
  2354.      * Read LABELSST record
  2355.      * This record represents a cell that contains a string. It
  2356.      * replaces the LABEL record and RSTRING record used in
  2357.      * BIFF2-BIFF5.
  2358.      *
  2359.      * --    "OpenOffice.org's Documentation of the Microsoft
  2360.      *         Excel File Format"
  2361.      */
  2362.     private function _readLabelSst()
  2363.     {
  2364.         $pos = $this->_pos;
  2365.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2366.         $recordData = substr($this->_data, $pos + 4, $length);
  2367.         $pos += 4;
  2368.  
  2369.         // offset: 0; size: 2; index to row
  2370.         $row = $this->_GetInt2d($this->_data, $pos);
  2371.  
  2372.         // offset: 2; size: 2; index to column
  2373.         $column = $this->_GetInt2d($this->_data, $pos + 2);
  2374.         $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
  2375.  
  2376.         // Read cell?
  2377.         if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
  2378.             // offset: 4; size: 2; index to XF record
  2379.             $xfindex = $this->_GetInt2d($this->_data, $pos + 4);
  2380.  
  2381.             // offset: 6; size: 4; index to SST record
  2382.             $index = $this->_GetInt4d($this->_data, $pos + 6);
  2383.  
  2384.             // add cell
  2385.             if (($fmtRuns = $this->_sst[$index]['fmtRuns']) && !$this->_readDataOnly) {
  2386.                 // then we should treat as rich text
  2387.                 $richText = new PHPExcel_RichText($this->_phpSheet->getCell($columnString . ($row + 1)));
  2388.                 $charPos = 0;
  2389.                 for ($i = 0; $i <= count($this->_sst[$index]['fmtRuns']); ++$i) {
  2390.                     if (isset($fmtRuns[$i])) {
  2391.                         $text = mb_substr($this->_sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos, 'UTF-8');
  2392.                         $charPos = $fmtRuns[$i]['charPos'];
  2393.                     } else {
  2394.                         $text = mb_substr($this->_sst[$index]['value'], $charPos, mb_strlen($this->_sst[$index]['value']), 'UTF-8');
  2395.                     }
  2396.  
  2397.                     if (mb_strlen($text) > 0) {
  2398.                         if ($i == 0) { // first text run, no style
  2399.                             $richText->createText($text);
  2400.                         } else {
  2401.                             $textRun = $richText->createTextRun($text);
  2402.                             if (isset($fmtRuns[$i - 1])) {
  2403.                                 if ($fmtRuns[$i - 1]['fontIndex'] < 4) {
  2404.                                     $fontIndex = $fmtRuns[$i - 1]['fontIndex'];
  2405.                                 } else {
  2406.                                     // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
  2407.                                     // check the OpenOffice documentation of the FONT record
  2408.                                     $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1;
  2409.                                 }
  2410.                                 $textRun->getFont()->applyFromArray($this->_fonts[$fontIndex]);
  2411.                             }
  2412.                         }
  2413.                     }
  2414.                 }
  2415.             } else {
  2416.                 $this->_phpSheet->setCellValueExplicit($columnString . ($row + 1), $this->_sst[$index]['value'], PHPExcel_Cell_DataType::TYPE_STRING);
  2417.             }
  2418.  
  2419.             // add style information
  2420.             if (!$this->_readDataOnly) {
  2421.                 $this->_phpSheet->getStyle($columnString . ($row + 1))->applyFromArray($this->_xf[$xfindex]);
  2422.             }
  2423.         }
  2424.  
  2425.         // move stream pointer to next record
  2426.         $this->_pos += 4 + $length;
  2427.     }
  2428.  
  2429.     /**
  2430.      * Read MULRK record
  2431.      * This record represents a cell range containing RK value
  2432.      * cells. All cells are located in the same row.
  2433.      *
  2434.      * --    "OpenOffice.org's Documentation of the Microsoft
  2435.      *         Excel File Format"
  2436.      */
  2437.     private function _readMulRk()
  2438.     {
  2439.         $pos = $this->_pos;
  2440.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2441.         $recordData = substr($this->_data, $pos + 4, $length);
  2442.         $pos += 4;
  2443.  
  2444.         // offset: 0; size: 2; index to row
  2445.         $row = $this->_GetInt2d($this->_data, $pos);
  2446.         
  2447.         // offset: 2; size: 2; index to first column
  2448.         $colFirst = $this->_GetInt2d($this->_data, $pos + 2);
  2449.         
  2450.         // offset: var; size: 2; index to last column
  2451.         $colLast = $this->_GetInt2d($this->_data, $pos + $length - 2);
  2452.         $columns = $colLast - $colFirst + 1;
  2453.  
  2454.         $tmppos = $pos + 4;
  2455.         for ($i = 0; $i < $columns; ++$i) {
  2456.             $columnString = PHPExcel_Cell::stringFromColumnIndex($colFirst + $i);
  2457.  
  2458.             // Read cell?
  2459.             if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
  2460.                 // offset: 0; size: 2; index to XF record
  2461.                 $xfindex = $this->_GetInt2d($recordData, 4 + 6 * $i);
  2462.  
  2463.                 // offset: 2; size: 4; RK value
  2464.                 $numValue = $this->_GetIEEE754($this->_GetInt4d($this->_data, $tmppos + 2));
  2465.                 if (!$this->_readDataOnly) {
  2466.                     // add style
  2467.                     $this->_phpSheet->getStyle($columnString . ($row + 1))->applyFromArray($this->_xf[$xfindex]);
  2468.  
  2469.                     if (PHPExcel_Shared_Date::isDateTimeFormatCode($this->_xf[$xfindex]['numberformat']['code'])) {
  2470.                         $numValue = PHPExcel_Shared_Date::ExcelToPHP($numValue);
  2471.                     }
  2472.                 }
  2473.  
  2474.                 // add cell value
  2475.                 $this->_phpSheet->setCellValueExplicit($columnString . ($row + 1), $numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC);
  2476.             }
  2477.  
  2478.             $tmppos += 6;
  2479.         }
  2480.  
  2481.         // move stream pointer to next record
  2482.         $this->_pos += 4 + $length;
  2483.     }
  2484.  
  2485.     /**
  2486.      * Read NUMBER record
  2487.      * This record represents a cell that contains a
  2488.      * floating-point value.
  2489.      *
  2490.      * --    "OpenOffice.org's Documentation of the Microsoft
  2491.      *         Excel File Format"
  2492.      */
  2493.     private function _readNumber()
  2494.     {
  2495.         $pos = $this->_pos;
  2496.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2497.         $recordData = substr($this->_data, $pos + 4, $length);
  2498.         $pos += 4;
  2499.  
  2500.         // offset: 0; size: 2; index to row
  2501.         $row = $this->_GetInt2d($this->_data, $pos);
  2502.  
  2503.         // offset: 2; size 2; index to column
  2504.         $column = $this->_GetInt2d($this->_data, $pos + 2);
  2505.         $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
  2506.  
  2507.         // Read cell?
  2508.         if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
  2509.             // offset 4; size: 2; index to XF record
  2510.             $xfindex = $this->_GetInt2d($recordData, 4);
  2511.  
  2512.             $numValue = $this->_createNumber($pos);
  2513.  
  2514.             // add cell style
  2515.             if (!$this->_readDataOnly) {
  2516.                 $this->_phpSheet->getStyle($columnString . ($row + 1))->applyFromArray($this->_xf[$xfindex]);
  2517.                 if (PHPExcel_Shared_Date::isDateTimeFormatCode($this->_xf[$xfindex]['numberformat']['code'])) {
  2518.                     $numValue = PHPExcel_Shared_Date::ExcelToPHP($numValue);
  2519.                 }
  2520.             }
  2521.  
  2522.             // add cell value
  2523.             $this->_phpSheet->setCellValueExplicit($columnString . ($row + 1), $numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC);
  2524.         }
  2525.  
  2526.         // move stream pointer to next record
  2527.         $this->_pos += 4 + $length;
  2528.     }
  2529.  
  2530.     /**
  2531.      * Read FORMULA record
  2532.      * This record contains the token array and the result of a
  2533.      * formula cell.
  2534.      *
  2535.      * --    "OpenOffice.org's Documentation of the Microsoft
  2536.      *         Excel File Format"
  2537.      */
  2538.     private function _readFormula()
  2539.     {
  2540.         $pos = $this->_pos;
  2541.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2542.         $recordData = substr($this->_data, $pos + 4, $length);
  2543.         $pos += 4;
  2544.  
  2545.         // offset: 0; size: 2; row index
  2546.         $row = $this->_GetInt2d($this->_data, $pos);
  2547.  
  2548.         // offset: 2; size: 2; col index
  2549.         $column = $this->_GetInt2d($this->_data, $pos + 2);
  2550.         $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
  2551.  
  2552.         // Read cell?
  2553.         if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
  2554.             // offset: 4; size: 2; XF index
  2555.             $xfindex = $this->_GetInt2d($this->_data, $pos + 4);
  2556.  
  2557.             // offset: 6; size: 8; result of the formula
  2558.             if ((ord($this->_data[$pos + 6]) == 0) &&
  2559.             (ord($this->_data[$pos + 12]) == 255) &&
  2560.             (ord($this->_data[$pos + 13]) == 255)) {
  2561.                 //String formula. Result follows in appended STRING record
  2562.                 $dataType = PHPExcel_Cell_DataType::TYPE_STRING;
  2563.                 $soff = $pos + $length;
  2564.                 $scode = $this->_GetInt2d($this->_data, $soff);
  2565.                 $slength = $this->_GetInt2d($this->_data, $soff + 2);
  2566.                 $sdata = substr($this->_data, $soff + 4, $slength);
  2567.                 if ($this->_version == self::XLS_BIFF8) {
  2568.                     $string = $this->_readUnicodeStringLong($sdata);
  2569.                     $value = $string['value'];
  2570.                 } else {
  2571.                     $string = $this->_readByteStringLong($sdata);
  2572.                     $value = $string['value'];
  2573.                 }
  2574.             } elseif ((ord($this->_data[$pos + 6]) == 1) &&
  2575.             (ord($this->_data[$pos + 12]) == 255) &&
  2576.             (ord($this->_data[$pos + 13]) == 255)) {
  2577.                 //Boolean formula. Result is in +2; 0=false,1=true
  2578.                 $dataType = PHPExcel_Cell_DataType::TYPE_BOOL;
  2579.                 $value = (bool) ord($this->_data[$pos + 8]);
  2580.             } elseif ((ord($this->_data[$pos + 6]) == 2) &&
  2581.             (ord($this->_data[$pos + 12]) == 255) &&
  2582.             (ord($this->_data[$pos + 13]) == 255)) {
  2583.                 //Error formula. Error code is in +2
  2584.                 $dataType = PHPExcel_Cell_DataType::TYPE_ERROR;
  2585.                 $value = $this->_mapErrorCode(ord($this->_data[$pos + 8]));
  2586.             } elseif ((ord($this->_data[$pos + 6]) == 3) &&
  2587.             (ord($this->_data[$pos + 12]) == 255) &&
  2588.             (ord($this->_data[$pos + 13]) == 255)) {
  2589.                 //Formula result is a null string
  2590.                 $dataType = PHPExcel_Cell_DataType::TYPE_NULL;
  2591.                 $value = '';
  2592.             } else {
  2593.                 // forumla result is a number, first 14 bytes like _NUMBER record
  2594.                 $dataType = PHPExcel_Cell_DataType::TYPE_NUMERIC;
  2595.                 $value = $this->_createNumber($pos);
  2596.             }
  2597.  
  2598.             // add cell style
  2599.             if (!$this->_readDataOnly) {
  2600.                 $this->_phpSheet->getStyle($columnString . ($row + 1))->applyFromArray($this->_xf[$xfindex]);
  2601.                 if (PHPExcel_Shared_Date::isDateTimeFormatCode($this->_xf[$xfindex]['numberformat']['code'])) {
  2602.                     $value = PHPExcel_Shared_Date::ExcelToPHP($value);
  2603.                 }
  2604.             }
  2605.  
  2606.             // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc.
  2607.             // offset: 16: size: 4; not used
  2608.             // offset: 20: size: variable; formula structure
  2609.             $formulaStructure = substr($recordData, 20);
  2610.  
  2611.             // add cell value
  2612.             try {
  2613.                 if ($this->_version != self::XLS_BIFF8) {
  2614.                     throw new Exception('Not BIFF8. Can only read BIFF8 formulas');
  2615.                 }
  2616.                 $formula = $this->_getFormulaFromStructure($formulaStructure); // get human language
  2617.                 $this->_phpSheet->getCell($columnString . ($row + 1))->setValueExplicit('=' . $formula, PHPExcel_Cell_DataType::TYPE_FORMULA);
  2618.             } catch (Exception $e) {
  2619.                 $this->_phpSheet->setCellValueExplicit($columnString . ($row + 1), $value, $dataType);
  2620.             }
  2621.         }
  2622.  
  2623.         // move stream pointer to next record
  2624.         $this->_pos += 4 + $length;
  2625.     }
  2626.  
  2627.     /**
  2628.      * Read BOOLERR record
  2629.      * This record represents a Boolean value or error value
  2630.      * cell.
  2631.      *
  2632.      * --    "OpenOffice.org's Documentation of the Microsoft
  2633.      *         Excel File Format"
  2634.      */
  2635.     private function _readBoolErr()
  2636.     {
  2637.         $pos = $this->_pos;
  2638.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2639.         $recordData = substr($this->_data, $pos + 4, $length);
  2640.         $pos += 4;
  2641.  
  2642.         // offset: 0; size: 2; row index
  2643.         $row = $this->_GetInt2d($this->_data, $pos);
  2644.  
  2645.         // offset: 2; size: 2; column index
  2646.         $column = $this->_GetInt2d($this->_data, $pos + 2);
  2647.         $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
  2648.  
  2649.         // Read cell?
  2650.         if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
  2651.             // offset: 4; size: 2; index to XF record
  2652.             $xfindex = $this->_GetInt2d($recordData, 4);
  2653.  
  2654.             // offset: 6; size: 1; the boolean value or error value
  2655.             $boolErr = ord($recordData[6]);
  2656.  
  2657.             // offset: 7; size: 1; 0=boolean; 1=error
  2658.             $isError = ord($recordData[7]);
  2659.  
  2660.             switch ($isError) {
  2661.  
  2662.             case 0: // boolean
  2663.                 $value = (bool) $boolErr;
  2664.  
  2665.                 // add cell value
  2666.                 $this->_phpSheet->getCell($columnString . ($row + 1))->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_BOOL);
  2667.                 break;
  2668.  
  2669.             case 1: // error type
  2670.                 $value = $this->_mapErrorCode($boolErr);
  2671.  
  2672.                 // add cell value
  2673.                 $this->_phpSheet->getCell($columnString . ($row + 1))->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_ERROR);
  2674.                 break;
  2675.             }
  2676.  
  2677.             // add cell style
  2678.             if (!$this->_readDataOnly) {
  2679.                 $this->_phpSheet->getStyle($columnString . ($row + 1))->applyFromArray($this->_xf[$xfindex]);
  2680.             }
  2681.         }
  2682.  
  2683.         // move stream pointer to next record
  2684.         $this->_pos += 4 + $length;
  2685.     }
  2686.  
  2687.     /**
  2688.      * Read MULBLANK record
  2689.      * This record represents a cell range of empty cells. All
  2690.      * cells are located in the same row
  2691.      *
  2692.      * --    "OpenOffice.org's Documentation of the Microsoft
  2693.      *         Excel File Format"
  2694.      */
  2695.     private function _readMulBlank()
  2696.     {
  2697.         $pos = $this->_pos;
  2698.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2699.         $recordData = substr($this->_data, $pos + 4, $length);
  2700.         $pos += 4;
  2701.  
  2702.         // offset: 0; size: 2; index to row
  2703.         $row = $this->_GetInt2d($recordData, 0);
  2704.  
  2705.         // offset: 2; size: 2; index to first column
  2706.         $fc = $this->_GetInt2d($recordData, 2);
  2707.  
  2708.         // offset: 4; size: 2 x nc; list of indexes to XF records
  2709.         // add style information
  2710.         if (!$this->_readDataOnly) {
  2711.             for ($i = 0; $i < $length / 2 - 3; ++$i) {
  2712.                 $columnString = PHPExcel_Cell::stringFromColumnIndex($fc + $i);
  2713.  
  2714.                 // Read cell?
  2715.                 if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
  2716.                     $xfindex = $this->_GetInt2d($recordData, 4 + 2 * $i);
  2717.                     $this->_phpSheet->getStyle($columnString . ($row + 1))->applyFromArray($this->_xf[$xfindex]);
  2718.                 }
  2719.             }
  2720.         }
  2721.  
  2722.         // offset: 6; size 2; index to last column (not needed)
  2723.  
  2724.         // move stream pointer to next record
  2725.         $this->_pos += 4 + $length;
  2726.     }
  2727.  
  2728.     /**
  2729.      * Read LABEL record
  2730.      * This record represents a cell that contains a string. In
  2731.      * BIFF8 it is usually replaced by the LABELSST record.
  2732.      * Excel still uses this record, if it copies unformatted
  2733.      * text cells to the clipboard.
  2734.      *
  2735.      * --    "OpenOffice.org's Documentation of the Microsoft
  2736.      *         Excel File Format"
  2737.      */
  2738.     private function _readLabel()
  2739.     {
  2740.         $pos = $this->_pos;
  2741.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2742.         $recordData = substr($this->_data, $pos + 4, $length);
  2743.         $pos += 4;
  2744.  
  2745.         // offset: 0; size: 2; index to row
  2746.         $row = $this->_GetInt2d($this->_data, $pos);
  2747.  
  2748.         // offset: 2; size: 2; index to column
  2749.         $column = $this->_GetInt2d($this->_data, $pos + 2);
  2750.         $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
  2751.  
  2752.         // Read cell?
  2753.         if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
  2754.             // offset: 4; size: 2; XF index
  2755.             $xfindex = $this->_GetInt2d($recordData, 4);
  2756.  
  2757.             // add cell value
  2758.             // todo: what if string is very long? continue record
  2759.             if ($this->_version == self::XLS_BIFF8) {
  2760.                 $string = $this->_readUnicodeStringLong(substr($recordData, 6));
  2761.                 $value = $string['value'];
  2762.             } else {
  2763.                 $string = $this->_readByteStringLong(substr($recordData, 6));
  2764.                 $value = $string['value'];
  2765.             }
  2766.             $this->_phpSheet->setCellValueExplicit($columnString . ($row + 1), $value, PHPExcel_Cell_DataType::TYPE_STRING);
  2767.  
  2768.             // add cell style
  2769.             if (!$this->_readDataOnly) {
  2770.                 $this->_phpSheet->getStyle($columnString . ($row + 1))->applyFromArray($this->_xf[$xfindex]);
  2771.             }
  2772.         }
  2773.  
  2774.         // move stream pointer to next record
  2775.         $this->_pos += 4 + $length;
  2776.     }
  2777.  
  2778.     /**
  2779.      * Read BLANK record
  2780.      */
  2781.     private function _readBlank()
  2782.     {
  2783.         $pos = $this->_pos;
  2784.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2785.         $recordData = substr($this->_data, $pos + 4, $length);
  2786.         $pos += 4;
  2787.  
  2788.         // offset: 0; size: 2; row index
  2789.         $row = $this->_GetInt2d($recordData, 0);
  2790.  
  2791.         // offset: 2; size: 2; col index
  2792.         $col = $this->_GetInt2d($recordData, 2);
  2793.         $columnString = PHPExcel_Cell::stringFromColumnIndex($col);
  2794.  
  2795.         // Read cell?
  2796.         if ( !is_null($this->getReadFilter()) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
  2797.             // offset: 4; size: 2; XF index
  2798.             $xfindex = $this->_GetInt2d($recordData, 4);
  2799.  
  2800.             // add style information
  2801.             if (!$this->_readDataOnly) {
  2802.                 $this->_phpSheet->getStyle($columnString . ($row + 1))->applyFromArray($this->_xf[$xfindex]);
  2803.             }
  2804.         }
  2805.  
  2806.         // move stream pointer to next record
  2807.         $this->_pos += 4 + $length;
  2808.     }
  2809.  
  2810.     /**
  2811.      * Read MSODRAWING record
  2812.      */
  2813.     private function _readMsoDrawing()
  2814.     {
  2815.         $pos = $this->_pos;
  2816.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2817.         $recordData = substr($this->_data, $pos + 4, $length);
  2818.         
  2819.         $this->_drawingData .= $recordData;
  2820.  
  2821.         // move stream pointer to next record
  2822.         $this->_pos += 4 + $length;
  2823.     }
  2824.  
  2825.     /**
  2826.      * Read OBJ record
  2827.      */
  2828.     private function _readObj()
  2829.     {
  2830.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  2831.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  2832.  
  2833.         // move stream pointer to next record
  2834.         $this->_pos += 4 + $length;
  2835.  
  2836.         if ($this->_readDataOnly || $this->_version != self::XLS_BIFF8) {
  2837.             return;
  2838.         }
  2839.  
  2840.         // recordData consists of an array of subrecords looking like this:
  2841.         //    ft: 2 bytes; id number
  2842.         //    cb: 2 bytes; size in bytes of following data
  2843.         //    data: var; subrecord data
  2844.  
  2845.         // for now, we are just interested in the second subrecord containing the object type
  2846.         $ot = $this->_GetInt2d($recordData, 4);
  2847.  
  2848.         $this->_objs[] = array(
  2849.             'type' => $ot,
  2850.         );
  2851.     }
  2852.  
  2853.     /**
  2854.      * Read WINDOW2 record
  2855.      */
  2856.     private function _readWindow2()
  2857.     {
  2858.         $pos = $this->_pos;
  2859.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2860.         $recordData = substr($this->_data, $pos + 4, $length);
  2861.         $pos += 4;
  2862.  
  2863.         // offset: 0; size: 2; option flags
  2864.         $options = $this->_GetInt2d($recordData, 0);
  2865.  
  2866.         // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines
  2867.         $showGridlines = (bool) ((0x0002 & $options) >> 1);
  2868.         $this->_phpSheet->setShowGridlines($showGridlines);
  2869.  
  2870.         // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen
  2871.         $this->_frozen = (bool) ((0x0008 & $options) >> 3);
  2872.  
  2873.         // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active
  2874.         $isActive = (bool) ((0x0400 & $options) >> 10);
  2875.         if ($isActive) {
  2876.             $this->_phpExcel->setActiveSheetIndex($this->_phpExcel->getIndex($this->_phpSheet));
  2877.         }
  2878.  
  2879.         // move stream pointer to next record
  2880.         $this->_pos += 4 + $length;
  2881.     }
  2882.  
  2883.     /**
  2884.      * Read SCL record
  2885.      */
  2886.     private function _readScl()
  2887.     {
  2888.         $pos = $this->_pos;
  2889.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2890.         $recordData = substr($this->_data, $pos + 4, $length);
  2891.         $pos += 4;
  2892.  
  2893.         // offset: 0; size: 2; numerator of the view magnification
  2894.         $numerator = $this->_GetInt2d($recordData, 0);
  2895.  
  2896.         // offset: 2; size: 2; numerator of the view magnification
  2897.         $denumerator = $this->_GetInt2d($recordData, 2);
  2898.  
  2899.         // set the zoom scale (in percent)
  2900.         $this->_phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator);
  2901.         
  2902.         // move stream pointer to next record
  2903.         $this->_pos += 4 + $length;
  2904.     }
  2905.  
  2906.     /**
  2907.      * Read PANE record
  2908.      */
  2909.     private function _readPane()
  2910.     {
  2911.         $pos = $this->_pos;
  2912.         $length = $this->_GetInt2d($this->_data, $pos + 2);
  2913.         $recordData = substr($this->_data, $pos + 4, $length);
  2914.         $pos += 4;
  2915.  
  2916.         if (!$this->_readDataOnly) {
  2917.             // offset: 0; size: 2; position of vertical split
  2918.             $px = $this->_GetInt2d($recordData, 0);
  2919.  
  2920.             // offset: 2; size: 2; position of horizontal split
  2921.             $py = $this->_GetInt2d($recordData, 2);
  2922.  
  2923.             if ($this->_frozen) {
  2924.                 // frozen panes
  2925.                 $this->_phpSheet->freezePane(PHPExcel_Cell::stringFromColumnIndex($px) . ($py + 1));
  2926.             } else {
  2927.                 // unfrozen panes; split windows; not supported by PHPExcel core
  2928.             }
  2929.         }
  2930.  
  2931.         // move stream pointer to next record
  2932.         $this->_pos += 4 + $length;
  2933.     }
  2934.  
  2935.     /**
  2936.      * MERGEDCELLS
  2937.      *
  2938.      * This record contains the addresses of merged cell ranges
  2939.      * in the current sheet.
  2940.      *
  2941.      * --    "OpenOffice.org's Documentation of the Microsoft
  2942.      *         Excel File Format"
  2943.      */
  2944.     private function _readMergedCells()
  2945.     {
  2946.         $spos = $this->_pos;
  2947.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  2948.         $recordData = substr($this->_data, $spos + 4, $length);
  2949.         $spos += 4;
  2950.         if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) {
  2951.             $cellRangeAddressList = $this->_readBIFF8CellRangeAddressList($recordData);
  2952.             foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) {
  2953.                 $this->_phpSheet->mergeCells($cellRangeAddress);
  2954.             }
  2955.         }
  2956.         $this->_pos += 4 + $length;
  2957.     }
  2958.  
  2959.     /**
  2960.      * Read HYPERLINK record
  2961.      */
  2962.     private function _readHyperLink()
  2963.     {
  2964.         $spos = $this->_pos;
  2965.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  2966.         $recordData = substr($this->_data, $spos + 4, $length);
  2967.         $spos += 4;
  2968.  
  2969.         // move stream pointer forward to next record
  2970.         $this->_pos += 4 + $length;
  2971.  
  2972.         if (!$this->_readDataOnly) {
  2973.             // offset: 0; size: 8; cell range address of all cells containing this hyperlink
  2974.             $cellRange = $this->_readBIFF8CellRangeAddressFixed($recordData, 0, 8);
  2975.             // offset: 8, size: 16; GUID of StdLink
  2976.             // offset: 24, size: 4; unknown value
  2977.             // offset: 28, size: 4; option flags
  2978.                 // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL
  2979.                 $isFileLinkOrUrl = (0x00000001 & $this->_GetInt2d($recordData, 28)) >> 0;
  2980.                 // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL
  2981.                 $isAbsPathOrUrl = (0x00000001 & $this->_GetInt2d($recordData, 28)) >> 1;
  2982.                 // bit: 2 (and 4); mask: 0x00000014; 0 = no description
  2983.                 $hasDesc = (0x00000014 & $this->_GetInt2d($recordData, 28)) >> 2;
  2984.                 // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text
  2985.                 $hasText = (0x00000008 & $this->_GetInt2d($recordData, 28)) >> 3;
  2986.                 // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame
  2987.                 $hasFrame = (0x00000080 & $this->_GetInt2d($recordData, 28)) >> 7;
  2988.                 // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name)
  2989.                 $isUNC = (0x00000100 & $this->_GetInt2d($recordData, 28)) >> 8;
  2990.  
  2991.             $offset = 32;
  2992.             if ($hasDesc) {
  2993.                 // offset: 32; size: var; character count of description text
  2994.                 $dl = $this->_GetInt4d($recordData, 32);
  2995.                 // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated
  2996.                 $desc = $this->_encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false);
  2997.                 $offset += 4 + 2 * $dl;
  2998.             }
  2999.             if ($hasFrame) {
  3000.                 $fl = $this->_GetInt4d($recordData, $offset);
  3001.                 $offset += 4 + 2 * $fl;
  3002.             }
  3003.  
  3004.             // detect type of hyperlink (there are 4 types)
  3005.             $hyperlinkType = null;
  3006.  
  3007.             if ($isUNC) {
  3008.                 $hyperlinkType = 'UNC';
  3009.             } else if (!$isFileLinkOrUrl) {
  3010.                 $hyperlinkType = 'workbook';
  3011.             } else if (ord($recordData[$offset]) == 0x03) {
  3012.                 $hyperlinkType = 'local';
  3013.             } else if (ord($recordData[$offset]) == 0xE0) {
  3014.                 $hyperlinkType = 'URL';
  3015.             }
  3016.  
  3017.             switch ($hyperlinkType) {
  3018.  
  3019.             case 'URL':
  3020.                 // offset: var; size: 16; GUID of URL Moniker
  3021.                 $offset += 16;
  3022.                 // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word
  3023.                 $us = $this->_GetInt4d($recordData, $offset);
  3024.                 $offset += 4;
  3025.                 // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated
  3026.                 $url = $this->_encodeUTF16(substr($recordData, $offset, $us - 1), false);
  3027.                 $url .= $hasText ? '#' : '';
  3028.                 $offset += $us;
  3029.                 break;
  3030.             case 'workbook':
  3031.                 // section 5.58.5: Hyperlink to the Current Workbook
  3032.                 // e.g. Sheet2!B1:C2, stored in text mark field
  3033.                 $url = 'sheet://';
  3034.                 break;
  3035.             case 'local':
  3036.                 // section 5.58.2: Hyperlink containing a URL
  3037.                 // e.g. http://example.org/index.php
  3038.                 // todo: implement
  3039.             case 'UNC':
  3040.                 // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path
  3041.                 // todo: implement
  3042.             default:
  3043.                 return;
  3044.  
  3045.             }
  3046.  
  3047.             if ($hasText) {
  3048.                 // offset: var; size: 4; character count of text mark including trailing zero word
  3049.                 $tl = $this->_GetInt4d($recordData, $offset);
  3050.                 $offset += 4;
  3051.                 // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated
  3052.                 $text = $this->_encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false);
  3053.                 $url .= $text;
  3054.             }
  3055.  
  3056.             // apply the hyperlink to all the relevant cells
  3057.             foreach (PHPExcel_Cell::extractAllCellReferencesInRange($cellRange) as $coordinate) {
  3058.                 $this->_phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url);
  3059.             }
  3060.         }
  3061.     }
  3062.  
  3063.     /**
  3064.      * Read RANGEPROTECTION record
  3065.      * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification,
  3066.      * where it is referred to as FEAT record
  3067.      */
  3068.     private function _readRangeProtection()
  3069.     {
  3070.         $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  3071.         $recordData = substr($this->_data, $this->_pos + 4, $length);
  3072.  
  3073.         // move stream pointer to next record
  3074.         $this->_pos += 4 + $length;
  3075.  
  3076.         // local pointer in record data
  3077.         $pos = 0;
  3078.  
  3079.         if (!$this->_readDataOnly) {
  3080.             $pos += 12;
  3081.  
  3082.             // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag
  3083.             $isf = $this->_GetInt2d($recordData, 12);
  3084.             if ($isf != 2) {
  3085.                 // we only read FEAT records of type 2
  3086.                 return;
  3087.             }
  3088.             $pos += 2;
  3089.  
  3090.             $pos += 5;
  3091.  
  3092.             // offset: 19; size: 2; count of ref ranges this feature is on
  3093.             $cref = $this->_GetInt2d($recordData, 19);
  3094.             $pos += 2;
  3095.  
  3096.             $pos += 6;
  3097.  
  3098.             // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record)
  3099.             $cellRanges = array();
  3100.             for ($i = 0; $i < $cref; ++$i) {
  3101.                 $cellRange = $this->_readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8));
  3102.                 $cellRanges[] = $cellRange;
  3103.                 $pos += 8;
  3104.             }
  3105.  
  3106.             // offset: var; size: var; variable length of feature specific data
  3107.             $rgbFeat = substr($recordData, $pos);
  3108.             $pos += 4;
  3109.  
  3110.             // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit)
  3111.             $wPassword = $this->_GetInt4d($recordData, $pos);
  3112.             $pos += 4;
  3113.  
  3114.             // Apply range protection to sheet
  3115.             if ($cellRanges) {
  3116.                 $this->_phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true);
  3117.             }
  3118.         }
  3119.     }
  3120.  
  3121.     /**
  3122.      * Read IMDATA record
  3123.      */
  3124.     private function _readImData()
  3125.     {
  3126.  
  3127.         $spos = $this->_pos;
  3128.         $length = $this->_GetInt2d($this->_data, $spos + 2);
  3129.         // get spliced record data
  3130.         
  3131.         $splicedRecordData = $this->_getSplicedRecordData();
  3132.         $recordData = $splicedRecordData['recordData'];
  3133.  
  3134.         // UNDER CONSTRUCTION
  3135.  
  3136.         // offset: 0; size: 2; image format
  3137.         $cf = $this->_GetInt2d($recordData, 0);
  3138.         // offset: 2; size: 2; environment from which the file was written
  3139.         $env = $this->_GetInt2d($recordData, 2);
  3140.         // offset: 4; size: 4; length of the image data
  3141.         $lcb = $this->_GetInt4d($recordData, 4);
  3142.         // offset: 8; size: var; image data
  3143.         $iData = substr($recordData, 8);
  3144.  
  3145.         switch ($cf) {
  3146.  
  3147.         case 0x09: // Windows bitmap format
  3148.             // BITMAPCOREINFO
  3149.             // 1. BITMAPCOREHEADER
  3150.             // offset: 0; size: 4; bcSize, Specifies the number of bytes required by the structure
  3151.             $bcSize = $this->_GetInt4d($iData, 0);
  3152.             var_dump($bcSize);
  3153.             // offset: 4; size: 2; bcWidth, specifies the width of the bitmap, in pixels
  3154.             $bcWidth = $this->_GetInt2d($iData, 4);
  3155.             var_dump($bcWidth);
  3156.             // offset: 6; size: 2; bcHeight, specifies the height of the bitmap, in pixels.
  3157.             $bcHeight = $this->_GetInt2d($iData, 6);
  3158.             var_dump($bcHeight);
  3159.             $ih = imagecreatetruecolor($bcWidth, $bcHeight);
  3160.             // offset: 8; size: 2; bcPlanes, specifies the number of planes for the target device. This value must be 1
  3161.             // offset: 10; size: 2; bcBitCount specifies the number of bits-per-pixel. This value must be 1, 4, 8, or 24
  3162.             $bcBitCount = $this->_GetInt2d($iData, 10);
  3163.             var_dump($bcBitCount);
  3164.  
  3165.             $rgbString = substr($iData, 12);
  3166.             $rgbTriples = array();
  3167.             while (strlen($rgbString) > 0) {
  3168.                 $rgbTriples[] = unpack('Cb/Cg/Cr', $rgbString);
  3169.                 $rgbString = substr($rgbString, 3);
  3170.             }
  3171.             $x = 0;
  3172.             $y = 0;
  3173.             foreach ($rgbTriples as $i => $rgbTriple) {
  3174.                 $color = imagecolorallocate($ih, $rgbTriple['r'], $rgbTriple['g'], $rgbTriple['b']);
  3175.                 imagesetpixel($ih, $x, $bcHeight - 1 - $y, $color);
  3176.                 $x = ($x + 1) % $bcWidth;
  3177.                 $y = $y + floor(($x + 1) / $bcWidth);
  3178.             }
  3179.             //imagepng($ih, 'image.png');
  3180.  
  3181.             $drawing = new PHPExcel_Worksheet_Drawing();
  3182.             $drawing->setPath($filename);
  3183.             $drawing->setWorksheet($this->_phpSheet);
  3184.  
  3185.             break;
  3186.  
  3187.         case 0x02: // Windows metafile or Macintosh PICT format
  3188.         case 0x0e: // native format
  3189.         default;
  3190.             break;
  3191.  
  3192.         }
  3193.  
  3194.         // _getSplicedRecordData() takes care of moving current position in data stream
  3195.     }
  3196.  
  3197.     /**
  3198.      * Reads a record from current position in data stream and continues reading data as long as CONTINUE
  3199.      * records are found. Splices the record data pieces and returns the combined string as if record data
  3200.      * is in one piece.
  3201.      * Moves to next current position in data stream to start of next record different from a CONtINUE record
  3202.      *
  3203.      * @return array 
  3204.      */
  3205.     private function _getSplicedRecordData()
  3206.     {
  3207.         $data = '';
  3208.         $spliceOffsets = array();
  3209.  
  3210.         $i = 0;
  3211.         $spliceOffsets[0] = 0;
  3212.  
  3213.         do {
  3214.             ++$i;
  3215.             
  3216.             // offset: 0; size: 2; identifier
  3217.             $identifier = $this->_GetInt2d($this->_data, $this->_pos);
  3218.             // offset: 2; size: 2; length
  3219.             $length = $this->_GetInt2d($this->_data, $this->_pos + 2);
  3220.             $data .= substr($this->_data, $this->_pos + 4, $length);
  3221.  
  3222.             $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length;
  3223.  
  3224.             $this->_pos += 4 + $length;
  3225.             $nextIdentifier = $this->_GetInt2d($this->_data, $this->_pos);
  3226.         }
  3227.         while ($nextIdentifier == self::XLS_Type_CONTINUE);
  3228.  
  3229.         $splicedData = array(
  3230.             'recordData' => $data,
  3231.             'spliceOffsets' => $spliceOffsets,
  3232.         );
  3233.         
  3234.         return $splicedData;
  3235.  
  3236.     }
  3237.  
  3238.     /**
  3239.      * Convert formula structure into human readable Excel formula like 'A3+A5*5'
  3240.      *
  3241.      * @param string $formulaStructure The complete binary data for the formula
  3242.      * @return string Human readable formula
  3243.      */
  3244.     private function _getFormulaFromStructure($formulaStructure)
  3245.     {
  3246.         // offset: 0; size: 2; size of the following formula data
  3247.         $sz = $this->_GetInt2d($formulaStructure, 0);
  3248.  
  3249.         // offset: 2; size: sz
  3250.         $formulaData = substr($formulaStructure, 2, $sz);
  3251.  
  3252.         // for debug: dump the formula data
  3253.         //echo '<xmp>';
  3254.         //echo 'size: ' . $sz . "\n";
  3255.         //echo 'the entire formula data: ';
  3256.         //Debug::dump($formulaData);
  3257.         //echo "\n----\n";
  3258.  
  3259.         // offset: 2 + sz; size: variable (optional)
  3260.         if (strlen($formulaStructure) > 2 + $sz) {
  3261.             $additionalData = substr($formulaStructure, 2 + $sz);
  3262.  
  3263.             // for debug: dump the additional data
  3264.             //echo 'the entire additional data: ';
  3265.             //Debug::dump($additionalData);
  3266.             //echo "\n----\n";
  3267.  
  3268.         } else {
  3269.             $additionalData = '';
  3270.         }
  3271.  
  3272.         return $this->_getFormulaFromData($formulaData, $additionalData);
  3273.     }
  3274.  
  3275.     /**
  3276.      * Take formula data and additional data for formula and return human readable formula
  3277.      *
  3278.      * @param string $formulaData The binary data for the formula itself
  3279.      * @param string $additionalData Additional binary data going with the formula
  3280.      * @return string Human readable formula
  3281.      */
  3282.     private function _getFormulaFromData($formulaData,  $additionalData = '')
  3283.     {
  3284.         // start parsing the formula data
  3285.         $tokens = array();
  3286.  
  3287.         while (strlen($formulaData) > 0 and $token = $this->_getNextToken($formulaData)) {
  3288.             $tokens[] = $token;
  3289.             $formulaData = substr($formulaData, $token['size']);
  3290.  
  3291.             // for debug: dump the token
  3292.             //var_dump($token);
  3293.         }
  3294.  
  3295.         $formulaString = $this->_createFormulaFromTokens($tokens, $additionalData);
  3296.  
  3297.         return $formulaString;
  3298.     }
  3299.  
  3300.     /**
  3301.      * Take array of tokens together with additional data for formula and return human readable formula
  3302.      *
  3303.      * @param array $tokens 
  3304.      * @param array $additionalData Additional binary data going with the formula
  3305.      * @return string Human readable formula
  3306.      */
  3307.     private function _createFormulaFromTokens($tokens, $additionalData)
  3308.     {
  3309.         $formulaStrings = array();
  3310.         foreach ($tokens as $token) {
  3311.             // initialize spaces
  3312.             $space0 = isset($space0) ? $space0 : ''; // spaces before next token, not tParen
  3313.             $space1 = isset($space1) ? $space1 : ''; // carriage returns before next token, not tParen
  3314.             $space2 = isset($space2) ? $space2 : ''; // spaces before opening parenthesis
  3315.             $space3 = isset($space3) ? $space3 : ''; // carriage returns before opening parenthesis
  3316.             $space4 = isset($space4) ? $space4 : ''; // spaces before closing parenthesis
  3317.             $space5 = isset($space5) ? $space5 : ''; // carriage returns before closing parenthesis
  3318.  
  3319.             switch ($token['name']) {
  3320.             case 'tAdd': // addition
  3321.             case 'tConcat': // addition
  3322.             case 'tDiv': // division
  3323.             case 'tEQ': // equaltiy
  3324.             case 'tGE': // greater than or equal
  3325.             case 'tGT': // greater than
  3326.             case 'tIsect': // intersection
  3327.             case 'tLE': // less than or equal
  3328.             case 'tList': // less than or equal
  3329.             case 'tLT': // less than
  3330.             case 'tMul': // multiplication
  3331.             case 'tNE': // multiplication
  3332.             case 'tPower': // power
  3333.             case 'tRange': // range
  3334.             case 'tSub': // subtraction
  3335.                 $op2 = array_pop($formulaStrings);
  3336.                 $op1 = array_pop($formulaStrings);
  3337.                 $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2";
  3338.                 unset($space0, $space1);
  3339.                 break;
  3340.             case 'tUplus': // unary plus
  3341.             case 'tUminus': // unary minus
  3342.                 $op = array_pop($formulaStrings);
  3343.                 $formulaStrings[] = "$space1$space0{$token['data']}$op";
  3344.                 unset($space0, $space1);
  3345.                 break;
  3346.             case 'tPercent': // percent sign
  3347.                 $op = array_pop($formulaStrings);
  3348.                 $formulaStrings[] = "$op$space1$space0{$token['data']}";
  3349.                 unset($space0, $space1);
  3350.                 break;
  3351.             case 'tAttrVolatile': // indicates volatile function
  3352.             case 'tAttrIf':
  3353.             case 'tAttrSkip':
  3354.             case 'tAttrChoose':
  3355.                 // token is only important for Excel formula evaluator
  3356.                 // do nothing
  3357.                 break;
  3358.             case 'tAttrSpace': // space / carriage return
  3359.                 // space will be used when next token arrives, do not alter formulaString stack
  3360.                 switch ($token['data']['spacetype']) {
  3361.                 case 'type0':
  3362.                     $space0 = str_repeat(' ', $token['data']['spacecount']);
  3363.                     break;
  3364.                 case 'type1':
  3365.                     $space1 = str_repeat("\n", $token['data']['spacecount']);
  3366.                     break;
  3367.                 case 'type2':
  3368.                     $space2 = str_repeat(' ', $token['data']['spacecount']);
  3369.                     break;
  3370.                 case 'type3':
  3371.                     $space3 = str_repeat("\n", $token['data']['spacecount']);
  3372.                     break;
  3373.                 case 'type4':
  3374.                     $space4 = str_repeat(' ', $token['data']['spacecount']);
  3375.                     break;
  3376.                 case 'type5':
  3377.                     $space5 = str_repeat("\n", $token['data']['spacecount']);
  3378.                     break;
  3379.                 }
  3380.                 break;
  3381.             case 'tAttrSum': // SUM function with one parameter
  3382.                 $op = array_pop($formulaStrings);
  3383.                 $formulaStrings[] = "{$space1}{$space0}SUM($op)";
  3384.                 unset($space0, $space1);
  3385.                 break;
  3386.             case 'tFunc': // function with fixed number of arguments
  3387.             case 'tFuncV': // function with variable number of arguments
  3388.                 $ops = array(); // array of operators
  3389.                 for ($i = 0; $i < $token['data']['args']; ++$i) {
  3390.                     $ops[] = array_pop($formulaStrings);
  3391.                 }
  3392.                 $ops = array_reverse($ops);
  3393.                 $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ")";
  3394.                 unset($space0, $space1);
  3395.                 break;
  3396.             case 'tParen': // parenthesis
  3397.                 $expression = array_pop($formulaStrings);
  3398.                 $formulaStrings[] = "$space3$space2($expression$space5$space4)";
  3399.                 unset($space2, $space3, $space4, $space5);
  3400.                 break;
  3401.             case 'tArray': // array constant
  3402.                 $constantArray = $this->_readBIFF8ConstantArray($additionalData);
  3403.                 $formulaStrings[] = $space1 . $space0 . $constantArray['value'];
  3404.                 $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data
  3405.                 unset($space0, $space1);
  3406.                 break;
  3407.             case 'tMemArea':
  3408.                 // bite off chunk of additional data
  3409.                 $cellRangeAddressList = $this->_readBIFF8CellRangeAddressList($additionalData);
  3410.                 $additionalData = substr($additionalData, $cellRangeAddressList['size']);
  3411.                 $formulaStrings[] = "$space1$space0{$token['data']}";
  3412.                 unset($space0, $space1);
  3413.                 break;
  3414.             case 'tArea': // cell range address
  3415.             case 'tBool': // boolean
  3416.             case 'tErr': // error code
  3417.             case 'tInt': // integer
  3418.             case 'tMemErr':
  3419.             case 'tMemFunc':
  3420.             case 'tMissArg':
  3421.             case 'tName':
  3422.             case 'tNum': // number
  3423.             case 'tRef': // single cell reference
  3424.             case 'tRef3d': // 3d cell reference
  3425.             case 'tArea3d': // 3d cell range reference
  3426.             case 'tStr': // string
  3427.                 $formulaStrings[] = "$space1$space0{$token['data']}";
  3428.                 unset($space0, $space1);
  3429.                 break;
  3430.             }
  3431.         }
  3432.         $formulaString = $formulaStrings[0];
  3433.  
  3434.         // for debug: dump the human readable formula
  3435.         //echo '----' . "\n";
  3436.         //echo 'Formula: ' . $formulaString;
  3437.  
  3438.         return $formulaString;
  3439.     }
  3440.  
  3441.     /**
  3442.      * Fetch next token from binary formula data
  3443.      *
  3444.      * @param string Formula data
  3445.      * @return array 
  3446.      * @throws Exception
  3447.      */
  3448.     private function _getNextToken($formulaData)
  3449.     {
  3450.         // offset: 0; size: 1; token id
  3451.         $id = ord($formulaData[0]); // token id
  3452.         $name = false; // initialize token name
  3453.  
  3454.         switch ($id) {
  3455.         case 0x03: $name = 'tAdd';        $size = 1;    $data = '+';    break;
  3456.         case 0x04: $name = 'tSub';        $size = 1;    $data = '-';    break;
  3457.         case 0x05: $name = 'tMul';        $size = 1;    $data = '*';    break;
  3458.         case 0x06: $name = 'tDiv';        $size = 1;    $data = '/';    break;
  3459.         case 0x07: $name = 'tPower';    $size = 1;    $data = '^';    break;
  3460.         case 0x08: $name = 'tConcat';    $size = 1;    $data = '&';    break;
  3461.         case 0x09: $name = 'tLT';        $size = 1;    $data = '<';    break;
  3462.         case 0x0A: $name = 'tLE';        $size = 1;    $data = '<=';    break;
  3463.         case 0x0B: $name = 'tEQ';        $size = 1;    $data = '=';    break;
  3464.         case 0x0C: $name = 'tGE';        $size = 1;    $data = '>=';    break;
  3465.         case 0x0D: $name = 'tGT';        $size = 1;    $data = '>';    break;
  3466.         case 0x0E: $name = 'tNE';        $size = 1;    $data = '<>';    break;
  3467.         case 0x0F: $name = 'tIsect';    $size = 1;    $data = ' ';    break;
  3468.         case 0x10: $name = 'tList';        $size = 1;    $data = ',';    break;
  3469.         case 0x11: $name = 'tRange';    $size = 1;    $data = ':';    break;
  3470.         case 0x12: $name = 'tUplus';    $size = 1;    $data = '+';    break;
  3471.         case 0x13: $name = 'tUminus';    $size = 1;    $data = '-';    break;
  3472.         case 0x14: $name = 'tPercent';    $size = 1;    $data = '%';    break;
  3473.         case 0x15: // parenthesis
  3474.             $name  = 'tParen';
  3475.             $size  = 1;
  3476.             $data = null;
  3477.             break;
  3478.         case 0x16: // missing argument
  3479.             $name = 'tMissArg';
  3480.             $size = 1;
  3481.             $data = '';
  3482.             break;
  3483.         case 0x17: // string
  3484.             $name = 'tStr';
  3485.             // offset: 1; size: var; Unicode string, 8-bit string length
  3486.             $string = $this->_readUnicodeStringShort(substr($formulaData, 1));
  3487.             $size = 1 + $string['size'];
  3488.             $data = $this->_UTF8toExcelDoubleQuoted($string['value']);
  3489.             break;
  3490.         case 0x19: // Special attribute
  3491.             // offset: 1; size: 1; attribute type flags:
  3492.             switch (ord($formulaData[1])) {
  3493.             case 0x01:
  3494.                 $name = 'tAttrVolatile';
  3495.                 $size = 4;
  3496.                 $data = null;
  3497.                 break;
  3498.             case 0x02:
  3499.                 $name = 'tAttrIf';
  3500.                 $size = 4;
  3501.                 $data = null;
  3502.                 break;
  3503.             case 0x04:
  3504.                 $name = 'tAttrChoose';
  3505.                 // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1)
  3506.                 $nc = $this->_GetInt2d($formulaData, 2);
  3507.                 // offset: 4; size: 2 * $nc
  3508.                 // offset: 4 + 2 * $nc; size: 2
  3509.                 $size = 2 * $nc + 6;
  3510.                 $data = null;
  3511.                 break;
  3512.             case 0x08:
  3513.                 $name = 'tAttrSkip';
  3514.                 $size = 4;
  3515.                 $data = null;
  3516.                 break;
  3517.             case 0x10:
  3518.                 $name = 'tAttrSum';
  3519.                 $size = 4;
  3520.                 $data = null;
  3521.                 break;
  3522.             case 0x40:
  3523.             case 0x41:
  3524.                 $name = 'tAttrSpace';
  3525.                 $size = 4;
  3526.                 // offset: 2; size: 2; space type and position
  3527.                 switch (ord($formulaData[2])) {
  3528.                 case 0x00:
  3529.                     $spacetype = 'type0';
  3530.                     break;
  3531.                 case 0x01:
  3532.                     $spacetype = 'type1';
  3533.                     break;
  3534.                 case 0x02:
  3535.                     $spacetype = 'type2';
  3536.                     break;
  3537.                 case 0x03:
  3538.                     $spacetype = 'type3';
  3539.                     break;
  3540.                 case 0x04:
  3541.                     $spacetype = 'type4';
  3542.                     break;
  3543.                 case 0x05:
  3544.                     $spacetype = 'type5';
  3545.                     break;
  3546.                 default:
  3547.                     throw new Exception('Unrecognized space type in tAttrSpace token');
  3548.                     break;
  3549.                 }
  3550.                 // offset: 3; size: 1; number of inserted spaces/carriage returns
  3551.                 $spacecount = ord($formulaData[3]);
  3552.  
  3553.                 $data = array('spacetype' => $spacetype, 'spacecount' => $spacecount);
  3554.                 break;
  3555.             default:
  3556.                 throw new Exception('Unrecognized attribute flag in tAttr token');
  3557.                 break;
  3558.             }
  3559.             break;
  3560.         case 0x1C: // error code
  3561.             // offset: 1; size: 1; error code
  3562.             $name = 'tErr';
  3563.             $size = 2;
  3564.             $data = $this->_mapErrorCode(ord($formulaData[1]));
  3565.             break;
  3566.         case 0x1D: // boolean
  3567.             // offset: 1; size: 1; 0 = false, 1 = true;
  3568.             $name = 'tBool';
  3569.             $size = 2;
  3570.             $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE';
  3571.             break;
  3572.         case 0x1E: // integer
  3573.             // offset: 1; size: 2; unsigned 16-bit integer
  3574.             $name = 'tInt';
  3575.             $size = 3;
  3576.             $data = $this->_GetInt2d($formulaData, 1);
  3577.             break;
  3578.         case 0x1F: // number
  3579.             // offset: 1; size: 8;
  3580.             $name = 'tNum';
  3581.             $size = 9;
  3582.             $data = $this->_extractNumber(substr($formulaData, 1));
  3583.             break;
  3584.         case 0x40: // array constant
  3585.         case 0x60: // array constant
  3586.             // offset: 1; size: 7; not used
  3587.             $name = 'tArray';
  3588.             $size = 8;
  3589.             $data = null;
  3590.             break;
  3591.         case 0x41: // function with fixed number of arguments
  3592.             $name = 'tFunc';
  3593.             $size = 3;
  3594.             // offset: 1; size: 2; index to built-in sheet function
  3595.             switch ($this->_GetInt2d($formulaData, 1)) {
  3596.             case   2: $function = 'ISNA';             $args = 1;     break;
  3597.             case   3: $function = 'ISERROR';         $args = 1;     break;
  3598.             case  10: $function = 'NA';             $args = 0;     break;
  3599.             case  15: $function = 'SIN';             $args = 1;     break;
  3600.             case  16: $function = 'COS';             $args = 1;     break;
  3601.             case  17: $function = 'TAN';             $args = 1;     break;
  3602.             case  18: $function = 'ATAN';             $args = 1;     break;
  3603.             case  19: $function = 'PI';             $args = 0;     break;
  3604.             case  20: $function = 'SQRT';             $args = 1;     break;
  3605.             case  21: $function = 'EXP';             $args = 1;     break;
  3606.             case  22: $function = 'LN';             $args = 1;     break;
  3607.             case  23: $function = 'LOG10';             $args = 1;     break;
  3608.             case  24: $function = 'ABS';             $args = 1;     break;
  3609.             case  25: $function = 'INT';             $args = 1;     break;
  3610.             case  26: $function = 'SIGN';             $args = 1;     break;
  3611.             case  27: $function = 'ROUND';             $args = 2;     break;
  3612.             case  30: $function = 'REPT';             $args = 2;     break;
  3613.             case  31: $function = 'MID';             $args = 3;     break;
  3614.             case  32: $function = 'LEN';             $args = 1;     break;
  3615.             case  33: $function = 'VALUE';             $args = 1;     break;
  3616.             case  34: $function = 'TRUE';             $args = 0;     break;
  3617.             case  35: $function = 'FALSE';             $args = 0;     break;
  3618.             case  38: $function = 'NOT';             $args = 1;     break;
  3619.             case  39: $function = 'MOD';             $args = 2;    break;
  3620.             case  40: $function = 'DCOUNT';         $args = 3;    break;
  3621.             case  41: $function = 'DSUM';             $args = 3;    break;
  3622.             case  42: $function = 'DAVERAGE';         $args = 3;    break;
  3623.             case  43: $function = 'DMIN';             $args = 3;    break;
  3624.             case  44: $function = 'DMAX';             $args = 3;    break;
  3625.             case  45: $function = 'DSTDEV';         $args = 3;    break;
  3626.             case  48: $function = 'TEXT';             $args = 2;    break;
  3627.             case  61: $function = 'MIRR';             $args = 3;    break;
  3628.             case  63: $function = 'RAND';             $args = 0;    break;
  3629.             case  65: $function = 'DATE';             $args = 3;    break;
  3630.             case  66: $function = 'TIME';             $args = 3;    break;
  3631.             case  67: $function = 'DAY';             $args = 1;    break;
  3632.             case  68: $function = 'MONTH';             $args = 1;    break;
  3633.             case  69: $function = 'YEAR';             $args = 1;    break;
  3634.             case  71: $function = 'HOUR';             $args = 1;    break;
  3635.             case  72: $function = 'MINUTE';         $args = 1;    break;
  3636.             case  73: $function = 'SECOND';         $args = 1;    break;
  3637.             case  74: $function = 'NOW';             $args = 0;    break;
  3638.             case  75: $function = 'AREAS';             $args = 1;    break;
  3639.             case  76: $function = 'ROWS';             $args = 1;    break;
  3640.             case  77: $function = 'COLUMNS';         $args = 1;    break;
  3641.             case  83: $function = 'TRANSPOSE';         $args = 1;    break;
  3642.             case  86: $function = 'TYPE';             $args = 1;    break;
  3643.             case  97: $function = 'ATAN2';             $args = 2;    break;
  3644.             case  98: $function = 'ASIN';             $args = 1;    break;
  3645.             case  99: $function = 'ACOS';             $args = 1;    break;
  3646.             case 105: $function = 'ISREF';             $args = 1;    break;
  3647.             case 111: $function = 'CHAR';             $args = 1;    break;
  3648.             case 112: $function = 'LOWER';             $args = 1;    break;
  3649.             case 113: $function = 'UPPER';             $args = 1;    break;
  3650.             case 114: $function = 'PROPER';         $args = 1;    break;
  3651.             case 117: $function = 'EXACT';             $args = 2;    break;
  3652.             case 118: $function = 'TRIM';             $args = 1;    break;
  3653.             case 119: $function = 'REPLACE';         $args = 4;    break;
  3654.             case 121: $function = 'CODE';             $args = 1;    break;
  3655.             case 126: $function = 'ISERR';             $args = 1;    break;
  3656.             case 127: $function = 'ISTEXT';         $args = 1;    break;
  3657.             case 128: $function = 'ISNUMBER';         $args = 1;    break;
  3658.             case 129: $function = 'ISBLANK';         $args = 1;    break;
  3659.             case 130: $function = 'T';                 $args = 1;    break;
  3660.             case 131: $function = 'N';                 $args = 1;    break;
  3661.             case 140: $function = 'DATEVALUE';         $args = 1;    break;
  3662.             case 141: $function = 'TIMEVALUE';         $args = 1;    break;
  3663.             case 142: $function = 'SLN';             $args = 3;    break;
  3664.             case 143: $function = 'SYD';             $args = 4;    break;
  3665.             case 162: $function = 'CLEAN';             $args = 1;    break;
  3666.             case 163: $function = 'MDETERM';         $args = 1;    break;
  3667.             case 164: $function = 'MINVERSE';         $args = 1;    break;
  3668.             case 165: $function = 'MMULT';             $args = 2;    break;
  3669.             case 184: $function = 'FACT';             $args = 1;    break;
  3670.             case 189: $function = 'DPRODUCT';         $args = 3;    break;
  3671.             case 190: $function = 'ISNONTEXT';         $args = 1;    break;
  3672.             case 195: $function = 'DSTDEVP';         $args = 3;    break;
  3673.             case 196: $function = 'DVARP';             $args = 3;    break;
  3674.             case 198: $function = 'ISLOGICAL';         $args = 1;    break;
  3675.             case 199: $function = 'DCOUNTA';         $args = 3;    break;
  3676.             case 207: $function = 'REPLACEB';         $args = 4;    break;
  3677.             case 210: $function = 'MIDB';             $args = 3;    break;
  3678.             case 211: $function = 'LENB';             $args = 1;    break;
  3679.             case 212: $function = 'ROUNDUP';         $args = 2;    break;
  3680.             case 213: $function = 'ROUNDDOWN';         $args = 2;    break;
  3681.             case 214: $function = 'ASC';             $args = 1;    break;
  3682.             case 215: $function = 'DBCS';             $args = 1;    break;
  3683.             case 221: $function = 'TODAY';             $args = 0;    break;
  3684.             case 229: $function = 'SINH';             $args = 1;    break;
  3685.             case 230: $function = 'COSH';             $args = 1;    break;
  3686.             case 231: $function = 'TANH';             $args = 1;    break;
  3687.             case 232: $function = 'ASINH';             $args = 1;    break;
  3688.             case 233: $function = 'ACOSH';             $args = 1;    break;
  3689.             case 234: $function = 'ATANH';             $args = 1;    break;
  3690.             case 235: $function = 'DGET';             $args = 3;    break;
  3691.             case 244: $function = 'INFO';             $args = 1;    break;
  3692.             case 252: $function = 'FREQUENCY';         $args = 2;    break;
  3693.             case 261: $function = 'ERROR.TYPE';     $args = 1;    break;
  3694.             case 271: $function = 'GAMMALN';         $args = 1;    break;
  3695.             case 273: $function = 'BINOMDIST';         $args = 4;    break;
  3696.             case 274: $function = 'CHIDIST';         $args = 2;    break;
  3697.             case 275: $function = 'CHIINV';         $args = 2;    break;
  3698.             case 276: $function = 'COMBIN';         $args = 2;    break;
  3699.             case 277: $function = 'CONFIDENCE';     $args = 3;    break;
  3700.             case 278: $function = 'CRITBINOM';         $args = 3;    break;
  3701.             case 279: $function = 'EVEN';             $args = 1;    break;
  3702.             case 280: $function = 'EXPONDIST';         $args = 3;    break;
  3703.             case 281: $function = 'FDIST';             $args = 3;    break;
  3704.             case 282: $function = 'FINV';             $args = 3;    break;
  3705.             case 283: $function = 'FISHER';         $args = 1;    break;
  3706.             case 284: $function = 'FISHERINV';         $args = 1;    break;
  3707.             case 285: $function = 'FLOOR';             $args = 2;    break;
  3708.             case 286: $function = 'GAMMADIST';         $args = 4;    break;
  3709.             case 287: $function = 'GAMMAINV';         $args = 3;    break;
  3710.             case 288: $function = 'CEILING';         $args = 2;    break;
  3711.             case 289: $function = 'HYPGEOMDIST';    $args = 4;    break;
  3712.             case 290: $function = 'LOGNORMDIST';    $args = 3;    break;
  3713.             case 291: $function = 'LOGINV';            $args = 3;    break;
  3714.             case 292: $function = 'NEGBINOMDIST';    $args = 3;    break;
  3715.             case 293: $function = 'NORMDIST';        $args = 4;    break;
  3716.             case 294: $function = 'NORMSDIST';        $args = 1;    break;
  3717.             case 295: $function = 'NORMINV';        $args = 3;    break;
  3718.             case 296: $function = 'NORMSINV';        $args = 1;    break;
  3719.             case 297: $function = 'STANDARDIZE';    $args = 3;    break;
  3720.             case 298: $function = 'ODD';            $args = 1;    break;
  3721.             case 299: $function = 'PERMUT';            $args = 2;    break;
  3722.             case 300: $function = 'POISSON';        $args = 3;    break;
  3723.             case 301: $function = 'TDIST';            $args = 3;    break;
  3724.             case 302: $function = 'WEIBULL';        $args = 4;    break;
  3725.             case 303: $function = 'SUMXMY2';        $args = 2;    break;
  3726.             case 304: $function = 'SUMX2MY2';        $args = 2;    break;
  3727.             case 305: $function = 'SUMX2PY2';        $args = 2;    break;
  3728.             case 306: $function = 'CHITEST';        $args = 2;    break;
  3729.             case 307: $function = 'CORREL';            $args = 2;    break;
  3730.             case 308: $function = 'COVAR';            $args = 2;    break;
  3731.             case 309: $function = 'FORECAST';        $args = 3;    break;
  3732.             case 310: $function = 'FTEST';            $args = 2;    break;
  3733.             case 311: $function = 'INTERCEPT';        $args = 2;    break;
  3734.             case 312: $function = 'PEARSON';        $args = 2;    break;
  3735.             case 313: $function = 'RSQ';            $args = 2;    break;
  3736.             case 314: $function = 'STEYX';            $args = 2;    break;
  3737.             case 315: $function = 'SLOPE';            $args = 2;    break;
  3738.             case 316: $function = 'TTEST';            $args = 4;    break;
  3739.             case 325: $function = 'LARGE';            $args = 2;    break;
  3740.             case 326: $function = 'SMALL';            $args = 2;    break;
  3741.             case 327: $function = 'QUARTILE';        $args = 2;    break;
  3742.             case 328: $function = 'PERCENTILE';        $args = 2;    break;
  3743.             case 331: $function = 'TRIMMEAN';        $args = 2;    break;
  3744.             case 332: $function = 'TINV';            $args = 2;    break;
  3745.             case 337: $function = 'POWER';            $args = 2;    break;
  3746.             case 342: $function = 'RADIANS';        $args = 1;    break;
  3747.             case 343: $function = 'DEGREES';        $args = 1;    break;
  3748.             case 346: $function = 'COUNTIF';        $args = 2;    break;
  3749.             case 347: $function = 'COUNTBLANK';        $args = 1;    break;
  3750.             case 350: $function = 'ISPMT';            $args = 4;    break;
  3751.             case 351: $function = 'DATEDIF';        $args = 3;    break;
  3752.             case 352: $function = 'DATESTRING';        $args = 1;    break;
  3753.             case 353: $function = 'NUMBERSTRING';    $args = 2;    break;
  3754.             case 360: $function = 'PHONETIC';        $args = 1;    break;
  3755.             default:
  3756.                 throw new Exception('Unrecognized function in formula');
  3757.                 break;
  3758.             }
  3759.             $data = array('function' => $function, 'args' => $args);
  3760.             break;
  3761.         case 0x42: // function with variable number of arguments
  3762.         case 0x62: // function with variable number of arguments
  3763.             $name = 'tFuncV';
  3764.             $size = 4;
  3765.             // offset: 1; size: 1; number of arguments
  3766.             $args = ord($formulaData[1]);
  3767.             // offset: 2: size: 2; index to built-in sheet function
  3768.             switch ($this->_GetInt2d($formulaData, 2)) {
  3769.             case   0: $function = 'COUNT';            break;
  3770.             case   1: $function = 'IF';                break;
  3771.             case   4: $function = 'SUM';            break;
  3772.             case   5: $function = 'AVERAGE';        break;
  3773.             case   6: $function = 'MIN';            break;
  3774.             case   7: $function = 'MAX';            break;
  3775.             case   8: $function = 'ROW';            break;
  3776.             case   9: $function = 'COLUMN';            break;
  3777.             case  11: $function = 'NPV';            break;
  3778.             case  12: $function = 'STDEV';            break;
  3779.             case  13: $function = 'DOLLAR';            break;
  3780.             case  14: $function = 'FIXED';            break;
  3781.             case  28: $function = 'LOOKUP';            break;
  3782.             case  29: $function = 'INDEX';            break;
  3783.             case  36: $function = 'AND';            break;
  3784.             case  37: $function = 'OR';                break;
  3785.             case  46: $function = 'VAR';            break;
  3786.             case  49: $function = 'LINEST';            break;
  3787.             case  50: $function = 'TREND';            break;
  3788.             case  51: $function = 'LOGEST';            break;
  3789.             case  52: $function = 'GROWTH';            break;
  3790.             case  56: $function = 'PV';                break;
  3791.             case  57: $function = 'FV';                break;
  3792.             case  58: $function = 'NPER';            break;
  3793.             case  59: $function = 'PMT';            break;
  3794.             case  60: $function = 'RATE';            break;
  3795.             case  62: $function = 'IRR';            break;
  3796.             case  64: $function = 'MATCH';            break;
  3797.             case  70: $function = 'WEEKDAY';        break;
  3798.             case  78: $function = 'OFFSET';            break;
  3799.             case  82: $function = 'SEARCH';            break;
  3800.             case 100: $function = 'CHOOSE';            break;
  3801.             case 101: $function = 'HLOOKUP';        break;
  3802.             case 102: $function = 'VLOOKUP';        break;
  3803.             case 109: $function = 'LOG';            break;
  3804.             case 115: $function = 'LEFT';            break;
  3805.             case 116: $function = 'RIGHT';            break;
  3806.             case 120: $function = 'SUBSTITUTE';        break;
  3807.             case 124: $function = 'FIND';            break;
  3808.             case 125: $function = 'CELL';            break;
  3809.             case 144: $function = 'DDB';            break;
  3810.             case 148: $function = 'INDIRECT';        break;
  3811.             case 167: $function = 'IPMT';            break;
  3812.             case 168: $function = 'PPMT';            break;
  3813.             case 169: $function = 'COUNTA';            break;
  3814.             case 183: $function = 'PRODUCT';        break;
  3815.             case 193: $function = 'STDEVP';            break;
  3816.             case 194: $function = 'VARP';            break;
  3817.             case 197: $function = 'TRUNC';            break;
  3818.             case 204: $function = 'USDOLLAR';        break;
  3819.             case 205: $function = 'FINDB';            break;
  3820.             case 206: $function = 'SEARCHB';        break;
  3821.             case 208: $function = 'LEFTB';            break;
  3822.             case 209: $function = 'RIGHTB';            break;
  3823.             case 216: $function = 'RANK';            break;
  3824.             case 219: $function = 'ADDRESS';        break;
  3825.             case 220: $function = 'DAYS360';        break;
  3826.             case 222: $function = 'VDB';            break;
  3827.             case 227: $function = 'MEDIAN';            break;
  3828.             case 228: $function = 'SUMPRODUCT';        break;
  3829.             case 247: $function = 'DB';                break;
  3830.             case 269: $function = 'AVEDEV';            break;
  3831.             case 270: $function = 'BETADIST';        break;
  3832.             case 272: $function = 'BETAINV';        break;
  3833.             case 317: $function = 'PROB';            break;
  3834.             case 318: $function = 'DEVSQ';            break;
  3835.             case 319: $function = 'GEOMEAN';        break;
  3836.             case 320: $function = 'HARMEAN';        break;
  3837.             case 321: $function = 'SUMSQ';            break;
  3838.             case 322: $function = 'KURT';            break;
  3839.             case 323: $function = 'SKEW';            break;
  3840.             case 324: $function = 'ZTEST';            break;
  3841.             case 329: $function = 'PERCENTRANK';    break;
  3842.             case 330: $function = 'MODE';            break;
  3843.             case 336: $function = 'CONCATENATE';    break;
  3844.             case 344: $function = 'SUBTOTAL';        break;
  3845.             case 345: $function = 'SUMIF';            break;
  3846.             case 354: $function = 'ROMAN';            break;
  3847.             case 358: $function = 'GETPIVOTDATA';    break;
  3848.             case 359: $function = 'HYPERLINK';        break;
  3849.             case 361: $function = 'AVERAGEA';        break;
  3850.             case 362: $function = 'MAXA';            break;
  3851.             case 363: $function = 'MINA';            break;
  3852.             case 364: $function = 'STDEVPA';        break;
  3853.             case 365: $function = 'VARPA';            break;
  3854.             case 366: $function = 'STDEVA';            break;
  3855.             case 367: $function = 'VARA';            break;
  3856.             default:
  3857.                 throw new Exception('Unrecognized function in formula');
  3858.                 break;
  3859.             }
  3860.             $data = array('function' => $function, 'args' => $args);
  3861.             break;
  3862.         case 0x23: // index to defined name
  3863.         case 0x43:
  3864.             $name = 'tName';
  3865.             $size = 5;
  3866.             // offset: 1; size: 2; one-based index to definedname record
  3867.             $definedNameIndex = $this->_GetInt2d($formulaData, 1) - 1;
  3868.             // offset: 2; size: 2; not used
  3869.             $data = $this->_definedname[$definedNameIndex]['name'];
  3870.             break;
  3871.         case 0x24: // single cell reference e.g. A5
  3872.         case 0x44:
  3873.         case 0x64:
  3874.             $name = 'tRef';
  3875.             $size = 5;
  3876.             $data = $this->_readBIFF8CellAddress(substr($formulaData, 1, 4));
  3877.             break;
  3878.         case 0x25: // cell range reference to cells in the same sheet
  3879.         case 0x45:
  3880.         case 0x65:
  3881.             $name = 'tArea';
  3882.             $size = 9;
  3883.             $data = $this->_readBIFF8CellRangeAddress(substr($formulaData, 1, 8));
  3884.             break;
  3885.         case 0x26:
  3886.         case 0x46:
  3887.             $name = 'tMemArea';
  3888.             // offset: 1; size: 4; not used
  3889.             // offset: 5; size: 2; size of the following subexpression
  3890.             $subSize = $this->_GetInt2d($formulaData, 5);
  3891.             $size = 7 + $subSize;
  3892.             $data = $this->_getFormulaFromData(substr($formulaData, 7, $subSize));
  3893.             break;
  3894.         case 0x47:
  3895.             $name = 'tMemErr';
  3896.             // offset: 1; size: 4; not used
  3897.             // offset: 5; size: 2; size of the following subexpression
  3898.             $subSize = $this->_GetInt2d($formulaData, 5);
  3899.             $size = 7 + $subSize;
  3900.             $data = $this->_getFormulaFromData(substr($formulaData, 7, $subSize));
  3901.             break;
  3902.         case 0x29:
  3903.         case 0x49:
  3904.             $name = 'tMemFunc';
  3905.             // offset: 1; size: 2; size of the following subexpression
  3906.             $subSize = $this->_GetInt2d($formulaData, 1);
  3907.             $size = 3 + $subSize;
  3908.             $data = $this->_getFormulaFromData(substr($formulaData, 3, $subSize));
  3909.             break;
  3910.         case 0x3A: // 3d reference to cell
  3911.         case 0x5A:
  3912.             $name = 'tRef3d';
  3913.             $size = 7;
  3914.             // offset: 1; size: 2; index to REF entry
  3915.             $sheetRange = $this->_readSheetRangeByRefIndex($this->_GetInt2d($formulaData, 1));
  3916.             // offset: 3; size: 4; cell address
  3917.             $cellAddress = $this->_readBIFF8CellAddress(substr($formulaData, 3, 4));
  3918.  
  3919.             $data = "$sheetRange!$cellAddress";
  3920.  
  3921.             break;
  3922.         case 0x3B: // 3d reference to cell range
  3923.         case 0x5B:
  3924.             $name = 'tArea3d';
  3925.             $size = 11;
  3926.             // offset: 1; size: 2; index to REF entry
  3927.             $sheetRange = $this->_readSheetRangeByRefIndex($this->_GetInt2d($formulaData, 1));
  3928.             // offset: 3; size: 8; cell address
  3929.             $cellRangeAddress = $this->_readBIFF8CellRangeAddress(substr($formulaData, 3, 8));
  3930.  
  3931.             $data = "$sheetRange!$cellRangeAddress";
  3932.  
  3933.             break;
  3934.         // case 0x39: // don't know how to deal with
  3935.         default:
  3936.             throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula');
  3937.             break;
  3938.         }
  3939.  
  3940.         return array(
  3941.             'id' => $id,
  3942.             'name' => $name,
  3943.             'size' => $size,
  3944.             'data' => $data,
  3945.         );
  3946.     }
  3947.  
  3948.     /**
  3949.      * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2'
  3950.      * section 3.3.4
  3951.      *
  3952.      * @param string $cellAddressStructure 
  3953.      * @return string 
  3954.      */
  3955.     private function _readBIFF8CellAddress($cellAddressStructure)
  3956.     {
  3957.         // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
  3958.             $row = $this->_GetInt2d($cellAddressStructure, 0) + 1;
  3959.  
  3960.         // offset: 2; size: 2; index to column or column offset + relative flags
  3961.             // bit: 7-0; mask 0x00FF; column index
  3962.             $column = PHPExcel_Cell::stringFromColumnIndex(0x00FF & $this->_GetInt2d($cellAddressStructure, 2));
  3963.             // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
  3964.             if (!(0x4000 & $this->_GetInt2d($cellAddressStructure, 2))) {
  3965.                 $column = '$' . $column;
  3966.             }
  3967.             // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
  3968.             if (!(0x8000 & $this->_GetInt2d($cellAddressStructure, 2))) {
  3969.                 $row = '$' . $row;
  3970.             }
  3971.  
  3972.         return $column . $row;
  3973.     }
  3974.  
  3975.     /**
  3976.      * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1'
  3977.      * always fixed range
  3978.      * section 2.5.14
  3979.      *
  3980.      * @param string $subData 
  3981.      * @return string 
  3982.      */
  3983.     private function _readBIFF8CellRangeAddressFixed($subData)
  3984.     {
  3985.         // offset: 0; size: 2; index to first row
  3986.         $fr = $this->_GetInt2d($subData, 0) + 1;
  3987.  
  3988.         // offset: 2; size: 2; index to last row
  3989.         $lr = $this->_GetInt2d($subData, 2) + 1;
  3990.  
  3991.         // offset: 4; size: 2; index to first column
  3992.         $fc = PHPExcel_Cell::stringFromColumnIndex($this->_GetInt2d($subData, 4));
  3993.  
  3994.         // offset: 6; size: 2; index to last column
  3995.         $lc = PHPExcel_Cell::stringFromColumnIndex($this->_GetInt2d($subData, 6));
  3996.  
  3997.         if ($fr == $lr and $fc == $lc) {
  3998.             return "$fc$fr";
  3999.         }
  4000.         return "$fc$fr:$lc$lr";
  4001.     }
  4002.  
  4003.     /**
  4004.      * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6'
  4005.      * there are flags indicating whether column/row index is relative
  4006.      * section 3.3.4
  4007.      *
  4008.      * @param string $subData 
  4009.      * @return string 
  4010.      */
  4011.     private function _readBIFF8CellRangeAddress($subData)
  4012.     {
  4013.         // todo: if cell range is just a single cell, should this funciton
  4014.         // not just return e.g. 'A1' and not 'A1:A1' ?
  4015.  
  4016.         // offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767))
  4017.             $fr = $this->_GetInt2d($subData, 0) + 1;
  4018.         // offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767))
  4019.             $lr = $this->_GetInt2d($subData, 2) + 1;
  4020.         // offset: 4; size: 2; index to first column or column offset + relative flags
  4021.             // bit: 7-0; mask 0x00FF; column index
  4022.             $fc = PHPExcel_Cell::stringFromColumnIndex(0x00FF & $this->_GetInt2d($subData, 4));
  4023.             // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
  4024.             if (!(0x4000 & $this->_GetInt2d($subData, 4))) {
  4025.                 $fc = '$' . $fc;
  4026.             }
  4027.             // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
  4028.             if (!(0x8000 & $this->_GetInt2d($subData, 4))) {
  4029.                 $fr = '$' . $fr;
  4030.             }
  4031.         // offset: 6; size: 2; index to last column or column offset + relative flags
  4032.             // bit: 7-0; mask 0x00FF; column index
  4033.             $lc = PHPExcel_Cell::stringFromColumnIndex(0x00FF & $this->_GetInt2d($subData, 6));
  4034.             // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
  4035.             if (!(0x4000 & $this->_GetInt2d($subData, 6))) {
  4036.                 $lc = '$' . $lc;
  4037.             }
  4038.             // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
  4039.             if (!(0x8000 & $this->_GetInt2d($subData, 6))) {
  4040.                 $lr = '$' . $lr;
  4041.             }
  4042.  
  4043.         return "$fc$fr:$lc$lr";
  4044.     }
  4045.  
  4046.     /**
  4047.      * Read BIFF8 cell range address list
  4048.      * section 2.5.15
  4049.      *
  4050.      * @param string $subData 
  4051.      * @return array 
  4052.      */
  4053.     private function _readBIFF8CellRangeAddressList($subData)
  4054.     {
  4055.         $cellRangeAddresses = array();
  4056.  
  4057.         // offset: 0; size: 2; number of the following cell range addresses
  4058.         $nm = $this->_GetInt2d($subData, 0);
  4059.  
  4060.         $offset = 2;
  4061.         // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses
  4062.         for ($i = 0; $i < $nm; ++$i) {
  4063.             $cellRangeAddresses[] = $this->_readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
  4064.             $offset += 8;
  4065.         }
  4066.  
  4067.         return array(
  4068.             'size' => 2 + 8 * $nm,
  4069.             'cellRangeAddresses' => $cellRangeAddresses,
  4070.         );
  4071.     }
  4072.  
  4073.     /**
  4074.      * Get a sheet range like Sheet1:Sheet3 from REF index
  4075.      * Note: If there is only one sheet in the range, one gets e.g Sheet1
  4076.      * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets,
  4077.      * in which case an exception is thrown
  4078.      *
  4079.      * @param int $index 
  4080.      * @return string|false
  4081.      * @throws Exception
  4082.      */
  4083.     private function _readSheetRangeByRefIndex($index)
  4084.     {
  4085.         // we are assuming that ref index refers to internal workbook
  4086.         // in general, this is wrong, fix later
  4087.         if (isset($this->_ref[$index])) {
  4088.  
  4089.             // check if we have deleted 3d reference
  4090.             if ($this->_ref[$index]['firstSheetIndex'] == 0xFFFF or $this->_ref[$index]['lastSheetIndex'] == 0xFFFF) {
  4091.                 throw new Exception('Deleted sheet reference');
  4092.             }
  4093.  
  4094.             // we have normal sheet range (collapsed or uncollapsed)
  4095.             $firstSheetName = $this->_sheets[$this->_ref[$index]['firstSheetIndex']]['name'];
  4096.             $lastSheetName = $this->_sheets[$this->_ref[$index]['lastSheetIndex']]['name'];
  4097.  
  4098.             if ($firstSheetName == $lastSheetName) {
  4099.                 // collapsed sheet range
  4100.                 $sheetRange = $firstSheetName;
  4101.             } else {
  4102.                 $sheetRange = "$firstSheetName:$lastSheetName";
  4103.             }
  4104.  
  4105.             // escape the single-quotes
  4106.             $sheetRange = str_replace("'", "''", $sheetRange);
  4107.  
  4108.             // if there are special characters, we need to enclose the range in single-quotes
  4109.             // todo: check if we have identified the whole set of special characters
  4110.             // it seems that the following characters are not accepted for sheet names
  4111.             // and we may assume that they are not present: []*/:\?
  4112.             if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/", $sheetRange)) {
  4113.                 $sheetRange = "'$sheetRange'";
  4114.             }
  4115.  
  4116.             return $sheetRange;
  4117.         }
  4118.         return false;
  4119.     }
  4120.  
  4121.     /**
  4122.      * read BIFF8 constant value array from array data
  4123.      * returns e.g. array('value' => '{1,2;3,4}', 'size' => 40}
  4124.      * section 2.5.8
  4125.      *
  4126.      * @param string $arrayData 
  4127.      * @return array 
  4128.      */
  4129.     private function _readBIFF8ConstantArray($arrayData)
  4130.     {
  4131.         // offset: 0; size: 1; number of columns decreased by 1
  4132.         $nc = ord($arrayData[0]);
  4133.         // offset: 1; size: 2; number of rows decreased by 1
  4134.         $nr = $this->_GetInt2d($arrayData, 1);
  4135.         $size = 3; // initialize
  4136.         $arrayData = substr($arrayData, 3);
  4137.         // offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values
  4138.         $matrixChunks = array();
  4139.         for ($r = 1; $r <= $nr + 1; ++$r) {
  4140.             $items = array();
  4141.             for ($c = 1; $c <= $nc + 1; ++$c) {
  4142.                 $constant = $this->_readBIFF8Constant($arrayData);
  4143.                 $items[] = $constant['value'];
  4144.                 $arrayData = substr($arrayData, $constant['size']);
  4145.                 $size += $constant['size'];
  4146.             }
  4147.             $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'
  4148.         }
  4149.         $matrix = '{' . implode(';', $matrixChunks) . '}';
  4150.  
  4151.         return array(
  4152.             'value' => $matrix,
  4153.             'size' => $size,
  4154.         );
  4155.     }
  4156.  
  4157.     /**
  4158.      * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value'
  4159.      * section 2.5.7
  4160.      * returns e.g. array('value' => '5', 'size' => 9)
  4161.      *
  4162.      * @param string $valueData 
  4163.      * @return array 
  4164.      */
  4165.     private function _readBIFF8Constant($valueData)
  4166.     {
  4167.         // offset: 0; size: 1; identifier for type of constant
  4168.         $identifier = ord($valueData[0]);
  4169.         switch ($identifier) {
  4170.         case 0x00: // empty constant (what is this?)
  4171.             $value = '';
  4172.             $size = 9;
  4173.             break;
  4174.         case 0x01: // number
  4175.             // offset: 1; size: 8; IEEE 754 floating-point value
  4176.             $value = $this->_extractNumber(substr($valueData, 1, 8));
  4177.             $size = 9;
  4178.             break;
  4179.         case 0x02: // string value
  4180.             // offset: 1; size: var; Unicode string, 16-bit string length
  4181.             $string = $this->_readUnicodeStringLong(substr($valueData, 1));
  4182.             $value = '"' . $string['value'] . '"';
  4183.             $size = 1 + $string['size'];
  4184.             break;
  4185.         case 0x04: // boolean
  4186.             // offset: 1; size: 1; 0 = FALSE, 1 = TRUE
  4187.             if (ord($valueData[1])) {
  4188.                 $value = 'TRUE';
  4189.             } else {
  4190.                 $value = 'FALSE';
  4191.             }
  4192.             $size = 9;
  4193.             break;
  4194.         case 0x10: // error code
  4195.             // offset: 1; size: 1; error code
  4196.             $value = $this->_mapErrorCode(ord($valueData[1]));
  4197.             $size = 9;
  4198.             break;
  4199.         }
  4200.         return array(
  4201.             'value' => $value,
  4202.             'size' => $size,
  4203.         );
  4204.     }
  4205.  
  4206.     /**
  4207.      * Extract RGB color
  4208.      * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4
  4209.      *
  4210.      * @param string $rgb Encoded RGB value (4 bytes)
  4211.      * @return array 
  4212.      */
  4213.     private function _readRGB($rgb)
  4214.     {
  4215.         // offset: 0; size 1; Red component
  4216.         $r = ord($rgb{0});
  4217.         
  4218.         // offset: 1; size: 1; Green component
  4219.         $g = ord($rgb{1});
  4220.         
  4221.         // offset: 2; size: 1; Blue component
  4222.         $b = ord($rgb{2});
  4223.         
  4224.         // HEX notation, e.g. 'FF00FC'
  4225.         $rgb = sprintf('%02X', $r) . sprintf('%02X', $g) . sprintf('%02X', $b);
  4226.         
  4227.         return array('rgb' => $rgb);
  4228.     }
  4229.  
  4230.     /**
  4231.      * Read byte string (8-bit string length)
  4232.      * OpenOffice documentation: 2.5.2
  4233.      *
  4234.      * @param string $subData 
  4235.      * @return array 
  4236.      */
  4237.     private function _readByteStringShort($subData)
  4238.     {
  4239.         // offset: 0; size: 1; length of the string (character count)
  4240.         $ln = ord($subData[0]);
  4241.         // offset: 1: size: var; character array (8-bit characters)
  4242.         $value = $this->_decodeCodepage(substr($subData, 1, $ln));
  4243.  
  4244.         return array(
  4245.             'value' => $value,
  4246.             'size' => 1 + $ln, // size in bytes of data structure
  4247.         );
  4248.     }
  4249.  
  4250.     /**
  4251.      * Read byte string (16-bit string length)
  4252.      * OpenOffice documentation: 2.5.2
  4253.      *
  4254.      * @param string $subData 
  4255.      * @return array 
  4256.      */
  4257.     private function _readByteStringLong($subData)
  4258.     {
  4259.         // offset: 0; size: 2; length of the string (character count)
  4260.         $ln = $this->_GetInt2d($subData, 0);
  4261.         // offset: 2: size: var; character array (8-bit characters)
  4262.         $value = $this->_decodeCodepage(substr($subData, 2));
  4263.  
  4264.         //return $string;
  4265.         return array(
  4266.             'value' => $value,
  4267.             'size' => 2 + $ln, // size in bytes of data structure
  4268.         );
  4269.     }
  4270.  
  4271.     /**
  4272.      * Extracts an Excel Unicode short string (8-bit string length)
  4273.      * OpenOffice documentation: 2.5.3
  4274.      * function will automatically find out where the Unicode string ends.
  4275.      *
  4276.      * @param string $subData 
  4277.      * @return array 
  4278.      */
  4279.     private function _readUnicodeStringShort($subData)
  4280.     {
  4281.         $value = '';
  4282.  
  4283.         // offset: 0: size: 1; length of the string (character count)
  4284.         $characterCount = ord($subData[0]);
  4285.  
  4286.         $string = $this->_readUnicodeString(substr($subData, 1), $characterCount);
  4287.  
  4288.         // add 1 for the string length
  4289.         $string['size'] += 1;
  4290.  
  4291.         return $string;
  4292.     }
  4293.  
  4294.     /**
  4295.      * Extracts an Excel Unicode long string (16-bit string length)
  4296.      * OpenOffice documentation: 2.5.3
  4297.      * this function is under construction, needs to support rich text, and Asian phonetic settings
  4298.      *
  4299.      * @param string $subData 
  4300.      * @return array 
  4301.      */
  4302.     private function _readUnicodeStringLong($subData)
  4303.     {
  4304.         $value = '';
  4305.  
  4306.         // offset: 0: size: 2; length of the string (character count)
  4307.         $characterCount = $this->_GetInt2d($subData, 0);
  4308.  
  4309.         $string = $this->_readUnicodeString(substr($subData, 2), $characterCount);
  4310.  
  4311.         // add 2 for the string length
  4312.         $string['size'] += 2;
  4313.  
  4314.         return $string;
  4315.     }
  4316.  
  4317.     /**
  4318.      * Read Unicode string with no string length field, but with known character count
  4319.      * this function is under construction, needs to support rich text, and Asian phonetic settings
  4320.      * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3
  4321.      *
  4322.      * @param string $subData 
  4323.      * @param int $characterCount 
  4324.      * @return array 
  4325.      */
  4326.     private function _readUnicodeString($subData, $characterCount)
  4327.     {
  4328.         $value = '';
  4329.  
  4330.         // offset: 0: size: 1; option flags
  4331.             // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)
  4332.             $isCompressed = !((0x01 & ord($subData[0])) >> 0);
  4333.  
  4334.             // bit: 2; mask: 0x04; Asian phonetic settings
  4335.             $hasAsian = (0x04) & ord($subData[0]) >> 2;
  4336.  
  4337.             // bit: 3; mask: 0x08; Rich-Text settings
  4338.             $hasRichText = (0x08) & ord($subData[0]) >> 3;
  4339.  
  4340.         // offset: 1: size: var; character array
  4341.         // this offset assumes richtext and Asian phonetic settings are off which is generally wrong
  4342.         // needs to be fixed
  4343.         $value = $this->_encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed);
  4344.  
  4345.         return array(
  4346.             'value' => $value,
  4347.             'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags
  4348.         );
  4349.     }
  4350.  
  4351.     /**
  4352.      * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas.
  4353.      * Example:  hello"world  -->  "hello""world"
  4354.      *
  4355.      * @param string $value UTF-8 encoded string
  4356.      * @return string 
  4357.      */
  4358.     private function _UTF8toExcelDoubleQuoted($value)
  4359.     {
  4360.         return '"' . str_replace('"', '""', $value) . '"';
  4361.     }
  4362.  
  4363.     /**
  4364.      * Reads 8 bytes and returns IEEE 754 float
  4365.      */
  4366.     private function _createNumber($spos)
  4367.     {
  4368.         $rknumhigh = $this->_GetInt4d($this->_data, $spos + 10);
  4369.         $rknumlow = $this->_GetInt4d($this->_data, $spos + 6);
  4370.         $sign = ($rknumhigh & 0x80000000) >> 31;
  4371.         $exp = ($rknumhigh & 0x7ff00000) >> 20;
  4372.         $mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
  4373.         $mantissalow1 = ($rknumlow & 0x80000000) >> 31;
  4374.         $mantissalow2 = ($rknumlow & 0x7fffffff);
  4375.         $value = $mantissa / pow( 2 , (20- ($exp - 1023)));
  4376.  
  4377.         if ($mantissalow1 != 0) {
  4378.             $value += 1 / pow (2 , (21 - ($exp - 1023)));
  4379.         }
  4380.  
  4381.         $value += $mantissalow2 / pow (2 , (52 - ($exp - 1023)));
  4382.         if ($sign) {
  4383.             $value = -1 * $value;
  4384.         }
  4385.  
  4386.         return    $value;
  4387.     }
  4388.  
  4389.     /**
  4390.      * Same as _createNumber, but not hardcoded to read from $this->_data
  4391.      */
  4392.     private function _extractNumber($subData)
  4393.     {
  4394.         $rknumhigh = $this->_GetInt4d($subData, 4);
  4395.         $rknumlow = $this->_GetInt4d($subData, 0);
  4396.         $sign = ($rknumhigh & 0x80000000) >> 31;
  4397.         $exp = ($rknumhigh & 0x7ff00000) >> 20;
  4398.         $mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
  4399.         $mantissalow1 = ($rknumlow & 0x80000000) >> 31;
  4400.         $mantissalow2 = ($rknumlow & 0x7fffffff);
  4401.         $value = $mantissa / pow( 2 , (20- ($exp - 1023)));
  4402.  
  4403.         if ($mantissalow1 != 0) {
  4404.             $value += 1 / pow (2 , (21 - ($exp - 1023)));
  4405.         }
  4406.  
  4407.         $value += $mantissalow2 / pow (2 , (52 - ($exp - 1023)));
  4408.         if ($sign) {
  4409.             $value = -1 * $value;
  4410.         }
  4411.  
  4412.         return    $value;
  4413.     }
  4414.  
  4415.     private function _GetIEEE754($rknum)
  4416.     {
  4417.         if (($rknum & 0x02) != 0) {
  4418.             $value = $rknum >> 2;
  4419.         }
  4420.         else {
  4421.             // changes by mmp, info on IEEE754 encoding from
  4422.             // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
  4423.             // The RK format calls for using only the most significant 30 bits
  4424.             // of the 64 bit floating point value. The other 34 bits are assumed
  4425.             // to be 0 so we use the upper 30 bits of $rknum as follows...
  4426.             $sign = ($rknum & 0x80000000) >> 31;
  4427.             $exp = ($rknum & 0x7ff00000) >> 20;
  4428.             $mantissa = (0x100000 | ($rknum & 0x000ffffc));
  4429.             $value = $mantissa / pow( 2 , (20- ($exp - 1023)));
  4430.             if ($sign) {
  4431.                 $value = -1 * $value;
  4432.             }
  4433.             //end of changes by mmp
  4434.         }
  4435.         if (($rknum & 0x01) != 0) {
  4436.             $value /= 100;
  4437.         }
  4438.         return $value;
  4439.     }
  4440.  
  4441.     /**
  4442.      * Get UTF-8 string from (compressed or uncompressed) UTF-16 string
  4443.      *
  4444.      * @param string $string 
  4445.      * @param bool $compressed 
  4446.      * @return string 
  4447.      */
  4448.     private function _encodeUTF16($string, $compressed = '')
  4449.     {
  4450.         $result = $string;
  4451.         if($compressed) {
  4452.             $string = $this->_uncompressByteString($string);
  4453.          }
  4454.         switch ($this->_encoderFunction){
  4455.             case 'iconv' :
  4456.                 $result = iconv('UTF-16LE', 'UTF-8', $string);
  4457.                 break;
  4458.             case 'mb_convert_encoding' :
  4459.                 $result = mb_convert_encoding($string, 'UTF-8', 'UTF-16LE');
  4460.                 break;
  4461.         }
  4462.         return $result;
  4463.     }
  4464.  
  4465.     /**
  4466.      * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8.
  4467.      *
  4468.      * @param string $string 
  4469.      * @return string 
  4470.      */
  4471.     private function _uncompressByteString($string)
  4472.     {
  4473.         $uncompressedString = '';
  4474.         for ($i = 0; $i < strlen($string); ++$i) {
  4475.             $uncompressedString .= $string[$i] . "\0";
  4476.         }
  4477.  
  4478.         return $uncompressedString;
  4479.     }
  4480.  
  4481.     /**
  4482.      * Convert string to UTF-8. Only used for BIFF5.
  4483.      *
  4484.      * @param string $string 
  4485.      * @return string 
  4486.      */
  4487.     private function _decodeCodepage($string)
  4488.     {
  4489.         $result = $string;
  4490.         if ($this->_codepage) {
  4491.             switch ($this->_encoderFunction) {
  4492.                 case 'iconv' :
  4493.                     $result = iconv($this->_codepage, 'UTF-8', $string);
  4494.                     break;
  4495.                 case 'mb_convert_encoding' :
  4496.                     $result = mb_convert_encoding($string, 'UTF-8', $this->_codepage );
  4497.                     break;
  4498.             }
  4499.         }
  4500.         return $result;
  4501.     }
  4502.  
  4503.     /**
  4504.      * Read 16-bit unsigned integer
  4505.      *
  4506.      * @param string $data 
  4507.      * @param int $pos 
  4508.      * @return int 
  4509.      */
  4510.     private function _GetInt2d($data, $pos)
  4511.     {
  4512.         return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
  4513.     }
  4514.  
  4515.     /**
  4516.      * Read 32-bit signed integer
  4517.      *
  4518.      * @param string $data 
  4519.      * @param int $pos 
  4520.      * @return int 
  4521.      */
  4522.     private function _GetInt4d($data, $pos)
  4523.     {
  4524.         //return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) |
  4525.         //    (ord($data[$pos + 2]) << 16) | (ord($data[$pos + 3]) << 24);
  4526.  
  4527.         // FIX: represent numbers correctly on 64-bit system
  4528.         // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
  4529.         $_or_24 = ord($data[$pos + 3]);
  4530.         if ($_or_24 >= 128) {
  4531.             // negative number
  4532.             $_ord_24 = -abs((256 - $_or_24) << 24);
  4533.         } else {
  4534.             $_ord_24 = ($_or_24 & 127) << 24;
  4535.         }
  4536.         return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
  4537.     }
  4538.  
  4539.     /**
  4540.      * Read color
  4541.      *
  4542.      * @param int $color Indexed color
  4543.      * @return array RGB color value, example: array('rgb' => 'FF0000')
  4544.      */
  4545.     private function _readColor($color)
  4546.     {
  4547.         if ($color <= 0x07) {
  4548.             // special built-in color
  4549.             $color = $this->_mapBuiltInColor($color);
  4550.         } else if (isset($this->_palette) && isset($this->_palette[$color - 8])) {
  4551.             // palette color, color index 0x08 maps to pallete index 0
  4552.             $color = $this->_palette[$color - 8];
  4553.         } else {
  4554.             // default color table
  4555.             if ($this->_version == self::XLS_BIFF8) {
  4556.                 $color = $this->_mapColor($color);
  4557.             } else {
  4558.                 // BIFF5
  4559.                 $color = $this->_mapColorBIFF5($color);
  4560.             }
  4561.         }
  4562.         
  4563.         return $color;
  4564.     }
  4565.     
  4566.     
  4567.     /**
  4568.      * Map border style
  4569.      * OpenOffice documentation: 2.5.11
  4570.      *
  4571.      * @param int $index 
  4572.      * @return string 
  4573.      */
  4574.     private function _mapBorderStyle($index)
  4575.     {
  4576.         switch ($index) {
  4577.         case 0x00: return PHPExcel_Style_Border::BORDER_NONE;
  4578.         case 0x01: return PHPExcel_Style_Border::BORDER_THIN;
  4579.         case 0x02: return PHPExcel_Style_Border::BORDER_MEDIUM;
  4580.         case 0x03: return PHPExcel_Style_Border::BORDER_DASHED;
  4581.         case 0x04: return PHPExcel_Style_Border::BORDER_DOTTED;
  4582.         case 0x05: return PHPExcel_Style_Border::BORDER_THICK;
  4583.         case 0x06: return PHPExcel_Style_Border::BORDER_DOUBLE;
  4584.         case 0x07: return PHPExcel_Style_Border::BORDER_HAIR;
  4585.         case 0x08: return PHPExcel_Style_Border::BORDER_MEDIUMDASHED;
  4586.         case 0x09: return PHPExcel_Style_Border::BORDER_DASHDOT;
  4587.         case 0x0A: return PHPExcel_Style_Border::BORDER_MEDIUMDASHDOT;
  4588.         case 0x0B: return PHPExcel_Style_Border::BORDER_DASHDOTDOT;
  4589.         case 0x0C: return PHPExcel_Style_Border::BORDER_MEDIUMDASHDOTDOT;
  4590.         case 0x0D: return PHPExcel_Style_Border::BORDER_SLANTDASHDOT;
  4591.         default:   return PHPExcel_Style_Border::BORDER_NONE;
  4592.         }
  4593.     }
  4594.  
  4595.     /**
  4596.      * Get fill pattern from index
  4597.      * OpenOffice documentation: 2.5.12
  4598.      *
  4599.      * @param int $index 
  4600.      * @return string 
  4601.      */
  4602.     private function _mapFillPattern($index)
  4603.     {
  4604.         switch ($index) {
  4605.         case 0x00: return PHPExcel_Style_Fill::FILL_NONE;
  4606.         case 0x01: return PHPExcel_Style_Fill::FILL_SOLID;
  4607.         case 0x02: return PHPExcel_Style_Fill::FILL_PATTERN_MEDIUMGRAY;
  4608.         case 0x03: return PHPExcel_Style_Fill::FILL_PATTERN_DARKGRAY;
  4609.         case 0x04: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTGRAY;
  4610.         case 0x05: return PHPExcel_Style_Fill::FILL_PATTERN_DARKHORIZONTAL;
  4611.         case 0x06: return PHPExcel_Style_Fill::FILL_PATTERN_DARKVERTICAL;
  4612.         case 0x07: return PHPExcel_Style_Fill::FILL_PATTERN_DARKDOWN;
  4613.         case 0x08: return PHPExcel_Style_Fill::FILL_PATTERN_DARKUP;
  4614.         case 0x09: return PHPExcel_Style_Fill::FILL_PATTERN_DARKGRID;
  4615.         case 0x0A: return PHPExcel_Style_Fill::FILL_PATTERN_DARKTRELLIS;
  4616.         case 0x0B: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTHORIZONTAL;
  4617.         case 0x0C: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTVERTICAL;
  4618.         case 0x0D: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTDOWN;
  4619.         case 0x0E: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTUP;
  4620.         case 0x0F: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTGRID;
  4621.         case 0x10: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTTRELLIS;
  4622.         case 0x11: return PHPExcel_Style_Fill::FILL_PATTERN_GRAY125;
  4623.         case 0x12: return PHPExcel_Style_Fill::FILL_PATTERN_GRAY0625;
  4624.         default:   return PHPExcel_Style_Fill::FILL_NONE;
  4625.         }
  4626.     }
  4627.  
  4628.     /**
  4629.      * Map error code, e.g. '#N/A'
  4630.      *
  4631.      * @param int $subData 
  4632.      * @return string 
  4633.      */
  4634.     private function _mapErrorCode($subData)
  4635.     {
  4636.         switch ($subData) {
  4637.         case 0x00: return '#NULL!';        break;
  4638.         case 0x07: return '#DIV/0!';    break;
  4639.         case 0x0F: return '#VALUE!';    break;
  4640.         case 0x17: return '#REF!';        break;
  4641.         case 0x1D: return '#NAME?';        break;
  4642.         case 0x24: return '#NUM!';        break;
  4643.         case 0x2A: return '#N/A';        break;
  4644.         default: return false;
  4645.         }
  4646.     }
  4647.  
  4648.     /**
  4649.      * Map built-in color to RGB value
  4650.      *
  4651.      * @param int $color Indexed color
  4652.      * @return array 
  4653.      */
  4654.     private function _mapBuiltInColor($color)
  4655.     {
  4656.         switch ($color) {
  4657.             case 0x00: return array('rgb' => '000000');
  4658.             case 0x01: return array('rgb' => 'FFFFFF');
  4659.             case 0x02: return array('rgb' => 'FF0000');
  4660.             case 0x03: return array('rgb' => '00FF00');
  4661.             case 0x04: return array('rgb' => '0000FF');
  4662.             case 0x05: return array('rgb' => 'FFFF00');
  4663.             case 0x06: return array('rgb' => 'FF00FF');
  4664.             case 0x07: return array('rgb' => '00FFFF');
  4665.             default:   return array('rgb' => '000000');
  4666.         }
  4667.     }
  4668.  
  4669.     /**
  4670.      * Map color array from BIFF5 built-in color index
  4671.      *
  4672.      * @param int $subData 
  4673.      * @return array 
  4674.      */
  4675.     private function _mapColorBIFF5($subData)
  4676.     {
  4677.         switch ($subData) {
  4678.             case 0x08: return array('rgb' => '000000');
  4679.             case 0x09: return array('rgb' => 'FFFFFF');
  4680.             case 0x0A: return array('rgb' => 'FF0000');
  4681.             case 0x0B: return array('rgb' => '00FF00');
  4682.             case 0x0C: return array('rgb' => '0000FF');
  4683.             case 0x0D: return array('rgb' => 'FFFF00');
  4684.             case 0x0E: return array('rgb' => 'FF00FF');
  4685.             case 0x0F: return array('rgb' => '00FFFF');
  4686.             case 0x10: return array('rgb' => '800000');
  4687.             case 0x11: return array('rgb' => '008000');
  4688.             case 0x12: return array('rgb' => '000080');
  4689.             case 0x13: return array('rgb' => '808000');
  4690.             case 0x14: return array('rgb' => '800080');
  4691.             case 0x15: return array('rgb' => '008080');
  4692.             case 0x16: return array('rgb' => 'C0C0C0');
  4693.             case 0x17: return array('rgb' => '808080');
  4694.             case 0x18: return array('rgb' => '8080FF');
  4695.             case 0x19: return array('rgb' => '802060');
  4696.             case 0x1A: return array('rgb' => 'FFFFC0');
  4697.             case 0x1B: return array('rgb' => 'A0E0F0');
  4698.             case 0x1C: return array('rgb' => '600080');
  4699.             case 0x1D: return array('rgb' => 'FF8080');
  4700.             case 0x1E: return array('rgb' => '0080C0');
  4701.             case 0x1F: return array('rgb' => 'C0C0FF');
  4702.             case 0x20: return array('rgb' => '000080');
  4703.             case 0x21: return array('rgb' => 'FF00FF');
  4704.             case 0x22: return array('rgb' => 'FFFF00');
  4705.             case 0x23: return array('rgb' => '00FFFF');
  4706.             case 0x24: return array('rgb' => '800080');
  4707.             case 0x25: return array('rgb' => '800000');
  4708.             case 0x26: return array('rgb' => '008080');
  4709.             case 0x27: return array('rgb' => '0000FF');
  4710.             case 0x28: return array('rgb' => '00CFFF');
  4711.             case 0x29: return array('rgb' => '69FFFF');
  4712.             case 0x2A: return array('rgb' => 'E0FFE0');
  4713.             case 0x2B: return array('rgb' => 'FFFF80');
  4714.             case 0x2C: return array('rgb' => 'A6CAF0');
  4715.             case 0x2D: return array('rgb' => 'DD9CB3');
  4716.             case 0x2E: return array('rgb' => 'B38FEE');
  4717.             case 0x2F: return array('rgb' => 'E3E3E3');
  4718.             case 0x30: return array('rgb' => '2A6FF9');
  4719.             case 0x31: return array('rgb' => '3FB8CD');
  4720.             case 0x32: return array('rgb' => '488436');
  4721.             case 0x33: return array('rgb' => '958C41');
  4722.             case 0x34: return array('rgb' => '8E5E42');
  4723.             case 0x35: return array('rgb' => 'A0627A');
  4724.             case 0x36: return array('rgb' => '624FAC');
  4725.             case 0x37: return array('rgb' => '969696');
  4726.             case 0x38: return array('rgb' => '1D2FBE');
  4727.             case 0x39: return array('rgb' => '286676');
  4728.             case 0x3A: return array('rgb' => '004500');
  4729.             case 0x3B: return array('rgb' => '453E01');
  4730.             case 0x3C: return array('rgb' => '6A2813');
  4731.             case 0x3D: return array('rgb' => '85396A');
  4732.             case 0x3E: return array('rgb' => '4A3285');
  4733.             case 0x3F: return array('rgb' => '424242');
  4734.             default:   return array('rgb' => '000000');
  4735.         }
  4736.     }
  4737.  
  4738.     /**
  4739.      * Map color array from BIFF8 built-in color index
  4740.      *
  4741.      * @param int $subData 
  4742.      * @return array 
  4743.      */
  4744.     private function _mapColor($subData)
  4745.     {
  4746.         switch ($subData) {
  4747.             case 0x08: return array('rgb' => '000000');
  4748.             case 0x09: return array('rgb' => 'FFFFFF');
  4749.             case 0x0A: return array('rgb' => 'FF0000');
  4750.             case 0x0B: return array('rgb' => '00FF00');
  4751.             case 0x0C: return array('rgb' => '0000FF');
  4752.             case 0x0D: return array('rgb' => 'FFFF00');
  4753.             case 0x0E: return array('rgb' => 'FF00FF');
  4754.             case 0x0F: return array('rgb' => '00FFFF');
  4755.             case 0x10: return array('rgb' => '800000');
  4756.             case 0x11: return array('rgb' => '008000');
  4757.             case 0x12: return array('rgb' => '000080');
  4758.             case 0x13: return array('rgb' => '808000');
  4759.             case 0x14: return array('rgb' => '800080');
  4760.             case 0x15: return array('rgb' => '008080');
  4761.             case 0x16: return array('rgb' => 'C0C0C0');
  4762.             case 0x17: return array('rgb' => '808080');
  4763.             case 0x18: return array('rgb' => '9999FF');
  4764.             case 0x19: return array('rgb' => '993366');
  4765.             case 0x1A: return array('rgb' => 'FFFFCC');
  4766.             case 0x1B: return array('rgb' => 'CCFFFF');
  4767.             case 0x1C: return array('rgb' => '660066');
  4768.             case 0x1D: return array('rgb' => 'FF8080');
  4769.             case 0x1E: return array('rgb' => '0066CC');
  4770.             case 0x1F: return array('rgb' => 'CCCCFF');
  4771.             case 0x20: return array('rgb' => '000080');
  4772.             case 0x21: return array('rgb' => 'FF00FF');
  4773.             case 0x22: return array('rgb' => 'FFFF00');
  4774.             case 0x23: return array('rgb' => '00FFFF');
  4775.             case 0x24: return array('rgb' => '800080');
  4776.             case 0x25: return array('rgb' => '800000');
  4777.             case 0x26: return array('rgb' => '008080');
  4778.             case 0x27: return array('rgb' => '0000FF');
  4779.             case 0x28: return array('rgb' => '00CCFF');
  4780.             case 0x29: return array('rgb' => 'CCFFFF');
  4781.             case 0x2A: return array('rgb' => 'CCFFCC');
  4782.             case 0x2B: return array('rgb' => 'FFFF99');
  4783.             case 0x2C: return array('rgb' => '99CCFF');
  4784.             case 0x2D: return array('rgb' => 'FF99CC');
  4785.             case 0x2E: return array('rgb' => 'CC99FF');
  4786.             case 0x2F: return array('rgb' => 'FFCC99');
  4787.             case 0x30: return array('rgb' => '3366FF');
  4788.             case 0x31: return array('rgb' => '33CCCC');
  4789.             case 0x32: return array('rgb' => '99CC00');
  4790.             case 0x33: return array('rgb' => 'FFCC00');
  4791.             case 0x34: return array('rgb' => 'FF9900');
  4792.             case 0x35: return array('rgb' => 'FF6600');
  4793.             case 0x36: return array('rgb' => '666699');
  4794.             case 0x37: return array('rgb' => '969696');
  4795.             case 0x38: return array('rgb' => '003366');
  4796.             case 0x39: return array('rgb' => '339966');
  4797.             case 0x3A: return array('rgb' => '003300');
  4798.             case 0x3B: return array('rgb' => '333300');
  4799.             case 0x3C: return array('rgb' => '993300');
  4800.             case 0x3D: return array('rgb' => '993366');
  4801.             case 0x3E: return array('rgb' => '333399');
  4802.             case 0x3F: return array('rgb' => '333333');
  4803.             default:   return array('rgb' => '000000');
  4804.         }
  4805.     }
  4806.  
  4807. }

Documentation generated on Mon, 05 Jan 2009 20:37:06 +0100 by phpDocumentor 1.4.1