[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/phpexcel/PHPExcel/Reader/ -> Excel5.php (source)

   1  <?php
   2  /**

   3   * PHPExcel

   4   *

   5   * Copyright (c) 2006 - 2014 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 the 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 - 2014 PHPExcel (http://www.codeplex.com/PHPExcel)

  24   * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL

  25   * @version    ##VERSION##, ##DATE##

  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 root directory */

  61  if (!defined('PHPEXCEL_ROOT')) {
  62      /**

  63       * @ignore

  64       */
  65      define('PHPEXCEL_ROOT', dirname(__FILE__) . '/../../');
  66      require (PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php');
  67  }
  68  
  69  /**

  70   * PHPExcel_Reader_Excel5

  71   *

  72   * This class uses {@link http://sourceforge.net/projects/phpexcelreader/parseXL}

  73   *

  74   * @category    PHPExcel

  75   * @package        PHPExcel_Reader_Excel5

  76   * @copyright    Copyright (c) 2006 - 2014 PHPExcel (http://www.codeplex.com/PHPExcel)

  77   */
  78  class PHPExcel_Reader_Excel5 extends PHPExcel_Reader_Abstract implements PHPExcel_Reader_IReader
  79  {
  80      // ParseXL definitions

  81      const XLS_BIFF8                        = 0x0600;
  82      const XLS_BIFF7                        = 0x0500;
  83      const XLS_WorkbookGlobals            = 0x0005;
  84      const XLS_Worksheet                    = 0x0010;
  85  
  86      // record identifiers

  87      const XLS_Type_FORMULA                = 0x0006;
  88      const XLS_Type_EOF                    = 0x000a;
  89      const XLS_Type_PROTECT                = 0x0012;
  90      const XLS_Type_OBJECTPROTECT        = 0x0063;
  91      const XLS_Type_SCENPROTECT            = 0x00dd;
  92      const XLS_Type_PASSWORD                = 0x0013;
  93      const XLS_Type_HEADER                = 0x0014;
  94      const XLS_Type_FOOTER                = 0x0015;
  95      const XLS_Type_EXTERNSHEET            = 0x0017;
  96      const XLS_Type_DEFINEDNAME            = 0x0018;
  97      const XLS_Type_VERTICALPAGEBREAKS    = 0x001a;
  98      const XLS_Type_HORIZONTALPAGEBREAKS    = 0x001b;
  99      const XLS_Type_NOTE                    = 0x001c;
 100      const XLS_Type_SELECTION            = 0x001d;
 101      const XLS_Type_DATEMODE                = 0x0022;
 102      const XLS_Type_EXTERNNAME            = 0x0023;
 103      const XLS_Type_LEFTMARGIN            = 0x0026;
 104      const XLS_Type_RIGHTMARGIN            = 0x0027;
 105      const XLS_Type_TOPMARGIN            = 0x0028;
 106      const XLS_Type_BOTTOMMARGIN            = 0x0029;
 107      const XLS_Type_PRINTGRIDLINES        = 0x002b;
 108      const XLS_Type_FILEPASS                = 0x002f;
 109      const XLS_Type_FONT                    = 0x0031;
 110      const XLS_Type_CONTINUE                = 0x003c;
 111      const XLS_Type_PANE                    = 0x0041;
 112      const XLS_Type_CODEPAGE                = 0x0042;
 113      const XLS_Type_DEFCOLWIDTH             = 0x0055;
 114      const XLS_Type_OBJ                    = 0x005d;
 115      const XLS_Type_COLINFO                = 0x007d;
 116      const XLS_Type_IMDATA                = 0x007f;
 117      const XLS_Type_SHEETPR                = 0x0081;
 118      const XLS_Type_HCENTER                = 0x0083;
 119      const XLS_Type_VCENTER                = 0x0084;
 120      const XLS_Type_SHEET                = 0x0085;
 121      const XLS_Type_PALETTE                = 0x0092;
 122      const XLS_Type_SCL                    = 0x00a0;
 123      const XLS_Type_PAGESETUP            = 0x00a1;
 124      const XLS_Type_MULRK                = 0x00bd;
 125      const XLS_Type_MULBLANK                = 0x00be;
 126      const XLS_Type_DBCELL                = 0x00d7;
 127      const XLS_Type_XF                    = 0x00e0;
 128      const XLS_Type_MERGEDCELLS            = 0x00e5;
 129      const XLS_Type_MSODRAWINGGROUP        = 0x00eb;
 130      const XLS_Type_MSODRAWING            = 0x00ec;
 131      const XLS_Type_SST                    = 0x00fc;
 132      const XLS_Type_LABELSST                = 0x00fd;
 133      const XLS_Type_EXTSST                = 0x00ff;
 134      const XLS_Type_EXTERNALBOOK            = 0x01ae;
 135      const XLS_Type_DATAVALIDATIONS        = 0x01b2;
 136      const XLS_Type_TXO                    = 0x01b6;
 137      const XLS_Type_HYPERLINK            = 0x01b8;
 138      const XLS_Type_DATAVALIDATION        = 0x01be;
 139      const XLS_Type_DIMENSION            = 0x0200;
 140      const XLS_Type_BLANK                = 0x0201;
 141      const XLS_Type_NUMBER                = 0x0203;
 142      const XLS_Type_LABEL                = 0x0204;
 143      const XLS_Type_BOOLERR                = 0x0205;
 144      const XLS_Type_STRING                = 0x0207;
 145      const XLS_Type_ROW                    = 0x0208;
 146      const XLS_Type_INDEX                = 0x020b;
 147      const XLS_Type_ARRAY                = 0x0221;
 148      const XLS_Type_DEFAULTROWHEIGHT     = 0x0225;
 149      const XLS_Type_WINDOW2                = 0x023e;
 150      const XLS_Type_RK                    = 0x027e;
 151      const XLS_Type_STYLE                = 0x0293;
 152      const XLS_Type_FORMAT                = 0x041e;
 153      const XLS_Type_SHAREDFMLA            = 0x04bc;
 154      const XLS_Type_BOF                    = 0x0809;
 155      const XLS_Type_SHEETPROTECTION        = 0x0867;
 156      const XLS_Type_RANGEPROTECTION        = 0x0868;
 157      const XLS_Type_SHEETLAYOUT            = 0x0862;
 158      const XLS_Type_XFEXT                = 0x087d;
 159      const XLS_Type_PAGELAYOUTVIEW        = 0x088b;
 160      const XLS_Type_UNKNOWN                = 0xffff;
 161  
 162      // Encryption type

 163      const MS_BIFF_CRYPTO_NONE = 0;
 164      const MS_BIFF_CRYPTO_XOR  = 1;
 165      const MS_BIFF_CRYPTO_RC4  = 2;
 166      
 167      // Size of stream blocks when using RC4 encryption

 168      const REKEY_BLOCK = 0x400;
 169  
 170      /**

 171       * Summary Information stream data.

 172       *

 173       * @var string

 174       */
 175      private $_summaryInformation;
 176  
 177      /**

 178       * Extended Summary Information stream data.

 179       *

 180       * @var string

 181       */
 182      private $_documentSummaryInformation;
 183  
 184      /**

 185       * User-Defined Properties stream data.

 186       *

 187       * @var string

 188       */
 189      private $_userDefinedProperties;
 190  
 191      /**

 192       * Workbook stream data. (Includes workbook globals substream as well as sheet substreams)

 193       *

 194       * @var string

 195       */
 196      private $_data;
 197  
 198      /**

 199       * Size in bytes of $this->_data

 200       *

 201       * @var int

 202       */
 203      private $_dataSize;
 204  
 205      /**

 206       * Current position in stream

 207       *

 208       * @var integer

 209       */
 210      private $_pos;
 211  
 212      /**

 213       * Workbook to be returned by the reader.

 214       *

 215       * @var PHPExcel

 216       */
 217      private $_phpExcel;
 218  
 219      /**

 220       * Worksheet that is currently being built by the reader.

 221       *

 222       * @var PHPExcel_Worksheet

 223       */
 224      private $_phpSheet;
 225  
 226      /**

 227       * BIFF version

 228       *

 229       * @var int

 230       */
 231      private $_version;
 232  
 233      /**

 234       * Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95)

 235       * For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'

 236       *

 237       * @var string

 238       */
 239      private $_codepage;
 240  
 241      /**

 242       * Shared formats

 243       *

 244       * @var array

 245       */
 246      private $_formats;
 247  
 248      /**

 249       * Shared fonts

 250       *

 251       * @var array

 252       */
 253      private $_objFonts;
 254  
 255      /**

 256       * Color palette

 257       *

 258       * @var array

 259       */
 260      private $_palette;
 261  
 262      /**

 263       * Worksheets

 264       *

 265       * @var array

 266       */
 267      private $_sheets;
 268  
 269      /**

 270       * External books

 271       *

 272       * @var array

 273       */
 274      private $_externalBooks;
 275  
 276      /**

 277       * REF structures. Only applies to BIFF8.

 278       *

 279       * @var array

 280       */
 281      private $_ref;
 282  
 283      /**

 284       * External names

 285       *

 286       * @var array

 287       */
 288      private $_externalNames;
 289  
 290      /**

 291       * Defined names

 292       *

 293       * @var array

 294       */
 295      private $_definedname;
 296  
 297      /**

 298       * Shared strings. Only applies to BIFF8.

 299       *

 300       * @var array

 301       */
 302      private $_sst;
 303  
 304      /**

 305       * Panes are frozen? (in sheet currently being read). See WINDOW2 record.

 306       *

 307       * @var boolean

 308       */
 309      private $_frozen;
 310  
 311      /**

 312       * Fit printout to number of pages? (in sheet currently being read). See SHEETPR record.

 313       *

 314       * @var boolean

 315       */
 316      private $_isFitToPages;
 317  
 318      /**

 319       * Objects. One OBJ record contributes with one entry.

 320       *

 321       * @var array

 322       */
 323      private $_objs;
 324  
 325      /**

 326       * Text Objects. One TXO record corresponds with one entry.

 327       *

 328       * @var array

 329       */
 330      private $_textObjects;
 331  
 332      /**

 333       * Cell Annotations (BIFF8)

 334       *

 335       * @var array

 336       */
 337      private $_cellNotes;
 338  
 339      /**

 340       * The combined MSODRAWINGGROUP data

 341       *

 342       * @var string

 343       */
 344      private $_drawingGroupData;
 345  
 346      /**

 347       * The combined MSODRAWING data (per sheet)

 348       *

 349       * @var string

 350       */
 351      private $_drawingData;
 352  
 353      /**

 354       * Keep track of XF index

 355       *

 356       * @var int

 357       */
 358      private $_xfIndex;
 359  
 360      /**

 361       * Mapping of XF index (that is a cell XF) to final index in cellXf collection

 362       *

 363       * @var array

 364       */
 365      private $_mapCellXfIndex;
 366  
 367      /**

 368       * Mapping of XF index (that is a style XF) to final index in cellStyleXf collection

 369       *

 370       * @var array

 371       */
 372      private $_mapCellStyleXfIndex;
 373  
 374      /**

 375       * The shared formulas in a sheet. One SHAREDFMLA record contributes with one value.

 376       *

 377       * @var array

 378       */
 379      private $_sharedFormulas;
 380  
 381      /**

 382       * The shared formula parts in a sheet. One FORMULA record contributes with one value if it

 383       * refers to a shared formula.

 384       *

 385       * @var array

 386       */
 387      private $_sharedFormulaParts;
 388  
 389      /**

 390       * The type of encryption in use

 391       *

 392       * @var int    

 393       */
 394      private $_encryption = 0;
 395      
 396      /**

 397       * The position in the stream after which contents are encrypted

 398       *

 399       * @var int

 400       */
 401      private $_encryptionStartPos = false;
 402  
 403      /**

 404       * The current RC4 decryption object

 405       *

 406       * @var PHPExcel_Reader_Excel5_RC4

 407       */
 408      private $_rc4Key = null;
 409  
 410      /**

 411       * The position in the stream that the RC4 decryption object was left at

 412       *

 413       * @var int

 414       */
 415      private $_rc4Pos = 0;
 416  
 417      /**

 418       * The current MD5 context state

 419       *

 420       * @var string

 421       */
 422      private $_md5Ctxt = null;
 423  
 424      /**

 425       * Create a new PHPExcel_Reader_Excel5 instance

 426       */
 427  	public function __construct() {
 428          $this->_readFilter = new PHPExcel_Reader_DefaultReadFilter();
 429      }
 430  
 431  
 432      /**

 433       * Can the current PHPExcel_Reader_IReader read the file?

 434       *

 435       * @param     string         $pFilename

 436       * @return     boolean

 437       * @throws PHPExcel_Reader_Exception

 438       */
 439  	public function canRead($pFilename)
 440      {
 441          // Check if file exists

 442          if (!file_exists($pFilename)) {
 443              throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist.");
 444          }
 445  
 446          try {
 447              // Use ParseXL for the hard work.

 448              $ole = new PHPExcel_Shared_OLERead();
 449  
 450              // get excel data

 451              $res = $ole->read($pFilename);
 452              return true;
 453          } catch (PHPExcel_Exception $e) {
 454              return false;
 455          }
 456      }
 457  
 458  
 459      /**

 460       * Reads names of the worksheets from a file, without parsing the whole file to a PHPExcel object

 461       *

 462       * @param     string         $pFilename

 463       * @throws     PHPExcel_Reader_Exception

 464       */
 465  	public function listWorksheetNames($pFilename)
 466      {
 467          // Check if file exists

 468          if (!file_exists($pFilename)) {
 469              throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist.");
 470          }
 471  
 472          $worksheetNames = array();
 473  
 474          // Read the OLE file

 475          $this->_loadOLE($pFilename);
 476  
 477          // total byte size of Excel data (workbook global substream + sheet substreams)

 478          $this->_dataSize = strlen($this->_data);
 479  
 480          $this->_pos        = 0;
 481          $this->_sheets    = array();
 482  
 483          // Parse Workbook Global Substream

 484          while ($this->_pos < $this->_dataSize) {
 485              $code = self::_GetInt2d($this->_data, $this->_pos);
 486  
 487              switch ($code) {
 488                  case self::XLS_Type_BOF:    $this->_readBof();        break;
 489                  case self::XLS_Type_SHEET:    $this->_readSheet();    break;
 490                  case self::XLS_Type_EOF:    $this->_readDefault();    break 2;
 491                  default:                    $this->_readDefault();    break;
 492              }
 493          }
 494  
 495          foreach ($this->_sheets as $sheet) {
 496              if ($sheet['sheetType'] != 0x00) {
 497                  // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module

 498                  continue;
 499              }
 500  
 501              $worksheetNames[] = $sheet['name'];
 502          }
 503  
 504          return $worksheetNames;
 505      }
 506  
 507  
 508      /**

 509       * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns)

 510       *

 511       * @param   string     $pFilename

 512       * @throws   PHPExcel_Reader_Exception

 513       */
 514  	public function listWorksheetInfo($pFilename)
 515      {
 516          // Check if file exists

 517          if (!file_exists($pFilename)) {
 518              throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist.");
 519          }
 520  
 521          $worksheetInfo = array();
 522  
 523          // Read the OLE file

 524          $this->_loadOLE($pFilename);
 525  
 526          // total byte size of Excel data (workbook global substream + sheet substreams)

 527          $this->_dataSize = strlen($this->_data);
 528  
 529          // initialize

 530          $this->_pos    = 0;
 531          $this->_sheets = array();
 532  
 533          // Parse Workbook Global Substream

 534          while ($this->_pos < $this->_dataSize) {
 535              $code = self::_GetInt2d($this->_data, $this->_pos);
 536  
 537              switch ($code) {
 538                  case self::XLS_Type_BOF:        $this->_readBof();        break;
 539                  case self::XLS_Type_SHEET:      $this->_readSheet();      break;
 540                  case self::XLS_Type_EOF:        $this->_readDefault();    break 2;
 541                  default:                        $this->_readDefault();    break;
 542              }
 543          }
 544  
 545          // Parse the individual sheets

 546          foreach ($this->_sheets as $sheet) {
 547  
 548              if ($sheet['sheetType'] != 0x00) {
 549                  // 0x00: Worksheet

 550                  // 0x02: Chart

 551                  // 0x06: Visual Basic module

 552                  continue;
 553              }
 554  
 555              $tmpInfo = array();
 556              $tmpInfo['worksheetName'] = $sheet['name'];
 557              $tmpInfo['lastColumnLetter'] = 'A';
 558              $tmpInfo['lastColumnIndex'] = 0;
 559              $tmpInfo['totalRows'] = 0;
 560              $tmpInfo['totalColumns'] = 0;
 561  
 562              $this->_pos = $sheet['offset'];
 563  
 564              while ($this->_pos <= $this->_dataSize - 4) {
 565                  $code = self::_GetInt2d($this->_data, $this->_pos);
 566  
 567                  switch ($code) {
 568                      case self::XLS_Type_RK:
 569                      case self::XLS_Type_LABELSST:
 570                      case self::XLS_Type_NUMBER:
 571                      case self::XLS_Type_FORMULA:
 572                      case self::XLS_Type_BOOLERR:
 573                      case self::XLS_Type_LABEL:
 574                          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
 575                          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
 576  
 577                          // move stream pointer to next record

 578                          $this->_pos += 4 + $length;
 579  
 580                          $rowIndex = self::_GetInt2d($recordData, 0) + 1;
 581                          $columnIndex = self::_GetInt2d($recordData, 2);
 582  
 583                          $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
 584                          $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
 585                          break;
 586                      case self::XLS_Type_BOF:      $this->_readBof();          break;
 587                      case self::XLS_Type_EOF:      $this->_readDefault();      break 2;
 588                      default:                      $this->_readDefault();      break;
 589                  }
 590              }
 591  
 592              $tmpInfo['lastColumnLetter'] = PHPExcel_Cell::stringFromColumnIndex($tmpInfo['lastColumnIndex']);
 593              $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
 594  
 595              $worksheetInfo[] = $tmpInfo;
 596          }
 597  
 598          return $worksheetInfo;
 599      }
 600  
 601  
 602      /**

 603       * Loads PHPExcel from file

 604       *

 605       * @param     string         $pFilename

 606       * @return     PHPExcel

 607       * @throws     PHPExcel_Reader_Exception

 608       */
 609  	public function load($pFilename)
 610      {
 611          // Read the OLE file

 612          $this->_loadOLE($pFilename);
 613  
 614          // Initialisations

 615          $this->_phpExcel = new PHPExcel;
 616          $this->_phpExcel->removeSheetByIndex(0); // remove 1st sheet

 617          if (!$this->_readDataOnly) {
 618              $this->_phpExcel->removeCellStyleXfByIndex(0); // remove the default style

 619              $this->_phpExcel->removeCellXfByIndex(0); // remove the default style

 620          }
 621  
 622          // Read the summary information stream (containing meta data)

 623          $this->_readSummaryInformation();
 624  
 625          // Read the Additional document summary information stream (containing application-specific meta data)

 626          $this->_readDocumentSummaryInformation();
 627  
 628          // total byte size of Excel data (workbook global substream + sheet substreams)

 629          $this->_dataSize = strlen($this->_data);
 630  
 631          // initialize

 632          $this->_pos                    = 0;
 633          $this->_codepage            = 'CP1252';
 634          $this->_formats                = array();
 635          $this->_objFonts            = array();
 636          $this->_palette                = array();
 637          $this->_sheets                = array();
 638          $this->_externalBooks        = array();
 639          $this->_ref                    = array();
 640          $this->_definedname            = array();
 641          $this->_sst                    = array();
 642          $this->_drawingGroupData    = '';
 643          $this->_xfIndex                = '';
 644          $this->_mapCellXfIndex        = array();
 645          $this->_mapCellStyleXfIndex    = array();
 646  
 647          // Parse Workbook Global Substream

 648          while ($this->_pos < $this->_dataSize) {
 649              $code = self::_GetInt2d($this->_data, $this->_pos);
 650  
 651              switch ($code) {
 652                  case self::XLS_Type_BOF:            $this->_readBof();                break;
 653                  case self::XLS_Type_FILEPASS:        $this->_readFilepass();            break;
 654                  case self::XLS_Type_CODEPAGE:        $this->_readCodepage();            break;
 655                  case self::XLS_Type_DATEMODE:        $this->_readDateMode();            break;
 656                  case self::XLS_Type_FONT:            $this->_readFont();                break;
 657                  case self::XLS_Type_FORMAT:            $this->_readFormat();            break;
 658                  case self::XLS_Type_XF:                $this->_readXf();                break;
 659                  case self::XLS_Type_XFEXT:            $this->_readXfExt();            break;
 660                  case self::XLS_Type_STYLE:            $this->_readStyle();            break;
 661                  case self::XLS_Type_PALETTE:        $this->_readPalette();            break;
 662                  case self::XLS_Type_SHEET:            $this->_readSheet();            break;
 663                  case self::XLS_Type_EXTERNALBOOK:    $this->_readExternalBook();        break;
 664                  case self::XLS_Type_EXTERNNAME:        $this->_readExternName();        break;
 665                  case self::XLS_Type_EXTERNSHEET:    $this->_readExternSheet();        break;
 666                  case self::XLS_Type_DEFINEDNAME:    $this->_readDefinedName();        break;
 667                  case self::XLS_Type_MSODRAWINGGROUP:    $this->_readMsoDrawingGroup();    break;
 668                  case self::XLS_Type_SST:            $this->_readSst();                break;
 669                  case self::XLS_Type_EOF:            $this->_readDefault();            break 2;
 670                  default:                            $this->_readDefault();            break;
 671              }
 672          }
 673  
 674          // Resolve indexed colors for font, fill, and border colors

 675          // Cannot be resolved already in XF record, because PALETTE record comes afterwards

 676          if (!$this->_readDataOnly) {
 677              foreach ($this->_objFonts as $objFont) {
 678                  if (isset($objFont->colorIndex)) {
 679                      $color = self::_readColor($objFont->colorIndex,$this->_palette,$this->_version);
 680                      $objFont->getColor()->setRGB($color['rgb']);
 681                  }
 682              }
 683  
 684              foreach ($this->_phpExcel->getCellXfCollection() as $objStyle) {
 685                  // fill start and end color

 686                  $fill = $objStyle->getFill();
 687  
 688                  if (isset($fill->startcolorIndex)) {
 689                      $startColor = self::_readColor($fill->startcolorIndex,$this->_palette,$this->_version);
 690                      $fill->getStartColor()->setRGB($startColor['rgb']);
 691                  }
 692  
 693                  if (isset($fill->endcolorIndex)) {
 694                      $endColor = self::_readColor($fill->endcolorIndex,$this->_palette,$this->_version);
 695                      $fill->getEndColor()->setRGB($endColor['rgb']);
 696                  }
 697  
 698                  // border colors

 699                  $top      = $objStyle->getBorders()->getTop();
 700                  $right    = $objStyle->getBorders()->getRight();
 701                  $bottom   = $objStyle->getBorders()->getBottom();
 702                  $left     = $objStyle->getBorders()->getLeft();
 703                  $diagonal = $objStyle->getBorders()->getDiagonal();
 704  
 705                  if (isset($top->colorIndex)) {
 706                      $borderTopColor = self::_readColor($top->colorIndex,$this->_palette,$this->_version);
 707                      $top->getColor()->setRGB($borderTopColor['rgb']);
 708                  }
 709  
 710                  if (isset($right->colorIndex)) {
 711                      $borderRightColor = self::_readColor($right->colorIndex,$this->_palette,$this->_version);
 712                      $right->getColor()->setRGB($borderRightColor['rgb']);
 713                  }
 714  
 715                  if (isset($bottom->colorIndex)) {
 716                      $borderBottomColor = self::_readColor($bottom->colorIndex,$this->_palette,$this->_version);
 717                      $bottom->getColor()->setRGB($borderBottomColor['rgb']);
 718                  }
 719  
 720                  if (isset($left->colorIndex)) {
 721                      $borderLeftColor = self::_readColor($left->colorIndex,$this->_palette,$this->_version);
 722                      $left->getColor()->setRGB($borderLeftColor['rgb']);
 723                  }
 724  
 725                  if (isset($diagonal->colorIndex)) {
 726                      $borderDiagonalColor = self::_readColor($diagonal->colorIndex,$this->_palette,$this->_version);
 727                      $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']);
 728                  }
 729              }
 730          }
 731  
 732          // treat MSODRAWINGGROUP records, workbook-level Escher

 733          if (!$this->_readDataOnly && $this->_drawingGroupData) {
 734              $escherWorkbook = new PHPExcel_Shared_Escher();
 735              $reader = new PHPExcel_Reader_Excel5_Escher($escherWorkbook);
 736              $escherWorkbook = $reader->load($this->_drawingGroupData);
 737  
 738              // debug Escher stream

 739              //$debug = new Debug_Escher(new PHPExcel_Shared_Escher());

 740              //$debug->load($this->_drawingGroupData);

 741          }
 742  
 743          // Parse the individual sheets

 744          foreach ($this->_sheets as $sheet) {
 745  
 746              if ($sheet['sheetType'] != 0x00) {
 747                  // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module

 748                  continue;
 749              }
 750  
 751              // check if sheet should be skipped

 752              if (isset($this->_loadSheetsOnly) && !in_array($sheet['name'], $this->_loadSheetsOnly)) {
 753                  continue;
 754              }
 755  
 756              // add sheet to PHPExcel object

 757              $this->_phpSheet = $this->_phpExcel->createSheet();
 758              //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula

 759              //        cells... during the load, all formulae should be correct, and we're simply bringing the worksheet

 760              //        name in line with the formula, not the reverse

 761              $this->_phpSheet->setTitle($sheet['name'],false);
 762              $this->_phpSheet->setSheetState($sheet['sheetState']);
 763  
 764              $this->_pos = $sheet['offset'];
 765  
 766              // Initialize isFitToPages. May change after reading SHEETPR record.

 767              $this->_isFitToPages = false;
 768  
 769              // Initialize drawingData

 770              $this->_drawingData = '';
 771  
 772              // Initialize objs

 773              $this->_objs = array();
 774  
 775              // Initialize shared formula parts

 776              $this->_sharedFormulaParts = array();
 777  
 778              // Initialize shared formulas

 779              $this->_sharedFormulas = array();
 780  
 781              // Initialize text objs

 782              $this->_textObjects = array();
 783  
 784              // Initialize cell annotations

 785              $this->_cellNotes = array();
 786              $this->textObjRef = -1;
 787  
 788              while ($this->_pos <= $this->_dataSize - 4) {
 789                  $code = self::_GetInt2d($this->_data, $this->_pos);
 790  
 791                  switch ($code) {
 792                      case self::XLS_Type_BOF:                    $this->_readBof();                        break;
 793                      case self::XLS_Type_PRINTGRIDLINES:            $this->_readPrintGridlines();            break;
 794                      case self::XLS_Type_DEFAULTROWHEIGHT:        $this->_readDefaultRowHeight();            break;
 795                      case self::XLS_Type_SHEETPR:                $this->_readSheetPr();                    break;
 796                      case self::XLS_Type_HORIZONTALPAGEBREAKS:    $this->_readHorizontalPageBreaks();        break;
 797                      case self::XLS_Type_VERTICALPAGEBREAKS:        $this->_readVerticalPageBreaks();        break;
 798                      case self::XLS_Type_HEADER:                    $this->_readHeader();                    break;
 799                      case self::XLS_Type_FOOTER:                    $this->_readFooter();                    break;
 800                      case self::XLS_Type_HCENTER:                $this->_readHcenter();                    break;
 801                      case self::XLS_Type_VCENTER:                $this->_readVcenter();                    break;
 802                      case self::XLS_Type_LEFTMARGIN:                $this->_readLeftMargin();                break;
 803                      case self::XLS_Type_RIGHTMARGIN:            $this->_readRightMargin();                break;
 804                      case self::XLS_Type_TOPMARGIN:                $this->_readTopMargin();                break;
 805                      case self::XLS_Type_BOTTOMMARGIN:            $this->_readBottomMargin();                break;
 806                      case self::XLS_Type_PAGESETUP:                $this->_readPageSetup();                break;
 807                      case self::XLS_Type_PROTECT:                $this->_readProtect();                    break;
 808                      case self::XLS_Type_SCENPROTECT:            $this->_readScenProtect();                break;
 809                      case self::XLS_Type_OBJECTPROTECT:            $this->_readObjectProtect();            break;
 810                      case self::XLS_Type_PASSWORD:                $this->_readPassword();                    break;
 811                      case self::XLS_Type_DEFCOLWIDTH:            $this->_readDefColWidth();                break;
 812                      case self::XLS_Type_COLINFO:                $this->_readColInfo();                    break;
 813                      case self::XLS_Type_DIMENSION:                $this->_readDefault();                    break;
 814                      case self::XLS_Type_ROW:                    $this->_readRow();                        break;
 815                      case self::XLS_Type_DBCELL:                    $this->_readDefault();                    break;
 816                      case self::XLS_Type_RK:                        $this->_readRk();                        break;
 817                      case self::XLS_Type_LABELSST:                $this->_readLabelSst();                    break;
 818                      case self::XLS_Type_MULRK:                    $this->_readMulRk();                    break;
 819                      case self::XLS_Type_NUMBER:                    $this->_readNumber();                    break;
 820                      case self::XLS_Type_FORMULA:                $this->_readFormula();                    break;
 821                      case self::XLS_Type_SHAREDFMLA:                $this->_readSharedFmla();                break;
 822                      case self::XLS_Type_BOOLERR:                $this->_readBoolErr();                    break;
 823                      case self::XLS_Type_MULBLANK:                $this->_readMulBlank();                    break;
 824                      case self::XLS_Type_LABEL:                    $this->_readLabel();                    break;
 825                      case self::XLS_Type_BLANK:                    $this->_readBlank();                    break;
 826                      case self::XLS_Type_MSODRAWING:                $this->_readMsoDrawing();                break;
 827                      case self::XLS_Type_OBJ:                    $this->_readObj();                        break;
 828                      case self::XLS_Type_WINDOW2:                $this->_readWindow2();                    break;
 829                      case self::XLS_Type_PAGELAYOUTVIEW:    $this->_readPageLayoutView();                    break;
 830                      case self::XLS_Type_SCL:                    $this->_readScl();                        break;
 831                      case self::XLS_Type_PANE:                    $this->_readPane();                        break;
 832                      case self::XLS_Type_SELECTION:                $this->_readSelection();                break;
 833                      case self::XLS_Type_MERGEDCELLS:            $this->_readMergedCells();                break;
 834                      case self::XLS_Type_HYPERLINK:                $this->_readHyperLink();                break;
 835                      case self::XLS_Type_DATAVALIDATIONS:        $this->_readDataValidations();            break;
 836                      case self::XLS_Type_DATAVALIDATION:            $this->_readDataValidation();            break;
 837                      case self::XLS_Type_SHEETLAYOUT:            $this->_readSheetLayout();                break;
 838                      case self::XLS_Type_SHEETPROTECTION:        $this->_readSheetProtection();            break;
 839                      case self::XLS_Type_RANGEPROTECTION:        $this->_readRangeProtection();            break;
 840                      case self::XLS_Type_NOTE:                    $this->_readNote();                        break;
 841                      //case self::XLS_Type_IMDATA:                $this->_readImData();                    break;

 842                      case self::XLS_Type_TXO:                    $this->_readTextObject();                break;
 843                      case self::XLS_Type_CONTINUE:                $this->_readContinue();                    break;
 844                      case self::XLS_Type_EOF:                    $this->_readDefault();                    break 2;
 845                      default:                                    $this->_readDefault();                    break;
 846                  }
 847  
 848              }
 849  
 850              // treat MSODRAWING records, sheet-level Escher

 851              if (!$this->_readDataOnly && $this->_drawingData) {
 852                  $escherWorksheet = new PHPExcel_Shared_Escher();
 853                  $reader = new PHPExcel_Reader_Excel5_Escher($escherWorksheet);
 854                  $escherWorksheet = $reader->load($this->_drawingData);
 855  
 856                  // debug Escher stream

 857                  //$debug = new Debug_Escher(new PHPExcel_Shared_Escher());

 858                  //$debug->load($this->_drawingData);

 859  
 860                  // get all spContainers in one long array, so they can be mapped to OBJ records

 861                  $allSpContainers = $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers();
 862              }
 863  
 864              // treat OBJ records

 865              foreach ($this->_objs as $n => $obj) {
 866  //                echo '<hr /><b>Object</b> reference is ',$n,'<br />';

 867  //                var_dump($obj);

 868  //                echo '<br />';

 869  
 870                  // the first shape container never has a corresponding OBJ record, hence $n + 1

 871                  if (isset($allSpContainers[$n + 1]) && is_object($allSpContainers[$n + 1])) {
 872                      $spContainer = $allSpContainers[$n + 1];
 873  
 874                      // we skip all spContainers that are a part of a group shape since we cannot yet handle those

 875                      if ($spContainer->getNestingLevel() > 1) {
 876                          continue;
 877                      }
 878  
 879                      // calculate the width and height of the shape

 880                      list($startColumn, $startRow) = PHPExcel_Cell::coordinateFromString($spContainer->getStartCoordinates());
 881                      list($endColumn, $endRow) = PHPExcel_Cell::coordinateFromString($spContainer->getEndCoordinates());
 882  
 883                      $startOffsetX = $spContainer->getStartOffsetX();
 884                      $startOffsetY = $spContainer->getStartOffsetY();
 885                      $endOffsetX = $spContainer->getEndOffsetX();
 886                      $endOffsetY = $spContainer->getEndOffsetY();
 887  
 888                      $width = PHPExcel_Shared_Excel5::getDistanceX($this->_phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX);
 889                      $height = PHPExcel_Shared_Excel5::getDistanceY($this->_phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY);
 890  
 891                      // calculate offsetX and offsetY of the shape

 892                      $offsetX = $startOffsetX * PHPExcel_Shared_Excel5::sizeCol($this->_phpSheet, $startColumn) / 1024;
 893                      $offsetY = $startOffsetY * PHPExcel_Shared_Excel5::sizeRow($this->_phpSheet, $startRow) / 256;
 894  
 895                      switch ($obj['otObjType']) {
 896                          case 0x19:
 897                              // Note

 898  //                            echo 'Cell Annotation Object<br />';

 899  //                            echo 'Object ID is ',$obj['idObjID'],'<br />';

 900  //

 901                              if (isset($this->_cellNotes[$obj['idObjID']])) {
 902                                  $cellNote = $this->_cellNotes[$obj['idObjID']];
 903  
 904                                  if (isset($this->_textObjects[$obj['idObjID']])) {
 905                                      $textObject = $this->_textObjects[$obj['idObjID']];
 906                                      $this->_cellNotes[$obj['idObjID']]['objTextData'] = $textObject;
 907                                  }
 908                              }
 909                              break;
 910  
 911                          case 0x08:
 912  //                            echo 'Picture Object<br />';

 913                              // picture

 914  
 915                              // get index to BSE entry (1-based)

 916                              $BSEindex = $spContainer->getOPT(0x0104);
 917                              $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection();
 918                              $BSE = $BSECollection[$BSEindex - 1];
 919                              $blipType = $BSE->getBlipType();
 920  
 921                              // need check because some blip types are not supported by Escher reader such as EMF

 922                              if ($blip = $BSE->getBlip()) {
 923                                  $ih = imagecreatefromstring($blip->getData());
 924                                  $drawing = new PHPExcel_Worksheet_MemoryDrawing();
 925                                  $drawing->setImageResource($ih);
 926  
 927                                  // width, height, offsetX, offsetY

 928                                  $drawing->setResizeProportional(false);
 929                                  $drawing->setWidth($width);
 930                                  $drawing->setHeight($height);
 931                                  $drawing->setOffsetX($offsetX);
 932                                  $drawing->setOffsetY($offsetY);
 933  
 934                                  switch ($blipType) {
 935                                      case PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_JPEG:
 936                                          $drawing->setRenderingFunction(PHPExcel_Worksheet_MemoryDrawing::RENDERING_JPEG);
 937                                          $drawing->setMimeType(PHPExcel_Worksheet_MemoryDrawing::MIMETYPE_JPEG);
 938                                          break;
 939  
 940                                      case PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG:
 941                                          $drawing->setRenderingFunction(PHPExcel_Worksheet_MemoryDrawing::RENDERING_PNG);
 942                                          $drawing->setMimeType(PHPExcel_Worksheet_MemoryDrawing::MIMETYPE_PNG);
 943                                          break;
 944                                  }
 945  
 946                                  $drawing->setWorksheet($this->_phpSheet);
 947                                  $drawing->setCoordinates($spContainer->getStartCoordinates());
 948                              }
 949  
 950                              break;
 951  
 952                          default:
 953                              // other object type

 954                              break;
 955  
 956                      }
 957                  }
 958              }
 959  
 960              // treat SHAREDFMLA records

 961              if ($this->_version == self::XLS_BIFF8) {
 962                  foreach ($this->_sharedFormulaParts as $cell => $baseCell) {
 963                      list($column, $row) = PHPExcel_Cell::coordinateFromString($cell);
 964                      if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($column, $row, $this->_phpSheet->getTitle()) ) {
 965                          $formula = $this->_getFormulaFromStructure($this->_sharedFormulas[$baseCell], $cell);
 966                          $this->_phpSheet->getCell($cell)->setValueExplicit('=' . $formula, PHPExcel_Cell_DataType::TYPE_FORMULA);
 967                      }
 968                  }
 969              }
 970  
 971              if (!empty($this->_cellNotes)) {
 972                  foreach($this->_cellNotes as $note => $noteDetails) {
 973                      if (!isset($noteDetails['objTextData'])) {
 974                          if (isset($this->_textObjects[$note])) {
 975                              $textObject = $this->_textObjects[$note];
 976                              $noteDetails['objTextData'] = $textObject;
 977                          } else {
 978                              $noteDetails['objTextData']['text'] = '';
 979                          }
 980                      }
 981  //                    echo '<b>Cell annotation ',$note,'</b><br />';

 982  //                    var_dump($noteDetails);

 983  //                    echo '<br />';

 984                      $cellAddress = str_replace('$','',$noteDetails['cellRef']);
 985                      $this->_phpSheet->getComment( $cellAddress )
 986                                                      ->setAuthor( $noteDetails['author'] )
 987                                                      ->setText($this->_parseRichText($noteDetails['objTextData']['text']) );
 988                  }
 989              }
 990          }
 991  
 992          // add the named ranges (defined names)

 993          foreach ($this->_definedname as $definedName) {
 994              if ($definedName['isBuiltInName']) {
 995                  switch ($definedName['name']) {
 996  
 997                  case pack('C', 0x06):
 998                      // print area

 999                      //    in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2

1000                      $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?

1001  
1002                      $extractedRanges = array();
1003                      foreach ($ranges as $range) {
1004                          // $range should look like one of these

1005                          //        Foo!$C$7:$J$66

1006                          //        Bar!$A$1:$IV$2

1007  
1008                          $explodes = explode('!', $range);    // FIXME: what if sheetname contains exclamation mark?

1009                          $sheetName = trim($explodes[0], "'");
1010  
1011                          if (count($explodes) == 2) {
1012                              if (strpos($explodes[1], ':') === FALSE) {
1013                                  $explodes[1] = $explodes[1] . ':' . $explodes[1];
1014                              }
1015                              $extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66

1016                          }
1017                      }
1018                      if ($docSheet = $this->_phpExcel->getSheetByName($sheetName)) {
1019                          $docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2

1020                      }
1021                      break;
1022  
1023                  case pack('C', 0x07):
1024                      // print titles (repeating rows)

1025                      // Assuming BIFF8, there are 3 cases

1026                      // 1. repeating rows

1027                      //        formula looks like this: Sheet!$A$1:$IV$2

1028                      //        rows 1-2 repeat

1029                      // 2. repeating columns

1030                      //        formula looks like this: Sheet!$A$1:$B$65536

1031                      //        columns A-B repeat

1032                      // 3. both repeating rows and repeating columns

1033                      //        formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2

1034  
1035                      $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?

1036  
1037                      foreach ($ranges as $range) {
1038                          // $range should look like this one of these

1039                          //        Sheet!$A$1:$B$65536

1040                          //        Sheet!$A$1:$IV$2

1041  
1042                          $explodes = explode('!', $range);
1043  
1044                          if (count($explodes) == 2) {
1045                              if ($docSheet = $this->_phpExcel->getSheetByName($explodes[0])) {
1046  
1047                                  $extractedRange = $explodes[1];
1048                                  $extractedRange = str_replace('$', '', $extractedRange);
1049  
1050                                  $coordinateStrings = explode(':', $extractedRange);
1051                                  if (count($coordinateStrings) == 2) {
1052                                      list($firstColumn, $firstRow) = PHPExcel_Cell::coordinateFromString($coordinateStrings[0]);
1053                                      list($lastColumn, $lastRow) = PHPExcel_Cell::coordinateFromString($coordinateStrings[1]);
1054  
1055                                      if ($firstColumn == 'A' and $lastColumn == 'IV') {
1056                                          // then we have repeating rows

1057                                          $docSheet->getPageSetup()->setRowsToRepeatAtTop(array($firstRow, $lastRow));
1058                                      } elseif ($firstRow == 1 and $lastRow == 65536) {
1059                                          // then we have repeating columns

1060                                          $docSheet->getPageSetup()->setColumnsToRepeatAtLeft(array($firstColumn, $lastColumn));
1061                                      }
1062                                  }
1063                              }
1064                          }
1065                      }
1066                      break;
1067  
1068                  }
1069              } else {
1070                  // Extract range

1071                  $explodes = explode('!', $definedName['formula']);
1072  
1073                  if (count($explodes) == 2) {
1074                      if (($docSheet = $this->_phpExcel->getSheetByName($explodes[0])) ||
1075                          ($docSheet = $this->_phpExcel->getSheetByName(trim($explodes[0],"'")))) {
1076                          $extractedRange = $explodes[1];
1077                          $extractedRange = str_replace('$', '', $extractedRange);
1078  
1079                          $localOnly = ($definedName['scope'] == 0) ? false : true;
1080  
1081                          $scope = ($definedName['scope'] == 0) ?
1082                              null : $this->_phpExcel->getSheetByName($this->_sheets[$definedName['scope'] - 1]['name']);
1083  
1084                          $this->_phpExcel->addNamedRange( new PHPExcel_NamedRange((string)$definedName['name'], $docSheet, $extractedRange, $localOnly, $scope) );
1085                      }
1086                  } else {
1087                      //    Named Value

1088                      //    TODO Provide support for named values

1089                  }
1090              }
1091          }
1092  
1093          return $this->_phpExcel;
1094      }
1095      
1096      /**

1097       * Read record data from stream, decrypting as required

1098       * 

1099       * @param string $data   Data stream to read from

1100       * @param int    $pos    Position to start reading from

1101       * @param int    $length Record data length

1102       * 

1103       * @return string Record data

1104       */
1105  	private function _readRecordData($data, $pos, $len)
1106      {
1107          $data = substr($data, $pos, $len);
1108          
1109          // File not encrypted, or record before encryption start point

1110          if ($this->_encryption == self::MS_BIFF_CRYPTO_NONE || $pos < $this->_encryptionStartPos) {
1111              return $data;
1112          }
1113      
1114          $recordData = '';
1115          if ($this->_encryption == self::MS_BIFF_CRYPTO_RC4) {
1116  
1117              $oldBlock = floor($this->_rc4Pos / self::REKEY_BLOCK);
1118              $block = floor($pos / self::REKEY_BLOCK);
1119              $endBlock = floor(($pos + $len) / self::REKEY_BLOCK);
1120  
1121              // Spin an RC4 decryptor to the right spot. If we have a decryptor sitting

1122              // at a point earlier in the current block, re-use it as we can save some time.

1123              if ($block != $oldBlock || $pos < $this->_rc4Pos || !$this->_rc4Key) {
1124                  $this->_rc4Key = $this->_makeKey($block, $this->_md5Ctxt);
1125                  $step = $pos % self::REKEY_BLOCK;
1126              } else {
1127                  $step = $pos - $this->_rc4Pos;
1128              }
1129              $this->_rc4Key->RC4(str_repeat("\0", $step));
1130  
1131              // Decrypt record data (re-keying at the end of every block)

1132              while ($block != $endBlock) {
1133                  $step = self::REKEY_BLOCK - ($pos % self::REKEY_BLOCK);
1134                  $recordData .= $this->_rc4Key->RC4(substr($data, 0, $step));
1135                  $data = substr($data, $step);
1136                  $pos += $step;
1137                  $len -= $step;
1138                  $block++;
1139                  $this->_rc4Key = $this->_makeKey($block, $this->_md5Ctxt);
1140              }
1141              $recordData .= $this->_rc4Key->RC4(substr($data, 0, $len));
1142  
1143              // Keep track of the position of this decryptor.

1144              // We'll try and re-use it later if we can to speed things up

1145              $this->_rc4Pos = $pos + $len;
1146              
1147          } elseif ($this->_encryption == self::MS_BIFF_CRYPTO_XOR) {
1148              throw new PHPExcel_Reader_Exception('XOr encryption not supported');
1149          }
1150          return $recordData;
1151      }
1152  
1153      /**

1154       * Use OLE reader to extract the relevant data streams from the OLE file

1155       *

1156       * @param string $pFilename

1157       */
1158  	private function _loadOLE($pFilename)
1159      {
1160          // OLE reader

1161          $ole = new PHPExcel_Shared_OLERead();
1162  
1163          // get excel data,

1164          $res = $ole->read($pFilename);
1165          // Get workbook data: workbook stream + sheet streams

1166          $this->_data = $ole->getStream($ole->wrkbook);
1167  
1168          // Get summary information data

1169          $this->_summaryInformation = $ole->getStream($ole->summaryInformation);
1170  
1171          // Get additional document summary information data

1172          $this->_documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation);
1173  
1174          // Get user-defined property data

1175  //        $this->_userDefinedProperties = $ole->getUserDefinedProperties();

1176      }
1177  
1178  
1179      /**

1180       * Read summary information

1181       */
1182  	private function _readSummaryInformation()
1183      {
1184          if (!isset($this->_summaryInformation)) {
1185              return;
1186          }
1187  
1188          // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)

1189          // offset: 2; size: 2;

1190          // offset: 4; size: 2; OS version

1191          // offset: 6; size: 2; OS indicator

1192          // offset: 8; size: 16

1193          // offset: 24; size: 4; section count

1194          $secCount = self::_GetInt4d($this->_summaryInformation, 24);
1195  
1196          // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9

1197          // offset: 44; size: 4

1198          $secOffset = self::_GetInt4d($this->_summaryInformation, 44);
1199  
1200          // section header

1201          // offset: $secOffset; size: 4; section length

1202          $secLength = self::_GetInt4d($this->_summaryInformation, $secOffset);
1203  
1204          // offset: $secOffset+4; size: 4; property count

1205          $countProperties = self::_GetInt4d($this->_summaryInformation, $secOffset+4);
1206  
1207          // initialize code page (used to resolve string values)

1208          $codePage = 'CP1252';
1209  
1210          // offset: ($secOffset+8); size: var

1211          // loop through property decarations and properties

1212          for ($i = 0; $i < $countProperties; ++$i) {
1213  
1214              // offset: ($secOffset+8) + (8 * $i); size: 4; property ID

1215              $id = self::_GetInt4d($this->_summaryInformation, ($secOffset+8) + (8 * $i));
1216  
1217              // Use value of property id as appropriate

1218              // offset: ($secOffset+12) + (8 * $i); size: 4; offset from beginning of section (48)

1219              $offset = self::_GetInt4d($this->_summaryInformation, ($secOffset+12) + (8 * $i));
1220  
1221              $type = self::_GetInt4d($this->_summaryInformation, $secOffset + $offset);
1222  
1223              // initialize property value

1224              $value = null;
1225  
1226              // extract property value based on property type

1227              switch ($type) {
1228                  case 0x02: // 2 byte signed integer
1229                      $value = self::_GetInt2d($this->_summaryInformation, $secOffset + 4 + $offset);
1230                      break;
1231  
1232                  case 0x03: // 4 byte signed integer
1233                      $value = self::_GetInt4d($this->_summaryInformation, $secOffset + 4 + $offset);
1234                      break;
1235  
1236                  case 0x13: // 4 byte unsigned integer
1237                      // not needed yet, fix later if necessary

1238                      break;
1239  
1240                  case 0x1E: // null-terminated string prepended by dword string length
1241                      $byteLength = self::_GetInt4d($this->_summaryInformation, $secOffset + 4 + $offset);
1242                      $value = substr($this->_summaryInformation, $secOffset + 8 + $offset, $byteLength);
1243                      $value = PHPExcel_Shared_String::ConvertEncoding($value, 'UTF-8', $codePage);
1244                      $value = rtrim($value);
1245                      break;
1246  
1247                  case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
1248                      // PHP-time

1249                      $value = PHPExcel_Shared_OLE::OLE2LocalDate(substr($this->_summaryInformation, $secOffset + 4 + $offset, 8));
1250                      break;
1251  
1252                  case 0x47: // Clipboard format
1253                      // not needed yet, fix later if necessary

1254                      break;
1255              }
1256  
1257              switch ($id) {
1258                  case 0x01:    //    Code Page
1259                      $codePage = PHPExcel_Shared_CodePage::NumberToName($value);
1260                      break;
1261  
1262                  case 0x02:    //    Title
1263                      $this->_phpExcel->getProperties()->setTitle($value);
1264                      break;
1265  
1266                  case 0x03:    //    Subject
1267                      $this->_phpExcel->getProperties()->setSubject($value);
1268                      break;
1269  
1270                  case 0x04:    //    Author (Creator)
1271                      $this->_phpExcel->getProperties()->setCreator($value);
1272                      break;
1273  
1274                  case 0x05:    //    Keywords
1275                      $this->_phpExcel->getProperties()->setKeywords($value);
1276                      break;
1277  
1278                  case 0x06:    //    Comments (Description)
1279                      $this->_phpExcel->getProperties()->setDescription($value);
1280                      break;
1281  
1282                  case 0x07:    //    Template
1283                      //    Not supported by PHPExcel

1284                      break;
1285  
1286                  case 0x08:    //    Last Saved By (LastModifiedBy)
1287                      $this->_phpExcel->getProperties()->setLastModifiedBy($value);
1288                      break;
1289  
1290                  case 0x09:    //    Revision
1291                      //    Not supported by PHPExcel

1292                      break;
1293  
1294                  case 0x0A:    //    Total Editing Time
1295                      //    Not supported by PHPExcel

1296                      break;
1297  
1298                  case 0x0B:    //    Last Printed
1299                      //    Not supported by PHPExcel

1300                      break;
1301  
1302                  case 0x0C:    //    Created Date/Time
1303                      $this->_phpExcel->getProperties()->setCreated($value);
1304                      break;
1305  
1306                  case 0x0D:    //    Modified Date/Time
1307                      $this->_phpExcel->getProperties()->setModified($value);
1308                      break;
1309  
1310                  case 0x0E:    //    Number of Pages
1311                      //    Not supported by PHPExcel

1312                      break;
1313  
1314                  case 0x0F:    //    Number of Words
1315                      //    Not supported by PHPExcel

1316                      break;
1317  
1318                  case 0x10:    //    Number of Characters
1319                      //    Not supported by PHPExcel

1320                      break;
1321  
1322                  case 0x11:    //    Thumbnail
1323                      //    Not supported by PHPExcel

1324                      break;
1325  
1326                  case 0x12:    //    Name of creating application
1327                      //    Not supported by PHPExcel

1328                      break;
1329  
1330                  case 0x13:    //    Security
1331                      //    Not supported by PHPExcel

1332                      break;
1333  
1334              }
1335          }
1336      }
1337  
1338  
1339      /**

1340       * Read additional document summary information

1341       */
1342  	private function _readDocumentSummaryInformation()
1343      {
1344          if (!isset($this->_documentSummaryInformation)) {
1345              return;
1346          }
1347  
1348          //    offset: 0;    size: 2;    must be 0xFE 0xFF (UTF-16 LE byte order mark)

1349          //    offset: 2;    size: 2;

1350          //    offset: 4;    size: 2;    OS version

1351          //    offset: 6;    size: 2;    OS indicator

1352          //    offset: 8;    size: 16

1353          //    offset: 24;    size: 4;    section count

1354          $secCount = self::_GetInt4d($this->_documentSummaryInformation, 24);
1355  //        echo '$secCount = ',$secCount,'<br />';

1356  
1357          // offset: 28;    size: 16;    first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae

1358          // offset: 44;    size: 4;    first section offset

1359          $secOffset = self::_GetInt4d($this->_documentSummaryInformation, 44);
1360  //        echo '$secOffset = ',$secOffset,'<br />';

1361  
1362          //    section header

1363          //    offset: $secOffset;    size: 4;    section length

1364          $secLength = self::_GetInt4d($this->_documentSummaryInformation, $secOffset);
1365  //        echo '$secLength = ',$secLength,'<br />';

1366  
1367          //    offset: $secOffset+4;    size: 4;    property count

1368          $countProperties = self::_GetInt4d($this->_documentSummaryInformation, $secOffset+4);
1369  //        echo '$countProperties = ',$countProperties,'<br />';

1370  
1371          // initialize code page (used to resolve string values)

1372          $codePage = 'CP1252';
1373  
1374          //    offset: ($secOffset+8);    size: var

1375          //    loop through property decarations and properties

1376          for ($i = 0; $i < $countProperties; ++$i) {
1377  //            echo 'Property ',$i,'<br />';

1378              //    offset: ($secOffset+8) + (8 * $i);    size: 4;    property ID

1379              $id = self::_GetInt4d($this->_documentSummaryInformation, ($secOffset+8) + (8 * $i));
1380  //            echo 'ID is ',$id,'<br />';

1381  
1382              // Use value of property id as appropriate

1383              // offset: 60 + 8 * $i;    size: 4;    offset from beginning of section (48)

1384              $offset = self::_GetInt4d($this->_documentSummaryInformation, ($secOffset+12) + (8 * $i));
1385  
1386              $type = self::_GetInt4d($this->_documentSummaryInformation, $secOffset + $offset);
1387  //            echo 'Type is ',$type,', ';

1388  
1389              // initialize property value

1390              $value = null;
1391  
1392              // extract property value based on property type

1393              switch ($type) {
1394                  case 0x02:    //    2 byte signed integer
1395                      $value = self::_GetInt2d($this->_documentSummaryInformation, $secOffset + 4 + $offset);
1396                      break;
1397  
1398                  case 0x03:    //    4 byte signed integer
1399                      $value = self::_GetInt4d($this->_documentSummaryInformation, $secOffset + 4 + $offset);
1400                      break;
1401  
1402                  case 0x0B:  // Boolean
1403                      $value = self::_GetInt2d($this->_documentSummaryInformation, $secOffset + 4 + $offset);
1404                      $value = ($value == 0 ? false : true);
1405                      break;
1406  
1407                  case 0x13:    //    4 byte unsigned integer
1408                      // not needed yet, fix later if necessary

1409                      break;
1410  
1411                  case 0x1E:    //    null-terminated string prepended by dword string length
1412                      $byteLength = self::_GetInt4d($this->_documentSummaryInformation, $secOffset + 4 + $offset);
1413                      $value = substr($this->_documentSummaryInformation, $secOffset + 8 + $offset, $byteLength);
1414                      $value = PHPExcel_Shared_String::ConvertEncoding($value, 'UTF-8', $codePage);
1415                      $value = rtrim($value);
1416                      break;
1417  
1418                  case 0x40:    //    Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
1419                      // PHP-Time

1420                      $value = PHPExcel_Shared_OLE::OLE2LocalDate(substr($this->_documentSummaryInformation, $secOffset + 4 + $offset, 8));
1421                      break;
1422  
1423                  case 0x47:    //    Clipboard format
1424                      // not needed yet, fix later if necessary

1425                      break;
1426              }
1427  
1428              switch ($id) {
1429                  case 0x01:    //    Code Page
1430                      $codePage = PHPExcel_Shared_CodePage::NumberToName($value);
1431                      break;
1432  
1433                  case 0x02:    //    Category
1434                      $this->_phpExcel->getProperties()->setCategory($value);
1435                      break;
1436  
1437                  case 0x03:    //    Presentation Target
1438                      //    Not supported by PHPExcel

1439                      break;
1440  
1441                  case 0x04:    //    Bytes
1442                      //    Not supported by PHPExcel

1443                      break;
1444  
1445                  case 0x05:    //    Lines
1446                      //    Not supported by PHPExcel

1447                      break;
1448  
1449                  case 0x06:    //    Paragraphs
1450                      //    Not supported by PHPExcel

1451                      break;
1452  
1453                  case 0x07:    //    Slides
1454                      //    Not supported by PHPExcel

1455                      break;
1456  
1457                  case 0x08:    //    Notes
1458                      //    Not supported by PHPExcel

1459                      break;
1460  
1461                  case 0x09:    //    Hidden Slides
1462                      //    Not supported by PHPExcel

1463                      break;
1464  
1465                  case 0x0A:    //    MM Clips
1466                      //    Not supported by PHPExcel

1467                      break;
1468  
1469                  case 0x0B:    //    Scale Crop
1470                      //    Not supported by PHPExcel

1471                      break;
1472  
1473                  case 0x0C:    //    Heading Pairs
1474                      //    Not supported by PHPExcel

1475                      break;
1476  
1477                  case 0x0D:    //    Titles of Parts
1478                      //    Not supported by PHPExcel

1479                      break;
1480  
1481                  case 0x0E:    //    Manager
1482                      $this->_phpExcel->getProperties()->setManager($value);
1483                      break;
1484  
1485                  case 0x0F:    //    Company
1486                      $this->_phpExcel->getProperties()->setCompany($value);
1487                      break;
1488  
1489                  case 0x10:    //    Links up-to-date
1490                      //    Not supported by PHPExcel

1491                      break;
1492  
1493              }
1494          }
1495      }
1496  
1497  
1498      /**

1499       * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record.

1500       */
1501  	private function _readDefault()
1502      {
1503          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1504  //        $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);

1505  
1506          // move stream pointer to next record

1507          $this->_pos += 4 + $length;
1508      }
1509  
1510  
1511      /**

1512       *    The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions,

1513       *        this record stores a note (cell note). This feature was significantly enhanced in Excel 97.

1514       */
1515  	private function _readNote()
1516      {
1517  //        echo '<b>Read Cell Annotation</b><br />';

1518          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1519          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
1520  
1521          // move stream pointer to next record

1522          $this->_pos += 4 + $length;
1523  
1524          if ($this->_readDataOnly) {
1525              return;
1526          }
1527  
1528          $cellAddress = $this->_readBIFF8CellAddress(substr($recordData, 0, 4));
1529          if ($this->_version == self::XLS_BIFF8) {
1530              $noteObjID = self::_GetInt2d($recordData, 6);
1531              $noteAuthor = self::_readUnicodeStringLong(substr($recordData, 8));
1532              $noteAuthor = $noteAuthor['value'];
1533  //            echo 'Note Address=',$cellAddress,'<br />';

1534  //            echo 'Note Object ID=',$noteObjID,'<br />';

1535  //            echo 'Note Author=',$noteAuthor,'<hr />';

1536  //

1537              $this->_cellNotes[$noteObjID] = array('cellRef'        => $cellAddress,
1538                                                    'objectID'    => $noteObjID,
1539                                                    'author'        => $noteAuthor
1540                                                   );
1541          } else {
1542              $extension = false;
1543              if ($cellAddress == '$B$65536') {
1544                  //    If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation

1545                  //        note from the previous cell annotation. We're not yet handling this, so annotations longer than the

1546                  //        max 2048 bytes will probably throw a wobbly.

1547                  $row = self::_GetInt2d($recordData, 0);
1548                  $extension = true;
1549                  $cellAddress = array_pop(array_keys($this->_phpSheet->getComments()));
1550              }
1551  //            echo 'Note Address=',$cellAddress,'<br />';

1552  
1553              $cellAddress = str_replace('$','',$cellAddress);
1554              $noteLength = self::_GetInt2d($recordData, 4);
1555              $noteText = trim(substr($recordData, 6));
1556  //            echo 'Note Length=',$noteLength,'<br />';

1557  //            echo 'Note Text=',$noteText,'<br />';

1558  
1559              if ($extension) {
1560                  //    Concatenate this extension with the currently set comment for the cell

1561                  $comment = $this->_phpSheet->getComment( $cellAddress );
1562                  $commentText = $comment->getText()->getPlainText();
1563                  $comment->setText($this->_parseRichText($commentText.$noteText) );
1564              } else {
1565                  //    Set comment for the cell

1566                  $this->_phpSheet->getComment( $cellAddress )
1567  //                                                    ->setAuthor( $author )

1568                                                      ->setText($this->_parseRichText($noteText) );
1569              }
1570          }
1571  
1572      }
1573  
1574  
1575      /**

1576       *    The TEXT Object record contains the text associated with a cell annotation.

1577       */
1578  	private function _readTextObject()
1579      {
1580          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1581          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
1582  
1583          // move stream pointer to next record

1584          $this->_pos += 4 + $length;
1585  
1586          if ($this->_readDataOnly) {
1587              return;
1588          }
1589  
1590          // recordData consists of an array of subrecords looking like this:

1591          //    grbit: 2 bytes; Option Flags

1592          //    rot: 2 bytes; rotation

1593          //    cchText: 2 bytes; length of the text (in the first continue record)

1594          //    cbRuns: 2 bytes; length of the formatting (in the second continue record)

1595          // followed by the continuation records containing the actual text and formatting

1596          $grbitOpts    = self::_GetInt2d($recordData, 0);
1597          $rot        = self::_GetInt2d($recordData, 2);
1598          $cchText    = self::_GetInt2d($recordData, 10);
1599          $cbRuns        = self::_GetInt2d($recordData, 12);
1600          $text        = $this->_getSplicedRecordData();
1601  
1602          $this->_textObjects[$this->textObjRef] = array(
1603                  'text'        => substr($text["recordData"],$text["spliceOffsets"][0]+1,$cchText),
1604                  'format'    => substr($text["recordData"],$text["spliceOffsets"][1],$cbRuns),
1605                  'alignment'    => $grbitOpts,
1606                  'rotation'    => $rot
1607               );
1608  
1609  //        echo '<b>_readTextObject()</b><br />';

1610  //        var_dump($this->_textObjects[$this->textObjRef]);

1611  //        echo '<br />';

1612      }
1613  
1614  
1615      /**

1616       * Read BOF

1617       */
1618  	private function _readBof()
1619      {
1620          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1621          $recordData = substr($this->_data, $this->_pos + 4, $length);
1622  
1623          // move stream pointer to next record

1624          $this->_pos += 4 + $length;
1625  
1626          // offset: 2; size: 2; type of the following data

1627          $substreamType = self::_GetInt2d($recordData, 2);
1628  
1629          switch ($substreamType) {
1630              case self::XLS_WorkbookGlobals:
1631                  $version = self::_GetInt2d($recordData, 0);
1632                  if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) {
1633                      throw new PHPExcel_Reader_Exception('Cannot read this Excel file. Version is too old.');
1634                  }
1635                  $this->_version = $version;
1636                  break;
1637  
1638              case self::XLS_Worksheet:
1639                  // do not use this version information for anything

1640                  // it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream

1641                  break;
1642  
1643              default:
1644                  // substream, e.g. chart

1645                  // just skip the entire substream

1646                  do {
1647                      $code = self::_GetInt2d($this->_data, $this->_pos);
1648                      $this->_readDefault();
1649                  } while ($code != self::XLS_Type_EOF && $this->_pos < $this->_dataSize);
1650                  break;
1651          }
1652      }
1653  
1654  
1655      /**

1656       * FILEPASS

1657       *

1658       * This record is part of the File Protection Block. It

1659       * contains information about the read/write password of the

1660       * file. All record contents following this record will be

1661       * encrypted.

1662       *

1663       * --    "OpenOffice.org's Documentation of the Microsoft

1664       *         Excel File Format"

1665       * 

1666       * The decryption functions and objects used from here on in

1667       * are based on the source of Spreadsheet-ParseExcel:

1668       * http://search.cpan.org/~jmcnamara/Spreadsheet-ParseExcel/

1669       */
1670  	private function _readFilepass()
1671      {
1672          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1673  
1674          if ($length != 54) {
1675              throw new PHPExcel_Reader_Exception('Unexpected file pass record length');
1676          }
1677          
1678          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
1679          
1680          // move stream pointer to next record

1681          $this->_pos += 4 + $length;
1682          
1683          if (!$this->_verifyPassword(
1684              'VelvetSweatshop',
1685              substr($recordData, 6,  16),
1686              substr($recordData, 22, 16),
1687              substr($recordData, 38, 16),
1688              $this->_md5Ctxt
1689          )) {
1690              throw new PHPExcel_Reader_Exception('Decryption password incorrect');
1691          }
1692          
1693          $this->_encryption = self::MS_BIFF_CRYPTO_RC4;
1694  
1695          // Decryption required from the record after next onwards

1696          $this->_encryptionStartPos = $this->_pos + self::_GetInt2d($this->_data, $this->_pos + 2);
1697      }
1698  
1699      /**

1700       * Make an RC4 decryptor for the given block

1701       * 

1702       * @var int    $block      Block for which to create decrypto

1703       * @var string $valContext MD5 context state

1704       * 

1705       * @return PHPExcel_Reader_Excel5_RC4

1706       */
1707  	private function _makeKey($block, $valContext)
1708      {
1709          $pwarray = str_repeat("\0", 64);
1710  
1711          for ($i = 0; $i < 5; $i++) {
1712              $pwarray[$i] = $valContext[$i];
1713          }
1714          
1715          $pwarray[5] = chr($block & 0xff);
1716          $pwarray[6] = chr(($block >> 8) & 0xff);
1717          $pwarray[7] = chr(($block >> 16) & 0xff);
1718          $pwarray[8] = chr(($block >> 24) & 0xff);
1719  
1720          $pwarray[9] = "\x80";
1721          $pwarray[56] = "\x48";
1722  
1723          $md5 = new PHPExcel_Reader_Excel5_MD5();
1724          $md5->add($pwarray);
1725  
1726          $s = $md5->getContext();
1727          return new PHPExcel_Reader_Excel5_RC4($s);
1728      }
1729  
1730      /**

1731       * Verify RC4 file password

1732       * 

1733       * @var string $password        Password to check

1734       * @var string $docid           Document id

1735       * @var string $salt_data       Salt data

1736       * @var string $hashedsalt_data Hashed salt data

1737       * @var string &$valContext     Set to the MD5 context of the value

1738       * 

1739       * @return bool Success

1740       */
1741  	private function _verifyPassword($password, $docid, $salt_data, $hashedsalt_data, &$valContext)
1742      {
1743          $pwarray = str_repeat("\0", 64);
1744  
1745          for ($i = 0; $i < strlen($password); $i++) {
1746              $o = ord(substr($password, $i, 1));
1747              $pwarray[2 * $i] = chr($o & 0xff);
1748              $pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff);
1749          }
1750          $pwarray[2 * $i] = chr(0x80);
1751          $pwarray[56] = chr(($i << 4) & 0xff);
1752  
1753          $md5 = new PHPExcel_Reader_Excel5_MD5();
1754          $md5->add($pwarray);
1755  
1756          $mdContext1 = $md5->getContext();
1757  
1758          $offset = 0;
1759          $keyoffset = 0;
1760          $tocopy = 5;
1761  
1762          $md5->reset();
1763  
1764          while ($offset != 16) {
1765              if ((64 - $offset) < 5) {
1766                  $tocopy = 64 - $offset;
1767              }
1768              
1769              for ($i = 0; $i <= $tocopy; $i++) {
1770                  $pwarray[$offset + $i] = $mdContext1[$keyoffset + $i];
1771              }
1772  
1773              $offset += $tocopy;
1774  
1775              if ($offset == 64) {
1776                  $md5->add($pwarray);
1777                  $keyoffset = $tocopy;
1778                  $tocopy = 5 - $tocopy;
1779                  $offset = 0;
1780                  continue;
1781              }
1782  
1783              $keyoffset = 0;
1784              $tocopy = 5;
1785              for ($i = 0; $i < 16; $i++) {
1786                  $pwarray[$offset + $i] = $docid[$i];
1787              }
1788              $offset += 16;
1789          }
1790  
1791          $pwarray[16] = "\x80";
1792          for ($i = 0; $i < 47; $i++) {
1793              $pwarray[17 + $i] = "\0";
1794          }
1795          $pwarray[56] = "\x80";
1796          $pwarray[57] = "\x0a";
1797  
1798          $md5->add($pwarray);
1799          $valContext = $md5->getContext();
1800  
1801          $key = $this->_makeKey(0, $valContext);
1802  
1803          $salt = $key->RC4($salt_data);
1804          $hashedsalt = $key->RC4($hashedsalt_data);
1805          
1806          $salt .= "\x80" . str_repeat("\0", 47);
1807          $salt[56] = "\x80";
1808  
1809          $md5->reset();
1810          $md5->add($salt);
1811          $mdContext2 = $md5->getContext();
1812  
1813          return $mdContext2 == $hashedsalt;
1814      }
1815  
1816      /**

1817       * CODEPAGE

1818       *

1819       * This record stores the text encoding used to write byte

1820       * strings, stored as MS Windows code page identifier.

1821       *

1822       * --    "OpenOffice.org's Documentation of the Microsoft

1823       *         Excel File Format"

1824       */
1825  	private function _readCodepage()
1826      {
1827          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1828          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
1829  
1830          // move stream pointer to next record

1831          $this->_pos += 4 + $length;
1832  
1833          // offset: 0; size: 2; code page identifier

1834          $codepage = self::_GetInt2d($recordData, 0);
1835  
1836          $this->_codepage = PHPExcel_Shared_CodePage::NumberToName($codepage);
1837      }
1838  
1839  
1840      /**

1841       * DATEMODE

1842       *

1843       * This record specifies the base date for displaying date

1844       * values. All dates are stored as count of days past this

1845       * base date. In BIFF2-BIFF4 this record is part of the

1846       * Calculation Settings Block. In BIFF5-BIFF8 it is

1847       * stored in the Workbook Globals Substream.

1848       *

1849       * --    "OpenOffice.org's Documentation of the Microsoft

1850       *         Excel File Format"

1851       */
1852  	private function _readDateMode()
1853      {
1854          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1855          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
1856  
1857          // move stream pointer to next record

1858          $this->_pos += 4 + $length;
1859  
1860          // offset: 0; size: 2; 0 = base 1900, 1 = base 1904

1861          PHPExcel_Shared_Date::setExcelCalendar(PHPExcel_Shared_Date::CALENDAR_WINDOWS_1900);
1862          if (ord($recordData{0}) == 1) {
1863              PHPExcel_Shared_Date::setExcelCalendar(PHPExcel_Shared_Date::CALENDAR_MAC_1904);
1864          }
1865      }
1866  
1867  
1868      /**

1869       * Read a FONT record

1870       */
1871  	private function _readFont()
1872      {
1873          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1874          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
1875  
1876          // move stream pointer to next record

1877          $this->_pos += 4 + $length;
1878  
1879          if (!$this->_readDataOnly) {
1880              $objFont = new PHPExcel_Style_Font();
1881  
1882              // offset: 0; size: 2; height of the font (in twips = 1/20 of a point)

1883              $size = self::_GetInt2d($recordData, 0);
1884              $objFont->setSize($size / 20);
1885  
1886              // offset: 2; size: 2; option flags

1887                  // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8)

1888                  // bit: 1; mask 0x0002; italic

1889                  $isItalic = (0x0002 & self::_GetInt2d($recordData, 2)) >> 1;
1890                  if ($isItalic) $objFont->setItalic(true);
1891  
1892                  // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8)

1893                  // bit: 3; mask 0x0008; strike

1894                  $isStrike = (0x0008 & self::_GetInt2d($recordData, 2)) >> 3;
1895                  if ($isStrike) $objFont->setStrikethrough(true);
1896  
1897              // offset: 4; size: 2; colour index

1898              $colorIndex = self::_GetInt2d($recordData, 4);
1899              $objFont->colorIndex = $colorIndex;
1900  
1901              // offset: 6; size: 2; font weight

1902              $weight = self::_GetInt2d($recordData, 6);
1903              switch ($weight) {
1904                  case 0x02BC:
1905                      $objFont->setBold(true);
1906                      break;
1907              }
1908  
1909              // offset: 8; size: 2; escapement type

1910              $escapement = self::_GetInt2d($recordData, 8);
1911              switch ($escapement) {
1912                  case 0x0001:
1913                      $objFont->setSuperScript(true);
1914                      break;
1915                  case 0x0002:
1916                      $objFont->setSubScript(true);
1917                      break;
1918              }
1919  
1920              // offset: 10; size: 1; underline type

1921              $underlineType = ord($recordData{10});
1922              switch ($underlineType) {
1923                  case 0x00:
1924                      break; // no underline

1925                  case 0x01:
1926                      $objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_SINGLE);
1927                      break;
1928                  case 0x02:
1929                      $objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_DOUBLE);
1930                      break;
1931                  case 0x21:
1932                      $objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_SINGLEACCOUNTING);
1933                      break;
1934                  case 0x22:
1935                      $objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_DOUBLEACCOUNTING);
1936                      break;
1937              }
1938  
1939              // offset: 11; size: 1; font family

1940              // offset: 12; size: 1; character set

1941              // offset: 13; size: 1; not used

1942              // offset: 14; size: var; font name

1943              if ($this->_version == self::XLS_BIFF8) {
1944                  $string = self::_readUnicodeStringShort(substr($recordData, 14));
1945              } else {
1946                  $string = $this->_readByteStringShort(substr($recordData, 14));
1947              }
1948              $objFont->setName($string['value']);
1949  
1950              $this->_objFonts[] = $objFont;
1951          }
1952      }
1953  
1954  
1955      /**

1956       * FORMAT

1957       *

1958       * This record contains information about a number format.

1959       * All FORMAT records occur together in a sequential list.

1960       *

1961       * In BIFF2-BIFF4 other records referencing a FORMAT record

1962       * contain a zero-based index into this list. From BIFF5 on

1963       * the FORMAT record contains the index itself that will be

1964       * used by other records.

1965       *

1966       * --    "OpenOffice.org's Documentation of the Microsoft

1967       *         Excel File Format"

1968       */
1969  	private function _readFormat()
1970      {
1971          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
1972          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
1973  
1974          // move stream pointer to next record

1975          $this->_pos += 4 + $length;
1976  
1977          if (!$this->_readDataOnly) {
1978              $indexCode = self::_GetInt2d($recordData, 0);
1979  
1980              if ($this->_version == self::XLS_BIFF8) {
1981                  $string = self::_readUnicodeStringLong(substr($recordData, 2));
1982              } else {
1983                  // BIFF7

1984                  $string = $this->_readByteStringShort(substr($recordData, 2));
1985              }
1986  
1987              $formatString = $string['value'];
1988              $this->_formats[$indexCode] = $formatString;
1989          }
1990      }
1991  
1992  
1993      /**

1994       * XF - Extended Format

1995       *

1996       * This record contains formatting information for cells, rows, columns or styles.

1997       * According to http://support.microsoft.com/kb/147732 there are always at least 15 cell style XF

1998       * and 1 cell XF.

1999       * Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF

2000       * and XF record 15 is a cell XF

2001       * We only read the first cell style XF and skip the remaining cell style XF records

2002       * We read all cell XF records.

2003       *

2004       * --    "OpenOffice.org's Documentation of the Microsoft

2005       *         Excel File Format"

2006       */
2007  	private function _readXf()
2008      {
2009          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2010          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
2011  
2012          // move stream pointer to next record

2013          $this->_pos += 4 + $length;
2014  
2015          $objStyle = new PHPExcel_Style();
2016  
2017          if (!$this->_readDataOnly) {
2018              // offset:  0; size: 2; Index to FONT record

2019              if (self::_GetInt2d($recordData, 0) < 4) {
2020                  $fontIndex = self::_GetInt2d($recordData, 0);
2021              } else {
2022                  // this has to do with that index 4 is omitted in all BIFF versions for some strange reason

2023                  // check the OpenOffice documentation of the FONT record

2024                  $fontIndex = self::_GetInt2d($recordData, 0) - 1;
2025              }
2026              $objStyle->setFont($this->_objFonts[$fontIndex]);
2027  
2028              // offset:  2; size: 2; Index to FORMAT record

2029              $numberFormatIndex = self::_GetInt2d($recordData, 2);
2030              if (isset($this->_formats[$numberFormatIndex])) {
2031                  // then we have user-defined format code

2032                  $numberformat = array('code' => $this->_formats[$numberFormatIndex]);
2033              } elseif (($code = PHPExcel_Style_NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') {
2034                  // then we have built-in format code

2035                  $numberformat = array('code' => $code);
2036              } else {
2037                  // we set the general format code

2038                  $numberformat = array('code' => 'General');
2039              }
2040              $objStyle->getNumberFormat()->setFormatCode($numberformat['code']);
2041  
2042              // offset:  4; size: 2; XF type, cell protection, and parent style XF

2043              // bit 2-0; mask 0x0007; XF_TYPE_PROT

2044              $xfTypeProt = self::_GetInt2d($recordData, 4);
2045              // bit 0; mask 0x01; 1 = cell is locked

2046              $isLocked = (0x01 & $xfTypeProt) >> 0;
2047              $objStyle->getProtection()->setLocked($isLocked ?
2048                  PHPExcel_Style_Protection::PROTECTION_INHERIT : PHPExcel_Style_Protection::PROTECTION_UNPROTECTED);
2049  
2050              // bit 1; mask 0x02; 1 = Formula is hidden

2051              $isHidden = (0x02 & $xfTypeProt) >> 1;
2052              $objStyle->getProtection()->setHidden($isHidden ?
2053                  PHPExcel_Style_Protection::PROTECTION_PROTECTED : PHPExcel_Style_Protection::PROTECTION_UNPROTECTED);
2054  
2055              // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF

2056              $isCellStyleXf = (0x04 & $xfTypeProt) >> 2;
2057  
2058              // offset:  6; size: 1; Alignment and text break

2059              // bit 2-0, mask 0x07; horizontal alignment

2060              $horAlign = (0x07 & ord($recordData{6})) >> 0;
2061              switch ($horAlign) {
2062                  case 0:
2063                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_GENERAL);
2064                      break;
2065                  case 1:
2066                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
2067                      break;
2068                  case 2:
2069                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER);
2070                      break;
2071                  case 3:
2072                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_RIGHT);
2073                      break;
2074                  case 4:
2075                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_FILL);
2076                      break;
2077                  case 5:
2078                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_JUSTIFY);
2079                      break;
2080                  case 6:
2081                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER_CONTINUOUS);
2082                      break;
2083              }
2084              // bit 3, mask 0x08; wrap text

2085              $wrapText = (0x08 & ord($recordData{6})) >> 3;
2086              switch ($wrapText) {
2087                  case 0:
2088                      $objStyle->getAlignment()->setWrapText(false);
2089                      break;
2090                  case 1:
2091                      $objStyle->getAlignment()->setWrapText(true);
2092                      break;
2093              }
2094              // bit 6-4, mask 0x70; vertical alignment

2095              $vertAlign = (0x70 & ord($recordData{6})) >> 4;
2096              switch ($vertAlign) {
2097                  case 0:
2098                      $objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_TOP);
2099                      break;
2100                  case 1:
2101                      $objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_CENTER);
2102                      break;
2103                  case 2:
2104                      $objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_BOTTOM);
2105                      break;
2106                  case 3:
2107                      $objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_JUSTIFY);
2108                      break;
2109              }
2110  
2111              if ($this->_version == self::XLS_BIFF8) {
2112                  // offset:  7; size: 1; XF_ROTATION: Text rotation angle

2113                      $angle = ord($recordData{7});
2114                      $rotation = 0;
2115                      if ($angle <= 90) {
2116                          $rotation = $angle;
2117                      } else if ($angle <= 180) {
2118                          $rotation = 90 - $angle;
2119                      } else if ($angle == 255) {
2120                          $rotation = -165;
2121                      }
2122                      $objStyle->getAlignment()->setTextRotation($rotation);
2123  
2124                  // offset:  8; size: 1; Indentation, shrink to cell size, and text direction

2125                      // bit: 3-0; mask: 0x0F; indent level

2126                      $indent = (0x0F & ord($recordData{8})) >> 0;
2127                      $objStyle->getAlignment()->setIndent($indent);
2128  
2129                      // bit: 4; mask: 0x10; 1 = shrink content to fit into cell

2130                      $shrinkToFit = (0x10 & ord($recordData{8})) >> 4;
2131                      switch ($shrinkToFit) {
2132                          case 0:
2133                              $objStyle->getAlignment()->setShrinkToFit(false);
2134                              break;
2135                          case 1:
2136                              $objStyle->getAlignment()->setShrinkToFit(true);
2137                              break;
2138                      }
2139  
2140                  // offset:  9; size: 1; Flags used for attribute groups

2141  
2142                  // offset: 10; size: 4; Cell border lines and background area

2143                      // bit: 3-0; mask: 0x0000000F; left style

2144                      if ($bordersLeftStyle = self::_mapBorderStyle((0x0000000F & self::_GetInt4d($recordData, 10)) >> 0)) {
2145                          $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle);
2146                      }
2147                      // bit: 7-4; mask: 0x000000F0; right style

2148                      if ($bordersRightStyle = self::_mapBorderStyle((0x000000F0 & self::_GetInt4d($recordData, 10)) >> 4)) {
2149                          $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle);
2150                      }
2151                      // bit: 11-8; mask: 0x00000F00; top style

2152                      if ($bordersTopStyle = self::_mapBorderStyle((0x00000F00 & self::_GetInt4d($recordData, 10)) >> 8)) {
2153                          $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle);
2154                      }
2155                      // bit: 15-12; mask: 0x0000F000; bottom style

2156                      if ($bordersBottomStyle = self::_mapBorderStyle((0x0000F000 & self::_GetInt4d($recordData, 10)) >> 12)) {
2157                          $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle);
2158                      }
2159                      // bit: 22-16; mask: 0x007F0000; left color

2160                      $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::_GetInt4d($recordData, 10)) >> 16;
2161  
2162                      // bit: 29-23; mask: 0x3F800000; right color

2163                      $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::_GetInt4d($recordData, 10)) >> 23;
2164  
2165                      // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom

2166                      $diagonalDown = (0x40000000 & self::_GetInt4d($recordData, 10)) >> 30 ?
2167                          true : false;
2168  
2169                      // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right

2170                      $diagonalUp = (0x80000000 & self::_GetInt4d($recordData, 10)) >> 31 ?
2171                          true : false;
2172  
2173                      if ($diagonalUp == false && $diagonalDown == false) {
2174                          $objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_NONE);
2175                      } elseif ($diagonalUp == true && $diagonalDown == false) {
2176                          $objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_UP);
2177                      } elseif ($diagonalUp == false && $diagonalDown == true) {
2178                          $objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_DOWN);
2179                      } elseif ($diagonalUp == true && $diagonalDown == true) {
2180                          $objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_BOTH);
2181                      }
2182  
2183                  // offset: 14; size: 4;

2184                      // bit: 6-0; mask: 0x0000007F; top color

2185                      $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::_GetInt4d($recordData, 14)) >> 0;
2186  
2187                      // bit: 13-7; mask: 0x00003F80; bottom color

2188                      $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::_GetInt4d($recordData, 14)) >> 7;
2189  
2190                      // bit: 20-14; mask: 0x001FC000; diagonal color

2191                      $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::_GetInt4d($recordData, 14)) >> 14;
2192  
2193                      // bit: 24-21; mask: 0x01E00000; diagonal style

2194                      if ($bordersDiagonalStyle = self::_mapBorderStyle((0x01E00000 & self::_GetInt4d($recordData, 14)) >> 21)) {
2195                          $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle);
2196                      }
2197  
2198                      // bit: 31-26; mask: 0xFC000000 fill pattern

2199                      if ($fillType = self::_mapFillPattern((0xFC000000 & self::_GetInt4d($recordData, 14)) >> 26)) {
2200                          $objStyle->getFill()->setFillType($fillType);
2201                      }
2202                  // offset: 18; size: 2; pattern and background colour

2203                      // bit: 6-0; mask: 0x007F; color index for pattern color

2204                      $objStyle->getFill()->startcolorIndex = (0x007F & self::_GetInt2d($recordData, 18)) >> 0;
2205  
2206                      // bit: 13-7; mask: 0x3F80; color index for pattern background

2207                      $objStyle->getFill()->endcolorIndex = (0x3F80 & self::_GetInt2d($recordData, 18)) >> 7;
2208              } else {
2209                  // BIFF5

2210  
2211                  // offset: 7; size: 1; Text orientation and flags

2212                  $orientationAndFlags = ord($recordData{7});
2213  
2214                  // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation

2215                  $xfOrientation = (0x03 & $orientationAndFlags) >> 0;
2216                  switch ($xfOrientation) {
2217                      case 0:
2218                          $objStyle->getAlignment()->setTextRotation(0);
2219                          break;
2220                      case 1:
2221                          $objStyle->getAlignment()->setTextRotation(-165);
2222                          break;
2223                      case 2:
2224                          $objStyle->getAlignment()->setTextRotation(90);
2225                          break;
2226                      case 3:
2227                          $objStyle->getAlignment()->setTextRotation(-90);
2228                          break;
2229                  }
2230  
2231                  // offset: 8; size: 4; cell border lines and background area

2232                  $borderAndBackground = self::_GetInt4d($recordData, 8);
2233  
2234                  // bit: 6-0; mask: 0x0000007F; color index for pattern color

2235                  $objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0;
2236  
2237                  // bit: 13-7; mask: 0x00003F80; color index for pattern background

2238                  $objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7;
2239  
2240                  // bit: 21-16; mask: 0x003F0000; fill pattern

2241                  $objStyle->getFill()->setFillType(self::_mapFillPattern((0x003F0000 & $borderAndBackground) >> 16));
2242  
2243                  // bit: 24-22; mask: 0x01C00000; bottom line style

2244                  $objStyle->getBorders()->getBottom()->setBorderStyle(self::_mapBorderStyle((0x01C00000 & $borderAndBackground) >> 22));
2245  
2246                  // bit: 31-25; mask: 0xFE000000; bottom line color

2247                  $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25;
2248  
2249                  // offset: 12; size: 4; cell border lines

2250                  $borderLines = self::_GetInt4d($recordData, 12);
2251  
2252                  // bit: 2-0; mask: 0x00000007; top line style

2253                  $objStyle->getBorders()->getTop()->setBorderStyle(self::_mapBorderStyle((0x00000007 & $borderLines) >> 0));
2254  
2255                  // bit: 5-3; mask: 0x00000038; left line style

2256                  $objStyle->getBorders()->getLeft()->setBorderStyle(self::_mapBorderStyle((0x00000038 & $borderLines) >> 3));
2257  
2258                  // bit: 8-6; mask: 0x000001C0; right line style

2259                  $objStyle->getBorders()->getRight()->setBorderStyle(self::_mapBorderStyle((0x000001C0 & $borderLines) >> 6));
2260  
2261                  // bit: 15-9; mask: 0x0000FE00; top line color index

2262                  $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9;
2263  
2264                  // bit: 22-16; mask: 0x007F0000; left line color index

2265                  $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16;
2266  
2267                  // bit: 29-23; mask: 0x3F800000; right line color index

2268                  $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23;
2269              }
2270  
2271              // add cellStyleXf or cellXf and update mapping

2272              if ($isCellStyleXf) {
2273                  // we only read one style XF record which is always the first

2274                  if ($this->_xfIndex == 0) {
2275                      $this->_phpExcel->addCellStyleXf($objStyle);
2276                      $this->_mapCellStyleXfIndex[$this->_xfIndex] = 0;
2277                  }
2278              } else {
2279                  // we read all cell XF records

2280                  $this->_phpExcel->addCellXf($objStyle);
2281                  $this->_mapCellXfIndex[$this->_xfIndex] = count($this->_phpExcel->getCellXfCollection()) - 1;
2282              }
2283  
2284              // update XF index for when we read next record

2285              ++$this->_xfIndex;
2286          }
2287      }
2288  
2289  
2290      /**

2291       *

2292       */
2293  	private function _readXfExt()
2294      {
2295          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2296          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
2297  
2298          // move stream pointer to next record

2299          $this->_pos += 4 + $length;
2300  
2301          if (!$this->_readDataOnly) {
2302              // offset: 0; size: 2; 0x087D = repeated header

2303  
2304              // offset: 2; size: 2

2305  
2306              // offset: 4; size: 8; not used

2307  
2308              // offset: 12; size: 2; record version

2309  
2310              // offset: 14; size: 2; index to XF record which this record modifies

2311              $ixfe = self::_GetInt2d($recordData, 14);
2312  
2313              // offset: 16; size: 2; not used

2314  
2315              // offset: 18; size: 2; number of extension properties that follow

2316              $cexts = self::_GetInt2d($recordData, 18);
2317  
2318              // start reading the actual extension data

2319              $offset = 20;
2320              while ($offset < $length) {
2321                  // extension type

2322                  $extType = self::_GetInt2d($recordData, $offset);
2323  
2324                  // extension length

2325                  $cb = self::_GetInt2d($recordData, $offset + 2);
2326  
2327                  // extension data

2328                  $extData = substr($recordData, $offset + 4, $cb);
2329  
2330                  switch ($extType) {
2331                      case 4:        // fill start color
2332                          $xclfType  = self::_GetInt2d($extData, 0); // color type

2333                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)

2334  
2335                          if ($xclfType == 2) {
2336                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2337  
2338                              // modify the relevant style property

2339                              if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2340                                  $fill = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getFill();
2341                                  $fill->getStartColor()->setRGB($rgb);
2342                                  unset($fill->startcolorIndex); // normal color index does not apply, discard

2343                              }
2344                          }
2345                          break;
2346  
2347                      case 5:        // fill end color
2348                          $xclfType  = self::_GetInt2d($extData, 0); // color type

2349                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)

2350  
2351                          if ($xclfType == 2) {
2352                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2353  
2354                              // modify the relevant style property

2355                              if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2356                                  $fill = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getFill();
2357                                  $fill->getEndColor()->setRGB($rgb);
2358                                  unset($fill->endcolorIndex); // normal color index does not apply, discard

2359                              }
2360                          }
2361                          break;
2362  
2363                      case 7:        // border color top
2364                          $xclfType  = self::_GetInt2d($extData, 0); // color type

2365                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)

2366  
2367                          if ($xclfType == 2) {
2368                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2369  
2370                              // modify the relevant style property

2371                              if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2372                                  $top = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getTop();
2373                                  $top->getColor()->setRGB($rgb);
2374                                  unset($top->colorIndex); // normal color index does not apply, discard

2375                              }
2376                          }
2377                          break;
2378  
2379                      case 8:        // border color bottom
2380                          $xclfType  = self::_GetInt2d($extData, 0); // color type

2381                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)

2382  
2383                          if ($xclfType == 2) {
2384                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2385  
2386                              // modify the relevant style property

2387                              if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2388                                  $bottom = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getBottom();
2389                                  $bottom->getColor()->setRGB($rgb);
2390                                  unset($bottom->colorIndex); // normal color index does not apply, discard

2391                              }
2392                          }
2393                          break;
2394  
2395                      case 9:        // border color left
2396                          $xclfType  = self::_GetInt2d($extData, 0); // color type

2397                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)

2398  
2399                          if ($xclfType == 2) {
2400                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2401  
2402                              // modify the relevant style property

2403                              if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2404                                  $left = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getLeft();
2405                                  $left->getColor()->setRGB($rgb);
2406                                  unset($left->colorIndex); // normal color index does not apply, discard

2407                              }
2408                          }
2409                          break;
2410  
2411                      case 10:        // border color right
2412                          $xclfType  = self::_GetInt2d($extData, 0); // color type

2413                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)

2414  
2415                          if ($xclfType == 2) {
2416                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2417  
2418                              // modify the relevant style property

2419                              if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2420                                  $right = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getRight();
2421                                  $right->getColor()->setRGB($rgb);
2422                                  unset($right->colorIndex); // normal color index does not apply, discard

2423                              }
2424                          }
2425                          break;
2426  
2427                      case 11:        // border color diagonal
2428                          $xclfType  = self::_GetInt2d($extData, 0); // color type

2429                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)

2430  
2431                          if ($xclfType == 2) {
2432                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2433  
2434                              // modify the relevant style property

2435                              if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2436                                  $diagonal = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getBorders()->getDiagonal();
2437                                  $diagonal->getColor()->setRGB($rgb);
2438                                  unset($diagonal->colorIndex); // normal color index does not apply, discard

2439                              }
2440                          }
2441                          break;
2442  
2443                      case 13:    // font color
2444                          $xclfType  = self::_GetInt2d($extData, 0); // color type

2445                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)

2446  
2447                          if ($xclfType == 2) {
2448                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2449  
2450                              // modify the relevant style property

2451                              if ( isset($this->_mapCellXfIndex[$ixfe]) ) {
2452                                  $font = $this->_phpExcel->getCellXfByIndex($this->_mapCellXfIndex[$ixfe])->getFont();
2453                                  $font->getColor()->setRGB($rgb);
2454                                  unset($font->colorIndex); // normal color index does not apply, discard

2455                              }
2456                          }
2457                          break;
2458                  }
2459  
2460                  $offset += $cb;
2461              }
2462          }
2463  
2464      }
2465  
2466  
2467      /**

2468       * Read STYLE record

2469       */
2470  	private function _readStyle()
2471      {
2472          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2473          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
2474  
2475          // move stream pointer to next record

2476          $this->_pos += 4 + $length;
2477  
2478          if (!$this->_readDataOnly) {
2479              // offset: 0; size: 2; index to XF record and flag for built-in style

2480              $ixfe = self::_GetInt2d($recordData, 0);
2481  
2482              // bit: 11-0; mask 0x0FFF; index to XF record

2483              $xfIndex = (0x0FFF & $ixfe) >> 0;
2484  
2485              // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style

2486              $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15);
2487  
2488              if ($isBuiltIn) {
2489                  // offset: 2; size: 1; identifier for built-in style

2490                  $builtInId = ord($recordData{2});
2491  
2492                  switch ($builtInId) {
2493                  case 0x00:
2494                      // currently, we are not using this for anything

2495                      break;
2496  
2497                  default:
2498                      break;
2499                  }
2500  
2501              } else {
2502                  // user-defined; not supported by PHPExcel

2503              }
2504          }
2505      }
2506  
2507  
2508      /**

2509       * Read PALETTE record

2510       */
2511  	private function _readPalette()
2512      {
2513          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2514          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
2515  
2516          // move stream pointer to next record

2517          $this->_pos += 4 + $length;
2518  
2519          if (!$this->_readDataOnly) {
2520              // offset: 0; size: 2; number of following colors

2521              $nm = self::_GetInt2d($recordData, 0);
2522  
2523              // list of RGB colors

2524              for ($i = 0; $i < $nm; ++$i) {
2525                  $rgb = substr($recordData, 2 + 4 * $i, 4);
2526                  $this->_palette[] = self::_readRGB($rgb);
2527              }
2528          }
2529      }
2530  
2531  
2532      /**

2533       * SHEET

2534       *

2535       * This record is  located in the  Workbook Globals

2536       * Substream  and represents a sheet inside the workbook.

2537       * One SHEET record is written for each sheet. It stores the

2538       * sheet name and a stream offset to the BOF record of the

2539       * respective Sheet Substream within the Workbook Stream.

2540       *

2541       * --    "OpenOffice.org's Documentation of the Microsoft

2542       *         Excel File Format"

2543       */
2544  	private function _readSheet()
2545      {
2546          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2547          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
2548  
2549          // offset: 0; size: 4; absolute stream position of the BOF record of the sheet

2550          // NOTE: not encrypted

2551          $rec_offset = self::_GetInt4d($this->_data, $this->_pos + 4);
2552  
2553          // move stream pointer to next record

2554          $this->_pos += 4 + $length;
2555  
2556          // offset: 4; size: 1; sheet state

2557          switch (ord($recordData{4})) {
2558              case 0x00: $sheetState = PHPExcel_Worksheet::SHEETSTATE_VISIBLE;    break;
2559              case 0x01: $sheetState = PHPExcel_Worksheet::SHEETSTATE_HIDDEN;     break;
2560              case 0x02: $sheetState = PHPExcel_Worksheet::SHEETSTATE_VERYHIDDEN; break;
2561              default: $sheetState = PHPExcel_Worksheet::SHEETSTATE_VISIBLE;      break;
2562          }
2563  
2564          // offset: 5; size: 1; sheet type

2565          $sheetType = ord($recordData{5});
2566  
2567          // offset: 6; size: var; sheet name

2568          if ($this->_version == self::XLS_BIFF8) {
2569              $string = self::_readUnicodeStringShort(substr($recordData, 6));
2570              $rec_name = $string['value'];
2571          } elseif ($this->_version == self::XLS_BIFF7) {
2572              $string = $this->_readByteStringShort(substr($recordData, 6));
2573              $rec_name = $string['value'];
2574          }
2575  
2576          $this->_sheets[] = array(
2577              'name' => $rec_name,
2578              'offset' => $rec_offset,
2579              'sheetState' => $sheetState,
2580              'sheetType' => $sheetType,
2581          );
2582      }
2583  
2584  
2585      /**

2586       * Read EXTERNALBOOK record

2587       */
2588  	private function _readExternalBook()
2589      {
2590          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2591          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
2592  
2593          // move stream pointer to next record

2594          $this->_pos += 4 + $length;
2595  
2596          // offset within record data

2597          $offset = 0;
2598  
2599          // there are 4 types of records

2600          if (strlen($recordData) > 4) {
2601              // external reference

2602              // offset: 0; size: 2; number of sheet names ($nm)

2603              $nm = self::_GetInt2d($recordData, 0);
2604              $offset += 2;
2605  
2606              // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length)

2607              $encodedUrlString = self::_readUnicodeStringLong(substr($recordData, 2));
2608              $offset += $encodedUrlString['size'];
2609  
2610              // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length)

2611              $externalSheetNames = array();
2612              for ($i = 0; $i < $nm; ++$i) {
2613                  $externalSheetNameString = self::_readUnicodeStringLong(substr($recordData, $offset));
2614                  $externalSheetNames[] = $externalSheetNameString['value'];
2615                  $offset += $externalSheetNameString['size'];
2616              }
2617  
2618              // store the record data

2619              $this->_externalBooks[] = array(
2620                  'type' => 'external',
2621                  'encodedUrl' => $encodedUrlString['value'],
2622                  'externalSheetNames' => $externalSheetNames,
2623              );
2624  
2625          } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) {
2626              // internal reference

2627              // offset: 0; size: 2; number of sheet in this document

2628              // offset: 2; size: 2; 0x01 0x04

2629              $this->_externalBooks[] = array(
2630                  'type' => 'internal',
2631              );
2632          } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) {
2633              // add-in function

2634              // offset: 0; size: 2; 0x0001

2635              $this->_externalBooks[] = array(
2636                  'type' => 'addInFunction',
2637              );
2638          } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) {
2639              // DDE links, OLE links

2640              // offset: 0; size: 2; 0x0000

2641              // offset: 2; size: var; encoded source document name

2642              $this->_externalBooks[] = array(
2643                  'type' => 'DDEorOLE',
2644              );
2645          }
2646      }
2647  
2648  
2649      /**

2650       * Read EXTERNNAME record.

2651       */
2652  	private function _readExternName()
2653      {
2654          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2655          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
2656  
2657          // move stream pointer to next record

2658          $this->_pos += 4 + $length;
2659  
2660          // external sheet references provided for named cells

2661          if ($this->_version == self::XLS_BIFF8) {
2662              // offset: 0; size: 2; options

2663              $options = self::_GetInt2d($recordData, 0);
2664  
2665              // offset: 2; size: 2;

2666  
2667              // offset: 4; size: 2; not used

2668  
2669              // offset: 6; size: var

2670              $nameString = self::_readUnicodeStringShort(substr($recordData, 6));
2671  
2672              // offset: var; size: var; formula data

2673              $offset = 6 + $nameString['size'];
2674              $formula = $this->_getFormulaFromStructure(substr($recordData, $offset));
2675  
2676              $this->_externalNames[] = array(
2677                  'name' => $nameString['value'],
2678                  'formula' => $formula,
2679              );
2680          }
2681      }
2682  
2683  
2684      /**

2685       * Read EXTERNSHEET record

2686       */
2687  	private function _readExternSheet()
2688      {
2689          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2690          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
2691  
2692          // move stream pointer to next record

2693          $this->_pos += 4 + $length;
2694  
2695          // external sheet references provided for named cells

2696          if ($this->_version == self::XLS_BIFF8) {
2697              // offset: 0; size: 2; number of following ref structures

2698              $nm = self::_GetInt2d($recordData, 0);
2699              for ($i = 0; $i < $nm; ++$i) {
2700                  $this->_ref[] = array(
2701                      // offset: 2 + 6 * $i; index to EXTERNALBOOK record

2702                      'externalBookIndex' => self::_GetInt2d($recordData, 2 + 6 * $i),
2703                      // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record

2704                      'firstSheetIndex' => self::_GetInt2d($recordData, 4 + 6 * $i),
2705                      // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record

2706                      'lastSheetIndex' => self::_GetInt2d($recordData, 6 + 6 * $i),
2707                  );
2708              }
2709          }
2710      }
2711  
2712  
2713      /**

2714       * DEFINEDNAME

2715       *

2716       * This record is part of a Link Table. It contains the name

2717       * and the token array of an internal defined name. Token

2718       * arrays of defined names contain tokens with aberrant

2719       * token classes.

2720       *

2721       * --    "OpenOffice.org's Documentation of the Microsoft

2722       *         Excel File Format"

2723       */
2724  	private function _readDefinedName()
2725      {
2726          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2727          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
2728  
2729          // move stream pointer to next record

2730          $this->_pos += 4 + $length;
2731  
2732          if ($this->_version == self::XLS_BIFF8) {
2733              // retrieves named cells

2734  
2735              // offset: 0; size: 2; option flags

2736              $opts = self::_GetInt2d($recordData, 0);
2737  
2738                  // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name

2739                  $isBuiltInName = (0x0020 & $opts) >> 5;
2740  
2741              // offset: 2; size: 1; keyboard shortcut

2742  
2743              // offset: 3; size: 1; length of the name (character count)

2744              $nlen = ord($recordData{3});
2745  
2746              // offset: 4; size: 2; size of the formula data (it can happen that this is zero)

2747              // note: there can also be additional data, this is not included in $flen

2748              $flen = self::_GetInt2d($recordData, 4);
2749  
2750              // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based)

2751              $scope = self::_GetInt2d($recordData, 8);
2752  
2753              // offset: 14; size: var; Name (Unicode string without length field)

2754              $string = self::_readUnicodeString(substr($recordData, 14), $nlen);
2755  
2756              // offset: var; size: $flen; formula data

2757              $offset = 14 + $string['size'];
2758              $formulaStructure = pack('v', $flen) . substr($recordData, $offset);
2759  
2760              try {
2761                  $formula = $this->_getFormulaFromStructure($formulaStructure);
2762              } catch (PHPExcel_Exception $e) {
2763                  $formula = '';
2764              }
2765  
2766              $this->_definedname[] = array(
2767                  'isBuiltInName' => $isBuiltInName,
2768                  'name' => $string['value'],
2769                  'formula' => $formula,
2770                  'scope' => $scope,
2771              );
2772          }
2773      }
2774  
2775  
2776      /**

2777       * Read MSODRAWINGGROUP record

2778       */
2779  	private function _readMsoDrawingGroup()
2780      {
2781          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2782  
2783          // get spliced record data

2784          $splicedRecordData = $this->_getSplicedRecordData();
2785          $recordData = $splicedRecordData['recordData'];
2786  
2787          $this->_drawingGroupData .= $recordData;
2788      }
2789  
2790  
2791      /**

2792       * SST - Shared String Table

2793       *

2794       * This record contains a list of all strings used anywhere

2795       * in the workbook. Each string occurs only once. The

2796       * workbook uses indexes into the list to reference the

2797       * strings.

2798       *

2799       * --    "OpenOffice.org's Documentation of the Microsoft

2800       *         Excel File Format"

2801       **/
2802  	private function _readSst()
2803      {
2804          // offset within (spliced) record data

2805          $pos = 0;
2806  
2807          // get spliced record data

2808          $splicedRecordData = $this->_getSplicedRecordData();
2809  
2810          $recordData = $splicedRecordData['recordData'];
2811          $spliceOffsets = $splicedRecordData['spliceOffsets'];
2812  
2813          // offset: 0; size: 4; total number of strings in the workbook

2814          $pos += 4;
2815  
2816          // offset: 4; size: 4; number of following strings ($nm)

2817          $nm = self::_GetInt4d($recordData, 4);
2818          $pos += 4;
2819  
2820          // loop through the Unicode strings (16-bit length)

2821          for ($i = 0; $i < $nm; ++$i) {
2822  
2823              // number of characters in the Unicode string

2824              $numChars = self::_GetInt2d($recordData, $pos);
2825              $pos += 2;
2826  
2827              // option flags

2828              $optionFlags = ord($recordData{$pos});
2829              ++$pos;
2830  
2831              // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed

2832              $isCompressed = (($optionFlags & 0x01) == 0) ;
2833  
2834              // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic

2835              $hasAsian = (($optionFlags & 0x04) != 0);
2836  
2837              // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text

2838              $hasRichText = (($optionFlags & 0x08) != 0);
2839  
2840              if ($hasRichText) {
2841                  // number of Rich-Text formatting runs

2842                  $formattingRuns = self::_GetInt2d($recordData, $pos);
2843                  $pos += 2;
2844              }
2845  
2846              if ($hasAsian) {
2847                  // size of Asian phonetic setting

2848                  $extendedRunLength = self::_GetInt4d($recordData, $pos);
2849                  $pos += 4;
2850              }
2851  
2852              // expected byte length of character array if not split

2853              $len = ($isCompressed) ? $numChars : $numChars * 2;
2854  
2855              // look up limit position

2856              foreach ($spliceOffsets as $spliceOffset) {
2857                  // it can happen that the string is empty, therefore we need

2858                  // <= and not just <

2859                  if ($pos <= $spliceOffset) {
2860                      $limitpos = $spliceOffset;
2861                      break;
2862                  }
2863              }
2864  
2865              if ($pos + $len <= $limitpos) {
2866                  // character array is not split between records

2867  
2868                  $retstr = substr($recordData, $pos, $len);
2869                  $pos += $len;
2870  
2871              } else {
2872                  // character array is split between records

2873  
2874                  // first part of character array

2875                  $retstr = substr($recordData, $pos, $limitpos - $pos);
2876  
2877                  $bytesRead = $limitpos - $pos;
2878  
2879                  // remaining characters in Unicode string

2880                  $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));
2881  
2882                  $pos = $limitpos;
2883  
2884                  // keep reading the characters

2885                  while ($charsLeft > 0) {
2886  
2887                      // look up next limit position, in case the string span more than one continue record

2888                      foreach ($spliceOffsets as $spliceOffset) {
2889                          if ($pos < $spliceOffset) {
2890                              $limitpos = $spliceOffset;
2891                              break;
2892                          }
2893                      }
2894  
2895                      // repeated option flags

2896                      // OpenOffice.org documentation 5.21

2897                      $option = ord($recordData{$pos});
2898                      ++$pos;
2899  
2900                      if ($isCompressed && ($option == 0)) {
2901                          // 1st fragment compressed

2902                          // this fragment compressed

2903                          $len = min($charsLeft, $limitpos - $pos);
2904                          $retstr .= substr($recordData, $pos, $len);
2905                          $charsLeft -= $len;
2906                          $isCompressed = true;
2907  
2908                      } elseif (!$isCompressed && ($option != 0)) {
2909                          // 1st fragment uncompressed

2910                          // this fragment uncompressed

2911                          $len = min($charsLeft * 2, $limitpos - $pos);
2912                          $retstr .= substr($recordData, $pos, $len);
2913                          $charsLeft -= $len / 2;
2914                          $isCompressed = false;
2915  
2916                      } elseif (!$isCompressed && ($option == 0)) {
2917                          // 1st fragment uncompressed

2918                          // this fragment compressed

2919                          $len = min($charsLeft, $limitpos - $pos);
2920                          for ($j = 0; $j < $len; ++$j) {
2921                              $retstr .= $recordData{$pos + $j} . chr(0);
2922                          }
2923                          $charsLeft -= $len;
2924                          $isCompressed = false;
2925  
2926                      } else {
2927                          // 1st fragment compressed

2928                          // this fragment uncompressed

2929                          $newstr = '';
2930                          for ($j = 0; $j < strlen($retstr); ++$j) {
2931                              $newstr .= $retstr[$j] . chr(0);
2932                          }
2933                          $retstr = $newstr;
2934                          $len = min($charsLeft * 2, $limitpos - $pos);
2935                          $retstr .= substr($recordData, $pos, $len);
2936                          $charsLeft -= $len / 2;
2937                          $isCompressed = false;
2938                      }
2939  
2940                      $pos += $len;
2941                  }
2942              }
2943  
2944              // convert to UTF-8

2945              $retstr = self::_encodeUTF16($retstr, $isCompressed);
2946  
2947              // read additional Rich-Text information, if any

2948              $fmtRuns = array();
2949              if ($hasRichText) {
2950                  // list of formatting runs

2951                  for ($j = 0; $j < $formattingRuns; ++$j) {
2952                      // first formatted character; zero-based

2953                      $charPos = self::_GetInt2d($recordData, $pos + $j * 4);
2954  
2955                      // index to font record

2956                      $fontIndex = self::_GetInt2d($recordData, $pos + 2 + $j * 4);
2957  
2958                      $fmtRuns[] = array(
2959                          'charPos' => $charPos,
2960                          'fontIndex' => $fontIndex,
2961                      );
2962                  }
2963                  $pos += 4 * $formattingRuns;
2964              }
2965  
2966              // read additional Asian phonetics information, if any

2967              if ($hasAsian) {
2968                  // For Asian phonetic settings, we skip the extended string data

2969                  $pos += $extendedRunLength;
2970              }
2971  
2972              // store the shared sting

2973              $this->_sst[] = array(
2974                  'value' => $retstr,
2975                  'fmtRuns' => $fmtRuns,
2976              );
2977          }
2978  
2979          // _getSplicedRecordData() takes care of moving current position in data stream

2980      }
2981  
2982  
2983      /**

2984       * Read PRINTGRIDLINES record

2985       */
2986  	private function _readPrintGridlines()
2987      {
2988          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
2989          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
2990  
2991          // move stream pointer to next record

2992          $this->_pos += 4 + $length;
2993  
2994          if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) {
2995              // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines

2996              $printGridlines = (bool) self::_GetInt2d($recordData, 0);
2997              $this->_phpSheet->setPrintGridlines($printGridlines);
2998          }
2999      }
3000  
3001  
3002      /**

3003       * Read DEFAULTROWHEIGHT record

3004       */
3005  	private function _readDefaultRowHeight()
3006      {
3007          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3008          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3009  
3010          // move stream pointer to next record

3011          $this->_pos += 4 + $length;
3012  
3013          // offset: 0; size: 2; option flags

3014          // offset: 2; size: 2; default height for unused rows, (twips 1/20 point)

3015          $height = self::_GetInt2d($recordData, 2);
3016          $this->_phpSheet->getDefaultRowDimension()->setRowHeight($height / 20);
3017      }
3018  
3019  
3020      /**

3021       * Read SHEETPR record

3022       */
3023  	private function _readSheetPr()
3024      {
3025          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3026          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3027  
3028          // move stream pointer to next record

3029          $this->_pos += 4 + $length;
3030  
3031          // offset: 0; size: 2

3032  
3033          // bit: 6; mask: 0x0040; 0 = outline buttons above outline group

3034          $isSummaryBelow = (0x0040 & self::_GetInt2d($recordData, 0)) >> 6;
3035          $this->_phpSheet->setShowSummaryBelow($isSummaryBelow);
3036  
3037          // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group

3038          $isSummaryRight = (0x0080 & self::_GetInt2d($recordData, 0)) >> 7;
3039          $this->_phpSheet->setShowSummaryRight($isSummaryRight);
3040  
3041          // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages

3042          // this corresponds to radio button setting in page setup dialog in Excel

3043          $this->_isFitToPages = (bool) ((0x0100 & self::_GetInt2d($recordData, 0)) >> 8);
3044      }
3045  
3046  
3047      /**

3048       * Read HORIZONTALPAGEBREAKS record

3049       */
3050  	private function _readHorizontalPageBreaks()
3051      {
3052          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3053          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3054  
3055          // move stream pointer to next record

3056          $this->_pos += 4 + $length;
3057  
3058          if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) {
3059  
3060              // offset: 0; size: 2; number of the following row index structures

3061              $nm = self::_GetInt2d($recordData, 0);
3062  
3063              // offset: 2; size: 6 * $nm; list of $nm row index structures

3064              for ($i = 0; $i < $nm; ++$i) {
3065                  $r = self::_GetInt2d($recordData, 2 + 6 * $i);
3066                  $cf = self::_GetInt2d($recordData, 2 + 6 * $i + 2);
3067                  $cl = self::_GetInt2d($recordData, 2 + 6 * $i + 4);
3068  
3069                  // not sure why two column indexes are necessary?

3070                  $this->_phpSheet->setBreakByColumnAndRow($cf, $r, PHPExcel_Worksheet::BREAK_ROW);
3071              }
3072          }
3073      }
3074  
3075  
3076      /**

3077       * Read VERTICALPAGEBREAKS record

3078       */
3079  	private function _readVerticalPageBreaks()
3080      {
3081          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3082          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3083  
3084          // move stream pointer to next record

3085          $this->_pos += 4 + $length;
3086  
3087          if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) {
3088              // offset: 0; size: 2; number of the following column index structures

3089              $nm = self::_GetInt2d($recordData, 0);
3090  
3091              // offset: 2; size: 6 * $nm; list of $nm row index structures

3092              for ($i = 0; $i < $nm; ++$i) {
3093                  $c = self::_GetInt2d($recordData, 2 + 6 * $i);
3094                  $rf = self::_GetInt2d($recordData, 2 + 6 * $i + 2);
3095                  $rl = self::_GetInt2d($recordData, 2 + 6 * $i + 4);
3096  
3097                  // not sure why two row indexes are necessary?

3098                  $this->_phpSheet->setBreakByColumnAndRow($c, $rf, PHPExcel_Worksheet::BREAK_COLUMN);
3099              }
3100          }
3101      }
3102  
3103  
3104      /**

3105       * Read HEADER record

3106       */
3107  	private function _readHeader()
3108      {
3109          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3110          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3111  
3112          // move stream pointer to next record

3113          $this->_pos += 4 + $length;
3114  
3115          if (!$this->_readDataOnly) {
3116              // offset: 0; size: var

3117              // realized that $recordData can be empty even when record exists

3118              if ($recordData) {
3119                  if ($this->_version == self::XLS_BIFF8) {
3120                      $string = self::_readUnicodeStringLong($recordData);
3121                  } else {
3122                      $string = $this->_readByteStringShort($recordData);
3123                  }
3124  
3125                  $this->_phpSheet->getHeaderFooter()->setOddHeader($string['value']);
3126                  $this->_phpSheet->getHeaderFooter()->setEvenHeader($string['value']);
3127              }
3128          }
3129      }
3130  
3131  
3132      /**

3133       * Read FOOTER record

3134       */
3135  	private function _readFooter()
3136      {
3137          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3138          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3139  
3140          // move stream pointer to next record

3141          $this->_pos += 4 + $length;
3142  
3143          if (!$this->_readDataOnly) {
3144              // offset: 0; size: var

3145              // realized that $recordData can be empty even when record exists

3146              if ($recordData) {
3147                  if ($this->_version == self::XLS_BIFF8) {
3148                      $string = self::_readUnicodeStringLong($recordData);
3149                  } else {
3150                      $string = $this->_readByteStringShort($recordData);
3151                  }
3152                  $this->_phpSheet->getHeaderFooter()->setOddFooter($string['value']);
3153                  $this->_phpSheet->getHeaderFooter()->setEvenFooter($string['value']);
3154              }
3155          }
3156      }
3157  
3158  
3159      /**

3160       * Read HCENTER record

3161       */
3162  	private function _readHcenter()
3163      {
3164          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3165          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3166  
3167          // move stream pointer to next record

3168          $this->_pos += 4 + $length;
3169  
3170          if (!$this->_readDataOnly) {
3171              // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally

3172              $isHorizontalCentered = (bool) self::_GetInt2d($recordData, 0);
3173  
3174              $this->_phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered);
3175          }
3176      }
3177  
3178  
3179      /**

3180       * Read VCENTER record

3181       */
3182  	private function _readVcenter()
3183      {
3184          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3185          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3186  
3187          // move stream pointer to next record

3188          $this->_pos += 4 + $length;
3189  
3190          if (!$this->_readDataOnly) {
3191              // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered

3192              $isVerticalCentered = (bool) self::_GetInt2d($recordData, 0);
3193  
3194              $this->_phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered);
3195          }
3196      }
3197  
3198  
3199      /**

3200       * Read LEFTMARGIN record

3201       */
3202  	private function _readLeftMargin()
3203      {
3204          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3205          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3206  
3207          // move stream pointer to next record

3208          $this->_pos += 4 + $length;
3209  
3210          if (!$this->_readDataOnly) {
3211              // offset: 0; size: 8

3212              $this->_phpSheet->getPageMargins()->setLeft(self::_extractNumber($recordData));
3213          }
3214      }
3215  
3216  
3217      /**

3218       * Read RIGHTMARGIN record

3219       */
3220  	private function _readRightMargin()
3221      {
3222          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3223          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3224  
3225          // move stream pointer to next record

3226          $this->_pos += 4 + $length;
3227  
3228          if (!$this->_readDataOnly) {
3229              // offset: 0; size: 8

3230              $this->_phpSheet->getPageMargins()->setRight(self::_extractNumber($recordData));
3231          }
3232      }
3233  
3234  
3235      /**

3236       * Read TOPMARGIN record

3237       */
3238  	private function _readTopMargin()
3239      {
3240          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3241          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3242  
3243          // move stream pointer to next record

3244          $this->_pos += 4 + $length;
3245  
3246          if (!$this->_readDataOnly) {
3247              // offset: 0; size: 8

3248              $this->_phpSheet->getPageMargins()->setTop(self::_extractNumber($recordData));
3249          }
3250      }
3251  
3252  
3253      /**

3254       * Read BOTTOMMARGIN record

3255       */
3256  	private function _readBottomMargin()
3257      {
3258          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3259          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3260  
3261          // move stream pointer to next record

3262          $this->_pos += 4 + $length;
3263  
3264          if (!$this->_readDataOnly) {
3265              // offset: 0; size: 8

3266              $this->_phpSheet->getPageMargins()->setBottom(self::_extractNumber($recordData));
3267          }
3268      }
3269  
3270  
3271      /**

3272       * Read PAGESETUP record

3273       */
3274  	private function _readPageSetup()
3275      {
3276          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3277          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3278  
3279          // move stream pointer to next record

3280          $this->_pos += 4 + $length;
3281  
3282          if (!$this->_readDataOnly) {
3283              // offset: 0; size: 2; paper size

3284              $paperSize = self::_GetInt2d($recordData, 0);
3285  
3286              // offset: 2; size: 2; scaling factor

3287              $scale = self::_GetInt2d($recordData, 2);
3288  
3289              // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed

3290              $fitToWidth = self::_GetInt2d($recordData, 6);
3291  
3292              // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed

3293              $fitToHeight = self::_GetInt2d($recordData, 8);
3294  
3295              // offset: 10; size: 2; option flags

3296  
3297                  // bit: 1; mask: 0x0002; 0=landscape, 1=portrait

3298                  $isPortrait = (0x0002 & self::_GetInt2d($recordData, 10)) >> 1;
3299  
3300                  // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init

3301                  // when this bit is set, do not use flags for those properties

3302                  $isNotInit = (0x0004 & self::_GetInt2d($recordData, 10)) >> 2;
3303  
3304              if (!$isNotInit) {
3305                  $this->_phpSheet->getPageSetup()->setPaperSize($paperSize);
3306                  switch ($isPortrait) {
3307                  case 0: $this->_phpSheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_LANDSCAPE); break;
3308                  case 1: $this->_phpSheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_PORTRAIT); break;
3309                  }
3310  
3311                  $this->_phpSheet->getPageSetup()->setScale($scale, false);
3312                  $this->_phpSheet->getPageSetup()->setFitToPage((bool) $this->_isFitToPages);
3313                  $this->_phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false);
3314                  $this->_phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false);
3315              }
3316  
3317              // offset: 16; size: 8; header margin (IEEE 754 floating-point value)

3318              $marginHeader = self::_extractNumber(substr($recordData, 16, 8));
3319              $this->_phpSheet->getPageMargins()->setHeader($marginHeader);
3320  
3321              // offset: 24; size: 8; footer margin (IEEE 754 floating-point value)

3322              $marginFooter = self::_extractNumber(substr($recordData, 24, 8));
3323              $this->_phpSheet->getPageMargins()->setFooter($marginFooter);
3324          }
3325      }
3326  
3327  
3328      /**

3329       * PROTECT - Sheet protection (BIFF2 through BIFF8)

3330       *   if this record is omitted, then it also means no sheet protection

3331       */
3332  	private function _readProtect()
3333      {
3334          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3335          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3336  
3337          // move stream pointer to next record

3338          $this->_pos += 4 + $length;
3339  
3340          if ($this->_readDataOnly) {
3341              return;
3342          }
3343  
3344          // offset: 0; size: 2;

3345  
3346          // bit 0, mask 0x01; 1 = sheet is protected

3347          $bool = (0x01 & self::_GetInt2d($recordData, 0)) >> 0;
3348          $this->_phpSheet->getProtection()->setSheet((bool)$bool);
3349      }
3350  
3351  
3352      /**

3353       * SCENPROTECT

3354       */
3355  	private function _readScenProtect()
3356      {
3357          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3358          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3359  
3360          // move stream pointer to next record

3361          $this->_pos += 4 + $length;
3362  
3363          if ($this->_readDataOnly) {
3364              return;
3365          }
3366  
3367          // offset: 0; size: 2;

3368  
3369          // bit: 0, mask 0x01; 1 = scenarios are protected

3370          $bool = (0x01 & self::_GetInt2d($recordData, 0)) >> 0;
3371  
3372          $this->_phpSheet->getProtection()->setScenarios((bool)$bool);
3373      }
3374  
3375  
3376      /**

3377       * OBJECTPROTECT

3378       */
3379  	private function _readObjectProtect()
3380      {
3381          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3382          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3383  
3384          // move stream pointer to next record

3385          $this->_pos += 4 + $length;
3386  
3387          if ($this->_readDataOnly) {
3388              return;
3389          }
3390  
3391          // offset: 0; size: 2;

3392  
3393          // bit: 0, mask 0x01; 1 = objects are protected

3394          $bool = (0x01 & self::_GetInt2d($recordData, 0)) >> 0;
3395  
3396          $this->_phpSheet->getProtection()->setObjects((bool)$bool);
3397      }
3398  
3399  
3400      /**

3401       * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8)

3402       */
3403  	private function _readPassword()
3404      {
3405          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3406          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3407  
3408          // move stream pointer to next record

3409          $this->_pos += 4 + $length;
3410  
3411          if (!$this->_readDataOnly) {
3412              // offset: 0; size: 2; 16-bit hash value of password

3413              $password = strtoupper(dechex(self::_GetInt2d($recordData, 0))); // the hashed password

3414              $this->_phpSheet->getProtection()->setPassword($password, true);
3415          }
3416      }
3417  
3418  
3419      /**

3420       * Read DEFCOLWIDTH record

3421       */
3422  	private function _readDefColWidth()
3423      {
3424          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3425          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3426  
3427          // move stream pointer to next record

3428          $this->_pos += 4 + $length;
3429  
3430          // offset: 0; size: 2; default column width

3431          $width = self::_GetInt2d($recordData, 0);
3432          if ($width != 8) {
3433              $this->_phpSheet->getDefaultColumnDimension()->setWidth($width);
3434          }
3435      }
3436  
3437  
3438      /**

3439       * Read COLINFO record

3440       */
3441  	private function _readColInfo()
3442      {
3443          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3444          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3445  
3446          // move stream pointer to next record

3447          $this->_pos += 4 + $length;
3448  
3449          if (!$this->_readDataOnly) {
3450              // offset: 0; size: 2; index to first column in range

3451              $fc = self::_GetInt2d($recordData, 0); // first column index

3452  
3453              // offset: 2; size: 2; index to last column in range

3454              $lc = self::_GetInt2d($recordData, 2); // first column index

3455  
3456              // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character

3457              $width = self::_GetInt2d($recordData, 4);
3458  
3459              // offset: 6; size: 2; index to XF record for default column formatting

3460              $xfIndex = self::_GetInt2d($recordData, 6);
3461  
3462              // offset: 8; size: 2; option flags

3463  
3464                  // bit: 0; mask: 0x0001; 1= columns are hidden

3465                  $isHidden = (0x0001 & self::_GetInt2d($recordData, 8)) >> 0;
3466  
3467                  // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline)

3468                  $level = (0x0700 & self::_GetInt2d($recordData, 8)) >> 8;
3469  
3470                  // bit: 12; mask: 0x1000; 1 = collapsed

3471                  $isCollapsed = (0x1000 & self::_GetInt2d($recordData, 8)) >> 12;
3472  
3473              // offset: 10; size: 2; not used

3474  
3475              for ($i = $fc; $i <= $lc; ++$i) {
3476                  if ($lc == 255 || $lc == 256) {
3477                      $this->_phpSheet->getDefaultColumnDimension()->setWidth($width / 256);
3478                      break;
3479                  }
3480                  $this->_phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256);
3481                  $this->_phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden);
3482                  $this->_phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level);
3483                  $this->_phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed);
3484                  $this->_phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3485              }
3486          }
3487      }
3488  
3489  
3490      /**

3491       * ROW

3492       *

3493       * This record contains the properties of a single row in a

3494       * sheet. Rows and cells in a sheet are divided into blocks

3495       * of 32 rows.

3496       *

3497       * --    "OpenOffice.org's Documentation of the Microsoft

3498       *         Excel File Format"

3499       */
3500  	private function _readRow()
3501      {
3502          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3503          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3504  
3505          // move stream pointer to next record

3506          $this->_pos += 4 + $length;
3507  
3508          if (!$this->_readDataOnly) {
3509              // offset: 0; size: 2; index of this row

3510              $r = self::_GetInt2d($recordData, 0);
3511  
3512              // offset: 2; size: 2; index to column of the first cell which is described by a cell record

3513  
3514              // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1

3515  
3516              // offset: 6; size: 2;

3517  
3518              // bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point

3519              $height = (0x7FFF & self::_GetInt2d($recordData, 6)) >> 0;
3520  
3521              // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height

3522              $useDefaultHeight = (0x8000 & self::_GetInt2d($recordData, 6)) >> 15;
3523  
3524              if (!$useDefaultHeight) {
3525                  $this->_phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
3526              }
3527  
3528              // offset: 8; size: 2; not used

3529  
3530              // offset: 10; size: 2; not used in BIFF5-BIFF8

3531  
3532              // offset: 12; size: 4; option flags and default row formatting

3533  
3534              // bit: 2-0: mask: 0x00000007; outline level of the row

3535              $level = (0x00000007 & self::_GetInt4d($recordData, 12)) >> 0;
3536              $this->_phpSheet->getRowDimension($r + 1)->setOutlineLevel($level);
3537  
3538              // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed

3539              $isCollapsed = (0x00000010 & self::_GetInt4d($recordData, 12)) >> 4;
3540              $this->_phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed);
3541  
3542              // bit: 5; mask: 0x00000020; 1 = row is hidden

3543              $isHidden = (0x00000020 & self::_GetInt4d($recordData, 12)) >> 5;
3544              $this->_phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden);
3545  
3546              // bit: 7; mask: 0x00000080; 1 = row has explicit format

3547              $hasExplicitFormat = (0x00000080 & self::_GetInt4d($recordData, 12)) >> 7;
3548  
3549              // bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record

3550              $xfIndex = (0x0FFF0000 & self::_GetInt4d($recordData, 12)) >> 16;
3551  
3552              if ($hasExplicitFormat) {
3553                  $this->_phpSheet->getRowDimension($r + 1)->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3554              }
3555          }
3556      }
3557  
3558  
3559      /**

3560       * Read RK record

3561       * This record represents a cell that contains an RK value

3562       * (encoded integer or floating-point value). If a

3563       * floating-point value cannot be encoded to an RK value,

3564       * a NUMBER record will be written. This record replaces the

3565       * record INTEGER written in BIFF2.

3566       *

3567       * --    "OpenOffice.org's Documentation of the Microsoft

3568       *         Excel File Format"

3569       */
3570  	private function _readRk()
3571      {
3572          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3573          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3574  
3575          // move stream pointer to next record

3576          $this->_pos += 4 + $length;
3577  
3578          // offset: 0; size: 2; index to row

3579          $row = self::_GetInt2d($recordData, 0);
3580  
3581          // offset: 2; size: 2; index to column

3582          $column = self::_GetInt2d($recordData, 2);
3583          $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3584  
3585          // Read cell?

3586          if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3587              // offset: 4; size: 2; index to XF record

3588              $xfIndex = self::_GetInt2d($recordData, 4);
3589  
3590              // offset: 6; size: 4; RK value

3591              $rknum = self::_GetInt4d($recordData, 6);
3592              $numValue = self::_GetIEEE754($rknum);
3593  
3594              $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3595              if (!$this->_readDataOnly) {
3596                  // add style information

3597                  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3598              }
3599  
3600              // add cell

3601              $cell->setValueExplicit($numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC);
3602          }
3603      }
3604  
3605  
3606      /**

3607       * Read LABELSST record

3608       * This record represents a cell that contains a string. It

3609       * replaces the LABEL record and RSTRING record used in

3610       * BIFF2-BIFF5.

3611       *

3612       * --    "OpenOffice.org's Documentation of the Microsoft

3613       *         Excel File Format"

3614       */
3615  	private function _readLabelSst()
3616      {
3617          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3618          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3619  
3620          // move stream pointer to next record

3621          $this->_pos += 4 + $length;
3622  
3623          // offset: 0; size: 2; index to row

3624          $row = self::_GetInt2d($recordData, 0);
3625  
3626          // offset: 2; size: 2; index to column

3627          $column = self::_GetInt2d($recordData, 2);
3628          $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3629  
3630          // Read cell?

3631          if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3632              // offset: 4; size: 2; index to XF record

3633              $xfIndex = self::_GetInt2d($recordData, 4);
3634  
3635              // offset: 6; size: 4; index to SST record

3636              $index = self::_GetInt4d($recordData, 6);
3637  
3638              // add cell

3639              if (($fmtRuns = $this->_sst[$index]['fmtRuns']) && !$this->_readDataOnly) {
3640                  // then we should treat as rich text

3641                  $richText = new PHPExcel_RichText();
3642                  $charPos = 0;
3643                  $sstCount = count($this->_sst[$index]['fmtRuns']);
3644                  for ($i = 0; $i <= $sstCount; ++$i) {
3645                      if (isset($fmtRuns[$i])) {
3646                          $text = PHPExcel_Shared_String::Substring($this->_sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos);
3647                          $charPos = $fmtRuns[$i]['charPos'];
3648                      } else {
3649                          $text = PHPExcel_Shared_String::Substring($this->_sst[$index]['value'], $charPos, PHPExcel_Shared_String::CountCharacters($this->_sst[$index]['value']));
3650                      }
3651  
3652                      if (PHPExcel_Shared_String::CountCharacters($text) > 0) {
3653                          if ($i == 0) { // first text run, no style
3654                              $richText->createText($text);
3655                          } else {
3656                              $textRun = $richText->createTextRun($text);
3657                              if (isset($fmtRuns[$i - 1])) {
3658                                  if ($fmtRuns[$i - 1]['fontIndex'] < 4) {
3659                                      $fontIndex = $fmtRuns[$i - 1]['fontIndex'];
3660                                  } else {
3661                                      // this has to do with that index 4 is omitted in all BIFF versions for some strange reason

3662                                      // check the OpenOffice documentation of the FONT record

3663                                      $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1;
3664                                  }
3665                                  $textRun->setFont(clone $this->_objFonts[$fontIndex]);
3666                              }
3667                          }
3668                      }
3669                  }
3670                  $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3671                  $cell->setValueExplicit($richText, PHPExcel_Cell_DataType::TYPE_STRING);
3672              } else {
3673                  $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3674                  $cell->setValueExplicit($this->_sst[$index]['value'], PHPExcel_Cell_DataType::TYPE_STRING);
3675              }
3676  
3677              if (!$this->_readDataOnly) {
3678                  // add style information

3679                  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3680              }
3681          }
3682      }
3683  
3684  
3685      /**

3686       * Read MULRK record

3687       * This record represents a cell range containing RK value

3688       * cells. All cells are located in the same row.

3689       *

3690       * --    "OpenOffice.org's Documentation of the Microsoft

3691       *         Excel File Format"

3692       */
3693  	private function _readMulRk()
3694      {
3695          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3696          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3697  
3698          // move stream pointer to next record

3699          $this->_pos += 4 + $length;
3700  
3701          // offset: 0; size: 2; index to row

3702          $row = self::_GetInt2d($recordData, 0);
3703  
3704          // offset: 2; size: 2; index to first column

3705          $colFirst = self::_GetInt2d($recordData, 2);
3706  
3707          // offset: var; size: 2; index to last column

3708          $colLast = self::_GetInt2d($recordData, $length - 2);
3709          $columns = $colLast - $colFirst + 1;
3710  
3711          // offset within record data

3712          $offset = 4;
3713  
3714          for ($i = 0; $i < $columns; ++$i) {
3715              $columnString = PHPExcel_Cell::stringFromColumnIndex($colFirst + $i);
3716  
3717              // Read cell?

3718              if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3719  
3720                  // offset: var; size: 2; index to XF record

3721                  $xfIndex = self::_GetInt2d($recordData, $offset);
3722  
3723                  // offset: var; size: 4; RK value

3724                  $numValue = self::_GetIEEE754(self::_GetInt4d($recordData, $offset + 2));
3725                  $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3726                  if (!$this->_readDataOnly) {
3727                      // add style

3728                      $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3729                  }
3730  
3731                  // add cell value

3732                  $cell->setValueExplicit($numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC);
3733              }
3734  
3735              $offset += 6;
3736          }
3737      }
3738  
3739  
3740      /**

3741       * Read NUMBER record

3742       * This record represents a cell that contains a

3743       * floating-point value.

3744       *

3745       * --    "OpenOffice.org's Documentation of the Microsoft

3746       *         Excel File Format"

3747       */
3748  	private function _readNumber()
3749      {
3750          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3751          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3752  
3753          // move stream pointer to next record

3754          $this->_pos += 4 + $length;
3755  
3756          // offset: 0; size: 2; index to row

3757          $row = self::_GetInt2d($recordData, 0);
3758  
3759          // offset: 2; size 2; index to column

3760          $column = self::_GetInt2d($recordData, 2);
3761          $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3762  
3763          // Read cell?

3764          if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3765              // offset 4; size: 2; index to XF record

3766              $xfIndex = self::_GetInt2d($recordData, 4);
3767  
3768              $numValue = self::_extractNumber(substr($recordData, 6, 8));
3769  
3770              $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3771              if (!$this->_readDataOnly) {
3772                  // add cell style

3773                  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3774              }
3775  
3776              // add cell value

3777              $cell->setValueExplicit($numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC);
3778          }
3779      }
3780  
3781  
3782      /**

3783       * Read FORMULA record + perhaps a following STRING record if formula result is a string

3784       * This record contains the token array and the result of a

3785       * formula cell.

3786       *

3787       * --    "OpenOffice.org's Documentation of the Microsoft

3788       *         Excel File Format"

3789       */
3790  	private function _readFormula()
3791      {
3792          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3793          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3794  
3795          // move stream pointer to next record

3796          $this->_pos += 4 + $length;
3797  
3798          // offset: 0; size: 2; row index

3799          $row = self::_GetInt2d($recordData, 0);
3800  
3801          // offset: 2; size: 2; col index

3802          $column = self::_GetInt2d($recordData, 2);
3803          $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3804  
3805          // offset: 20: size: variable; formula structure

3806          $formulaStructure = substr($recordData, 20);
3807  
3808          // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc.

3809          $options = self::_GetInt2d($recordData, 14);
3810  
3811          // bit: 0; mask: 0x0001; 1 = recalculate always

3812          // bit: 1; mask: 0x0002; 1 = calculate on open

3813          // bit: 2; mask: 0x0008; 1 = part of a shared formula

3814          $isPartOfSharedFormula = (bool) (0x0008 & $options);
3815  
3816          // WARNING:

3817          // We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true

3818          // the formula data may be ordinary formula data, therefore we need to check

3819          // explicitly for the tExp token (0x01)

3820          $isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure{2}) == 0x01;
3821  
3822          if ($isPartOfSharedFormula) {
3823              // part of shared formula which means there will be a formula with a tExp token and nothing else

3824              // get the base cell, grab tExp token

3825              $baseRow = self::_GetInt2d($formulaStructure, 3);
3826              $baseCol = self::_GetInt2d($formulaStructure, 5);
3827              $this->_baseCell = PHPExcel_Cell::stringFromColumnIndex($baseCol). ($baseRow + 1);
3828          }
3829  
3830          // Read cell?

3831          if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
3832  
3833              if ($isPartOfSharedFormula) {
3834                  // formula is added to this cell after the sheet has been read

3835                  $this->_sharedFormulaParts[$columnString . ($row + 1)] = $this->_baseCell;
3836              }
3837  
3838              // offset: 16: size: 4; not used

3839  
3840              // offset: 4; size: 2; XF index

3841              $xfIndex = self::_GetInt2d($recordData, 4);
3842  
3843              // offset: 6; size: 8; result of the formula

3844              if ( (ord($recordData{6}) == 0)
3845                  && (ord($recordData{12}) == 255)
3846                  && (ord($recordData{13}) == 255) ) {
3847  
3848                  // String formula. Result follows in appended STRING record

3849                  $dataType = PHPExcel_Cell_DataType::TYPE_STRING;
3850  
3851                  // read possible SHAREDFMLA record

3852                  $code = self::_GetInt2d($this->_data, $this->_pos);
3853                  if ($code == self::XLS_Type_SHAREDFMLA) {
3854                      $this->_readSharedFmla();
3855                  }
3856  
3857                  // read STRING record

3858                  $value = $this->_readString();
3859  
3860              } elseif ((ord($recordData{6}) == 1)
3861                  && (ord($recordData{12}) == 255)
3862                  && (ord($recordData{13}) == 255)) {
3863  
3864                  // Boolean formula. Result is in +2; 0=false, 1=true

3865                  $dataType = PHPExcel_Cell_DataType::TYPE_BOOL;
3866                  $value = (bool) ord($recordData{8});
3867  
3868              } elseif ((ord($recordData{6}) == 2)
3869                  && (ord($recordData{12}) == 255)
3870                  && (ord($recordData{13}) == 255)) {
3871  
3872                  // Error formula. Error code is in +2

3873                  $dataType = PHPExcel_Cell_DataType::TYPE_ERROR;
3874                  $value = self::_mapErrorCode(ord($recordData{8}));
3875  
3876              } elseif ((ord($recordData{6}) == 3)
3877                  && (ord($recordData{12}) == 255)
3878                  && (ord($recordData{13}) == 255)) {
3879  
3880                  // Formula result is a null string

3881                  $dataType = PHPExcel_Cell_DataType::TYPE_NULL;
3882                  $value = '';
3883  
3884              } else {
3885  
3886                  // forumla result is a number, first 14 bytes like _NUMBER record

3887                  $dataType = PHPExcel_Cell_DataType::TYPE_NUMERIC;
3888                  $value = self::_extractNumber(substr($recordData, 6, 8));
3889  
3890              }
3891  
3892              $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
3893              if (!$this->_readDataOnly) {
3894                  // add cell style

3895                  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
3896              }
3897  
3898              // store the formula

3899              if (!$isPartOfSharedFormula) {
3900                  // not part of shared formula

3901                  // add cell value. If we can read formula, populate with formula, otherwise just used cached value

3902                  try {
3903                      if ($this->_version != self::XLS_BIFF8) {
3904                          throw new PHPExcel_Reader_Exception('Not BIFF8. Can only read BIFF8 formulas');
3905                      }
3906                      $formula = $this->_getFormulaFromStructure($formulaStructure); // get formula in human language

3907                      $cell->setValueExplicit('=' . $formula, PHPExcel_Cell_DataType::TYPE_FORMULA);
3908  
3909                  } catch (PHPExcel_Exception $e) {
3910                      $cell->setValueExplicit($value, $dataType);
3911                  }
3912              } else {
3913                  if ($this->_version == self::XLS_BIFF8) {
3914                      // do nothing at this point, formula id added later in the code

3915                  } else {
3916                      $cell->setValueExplicit($value, $dataType);
3917                  }
3918              }
3919  
3920              // store the cached calculated value

3921              $cell->setCalculatedValue($value);
3922          }
3923      }
3924  
3925  
3926      /**

3927       * Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader,

3928       * which usually contains relative references.

3929       * These will be used to construct the formula in each shared formula part after the sheet is read.

3930       */
3931  	private function _readSharedFmla()
3932      {
3933          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3934          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3935  
3936          // move stream pointer to next record

3937          $this->_pos += 4 + $length;
3938  
3939          // offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything

3940          $cellRange = substr($recordData, 0, 6);
3941          $cellRange = $this->_readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax

3942  
3943          // offset: 6, size: 1; not used

3944  
3945          // offset: 7, size: 1; number of existing FORMULA records for this shared formula

3946          $no = ord($recordData{7});
3947  
3948          // offset: 8, size: var; Binary token array of the shared formula

3949          $formula = substr($recordData, 8);
3950  
3951          // at this point we only store the shared formula for later use

3952          $this->_sharedFormulas[$this->_baseCell] = $formula;
3953  
3954      }
3955  
3956  
3957      /**

3958       * Read a STRING record from current stream position and advance the stream pointer to next record

3959       * This record is used for storing result from FORMULA record when it is a string, and

3960       * it occurs directly after the FORMULA record

3961       *

3962       * @return string The string contents as UTF-8

3963       */
3964  	private function _readString()
3965      {
3966          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3967          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3968  
3969          // move stream pointer to next record

3970          $this->_pos += 4 + $length;
3971  
3972          if ($this->_version == self::XLS_BIFF8) {
3973              $string = self::_readUnicodeStringLong($recordData);
3974              $value = $string['value'];
3975          } else {
3976              $string = $this->_readByteStringLong($recordData);
3977              $value = $string['value'];
3978          }
3979  
3980          return $value;
3981      }
3982  
3983  
3984      /**

3985       * Read BOOLERR record

3986       * This record represents a Boolean value or error value

3987       * cell.

3988       *

3989       * --    "OpenOffice.org's Documentation of the Microsoft

3990       *         Excel File Format"

3991       */
3992  	private function _readBoolErr()
3993      {
3994          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
3995          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
3996  
3997          // move stream pointer to next record

3998          $this->_pos += 4 + $length;
3999  
4000          // offset: 0; size: 2; row index

4001          $row = self::_GetInt2d($recordData, 0);
4002  
4003          // offset: 2; size: 2; column index

4004          $column = self::_GetInt2d($recordData, 2);
4005          $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
4006  
4007          // Read cell?

4008          if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
4009              // offset: 4; size: 2; index to XF record

4010              $xfIndex = self::_GetInt2d($recordData, 4);
4011  
4012              // offset: 6; size: 1; the boolean value or error value

4013              $boolErr = ord($recordData{6});
4014  
4015              // offset: 7; size: 1; 0=boolean; 1=error

4016              $isError = ord($recordData{7});
4017  
4018              $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
4019              switch ($isError) {
4020                  case 0: // boolean
4021                      $value = (bool) $boolErr;
4022  
4023                      // add cell value

4024                      $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_BOOL);
4025                      break;
4026  
4027                  case 1: // error type
4028                      $value = self::_mapErrorCode($boolErr);
4029  
4030                      // add cell value

4031                      $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_ERROR);
4032                      break;
4033              }
4034  
4035              if (!$this->_readDataOnly) {
4036                  // add cell style

4037                  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
4038              }
4039          }
4040      }
4041  
4042  
4043      /**

4044       * Read MULBLANK record

4045       * This record represents a cell range of empty cells. All

4046       * cells are located in the same row

4047       *

4048       * --    "OpenOffice.org's Documentation of the Microsoft

4049       *         Excel File Format"

4050       */
4051  	private function _readMulBlank()
4052      {
4053          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4054          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4055  
4056          // move stream pointer to next record

4057          $this->_pos += 4 + $length;
4058  
4059          // offset: 0; size: 2; index to row

4060          $row = self::_GetInt2d($recordData, 0);
4061  
4062          // offset: 2; size: 2; index to first column

4063          $fc = self::_GetInt2d($recordData, 2);
4064  
4065          // offset: 4; size: 2 x nc; list of indexes to XF records

4066          // add style information

4067          if (!$this->_readDataOnly) {
4068              for ($i = 0; $i < $length / 2 - 3; ++$i) {
4069                  $columnString = PHPExcel_Cell::stringFromColumnIndex($fc + $i);
4070  
4071                  // Read cell?

4072                  if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
4073                      $xfIndex = self::_GetInt2d($recordData, 4 + 2 * $i);
4074                      $this->_phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
4075                  }
4076              }
4077          }
4078  
4079          // offset: 6; size 2; index to last column (not needed)

4080      }
4081  
4082  
4083      /**

4084       * Read LABEL record

4085       * This record represents a cell that contains a string. In

4086       * BIFF8 it is usually replaced by the LABELSST record.

4087       * Excel still uses this record, if it copies unformatted

4088       * text cells to the clipboard.

4089       *

4090       * --    "OpenOffice.org's Documentation of the Microsoft

4091       *         Excel File Format"

4092       */
4093  	private function _readLabel()
4094      {
4095          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4096          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4097  
4098          // move stream pointer to next record

4099          $this->_pos += 4 + $length;
4100  
4101          // offset: 0; size: 2; index to row

4102          $row = self::_GetInt2d($recordData, 0);
4103  
4104          // offset: 2; size: 2; index to column

4105          $column = self::_GetInt2d($recordData, 2);
4106          $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
4107  
4108          // Read cell?

4109          if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
4110              // offset: 4; size: 2; XF index

4111              $xfIndex = self::_GetInt2d($recordData, 4);
4112  
4113              // add cell value

4114              // todo: what if string is very long? continue record

4115              if ($this->_version == self::XLS_BIFF8) {
4116                  $string = self::_readUnicodeStringLong(substr($recordData, 6));
4117                  $value = $string['value'];
4118              } else {
4119                  $string = $this->_readByteStringLong(substr($recordData, 6));
4120                  $value = $string['value'];
4121              }
4122              $cell = $this->_phpSheet->getCell($columnString . ($row + 1));
4123              $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_STRING);
4124  
4125              if (!$this->_readDataOnly) {
4126                  // add cell style

4127                  $cell->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
4128              }
4129          }
4130      }
4131  
4132  
4133      /**

4134       * Read BLANK record

4135       */
4136  	private function _readBlank()
4137      {
4138          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4139          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4140  
4141          // move stream pointer to next record

4142          $this->_pos += 4 + $length;
4143  
4144          // offset: 0; size: 2; row index

4145          $row = self::_GetInt2d($recordData, 0);
4146  
4147          // offset: 2; size: 2; col index

4148          $col = self::_GetInt2d($recordData, 2);
4149          $columnString = PHPExcel_Cell::stringFromColumnIndex($col);
4150  
4151          // Read cell?

4152          if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->_phpSheet->getTitle()) ) {
4153              // offset: 4; size: 2; XF index

4154              $xfIndex = self::_GetInt2d($recordData, 4);
4155  
4156              // add style information

4157              if (!$this->_readDataOnly) {
4158                  $this->_phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->_mapCellXfIndex[$xfIndex]);
4159              }
4160          }
4161  
4162      }
4163  
4164  
4165      /**

4166       * Read MSODRAWING record

4167       */
4168  	private function _readMsoDrawing()
4169      {
4170          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4171  
4172          // get spliced record data

4173          $splicedRecordData = $this->_getSplicedRecordData();
4174          $recordData = $splicedRecordData['recordData'];
4175  
4176          $this->_drawingData .= $recordData;
4177      }
4178  
4179  
4180      /**

4181       * Read OBJ record

4182       */
4183  	private function _readObj()
4184      {
4185          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4186          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4187  
4188          // move stream pointer to next record

4189          $this->_pos += 4 + $length;
4190  
4191          if ($this->_readDataOnly || $this->_version != self::XLS_BIFF8) {
4192              return;
4193          }
4194  
4195          // recordData consists of an array of subrecords looking like this:

4196          //    ft: 2 bytes; ftCmo type (0x15)

4197          //    cb: 2 bytes; size in bytes of ftCmo data

4198          //    ot: 2 bytes; Object Type

4199          //    id: 2 bytes; Object id number

4200          //    grbit: 2 bytes; Option Flags

4201          //    data: var; subrecord data

4202  
4203          // for now, we are just interested in the second subrecord containing the object type

4204          $ftCmoType    = self::_GetInt2d($recordData, 0);
4205          $cbCmoSize    = self::_GetInt2d($recordData, 2);
4206          $otObjType    = self::_GetInt2d($recordData, 4);
4207          $idObjID    = self::_GetInt2d($recordData, 6);
4208          $grbitOpts    = self::_GetInt2d($recordData, 6);
4209  
4210          $this->_objs[] = array(
4211              'ftCmoType'    => $ftCmoType,
4212              'cbCmoSize'    => $cbCmoSize,
4213              'otObjType'    => $otObjType,
4214              'idObjID'    => $idObjID,
4215              'grbitOpts'    => $grbitOpts
4216          );
4217          $this->textObjRef = $idObjID;
4218  
4219  //        echo '<b>_readObj()</b><br />';

4220  //        var_dump(end($this->_objs));

4221  //        echo '<br />';

4222      }
4223  
4224  
4225      /**

4226       * Read WINDOW2 record

4227       */
4228  	private function _readWindow2()
4229      {
4230          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4231          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4232  
4233          // move stream pointer to next record

4234          $this->_pos += 4 + $length;
4235  
4236          // offset: 0; size: 2; option flags

4237          $options = self::_GetInt2d($recordData, 0);
4238  
4239          // offset: 2; size: 2; index to first visible row

4240          $firstVisibleRow = self::_GetInt2d($recordData, 2);
4241  
4242          // offset: 4; size: 2; index to first visible colum

4243          $firstVisibleColumn = self::_GetInt2d($recordData, 4);
4244          if ($this->_version === self::XLS_BIFF8) {
4245              // offset:  8; size: 2; not used

4246              // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%)

4247              // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%)

4248              // offset: 14; size: 4; not used

4249              $zoomscaleInPageBreakPreview = self::_GetInt2d($recordData, 10);
4250              if ($zoomscaleInPageBreakPreview === 0) $zoomscaleInPageBreakPreview = 60;
4251              $zoomscaleInNormalView = self::_GetInt2d($recordData, 12);
4252              if ($zoomscaleInNormalView === 0) $zoomscaleInNormalView = 100;
4253          }
4254  
4255          // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines

4256          $showGridlines = (bool) ((0x0002 & $options) >> 1);
4257          $this->_phpSheet->setShowGridlines($showGridlines);
4258  
4259          // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers

4260          $showRowColHeaders = (bool) ((0x0004 & $options) >> 2);
4261          $this->_phpSheet->setShowRowColHeaders($showRowColHeaders);
4262  
4263          // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen

4264          $this->_frozen = (bool) ((0x0008 & $options) >> 3);
4265  
4266          // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left

4267          $this->_phpSheet->setRightToLeft((bool)((0x0040 & $options) >> 6));
4268  
4269          // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active

4270          $isActive = (bool) ((0x0400 & $options) >> 10);
4271          if ($isActive) {
4272              $this->_phpExcel->setActiveSheetIndex($this->_phpExcel->getIndex($this->_phpSheet));
4273          }
4274  
4275          // bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view

4276          $isPageBreakPreview = (bool) ((0x0800 & $options) >> 11);
4277  
4278          //FIXME: set $firstVisibleRow and $firstVisibleColumn

4279  
4280          if ($this->_phpSheet->getSheetView()->getView() !== PHPExcel_Worksheet_SheetView::SHEETVIEW_PAGE_LAYOUT) {
4281              //NOTE: this setting is inferior to page layout view(Excel2007-)

4282              $view = $isPageBreakPreview? PHPExcel_Worksheet_SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW :
4283                  PHPExcel_Worksheet_SheetView::SHEETVIEW_NORMAL;
4284              $this->_phpSheet->getSheetView()->setView($view);
4285              if ($this->_version === self::XLS_BIFF8) {
4286                  $zoomScale = $isPageBreakPreview? $zoomscaleInPageBreakPreview : $zoomscaleInNormalView;
4287                  $this->_phpSheet->getSheetView()->setZoomScale($zoomScale);
4288                  $this->_phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView);
4289              }
4290          }
4291      }
4292  
4293      /**

4294       * Read PLV Record(Created by Excel2007 or upper)

4295       */
4296  	private function _readPageLayoutView(){
4297          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4298          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4299  
4300          // move stream pointer to next record

4301          $this->_pos += 4 + $length;
4302  
4303          //var_dump(unpack("vrt/vgrbitFrt/V2reserved/vwScalePLV/vgrbit", $recordData));

4304  
4305          // offset: 0; size: 2; rt

4306          //->ignore

4307          $rt = self::_GetInt2d($recordData, 0);
4308          // offset: 2; size: 2; grbitfr

4309          //->ignore

4310          $grbitFrt = self::_GetInt2d($recordData, 2);
4311          // offset: 4; size: 8; reserved

4312          //->ignore

4313  
4314          // offset: 12; size 2; zoom scale

4315          $wScalePLV = self::_GetInt2d($recordData, 12);
4316          // offset: 14; size 2; grbit

4317          $grbit = self::_GetInt2d($recordData, 14);
4318  
4319          // decomprise grbit

4320          $fPageLayoutView   = $grbit & 0x01;
4321          $fRulerVisible     = ($grbit >> 1) & 0x01; //no support

4322          $fWhitespaceHidden = ($grbit >> 3) & 0x01; //no support

4323  
4324          if ($fPageLayoutView === 1) {
4325              $this->_phpSheet->getSheetView()->setView(PHPExcel_Worksheet_SheetView::SHEETVIEW_PAGE_LAYOUT);
4326              $this->_phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT

4327          }
4328          //otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW.

4329      }
4330  
4331      /**

4332       * Read SCL record

4333       */
4334  	private function _readScl()
4335      {
4336          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4337          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4338  
4339          // move stream pointer to next record

4340          $this->_pos += 4 + $length;
4341  
4342          // offset: 0; size: 2; numerator of the view magnification

4343          $numerator = self::_GetInt2d($recordData, 0);
4344  
4345          // offset: 2; size: 2; numerator of the view magnification

4346          $denumerator = self::_GetInt2d($recordData, 2);
4347  
4348          // set the zoom scale (in percent)

4349          $this->_phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator);
4350      }
4351  
4352  
4353      /**

4354       * Read PANE record

4355       */
4356  	private function _readPane()
4357      {
4358          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4359          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4360  
4361          // move stream pointer to next record

4362          $this->_pos += 4 + $length;
4363  
4364          if (!$this->_readDataOnly) {
4365              // offset: 0; size: 2; position of vertical split

4366              $px = self::_GetInt2d($recordData, 0);
4367  
4368              // offset: 2; size: 2; position of horizontal split

4369              $py = self::_GetInt2d($recordData, 2);
4370  
4371              if ($this->_frozen) {
4372                  // frozen panes

4373                  $this->_phpSheet->freezePane(PHPExcel_Cell::stringFromColumnIndex($px) . ($py + 1));
4374              } else {
4375                  // unfrozen panes; split windows; not supported by PHPExcel core

4376              }
4377          }
4378      }
4379  
4380  
4381      /**

4382       * Read SELECTION record. There is one such record for each pane in the sheet.

4383       */
4384  	private function _readSelection()
4385      {
4386          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4387          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4388  
4389          // move stream pointer to next record

4390          $this->_pos += 4 + $length;
4391  
4392          if (!$this->_readDataOnly) {
4393              // offset: 0; size: 1; pane identifier

4394              $paneId = ord($recordData{0});
4395  
4396              // offset: 1; size: 2; index to row of the active cell

4397              $r = self::_GetInt2d($recordData, 1);
4398  
4399              // offset: 3; size: 2; index to column of the active cell

4400              $c = self::_GetInt2d($recordData, 3);
4401  
4402              // offset: 5; size: 2; index into the following cell range list to the

4403              //  entry that contains the active cell

4404              $index = self::_GetInt2d($recordData, 5);
4405  
4406              // offset: 7; size: var; cell range address list containing all selected cell ranges

4407              $data = substr($recordData, 7);
4408              $cellRangeAddressList = $this->_readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax

4409  
4410              $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0];
4411  
4412              // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!)

4413              if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) {
4414                  $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '$1}1048576', $selectedCells);
4415              }
4416  
4417              // first row '1' + last row '65536' indicates that full column is selected

4418              if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) {
4419                  $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '$1}1048576', $selectedCells);
4420              }
4421  
4422              // first column 'A' + last column 'IV' indicates that full row is selected

4423              if (preg_match('/^(A[0-9]+\:)IV([0-9]+)$/', $selectedCells)) {
4424                  $selectedCells = preg_replace('/^(A[0-9]+\:)IV([0-9]+)$/', '$1}XFD$2}', $selectedCells);
4425              }
4426  
4427              $this->_phpSheet->setSelectedCells($selectedCells);
4428          }
4429      }
4430  
4431  
4432  	private function _includeCellRangeFiltered($cellRangeAddress)
4433      {
4434          $includeCellRange = true;
4435          if ($this->getReadFilter() !== NULL) {
4436              $includeCellRange = false;
4437              $rangeBoundaries = PHPExcel_Cell::getRangeBoundaries($cellRangeAddress);
4438              $rangeBoundaries[1][0]++;
4439              for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; $row++) {
4440                  for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; $column++) {
4441                      if ($this->getReadFilter()->readCell($column, $row, $this->_phpSheet->getTitle())) {
4442                          $includeCellRange = true;
4443                          break 2;
4444                      }
4445                  }
4446              }
4447          }
4448          return $includeCellRange;
4449      }
4450  
4451  
4452      /**

4453       * MERGEDCELLS

4454       *

4455       * This record contains the addresses of merged cell ranges

4456       * in the current sheet.

4457       *

4458       * --    "OpenOffice.org's Documentation of the Microsoft

4459       *         Excel File Format"

4460       */
4461  	private function _readMergedCells()
4462      {
4463          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4464          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4465  
4466          // move stream pointer to next record

4467          $this->_pos += 4 + $length;
4468  
4469          if ($this->_version == self::XLS_BIFF8 && !$this->_readDataOnly) {
4470              $cellRangeAddressList = $this->_readBIFF8CellRangeAddressList($recordData);
4471              foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) {
4472                  if ((strpos($cellRangeAddress,':') !== FALSE) &&
4473                      ($this->_includeCellRangeFiltered($cellRangeAddress))) {
4474                      $this->_phpSheet->mergeCells($cellRangeAddress);
4475                  }
4476              }
4477          }
4478      }
4479  
4480  
4481      /**

4482       * Read HYPERLINK record

4483       */
4484  	private function _readHyperLink()
4485      {
4486          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4487          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4488  
4489          // move stream pointer forward to next record

4490          $this->_pos += 4 + $length;
4491  
4492          if (!$this->_readDataOnly) {
4493              // offset: 0; size: 8; cell range address of all cells containing this hyperlink

4494              try {
4495                  $cellRange = $this->_readBIFF8CellRangeAddressFixed($recordData, 0, 8);
4496              } catch (PHPExcel_Exception $e) {
4497                  return;
4498              }
4499  
4500              // offset: 8, size: 16; GUID of StdLink

4501  
4502              // offset: 24, size: 4; unknown value

4503  
4504              // offset: 28, size: 4; option flags

4505  
4506                  // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL

4507                  $isFileLinkOrUrl = (0x00000001 & self::_GetInt2d($recordData, 28)) >> 0;
4508  
4509                  // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL

4510                  $isAbsPathOrUrl = (0x00000001 & self::_GetInt2d($recordData, 28)) >> 1;
4511  
4512                  // bit: 2 (and 4); mask: 0x00000014; 0 = no description

4513                  $hasDesc = (0x00000014 & self::_GetInt2d($recordData, 28)) >> 2;
4514  
4515                  // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text

4516                  $hasText = (0x00000008 & self::_GetInt2d($recordData, 28)) >> 3;
4517  
4518                  // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame

4519                  $hasFrame = (0x00000080 & self::_GetInt2d($recordData, 28)) >> 7;
4520  
4521                  // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name)

4522                  $isUNC = (0x00000100 & self::_GetInt2d($recordData, 28)) >> 8;
4523  
4524              // offset within record data

4525              $offset = 32;
4526  
4527              if ($hasDesc) {
4528                  // offset: 32; size: var; character count of description text

4529                  $dl = self::_GetInt4d($recordData, 32);
4530                  // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated

4531                  $desc = self::_encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false);
4532                  $offset += 4 + 2 * $dl;
4533              }
4534              if ($hasFrame) {
4535                  $fl = self::_GetInt4d($recordData, $offset);
4536                  $offset += 4 + 2 * $fl;
4537              }
4538  
4539              // detect type of hyperlink (there are 4 types)

4540              $hyperlinkType = null;
4541  
4542              if ($isUNC) {
4543                  $hyperlinkType = 'UNC';
4544              } else if (!$isFileLinkOrUrl) {
4545                  $hyperlinkType = 'workbook';
4546              } else if (ord($recordData{$offset}) == 0x03) {
4547                  $hyperlinkType = 'local';
4548              } else if (ord($recordData{$offset}) == 0xE0) {
4549                  $hyperlinkType = 'URL';
4550              }
4551  
4552              switch ($hyperlinkType) {
4553              case 'URL':
4554                  // section 5.58.2: Hyperlink containing a URL

4555                  // e.g. http://example.org/index.php

4556  
4557                  // offset: var; size: 16; GUID of URL Moniker

4558                  $offset += 16;
4559                  // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word

4560                  $us = self::_GetInt4d($recordData, $offset);
4561                  $offset += 4;
4562                  // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated

4563                  $url = self::_encodeUTF16(substr($recordData, $offset, $us - 2), false);
4564                  $url .= $hasText ? '#' : '';
4565                  $offset += $us;
4566                  break;
4567  
4568              case 'local':
4569                  // section 5.58.3: Hyperlink to local file

4570                  // examples:

4571                  //   mydoc.txt

4572                  //   ../../somedoc.xls#Sheet!A1

4573  
4574                  // offset: var; size: 16; GUI of File Moniker

4575                  $offset += 16;
4576  
4577                  // offset: var; size: 2; directory up-level count.

4578                  $upLevelCount = self::_GetInt2d($recordData, $offset);
4579                  $offset += 2;
4580  
4581                  // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word

4582                  $sl = self::_GetInt4d($recordData, $offset);
4583                  $offset += 4;
4584  
4585                  // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string)

4586                  $shortenedFilePath = substr($recordData, $offset, $sl);
4587                  $shortenedFilePath = self::_encodeUTF16($shortenedFilePath, true);
4588                  $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero

4589  
4590                  $offset += $sl;
4591  
4592                  // offset: var; size: 24; unknown sequence

4593                  $offset += 24;
4594  
4595                  // extended file path

4596                  // offset: var; size: 4; size of the following file link field including string lenth mark

4597                  $sz = self::_GetInt4d($recordData, $offset);
4598                  $offset += 4;
4599  
4600                  // only present if $sz > 0

4601                  if ($sz > 0) {
4602                      // offset: var; size: 4; size of the character array of the extended file path and name

4603                      $xl = self::_GetInt4d($recordData, $offset);
4604                      $offset += 4;
4605  
4606                      // offset: var; size 2; unknown

4607                      $offset += 2;
4608  
4609                      // offset: var; size $xl; character array of the extended file path and name.

4610                      $extendedFilePath = substr($recordData, $offset, $xl);
4611                      $extendedFilePath = self::_encodeUTF16($extendedFilePath, false);
4612                      $offset += $xl;
4613                  }
4614  
4615                  // construct the path

4616                  $url = str_repeat('..\\', $upLevelCount);
4617                  $url .= ($sz > 0) ?
4618                      $extendedFilePath : $shortenedFilePath; // use extended path if available

4619                  $url .= $hasText ? '#' : '';
4620  
4621                  break;
4622  
4623  
4624              case 'UNC':
4625                  // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path

4626                  // todo: implement

4627                  return;
4628  
4629              case 'workbook':
4630                  // section 5.58.5: Hyperlink to the Current Workbook

4631                  // e.g. Sheet2!B1:C2, stored in text mark field

4632                  $url = 'sheet://';
4633                  break;
4634  
4635              default:
4636                  return;
4637  
4638              }
4639  
4640              if ($hasText) {
4641                  // offset: var; size: 4; character count of text mark including trailing zero word

4642                  $tl = self::_GetInt4d($recordData, $offset);
4643                  $offset += 4;
4644                  // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated

4645                  $text = self::_encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false);
4646                  $url .= $text;
4647              }
4648  
4649              // apply the hyperlink to all the relevant cells

4650              foreach (PHPExcel_Cell::extractAllCellReferencesInRange($cellRange) as $coordinate) {
4651                  $this->_phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url);
4652              }
4653          }
4654      }
4655  
4656  
4657      /**

4658       * Read DATAVALIDATIONS record

4659       */
4660  	private function _readDataValidations()
4661      {
4662          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4663          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4664  
4665          // move stream pointer forward to next record

4666          $this->_pos += 4 + $length;
4667      }
4668  
4669  
4670      /**

4671       * Read DATAVALIDATION record

4672       */
4673  	private function _readDataValidation()
4674      {
4675          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4676          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4677  
4678          // move stream pointer forward to next record

4679          $this->_pos += 4 + $length;
4680  
4681          if ($this->_readDataOnly) {
4682              return;
4683          }
4684  
4685          // offset: 0; size: 4; Options

4686          $options = self::_GetInt4d($recordData, 0);
4687  
4688          // bit: 0-3; mask: 0x0000000F; type

4689          $type = (0x0000000F & $options) >> 0;
4690          switch ($type) {
4691              case 0x00:    $type = PHPExcel_Cell_DataValidation::TYPE_NONE;        break;
4692              case 0x01:    $type = PHPExcel_Cell_DataValidation::TYPE_WHOLE;        break;
4693              case 0x02:    $type = PHPExcel_Cell_DataValidation::TYPE_DECIMAL;        break;
4694              case 0x03:    $type = PHPExcel_Cell_DataValidation::TYPE_LIST;        break;
4695              case 0x04:    $type = PHPExcel_Cell_DataValidation::TYPE_DATE;        break;
4696              case 0x05:    $type = PHPExcel_Cell_DataValidation::TYPE_TIME;        break;
4697              case 0x06:    $type = PHPExcel_Cell_DataValidation::TYPE_TEXTLENGTH;    break;
4698              case 0x07:    $type = PHPExcel_Cell_DataValidation::TYPE_CUSTOM;        break;
4699          }
4700  
4701          // bit: 4-6; mask: 0x00000070; error type

4702          $errorStyle = (0x00000070 & $options) >> 4;
4703          switch ($errorStyle) {
4704              case 0x00:    $errorStyle = PHPExcel_Cell_DataValidation::STYLE_STOP;            break;
4705              case 0x01:    $errorStyle = PHPExcel_Cell_DataValidation::STYLE_WARNING;        break;
4706              case 0x02:    $errorStyle = PHPExcel_Cell_DataValidation::STYLE_INFORMATION;    break;
4707          }
4708  
4709          // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)

4710          // I have only seen cases where this is 1

4711          $explicitFormula = (0x00000080 & $options) >> 7;
4712  
4713          // bit: 8; mask: 0x00000100; 1= empty cells allowed

4714          $allowBlank = (0x00000100 & $options) >> 8;
4715  
4716          // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity

4717          $suppressDropDown = (0x00000200 & $options) >> 9;
4718  
4719          // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected

4720          $showInputMessage = (0x00040000 & $options) >> 18;
4721  
4722          // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered

4723          $showErrorMessage = (0x00080000 & $options) >> 19;
4724  
4725          // bit: 20-23; mask: 0x00F00000; condition operator

4726          $operator = (0x00F00000 & $options) >> 20;
4727          switch ($operator) {
4728              case 0x00: $operator = PHPExcel_Cell_DataValidation::OPERATOR_BETWEEN            ;    break;
4729              case 0x01: $operator = PHPExcel_Cell_DataValidation::OPERATOR_NOTBETWEEN        ;    break;
4730              case 0x02: $operator = PHPExcel_Cell_DataValidation::OPERATOR_EQUAL                ;    break;
4731              case 0x03: $operator = PHPExcel_Cell_DataValidation::OPERATOR_NOTEQUAL            ;    break;
4732              case 0x04: $operator = PHPExcel_Cell_DataValidation::OPERATOR_GREATERTHAN        ;    break;
4733              case 0x05: $operator = PHPExcel_Cell_DataValidation::OPERATOR_LESSTHAN            ;    break;
4734              case 0x06: $operator = PHPExcel_Cell_DataValidation::OPERATOR_GREATERTHANOREQUAL;    break;
4735              case 0x07: $operator = PHPExcel_Cell_DataValidation::OPERATOR_LESSTHANOREQUAL    ;    break;
4736          }
4737  
4738          // offset: 4; size: var; title of the prompt box

4739          $offset = 4;
4740          $string = self::_readUnicodeStringLong(substr($recordData, $offset));
4741          $promptTitle = $string['value'] !== chr(0) ?
4742              $string['value'] : '';
4743          $offset += $string['size'];
4744  
4745          // offset: var; size: var; title of the error box

4746          $string = self::_readUnicodeStringLong(substr($recordData, $offset));
4747          $errorTitle = $string['value'] !== chr(0) ?
4748              $string['value'] : '';
4749          $offset += $string['size'];
4750  
4751          // offset: var; size: var; text of the prompt box

4752          $string = self::_readUnicodeStringLong(substr($recordData, $offset));
4753          $prompt = $string['value'] !== chr(0) ?
4754              $string['value'] : '';
4755          $offset += $string['size'];
4756  
4757          // offset: var; size: var; text of the error box

4758          $string = self::_readUnicodeStringLong(substr($recordData, $offset));
4759          $error = $string['value'] !== chr(0) ?
4760              $string['value'] : '';
4761          $offset += $string['size'];
4762  
4763          // offset: var; size: 2; size of the formula data for the first condition

4764          $sz1 = self::_GetInt2d($recordData, $offset);
4765          $offset += 2;
4766  
4767          // offset: var; size: 2; not used

4768          $offset += 2;
4769  
4770          // offset: var; size: $sz1; formula data for first condition (without size field)

4771          $formula1 = substr($recordData, $offset, $sz1);
4772          $formula1 = pack('v', $sz1) . $formula1; // prepend the length

4773          try {
4774              $formula1 = $this->_getFormulaFromStructure($formula1);
4775  
4776              // in list type validity, null characters are used as item separators

4777              if ($type == PHPExcel_Cell_DataValidation::TYPE_LIST) {
4778                  $formula1 = str_replace(chr(0), ',', $formula1);
4779              }
4780          } catch (PHPExcel_Exception $e) {
4781              return;
4782          }
4783          $offset += $sz1;
4784  
4785          // offset: var; size: 2; size of the formula data for the first condition

4786          $sz2 = self::_GetInt2d($recordData, $offset);
4787          $offset += 2;
4788  
4789          // offset: var; size: 2; not used

4790          $offset += 2;
4791  
4792          // offset: var; size: $sz2; formula data for second condition (without size field)

4793          $formula2 = substr($recordData, $offset, $sz2);
4794          $formula2 = pack('v', $sz2) . $formula2; // prepend the length

4795          try {
4796              $formula2 = $this->_getFormulaFromStructure($formula2);
4797          } catch (PHPExcel_Exception $e) {
4798              return;
4799          }
4800          $offset += $sz2;
4801  
4802          // offset: var; size: var; cell range address list with

4803          $cellRangeAddressList = $this->_readBIFF8CellRangeAddressList(substr($recordData, $offset));
4804          $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
4805  
4806          foreach ($cellRangeAddresses as $cellRange) {
4807              $stRange = $this->_phpSheet->shrinkRangeToFit($cellRange);
4808              $stRange = PHPExcel_Cell::extractAllCellReferencesInRange($stRange);
4809              foreach ($stRange as $coordinate) {
4810                  $objValidation = $this->_phpSheet->getCell($coordinate)->getDataValidation();
4811                  $objValidation->setType($type);
4812                  $objValidation->setErrorStyle($errorStyle);
4813                  $objValidation->setAllowBlank((bool)$allowBlank);
4814                  $objValidation->setShowInputMessage((bool)$showInputMessage);
4815                  $objValidation->setShowErrorMessage((bool)$showErrorMessage);
4816                  $objValidation->setShowDropDown(!$suppressDropDown);
4817                  $objValidation->setOperator($operator);
4818                  $objValidation->setErrorTitle($errorTitle);
4819                  $objValidation->setError($error);
4820                  $objValidation->setPromptTitle($promptTitle);
4821                  $objValidation->setPrompt($prompt);
4822                  $objValidation->setFormula1($formula1);
4823                  $objValidation->setFormula2($formula2);
4824              }
4825          }
4826  
4827      }
4828  
4829  
4830      /**

4831       * Read SHEETLAYOUT record. Stores sheet tab color information.

4832       */
4833  	private function _readSheetLayout()
4834      {
4835          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4836          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4837  
4838          // move stream pointer to next record

4839          $this->_pos += 4 + $length;
4840  
4841          // local pointer in record data

4842          $offset = 0;
4843  
4844          if (!$this->_readDataOnly) {
4845              // offset: 0; size: 2; repeated record identifier 0x0862

4846  
4847              // offset: 2; size: 10; not used

4848  
4849              // offset: 12; size: 4; size of record data

4850              // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?)

4851              $sz = self::_GetInt4d($recordData, 12);
4852  
4853              switch ($sz) {
4854                  case 0x14:
4855                      // offset: 16; size: 2; color index for sheet tab

4856                      $colorIndex = self::_GetInt2d($recordData, 16);
4857                      $color = self::_readColor($colorIndex,$this->_palette,$this->_version);
4858                      $this->_phpSheet->getTabColor()->setRGB($color['rgb']);
4859                      break;
4860  
4861                  case 0x28:
4862                      // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007

4863                      return;
4864                      break;
4865              }
4866          }
4867      }
4868  
4869  
4870      /**

4871       * Read SHEETPROTECTION record (FEATHEADR)

4872       */
4873  	private function _readSheetProtection()
4874      {
4875          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4876          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4877  
4878          // move stream pointer to next record

4879          $this->_pos += 4 + $length;
4880  
4881          if ($this->_readDataOnly) {
4882              return;
4883          }
4884  
4885          // offset: 0; size: 2; repeated record header

4886  
4887          // offset: 2; size: 2; FRT cell reference flag (=0 currently)

4888  
4889          // offset: 4; size: 8; Currently not used and set to 0

4890  
4891          // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag)

4892          $isf = self::_GetInt2d($recordData, 12);
4893          if ($isf != 2) {
4894              return;
4895          }
4896  
4897          // offset: 14; size: 1; =1 since this is a feat header

4898  
4899          // offset: 15; size: 4; size of rgbHdrSData

4900  
4901          // rgbHdrSData, assume "Enhanced Protection"

4902          // offset: 19; size: 2; option flags

4903          $options = self::_GetInt2d($recordData, 19);
4904  
4905          // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects

4906          $bool = (0x0001 & $options) >> 0;
4907          $this->_phpSheet->getProtection()->setObjects(!$bool);
4908  
4909          // bit: 1; mask 0x0002; edit scenarios

4910          $bool = (0x0002 & $options) >> 1;
4911          $this->_phpSheet->getProtection()->setScenarios(!$bool);
4912  
4913          // bit: 2; mask 0x0004; format cells

4914          $bool = (0x0004 & $options) >> 2;
4915          $this->_phpSheet->getProtection()->setFormatCells(!$bool);
4916  
4917          // bit: 3; mask 0x0008; format columns

4918          $bool = (0x0008 & $options) >> 3;
4919          $this->_phpSheet->getProtection()->setFormatColumns(!$bool);
4920  
4921          // bit: 4; mask 0x0010; format rows

4922          $bool = (0x0010 & $options) >> 4;
4923          $this->_phpSheet->getProtection()->setFormatRows(!$bool);
4924  
4925          // bit: 5; mask 0x0020; insert columns

4926          $bool = (0x0020 & $options) >> 5;
4927          $this->_phpSheet->getProtection()->setInsertColumns(!$bool);
4928  
4929          // bit: 6; mask 0x0040; insert rows

4930          $bool = (0x0040 & $options) >> 6;
4931          $this->_phpSheet->getProtection()->setInsertRows(!$bool);
4932  
4933          // bit: 7; mask 0x0080; insert hyperlinks

4934          $bool = (0x0080 & $options) >> 7;
4935          $this->_phpSheet->getProtection()->setInsertHyperlinks(!$bool);
4936  
4937          // bit: 8; mask 0x0100; delete columns

4938          $bool = (0x0100 & $options) >> 8;
4939          $this->_phpSheet->getProtection()->setDeleteColumns(!$bool);
4940  
4941          // bit: 9; mask 0x0200; delete rows

4942          $bool = (0x0200 & $options) >> 9;
4943          $this->_phpSheet->getProtection()->setDeleteRows(!$bool);
4944  
4945          // bit: 10; mask 0x0400; select locked cells

4946          $bool = (0x0400 & $options) >> 10;
4947          $this->_phpSheet->getProtection()->setSelectLockedCells(!$bool);
4948  
4949          // bit: 11; mask 0x0800; sort cell range

4950          $bool = (0x0800 & $options) >> 11;
4951          $this->_phpSheet->getProtection()->setSort(!$bool);
4952  
4953          // bit: 12; mask 0x1000; auto filter

4954          $bool = (0x1000 & $options) >> 12;
4955          $this->_phpSheet->getProtection()->setAutoFilter(!$bool);
4956  
4957          // bit: 13; mask 0x2000; pivot tables

4958          $bool = (0x2000 & $options) >> 13;
4959          $this->_phpSheet->getProtection()->setPivotTables(!$bool);
4960  
4961          // bit: 14; mask 0x4000; select unlocked cells

4962          $bool = (0x4000 & $options) >> 14;
4963          $this->_phpSheet->getProtection()->setSelectUnlockedCells(!$bool);
4964  
4965          // offset: 21; size: 2; not used

4966      }
4967  
4968  
4969      /**

4970       * Read RANGEPROTECTION record

4971       * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification,

4972       * where it is referred to as FEAT record

4973       */
4974  	private function _readRangeProtection()
4975      {
4976          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
4977          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
4978  
4979          // move stream pointer to next record

4980          $this->_pos += 4 + $length;
4981  
4982          // local pointer in record data

4983          $offset = 0;
4984  
4985          if (!$this->_readDataOnly) {
4986              $offset += 12;
4987  
4988              // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag

4989              $isf = self::_GetInt2d($recordData, 12);
4990              if ($isf != 2) {
4991                  // we only read FEAT records of type 2

4992                  return;
4993              }
4994              $offset += 2;
4995  
4996              $offset += 5;
4997  
4998              // offset: 19; size: 2; count of ref ranges this feature is on

4999              $cref = self::_GetInt2d($recordData, 19);
5000              $offset += 2;
5001  
5002              $offset += 6;
5003  
5004              // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record)

5005              $cellRanges = array();
5006              for ($i = 0; $i < $cref; ++$i) {
5007                  try {
5008                      $cellRange = $this->_readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8));
5009                  } catch (PHPExcel_Exception $e) {
5010                      return;
5011                  }
5012                  $cellRanges[] = $cellRange;
5013                  $offset += 8;
5014              }
5015  
5016              // offset: var; size: var; variable length of feature specific data

5017              $rgbFeat = substr($recordData, $offset);
5018              $offset += 4;
5019  
5020              // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit)

5021              $wPassword = self::_GetInt4d($recordData, $offset);
5022              $offset += 4;
5023  
5024              // Apply range protection to sheet

5025              if ($cellRanges) {
5026                  $this->_phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true);
5027              }
5028          }
5029      }
5030  
5031  
5032      /**

5033       * Read IMDATA record

5034       */
5035  	private function _readImData()
5036      {
5037          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
5038  
5039          // get spliced record data

5040          $splicedRecordData = $this->_getSplicedRecordData();
5041          $recordData = $splicedRecordData['recordData'];
5042  
5043          // UNDER CONSTRUCTION

5044  
5045          // offset: 0; size: 2; image format

5046          $cf = self::_GetInt2d($recordData, 0);
5047  
5048          // offset: 2; size: 2; environment from which the file was written

5049          $env = self::_GetInt2d($recordData, 2);
5050  
5051          // offset: 4; size: 4; length of the image data

5052          $lcb = self::_GetInt4d($recordData, 4);
5053  
5054          // offset: 8; size: var; image data

5055          $iData = substr($recordData, 8);
5056  
5057          switch ($cf) {
5058          case 0x09: // Windows bitmap format
5059              // BITMAPCOREINFO

5060              // 1. BITMAPCOREHEADER

5061              // offset: 0; size: 4; bcSize, Specifies the number of bytes required by the structure

5062              $bcSize = self::_GetInt4d($iData, 0);
5063  //            var_dump($bcSize);

5064  
5065              // offset: 4; size: 2; bcWidth, specifies the width of the bitmap, in pixels

5066              $bcWidth = self::_GetInt2d($iData, 4);
5067  //            var_dump($bcWidth);

5068  
5069              // offset: 6; size: 2; bcHeight, specifies the height of the bitmap, in pixels.

5070              $bcHeight = self::_GetInt2d($iData, 6);
5071  //            var_dump($bcHeight);

5072              $ih = imagecreatetruecolor($bcWidth, $bcHeight);
5073  
5074              // offset: 8; size: 2; bcPlanes, specifies the number of planes for the target device. This value must be 1

5075  
5076              // offset: 10; size: 2; bcBitCount specifies the number of bits-per-pixel. This value must be 1, 4, 8, or 24

5077              $bcBitCount = self::_GetInt2d($iData, 10);
5078  //            var_dump($bcBitCount);

5079  
5080              $rgbString = substr($iData, 12);
5081              $rgbTriples = array();
5082              while (strlen($rgbString) > 0) {
5083                  $rgbTriples[] = unpack('Cb/Cg/Cr', $rgbString);
5084                  $rgbString = substr($rgbString, 3);
5085              }
5086              $x = 0;
5087              $y = 0;
5088              foreach ($rgbTriples as $i => $rgbTriple) {
5089                  $color = imagecolorallocate($ih, $rgbTriple['r'], $rgbTriple['g'], $rgbTriple['b']);
5090                  imagesetpixel($ih, $x, $bcHeight - 1 - $y, $color);
5091                  $x = ($x + 1) % $bcWidth;
5092                  $y = $y + floor(($x + 1) / $bcWidth);
5093              }
5094              //imagepng($ih, 'image.png');

5095  
5096              $drawing = new PHPExcel_Worksheet_Drawing();
5097              $drawing->setPath($filename);
5098              $drawing->setWorksheet($this->_phpSheet);
5099  
5100              break;
5101  
5102          case 0x02: // Windows metafile or Macintosh PICT format
5103          case 0x0e: // native format
5104          default;
5105              break;
5106  
5107          }
5108  
5109          // _getSplicedRecordData() takes care of moving current position in data stream

5110      }
5111  
5112  
5113      /**

5114       * Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record

5115       * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented.

5116       * In this case, we must treat the CONTINUE record as a MSODRAWING record

5117       */
5118  	private function _readContinue()
5119      {
5120          $length = self::_GetInt2d($this->_data, $this->_pos + 2);
5121          $recordData = $this->_readRecordData($this->_data, $this->_pos + 4, $length);
5122  
5123          // check if we are reading drawing data

5124          // this is in case a free CONTINUE record occurs in other circumstances we are unaware of

5125          if ($this->_drawingData == '') {
5126              // move stream pointer to next record

5127              $this->_pos += 4 + $length;
5128  
5129              return;
5130          }
5131  
5132          // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data

5133          if ($length < 4) {
5134              // move stream pointer to next record

5135              $this->_pos += 4 + $length;
5136  
5137              return;
5138          }
5139  
5140          // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record

5141          // look inside CONTINUE record to see if it looks like a part of an Escher stream

5142          // we know that Escher stream may be split at least at

5143          //        0xF003 MsofbtSpgrContainer

5144          //        0xF004 MsofbtSpContainer

5145          //        0xF00D MsofbtClientTextbox

5146          $validSplitPoints = array(0xF003, 0xF004, 0xF00D); // add identifiers if we find more

5147  
5148          $splitPoint = self::_GetInt2d($recordData, 2);
5149          if (in_array($splitPoint, $validSplitPoints)) {
5150              // get spliced record data (and move pointer to next record)

5151              $splicedRecordData = $this->_getSplicedRecordData();
5152              $this->_drawingData .= $splicedRecordData['recordData'];
5153  
5154              return;
5155          }
5156  
5157          // move stream pointer to next record

5158          $this->_pos += 4 + $length;
5159  
5160      }
5161  
5162  
5163      /**

5164       * Reads a record from current position in data stream and continues reading data as long as CONTINUE

5165       * records are found. Splices the record data pieces and returns the combined string as if record data

5166       * is in one piece.

5167       * Moves to next current position in data stream to start of next record different from a CONtINUE record

5168       *

5169       * @return array

5170       */
5171  	private function _getSplicedRecordData()
5172      {
5173          $data = '';
5174          $spliceOffsets = array();
5175  
5176          $i = 0;
5177          $spliceOffsets[0] = 0;
5178  
5179          do {
5180              ++$i;
5181  
5182              // offset: 0; size: 2; identifier

5183              $identifier = self::_GetInt2d($this->_data, $this->_pos);
5184              // offset: 2; size: 2; length

5185              $length = self::_GetInt2d($this->_data, $this->_pos + 2);
5186              $data .= $this->_readRecordData($this->_data, $this->_pos + 4, $length);
5187  
5188              $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length;
5189  
5190              $this->_pos += 4 + $length;
5191              $nextIdentifier = self::_GetInt2d($this->_data, $this->_pos);
5192          }
5193          while ($nextIdentifier == self::XLS_Type_CONTINUE);
5194  
5195          $splicedData = array(
5196              'recordData' => $data,
5197              'spliceOffsets' => $spliceOffsets,
5198          );
5199  
5200          return $splicedData;
5201  
5202      }
5203  
5204  
5205      /**

5206       * Convert formula structure into human readable Excel formula like 'A3+A5*5'

5207       *

5208       * @param string $formulaStructure The complete binary data for the formula

5209       * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas

5210       * @return string Human readable formula

5211       */
5212  	private function _getFormulaFromStructure($formulaStructure, $baseCell = 'A1')
5213      {
5214          // offset: 0; size: 2; size of the following formula data

5215          $sz = self::_GetInt2d($formulaStructure, 0);
5216  
5217          // offset: 2; size: sz

5218          $formulaData = substr($formulaStructure, 2, $sz);
5219  
5220          // for debug: dump the formula data

5221          //echo '<xmp>';

5222          //echo 'size: ' . $sz . "\n";

5223          //echo 'the entire formula data: ';

5224          //Debug::dump($formulaData);

5225          //echo "\n----\n";

5226  
5227          // offset: 2 + sz; size: variable (optional)

5228          if (strlen($formulaStructure) > 2 + $sz) {
5229              $additionalData = substr($formulaStructure, 2 + $sz);
5230  
5231              // for debug: dump the additional data

5232              //echo 'the entire additional data: ';

5233              //Debug::dump($additionalData);

5234              //echo "\n----\n";

5235  
5236          } else {
5237              $additionalData = '';
5238          }
5239  
5240          return $this->_getFormulaFromData($formulaData, $additionalData, $baseCell);
5241      }
5242  
5243  
5244      /**

5245       * Take formula data and additional data for formula and return human readable formula

5246       *

5247       * @param string $formulaData The binary data for the formula itself

5248       * @param string $additionalData Additional binary data going with the formula

5249       * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas

5250       * @return string Human readable formula

5251       */
5252  	private function _getFormulaFromData($formulaData,  $additionalData = '', $baseCell = 'A1')
5253      {
5254          // start parsing the formula data

5255          $tokens = array();
5256  
5257          while (strlen($formulaData) > 0 and $token = $this->_getNextToken($formulaData, $baseCell)) {
5258              $tokens[] = $token;
5259              $formulaData = substr($formulaData, $token['size']);
5260  
5261              // for debug: dump the token

5262              //var_dump($token);

5263          }
5264  
5265          $formulaString = $this->_createFormulaFromTokens($tokens, $additionalData);
5266  
5267          return $formulaString;
5268      }
5269  
5270  
5271      /**

5272       * Take array of tokens together with additional data for formula and return human readable formula

5273       *

5274       * @param array $tokens

5275       * @param array $additionalData Additional binary data going with the formula

5276       * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas

5277       * @return string Human readable formula

5278       */
5279  	private function _createFormulaFromTokens($tokens, $additionalData)
5280      {
5281          // empty formula?

5282          if (empty($tokens)) {
5283              return '';
5284          }
5285  
5286          $formulaStrings = array();
5287          foreach ($tokens as $token) {
5288              // initialize spaces

5289              $space0 = isset($space0) ? $space0 : ''; // spaces before next token, not tParen

5290              $space1 = isset($space1) ? $space1 : ''; // carriage returns before next token, not tParen

5291              $space2 = isset($space2) ? $space2 : ''; // spaces before opening parenthesis

5292              $space3 = isset($space3) ? $space3 : ''; // carriage returns before opening parenthesis

5293              $space4 = isset($space4) ? $space4 : ''; // spaces before closing parenthesis

5294              $space5 = isset($space5) ? $space5 : ''; // carriage returns before closing parenthesis

5295  
5296              switch ($token['name']) {
5297              case 'tAdd': // addition
5298              case 'tConcat': // addition
5299              case 'tDiv': // division
5300              case 'tEQ': // equality
5301              case 'tGE': // greater than or equal
5302              case 'tGT': // greater than
5303              case 'tIsect': // intersection
5304              case 'tLE': // less than or equal
5305              case 'tList': // less than or equal
5306              case 'tLT': // less than
5307              case 'tMul': // multiplication
5308              case 'tNE': // multiplication
5309              case 'tPower': // power
5310              case 'tRange': // range
5311              case 'tSub': // subtraction
5312                  $op2 = array_pop($formulaStrings);
5313                  $op1 = array_pop($formulaStrings);
5314                  $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2";
5315                  unset($space0, $space1);
5316                  break;
5317              case 'tUplus': // unary plus
5318              case 'tUminus': // unary minus
5319                  $op = array_pop($formulaStrings);
5320                  $formulaStrings[] = "$space1$space0{$token['data']}$op";
5321                  unset($space0, $space1);
5322                  break;
5323              case 'tPercent': // percent sign
5324                  $op = array_pop($formulaStrings);
5325                  $formulaStrings[] = "$op$space1$space0{$token['data']}";
5326                  unset($space0, $space1);
5327                  break;
5328              case 'tAttrVolatile': // indicates volatile function
5329              case 'tAttrIf':
5330              case 'tAttrSkip':
5331              case 'tAttrChoose':
5332                  // token is only important for Excel formula evaluator

5333                  // do nothing

5334                  break;
5335              case 'tAttrSpace': // space / carriage return
5336                  // space will be used when next token arrives, do not alter formulaString stack

5337                  switch ($token['data']['spacetype']) {
5338                  case 'type0':
5339                      $space0 = str_repeat(' ', $token['data']['spacecount']);
5340                      break;
5341                  case 'type1':
5342                      $space1 = str_repeat("\n", $token['data']['spacecount']);
5343                      break;
5344                  case 'type2':
5345                      $space2 = str_repeat(' ', $token['data']['spacecount']);
5346                      break;
5347                  case 'type3':
5348                      $space3 = str_repeat("\n", $token['data']['spacecount']);
5349                      break;
5350                  case 'type4':
5351                      $space4 = str_repeat(' ', $token['data']['spacecount']);
5352                      break;
5353                  case 'type5':
5354                      $space5 = str_repeat("\n", $token['data']['spacecount']);
5355                      break;
5356                  }
5357                  break;
5358              case 'tAttrSum': // SUM function with one parameter
5359                  $op = array_pop($formulaStrings);
5360                  $formulaStrings[] = "{$space1}{$space0}SUM($op)";
5361                  unset($space0, $space1);
5362                  break;
5363              case 'tFunc': // function with fixed number of arguments
5364              case 'tFuncV': // function with variable number of arguments
5365                  if ($token['data']['function'] != '') {
5366                      // normal function

5367                      $ops = array(); // array of operators

5368                      for ($i = 0; $i < $token['data']['args']; ++$i) {
5369                          $ops[] = array_pop($formulaStrings);
5370                      }
5371                      $ops = array_reverse($ops);
5372                      $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ")";
5373                      unset($space0, $space1);
5374                  } else {
5375                      // add-in function

5376                      $ops = array(); // array of operators

5377                      for ($i = 0; $i < $token['data']['args'] - 1; ++$i) {
5378                          $ops[] = array_pop($formulaStrings);
5379                      }
5380                      $ops = array_reverse($ops);
5381                      $function = array_pop($formulaStrings);
5382                      $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ")";
5383                      unset($space0, $space1);
5384                  }
5385                  break;
5386              case 'tParen': // parenthesis
5387                  $expression = array_pop($formulaStrings);
5388                  $formulaStrings[] = "$space3$space2($expression$space5$space4)";
5389                  unset($space2, $space3, $space4, $space5);
5390                  break;
5391              case 'tArray': // array constant
5392                  $constantArray = self::_readBIFF8ConstantArray($additionalData);
5393                  $formulaStrings[] = $space1 . $space0 . $constantArray['value'];
5394                  $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data

5395                  unset($space0, $space1);
5396                  break;
5397              case 'tMemArea':
5398                  // bite off chunk of additional data

5399                  $cellRangeAddressList = $this->_readBIFF8CellRangeAddressList($additionalData);
5400                  $additionalData = substr($additionalData, $cellRangeAddressList['size']);
5401                  $formulaStrings[] = "$space1$space0{$token['data']}";
5402                  unset($space0, $space1);
5403                  break;
5404              case 'tArea': // cell range address
5405              case 'tBool': // boolean
5406              case 'tErr': // error code
5407              case 'tInt': // integer
5408              case 'tMemErr':
5409              case 'tMemFunc':
5410              case 'tMissArg':
5411              case 'tName':
5412              case 'tNameX':
5413              case 'tNum': // number
5414              case 'tRef': // single cell reference
5415              case 'tRef3d': // 3d cell reference
5416              case 'tArea3d': // 3d cell range reference
5417              case 'tRefN':
5418              case 'tAreaN':
5419              case 'tStr': // string
5420                  $formulaStrings[] = "$space1$space0{$token['data']}";
5421                  unset($space0, $space1);
5422                  break;
5423              }
5424          }
5425          $formulaString = $formulaStrings[0];
5426  
5427          // for debug: dump the human readable formula

5428          //echo '----' . "\n";

5429          //echo 'Formula: ' . $formulaString;

5430  
5431          return $formulaString;
5432      }
5433  
5434  
5435      /**

5436       * Fetch next token from binary formula data

5437       *

5438       * @param string Formula data

5439       * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas

5440       * @return array

5441       * @throws PHPExcel_Reader_Exception

5442       */
5443  	private function _getNextToken($formulaData, $baseCell = 'A1')
5444      {
5445          // offset: 0; size: 1; token id

5446          $id = ord($formulaData[0]); // token id

5447          $name = false; // initialize token name

5448  
5449          switch ($id) {
5450          case 0x03: $name = 'tAdd';        $size = 1;    $data = '+';    break;
5451          case 0x04: $name = 'tSub';        $size = 1;    $data = '-';    break;
5452          case 0x05: $name = 'tMul';        $size = 1;    $data = '*';    break;
5453          case 0x06: $name = 'tDiv';        $size = 1;    $data = '/';    break;
5454          case 0x07: $name = 'tPower';    $size = 1;    $data = '^';    break;
5455          case 0x08: $name = 'tConcat';    $size = 1;    $data = '&';    break;
5456          case 0x09: $name = 'tLT';        $size = 1;    $data = '<';    break;
5457          case 0x0A: $name = 'tLE';        $size = 1;    $data = '<=';    break;
5458          case 0x0B: $name = 'tEQ';        $size = 1;    $data = '=';    break;
5459          case 0x0C: $name = 'tGE';        $size = 1;    $data = '>=';    break;
5460          case 0x0D: $name = 'tGT';        $size = 1;    $data = '>';    break;
5461          case 0x0E: $name = 'tNE';        $size = 1;    $data = '<>';    break;
5462          case 0x0F: $name = 'tIsect';    $size = 1;    $data = ' ';    break;
5463          case 0x10: $name = 'tList';        $size = 1;    $data = ',';    break;
5464          case 0x11: $name = 'tRange';    $size = 1;    $data = ':';    break;
5465          case 0x12: $name = 'tUplus';    $size = 1;    $data = '+';    break;
5466          case 0x13: $name = 'tUminus';    $size = 1;    $data = '-';    break;
5467          case 0x14: $name = 'tPercent';    $size = 1;    $data = '%';    break;
5468          case 0x15:    //    parenthesis
5469              $name  = 'tParen';
5470              $size  = 1;
5471              $data = null;
5472              break;
5473          case 0x16:    //    missing argument
5474              $name = 'tMissArg';
5475              $size = 1;
5476              $data = '';
5477              break;
5478          case 0x17:    //    string
5479              $name = 'tStr';
5480              // offset: 1; size: var; Unicode string, 8-bit string length

5481              $string = self::_readUnicodeStringShort(substr($formulaData, 1));
5482              $size = 1 + $string['size'];
5483              $data = self::_UTF8toExcelDoubleQuoted($string['value']);
5484              break;
5485          case 0x19:    //    Special attribute
5486              // offset: 1; size: 1; attribute type flags:

5487              switch (ord($formulaData[1])) {
5488              case 0x01:
5489                  $name = 'tAttrVolatile';
5490                  $size = 4;
5491                  $data = null;
5492                  break;
5493              case 0x02:
5494                  $name = 'tAttrIf';
5495                  $size = 4;
5496                  $data = null;
5497                  break;
5498              case 0x04:
5499                  $name = 'tAttrChoose';
5500                  // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1)

5501                  $nc = self::_GetInt2d($formulaData, 2);
5502                  // offset: 4; size: 2 * $nc

5503                  // offset: 4 + 2 * $nc; size: 2

5504                  $size = 2 * $nc + 6;
5505                  $data = null;
5506                  break;
5507              case 0x08:
5508                  $name = 'tAttrSkip';
5509                  $size = 4;
5510                  $data = null;
5511                  break;
5512              case 0x10:
5513                  $name = 'tAttrSum';
5514                  $size = 4;
5515                  $data = null;
5516                  break;
5517              case 0x40:
5518              case 0x41:
5519                  $name = 'tAttrSpace';
5520                  $size = 4;
5521                  // offset: 2; size: 2; space type and position

5522                  switch (ord($formulaData[2])) {
5523                  case 0x00:
5524                      $spacetype = 'type0';
5525                      break;
5526                  case 0x01:
5527                      $spacetype = 'type1';
5528                      break;
5529                  case 0x02:
5530                      $spacetype = 'type2';
5531                      break;
5532                  case 0x03:
5533                      $spacetype = 'type3';
5534                      break;
5535                  case 0x04:
5536                      $spacetype = 'type4';
5537                      break;
5538                  case 0x05:
5539                      $spacetype = 'type5';
5540                      break;
5541                  default:
5542                      throw new PHPExcel_Reader_Exception('Unrecognized space type in tAttrSpace token');
5543                      break;
5544                  }
5545                  // offset: 3; size: 1; number of inserted spaces/carriage returns

5546                  $spacecount = ord($formulaData[3]);
5547  
5548                  $data = array('spacetype' => $spacetype, 'spacecount' => $spacecount);
5549                  break;
5550              default:
5551                  throw new PHPExcel_Reader_Exception('Unrecognized attribute flag in tAttr token');
5552                  break;
5553              }
5554              break;
5555          case 0x1C:    //    error code
5556              // offset: 1; size: 1; error code

5557              $name = 'tErr';
5558              $size = 2;
5559              $data = self::_mapErrorCode(ord($formulaData[1]));
5560              break;
5561          case 0x1D:    //    boolean
5562              // offset: 1; size: 1; 0 = false, 1 = true;

5563              $name = 'tBool';
5564              $size = 2;
5565              $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE';
5566              break;
5567          case 0x1E:    //    integer
5568              // offset: 1; size: 2; unsigned 16-bit integer

5569              $name = 'tInt';
5570              $size = 3;
5571              $data = self::_GetInt2d($formulaData, 1);
5572              break;
5573          case 0x1F:    //    number
5574              // offset: 1; size: 8;

5575              $name = 'tNum';
5576              $size = 9;
5577              $data = self::_extractNumber(substr($formulaData, 1));
5578              $data = str_replace(',', '.', (string)$data); // in case non-English locale

5579              break;
5580          case 0x20:    //    array constant
5581          case 0x40:
5582          case 0x60:
5583              // offset: 1; size: 7; not used

5584              $name = 'tArray';
5585              $size = 8;
5586              $data = null;
5587              break;
5588          case 0x21:    //    function with fixed number of arguments
5589          case 0x41:
5590          case 0x61:
5591              $name = 'tFunc';
5592              $size = 3;
5593              // offset: 1; size: 2; index to built-in sheet function

5594              switch (self::_GetInt2d($formulaData, 1)) {
5595              case   2: $function = 'ISNA';             $args = 1;     break;
5596              case   3: $function = 'ISERROR';         $args = 1;     break;
5597              case  10: $function = 'NA';             $args = 0;     break;
5598              case  15: $function = 'SIN';             $args = 1;     break;
5599              case  16: $function = 'COS';             $args = 1;     break;
5600              case  17: $function = 'TAN';             $args = 1;     break;
5601              case  18: $function = 'ATAN';             $args = 1;     break;
5602              case  19: $function = 'PI';             $args = 0;     break;
5603              case  20: $function = 'SQRT';             $args = 1;     break;
5604              case  21: $function = 'EXP';             $args = 1;     break;
5605              case  22: $function = 'LN';             $args = 1;     break;
5606              case  23: $function = 'LOG10';             $args = 1;     break;
5607              case  24: $function = 'ABS';             $args = 1;     break;
5608              case  25: $function = 'INT';             $args = 1;     break;
5609              case  26: $function = 'SIGN';             $args = 1;     break;
5610              case  27: $function = 'ROUND';             $args = 2;     break;
5611              case  30: $function = 'REPT';             $args = 2;     break;
5612              case  31: $function = 'MID';             $args = 3;     break;
5613              case  32: $function = 'LEN';             $args = 1;     break;
5614              case  33: $function = 'VALUE';             $args = 1;     break;
5615              case  34: $function = 'TRUE';             $args = 0;     break;
5616              case  35: $function = 'FALSE';             $args = 0;     break;
5617              case  38: $function = 'NOT';             $args = 1;     break;
5618              case  39: $function = 'MOD';             $args = 2;    break;
5619              case  40: $function = 'DCOUNT';         $args = 3;    break;
5620              case  41: $function = 'DSUM';             $args = 3;    break;
5621              case  42: $function = 'DAVERAGE';         $args = 3;    break;
5622              case  43: $function = 'DMIN';             $args = 3;    break;
5623              case  44: $function = 'DMAX';             $args = 3;    break;
5624              case  45: $function = 'DSTDEV';         $args = 3;    break;
5625              case  48: $function = 'TEXT';             $args = 2;    break;
5626              case  61: $function = 'MIRR';             $args = 3;    break;
5627              case  63: $function = 'RAND';             $args = 0;    break;
5628              case  65: $function = 'DATE';             $args = 3;    break;
5629              case  66: $function = 'TIME';             $args = 3;    break;
5630              case  67: $function = 'DAY';             $args = 1;    break;
5631              case  68: $function = 'MONTH';             $args = 1;    break;
5632              case  69: $function = 'YEAR';             $args = 1;    break;
5633              case  71: $function = 'HOUR';             $args = 1;    break;
5634              case  72: $function = 'MINUTE';         $args = 1;    break;
5635              case  73: $function = 'SECOND';         $args = 1;    break;
5636              case  74: $function = 'NOW';             $args = 0;    break;
5637              case  75: $function = 'AREAS';             $args = 1;    break;
5638              case  76: $function = 'ROWS';             $args = 1;    break;
5639              case  77: $function = 'COLUMNS';         $args = 1;    break;
5640              case  83: $function = 'TRANSPOSE';         $args = 1;    break;
5641              case  86: $function = 'TYPE';             $args = 1;    break;
5642              case  97: $function = 'ATAN2';             $args = 2;    break;
5643              case  98: $function = 'ASIN';             $args = 1;    break;
5644              case  99: $function = 'ACOS';             $args = 1;    break;
5645              case 105: $function = 'ISREF';             $args = 1;    break;
5646              case 111: $function = 'CHAR';             $args = 1;    break;
5647              case 112: $function = 'LOWER';             $args = 1;    break;
5648              case 113: $function = 'UPPER';             $args = 1;    break;
5649              case 114: $function = 'PROPER';         $args = 1;    break;
5650              case 117: $function = 'EXACT';             $args = 2;    break;
5651              case 118: $function = 'TRIM';             $args = 1;    break;
5652              case 119: $function = 'REPLACE';         $args = 4;    break;
5653              case 121: $function = 'CODE';             $args = 1;    break;
5654              case 126: $function = 'ISERR';             $args = 1;    break;
5655              case 127: $function = 'ISTEXT';         $args = 1;    break;
5656              case 128: $function = 'ISNUMBER';         $args = 1;    break;
5657              case 129: $function = 'ISBLANK';         $args = 1;    break;
5658              case 130: $function = 'T';                 $args = 1;    break;
5659              case 131: $function = 'N';                 $args = 1;    break;
5660              case 140: $function = 'DATEVALUE';         $args = 1;    break;
5661              case 141: $function = 'TIMEVALUE';         $args = 1;    break;
5662              case 142: $function = 'SLN';             $args = 3;    break;
5663              case 143: $function = 'SYD';             $args = 4;    break;
5664              case 162: $function = 'CLEAN';             $args = 1;    break;
5665              case 163: $function = 'MDETERM';         $args = 1;    break;
5666              case 164: $function = 'MINVERSE';         $args = 1;    break;
5667              case 165: $function = 'MMULT';             $args = 2;    break;
5668              case 184: $function = 'FACT';             $args = 1;    break;
5669              case 189: $function = 'DPRODUCT';         $args = 3;    break;
5670              case 190: $function = 'ISNONTEXT';         $args = 1;    break;
5671              case 195: $function = 'DSTDEVP';         $args = 3;    break;
5672              case 196: $function = 'DVARP';             $args = 3;    break;
5673              case 198: $function = 'ISLOGICAL';         $args = 1;    break;
5674              case 199: $function = 'DCOUNTA';         $args = 3;    break;
5675              case 207: $function = 'REPLACEB';         $args = 4;    break;
5676              case 210: $function = 'MIDB';             $args = 3;    break;
5677              case 211: $function = 'LENB';             $args = 1;    break;
5678              case 212: $function = 'ROUNDUP';         $args = 2;    break;
5679              case 213: $function = 'ROUNDDOWN';         $args = 2;    break;
5680              case 214: $function = 'ASC';             $args = 1;    break;
5681              case 215: $function = 'DBCS';             $args = 1;    break;
5682              case 221: $function = 'TODAY';             $args = 0;    break;
5683              case 229: $function = 'SINH';             $args = 1;    break;
5684              case 230: $function = 'COSH';             $args = 1;    break;
5685              case 231: $function = 'TANH';             $args = 1;    break;
5686              case 232: $function = 'ASINH';             $args = 1;    break;
5687              case 233: $function = 'ACOSH';             $args = 1;    break;
5688              case 234: $function = 'ATANH';             $args = 1;    break;
5689              case 235: $function = 'DGET';             $args = 3;    break;
5690              case 244: $function = 'INFO';             $args = 1;    break;
5691              case 252: $function = 'FREQUENCY';         $args = 2;    break;
5692              case 261: $function = 'ERROR.TYPE';     $args = 1;    break;
5693              case 271: $function = 'GAMMALN';         $args = 1;    break;
5694              case 273: $function = 'BINOMDIST';         $args = 4;    break;
5695              case 274: $function = 'CHIDIST';         $args = 2;    break;
5696              case 275: $function = 'CHIINV';         $args = 2;    break;
5697              case 276: $function = 'COMBIN';         $args = 2;    break;
5698              case 277: $function = 'CONFIDENCE';     $args = 3;    break;
5699              case 278: $function = 'CRITBINOM';         $args = 3;    break;
5700              case 279: $function = 'EVEN';             $args = 1;    break;
5701              case 280: $function = 'EXPONDIST';         $args = 3;    break;
5702              case 281: $function = 'FDIST';             $args = 3;    break;
5703              case 282: $function = 'FINV';             $args = 3;    break;
5704              case 283: $function = 'FISHER';         $args = 1;    break;
5705              case 284: $function = 'FISHERINV';         $args = 1;    break;
5706              case 285: $function = 'FLOOR';             $args = 2;    break;
5707              case 286: $function = 'GAMMADIST';         $args = 4;    break;
5708              case 287: $function = 'GAMMAINV';         $args = 3;    break;
5709              case 288: $function = 'CEILING';         $args = 2;    break;
5710              case 289: $function = 'HYPGEOMDIST';    $args = 4;    break;
5711              case 290: $function = 'LOGNORMDIST';    $args = 3;    break;
5712              case 291: $function = 'LOGINV';            $args = 3;    break;
5713              case 292: $function = 'NEGBINOMDIST';    $args = 3;    break;
5714              case 293: $function = 'NORMDIST';        $args = 4;    break;
5715              case 294: $function = 'NORMSDIST';        $args = 1;    break;
5716              case 295: $function = 'NORMINV';        $args = 3;    break;
5717              case 296: $function = 'NORMSINV';        $args = 1;    break;
5718              case 297: $function = 'STANDARDIZE';    $args = 3;    break;
5719              case 298: $function = 'ODD';            $args = 1;    break;
5720              case 299: $function = 'PERMUT';            $args = 2;    break;
5721              case 300: $function = 'POISSON';        $args = 3;    break;
5722              case 301: $function = 'TDIST';            $args = 3;    break;
5723              case 302: $function = 'WEIBULL';        $args = 4;    break;
5724              case 303: $function = 'SUMXMY2';        $args = 2;    break;
5725              case 304: $function = 'SUMX2MY2';        $args = 2;    break;
5726              case 305: $function = 'SUMX2PY2';        $args = 2;    break;
5727              case 306: $function = 'CHITEST';        $args = 2;    break;
5728              case 307: $function = 'CORREL';            $args = 2;    break;
5729              case 308: $function = 'COVAR';            $args = 2;    break;
5730              case 309: $function = 'FORECAST';        $args = 3;    break;
5731              case 310: $function = 'FTEST';            $args = 2;    break;
5732              case 311: $function = 'INTERCEPT';        $args = 2;    break;
5733              case 312: $function = 'PEARSON';        $args = 2;    break;
5734              case 313: $function = 'RSQ';            $args = 2;    break;
5735              case 314: $function = 'STEYX';            $args = 2;    break;
5736              case 315: $function = 'SLOPE';            $args = 2;    break;
5737              case 316: $function = 'TTEST';            $args = 4;    break;
5738              case 325: $function = 'LARGE';            $args = 2;    break;
5739              case 326: $function = 'SMALL';            $args = 2;    break;
5740              case 327: $function = 'QUARTILE';        $args = 2;    break;
5741              case 328: $function = 'PERCENTILE';        $args = 2;    break;
5742              case 331: $function = 'TRIMMEAN';        $args = 2;    break;
5743              case 332: $function = 'TINV';            $args = 2;    break;
5744              case 337: $function = 'POWER';            $args = 2;    break;
5745              case 342: $function = 'RADIANS';        $args = 1;    break;
5746              case 343: $function = 'DEGREES';        $args = 1;    break;
5747              case 346: $function = 'COUNTIF';        $args = 2;    break;
5748              case 347: $function = 'COUNTBLANK';        $args = 1;    break;
5749              case 350: $function = 'ISPMT';            $args = 4;    break;
5750              case 351: $function = 'DATEDIF';        $args = 3;    break;
5751              case 352: $function = 'DATESTRING';        $args = 1;    break;
5752              case 353: $function = 'NUMBERSTRING';    $args = 2;    break;
5753              case 360: $function = 'PHONETIC';        $args = 1;    break;
5754              case 368: $function = 'BAHTTEXT';        $args = 1;    break;
5755              default:
5756                  throw new PHPExcel_Reader_Exception('Unrecognized function in formula');
5757                  break;
5758              }
5759              $data = array('function' => $function, 'args' => $args);
5760              break;
5761          case 0x22:    //    function with variable number of arguments
5762          case 0x42:
5763          case 0x62:
5764              $name = 'tFuncV';
5765              $size = 4;
5766              // offset: 1; size: 1; number of arguments

5767              $args = ord($formulaData[1]);
5768              // offset: 2: size: 2; index to built-in sheet function

5769              $index = self::_GetInt2d($formulaData, 2);
5770              switch ($index) {
5771              case   0: $function = 'COUNT';            break;
5772              case   1: $function = 'IF';                break;
5773              case   4: $function = 'SUM';            break;
5774              case   5: $function = 'AVERAGE';        break;
5775              case   6: $function = 'MIN';            break;
5776              case   7: $function = 'MAX';            break;
5777              case   8: $function = 'ROW';            break;
5778              case   9: $function = 'COLUMN';            break;
5779              case  11: $function = 'NPV';            break;
5780              case  12: $function = 'STDEV';            break;
5781              case  13: $function = 'DOLLAR';            break;
5782              case  14: $function = 'FIXED';            break;
5783              case  28: $function = 'LOOKUP';            break;
5784              case  29: $function = 'INDEX';            break;
5785              case  36: $function = 'AND';            break;
5786              case  37: $function = 'OR';                break;
5787              case  46: $function = 'VAR';            break;
5788              case  49: $function = 'LINEST';            break;
5789              case  50: $function = 'TREND';            break;
5790              case  51: $function = 'LOGEST';            break;
5791              case  52: $function = 'GROWTH';            break;
5792              case  56: $function = 'PV';                break;
5793              case  57: $function = 'FV';                break;
5794              case  58: $function = 'NPER';            break;
5795              case  59: $function = 'PMT';            break;
5796              case  60: $function = 'RATE';            break;
5797              case  62: $function = 'IRR';            break;
5798              case  64: $function = 'MATCH';            break;
5799              case  70: $function = 'WEEKDAY';        break;
5800              case  78: $function = 'OFFSET';            break;
5801              case  82: $function = 'SEARCH';            break;
5802              case 100: $function = 'CHOOSE';            break;
5803              case 101: $function = 'HLOOKUP';        break;
5804              case 102: $function = 'VLOOKUP';        break;
5805              case 109: $function = 'LOG';            break;
5806              case 115: $function = 'LEFT';            break;
5807              case 116: $function = 'RIGHT';            break;
5808              case 120: $function = 'SUBSTITUTE';        break;
5809              case 124: $function = 'FIND';            break;
5810              case 125: $function = 'CELL';            break;
5811              case 144: $function = 'DDB';            break;
5812              case 148: $function = 'INDIRECT';        break;
5813              case 167: $function = 'IPMT';            break;
5814              case 168: $function = 'PPMT';            break;
5815              case 169: $function = 'COUNTA';            break;
5816              case 183: $function = 'PRODUCT';        break;
5817              case 193: $function = 'STDEVP';            break;
5818              case 194: $function = 'VARP';            break;
5819              case 197: $function = 'TRUNC';            break;
5820              case 204: $function = 'USDOLLAR';        break;
5821              case 205: $function = 'FINDB';            break;
5822              case 206: $function = 'SEARCHB';        break;
5823              case 208: $function = 'LEFTB';            break;
5824              case 209: $function = 'RIGHTB';            break;
5825              case 216: $function = 'RANK';            break;
5826              case 219: $function = 'ADDRESS';        break;
5827              case 220: $function = 'DAYS360';        break;
5828              case 222: $function = 'VDB';            break;
5829              case 227: $function = 'MEDIAN';            break;
5830              case 228: $function = 'SUMPRODUCT';        break;
5831              case 247: $function = 'DB';                break;
5832              case 255: $function = '';                break;
5833              case 269: $function = 'AVEDEV';            break;
5834              case 270: $function = 'BETADIST';        break;
5835              case 272: $function = 'BETAINV';        break;
5836              case 317: $function = 'PROB';            break;
5837              case 318: $function = 'DEVSQ';            break;
5838              case 319: $function = 'GEOMEAN';        break;
5839              case 320: $function = 'HARMEAN';        break;
5840              case 321: $function = 'SUMSQ';            break;
5841              case 322: $function = 'KURT';            break;
5842              case 323: $function = 'SKEW';            break;
5843              case 324: $function = 'ZTEST';            break;
5844              case 329: $function = 'PERCENTRANK';    break;
5845              case 330: $function = 'MODE';            break;
5846              case 336: $function = 'CONCATENATE';    break;
5847              case 344: $function = 'SUBTOTAL';        break;
5848              case 345: $function = 'SUMIF';            break;
5849              case 354: $function = 'ROMAN';            break;
5850              case 358: $function = 'GETPIVOTDATA';    break;
5851              case 359: $function = 'HYPERLINK';        break;
5852              case 361: $function = 'AVERAGEA';        break;
5853              case 362: $function = 'MAXA';            break;
5854              case 363: $function = 'MINA';            break;
5855              case 364: $function = 'STDEVPA';        break;
5856              case 365: $function = 'VARPA';            break;
5857              case 366: $function = 'STDEVA';            break;
5858              case 367: $function = 'VARA';            break;
5859              default:
5860                  throw new PHPExcel_Reader_Exception('Unrecognized function in formula');
5861                  break;
5862              }
5863              $data = array('function' => $function, 'args' => $args);
5864              break;
5865          case 0x23:    //    index to defined name
5866          case 0x43:
5867          case 0x63:
5868              $name = 'tName';
5869              $size = 5;
5870              // offset: 1; size: 2; one-based index to definedname record

5871              $definedNameIndex = self::_GetInt2d($formulaData, 1) - 1;
5872              // offset: 2; size: 2; not used

5873              $data = $this->_definedname[$definedNameIndex]['name'];
5874              break;
5875          case 0x24:    //    single cell reference e.g. A5
5876          case 0x44:
5877          case 0x64:
5878              $name = 'tRef';
5879              $size = 5;
5880              $data = $this->_readBIFF8CellAddress(substr($formulaData, 1, 4));
5881              break;
5882          case 0x25:    //    cell range reference to cells in the same sheet (2d)
5883          case 0x45:
5884          case 0x65:
5885              $name = 'tArea';
5886              $size = 9;
5887              $data = $this->_readBIFF8CellRangeAddress(substr($formulaData, 1, 8));
5888              break;
5889          case 0x26:    //    Constant reference sub-expression
5890          case 0x46:
5891          case 0x66:
5892              $name = 'tMemArea';
5893              // offset: 1; size: 4; not used

5894              // offset: 5; size: 2; size of the following subexpression

5895              $subSize = self::_GetInt2d($formulaData, 5);
5896              $size = 7 + $subSize;
5897              $data = $this->_getFormulaFromData(substr($formulaData, 7, $subSize));
5898              break;
5899          case 0x27:    //    Deleted constant reference sub-expression
5900          case 0x47:
5901          case 0x67:
5902              $name = 'tMemErr';
5903              // offset: 1; size: 4; not used

5904              // offset: 5; size: 2; size of the following subexpression

5905              $subSize = self::_GetInt2d($formulaData, 5);
5906              $size = 7 + $subSize;
5907              $data = $this->_getFormulaFromData(substr($formulaData, 7, $subSize));
5908              break;
5909          case 0x29:    //    Variable reference sub-expression
5910          case 0x49:
5911          case 0x69:
5912              $name = 'tMemFunc';
5913              // offset: 1; size: 2; size of the following sub-expression

5914              $subSize = self::_GetInt2d($formulaData, 1);
5915              $size = 3 + $subSize;
5916              $data = $this->_getFormulaFromData(substr($formulaData, 3, $subSize));
5917              break;
5918  
5919          case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places
5920          case 0x4C:
5921          case 0x6C:
5922              $name = 'tRefN';
5923              $size = 5;
5924              $data = $this->_readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell);
5925              break;
5926  
5927          case 0x2D:    //    Relative 2d range reference
5928          case 0x4D:
5929          case 0x6D:
5930              $name = 'tAreaN';
5931              $size = 9;
5932              $data = $this->_readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell);
5933              break;
5934  
5935          case 0x39:    //    External name
5936          case 0x59:
5937          case 0x79:
5938              $name = 'tNameX';
5939              $size = 7;
5940              // offset: 1; size: 2; index to REF entry in EXTERNSHEET record

5941              // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record

5942              $index = self::_GetInt2d($formulaData, 3);
5943              // assume index is to EXTERNNAME record

5944              $data = $this->_externalNames[$index - 1]['name'];
5945              // offset: 5; size: 2; not used

5946              break;
5947  
5948          case 0x3A:    //    3d reference to cell
5949          case 0x5A:
5950          case 0x7A:
5951              $name = 'tRef3d';
5952              $size = 7;
5953  
5954              try {
5955                  // offset: 1; size: 2; index to REF entry

5956                  $sheetRange = $this->_readSheetRangeByRefIndex(self::_GetInt2d($formulaData, 1));
5957                  // offset: 3; size: 4; cell address

5958                  $cellAddress = $this->_readBIFF8CellAddress(substr($formulaData, 3, 4));
5959  
5960                  $data = "$sheetRange!$cellAddress";
5961              } catch (PHPExcel_Exception $e) {
5962                  // deleted sheet reference

5963                  $data = '#REF!';
5964              }
5965  
5966              break;
5967          case 0x3B:    //    3d reference to cell range
5968          case 0x5B:
5969          case 0x7B:
5970              $name = 'tArea3d';
5971              $size = 11;
5972  
5973              try {
5974                  // offset: 1; size: 2; index to REF entry

5975                  $sheetRange = $this->_readSheetRangeByRefIndex(self::_GetInt2d($formulaData, 1));
5976                  // offset: 3; size: 8; cell address

5977                  $cellRangeAddress = $this->_readBIFF8CellRangeAddress(substr($formulaData, 3, 8));
5978  
5979                  $data = "$sheetRange!$cellRangeAddress";
5980              } catch (PHPExcel_Exception $e) {
5981                  // deleted sheet reference

5982                  $data = '#REF!';
5983              }
5984  
5985              break;
5986          // Unknown cases    // don't know how to deal with

5987          default:
5988              throw new PHPExcel_Reader_Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula');
5989              break;
5990          }
5991  
5992          return array(
5993              'id' => $id,
5994              'name' => $name,
5995              'size' => $size,
5996              'data' => $data,
5997          );
5998      }
5999  
6000  
6001      /**

6002       * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2'

6003       * section 3.3.4

6004       *

6005       * @param string $cellAddressStructure

6006       * @return string

6007       */
6008  	private function _readBIFF8CellAddress($cellAddressStructure)
6009      {
6010          // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))

6011          $row = self::_GetInt2d($cellAddressStructure, 0) + 1;
6012  
6013          // offset: 2; size: 2; index to column or column offset + relative flags

6014  
6015              // bit: 7-0; mask 0x00FF; column index

6016              $column = PHPExcel_Cell::stringFromColumnIndex(0x00FF & self::_GetInt2d($cellAddressStructure, 2));
6017  
6018              // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)

6019              if (!(0x4000 & self::_GetInt2d($cellAddressStructure, 2))) {
6020                  $column = '$' . $column;
6021              }
6022              // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)

6023              if (!(0x8000 & self::_GetInt2d($cellAddressStructure, 2))) {
6024                  $row = '$' . $row;
6025              }
6026  
6027          return $column . $row;
6028      }
6029  
6030  
6031      /**

6032       * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column

6033       * to indicate offsets from a base cell

6034       * section 3.3.4

6035       *

6036       * @param string $cellAddressStructure

6037       * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas

6038       * @return string

6039       */
6040  	private function _readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1')
6041      {
6042          list($baseCol, $baseRow) = PHPExcel_Cell::coordinateFromString($baseCell);
6043          $baseCol = PHPExcel_Cell::columnIndexFromString($baseCol) - 1;
6044  
6045          // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))

6046              $rowIndex = self::_GetInt2d($cellAddressStructure, 0);
6047              $row = self::_GetInt2d($cellAddressStructure, 0) + 1;
6048  
6049          // offset: 2; size: 2; index to column or column offset + relative flags

6050  
6051              // bit: 7-0; mask 0x00FF; column index

6052              $colIndex = 0x00FF & self::_GetInt2d($cellAddressStructure, 2);
6053  
6054              // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)

6055              if (!(0x4000 & self::_GetInt2d($cellAddressStructure, 2))) {
6056                  $column = PHPExcel_Cell::stringFromColumnIndex($colIndex);
6057                  $column = '$' . $column;
6058              } else {
6059                  $colIndex = ($colIndex <= 127) ? $colIndex : $colIndex - 256;
6060                  $column = PHPExcel_Cell::stringFromColumnIndex($baseCol + $colIndex);
6061              }
6062  
6063              // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)

6064              if (!(0x8000 & self::_GetInt2d($cellAddressStructure, 2))) {
6065                  $row = '$' . $row;
6066              } else {
6067                  $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536;
6068                  $row = $baseRow + $rowIndex;
6069              }
6070  
6071          return $column . $row;
6072      }
6073  
6074  
6075      /**

6076       * Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1'

6077       * always fixed range

6078       * section 2.5.14

6079       *

6080       * @param string $subData

6081       * @return string

6082       * @throws PHPExcel_Reader_Exception

6083       */
6084  	private function _readBIFF5CellRangeAddressFixed($subData)
6085      {
6086          // offset: 0; size: 2; index to first row

6087          $fr = self::_GetInt2d($subData, 0) + 1;
6088  
6089          // offset: 2; size: 2; index to last row

6090          $lr = self::_GetInt2d($subData, 2) + 1;
6091  
6092          // offset: 4; size: 1; index to first column

6093          $fc = ord($subData{4});
6094  
6095          // offset: 5; size: 1; index to last column

6096          $lc = ord($subData{5});
6097  
6098          // check values

6099          if ($fr > $lr || $fc > $lc) {
6100              throw new PHPExcel_Reader_Exception('Not a cell range address');
6101          }
6102  
6103          // column index to letter

6104          $fc = PHPExcel_Cell::stringFromColumnIndex($fc);
6105          $lc = PHPExcel_Cell::stringFromColumnIndex($lc);
6106  
6107          if ($fr == $lr and $fc == $lc) {
6108              return "$fc$fr";
6109          }
6110          return "$fc$fr:$lc$lr";
6111      }
6112  
6113  
6114      /**

6115       * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1'

6116       * always fixed range

6117       * section 2.5.14

6118       *

6119       * @param string $subData

6120       * @return string

6121       * @throws PHPExcel_Reader_Exception

6122       */
6123  	private function _readBIFF8CellRangeAddressFixed($subData)
6124      {
6125          // offset: 0; size: 2; index to first row

6126          $fr = self::_GetInt2d($subData, 0) + 1;
6127  
6128          // offset: 2; size: 2; index to last row

6129          $lr = self::_GetInt2d($subData, 2) + 1;
6130  
6131          // offset: 4; size: 2; index to first column

6132          $fc = self::_GetInt2d($subData, 4);
6133  
6134          // offset: 6; size: 2; index to last column

6135          $lc = self::_GetInt2d($subData, 6);
6136  
6137          // check values

6138          if ($fr > $lr || $fc > $lc) {
6139              throw new PHPExcel_Reader_Exception('Not a cell range address');
6140          }
6141  
6142          // column index to letter

6143          $fc = PHPExcel_Cell::stringFromColumnIndex($fc);
6144          $lc = PHPExcel_Cell::stringFromColumnIndex($lc);
6145  
6146          if ($fr == $lr and $fc == $lc) {
6147              return "$fc$fr";
6148          }
6149          return "$fc$fr:$lc$lr";
6150      }
6151  
6152  
6153      /**

6154       * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6'

6155       * there are flags indicating whether column/row index is relative

6156       * section 3.3.4

6157       *

6158       * @param string $subData

6159       * @return string

6160       */
6161  	private function _readBIFF8CellRangeAddress($subData)
6162      {
6163          // todo: if cell range is just a single cell, should this funciton

6164          // not just return e.g. 'A1' and not 'A1:A1' ?

6165  
6166          // offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767))

6167              $fr = self::_GetInt2d($subData, 0) + 1;
6168  
6169          // offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767))

6170              $lr = self::_GetInt2d($subData, 2) + 1;
6171  
6172          // offset: 4; size: 2; index to first column or column offset + relative flags

6173  
6174          // bit: 7-0; mask 0x00FF; column index

6175          $fc = PHPExcel_Cell::stringFromColumnIndex(0x00FF & self::_GetInt2d($subData, 4));
6176  
6177          // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)

6178          if (!(0x4000 & self::_GetInt2d($subData, 4))) {
6179              $fc = '$' . $fc;
6180          }
6181  
6182          // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)

6183          if (!(0x8000 & self::_GetInt2d($subData, 4))) {
6184              $fr = '$' . $fr;
6185          }
6186  
6187          // offset: 6; size: 2; index to last column or column offset + relative flags

6188  
6189          // bit: 7-0; mask 0x00FF; column index

6190          $lc = PHPExcel_Cell::stringFromColumnIndex(0x00FF & self::_GetInt2d($subData, 6));
6191  
6192          // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)

6193          if (!(0x4000 & self::_GetInt2d($subData, 6))) {
6194              $lc = '$' . $lc;
6195          }
6196  
6197          // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)

6198          if (!(0x8000 & self::_GetInt2d($subData, 6))) {
6199              $lr = '$' . $lr;
6200          }
6201  
6202          return "$fc$fr:$lc$lr";
6203      }
6204  
6205  
6206      /**

6207       * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column

6208       * to indicate offsets from a base cell

6209       * section 3.3.4

6210       *

6211       * @param string $subData

6212       * @param string $baseCell Base cell

6213       * @return string Cell range address

6214       */
6215  	private function _readBIFF8CellRangeAddressB($subData, $baseCell = 'A1')
6216      {
6217          list($baseCol, $baseRow) = PHPExcel_Cell::coordinateFromString($baseCell);
6218          $baseCol = PHPExcel_Cell::columnIndexFromString($baseCol) - 1;
6219  
6220          // TODO: if cell range is just a single cell, should this funciton

6221          // not just return e.g. 'A1' and not 'A1:A1' ?

6222  
6223          // offset: 0; size: 2; first row

6224          $frIndex = self::_GetInt2d($subData, 0); // adjust below

6225  
6226          // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767)

6227          $lrIndex = self::_GetInt2d($subData, 2); // adjust below

6228  
6229          // offset: 4; size: 2; first column with relative/absolute flags

6230  
6231          // bit: 7-0; mask 0x00FF; column index

6232          $fcIndex = 0x00FF & self::_GetInt2d($subData, 4);
6233  
6234          // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)

6235          if (!(0x4000 & self::_GetInt2d($subData, 4))) {
6236              // absolute column index

6237              $fc = PHPExcel_Cell::stringFromColumnIndex($fcIndex);
6238              $fc = '$' . $fc;
6239          } else {
6240              // column offset

6241              $fcIndex = ($fcIndex <= 127) ? $fcIndex : $fcIndex - 256;
6242              $fc = PHPExcel_Cell::stringFromColumnIndex($baseCol + $fcIndex);
6243          }
6244  
6245          // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)

6246          if (!(0x8000 & self::_GetInt2d($subData, 4))) {
6247              // absolute row index

6248              $fr = $frIndex + 1;
6249              $fr = '$' . $fr;
6250          } else {
6251              // row offset

6252              $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536;
6253              $fr = $baseRow + $frIndex;
6254          }
6255  
6256          // offset: 6; size: 2; last column with relative/absolute flags

6257  
6258          // bit: 7-0; mask 0x00FF; column index

6259          $lcIndex = 0x00FF & self::_GetInt2d($subData, 6);
6260          $lcIndex = ($lcIndex <= 127) ? $lcIndex : $lcIndex - 256;
6261          $lc = PHPExcel_Cell::stringFromColumnIndex($baseCol + $lcIndex);
6262  
6263          // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)

6264          if (!(0x4000 & self::_GetInt2d($subData, 6))) {
6265              // absolute column index

6266              $lc = PHPExcel_Cell::stringFromColumnIndex($lcIndex);
6267              $lc = '$' . $lc;
6268          } else {
6269              // column offset

6270              $lcIndex = ($lcIndex <= 127) ? $lcIndex : $lcIndex - 256;
6271              $lc = PHPExcel_Cell::stringFromColumnIndex($baseCol + $lcIndex);
6272          }
6273  
6274          // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)

6275          if (!(0x8000 & self::_GetInt2d($subData, 6))) {
6276              // absolute row index

6277              $lr = $lrIndex + 1;
6278              $lr = '$' . $lr;
6279          } else {
6280              // row offset

6281              $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536;
6282              $lr = $baseRow + $lrIndex;
6283          }
6284  
6285          return "$fc$fr:$lc$lr";
6286      }
6287  
6288  
6289      /**

6290       * Read BIFF8 cell range address list

6291       * section 2.5.15

6292       *

6293       * @param string $subData

6294       * @return array

6295       */
6296  	private function _readBIFF8CellRangeAddressList($subData)
6297      {
6298          $cellRangeAddresses = array();
6299  
6300          // offset: 0; size: 2; number of the following cell range addresses

6301          $nm = self::_GetInt2d($subData, 0);
6302  
6303          $offset = 2;
6304          // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses

6305          for ($i = 0; $i < $nm; ++$i) {
6306              $cellRangeAddresses[] = $this->_readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
6307              $offset += 8;
6308          }
6309  
6310          return array(
6311              'size' => 2 + 8 * $nm,
6312              'cellRangeAddresses' => $cellRangeAddresses,
6313          );
6314      }
6315  
6316  
6317      /**

6318       * Read BIFF5 cell range address list

6319       * section 2.5.15

6320       *

6321       * @param string $subData

6322       * @return array

6323       */
6324  	private function _readBIFF5CellRangeAddressList($subData)
6325      {
6326          $cellRangeAddresses = array();
6327  
6328          // offset: 0; size: 2; number of the following cell range addresses

6329          $nm = self::_GetInt2d($subData, 0);
6330  
6331          $offset = 2;
6332          // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses

6333          for ($i = 0; $i < $nm; ++$i) {
6334              $cellRangeAddresses[] = $this->_readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6));
6335              $offset += 6;
6336          }
6337  
6338          return array(
6339              'size' => 2 + 6 * $nm,
6340              'cellRangeAddresses' => $cellRangeAddresses,
6341          );
6342      }
6343  
6344  
6345      /**

6346       * Get a sheet range like Sheet1:Sheet3 from REF index

6347       * Note: If there is only one sheet in the range, one gets e.g Sheet1

6348       * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets,

6349       * in which case an PHPExcel_Reader_Exception is thrown

6350       *

6351       * @param int $index

6352       * @return string|false

6353       * @throws PHPExcel_Reader_Exception

6354       */
6355  	private function _readSheetRangeByRefIndex($index)
6356      {
6357          if (isset($this->_ref[$index])) {
6358  
6359              $type = $this->_externalBooks[$this->_ref[$index]['externalBookIndex']]['type'];
6360  
6361              switch ($type) {
6362                  case 'internal':
6363                      // check if we have a deleted 3d reference

6364                      if ($this->_ref[$index]['firstSheetIndex'] == 0xFFFF or $this->_ref[$index]['lastSheetIndex'] == 0xFFFF) {
6365                          throw new PHPExcel_Reader_Exception('Deleted sheet reference');
6366                      }
6367  
6368                      // we have normal sheet range (collapsed or uncollapsed)

6369                      $firstSheetName = $this->_sheets[$this->_ref[$index]['firstSheetIndex']]['name'];
6370                      $lastSheetName = $this->_sheets[$this->_ref[$index]['lastSheetIndex']]['name'];
6371  
6372                      if ($firstSheetName == $lastSheetName) {
6373                          // collapsed sheet range

6374                          $sheetRange = $firstSheetName;
6375                      } else {
6376                          $sheetRange = "$firstSheetName:$lastSheetName";
6377                      }
6378  
6379                      // escape the single-quotes

6380                      $sheetRange = str_replace("'", "''", $sheetRange);
6381  
6382                      // if there are special characters, we need to enclose the range in single-quotes

6383                      // todo: check if we have identified the whole set of special characters

6384                      // it seems that the following characters are not accepted for sheet names

6385                      // and we may assume that they are not present: []*/:\?

6386                      if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/", $sheetRange)) {
6387                          $sheetRange = "'$sheetRange'";
6388                      }
6389  
6390                      return $sheetRange;
6391                      break;
6392  
6393                  default:
6394                      // TODO: external sheet support

6395                      throw new PHPExcel_Reader_Exception('Excel5 reader only supports internal sheets in fomulas');
6396                      break;
6397              }
6398          }
6399          return false;
6400      }
6401  
6402  
6403      /**

6404       * read BIFF8 constant value array from array data

6405       * returns e.g. array('value' => '{1,2;3,4}', 'size' => 40}

6406       * section 2.5.8

6407       *

6408       * @param string $arrayData

6409       * @return array

6410       */
6411  	private static function _readBIFF8ConstantArray($arrayData)
6412      {
6413          // offset: 0; size: 1; number of columns decreased by 1

6414          $nc = ord($arrayData[0]);
6415  
6416          // offset: 1; size: 2; number of rows decreased by 1

6417          $nr = self::_GetInt2d($arrayData, 1);
6418          $size = 3; // initialize

6419          $arrayData = substr($arrayData, 3);
6420  
6421          // offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values

6422          $matrixChunks = array();
6423          for ($r = 1; $r <= $nr + 1; ++$r) {
6424              $items = array();
6425              for ($c = 1; $c <= $nc + 1; ++$c) {
6426                  $constant = self::_readBIFF8Constant($arrayData);
6427                  $items[] = $constant['value'];
6428                  $arrayData = substr($arrayData, $constant['size']);
6429                  $size += $constant['size'];
6430              }
6431              $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'

6432          }
6433          $matrix = '{' . implode(';', $matrixChunks) . '}';
6434  
6435          return array(
6436              'value' => $matrix,
6437              'size' => $size,
6438          );
6439      }
6440  
6441  
6442      /**

6443       * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value'

6444       * section 2.5.7

6445       * returns e.g. array('value' => '5', 'size' => 9)

6446       *

6447       * @param string $valueData

6448       * @return array

6449       */
6450  	private static function _readBIFF8Constant($valueData)
6451      {
6452          // offset: 0; size: 1; identifier for type of constant

6453          $identifier = ord($valueData[0]);
6454  
6455          switch ($identifier) {
6456          case 0x00: // empty constant (what is this?)
6457              $value = '';
6458              $size = 9;
6459              break;
6460          case 0x01: // number
6461              // offset: 1; size: 8; IEEE 754 floating-point value

6462              $value = self::_extractNumber(substr($valueData, 1, 8));
6463              $size = 9;
6464              break;
6465          case 0x02: // string value
6466              // offset: 1; size: var; Unicode string, 16-bit string length

6467              $string = self::_readUnicodeStringLong(substr($valueData, 1));
6468              $value = '"' . $string['value'] . '"';
6469              $size = 1 + $string['size'];
6470              break;
6471          case 0x04: // boolean
6472              // offset: 1; size: 1; 0 = FALSE, 1 = TRUE

6473              if (ord($valueData[1])) {
6474                  $value = 'TRUE';
6475              } else {
6476                  $value = 'FALSE';
6477              }
6478              $size = 9;
6479              break;
6480          case 0x10: // error code
6481              // offset: 1; size: 1; error code

6482              $value = self::_mapErrorCode(ord($valueData[1]));
6483              $size = 9;
6484              break;
6485          }
6486          return array(
6487              'value' => $value,
6488              'size' => $size,
6489          );
6490      }
6491  
6492  
6493      /**

6494       * Extract RGB color

6495       * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4

6496       *

6497       * @param string $rgb Encoded RGB value (4 bytes)

6498       * @return array

6499       */
6500  	private static function _readRGB($rgb)
6501      {
6502          // offset: 0; size 1; Red component

6503          $r = ord($rgb{0});
6504  
6505          // offset: 1; size: 1; Green component

6506          $g = ord($rgb{1});
6507  
6508          // offset: 2; size: 1; Blue component

6509          $b = ord($rgb{2});
6510  
6511          // HEX notation, e.g. 'FF00FC'

6512          $rgb = sprintf('%02X%02X%02X', $r, $g, $b);
6513  
6514          return array('rgb' => $rgb);
6515      }
6516  
6517  
6518      /**

6519       * Read byte string (8-bit string length)

6520       * OpenOffice documentation: 2.5.2

6521       *

6522       * @param string $subData

6523       * @return array

6524       */
6525  	private function _readByteStringShort($subData)
6526      {
6527          // offset: 0; size: 1; length of the string (character count)

6528          $ln = ord($subData[0]);
6529  
6530          // offset: 1: size: var; character array (8-bit characters)

6531          $value = $this->_decodeCodepage(substr($subData, 1, $ln));
6532  
6533          return array(
6534              'value' => $value,
6535              'size' => 1 + $ln, // size in bytes of data structure
6536          );
6537      }
6538  
6539  
6540      /**

6541       * Read byte string (16-bit string length)

6542       * OpenOffice documentation: 2.5.2

6543       *

6544       * @param string $subData

6545       * @return array

6546       */
6547  	private function _readByteStringLong($subData)
6548      {
6549          // offset: 0; size: 2; length of the string (character count)

6550          $ln = self::_GetInt2d($subData, 0);
6551  
6552          // offset: 2: size: var; character array (8-bit characters)

6553          $value = $this->_decodeCodepage(substr($subData, 2));
6554  
6555          //return $string;

6556          return array(
6557              'value' => $value,
6558              'size' => 2 + $ln, // size in bytes of data structure
6559          );
6560      }
6561  
6562  
6563      /**

6564       * Extracts an Excel Unicode short string (8-bit string length)

6565       * OpenOffice documentation: 2.5.3

6566       * function will automatically find out where the Unicode string ends.

6567       *

6568       * @param string $subData

6569       * @return array

6570       */
6571  	private static function _readUnicodeStringShort($subData)
6572      {
6573          $value = '';
6574  
6575          // offset: 0: size: 1; length of the string (character count)

6576          $characterCount = ord($subData[0]);
6577  
6578          $string = self::_readUnicodeString(substr($subData, 1), $characterCount);
6579  
6580          // add 1 for the string length

6581          $string['size'] += 1;
6582  
6583          return $string;
6584      }
6585  
6586  
6587      /**

6588       * Extracts an Excel Unicode long string (16-bit string length)

6589       * OpenOffice documentation: 2.5.3

6590       * this function is under construction, needs to support rich text, and Asian phonetic settings

6591       *

6592       * @param string $subData

6593       * @return array

6594       */
6595  	private static function _readUnicodeStringLong($subData)
6596      {
6597          $value = '';
6598  
6599          // offset: 0: size: 2; length of the string (character count)

6600          $characterCount = self::_GetInt2d($subData, 0);
6601  
6602          $string = self::_readUnicodeString(substr($subData, 2), $characterCount);
6603  
6604          // add 2 for the string length

6605          $string['size'] += 2;
6606  
6607          return $string;
6608      }
6609  
6610  
6611      /**

6612       * Read Unicode string with no string length field, but with known character count

6613       * this function is under construction, needs to support rich text, and Asian phonetic settings

6614       * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3

6615       *

6616       * @param string $subData

6617       * @param int $characterCount

6618       * @return array

6619       */
6620  	private static function _readUnicodeString($subData, $characterCount)
6621      {
6622          $value = '';
6623  
6624          // offset: 0: size: 1; option flags

6625  
6626              // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)

6627              $isCompressed = !((0x01 & ord($subData[0])) >> 0);
6628  
6629              // bit: 2; mask: 0x04; Asian phonetic settings

6630              $hasAsian = (0x04) & ord($subData[0]) >> 2;
6631  
6632              // bit: 3; mask: 0x08; Rich-Text settings

6633              $hasRichText = (0x08) & ord($subData[0]) >> 3;
6634  
6635          // offset: 1: size: var; character array

6636          // this offset assumes richtext and Asian phonetic settings are off which is generally wrong

6637          // needs to be fixed

6638          $value = self::_encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed);
6639  
6640          return array(
6641              'value' => $value,
6642              'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags
6643          );
6644      }
6645  
6646  
6647      /**

6648       * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas.

6649       * Example:  hello"world  -->  "hello""world"

6650       *

6651       * @param string $value UTF-8 encoded string

6652       * @return string

6653       */
6654  	private static function _UTF8toExcelDoubleQuoted($value)
6655      {
6656          return '"' . str_replace('"', '""', $value) . '"';
6657      }
6658  
6659  
6660      /**

6661       * Reads first 8 bytes of a string and return IEEE 754 float

6662       *

6663       * @param string $data Binary string that is at least 8 bytes long

6664       * @return float

6665       */
6666  	private static function _extractNumber($data)
6667      {
6668          $rknumhigh = self::_GetInt4d($data, 4);
6669          $rknumlow = self::_GetInt4d($data, 0);
6670          $sign = ($rknumhigh & 0x80000000) >> 31;
6671          $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023;
6672          $mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
6673          $mantissalow1 = ($rknumlow & 0x80000000) >> 31;
6674          $mantissalow2 = ($rknumlow & 0x7fffffff);
6675          $value = $mantissa / pow( 2 , (20 - $exp));
6676  
6677          if ($mantissalow1 != 0) {
6678              $value += 1 / pow (2 , (21 - $exp));
6679          }
6680  
6681          $value += $mantissalow2 / pow (2 , (52 - $exp));
6682          if ($sign) {
6683              $value *= -1;
6684          }
6685  
6686          return $value;
6687      }
6688  
6689  
6690  	private static function _GetIEEE754($rknum)
6691      {
6692          if (($rknum & 0x02) != 0) {
6693              $value = $rknum >> 2;
6694          } else {
6695              // changes by mmp, info on IEEE754 encoding from

6696              // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html

6697              // The RK format calls for using only the most significant 30 bits

6698              // of the 64 bit floating point value. The other 34 bits are assumed

6699              // to be 0 so we use the upper 30 bits of $rknum as follows...

6700              $sign = ($rknum & 0x80000000) >> 31;
6701              $exp = ($rknum & 0x7ff00000) >> 20;
6702              $mantissa = (0x100000 | ($rknum & 0x000ffffc));
6703              $value = $mantissa / pow( 2 , (20- ($exp - 1023)));
6704              if ($sign) {
6705                  $value = -1 * $value;
6706              }
6707              //end of changes by mmp

6708          }
6709          if (($rknum & 0x01) != 0) {
6710              $value /= 100;
6711          }
6712          return $value;
6713      }
6714  
6715  
6716      /**

6717       * Get UTF-8 string from (compressed or uncompressed) UTF-16 string

6718       *

6719       * @param string $string

6720       * @param bool $compressed

6721       * @return string

6722       */
6723  	private static function _encodeUTF16($string, $compressed = '')
6724      {
6725          if ($compressed) {
6726              $string = self::_uncompressByteString($string);
6727           }
6728  
6729          return PHPExcel_Shared_String::ConvertEncoding($string, 'UTF-8', 'UTF-16LE');
6730      }
6731  
6732  
6733      /**

6734       * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8.

6735       *

6736       * @param string $string

6737       * @return string

6738       */
6739  	private static function _uncompressByteString($string)
6740      {
6741          $uncompressedString = '';
6742          $strLen = strlen($string);
6743          for ($i = 0; $i < $strLen; ++$i) {
6744              $uncompressedString .= $string[$i] . "\0";
6745          }
6746  
6747          return $uncompressedString;
6748      }
6749  
6750  
6751      /**

6752       * Convert string to UTF-8. Only used for BIFF5.

6753       *

6754       * @param string $string

6755       * @return string

6756       */
6757  	private function _decodeCodepage($string)
6758      {
6759          return PHPExcel_Shared_String::ConvertEncoding($string, 'UTF-8', $this->_codepage);
6760      }
6761  
6762  
6763      /**

6764       * Read 16-bit unsigned integer

6765       *

6766       * @param string $data

6767       * @param int $pos

6768       * @return int

6769       */
6770  	public static function _GetInt2d($data, $pos)
6771      {
6772          return ord($data[$pos]) | (ord($data[$pos+1]) << 8);
6773      }
6774  
6775  
6776      /**

6777       * Read 32-bit signed integer

6778       *

6779       * @param string $data

6780       * @param int $pos

6781       * @return int

6782       */
6783  	public static function _GetInt4d($data, $pos)
6784      {
6785          // FIX: represent numbers correctly on 64-bit system

6786          // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334

6787          // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems

6788          $_or_24 = ord($data[$pos + 3]);
6789          if ($_or_24 >= 128) {
6790              // negative number

6791              $_ord_24 = -abs((256 - $_or_24) << 24);
6792          } else {
6793              $_ord_24 = ($_or_24 & 127) << 24;
6794          }
6795          return ord($data[$pos]) | (ord($data[$pos+1]) << 8) | (ord($data[$pos+2]) << 16) | $_ord_24;
6796      }
6797  
6798  
6799      /**

6800       * Read color

6801       *

6802       * @param int $color Indexed color

6803       * @param array $palette Color palette

6804       * @return array RGB color value, example: array('rgb' => 'FF0000')

6805       */
6806  	private static function _readColor($color,$palette,$version)
6807      {
6808          if ($color <= 0x07 || $color >= 0x40) {
6809              // special built-in color

6810              return self::_mapBuiltInColor($color);
6811          } elseif (isset($palette) && isset($palette[$color - 8])) {
6812              // palette color, color index 0x08 maps to pallete index 0

6813              return $palette[$color - 8];
6814          } else {
6815              // default color table

6816              if ($version == self::XLS_BIFF8) {
6817                  return self::_mapColor($color);
6818              } else {
6819                  // BIFF5

6820                  return self::_mapColorBIFF5($color);
6821              }
6822          }
6823  
6824          return $color;
6825      }
6826  
6827  
6828      /**

6829       * Map border style

6830       * OpenOffice documentation: 2.5.11

6831       *

6832       * @param int $index

6833       * @return string

6834       */
6835  	private static function _mapBorderStyle($index)
6836      {
6837          switch ($index) {
6838              case 0x00: return PHPExcel_Style_Border::BORDER_NONE;
6839              case 0x01: return PHPExcel_Style_Border::BORDER_THIN;
6840              case 0x02: return PHPExcel_Style_Border::BORDER_MEDIUM;
6841              case 0x03: return PHPExcel_Style_Border::BORDER_DASHED;
6842              case 0x04: return PHPExcel_Style_Border::BORDER_DOTTED;
6843              case 0x05: return PHPExcel_Style_Border::BORDER_THICK;
6844              case 0x06: return PHPExcel_Style_Border::BORDER_DOUBLE;
6845              case 0x07: return PHPExcel_Style_Border::BORDER_HAIR;
6846              case 0x08: return PHPExcel_Style_Border::BORDER_MEDIUMDASHED;
6847              case 0x09: return PHPExcel_Style_Border::BORDER_DASHDOT;
6848              case 0x0A: return PHPExcel_Style_Border::BORDER_MEDIUMDASHDOT;
6849              case 0x0B: return PHPExcel_Style_Border::BORDER_DASHDOTDOT;
6850              case 0x0C: return PHPExcel_Style_Border::BORDER_MEDIUMDASHDOTDOT;
6851              case 0x0D: return PHPExcel_Style_Border::BORDER_SLANTDASHDOT;
6852              default:   return PHPExcel_Style_Border::BORDER_NONE;
6853          }
6854      }
6855  
6856  
6857      /**

6858       * Get fill pattern from index

6859       * OpenOffice documentation: 2.5.12

6860       *

6861       * @param int $index

6862       * @return string

6863       */
6864  	private static function _mapFillPattern($index)
6865      {
6866          switch ($index) {
6867              case 0x00: return PHPExcel_Style_Fill::FILL_NONE;
6868              case 0x01: return PHPExcel_Style_Fill::FILL_SOLID;
6869              case 0x02: return PHPExcel_Style_Fill::FILL_PATTERN_MEDIUMGRAY;
6870              case 0x03: return PHPExcel_Style_Fill::FILL_PATTERN_DARKGRAY;
6871              case 0x04: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTGRAY;
6872              case 0x05: return PHPExcel_Style_Fill::FILL_PATTERN_DARKHORIZONTAL;
6873              case 0x06: return PHPExcel_Style_Fill::FILL_PATTERN_DARKVERTICAL;
6874              case 0x07: return PHPExcel_Style_Fill::FILL_PATTERN_DARKDOWN;
6875              case 0x08: return PHPExcel_Style_Fill::FILL_PATTERN_DARKUP;
6876              case 0x09: return PHPExcel_Style_Fill::FILL_PATTERN_DARKGRID;
6877              case 0x0A: return PHPExcel_Style_Fill::FILL_PATTERN_DARKTRELLIS;
6878              case 0x0B: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTHORIZONTAL;
6879              case 0x0C: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTVERTICAL;
6880              case 0x0D: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTDOWN;
6881              case 0x0E: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTUP;
6882              case 0x0F: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTGRID;
6883              case 0x10: return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTTRELLIS;
6884              case 0x11: return PHPExcel_Style_Fill::FILL_PATTERN_GRAY125;
6885              case 0x12: return PHPExcel_Style_Fill::FILL_PATTERN_GRAY0625;
6886              default:   return PHPExcel_Style_Fill::FILL_NONE;
6887          }
6888      }
6889  
6890  
6891      /**

6892       * Map error code, e.g. '#N/A'

6893       *

6894       * @param int $subData

6895       * @return string

6896       */
6897  	private static function _mapErrorCode($subData)
6898      {
6899          switch ($subData) {
6900              case 0x00: return '#NULL!';        break;
6901              case 0x07: return '#DIV/0!';    break;
6902              case 0x0F: return '#VALUE!';    break;
6903              case 0x17: return '#REF!';        break;
6904              case 0x1D: return '#NAME?';        break;
6905              case 0x24: return '#NUM!';        break;
6906              case 0x2A: return '#N/A';        break;
6907              default: return false;
6908          }
6909      }
6910  
6911  
6912      /**

6913       * Map built-in color to RGB value

6914       *

6915       * @param int $color Indexed color

6916       * @return array

6917       */
6918  	private static function _mapBuiltInColor($color)
6919      {
6920          switch ($color) {
6921              case 0x00: return array('rgb' => '000000');
6922              case 0x01: return array('rgb' => 'FFFFFF');
6923              case 0x02: return array('rgb' => 'FF0000');
6924              case 0x03: return array('rgb' => '00FF00');
6925              case 0x04: return array('rgb' => '0000FF');
6926              case 0x05: return array('rgb' => 'FFFF00');
6927              case 0x06: return array('rgb' => 'FF00FF');
6928              case 0x07: return array('rgb' => '00FFFF');
6929              case 0x40: return array('rgb' => '000000'); // system window text color

6930              case 0x41: return array('rgb' => 'FFFFFF'); // system window background color

6931              default:   return array('rgb' => '000000');
6932          }
6933      }
6934  
6935  
6936      /**

6937       * Map color array from BIFF5 built-in color index

6938       *

6939       * @param int $subData

6940       * @return array

6941       */
6942  	private static function _mapColorBIFF5($subData)
6943      {
6944          switch ($subData) {
6945              case 0x08: return array('rgb' => '000000');
6946              case 0x09: return array('rgb' => 'FFFFFF');
6947              case 0x0A: return array('rgb' => 'FF0000');
6948              case 0x0B: return array('rgb' => '00FF00');
6949              case 0x0C: return array('rgb' => '0000FF');
6950              case 0x0D: return array('rgb' => 'FFFF00');
6951              case 0x0E: return array('rgb' => 'FF00FF');
6952              case 0x0F: return array('rgb' => '00FFFF');
6953              case 0x10: return array('rgb' => '800000');
6954              case 0x11: return array('rgb' => '008000');
6955              case 0x12: return array('rgb' => '000080');
6956              case 0x13: return array('rgb' => '808000');
6957              case 0x14: return array('rgb' => '800080');
6958              case 0x15: return array('rgb' => '008080');
6959              case 0x16: return array('rgb' => 'C0C0C0');
6960              case 0x17: return array('rgb' => '808080');
6961              case 0x18: return array('rgb' => '8080FF');
6962              case 0x19: return array('rgb' => '802060');
6963              case 0x1A: return array('rgb' => 'FFFFC0');
6964              case 0x1B: return array('rgb' => 'A0E0F0');
6965              case 0x1C: return array('rgb' => '600080');
6966              case 0x1D: return array('rgb' => 'FF8080');
6967              case 0x1E: return array('rgb' => '0080C0');
6968              case 0x1F: return array('rgb' => 'C0C0FF');
6969              case 0x20: return array('rgb' => '000080');
6970              case 0x21: return array('rgb' => 'FF00FF');
6971              case 0x22: return array('rgb' => 'FFFF00');
6972              case 0x23: return array('rgb' => '00FFFF');
6973              case 0x24: return array('rgb' => '800080');
6974              case 0x25: return array('rgb' => '800000');
6975              case 0x26: return array('rgb' => '008080');
6976              case 0x27: return array('rgb' => '0000FF');
6977              case 0x28: return array('rgb' => '00CFFF');
6978              case 0x29: return array('rgb' => '69FFFF');
6979              case 0x2A: return array('rgb' => 'E0FFE0');
6980              case 0x2B: return array('rgb' => 'FFFF80');
6981              case 0x2C: return array('rgb' => 'A6CAF0');
6982              case 0x2D: return array('rgb' => 'DD9CB3');
6983              case 0x2E: return array('rgb' => 'B38FEE');
6984              case 0x2F: return array('rgb' => 'E3E3E3');
6985              case 0x30: return array('rgb' => '2A6FF9');
6986              case 0x31: return array('rgb' => '3FB8CD');
6987              case 0x32: return array('rgb' => '488436');
6988              case 0x33: return array('rgb' => '958C41');
6989              case 0x34: return array('rgb' => '8E5E42');
6990              case 0x35: return array('rgb' => 'A0627A');
6991              case 0x36: return array('rgb' => '624FAC');
6992              case 0x37: return array('rgb' => '969696');
6993              case 0x38: return array('rgb' => '1D2FBE');
6994              case 0x39: return array('rgb' => '286676');
6995              case 0x3A: return array('rgb' => '004500');
6996              case 0x3B: return array('rgb' => '453E01');
6997              case 0x3C: return array('rgb' => '6A2813');
6998              case 0x3D: return array('rgb' => '85396A');
6999              case 0x3E: return array('rgb' => '4A3285');
7000              case 0x3F: return array('rgb' => '424242');
7001              default:   return array('rgb' => '000000');
7002          }
7003      }
7004  
7005  
7006      /**

7007       * Map color array from BIFF8 built-in color index

7008       *

7009       * @param int $subData

7010       * @return array

7011       */
7012  	private static function _mapColor($subData)
7013      {
7014          switch ($subData) {
7015              case 0x08: return array('rgb' => '000000');
7016              case 0x09: return array('rgb' => 'FFFFFF');
7017              case 0x0A: return array('rgb' => 'FF0000');
7018              case 0x0B: return array('rgb' => '00FF00');
7019              case 0x0C: return array('rgb' => '0000FF');
7020              case 0x0D: return array('rgb' => 'FFFF00');
7021              case 0x0E: return array('rgb' => 'FF00FF');
7022              case 0x0F: return array('rgb' => '00FFFF');
7023              case 0x10: return array('rgb' => '800000');
7024              case 0x11: return array('rgb' => '008000');
7025              case 0x12: return array('rgb' => '000080');
7026              case 0x13: return array('rgb' => '808000');
7027              case 0x14: return array('rgb' => '800080');
7028              case 0x15: return array('rgb' => '008080');
7029              case 0x16: return array('rgb' => 'C0C0C0');
7030              case 0x17: return array('rgb' => '808080');
7031              case 0x18: return array('rgb' => '9999FF');
7032              case 0x19: return array('rgb' => '993366');
7033              case 0x1A: return array('rgb' => 'FFFFCC');
7034              case 0x1B: return array('rgb' => 'CCFFFF');
7035              case 0x1C: return array('rgb' => '660066');
7036              case 0x1D: return array('rgb' => 'FF8080');
7037              case 0x1E: return array('rgb' => '0066CC');
7038              case 0x1F: return array('rgb' => 'CCCCFF');
7039              case 0x20: return array('rgb' => '000080');
7040              case 0x21: return array('rgb' => 'FF00FF');
7041              case 0x22: return array('rgb' => 'FFFF00');
7042              case 0x23: return array('rgb' => '00FFFF');
7043              case 0x24: return array('rgb' => '800080');
7044              case 0x25: return array('rgb' => '800000');
7045              case 0x26: return array('rgb' => '008080');
7046              case 0x27: return array('rgb' => '0000FF');
7047              case 0x28: return array('rgb' => '00CCFF');
7048              case 0x29: return array('rgb' => 'CCFFFF');
7049              case 0x2A: return array('rgb' => 'CCFFCC');
7050              case 0x2B: return array('rgb' => 'FFFF99');
7051              case 0x2C: return array('rgb' => '99CCFF');
7052              case 0x2D: return array('rgb' => 'FF99CC');
7053              case 0x2E: return array('rgb' => 'CC99FF');
7054              case 0x2F: return array('rgb' => 'FFCC99');
7055              case 0x30: return array('rgb' => '3366FF');
7056              case 0x31: return array('rgb' => '33CCCC');
7057              case 0x32: return array('rgb' => '99CC00');
7058              case 0x33: return array('rgb' => 'FFCC00');
7059              case 0x34: return array('rgb' => 'FF9900');
7060              case 0x35: return array('rgb' => 'FF6600');
7061              case 0x36: return array('rgb' => '666699');
7062              case 0x37: return array('rgb' => '969696');
7063              case 0x38: return array('rgb' => '003366');
7064              case 0x39: return array('rgb' => '339966');
7065              case 0x3A: return array('rgb' => '003300');
7066              case 0x3B: return array('rgb' => '333300');
7067              case 0x3C: return array('rgb' => '993300');
7068              case 0x3D: return array('rgb' => '993366');
7069              case 0x3E: return array('rgb' => '333399');
7070              case 0x3F: return array('rgb' => '333333');
7071              default:   return array('rgb' => '000000');
7072          }
7073      }
7074  
7075  
7076  	private function _parseRichText($is = '') {
7077          $value = new PHPExcel_RichText();
7078  
7079          $value->createText($is);
7080  
7081          return $value;
7082      }
7083  
7084  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1