[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

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

   1  <?php
   2  
   3  /** PHPExcel root directory */
   4  if (!defined('PHPEXCEL_ROOT')) {
   5      /**
   6       * @ignore
   7       */
   8      define('PHPEXCEL_ROOT', dirname(__FILE__) . '/../../');
   9      require (PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php');
  10  }
  11  
  12  /**
  13   * PHPExcel_Reader_Excel5
  14   *
  15   * Copyright (c) 2006 - 2015 PHPExcel
  16   *
  17   * This library is free software; you can redistribute it and/or
  18   * modify it under the terms of the GNU Lesser General Public
  19   * License as published by the Free Software Foundation; either
  20   * version 2.1 of the License, or (at your option) any later version.
  21   *
  22   * This library is distributed in the hope that it will be useful,
  23   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  25   * Lesser General Public License for more details.
  26   *
  27   * You should have received a copy of the GNU Lesser General Public
  28   * License along with this library; if not, write to the Free Software
  29   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  30   *
  31   * @category   PHPExcel
  32   * @package    PHPExcel_Reader_Excel5
  33   * @copyright  Copyright (c) 2006 - 2015 PHPExcel (http://www.codeplex.com/PHPExcel)
  34   * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt    LGPL
  35   * @version    ##VERSION##, ##DATE##
  36   */
  37  
  38  // Original file header of ParseXL (used as the base for this class):
  39  // --------------------------------------------------------------------------------
  40  // Adapted from Excel_Spreadsheet_Reader developed by users bizon153,
  41  // trex005, and mmp11 (SourceForge.net)
  42  // http://sourceforge.net/projects/phpexcelreader/
  43  // Primary changes made by canyoncasa (dvc) for ParseXL 1.00 ...
  44  //     Modelled moreso after Perl Excel Parse/Write modules
  45  //     Added Parse_Excel_Spreadsheet object
  46  //         Reads a whole worksheet or tab as row,column array or as
  47  //         associated hash of indexed rows and named column fields
  48  //     Added variables for worksheet (tab) indexes and names
  49  //     Added an object call for loading individual woorksheets
  50  //     Changed default indexing defaults to 0 based arrays
  51  //     Fixed date/time and percent formats
  52  //     Includes patches found at SourceForge...
  53  //         unicode patch by nobody
  54  //         unpack("d") machine depedency patch by matchy
  55  //         boundsheet utf16 patch by bjaenichen
  56  //     Renamed functions for shorter names
  57  //     General code cleanup and rigor, including <80 column width
  58  //     Included a testcase Excel file and PHP example calls
  59  //     Code works for PHP 5.x
  60  
  61  // Primary changes made by canyoncasa (dvc) for ParseXL 1.10 ...
  62  // http://sourceforge.net/tracker/index.php?func=detail&aid=1466964&group_id=99160&atid=623334
  63  //     Decoding of formula conditions, results, and tokens.
  64  //     Support for user-defined named cells added as an array "namedcells"
  65  //         Patch code for user-defined named cells supports single cells only.
  66  //         NOTE: this patch only works for BIFF8 as BIFF5-7 use a different
  67  //         external sheet reference structure
  68  class PHPExcel_Reader_Excel5 extends PHPExcel_Reader_Abstract implements PHPExcel_Reader_IReader
  69  {
  70      // ParseXL definitions
  71      const XLS_BIFF8                     = 0x0600;
  72      const XLS_BIFF7                     = 0x0500;
  73      const XLS_WorkbookGlobals           = 0x0005;
  74      const XLS_Worksheet                 = 0x0010;
  75  
  76      // record identifiers
  77      const XLS_TYPE_FORMULA              = 0x0006;
  78      const XLS_TYPE_EOF                  = 0x000a;
  79      const XLS_TYPE_PROTECT              = 0x0012;
  80      const XLS_TYPE_OBJECTPROTECT        = 0x0063;
  81      const XLS_TYPE_SCENPROTECT          = 0x00dd;
  82      const XLS_TYPE_PASSWORD             = 0x0013;
  83      const XLS_TYPE_HEADER               = 0x0014;
  84      const XLS_TYPE_FOOTER               = 0x0015;
  85      const XLS_TYPE_EXTERNSHEET          = 0x0017;
  86      const XLS_TYPE_DEFINEDNAME          = 0x0018;
  87      const XLS_TYPE_VERTICALPAGEBREAKS   = 0x001a;
  88      const XLS_TYPE_HORIZONTALPAGEBREAKS = 0x001b;
  89      const XLS_TYPE_NOTE                 = 0x001c;
  90      const XLS_TYPE_SELECTION            = 0x001d;
  91      const XLS_TYPE_DATEMODE             = 0x0022;
  92      const XLS_TYPE_EXTERNNAME           = 0x0023;
  93      const XLS_TYPE_LEFTMARGIN           = 0x0026;
  94      const XLS_TYPE_RIGHTMARGIN          = 0x0027;
  95      const XLS_TYPE_TOPMARGIN            = 0x0028;
  96      const XLS_TYPE_BOTTOMMARGIN         = 0x0029;
  97      const XLS_TYPE_PRINTGRIDLINES       = 0x002b;
  98      const XLS_TYPE_FILEPASS             = 0x002f;
  99      const XLS_TYPE_FONT                 = 0x0031;
 100      const XLS_TYPE_CONTINUE             = 0x003c;
 101      const XLS_TYPE_PANE                 = 0x0041;
 102      const XLS_TYPE_CODEPAGE             = 0x0042;
 103      const XLS_TYPE_DEFCOLWIDTH          = 0x0055;
 104      const XLS_TYPE_OBJ                  = 0x005d;
 105      const XLS_TYPE_COLINFO              = 0x007d;
 106      const XLS_TYPE_IMDATA               = 0x007f;
 107      const XLS_TYPE_SHEETPR              = 0x0081;
 108      const XLS_TYPE_HCENTER              = 0x0083;
 109      const XLS_TYPE_VCENTER              = 0x0084;
 110      const XLS_TYPE_SHEET                = 0x0085;
 111      const XLS_TYPE_PALETTE              = 0x0092;
 112      const XLS_TYPE_SCL                  = 0x00a0;
 113      const XLS_TYPE_PAGESETUP            = 0x00a1;
 114      const XLS_TYPE_MULRK                = 0x00bd;
 115      const XLS_TYPE_MULBLANK             = 0x00be;
 116      const XLS_TYPE_DBCELL               = 0x00d7;
 117      const XLS_TYPE_XF                   = 0x00e0;
 118      const XLS_TYPE_MERGEDCELLS          = 0x00e5;
 119      const XLS_TYPE_MSODRAWINGGROUP      = 0x00eb;
 120      const XLS_TYPE_MSODRAWING           = 0x00ec;
 121      const XLS_TYPE_SST                  = 0x00fc;
 122      const XLS_TYPE_LABELSST             = 0x00fd;
 123      const XLS_TYPE_EXTSST               = 0x00ff;
 124      const XLS_TYPE_EXTERNALBOOK         = 0x01ae;
 125      const XLS_TYPE_DATAVALIDATIONS      = 0x01b2;
 126      const XLS_TYPE_TXO                  = 0x01b6;
 127      const XLS_TYPE_HYPERLINK            = 0x01b8;
 128      const XLS_TYPE_DATAVALIDATION       = 0x01be;
 129      const XLS_TYPE_DIMENSION            = 0x0200;
 130      const XLS_TYPE_BLANK                = 0x0201;
 131      const XLS_TYPE_NUMBER               = 0x0203;
 132      const XLS_TYPE_LABEL                = 0x0204;
 133      const XLS_TYPE_BOOLERR              = 0x0205;
 134      const XLS_TYPE_STRING               = 0x0207;
 135      const XLS_TYPE_ROW                  = 0x0208;
 136      const XLS_TYPE_INDEX                = 0x020b;
 137      const XLS_TYPE_ARRAY                = 0x0221;
 138      const XLS_TYPE_DEFAULTROWHEIGHT     = 0x0225;
 139      const XLS_TYPE_WINDOW2              = 0x023e;
 140      const XLS_TYPE_RK                   = 0x027e;
 141      const XLS_TYPE_STYLE                = 0x0293;
 142      const XLS_TYPE_FORMAT               = 0x041e;
 143      const XLS_TYPE_SHAREDFMLA           = 0x04bc;
 144      const XLS_TYPE_BOF                  = 0x0809;
 145      const XLS_TYPE_SHEETPROTECTION      = 0x0867;
 146      const XLS_TYPE_RANGEPROTECTION      = 0x0868;
 147      const XLS_TYPE_SHEETLAYOUT          = 0x0862;
 148      const XLS_TYPE_XFEXT                = 0x087d;
 149      const XLS_TYPE_PAGELAYOUTVIEW       = 0x088b;
 150      const XLS_TYPE_UNKNOWN              = 0xffff;
 151  
 152      // Encryption type
 153      const MS_BIFF_CRYPTO_NONE           = 0;
 154      const MS_BIFF_CRYPTO_XOR            = 1;
 155      const MS_BIFF_CRYPTO_RC4            = 2;
 156      
 157      // Size of stream blocks when using RC4 encryption
 158      const REKEY_BLOCK                   = 0x400;
 159  
 160      /**
 161       * Summary Information stream data.
 162       *
 163       * @var string
 164       */
 165      private $summaryInformation;
 166  
 167      /**
 168       * Extended Summary Information stream data.
 169       *
 170       * @var string
 171       */
 172      private $documentSummaryInformation;
 173  
 174      /**
 175       * User-Defined Properties stream data.
 176       *
 177       * @var string
 178       */
 179      private $userDefinedProperties;
 180  
 181      /**
 182       * Workbook stream data. (Includes workbook globals substream as well as sheet substreams)
 183       *
 184       * @var string
 185       */
 186      private $data;
 187  
 188      /**
 189       * Size in bytes of $this->data
 190       *
 191       * @var int
 192       */
 193      private $dataSize;
 194  
 195      /**
 196       * Current position in stream
 197       *
 198       * @var integer
 199       */
 200      private $pos;
 201  
 202      /**
 203       * Workbook to be returned by the reader.
 204       *
 205       * @var PHPExcel
 206       */
 207      private $phpExcel;
 208  
 209      /**
 210       * Worksheet that is currently being built by the reader.
 211       *
 212       * @var PHPExcel_Worksheet
 213       */
 214      private $phpSheet;
 215  
 216      /**
 217       * BIFF version
 218       *
 219       * @var int
 220       */
 221      private $version;
 222  
 223      /**
 224       * Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95)
 225       * For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'
 226       *
 227       * @var string
 228       */
 229      private $codepage;
 230  
 231      /**
 232       * Shared formats
 233       *
 234       * @var array
 235       */
 236      private $formats;
 237  
 238      /**
 239       * Shared fonts
 240       *
 241       * @var array
 242       */
 243      private $objFonts;
 244  
 245      /**
 246       * Color palette
 247       *
 248       * @var array
 249       */
 250      private $palette;
 251  
 252      /**
 253       * Worksheets
 254       *
 255       * @var array
 256       */
 257      private $sheets;
 258  
 259      /**
 260       * External books
 261       *
 262       * @var array
 263       */
 264      private $externalBooks;
 265  
 266      /**
 267       * REF structures. Only applies to BIFF8.
 268       *
 269       * @var array
 270       */
 271      private $ref;
 272  
 273      /**
 274       * External names
 275       *
 276       * @var array
 277       */
 278      private $externalNames;
 279  
 280      /**
 281       * Defined names
 282       *
 283       * @var array
 284       */
 285      private $definedname;
 286  
 287      /**
 288       * Shared strings. Only applies to BIFF8.
 289       *
 290       * @var array
 291       */
 292      private $sst;
 293  
 294      /**
 295       * Panes are frozen? (in sheet currently being read). See WINDOW2 record.
 296       *
 297       * @var boolean
 298       */
 299      private $frozen;
 300  
 301      /**
 302       * Fit printout to number of pages? (in sheet currently being read). See SHEETPR record.
 303       *
 304       * @var boolean
 305       */
 306      private $isFitToPages;
 307  
 308      /**
 309       * Objects. One OBJ record contributes with one entry.
 310       *
 311       * @var array
 312       */
 313      private $objs;
 314  
 315      /**
 316       * Text Objects. One TXO record corresponds with one entry.
 317       *
 318       * @var array
 319       */
 320      private $textObjects;
 321  
 322      /**
 323       * Cell Annotations (BIFF8)
 324       *
 325       * @var array
 326       */
 327      private $cellNotes;
 328  
 329      /**
 330       * The combined MSODRAWINGGROUP data
 331       *
 332       * @var string
 333       */
 334      private $drawingGroupData;
 335  
 336      /**
 337       * The combined MSODRAWING data (per sheet)
 338       *
 339       * @var string
 340       */
 341      private $drawingData;
 342  
 343      /**
 344       * Keep track of XF index
 345       *
 346       * @var int
 347       */
 348      private $xfIndex;
 349  
 350      /**
 351       * Mapping of XF index (that is a cell XF) to final index in cellXf collection
 352       *
 353       * @var array
 354       */
 355      private $mapCellXfIndex;
 356  
 357      /**
 358       * Mapping of XF index (that is a style XF) to final index in cellStyleXf collection
 359       *
 360       * @var array
 361       */
 362      private $mapCellStyleXfIndex;
 363  
 364      /**
 365       * The shared formulas in a sheet. One SHAREDFMLA record contributes with one value.
 366       *
 367       * @var array
 368       */
 369      private $sharedFormulas;
 370  
 371      /**
 372       * The shared formula parts in a sheet. One FORMULA record contributes with one value if it
 373       * refers to a shared formula.
 374       *
 375       * @var array
 376       */
 377      private $sharedFormulaParts;
 378  
 379      /**
 380       * The type of encryption in use
 381       *
 382       * @var int
 383       */
 384      private $encryption = 0;
 385      
 386      /**
 387       * The position in the stream after which contents are encrypted
 388       *
 389       * @var int
 390       */
 391      private $encryptionStartPos = false;
 392  
 393      /**
 394       * The current RC4 decryption object
 395       *
 396       * @var PHPExcel_Reader_Excel5_RC4
 397       */
 398      private $rc4Key = null;
 399  
 400      /**
 401       * The position in the stream that the RC4 decryption object was left at
 402       *
 403       * @var int
 404       */
 405      private $rc4Pos = 0;
 406  
 407      /**
 408       * The current MD5 context state
 409       *
 410       * @var string
 411       */
 412      private $md5Ctxt = null;
 413  
 414      /**
 415       * Create a new PHPExcel_Reader_Excel5 instance
 416       */
 417      public function __construct()
 418      {
 419          $this->readFilter = new PHPExcel_Reader_DefaultReadFilter();
 420      }
 421  
 422      /**
 423       * Can the current PHPExcel_Reader_IReader read the file?
 424       *
 425       * @param     string         $pFilename
 426       * @return     boolean
 427       * @throws PHPExcel_Reader_Exception
 428       */
 429      public function canRead($pFilename)
 430      {
 431          // Check if file exists
 432          if (!file_exists($pFilename)) {
 433              throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist.");
 434          }
 435  
 436          try {
 437              // Use ParseXL for the hard work.
 438              $ole = new PHPExcel_Shared_OLERead();
 439  
 440              // get excel data
 441              $res = $ole->read($pFilename);
 442              return true;
 443          } catch (PHPExcel_Exception $e) {
 444              return false;
 445          }
 446      }
 447  
 448      /**
 449       * Reads names of the worksheets from a file, without parsing the whole file to a PHPExcel object
 450       *
 451       * @param     string         $pFilename
 452       * @throws     PHPExcel_Reader_Exception
 453       */
 454      public function listWorksheetNames($pFilename)
 455      {
 456          // Check if file exists
 457          if (!file_exists($pFilename)) {
 458              throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist.");
 459          }
 460  
 461          $worksheetNames = array();
 462  
 463          // Read the OLE file
 464          $this->loadOLE($pFilename);
 465  
 466          // total byte size of Excel data (workbook global substream + sheet substreams)
 467          $this->dataSize = strlen($this->data);
 468  
 469          $this->pos        = 0;
 470          $this->sheets    = array();
 471  
 472          // Parse Workbook Global Substream
 473          while ($this->pos < $this->dataSize) {
 474              $code = self::getInt2d($this->data, $this->pos);
 475  
 476              switch ($code) {
 477                  case self::XLS_TYPE_BOF:
 478                      $this->readBof();
 479                      break;
 480                  case self::XLS_TYPE_SHEET:
 481                      $this->readSheet();
 482                      break;
 483                  case self::XLS_TYPE_EOF:
 484                      $this->readDefault();
 485                      break 2;
 486                  default:
 487                      $this->readDefault();
 488                      break;
 489              }
 490          }
 491  
 492          foreach ($this->sheets as $sheet) {
 493              if ($sheet['sheetType'] != 0x00) {
 494                  // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
 495                  continue;
 496              }
 497  
 498              $worksheetNames[] = $sheet['name'];
 499          }
 500  
 501          return $worksheetNames;
 502      }
 503  
 504  
 505      /**
 506       * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns)
 507       *
 508       * @param   string     $pFilename
 509       * @throws   PHPExcel_Reader_Exception
 510       */
 511      public function listWorksheetInfo($pFilename)
 512      {
 513          // Check if file exists
 514          if (!file_exists($pFilename)) {
 515              throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist.");
 516          }
 517  
 518          $worksheetInfo = array();
 519  
 520          // Read the OLE file
 521          $this->loadOLE($pFilename);
 522  
 523          // total byte size of Excel data (workbook global substream + sheet substreams)
 524          $this->dataSize = strlen($this->data);
 525  
 526          // initialize
 527          $this->pos    = 0;
 528          $this->sheets = array();
 529  
 530          // Parse Workbook Global Substream
 531          while ($this->pos < $this->dataSize) {
 532              $code = self::getInt2d($this->data, $this->pos);
 533  
 534              switch ($code) {
 535                  case self::XLS_TYPE_BOF:
 536                      $this->readBof();
 537                      break;
 538                  case self::XLS_TYPE_SHEET:
 539                      $this->readSheet();
 540                      break;
 541                  case self::XLS_TYPE_EOF:
 542                      $this->readDefault();
 543                      break 2;
 544                  default:
 545                      $this->readDefault();
 546                      break;
 547              }
 548          }
 549  
 550          // Parse the individual sheets
 551          foreach ($this->sheets as $sheet) {
 552              if ($sheet['sheetType'] != 0x00) {
 553                  // 0x00: Worksheet
 554                  // 0x02: Chart
 555                  // 0x06: Visual Basic module
 556                  continue;
 557              }
 558  
 559              $tmpInfo = array();
 560              $tmpInfo['worksheetName'] = $sheet['name'];
 561              $tmpInfo['lastColumnLetter'] = 'A';
 562              $tmpInfo['lastColumnIndex'] = 0;
 563              $tmpInfo['totalRows'] = 0;
 564              $tmpInfo['totalColumns'] = 0;
 565  
 566              $this->pos = $sheet['offset'];
 567  
 568              while ($this->pos <= $this->dataSize - 4) {
 569                  $code = self::getInt2d($this->data, $this->pos);
 570  
 571                  switch ($code) {
 572                      case self::XLS_TYPE_RK:
 573                      case self::XLS_TYPE_LABELSST:
 574                      case self::XLS_TYPE_NUMBER:
 575                      case self::XLS_TYPE_FORMULA:
 576                      case self::XLS_TYPE_BOOLERR:
 577                      case self::XLS_TYPE_LABEL:
 578                          $length = self::getInt2d($this->data, $this->pos + 2);
 579                          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
 580  
 581                          // move stream pointer to next record
 582                          $this->pos += 4 + $length;
 583  
 584                          $rowIndex = self::getInt2d($recordData, 0) + 1;
 585                          $columnIndex = self::getInt2d($recordData, 2);
 586  
 587                          $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
 588                          $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
 589                          break;
 590                      case self::XLS_TYPE_BOF:
 591                          $this->readBof();
 592                          break;
 593                      case self::XLS_TYPE_EOF:
 594                          $this->readDefault();
 595                          break 2;
 596                      default:
 597                          $this->readDefault();
 598                          break;
 599                  }
 600              }
 601  
 602              $tmpInfo['lastColumnLetter'] = PHPExcel_Cell::stringFromColumnIndex($tmpInfo['lastColumnIndex']);
 603              $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
 604  
 605              $worksheetInfo[] = $tmpInfo;
 606          }
 607  
 608          return $worksheetInfo;
 609      }
 610  
 611  
 612      /**
 613       * Loads PHPExcel from file
 614       *
 615       * @param     string         $pFilename
 616       * @return     PHPExcel
 617       * @throws     PHPExcel_Reader_Exception
 618       */
 619      public function load($pFilename)
 620      {
 621          // Read the OLE file
 622          $this->loadOLE($pFilename);
 623  
 624          // Initialisations
 625          $this->phpExcel = new PHPExcel;
 626          $this->phpExcel->removeSheetByIndex(0); // remove 1st sheet
 627          if (!$this->readDataOnly) {
 628              $this->phpExcel->removeCellStyleXfByIndex(0); // remove the default style
 629              $this->phpExcel->removeCellXfByIndex(0); // remove the default style
 630          }
 631  
 632          // Read the summary information stream (containing meta data)
 633          $this->readSummaryInformation();
 634  
 635          // Read the Additional document summary information stream (containing application-specific meta data)
 636          $this->readDocumentSummaryInformation();
 637  
 638          // total byte size of Excel data (workbook global substream + sheet substreams)
 639          $this->dataSize = strlen($this->data);
 640  
 641          // initialize
 642          $this->pos                 = 0;
 643          $this->codepage            = 'CP1252';
 644          $this->formats             = array();
 645          $this->objFonts            = array();
 646          $this->palette             = array();
 647          $this->sheets              = array();
 648          $this->externalBooks       = array();
 649          $this->ref                 = array();
 650          $this->definedname         = array();
 651          $this->sst                 = array();
 652          $this->drawingGroupData    = '';
 653          $this->xfIndex             = '';
 654          $this->mapCellXfIndex      = array();
 655          $this->mapCellStyleXfIndex = array();
 656  
 657          // Parse Workbook Global Substream
 658          while ($this->pos < $this->dataSize) {
 659              $code = self::getInt2d($this->data, $this->pos);
 660  
 661              switch ($code) {
 662                  case self::XLS_TYPE_BOF:
 663                      $this->readBof();
 664                      break;
 665                  case self::XLS_TYPE_FILEPASS:
 666                      $this->readFilepass();
 667                      break;
 668                  case self::XLS_TYPE_CODEPAGE:
 669                      $this->readCodepage();
 670                      break;
 671                  case self::XLS_TYPE_DATEMODE:
 672                      $this->readDateMode();
 673                      break;
 674                  case self::XLS_TYPE_FONT:
 675                      $this->readFont();
 676                      break;
 677                  case self::XLS_TYPE_FORMAT:
 678                      $this->readFormat();
 679                      break;
 680                  case self::XLS_TYPE_XF:
 681                      $this->readXf();
 682                      break;
 683                  case self::XLS_TYPE_XFEXT:
 684                      $this->readXfExt();
 685                      break;
 686                  case self::XLS_TYPE_STYLE:
 687                      $this->readStyle();
 688                      break;
 689                  case self::XLS_TYPE_PALETTE:
 690                      $this->readPalette();
 691                      break;
 692                  case self::XLS_TYPE_SHEET:
 693                      $this->readSheet();
 694                      break;
 695                  case self::XLS_TYPE_EXTERNALBOOK:
 696                      $this->readExternalBook();
 697                      break;
 698                  case self::XLS_TYPE_EXTERNNAME:
 699                      $this->readExternName();
 700                      break;
 701                  case self::XLS_TYPE_EXTERNSHEET:
 702                      $this->readExternSheet();
 703                      break;
 704                  case self::XLS_TYPE_DEFINEDNAME:
 705                      $this->readDefinedName();
 706                      break;
 707                  case self::XLS_TYPE_MSODRAWINGGROUP:
 708                      $this->readMsoDrawingGroup();
 709                      break;
 710                  case self::XLS_TYPE_SST:
 711                      $this->readSst();
 712                      break;
 713                  case self::XLS_TYPE_EOF:
 714                      $this->readDefault();
 715                      break 2;
 716                  default:
 717                      $this->readDefault();
 718                      break;
 719              }
 720          }
 721  
 722          // Resolve indexed colors for font, fill, and border colors
 723          // Cannot be resolved already in XF record, because PALETTE record comes afterwards
 724          if (!$this->readDataOnly) {
 725              foreach ($this->objFonts as $objFont) {
 726                  if (isset($objFont->colorIndex)) {
 727                      $color = self::readColor($objFont->colorIndex, $this->palette, $this->version);
 728                      $objFont->getColor()->setRGB($color['rgb']);
 729                  }
 730              }
 731  
 732              foreach ($this->phpExcel->getCellXfCollection() as $objStyle) {
 733                  // fill start and end color
 734                  $fill = $objStyle->getFill();
 735  
 736                  if (isset($fill->startcolorIndex)) {
 737                      $startColor = self::readColor($fill->startcolorIndex, $this->palette, $this->version);
 738                      $fill->getStartColor()->setRGB($startColor['rgb']);
 739                  }
 740                  if (isset($fill->endcolorIndex)) {
 741                      $endColor = self::readColor($fill->endcolorIndex, $this->palette, $this->version);
 742                      $fill->getEndColor()->setRGB($endColor['rgb']);
 743                  }
 744  
 745                  // border colors
 746                  $top      = $objStyle->getBorders()->getTop();
 747                  $right    = $objStyle->getBorders()->getRight();
 748                  $bottom   = $objStyle->getBorders()->getBottom();
 749                  $left     = $objStyle->getBorders()->getLeft();
 750                  $diagonal = $objStyle->getBorders()->getDiagonal();
 751  
 752                  if (isset($top->colorIndex)) {
 753                      $borderTopColor = self::readColor($top->colorIndex, $this->palette, $this->version);
 754                      $top->getColor()->setRGB($borderTopColor['rgb']);
 755                  }
 756                  if (isset($right->colorIndex)) {
 757                      $borderRightColor = self::readColor($right->colorIndex, $this->palette, $this->version);
 758                      $right->getColor()->setRGB($borderRightColor['rgb']);
 759                  }
 760                  if (isset($bottom->colorIndex)) {
 761                      $borderBottomColor = self::readColor($bottom->colorIndex, $this->palette, $this->version);
 762                      $bottom->getColor()->setRGB($borderBottomColor['rgb']);
 763                  }
 764                  if (isset($left->colorIndex)) {
 765                      $borderLeftColor = self::readColor($left->colorIndex, $this->palette, $this->version);
 766                      $left->getColor()->setRGB($borderLeftColor['rgb']);
 767                  }
 768                  if (isset($diagonal->colorIndex)) {
 769                      $borderDiagonalColor = self::readColor($diagonal->colorIndex, $this->palette, $this->version);
 770                      $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']);
 771                  }
 772              }
 773          }
 774  
 775          // treat MSODRAWINGGROUP records, workbook-level Escher
 776          if (!$this->readDataOnly && $this->drawingGroupData) {
 777              $escherWorkbook = new PHPExcel_Shared_Escher();
 778              $reader = new PHPExcel_Reader_Excel5_Escher($escherWorkbook);
 779              $escherWorkbook = $reader->load($this->drawingGroupData);
 780  
 781              // debug Escher stream
 782              //$debug = new Debug_Escher(new PHPExcel_Shared_Escher());
 783              //$debug->load($this->drawingGroupData);
 784          }
 785  
 786          // Parse the individual sheets
 787          foreach ($this->sheets as $sheet) {
 788              if ($sheet['sheetType'] != 0x00) {
 789                  // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
 790                  continue;
 791              }
 792  
 793              // check if sheet should be skipped
 794              if (isset($this->loadSheetsOnly) && !in_array($sheet['name'], $this->loadSheetsOnly)) {
 795                  continue;
 796              }
 797  
 798              // add sheet to PHPExcel object
 799              $this->phpSheet = $this->phpExcel->createSheet();
 800              //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
 801              //        cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
 802              //        name in line with the formula, not the reverse
 803              $this->phpSheet->setTitle($sheet['name'], false);
 804              $this->phpSheet->setSheetState($sheet['sheetState']);
 805  
 806              $this->pos = $sheet['offset'];
 807  
 808              // Initialize isFitToPages. May change after reading SHEETPR record.
 809              $this->isFitToPages = false;
 810  
 811              // Initialize drawingData
 812              $this->drawingData = '';
 813  
 814              // Initialize objs
 815              $this->objs = array();
 816  
 817              // Initialize shared formula parts
 818              $this->sharedFormulaParts = array();
 819  
 820              // Initialize shared formulas
 821              $this->sharedFormulas = array();
 822  
 823              // Initialize text objs
 824              $this->textObjects = array();
 825  
 826              // Initialize cell annotations
 827              $this->cellNotes = array();
 828              $this->textObjRef = -1;
 829  
 830              while ($this->pos <= $this->dataSize - 4) {
 831                  $code = self::getInt2d($this->data, $this->pos);
 832  
 833                  switch ($code) {
 834                      case self::XLS_TYPE_BOF:
 835                          $this->readBof();
 836                          break;
 837                      case self::XLS_TYPE_PRINTGRIDLINES:
 838                          $this->readPrintGridlines();
 839                          break;
 840                      case self::XLS_TYPE_DEFAULTROWHEIGHT:
 841                          $this->readDefaultRowHeight();
 842                          break;
 843                      case self::XLS_TYPE_SHEETPR:
 844                          $this->readSheetPr();
 845                          break;
 846                      case self::XLS_TYPE_HORIZONTALPAGEBREAKS:
 847                          $this->readHorizontalPageBreaks();
 848                          break;
 849                      case self::XLS_TYPE_VERTICALPAGEBREAKS:
 850                          $this->readVerticalPageBreaks();
 851                          break;
 852                      case self::XLS_TYPE_HEADER:
 853                          $this->readHeader();
 854                          break;
 855                      case self::XLS_TYPE_FOOTER:
 856                          $this->readFooter();
 857                          break;
 858                      case self::XLS_TYPE_HCENTER:
 859                          $this->readHcenter();
 860                          break;
 861                      case self::XLS_TYPE_VCENTER:
 862                          $this->readVcenter();
 863                          break;
 864                      case self::XLS_TYPE_LEFTMARGIN:
 865                          $this->readLeftMargin();
 866                          break;
 867                      case self::XLS_TYPE_RIGHTMARGIN:
 868                          $this->readRightMargin();
 869                          break;
 870                      case self::XLS_TYPE_TOPMARGIN:
 871                          $this->readTopMargin();
 872                          break;
 873                      case self::XLS_TYPE_BOTTOMMARGIN:
 874                          $this->readBottomMargin();
 875                          break;
 876                      case self::XLS_TYPE_PAGESETUP:
 877                          $this->readPageSetup();
 878                          break;
 879                      case self::XLS_TYPE_PROTECT:
 880                          $this->readProtect();
 881                          break;
 882                      case self::XLS_TYPE_SCENPROTECT:
 883                          $this->readScenProtect();
 884                          break;
 885                      case self::XLS_TYPE_OBJECTPROTECT:
 886                          $this->readObjectProtect();
 887                          break;
 888                      case self::XLS_TYPE_PASSWORD:
 889                          $this->readPassword();
 890                          break;
 891                      case self::XLS_TYPE_DEFCOLWIDTH:
 892                          $this->readDefColWidth();
 893                          break;
 894                      case self::XLS_TYPE_COLINFO:
 895                          $this->readColInfo();
 896                          break;
 897                      case self::XLS_TYPE_DIMENSION:
 898                          $this->readDefault();
 899                          break;
 900                      case self::XLS_TYPE_ROW:
 901                          $this->readRow();
 902                          break;
 903                      case self::XLS_TYPE_DBCELL:
 904                          $this->readDefault();
 905                          break;
 906                      case self::XLS_TYPE_RK:
 907                          $this->readRk();
 908                          break;
 909                      case self::XLS_TYPE_LABELSST:
 910                          $this->readLabelSst();
 911                          break;
 912                      case self::XLS_TYPE_MULRK:
 913                          $this->readMulRk();
 914                          break;
 915                      case self::XLS_TYPE_NUMBER:
 916                          $this->readNumber();
 917                          break;
 918                      case self::XLS_TYPE_FORMULA:
 919                          $this->readFormula();
 920                          break;
 921                      case self::XLS_TYPE_SHAREDFMLA:
 922                          $this->readSharedFmla();
 923                          break;
 924                      case self::XLS_TYPE_BOOLERR:
 925                          $this->readBoolErr();
 926                          break;
 927                      case self::XLS_TYPE_MULBLANK:
 928                          $this->readMulBlank();
 929                          break;
 930                      case self::XLS_TYPE_LABEL:
 931                          $this->readLabel();
 932                          break;
 933                      case self::XLS_TYPE_BLANK:
 934                          $this->readBlank();
 935                          break;
 936                      case self::XLS_TYPE_MSODRAWING:
 937                          $this->readMsoDrawing();
 938                          break;
 939                      case self::XLS_TYPE_OBJ:
 940                          $this->readObj();
 941                          break;
 942                      case self::XLS_TYPE_WINDOW2:
 943                          $this->readWindow2();
 944                          break;
 945                      case self::XLS_TYPE_PAGELAYOUTVIEW:
 946                          $this->readPageLayoutView();
 947                          break;
 948                      case self::XLS_TYPE_SCL:
 949                          $this->readScl();
 950                          break;
 951                      case self::XLS_TYPE_PANE:
 952                          $this->readPane();
 953                          break;
 954                      case self::XLS_TYPE_SELECTION:
 955                          $this->readSelection();
 956                          break;
 957                      case self::XLS_TYPE_MERGEDCELLS:
 958                          $this->readMergedCells();
 959                          break;
 960                      case self::XLS_TYPE_HYPERLINK:
 961                          $this->readHyperLink();
 962                          break;
 963                      case self::XLS_TYPE_DATAVALIDATIONS:
 964                          $this->readDataValidations();
 965                          break;
 966                      case self::XLS_TYPE_DATAVALIDATION:
 967                          $this->readDataValidation();
 968                          break;
 969                      case self::XLS_TYPE_SHEETLAYOUT:
 970                          $this->readSheetLayout();
 971                          break;
 972                      case self::XLS_TYPE_SHEETPROTECTION:
 973                          $this->readSheetProtection();
 974                          break;
 975                      case self::XLS_TYPE_RANGEPROTECTION:
 976                          $this->readRangeProtection();
 977                          break;
 978                      case self::XLS_TYPE_NOTE:
 979                          $this->readNote();
 980                          break;
 981                      //case self::XLS_TYPE_IMDATA:                $this->readImData();                    break;
 982                      case self::XLS_TYPE_TXO:
 983                          $this->readTextObject();
 984                          break;
 985                      case self::XLS_TYPE_CONTINUE:
 986                          $this->readContinue();
 987                          break;
 988                      case self::XLS_TYPE_EOF:
 989                          $this->readDefault();
 990                          break 2;
 991                      default:
 992                          $this->readDefault();
 993                          break;
 994                  }
 995  
 996              }
 997  
 998              // treat MSODRAWING records, sheet-level Escher
 999              if (!$this->readDataOnly && $this->drawingData) {
1000                  $escherWorksheet = new PHPExcel_Shared_Escher();
1001                  $reader = new PHPExcel_Reader_Excel5_Escher($escherWorksheet);
1002                  $escherWorksheet = $reader->load($this->drawingData);
1003  
1004                  // debug Escher stream
1005                  //$debug = new Debug_Escher(new PHPExcel_Shared_Escher());
1006                  //$debug->load($this->drawingData);
1007  
1008                  // get all spContainers in one long array, so they can be mapped to OBJ records
1009                  $allSpContainers = $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers();
1010              }
1011  
1012              // treat OBJ records
1013              foreach ($this->objs as $n => $obj) {
1014  //                echo '<hr /><b>Object</b> reference is ', $n,'<br />';
1015  //                var_dump($obj);
1016  //                echo '<br />';
1017  
1018                  // the first shape container never has a corresponding OBJ record, hence $n + 1
1019                  if (isset($allSpContainers[$n + 1]) && is_object($allSpContainers[$n + 1])) {
1020                      $spContainer = $allSpContainers[$n + 1];
1021  
1022                      // we skip all spContainers that are a part of a group shape since we cannot yet handle those
1023                      if ($spContainer->getNestingLevel() > 1) {
1024                          continue;
1025                      }
1026  
1027                      // calculate the width and height of the shape
1028                      list($startColumn, $startRow) = PHPExcel_Cell::coordinateFromString($spContainer->getStartCoordinates());
1029                      list($endColumn, $endRow) = PHPExcel_Cell::coordinateFromString($spContainer->getEndCoordinates());
1030  
1031                      $startOffsetX = $spContainer->getStartOffsetX();
1032                      $startOffsetY = $spContainer->getStartOffsetY();
1033                      $endOffsetX = $spContainer->getEndOffsetX();
1034                      $endOffsetY = $spContainer->getEndOffsetY();
1035  
1036                      $width = PHPExcel_Shared_Excel5::getDistanceX($this->phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX);
1037                      $height = PHPExcel_Shared_Excel5::getDistanceY($this->phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY);
1038  
1039                      // calculate offsetX and offsetY of the shape
1040                      $offsetX = $startOffsetX * PHPExcel_Shared_Excel5::sizeCol($this->phpSheet, $startColumn) / 1024;
1041                      $offsetY = $startOffsetY * PHPExcel_Shared_Excel5::sizeRow($this->phpSheet, $startRow) / 256;
1042  
1043                      switch ($obj['otObjType']) {
1044                          case 0x19:
1045                              // Note
1046  //                            echo 'Cell Annotation Object<br />';
1047  //                            echo 'Object ID is ', $obj['idObjID'],'<br />';
1048                              if (isset($this->cellNotes[$obj['idObjID']])) {
1049                                  $cellNote = $this->cellNotes[$obj['idObjID']];
1050  
1051                                  if (isset($this->textObjects[$obj['idObjID']])) {
1052                                      $textObject = $this->textObjects[$obj['idObjID']];
1053                                      $this->cellNotes[$obj['idObjID']]['objTextData'] = $textObject;
1054                                  }
1055                              }
1056                              break;
1057                          case 0x08:
1058  //                            echo 'Picture Object<br />';
1059                              // picture
1060                              // get index to BSE entry (1-based)
1061                              $BSEindex = $spContainer->getOPT(0x0104);
1062                              $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection();
1063                              $BSE = $BSECollection[$BSEindex - 1];
1064                              $blipType = $BSE->getBlipType();
1065  
1066                              // need check because some blip types are not supported by Escher reader such as EMF
1067                              if ($blip = $BSE->getBlip()) {
1068                                  $ih = imagecreatefromstring($blip->getData());
1069                                  $drawing = new PHPExcel_Worksheet_MemoryDrawing();
1070                                  $drawing->setImageResource($ih);
1071  
1072                                  // width, height, offsetX, offsetY
1073                                  $drawing->setResizeProportional(false);
1074                                  $drawing->setWidth($width);
1075                                  $drawing->setHeight($height);
1076                                  $drawing->setOffsetX($offsetX);
1077                                  $drawing->setOffsetY($offsetY);
1078  
1079                                  switch ($blipType) {
1080                                      case PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_JPEG:
1081                                          $drawing->setRenderingFunction(PHPExcel_Worksheet_MemoryDrawing::RENDERING_JPEG);
1082                                          $drawing->setMimeType(PHPExcel_Worksheet_MemoryDrawing::MIMETYPE_JPEG);
1083                                          break;
1084                                      case PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG:
1085                                          $drawing->setRenderingFunction(PHPExcel_Worksheet_MemoryDrawing::RENDERING_PNG);
1086                                          $drawing->setMimeType(PHPExcel_Worksheet_MemoryDrawing::MIMETYPE_PNG);
1087                                          break;
1088                                  }
1089  
1090                                  $drawing->setWorksheet($this->phpSheet);
1091                                  $drawing->setCoordinates($spContainer->getStartCoordinates());
1092                              }
1093                              break;
1094                          default:
1095                              // other object type
1096                              break;
1097                      }
1098                  }
1099              }
1100  
1101              // treat SHAREDFMLA records
1102              if ($this->version == self::XLS_BIFF8) {
1103                  foreach ($this->sharedFormulaParts as $cell => $baseCell) {
1104                      list($column, $row) = PHPExcel_Cell::coordinateFromString($cell);
1105                      if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
1106                          $formula = $this->getFormulaFromStructure($this->sharedFormulas[$baseCell], $cell);
1107                          $this->phpSheet->getCell($cell)->setValueExplicit('=' . $formula, PHPExcel_Cell_DataType::TYPE_FORMULA);
1108                      }
1109                  }
1110              }
1111  
1112              if (!empty($this->cellNotes)) {
1113                  foreach ($this->cellNotes as $note => $noteDetails) {
1114                      if (!isset($noteDetails['objTextData'])) {
1115                          if (isset($this->textObjects[$note])) {
1116                              $textObject = $this->textObjects[$note];
1117                              $noteDetails['objTextData'] = $textObject;
1118                          } else {
1119                              $noteDetails['objTextData']['text'] = '';
1120                          }
1121                      }
1122  //                    echo '<b>Cell annotation ', $note,'</b><br />';
1123  //                    var_dump($noteDetails);
1124  //                    echo '<br />';
1125                      $cellAddress = str_replace('$', '', $noteDetails['cellRef']);
1126                      $this->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($this->parseRichText($noteDetails['objTextData']['text']));
1127                  }
1128              }
1129          }
1130  
1131          // add the named ranges (defined names)
1132          foreach ($this->definedname as $definedName) {
1133              if ($definedName['isBuiltInName']) {
1134                  switch ($definedName['name']) {
1135                      case pack('C', 0x06):
1136                          // print area
1137                          //    in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2
1138                          $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
1139  
1140                          $extractedRanges = array();
1141                          foreach ($ranges as $range) {
1142                              // $range should look like one of these
1143                              //        Foo!$C$7:$J$66
1144                              //        Bar!$A$1:$IV$2
1145                              $explodes = explode('!', $range);    // FIXME: what if sheetname contains exclamation mark?
1146                              $sheetName = trim($explodes[0], "'");
1147                              if (count($explodes) == 2) {
1148                                  if (strpos($explodes[1], ':') === false) {
1149                                      $explodes[1] = $explodes[1] . ':' . $explodes[1];
1150                                  }
1151                                  $extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66
1152                              }
1153                          }
1154                          if ($docSheet = $this->phpExcel->getSheetByName($sheetName)) {
1155                              $docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2
1156                          }
1157                          break;
1158                      case pack('C', 0x07):
1159                          // print titles (repeating rows)
1160                          // Assuming BIFF8, there are 3 cases
1161                          // 1. repeating rows
1162                          //        formula looks like this: Sheet!$A$1:$IV$2
1163                          //        rows 1-2 repeat
1164                          // 2. repeating columns
1165                          //        formula looks like this: Sheet!$A$1:$B$65536
1166                          //        columns A-B repeat
1167                          // 3. both repeating rows and repeating columns
1168                          //        formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2
1169                          $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
1170                          foreach ($ranges as $range) {
1171                              // $range should look like this one of these
1172                              //        Sheet!$A$1:$B$65536
1173                              //        Sheet!$A$1:$IV$2
1174                              $explodes = explode('!', $range);
1175                              if (count($explodes) == 2) {
1176                                  if ($docSheet = $this->phpExcel->getSheetByName($explodes[0])) {
1177                                      $extractedRange = $explodes[1];
1178                                      $extractedRange = str_replace('$', '', $extractedRange);
1179  
1180                                      $coordinateStrings = explode(':', $extractedRange);
1181                                      if (count($coordinateStrings) == 2) {
1182                                          list($firstColumn, $firstRow) = PHPExcel_Cell::coordinateFromString($coordinateStrings[0]);
1183                                          list($lastColumn, $lastRow) = PHPExcel_Cell::coordinateFromString($coordinateStrings[1]);
1184  
1185                                          if ($firstColumn == 'A' and $lastColumn == 'IV') {
1186                                              // then we have repeating rows
1187                                              $docSheet->getPageSetup()->setRowsToRepeatAtTop(array($firstRow, $lastRow));
1188                                          } elseif ($firstRow == 1 and $lastRow == 65536) {
1189                                              // then we have repeating columns
1190                                              $docSheet->getPageSetup()->setColumnsToRepeatAtLeft(array($firstColumn, $lastColumn));
1191                                          }
1192                                      }
1193                                  }
1194                              }
1195                          }
1196                          break;
1197                  }
1198              } else {
1199                  // Extract range
1200                  $explodes = explode('!', $definedName['formula']);
1201  
1202                  if (count($explodes) == 2) {
1203                      if (($docSheet = $this->phpExcel->getSheetByName($explodes[0])) ||
1204                          ($docSheet = $this->phpExcel->getSheetByName(trim($explodes[0], "'")))) {
1205                          $extractedRange = $explodes[1];
1206                          $extractedRange = str_replace('$', '', $extractedRange);
1207  
1208                          $localOnly = ($definedName['scope'] == 0) ? false : true;
1209  
1210                          $scope = ($definedName['scope'] == 0) ? null : $this->phpExcel->getSheetByName($this->sheets[$definedName['scope'] - 1]['name']);
1211  
1212                          $this->phpExcel->addNamedRange(new PHPExcel_NamedRange((string)$definedName['name'], $docSheet, $extractedRange, $localOnly, $scope));
1213                      }
1214                  } else {
1215                      //    Named Value
1216                      //    TODO Provide support for named values
1217                  }
1218              }
1219          }
1220          $this->data = null;
1221  
1222          return $this->phpExcel;
1223      }
1224      
1225      /**
1226       * Read record data from stream, decrypting as required
1227       *
1228       * @param string $data   Data stream to read from
1229       * @param int    $pos    Position to start reading from
1230       * @param int    $length Record data length
1231       *
1232       * @return string Record data
1233       */
1234      private function readRecordData($data, $pos, $len)
1235      {
1236          $data = substr($data, $pos, $len);
1237          
1238          // File not encrypted, or record before encryption start point
1239          if ($this->encryption == self::MS_BIFF_CRYPTO_NONE || $pos < $this->encryptionStartPos) {
1240              return $data;
1241          }
1242      
1243          $recordData = '';
1244          if ($this->encryption == self::MS_BIFF_CRYPTO_RC4) {
1245              $oldBlock = floor($this->rc4Pos / self::REKEY_BLOCK);
1246              $block = floor($pos / self::REKEY_BLOCK);
1247              $endBlock = floor(($pos + $len) / self::REKEY_BLOCK);
1248  
1249              // Spin an RC4 decryptor to the right spot. If we have a decryptor sitting
1250              // at a point earlier in the current block, re-use it as we can save some time.
1251              if ($block != $oldBlock || $pos < $this->rc4Pos || !$this->rc4Key) {
1252                  $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
1253                  $step = $pos % self::REKEY_BLOCK;
1254              } else {
1255                  $step = $pos - $this->rc4Pos;
1256              }
1257              $this->rc4Key->RC4(str_repeat("\0", $step));
1258  
1259              // Decrypt record data (re-keying at the end of every block)
1260              while ($block != $endBlock) {
1261                  $step = self::REKEY_BLOCK - ($pos % self::REKEY_BLOCK);
1262                  $recordData .= $this->rc4Key->RC4(substr($data, 0, $step));
1263                  $data = substr($data, $step);
1264                  $pos += $step;
1265                  $len -= $step;
1266                  $block++;
1267                  $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
1268              }
1269              $recordData .= $this->rc4Key->RC4(substr($data, 0, $len));
1270  
1271              // Keep track of the position of this decryptor.
1272              // We'll try and re-use it later if we can to speed things up
1273              $this->rc4Pos = $pos + $len;
1274          } elseif ($this->encryption == self::MS_BIFF_CRYPTO_XOR) {
1275              throw new PHPExcel_Reader_Exception('XOr encryption not supported');
1276          }
1277          return $recordData;
1278      }
1279  
1280      /**
1281       * Use OLE reader to extract the relevant data streams from the OLE file
1282       *
1283       * @param string $pFilename
1284       */
1285      private function loadOLE($pFilename)
1286      {
1287          // OLE reader
1288          $ole = new PHPExcel_Shared_OLERead();
1289          // get excel data,
1290          $res = $ole->read($pFilename);
1291          // Get workbook data: workbook stream + sheet streams
1292          $this->data = $ole->getStream($ole->wrkbook);
1293          // Get summary information data
1294          $this->summaryInformation = $ole->getStream($ole->summaryInformation);
1295          // Get additional document summary information data
1296          $this->documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation);
1297          // Get user-defined property data
1298  //        $this->userDefinedProperties = $ole->getUserDefinedProperties();
1299      }
1300  
1301  
1302      /**
1303       * Read summary information
1304       */
1305      private function readSummaryInformation()
1306      {
1307          if (!isset($this->summaryInformation)) {
1308              return;
1309          }
1310  
1311          // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
1312          // offset: 2; size: 2;
1313          // offset: 4; size: 2; OS version
1314          // offset: 6; size: 2; OS indicator
1315          // offset: 8; size: 16
1316          // offset: 24; size: 4; section count
1317          $secCount = self::getInt4d($this->summaryInformation, 24);
1318  
1319          // 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
1320          // offset: 44; size: 4
1321          $secOffset = self::getInt4d($this->summaryInformation, 44);
1322  
1323          // section header
1324          // offset: $secOffset; size: 4; section length
1325          $secLength = self::getInt4d($this->summaryInformation, $secOffset);
1326  
1327          // offset: $secOffset+4; size: 4; property count
1328          $countProperties = self::getInt4d($this->summaryInformation, $secOffset+4);
1329  
1330          // initialize code page (used to resolve string values)
1331          $codePage = 'CP1252';
1332  
1333          // offset: ($secOffset+8); size: var
1334          // loop through property decarations and properties
1335          for ($i = 0; $i < $countProperties; ++$i) {
1336              // offset: ($secOffset+8) + (8 * $i); size: 4; property ID
1337              $id = self::getInt4d($this->summaryInformation, ($secOffset+8) + (8 * $i));
1338  
1339              // Use value of property id as appropriate
1340              // offset: ($secOffset+12) + (8 * $i); size: 4; offset from beginning of section (48)
1341              $offset = self::getInt4d($this->summaryInformation, ($secOffset+12) + (8 * $i));
1342  
1343              $type = self::getInt4d($this->summaryInformation, $secOffset + $offset);
1344  
1345              // initialize property value
1346              $value = null;
1347  
1348              // extract property value based on property type
1349              switch ($type) {
1350                  case 0x02: // 2 byte signed integer
1351                      $value = self::getInt2d($this->summaryInformation, $secOffset + 4 + $offset);
1352                      break;
1353                  case 0x03: // 4 byte signed integer
1354                      $value = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
1355                      break;
1356                  case 0x13: // 4 byte unsigned integer
1357                      // not needed yet, fix later if necessary
1358                      break;
1359                  case 0x1E: // null-terminated string prepended by dword string length
1360                      $byteLength = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
1361                      $value = substr($this->summaryInformation, $secOffset + 8 + $offset, $byteLength);
1362                      $value = PHPExcel_Shared_String::ConvertEncoding($value, 'UTF-8', $codePage);
1363                      $value = rtrim($value);
1364                      break;
1365                  case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
1366                      // PHP-time
1367                      $value = PHPExcel_Shared_OLE::OLE2LocalDate(substr($this->summaryInformation, $secOffset + 4 + $offset, 8));
1368                      break;
1369                  case 0x47: // Clipboard format
1370                      // not needed yet, fix later if necessary
1371                      break;
1372              }
1373  
1374              switch ($id) {
1375                  case 0x01:    //    Code Page
1376                      $codePage = PHPExcel_Shared_CodePage::NumberToName($value);
1377                      break;
1378                  case 0x02:    //    Title
1379                      $this->phpExcel->getProperties()->setTitle($value);
1380                      break;
1381                  case 0x03:    //    Subject
1382                      $this->phpExcel->getProperties()->setSubject($value);
1383                      break;
1384                  case 0x04:    //    Author (Creator)
1385                      $this->phpExcel->getProperties()->setCreator($value);
1386                      break;
1387                  case 0x05:    //    Keywords
1388                      $this->phpExcel->getProperties()->setKeywords($value);
1389                      break;
1390                  case 0x06:    //    Comments (Description)
1391                      $this->phpExcel->getProperties()->setDescription($value);
1392                      break;
1393                  case 0x07:    //    Template
1394                      //    Not supported by PHPExcel
1395                      break;
1396                  case 0x08:    //    Last Saved By (LastModifiedBy)
1397                      $this->phpExcel->getProperties()->setLastModifiedBy($value);
1398                      break;
1399                  case 0x09:    //    Revision
1400                      //    Not supported by PHPExcel
1401                      break;
1402                  case 0x0A:    //    Total Editing Time
1403                      //    Not supported by PHPExcel
1404                      break;
1405                  case 0x0B:    //    Last Printed
1406                      //    Not supported by PHPExcel
1407                      break;
1408                  case 0x0C:    //    Created Date/Time
1409                      $this->phpExcel->getProperties()->setCreated($value);
1410                      break;
1411                  case 0x0D:    //    Modified Date/Time
1412                      $this->phpExcel->getProperties()->setModified($value);
1413                      break;
1414                  case 0x0E:    //    Number of Pages
1415                      //    Not supported by PHPExcel
1416                      break;
1417                  case 0x0F:    //    Number of Words
1418                      //    Not supported by PHPExcel
1419                      break;
1420                  case 0x10:    //    Number of Characters
1421                      //    Not supported by PHPExcel
1422                      break;
1423                  case 0x11:    //    Thumbnail
1424                      //    Not supported by PHPExcel
1425                      break;
1426                  case 0x12:    //    Name of creating application
1427                      //    Not supported by PHPExcel
1428                      break;
1429                  case 0x13:    //    Security
1430                      //    Not supported by PHPExcel
1431                      break;
1432              }
1433          }
1434      }
1435  
1436  
1437      /**
1438       * Read additional document summary information
1439       */
1440      private function readDocumentSummaryInformation()
1441      {
1442          if (!isset($this->documentSummaryInformation)) {
1443              return;
1444          }
1445  
1446          //    offset: 0;    size: 2;    must be 0xFE 0xFF (UTF-16 LE byte order mark)
1447          //    offset: 2;    size: 2;
1448          //    offset: 4;    size: 2;    OS version
1449          //    offset: 6;    size: 2;    OS indicator
1450          //    offset: 8;    size: 16
1451          //    offset: 24;    size: 4;    section count
1452          $secCount = self::getInt4d($this->documentSummaryInformation, 24);
1453  //        echo '$secCount = ', $secCount,'<br />';
1454  
1455          // 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
1456          // offset: 44;    size: 4;    first section offset
1457          $secOffset = self::getInt4d($this->documentSummaryInformation, 44);
1458  //        echo '$secOffset = ', $secOffset,'<br />';
1459  
1460          //    section header
1461          //    offset: $secOffset;    size: 4;    section length
1462          $secLength = self::getInt4d($this->documentSummaryInformation, $secOffset);
1463  //        echo '$secLength = ', $secLength,'<br />';
1464  
1465          //    offset: $secOffset+4;    size: 4;    property count
1466          $countProperties = self::getInt4d($this->documentSummaryInformation, $secOffset+4);
1467  //        echo '$countProperties = ', $countProperties,'<br />';
1468  
1469          // initialize code page (used to resolve string values)
1470          $codePage = 'CP1252';
1471  
1472          //    offset: ($secOffset+8);    size: var
1473          //    loop through property decarations and properties
1474          for ($i = 0; $i < $countProperties; ++$i) {
1475  //            echo 'Property ', $i,'<br />';
1476              //    offset: ($secOffset+8) + (8 * $i);    size: 4;    property ID
1477              $id = self::getInt4d($this->documentSummaryInformation, ($secOffset+8) + (8 * $i));
1478  //            echo 'ID is ', $id,'<br />';
1479  
1480              // Use value of property id as appropriate
1481              // offset: 60 + 8 * $i;    size: 4;    offset from beginning of section (48)
1482              $offset = self::getInt4d($this->documentSummaryInformation, ($secOffset+12) + (8 * $i));
1483  
1484              $type = self::getInt4d($this->documentSummaryInformation, $secOffset + $offset);
1485  //            echo 'Type is ', $type,', ';
1486  
1487              // initialize property value
1488              $value = null;
1489  
1490              // extract property value based on property type
1491              switch ($type) {
1492                  case 0x02:    //    2 byte signed integer
1493                      $value = self::getInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1494                      break;
1495                  case 0x03:    //    4 byte signed integer
1496                      $value = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1497                      break;
1498                  case 0x0B:  // Boolean
1499                      $value = self::getInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1500                      $value = ($value == 0 ? false : true);
1501                      break;
1502                  case 0x13:    //    4 byte unsigned integer
1503                      // not needed yet, fix later if necessary
1504                      break;
1505                  case 0x1E:    //    null-terminated string prepended by dword string length
1506                      $byteLength = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1507                      $value = substr($this->documentSummaryInformation, $secOffset + 8 + $offset, $byteLength);
1508                      $value = PHPExcel_Shared_String::ConvertEncoding($value, 'UTF-8', $codePage);
1509                      $value = rtrim($value);
1510                      break;
1511                  case 0x40:    //    Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
1512                      // PHP-Time
1513                      $value = PHPExcel_Shared_OLE::OLE2LocalDate(substr($this->documentSummaryInformation, $secOffset + 4 + $offset, 8));
1514                      break;
1515                  case 0x47:    //    Clipboard format
1516                      // not needed yet, fix later if necessary
1517                      break;
1518              }
1519  
1520              switch ($id) {
1521                  case 0x01:    //    Code Page
1522                      $codePage = PHPExcel_Shared_CodePage::NumberToName($value);
1523                      break;
1524                  case 0x02:    //    Category
1525                      $this->phpExcel->getProperties()->setCategory($value);
1526                      break;
1527                  case 0x03:    //    Presentation Target
1528                      //    Not supported by PHPExcel
1529                      break;
1530                  case 0x04:    //    Bytes
1531                      //    Not supported by PHPExcel
1532                      break;
1533                  case 0x05:    //    Lines
1534                      //    Not supported by PHPExcel
1535                      break;
1536                  case 0x06:    //    Paragraphs
1537                      //    Not supported by PHPExcel
1538                      break;
1539                  case 0x07:    //    Slides
1540                      //    Not supported by PHPExcel
1541                      break;
1542                  case 0x08:    //    Notes
1543                      //    Not supported by PHPExcel
1544                      break;
1545                  case 0x09:    //    Hidden Slides
1546                      //    Not supported by PHPExcel
1547                      break;
1548                  case 0x0A:    //    MM Clips
1549                      //    Not supported by PHPExcel
1550                      break;
1551                  case 0x0B:    //    Scale Crop
1552                      //    Not supported by PHPExcel
1553                      break;
1554                  case 0x0C:    //    Heading Pairs
1555                      //    Not supported by PHPExcel
1556                      break;
1557                  case 0x0D:    //    Titles of Parts
1558                      //    Not supported by PHPExcel
1559                      break;
1560                  case 0x0E:    //    Manager
1561                      $this->phpExcel->getProperties()->setManager($value);
1562                      break;
1563                  case 0x0F:    //    Company
1564                      $this->phpExcel->getProperties()->setCompany($value);
1565                      break;
1566                  case 0x10:    //    Links up-to-date
1567                      //    Not supported by PHPExcel
1568                      break;
1569              }
1570          }
1571      }
1572  
1573  
1574      /**
1575       * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record.
1576       */
1577      private function readDefault()
1578      {
1579          $length = self::getInt2d($this->data, $this->pos + 2);
1580  //        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1581  
1582          // move stream pointer to next record
1583          $this->pos += 4 + $length;
1584      }
1585  
1586  
1587      /**
1588       *    The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions,
1589       *        this record stores a note (cell note). This feature was significantly enhanced in Excel 97.
1590       */
1591      private function readNote()
1592      {
1593  //        echo '<b>Read Cell Annotation</b><br />';
1594          $length = self::getInt2d($this->data, $this->pos + 2);
1595          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1596  
1597          // move stream pointer to next record
1598          $this->pos += 4 + $length;
1599  
1600          if ($this->readDataOnly) {
1601              return;
1602          }
1603  
1604          $cellAddress = $this->readBIFF8CellAddress(substr($recordData, 0, 4));
1605          if ($this->version == self::XLS_BIFF8) {
1606              $noteObjID = self::getInt2d($recordData, 6);
1607              $noteAuthor = self::readUnicodeStringLong(substr($recordData, 8));
1608              $noteAuthor = $noteAuthor['value'];
1609  //            echo 'Note Address=', $cellAddress,'<br />';
1610  //            echo 'Note Object ID=', $noteObjID,'<br />';
1611  //            echo 'Note Author=', $noteAuthor,'<hr />';
1612  //
1613              $this->cellNotes[$noteObjID] = array(
1614                  'cellRef'   => $cellAddress,
1615                  'objectID'  => $noteObjID,
1616                  'author'    => $noteAuthor
1617              );
1618          } else {
1619              $extension = false;
1620              if ($cellAddress == '$B$65536') {
1621                  //    If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation
1622                  //        note from the previous cell annotation. We're not yet handling this, so annotations longer than the
1623                  //        max 2048 bytes will probably throw a wobbly.
1624                  $row = self::getInt2d($recordData, 0);
1625                  $extension = true;
1626                  $cellAddress = array_pop(array_keys($this->phpSheet->getComments()));
1627              }
1628  //            echo 'Note Address=', $cellAddress,'<br />';
1629  
1630              $cellAddress = str_replace('$', '', $cellAddress);
1631              $noteLength = self::getInt2d($recordData, 4);
1632              $noteText = trim(substr($recordData, 6));
1633  //            echo 'Note Length=', $noteLength,'<br />';
1634  //            echo 'Note Text=', $noteText,'<br />';
1635  
1636              if ($extension) {
1637                  //    Concatenate this extension with the currently set comment for the cell
1638                  $comment = $this->phpSheet->getComment($cellAddress);
1639                  $commentText = $comment->getText()->getPlainText();
1640                  $comment->setText($this->parseRichText($commentText.$noteText));
1641              } else {
1642                  //    Set comment for the cell
1643                  $this->phpSheet->getComment($cellAddress)->setText($this->parseRichText($noteText));
1644  //                                                    ->setAuthor($author)
1645              }
1646          }
1647  
1648      }
1649  
1650  
1651      /**
1652       *    The TEXT Object record contains the text associated with a cell annotation.
1653       */
1654      private function readTextObject()
1655      {
1656          $length = self::getInt2d($this->data, $this->pos + 2);
1657          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1658  
1659          // move stream pointer to next record
1660          $this->pos += 4 + $length;
1661  
1662          if ($this->readDataOnly) {
1663              return;
1664          }
1665  
1666          // recordData consists of an array of subrecords looking like this:
1667          //    grbit: 2 bytes; Option Flags
1668          //    rot: 2 bytes; rotation
1669          //    cchText: 2 bytes; length of the text (in the first continue record)
1670          //    cbRuns: 2 bytes; length of the formatting (in the second continue record)
1671          // followed by the continuation records containing the actual text and formatting
1672          $grbitOpts  = self::getInt2d($recordData, 0);
1673          $rot        = self::getInt2d($recordData, 2);
1674          $cchText    = self::getInt2d($recordData, 10);
1675          $cbRuns     = self::getInt2d($recordData, 12);
1676          $text       = $this->getSplicedRecordData();
1677  
1678          $this->textObjects[$this->textObjRef] = array(
1679              'text'      => substr($text["recordData"], $text["spliceOffsets"][0]+1, $cchText),
1680              'format'    => substr($text["recordData"], $text["spliceOffsets"][1], $cbRuns),
1681              'alignment' => $grbitOpts,
1682              'rotation'  => $rot
1683          );
1684  
1685  //        echo '<b>_readTextObject()</b><br />';
1686  //        var_dump($this->textObjects[$this->textObjRef]);
1687  //        echo '<br />';
1688      }
1689  
1690  
1691      /**
1692       * Read BOF
1693       */
1694      private function readBof()
1695      {
1696          $length = self::getInt2d($this->data, $this->pos + 2);
1697          $recordData = substr($this->data, $this->pos + 4, $length);
1698  
1699          // move stream pointer to next record
1700          $this->pos += 4 + $length;
1701  
1702          // offset: 2; size: 2; type of the following data
1703          $substreamType = self::getInt2d($recordData, 2);
1704  
1705          switch ($substreamType) {
1706              case self::XLS_WorkbookGlobals:
1707                  $version = self::getInt2d($recordData, 0);
1708                  if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) {
1709                      throw new PHPExcel_Reader_Exception('Cannot read this Excel file. Version is too old.');
1710                  }
1711                  $this->version = $version;
1712                  break;
1713              case self::XLS_Worksheet:
1714                  // do not use this version information for anything
1715                  // it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream
1716                  break;
1717              default:
1718                  // substream, e.g. chart
1719                  // just skip the entire substream
1720                  do {
1721                      $code = self::getInt2d($this->data, $this->pos);
1722                      $this->readDefault();
1723                  } while ($code != self::XLS_TYPE_EOF && $this->pos < $this->dataSize);
1724                  break;
1725          }
1726      }
1727  
1728  
1729      /**
1730       * FILEPASS
1731       *
1732       * This record is part of the File Protection Block. It
1733       * contains information about the read/write password of the
1734       * file. All record contents following this record will be
1735       * encrypted.
1736       *
1737       * --    "OpenOffice.org's Documentation of the Microsoft
1738       *         Excel File Format"
1739       *
1740       * The decryption functions and objects used from here on in
1741       * are based on the source of Spreadsheet-ParseExcel:
1742       * http://search.cpan.org/~jmcnamara/Spreadsheet-ParseExcel/
1743       */
1744      private function readFilepass()
1745      {
1746          $length = self::getInt2d($this->data, $this->pos + 2);
1747  
1748          if ($length != 54) {
1749              throw new PHPExcel_Reader_Exception('Unexpected file pass record length');
1750          }
1751          
1752          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1753          
1754          // move stream pointer to next record
1755          $this->pos += 4 + $length;
1756          
1757          if (!$this->verifyPassword('VelvetSweatshop', substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) {
1758              throw new PHPExcel_Reader_Exception('Decryption password incorrect');
1759          }
1760          
1761          $this->encryption = self::MS_BIFF_CRYPTO_RC4;
1762  
1763          // Decryption required from the record after next onwards
1764          $this->encryptionStartPos = $this->pos + self::getInt2d($this->data, $this->pos + 2);
1765      }
1766  
1767      /**
1768       * Make an RC4 decryptor for the given block
1769       *
1770       * @var int    $block      Block for which to create decrypto
1771       * @var string $valContext MD5 context state
1772       *
1773       * @return PHPExcel_Reader_Excel5_RC4
1774       */
1775      private function makeKey($block, $valContext)
1776      {
1777          $pwarray = str_repeat("\0", 64);
1778  
1779          for ($i = 0; $i < 5; $i++) {
1780              $pwarray[$i] = $valContext[$i];
1781          }
1782          
1783          $pwarray[5] = chr($block & 0xff);
1784          $pwarray[6] = chr(($block >> 8) & 0xff);
1785          $pwarray[7] = chr(($block >> 16) & 0xff);
1786          $pwarray[8] = chr(($block >> 24) & 0xff);
1787  
1788          $pwarray[9] = "\x80";
1789          $pwarray[56] = "\x48";
1790  
1791          $md5 = new PHPExcel_Reader_Excel5_MD5();
1792          $md5->add($pwarray);
1793  
1794          $s = $md5->getContext();
1795          return new PHPExcel_Reader_Excel5_RC4($s);
1796      }
1797  
1798      /**
1799       * Verify RC4 file password
1800       *
1801       * @var string $password        Password to check
1802       * @var string $docid           Document id
1803       * @var string $salt_data       Salt data
1804       * @var string $hashedsalt_data Hashed salt data
1805       * @var string &$valContext     Set to the MD5 context of the value
1806       *
1807       * @return bool Success
1808       */
1809      private function verifyPassword($password, $docid, $salt_data, $hashedsalt_data, &$valContext)
1810      {
1811          $pwarray = str_repeat("\0", 64);
1812  
1813          for ($i = 0; $i < strlen($password); $i++) {
1814              $o = ord(substr($password, $i, 1));
1815              $pwarray[2 * $i] = chr($o & 0xff);
1816              $pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff);
1817          }
1818          $pwarray[2 * $i] = chr(0x80);
1819          $pwarray[56] = chr(($i << 4) & 0xff);
1820  
1821          $md5 = new PHPExcel_Reader_Excel5_MD5();
1822          $md5->add($pwarray);
1823  
1824          $mdContext1 = $md5->getContext();
1825  
1826          $offset = 0;
1827          $keyoffset = 0;
1828          $tocopy = 5;
1829  
1830          $md5->reset();
1831  
1832          while ($offset != 16) {
1833              if ((64 - $offset) < 5) {
1834                  $tocopy = 64 - $offset;
1835              }
1836              for ($i = 0; $i <= $tocopy; $i++) {
1837                  $pwarray[$offset + $i] = $mdContext1[$keyoffset + $i];
1838              }
1839              $offset += $tocopy;
1840  
1841              if ($offset == 64) {
1842                  $md5->add($pwarray);
1843                  $keyoffset = $tocopy;
1844                  $tocopy = 5 - $tocopy;
1845                  $offset = 0;
1846                  continue;
1847              }
1848  
1849              $keyoffset = 0;
1850              $tocopy = 5;
1851              for ($i = 0; $i < 16; $i++) {
1852                  $pwarray[$offset + $i] = $docid[$i];
1853              }
1854              $offset += 16;
1855          }
1856  
1857          $pwarray[16] = "\x80";
1858          for ($i = 0; $i < 47; $i++) {
1859              $pwarray[17 + $i] = "\0";
1860          }
1861          $pwarray[56] = "\x80";
1862          $pwarray[57] = "\x0a";
1863  
1864          $md5->add($pwarray);
1865          $valContext = $md5->getContext();
1866  
1867          $key = $this->makeKey(0, $valContext);
1868  
1869          $salt = $key->RC4($salt_data);
1870          $hashedsalt = $key->RC4($hashedsalt_data);
1871          
1872          $salt .= "\x80" . str_repeat("\0", 47);
1873          $salt[56] = "\x80";
1874  
1875          $md5->reset();
1876          $md5->add($salt);
1877          $mdContext2 = $md5->getContext();
1878  
1879          return $mdContext2 == $hashedsalt;
1880      }
1881  
1882      /**
1883       * CODEPAGE
1884       *
1885       * This record stores the text encoding used to write byte
1886       * strings, stored as MS Windows code page identifier.
1887       *
1888       * --    "OpenOffice.org's Documentation of the Microsoft
1889       *         Excel File Format"
1890       */
1891      private function readCodepage()
1892      {
1893          $length = self::getInt2d($this->data, $this->pos + 2);
1894          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1895  
1896          // move stream pointer to next record
1897          $this->pos += 4 + $length;
1898  
1899          // offset: 0; size: 2; code page identifier
1900          $codepage = self::getInt2d($recordData, 0);
1901  
1902          $this->codepage = PHPExcel_Shared_CodePage::NumberToName($codepage);
1903      }
1904  
1905  
1906      /**
1907       * DATEMODE
1908       *
1909       * This record specifies the base date for displaying date
1910       * values. All dates are stored as count of days past this
1911       * base date. In BIFF2-BIFF4 this record is part of the
1912       * Calculation Settings Block. In BIFF5-BIFF8 it is
1913       * stored in the Workbook Globals Substream.
1914       *
1915       * --    "OpenOffice.org's Documentation of the Microsoft
1916       *         Excel File Format"
1917       */
1918      private function readDateMode()
1919      {
1920          $length = self::getInt2d($this->data, $this->pos + 2);
1921          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1922  
1923          // move stream pointer to next record
1924          $this->pos += 4 + $length;
1925  
1926          // offset: 0; size: 2; 0 = base 1900, 1 = base 1904
1927          PHPExcel_Shared_Date::setExcelCalendar(PHPExcel_Shared_Date::CALENDAR_WINDOWS_1900);
1928          if (ord($recordData{0}) == 1) {
1929              PHPExcel_Shared_Date::setExcelCalendar(PHPExcel_Shared_Date::CALENDAR_MAC_1904);
1930          }
1931      }
1932  
1933  
1934      /**
1935       * Read a FONT record
1936       */
1937      private function readFont()
1938      {
1939          $length = self::getInt2d($this->data, $this->pos + 2);
1940          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1941  
1942          // move stream pointer to next record
1943          $this->pos += 4 + $length;
1944  
1945          if (!$this->readDataOnly) {
1946              $objFont = new PHPExcel_Style_Font();
1947  
1948              // offset: 0; size: 2; height of the font (in twips = 1/20 of a point)
1949              $size = self::getInt2d($recordData, 0);
1950              $objFont->setSize($size / 20);
1951  
1952              // offset: 2; size: 2; option flags
1953              // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8)
1954              // bit: 1; mask 0x0002; italic
1955              $isItalic = (0x0002 & self::getInt2d($recordData, 2)) >> 1;
1956              if ($isItalic) {
1957                  $objFont->setItalic(true);
1958              }
1959  
1960              // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8)
1961              // bit: 3; mask 0x0008; strike
1962              $isStrike = (0x0008 & self::getInt2d($recordData, 2)) >> 3;
1963              if ($isStrike) {
1964                  $objFont->setStrikethrough(true);
1965              }
1966  
1967              // offset: 4; size: 2; colour index
1968              $colorIndex = self::getInt2d($recordData, 4);
1969              $objFont->colorIndex = $colorIndex;
1970  
1971              // offset: 6; size: 2; font weight
1972              $weight = self::getInt2d($recordData, 6);
1973              switch ($weight) {
1974                  case 0x02BC:
1975                      $objFont->setBold(true);
1976                      break;
1977              }
1978  
1979              // offset: 8; size: 2; escapement type
1980              $escapement = self::getInt2d($recordData, 8);
1981              switch ($escapement) {
1982                  case 0x0001:
1983                      $objFont->setSuperScript(true);
1984                      break;
1985                  case 0x0002:
1986                      $objFont->setSubScript(true);
1987                      break;
1988              }
1989  
1990              // offset: 10; size: 1; underline type
1991              $underlineType = ord($recordData{10});
1992              switch ($underlineType) {
1993                  case 0x00:
1994                      break; // no underline
1995                  case 0x01:
1996                      $objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_SINGLE);
1997                      break;
1998                  case 0x02:
1999                      $objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_DOUBLE);
2000                      break;
2001                  case 0x21:
2002                      $objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_SINGLEACCOUNTING);
2003                      break;
2004                  case 0x22:
2005                      $objFont->setUnderline(PHPExcel_Style_Font::UNDERLINE_DOUBLEACCOUNTING);
2006                      break;
2007              }
2008  
2009              // offset: 11; size: 1; font family
2010              // offset: 12; size: 1; character set
2011              // offset: 13; size: 1; not used
2012              // offset: 14; size: var; font name
2013              if ($this->version == self::XLS_BIFF8) {
2014                  $string = self::readUnicodeStringShort(substr($recordData, 14));
2015              } else {
2016                  $string = $this->readByteStringShort(substr($recordData, 14));
2017              }
2018              $objFont->setName($string['value']);
2019  
2020              $this->objFonts[] = $objFont;
2021          }
2022      }
2023  
2024  
2025      /**
2026       * FORMAT
2027       *
2028       * This record contains information about a number format.
2029       * All FORMAT records occur together in a sequential list.
2030       *
2031       * In BIFF2-BIFF4 other records referencing a FORMAT record
2032       * contain a zero-based index into this list. From BIFF5 on
2033       * the FORMAT record contains the index itself that will be
2034       * used by other records.
2035       *
2036       * --    "OpenOffice.org's Documentation of the Microsoft
2037       *         Excel File Format"
2038       */
2039      private function readFormat()
2040      {
2041          $length = self::getInt2d($this->data, $this->pos + 2);
2042          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2043  
2044          // move stream pointer to next record
2045          $this->pos += 4 + $length;
2046  
2047          if (!$this->readDataOnly) {
2048              $indexCode = self::getInt2d($recordData, 0);
2049  
2050              if ($this->version == self::XLS_BIFF8) {
2051                  $string = self::readUnicodeStringLong(substr($recordData, 2));
2052              } else {
2053                  // BIFF7
2054                  $string = $this->readByteStringShort(substr($recordData, 2));
2055              }
2056  
2057              $formatString = $string['value'];
2058              $this->formats[$indexCode] = $formatString;
2059          }
2060      }
2061  
2062  
2063      /**
2064       * XF - Extended Format
2065       *
2066       * This record contains formatting information for cells, rows, columns or styles.
2067       * According to http://support.microsoft.com/kb/147732 there are always at least 15 cell style XF
2068       * and 1 cell XF.
2069       * Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF
2070       * and XF record 15 is a cell XF
2071       * We only read the first cell style XF and skip the remaining cell style XF records
2072       * We read all cell XF records.
2073       *
2074       * --    "OpenOffice.org's Documentation of the Microsoft
2075       *         Excel File Format"
2076       */
2077      private function readXf()
2078      {
2079          $length = self::getInt2d($this->data, $this->pos + 2);
2080          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2081  
2082          // move stream pointer to next record
2083          $this->pos += 4 + $length;
2084  
2085          $objStyle = new PHPExcel_Style();
2086  
2087          if (!$this->readDataOnly) {
2088              // offset:  0; size: 2; Index to FONT record
2089              if (self::getInt2d($recordData, 0) < 4) {
2090                  $fontIndex = self::getInt2d($recordData, 0);
2091              } else {
2092                  // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
2093                  // check the OpenOffice documentation of the FONT record
2094                  $fontIndex = self::getInt2d($recordData, 0) - 1;
2095              }
2096              $objStyle->setFont($this->objFonts[$fontIndex]);
2097  
2098              // offset:  2; size: 2; Index to FORMAT record
2099              $numberFormatIndex = self::getInt2d($recordData, 2);
2100              if (isset($this->formats[$numberFormatIndex])) {
2101                  // then we have user-defined format code
2102                  $numberformat = array('code' => $this->formats[$numberFormatIndex]);
2103              } elseif (($code = PHPExcel_Style_NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') {
2104                  // then we have built-in format code
2105                  $numberformat = array('code' => $code);
2106              } else {
2107                  // we set the general format code
2108                  $numberformat = array('code' => 'General');
2109              }
2110              $objStyle->getNumberFormat()->setFormatCode($numberformat['code']);
2111  
2112              // offset:  4; size: 2; XF type, cell protection, and parent style XF
2113              // bit 2-0; mask 0x0007; XF_TYPE_PROT
2114              $xfTypeProt = self::getInt2d($recordData, 4);
2115              // bit 0; mask 0x01; 1 = cell is locked
2116              $isLocked = (0x01 & $xfTypeProt) >> 0;
2117              $objStyle->getProtection()->setLocked($isLocked ? PHPExcel_Style_Protection::PROTECTION_INHERIT : PHPExcel_Style_Protection::PROTECTION_UNPROTECTED);
2118  
2119              // bit 1; mask 0x02; 1 = Formula is hidden
2120              $isHidden = (0x02 & $xfTypeProt) >> 1;
2121              $objStyle->getProtection()->setHidden($isHidden ? PHPExcel_Style_Protection::PROTECTION_PROTECTED : PHPExcel_Style_Protection::PROTECTION_UNPROTECTED);
2122  
2123              // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF
2124              $isCellStyleXf = (0x04 & $xfTypeProt) >> 2;
2125  
2126              // offset:  6; size: 1; Alignment and text break
2127              // bit 2-0, mask 0x07; horizontal alignment
2128              $horAlign = (0x07 & ord($recordData{6})) >> 0;
2129              switch ($horAlign) {
2130                  case 0:
2131                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_GENERAL);
2132                      break;
2133                  case 1:
2134                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
2135                      break;
2136                  case 2:
2137                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER);
2138                      break;
2139                  case 3:
2140                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_RIGHT);
2141                      break;
2142                  case 4:
2143                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_FILL);
2144                      break;
2145                  case 5:
2146                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_JUSTIFY);
2147                      break;
2148                  case 6:
2149                      $objStyle->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_CENTER_CONTINUOUS);
2150                      break;
2151              }
2152              // bit 3, mask 0x08; wrap text
2153              $wrapText = (0x08 & ord($recordData{6})) >> 3;
2154              switch ($wrapText) {
2155                  case 0:
2156                      $objStyle->getAlignment()->setWrapText(false);
2157                      break;
2158                  case 1:
2159                      $objStyle->getAlignment()->setWrapText(true);
2160                      break;
2161              }
2162              // bit 6-4, mask 0x70; vertical alignment
2163              $vertAlign = (0x70 & ord($recordData{6})) >> 4;
2164              switch ($vertAlign) {
2165                  case 0:
2166                      $objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_TOP);
2167                      break;
2168                  case 1:
2169                      $objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_CENTER);
2170                      break;
2171                  case 2:
2172                      $objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_BOTTOM);
2173                      break;
2174                  case 3:
2175                      $objStyle->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_JUSTIFY);
2176                      break;
2177              }
2178  
2179              if ($this->version == self::XLS_BIFF8) {
2180                  // offset:  7; size: 1; XF_ROTATION: Text rotation angle
2181                  $angle = ord($recordData{7});
2182                  $rotation = 0;
2183                  if ($angle <= 90) {
2184                      $rotation = $angle;
2185                  } elseif ($angle <= 180) {
2186                      $rotation = 90 - $angle;
2187                  } elseif ($angle == 255) {
2188                      $rotation = -165;
2189                  }
2190                  $objStyle->getAlignment()->setTextRotation($rotation);
2191  
2192                  // offset:  8; size: 1; Indentation, shrink to cell size, and text direction
2193                  // bit: 3-0; mask: 0x0F; indent level
2194                  $indent = (0x0F & ord($recordData{8})) >> 0;
2195                  $objStyle->getAlignment()->setIndent($indent);
2196  
2197                  // bit: 4; mask: 0x10; 1 = shrink content to fit into cell
2198                  $shrinkToFit = (0x10 & ord($recordData{8})) >> 4;
2199                  switch ($shrinkToFit) {
2200                      case 0:
2201                          $objStyle->getAlignment()->setShrinkToFit(false);
2202                          break;
2203                      case 1:
2204                          $objStyle->getAlignment()->setShrinkToFit(true);
2205                          break;
2206                  }
2207  
2208                  // offset:  9; size: 1; Flags used for attribute groups
2209  
2210                  // offset: 10; size: 4; Cell border lines and background area
2211                  // bit: 3-0; mask: 0x0000000F; left style
2212                  if ($bordersLeftStyle = self::mapBorderStyle((0x0000000F & self::getInt4d($recordData, 10)) >> 0)) {
2213                      $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle);
2214                  }
2215                  // bit: 7-4; mask: 0x000000F0; right style
2216                  if ($bordersRightStyle = self::mapBorderStyle((0x000000F0 & self::getInt4d($recordData, 10)) >> 4)) {
2217                      $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle);
2218                  }
2219                  // bit: 11-8; mask: 0x00000F00; top style
2220                  if ($bordersTopStyle = self::mapBorderStyle((0x00000F00 & self::getInt4d($recordData, 10)) >> 8)) {
2221                      $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle);
2222                  }
2223                  // bit: 15-12; mask: 0x0000F000; bottom style
2224                  if ($bordersBottomStyle = self::mapBorderStyle((0x0000F000 & self::getInt4d($recordData, 10)) >> 12)) {
2225                      $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle);
2226                  }
2227                  // bit: 22-16; mask: 0x007F0000; left color
2228                  $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::getInt4d($recordData, 10)) >> 16;
2229  
2230                  // bit: 29-23; mask: 0x3F800000; right color
2231                  $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::getInt4d($recordData, 10)) >> 23;
2232  
2233                  // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom
2234                  $diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false;
2235  
2236                  // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right
2237                  $diagonalUp = (0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false;
2238  
2239                  if ($diagonalUp == false && $diagonalDown == false) {
2240                      $objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_NONE);
2241                  } elseif ($diagonalUp == true && $diagonalDown == false) {
2242                      $objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_UP);
2243                  } elseif ($diagonalUp == false && $diagonalDown == true) {
2244                      $objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_DOWN);
2245                  } elseif ($diagonalUp == true && $diagonalDown == true) {
2246                      $objStyle->getBorders()->setDiagonalDirection(PHPExcel_Style_Borders::DIAGONAL_BOTH);
2247                  }
2248  
2249                  // offset: 14; size: 4;
2250                  // bit: 6-0; mask: 0x0000007F; top color
2251                  $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::getInt4d($recordData, 14)) >> 0;
2252  
2253                  // bit: 13-7; mask: 0x00003F80; bottom color
2254                  $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::getInt4d($recordData, 14)) >> 7;
2255  
2256                  // bit: 20-14; mask: 0x001FC000; diagonal color
2257                  $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::getInt4d($recordData, 14)) >> 14;
2258  
2259                  // bit: 24-21; mask: 0x01E00000; diagonal style
2260                  if ($bordersDiagonalStyle = self::mapBorderStyle((0x01E00000 & self::getInt4d($recordData, 14)) >> 21)) {
2261                      $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle);
2262                  }
2263  
2264                  // bit: 31-26; mask: 0xFC000000 fill pattern
2265                  if ($fillType = self::mapFillPattern((0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) {
2266                      $objStyle->getFill()->setFillType($fillType);
2267                  }
2268                  // offset: 18; size: 2; pattern and background colour
2269                  // bit: 6-0; mask: 0x007F; color index for pattern color
2270                  $objStyle->getFill()->startcolorIndex = (0x007F & self::getInt2d($recordData, 18)) >> 0;
2271  
2272                  // bit: 13-7; mask: 0x3F80; color index for pattern background
2273                  $objStyle->getFill()->endcolorIndex = (0x3F80 & self::getInt2d($recordData, 18)) >> 7;
2274              } else {
2275                  // BIFF5
2276  
2277                  // offset: 7; size: 1; Text orientation and flags
2278                  $orientationAndFlags = ord($recordData{7});
2279  
2280                  // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation
2281                  $xfOrientation = (0x03 & $orientationAndFlags) >> 0;
2282                  switch ($xfOrientation) {
2283                      case 0:
2284                          $objStyle->getAlignment()->setTextRotation(0);
2285                          break;
2286                      case 1:
2287                          $objStyle->getAlignment()->setTextRotation(-165);
2288                          break;
2289                      case 2:
2290                          $objStyle->getAlignment()->setTextRotation(90);
2291                          break;
2292                      case 3:
2293                          $objStyle->getAlignment()->setTextRotation(-90);
2294                          break;
2295                  }
2296  
2297                  // offset: 8; size: 4; cell border lines and background area
2298                  $borderAndBackground = self::getInt4d($recordData, 8);
2299  
2300                  // bit: 6-0; mask: 0x0000007F; color index for pattern color
2301                  $objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0;
2302  
2303                  // bit: 13-7; mask: 0x00003F80; color index for pattern background
2304                  $objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7;
2305  
2306                  // bit: 21-16; mask: 0x003F0000; fill pattern
2307                  $objStyle->getFill()->setFillType(self::mapFillPattern((0x003F0000 & $borderAndBackground) >> 16));
2308  
2309                  // bit: 24-22; mask: 0x01C00000; bottom line style
2310                  $objStyle->getBorders()->getBottom()->setBorderStyle(self::mapBorderStyle((0x01C00000 & $borderAndBackground) >> 22));
2311  
2312                  // bit: 31-25; mask: 0xFE000000; bottom line color
2313                  $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25;
2314  
2315                  // offset: 12; size: 4; cell border lines
2316                  $borderLines = self::getInt4d($recordData, 12);
2317  
2318                  // bit: 2-0; mask: 0x00000007; top line style
2319                  $objStyle->getBorders()->getTop()->setBorderStyle(self::mapBorderStyle((0x00000007 & $borderLines) >> 0));
2320  
2321                  // bit: 5-3; mask: 0x00000038; left line style
2322                  $objStyle->getBorders()->getLeft()->setBorderStyle(self::mapBorderStyle((0x00000038 & $borderLines) >> 3));
2323  
2324                  // bit: 8-6; mask: 0x000001C0; right line style
2325                  $objStyle->getBorders()->getRight()->setBorderStyle(self::mapBorderStyle((0x000001C0 & $borderLines) >> 6));
2326  
2327                  // bit: 15-9; mask: 0x0000FE00; top line color index
2328                  $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9;
2329  
2330                  // bit: 22-16; mask: 0x007F0000; left line color index
2331                  $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16;
2332  
2333                  // bit: 29-23; mask: 0x3F800000; right line color index
2334                  $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23;
2335              }
2336  
2337              // add cellStyleXf or cellXf and update mapping
2338              if ($isCellStyleXf) {
2339                  // we only read one style XF record which is always the first
2340                  if ($this->xfIndex == 0) {
2341                      $this->phpExcel->addCellStyleXf($objStyle);
2342                      $this->mapCellStyleXfIndex[$this->xfIndex] = 0;
2343                  }
2344              } else {
2345                  // we read all cell XF records
2346                  $this->phpExcel->addCellXf($objStyle);
2347                  $this->mapCellXfIndex[$this->xfIndex] = count($this->phpExcel->getCellXfCollection()) - 1;
2348              }
2349  
2350              // update XF index for when we read next record
2351              ++$this->xfIndex;
2352          }
2353      }
2354  
2355  
2356      /**
2357       *
2358       */
2359      private function readXfExt()
2360      {
2361          $length = self::getInt2d($this->data, $this->pos + 2);
2362          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2363  
2364          // move stream pointer to next record
2365          $this->pos += 4 + $length;
2366  
2367          if (!$this->readDataOnly) {
2368              // offset: 0; size: 2; 0x087D = repeated header
2369  
2370              // offset: 2; size: 2
2371  
2372              // offset: 4; size: 8; not used
2373  
2374              // offset: 12; size: 2; record version
2375  
2376              // offset: 14; size: 2; index to XF record which this record modifies
2377              $ixfe = self::getInt2d($recordData, 14);
2378  
2379              // offset: 16; size: 2; not used
2380  
2381              // offset: 18; size: 2; number of extension properties that follow
2382              $cexts = self::getInt2d($recordData, 18);
2383  
2384              // start reading the actual extension data
2385              $offset = 20;
2386              while ($offset < $length) {
2387                  // extension type
2388                  $extType = self::getInt2d($recordData, $offset);
2389  
2390                  // extension length
2391                  $cb = self::getInt2d($recordData, $offset + 2);
2392  
2393                  // extension data
2394                  $extData = substr($recordData, $offset + 4, $cb);
2395  
2396                  switch ($extType) {
2397                      case 4:        // fill start color
2398                          $xclfType  = self::getInt2d($extData, 0); // color type
2399                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2400  
2401                          if ($xclfType == 2) {
2402                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2403  
2404                              // modify the relevant style property
2405                              if (isset($this->mapCellXfIndex[$ixfe])) {
2406                                  $fill = $this->phpExcel->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
2407                                  $fill->getStartColor()->setRGB($rgb);
2408                                  unset($fill->startcolorIndex); // normal color index does not apply, discard
2409                              }
2410                          }
2411                          break;
2412                      case 5:        // fill end color
2413                          $xclfType  = self::getInt2d($extData, 0); // color type
2414                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2415  
2416                          if ($xclfType == 2) {
2417                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2418  
2419                              // modify the relevant style property
2420                              if (isset($this->mapCellXfIndex[$ixfe])) {
2421                                  $fill = $this->phpExcel->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
2422                                  $fill->getEndColor()->setRGB($rgb);
2423                                  unset($fill->endcolorIndex); // normal color index does not apply, discard
2424                              }
2425                          }
2426                          break;
2427                      case 7:        // border color top
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                                  $top = $this->phpExcel->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getTop();
2437                                  $top->getColor()->setRGB($rgb);
2438                                  unset($top->colorIndex); // normal color index does not apply, discard
2439                              }
2440                          }
2441                          break;
2442                      case 8:        // border color bottom
2443                          $xclfType  = self::getInt2d($extData, 0); // color type
2444                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2445  
2446                          if ($xclfType == 2) {
2447                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2448  
2449                              // modify the relevant style property
2450                              if (isset($this->mapCellXfIndex[$ixfe])) {
2451                                  $bottom = $this->phpExcel->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getBottom();
2452                                  $bottom->getColor()->setRGB($rgb);
2453                                  unset($bottom->colorIndex); // normal color index does not apply, discard
2454                              }
2455                          }
2456                          break;
2457                      case 9:        // border color left
2458                          $xclfType  = self::getInt2d($extData, 0); // color type
2459                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2460  
2461                          if ($xclfType == 2) {
2462                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2463  
2464                              // modify the relevant style property
2465                              if (isset($this->mapCellXfIndex[$ixfe])) {
2466                                  $left = $this->phpExcel->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getLeft();
2467                                  $left->getColor()->setRGB($rgb);
2468                                  unset($left->colorIndex); // normal color index does not apply, discard
2469                              }
2470                          }
2471                          break;
2472                      case 10:        // border color right
2473                          $xclfType  = self::getInt2d($extData, 0); // color type
2474                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2475  
2476                          if ($xclfType == 2) {
2477                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2478  
2479                              // modify the relevant style property
2480                              if (isset($this->mapCellXfIndex[$ixfe])) {
2481                                  $right = $this->phpExcel->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getRight();
2482                                  $right->getColor()->setRGB($rgb);
2483                                  unset($right->colorIndex); // normal color index does not apply, discard
2484                              }
2485                          }
2486                          break;
2487                      case 11:        // border color diagonal
2488                          $xclfType  = self::getInt2d($extData, 0); // color type
2489                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2490  
2491                          if ($xclfType == 2) {
2492                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2493  
2494                              // modify the relevant style property
2495                              if (isset($this->mapCellXfIndex[$ixfe])) {
2496                                  $diagonal = $this->phpExcel->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getDiagonal();
2497                                  $diagonal->getColor()->setRGB($rgb);
2498                                  unset($diagonal->colorIndex); // normal color index does not apply, discard
2499                              }
2500                          }
2501                          break;
2502                      case 13:    // font color
2503                          $xclfType  = self::getInt2d($extData, 0); // color type
2504                          $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2505  
2506                          if ($xclfType == 2) {
2507                              $rgb = sprintf('%02X%02X%02X', ord($xclrValue{0}), ord($xclrValue{1}), ord($xclrValue{2}));
2508  
2509                              // modify the relevant style property
2510                              if (isset($this->mapCellXfIndex[$ixfe])) {
2511                                  $font = $this->phpExcel->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont();
2512                                  $font->getColor()->setRGB($rgb);
2513                                  unset($font->colorIndex); // normal color index does not apply, discard
2514                              }
2515                          }
2516                          break;
2517                  }
2518  
2519                  $offset += $cb;
2520              }
2521          }
2522  
2523      }
2524  
2525  
2526      /**
2527       * Read STYLE record
2528       */
2529      private function readStyle()
2530      {
2531          $length = self::getInt2d($this->data, $this->pos + 2);
2532          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2533  
2534          // move stream pointer to next record
2535          $this->pos += 4 + $length;
2536  
2537          if (!$this->readDataOnly) {
2538              // offset: 0; size: 2; index to XF record and flag for built-in style
2539              $ixfe = self::getInt2d($recordData, 0);
2540  
2541              // bit: 11-0; mask 0x0FFF; index to XF record
2542              $xfIndex = (0x0FFF & $ixfe) >> 0;
2543  
2544              // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style
2545              $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15);
2546  
2547              if ($isBuiltIn) {
2548                  // offset: 2; size: 1; identifier for built-in style
2549                  $builtInId = ord($recordData{2});
2550  
2551                  switch ($builtInId) {
2552                      case 0x00:
2553                          // currently, we are not using this for anything
2554                          break;
2555                      default:
2556                          break;
2557                  }
2558              } else {
2559                  // user-defined; not supported by PHPExcel
2560              }
2561          }
2562      }
2563  
2564  
2565      /**
2566       * Read PALETTE record
2567       */
2568      private function readPalette()
2569      {
2570          $length = self::getInt2d($this->data, $this->pos + 2);
2571          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2572  
2573          // move stream pointer to next record
2574          $this->pos += 4 + $length;
2575  
2576          if (!$this->readDataOnly) {
2577              // offset: 0; size: 2; number of following colors
2578              $nm = self::getInt2d($recordData, 0);
2579  
2580              // list of RGB colors
2581              for ($i = 0; $i < $nm; ++$i) {
2582                  $rgb = substr($recordData, 2 + 4 * $i, 4);
2583                  $this->palette[] = self::readRGB($rgb);
2584              }
2585          }
2586      }
2587  
2588  
2589      /**
2590       * SHEET
2591       *
2592       * This record is  located in the  Workbook Globals
2593       * Substream  and represents a sheet inside the workbook.
2594       * One SHEET record is written for each sheet. It stores the
2595       * sheet name and a stream offset to the BOF record of the
2596       * respective Sheet Substream within the Workbook Stream.
2597       *
2598       * --    "OpenOffice.org's Documentation of the Microsoft
2599       *         Excel File Format"
2600       */
2601      private function readSheet()
2602      {
2603          $length = self::getInt2d($this->data, $this->pos + 2);
2604          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2605  
2606          // offset: 0; size: 4; absolute stream position of the BOF record of the sheet
2607          // NOTE: not encrypted
2608          $rec_offset = self::getInt4d($this->data, $this->pos + 4);
2609  
2610          // move stream pointer to next record
2611          $this->pos += 4 + $length;
2612  
2613          // offset: 4; size: 1; sheet state
2614          switch (ord($recordData{4})) {
2615              case 0x00:
2616                  $sheetState = PHPExcel_Worksheet::SHEETSTATE_VISIBLE;
2617                  break;
2618              case 0x01:
2619                  $sheetState = PHPExcel_Worksheet::SHEETSTATE_HIDDEN;
2620                  break;
2621              case 0x02:
2622                  $sheetState = PHPExcel_Worksheet::SHEETSTATE_VERYHIDDEN;
2623                  break;
2624              default:
2625                  $sheetState = PHPExcel_Worksheet::SHEETSTATE_VISIBLE;
2626                  break;
2627          }
2628  
2629          // offset: 5; size: 1; sheet type
2630          $sheetType = ord($recordData{5});
2631  
2632          // offset: 6; size: var; sheet name
2633          if ($this->version == self::XLS_BIFF8) {
2634              $string = self::readUnicodeStringShort(substr($recordData, 6));
2635              $rec_name = $string['value'];
2636          } elseif ($this->version == self::XLS_BIFF7) {
2637              $string = $this->readByteStringShort(substr($recordData, 6));
2638              $rec_name = $string['value'];
2639          }
2640  
2641          $this->sheets[] = array(
2642              'name' => $rec_name,
2643              'offset' => $rec_offset,
2644              'sheetState' => $sheetState,
2645              'sheetType' => $sheetType,
2646          );
2647      }
2648  
2649  
2650      /**
2651       * Read EXTERNALBOOK record
2652       */
2653      private function readExternalBook()
2654      {
2655          $length = self::getInt2d($this->data, $this->pos + 2);
2656          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2657  
2658          // move stream pointer to next record
2659          $this->pos += 4 + $length;
2660  
2661          // offset within record data
2662          $offset = 0;
2663  
2664          // there are 4 types of records
2665          if (strlen($recordData) > 4) {
2666              // external reference
2667              // offset: 0; size: 2; number of sheet names ($nm)
2668              $nm = self::getInt2d($recordData, 0);
2669              $offset += 2;
2670  
2671              // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length)
2672              $encodedUrlString = self::readUnicodeStringLong(substr($recordData, 2));
2673              $offset += $encodedUrlString['size'];
2674  
2675              // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length)
2676              $externalSheetNames = array();
2677              for ($i = 0; $i < $nm; ++$i) {
2678                  $externalSheetNameString = self::readUnicodeStringLong(substr($recordData, $offset));
2679                  $externalSheetNames[] = $externalSheetNameString['value'];
2680                  $offset += $externalSheetNameString['size'];
2681              }
2682  
2683              // store the record data
2684              $this->externalBooks[] = array(
2685                  'type' => 'external',
2686                  'encodedUrl' => $encodedUrlString['value'],
2687                  'externalSheetNames' => $externalSheetNames,
2688              );
2689          } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) {
2690              // internal reference
2691              // offset: 0; size: 2; number of sheet in this document
2692              // offset: 2; size: 2; 0x01 0x04
2693              $this->externalBooks[] = array(
2694                  'type' => 'internal',
2695              );
2696          } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) {
2697              // add-in function
2698              // offset: 0; size: 2; 0x0001
2699              $this->externalBooks[] = array(
2700                  'type' => 'addInFunction',
2701              );
2702          } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) {
2703              // DDE links, OLE links
2704              // offset: 0; size: 2; 0x0000
2705              // offset: 2; size: var; encoded source document name
2706              $this->externalBooks[] = array(
2707                  'type' => 'DDEorOLE',
2708              );
2709          }
2710      }
2711  
2712  
2713      /**
2714       * Read EXTERNNAME record.
2715       */
2716      private function readExternName()
2717      {
2718          $length = self::getInt2d($this->data, $this->pos + 2);
2719          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2720  
2721          // move stream pointer to next record
2722          $this->pos += 4 + $length;
2723  
2724          // external sheet references provided for named cells
2725          if ($this->version == self::XLS_BIFF8) {
2726              // offset: 0; size: 2; options
2727              $options = self::getInt2d($recordData, 0);
2728  
2729              // offset: 2; size: 2;
2730  
2731              // offset: 4; size: 2; not used
2732  
2733              // offset: 6; size: var
2734              $nameString = self::readUnicodeStringShort(substr($recordData, 6));
2735  
2736              // offset: var; size: var; formula data
2737              $offset = 6 + $nameString['size'];
2738              $formula = $this->getFormulaFromStructure(substr($recordData, $offset));
2739  
2740              $this->externalNames[] = array(
2741                  'name' => $nameString['value'],
2742                  'formula' => $formula,
2743              );
2744          }
2745      }
2746  
2747  
2748      /**
2749       * Read EXTERNSHEET record
2750       */
2751      private function readExternSheet()
2752      {
2753          $length = self::getInt2d($this->data, $this->pos + 2);
2754          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2755  
2756          // move stream pointer to next record
2757          $this->pos += 4 + $length;
2758  
2759          // external sheet references provided for named cells
2760          if ($this->version == self::XLS_BIFF8) {
2761              // offset: 0; size: 2; number of following ref structures
2762              $nm = self::getInt2d($recordData, 0);
2763              for ($i = 0; $i < $nm; ++$i) {
2764                  $this->ref[] = array(
2765                      // offset: 2 + 6 * $i; index to EXTERNALBOOK record
2766                      'externalBookIndex' => self::getInt2d($recordData, 2 + 6 * $i),
2767                      // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record
2768                      'firstSheetIndex' => self::getInt2d($recordData, 4 + 6 * $i),
2769                      // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record
2770                      'lastSheetIndex' => self::getInt2d($recordData, 6 + 6 * $i),
2771                  );
2772              }
2773          }
2774      }
2775  
2776  
2777      /**
2778       * DEFINEDNAME
2779       *
2780       * This record is part of a Link Table. It contains the name
2781       * and the token array of an internal defined name. Token
2782       * arrays of defined names contain tokens with aberrant
2783       * token classes.
2784       *
2785       * --    "OpenOffice.org's Documentation of the Microsoft
2786       *         Excel File Format"
2787       */
2788      private function readDefinedName()
2789      {
2790          $length = self::getInt2d($this->data, $this->pos + 2);
2791          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2792  
2793          // move stream pointer to next record
2794          $this->pos += 4 + $length;
2795  
2796          if ($this->version == self::XLS_BIFF8) {
2797              // retrieves named cells
2798  
2799              // offset: 0; size: 2; option flags
2800              $opts = self::getInt2d($recordData, 0);
2801  
2802              // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name
2803              $isBuiltInName = (0x0020 & $opts) >> 5;
2804  
2805              // offset: 2; size: 1; keyboard shortcut
2806  
2807              // offset: 3; size: 1; length of the name (character count)
2808              $nlen = ord($recordData{3});
2809  
2810              // offset: 4; size: 2; size of the formula data (it can happen that this is zero)
2811              // note: there can also be additional data, this is not included in $flen
2812              $flen = self::getInt2d($recordData, 4);
2813  
2814              // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based)
2815              $scope = self::getInt2d($recordData, 8);
2816  
2817              // offset: 14; size: var; Name (Unicode string without length field)
2818              $string = self::readUnicodeString(substr($recordData, 14), $nlen);
2819  
2820              // offset: var; size: $flen; formula data
2821              $offset = 14 + $string['size'];
2822              $formulaStructure = pack('v', $flen) . substr($recordData, $offset);
2823  
2824              try {
2825                  $formula = $this->getFormulaFromStructure($formulaStructure);
2826              } catch (PHPExcel_Exception $e) {
2827                  $formula = '';
2828              }
2829  
2830              $this->definedname[] = array(
2831                  'isBuiltInName' => $isBuiltInName,
2832                  'name' => $string['value'],
2833                  'formula' => $formula,
2834                  'scope' => $scope,
2835              );
2836          }
2837      }
2838  
2839  
2840      /**
2841       * Read MSODRAWINGGROUP record
2842       */
2843      private function readMsoDrawingGroup()
2844      {
2845          $length = self::getInt2d($this->data, $this->pos + 2);
2846  
2847          // get spliced record data
2848          $splicedRecordData = $this->getSplicedRecordData();
2849          $recordData = $splicedRecordData['recordData'];
2850  
2851          $this->drawingGroupData .= $recordData;
2852      }
2853  
2854  
2855      /**
2856       * SST - Shared String Table
2857       *
2858       * This record contains a list of all strings used anywhere
2859       * in the workbook. Each string occurs only once. The
2860       * workbook uses indexes into the list to reference the
2861       * strings.
2862       *
2863       * --    "OpenOffice.org's Documentation of the Microsoft
2864       *         Excel File Format"
2865       **/
2866      private function readSst()
2867      {
2868          // offset within (spliced) record data
2869          $pos = 0;
2870  
2871          // get spliced record data
2872          $splicedRecordData = $this->getSplicedRecordData();
2873  
2874          $recordData = $splicedRecordData['recordData'];
2875          $spliceOffsets = $splicedRecordData['spliceOffsets'];
2876  
2877          // offset: 0; size: 4; total number of strings in the workbook
2878          $pos += 4;
2879  
2880          // offset: 4; size: 4; number of following strings ($nm)
2881          $nm = self::getInt4d($recordData, 4);
2882          $pos += 4;
2883  
2884          // loop through the Unicode strings (16-bit length)
2885          for ($i = 0; $i < $nm; ++$i) {
2886              // number of characters in the Unicode string
2887              $numChars = self::getInt2d($recordData, $pos);
2888              $pos += 2;
2889  
2890              // option flags
2891              $optionFlags = ord($recordData{$pos});
2892              ++$pos;
2893  
2894              // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed
2895              $isCompressed = (($optionFlags & 0x01) == 0) ;
2896  
2897              // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic
2898              $hasAsian = (($optionFlags & 0x04) != 0);
2899  
2900              // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text
2901              $hasRichText = (($optionFlags & 0x08) != 0);
2902  
2903              if ($hasRichText) {
2904                  // number of Rich-Text formatting runs
2905                  $formattingRuns = self::getInt2d($recordData, $pos);
2906                  $pos += 2;
2907              }
2908  
2909              if ($hasAsian) {
2910                  // size of Asian phonetic setting
2911                  $extendedRunLength = self::getInt4d($recordData, $pos);
2912                  $pos += 4;
2913              }
2914  
2915              // expected byte length of character array if not split
2916              $len = ($isCompressed) ? $numChars : $numChars * 2;
2917  
2918              // look up limit position
2919              foreach ($spliceOffsets as $spliceOffset) {
2920                  // it can happen that the string is empty, therefore we need
2921                  // <= and not just <
2922                  if ($pos <= $spliceOffset) {
2923                      $limitpos = $spliceOffset;
2924                      break;
2925                  }
2926              }
2927  
2928              if ($pos + $len <= $limitpos) {
2929                  // character array is not split between records
2930  
2931                  $retstr = substr($recordData, $pos, $len);
2932                  $pos += $len;
2933              } else {
2934                  // character array is split between records
2935  
2936                  // first part of character array
2937                  $retstr = substr($recordData, $pos, $limitpos - $pos);
2938  
2939                  $bytesRead = $limitpos - $pos;
2940  
2941                  // remaining characters in Unicode string
2942                  $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));
2943  
2944                  $pos = $limitpos;
2945  
2946                  // keep reading the characters
2947                  while ($charsLeft > 0) {
2948                      // look up next limit position, in case the string span more than one continue record
2949                      foreach ($spliceOffsets as $spliceOffset) {
2950                          if ($pos < $spliceOffset) {
2951                              $limitpos = $spliceOffset;
2952                              break;
2953                          }
2954                      }
2955  
2956                      // repeated option flags
2957                      // OpenOffice.org documentation 5.21
2958                      $option = ord($recordData{$pos});
2959                      ++$pos;
2960  
2961                      if ($isCompressed && ($option == 0)) {
2962                          // 1st fragment compressed
2963                          // this fragment compressed
2964                          $len = min($charsLeft, $limitpos - $pos);
2965                          $retstr .= substr($recordData, $pos, $len);
2966                          $charsLeft -= $len;
2967                          $isCompressed = true;
2968                      } elseif (!$isCompressed && ($option != 0)) {
2969                          // 1st fragment uncompressed
2970                          // this fragment uncompressed
2971                          $len = min($charsLeft * 2, $limitpos - $pos);
2972                          $retstr .= substr($recordData, $pos, $len);
2973                          $charsLeft -= $len / 2;
2974                          $isCompressed = false;
2975                      } elseif (!$isCompressed && ($option == 0)) {
2976                          // 1st fragment uncompressed
2977                          // this fragment compressed
2978                          $len = min($charsLeft, $limitpos - $pos);
2979                          for ($j = 0; $j < $len; ++$j) {
2980                              $retstr .= $recordData{$pos + $j} . chr(0);
2981                          }
2982                          $charsLeft -= $len;
2983                          $isCompressed = false;
2984                      } else {
2985                          // 1st fragment compressed
2986                          // this fragment uncompressed
2987                          $newstr = '';
2988                          for ($j = 0; $j < strlen($retstr); ++$j) {
2989                              $newstr .= $retstr[$j] . chr(0);
2990                          }
2991                          $retstr = $newstr;
2992                          $len = min($charsLeft * 2, $limitpos - $pos);
2993                          $retstr .= substr($recordData, $pos, $len);
2994                          $charsLeft -= $len / 2;
2995                          $isCompressed = false;
2996                      }
2997  
2998                      $pos += $len;
2999                  }
3000              }
3001  
3002              // convert to UTF-8
3003              $retstr = self::encodeUTF16($retstr, $isCompressed);
3004  
3005              // read additional Rich-Text information, if any
3006              $fmtRuns = array();
3007              if ($hasRichText) {
3008                  // list of formatting runs
3009                  for ($j = 0; $j < $formattingRuns; ++$j) {
3010                      // first formatted character; zero-based
3011                      $charPos = self::getInt2d($recordData, $pos + $j * 4);
3012  
3013                      // index to font record
3014                      $fontIndex = self::getInt2d($recordData, $pos + 2 + $j * 4);
3015  
3016                      $fmtRuns[] = array(
3017                          'charPos' => $charPos,
3018                          'fontIndex' => $fontIndex,
3019                      );
3020                  }
3021                  $pos += 4 * $formattingRuns;
3022              }
3023  
3024              // read additional Asian phonetics information, if any
3025              if ($hasAsian) {
3026                  // For Asian phonetic settings, we skip the extended string data
3027                  $pos += $extendedRunLength;
3028              }
3029  
3030              // store the shared sting
3031              $this->sst[] = array(
3032                  'value' => $retstr,
3033                  'fmtRuns' => $fmtRuns,
3034              );
3035          }
3036  
3037          // getSplicedRecordData() takes care of moving current position in data stream
3038      }
3039  
3040  
3041      /**
3042       * Read PRINTGRIDLINES record
3043       */
3044      private function readPrintGridlines()
3045      {
3046          $length = self::getInt2d($this->data, $this->pos + 2);
3047          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3048  
3049          // move stream pointer to next record
3050          $this->pos += 4 + $length;
3051  
3052          if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3053              // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines
3054              $printGridlines = (bool) self::getInt2d($recordData, 0);
3055              $this->phpSheet->setPrintGridlines($printGridlines);
3056          }
3057      }
3058  
3059  
3060      /**
3061       * Read DEFAULTROWHEIGHT record
3062       */
3063      private function readDefaultRowHeight()
3064      {
3065          $length = self::getInt2d($this->data, $this->pos + 2);
3066          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3067  
3068          // move stream pointer to next record
3069          $this->pos += 4 + $length;
3070  
3071          // offset: 0; size: 2; option flags
3072          // offset: 2; size: 2; default height for unused rows, (twips 1/20 point)
3073          $height = self::getInt2d($recordData, 2);
3074          $this->phpSheet->getDefaultRowDimension()->setRowHeight($height / 20);
3075      }
3076  
3077  
3078      /**
3079       * Read SHEETPR record
3080       */
3081      private function readSheetPr()
3082      {
3083          $length = self::getInt2d($this->data, $this->pos + 2);
3084          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3085  
3086          // move stream pointer to next record
3087          $this->pos += 4 + $length;
3088  
3089          // offset: 0; size: 2
3090  
3091          // bit: 6; mask: 0x0040; 0 = outline buttons above outline group
3092          $isSummaryBelow = (0x0040 & self::getInt2d($recordData, 0)) >> 6;
3093          $this->phpSheet->setShowSummaryBelow($isSummaryBelow);
3094  
3095          // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group
3096          $isSummaryRight = (0x0080 & self::getInt2d($recordData, 0)) >> 7;
3097          $this->phpSheet->setShowSummaryRight($isSummaryRight);
3098  
3099          // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages
3100          // this corresponds to radio button setting in page setup dialog in Excel
3101          $this->isFitToPages = (bool) ((0x0100 & self::getInt2d($recordData, 0)) >> 8);
3102      }
3103  
3104  
3105      /**
3106       * Read HORIZONTALPAGEBREAKS record
3107       */
3108      private function readHorizontalPageBreaks()
3109      {
3110          $length = self::getInt2d($this->data, $this->pos + 2);
3111          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3112  
3113          // move stream pointer to next record
3114          $this->pos += 4 + $length;
3115  
3116          if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3117              // offset: 0; size: 2; number of the following row index structures
3118              $nm = self::getInt2d($recordData, 0);
3119  
3120              // offset: 2; size: 6 * $nm; list of $nm row index structures
3121              for ($i = 0; $i < $nm; ++$i) {
3122                  $r = self::getInt2d($recordData, 2 + 6 * $i);
3123                  $cf = self::getInt2d($recordData, 2 + 6 * $i + 2);
3124                  $cl = self::getInt2d($recordData, 2 + 6 * $i + 4);
3125  
3126                  // not sure why two column indexes are necessary?
3127                  $this->phpSheet->setBreakByColumnAndRow($cf, $r, PHPExcel_Worksheet::BREAK_ROW);
3128              }
3129          }
3130      }
3131  
3132  
3133      /**
3134       * Read VERTICALPAGEBREAKS record
3135       */
3136      private function readVerticalPageBreaks()
3137      {
3138          $length = self::getInt2d($this->data, $this->pos + 2);
3139          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3140  
3141          // move stream pointer to next record
3142          $this->pos += 4 + $length;
3143  
3144          if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3145              // offset: 0; size: 2; number of the following column index structures
3146              $nm = self::getInt2d($recordData, 0);
3147  
3148              // offset: 2; size: 6 * $nm; list of $nm row index structures
3149              for ($i = 0; $i < $nm; ++$i) {
3150                  $c = self::getInt2d($recordData, 2 + 6 * $i);
3151                  $rf = self::getInt2d($recordData, 2 + 6 * $i + 2);
3152                  $rl = self::getInt2d($recordData, 2 + 6 * $i + 4);
3153  
3154                  // not sure why two row indexes are necessary?
3155                  $this->phpSheet->setBreakByColumnAndRow($c, $rf, PHPExcel_Worksheet::BREAK_COLUMN);
3156              }
3157          }
3158      }
3159  
3160  
3161      /**
3162       * Read HEADER record
3163       */
3164      private function readHeader()
3165      {
3166          $length = self::getInt2d($this->data, $this->pos + 2);
3167          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3168  
3169          // move stream pointer to next record
3170          $this->pos += 4 + $length;
3171  
3172          if (!$this->readDataOnly) {
3173              // offset: 0; size: var
3174              // realized that $recordData can be empty even when record exists
3175              if ($recordData) {
3176                  if ($this->version == self::XLS_BIFF8) {
3177                      $string = self::readUnicodeStringLong($recordData);
3178                  } else {
3179                      $string = $this->readByteStringShort($recordData);
3180                  }
3181  
3182                  $this->phpSheet->getHeaderFooter()->setOddHeader($string['value']);
3183                  $this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']);
3184              }
3185          }
3186      }
3187  
3188  
3189      /**
3190       * Read FOOTER record
3191       */
3192      private function readFooter()
3193      {
3194          $length = self::getInt2d($this->data, $this->pos + 2);
3195          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3196  
3197          // move stream pointer to next record
3198          $this->pos += 4 + $length;
3199  
3200          if (!$this->readDataOnly) {
3201              // offset: 0; size: var
3202              // realized that $recordData can be empty even when record exists
3203              if ($recordData) {
3204                  if ($this->version == self::XLS_BIFF8) {
3205                      $string = self::readUnicodeStringLong($recordData);
3206                  } else {
3207                      $string = $this->readByteStringShort($recordData);
3208                  }
3209                  $this->phpSheet->getHeaderFooter()->setOddFooter($string['value']);
3210                  $this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']);
3211              }
3212          }
3213      }
3214  
3215  
3216      /**
3217       * Read HCENTER record
3218       */
3219      private function readHcenter()
3220      {
3221          $length = self::getInt2d($this->data, $this->pos + 2);
3222          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3223  
3224          // move stream pointer to next record
3225          $this->pos += 4 + $length;
3226  
3227          if (!$this->readDataOnly) {
3228              // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally
3229              $isHorizontalCentered = (bool) self::getInt2d($recordData, 0);
3230  
3231              $this->phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered);
3232          }
3233      }
3234  
3235  
3236      /**
3237       * Read VCENTER record
3238       */
3239      private function readVcenter()
3240      {
3241          $length = self::getInt2d($this->data, $this->pos + 2);
3242          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3243  
3244          // move stream pointer to next record
3245          $this->pos += 4 + $length;
3246  
3247          if (!$this->readDataOnly) {
3248              // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered
3249              $isVerticalCentered = (bool) self::getInt2d($recordData, 0);
3250  
3251              $this->phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered);
3252          }
3253      }
3254  
3255  
3256      /**
3257       * Read LEFTMARGIN record
3258       */
3259      private function readLeftMargin()
3260      {
3261          $length = self::getInt2d($this->data, $this->pos + 2);
3262          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3263  
3264          // move stream pointer to next record
3265          $this->pos += 4 + $length;
3266  
3267          if (!$this->readDataOnly) {
3268              // offset: 0; size: 8
3269              $this->phpSheet->getPageMargins()->setLeft(self::extractNumber($recordData));
3270          }
3271      }
3272  
3273  
3274      /**
3275       * Read RIGHTMARGIN record
3276       */
3277      private function readRightMargin()
3278      {
3279          $length = self::getInt2d($this->data, $this->pos + 2);
3280          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3281  
3282          // move stream pointer to next record
3283          $this->pos += 4 + $length;
3284  
3285          if (!$this->readDataOnly) {
3286              // offset: 0; size: 8
3287              $this->phpSheet->getPageMargins()->setRight(self::extractNumber($recordData));
3288          }
3289      }
3290  
3291  
3292      /**
3293       * Read TOPMARGIN record
3294       */
3295      private function readTopMargin()
3296      {
3297          $length = self::getInt2d($this->data, $this->pos + 2);
3298          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3299  
3300          // move stream pointer to next record
3301          $this->pos += 4 + $length;
3302  
3303          if (!$this->readDataOnly) {
3304              // offset: 0; size: 8
3305              $this->phpSheet->getPageMargins()->setTop(self::extractNumber($recordData));
3306          }
3307      }
3308  
3309  
3310      /**
3311       * Read BOTTOMMARGIN record
3312       */
3313      private function readBottomMargin()
3314      {
3315          $length = self::getInt2d($this->data, $this->pos + 2);
3316          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3317  
3318          // move stream pointer to next record
3319          $this->pos += 4 + $length;
3320  
3321          if (!$this->readDataOnly) {
3322              // offset: 0; size: 8
3323              $this->phpSheet->getPageMargins()->setBottom(self::extractNumber($recordData));
3324          }
3325      }
3326  
3327  
3328      /**
3329       * Read PAGESETUP record
3330       */
3331      private function readPageSetup()
3332      {
3333          $length = self::getInt2d($this->data, $this->pos + 2);
3334          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3335  
3336          // move stream pointer to next record
3337          $this->pos += 4 + $length;
3338  
3339          if (!$this->readDataOnly) {
3340              // offset: 0; size: 2; paper size
3341              $paperSize = self::getInt2d($recordData, 0);
3342  
3343              // offset: 2; size: 2; scaling factor
3344              $scale = self::getInt2d($recordData, 2);
3345  
3346              // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed
3347              $fitToWidth = self::getInt2d($recordData, 6);
3348  
3349              // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed
3350              $fitToHeight = self::getInt2d($recordData, 8);
3351  
3352              // offset: 10; size: 2; option flags
3353  
3354              // bit: 1; mask: 0x0002; 0=landscape, 1=portrait
3355              $isPortrait = (0x0002 & self::getInt2d($recordData, 10)) >> 1;
3356  
3357              // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init
3358              // when this bit is set, do not use flags for those properties
3359              $isNotInit = (0x0004 & self::getInt2d($recordData, 10)) >> 2;
3360  
3361              if (!$isNotInit) {
3362                  $this->phpSheet->getPageSetup()->setPaperSize($paperSize);
3363                  switch ($isPortrait) {
3364                      case 0:
3365                          $this->phpSheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_LANDSCAPE);
3366                          break;
3367                      case 1:
3368                          $this->phpSheet->getPageSetup()->setOrientation(PHPExcel_Worksheet_PageSetup::ORIENTATION_PORTRAIT);
3369                          break;
3370                  }
3371  
3372                  $this->phpSheet->getPageSetup()->setScale($scale, false);
3373                  $this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages);
3374                  $this->phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false);
3375                  $this->phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false);
3376              }
3377  
3378              // offset: 16; size: 8; header margin (IEEE 754 floating-point value)
3379              $marginHeader = self::extractNumber(substr($recordData, 16, 8));
3380              $this->phpSheet->getPageMargins()->setHeader($marginHeader);
3381  
3382              // offset: 24; size: 8; footer margin (IEEE 754 floating-point value)
3383              $marginFooter = self::extractNumber(substr($recordData, 24, 8));
3384              $this->phpSheet->getPageMargins()->setFooter($marginFooter);
3385          }
3386      }
3387  
3388  
3389      /**
3390       * PROTECT - Sheet protection (BIFF2 through BIFF8)
3391       *   if this record is omitted, then it also means no sheet protection
3392       */
3393      private function readProtect()
3394      {
3395          $length = self::getInt2d($this->data, $this->pos + 2);
3396          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3397  
3398          // move stream pointer to next record
3399          $this->pos += 4 + $length;
3400  
3401          if ($this->readDataOnly) {
3402              return;
3403          }
3404  
3405          // offset: 0; size: 2;
3406  
3407          // bit 0, mask 0x01; 1 = sheet is protected
3408          $bool = (0x01 & self::getInt2d($recordData, 0)) >> 0;
3409          $this->phpSheet->getProtection()->setSheet((bool)$bool);
3410      }
3411  
3412  
3413      /**
3414       * SCENPROTECT
3415       */
3416      private function readScenProtect()
3417      {
3418          $length = self::getInt2d($this->data, $this->pos + 2);
3419          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3420  
3421          // move stream pointer to next record
3422          $this->pos += 4 + $length;
3423  
3424          if ($this->readDataOnly) {
3425              return;
3426          }
3427  
3428          // offset: 0; size: 2;
3429  
3430          // bit: 0, mask 0x01; 1 = scenarios are protected
3431          $bool = (0x01 & self::getInt2d($recordData, 0)) >> 0;
3432  
3433          $this->phpSheet->getProtection()->setScenarios((bool)$bool);
3434      }
3435  
3436  
3437      /**
3438       * OBJECTPROTECT
3439       */
3440      private function readObjectProtect()
3441      {
3442          $length = self::getInt2d($this->data, $this->pos + 2);
3443          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3444  
3445          // move stream pointer to next record
3446          $this->pos += 4 + $length;
3447  
3448          if ($this->readDataOnly) {
3449              return;
3450          }
3451  
3452          // offset: 0; size: 2;
3453  
3454          // bit: 0, mask 0x01; 1 = objects are protected
3455          $bool = (0x01 & self::getInt2d($recordData, 0)) >> 0;
3456  
3457          $this->phpSheet->getProtection()->setObjects((bool)$bool);
3458      }
3459  
3460  
3461      /**
3462       * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8)
3463       */
3464      private function readPassword()
3465      {
3466          $length = self::getInt2d($this->data, $this->pos + 2);
3467          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3468  
3469          // move stream pointer to next record
3470          $this->pos += 4 + $length;
3471  
3472          if (!$this->readDataOnly) {
3473              // offset: 0; size: 2; 16-bit hash value of password
3474              $password = strtoupper(dechex(self::getInt2d($recordData, 0))); // the hashed password
3475              $this->phpSheet->getProtection()->setPassword($password, true);
3476          }
3477      }
3478  
3479  
3480      /**
3481       * Read DEFCOLWIDTH record
3482       */
3483      private function readDefColWidth()
3484      {
3485          $length = self::getInt2d($this->data, $this->pos + 2);
3486          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3487  
3488          // move stream pointer to next record
3489          $this->pos += 4 + $length;
3490  
3491          // offset: 0; size: 2; default column width
3492          $width = self::getInt2d($recordData, 0);
3493          if ($width != 8) {
3494              $this->phpSheet->getDefaultColumnDimension()->setWidth($width);
3495          }
3496      }
3497  
3498  
3499      /**
3500       * Read COLINFO record
3501       */
3502      private function readColInfo()
3503      {
3504          $length = self::getInt2d($this->data, $this->pos + 2);
3505          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3506  
3507          // move stream pointer to next record
3508          $this->pos += 4 + $length;
3509  
3510          if (!$this->readDataOnly) {
3511              // offset: 0; size: 2; index to first column in range
3512              $fc = self::getInt2d($recordData, 0); // first column index
3513  
3514              // offset: 2; size: 2; index to last column in range
3515              $lc = self::getInt2d($recordData, 2); // first column index
3516  
3517              // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character
3518              $width = self::getInt2d($recordData, 4);
3519  
3520              // offset: 6; size: 2; index to XF record for default column formatting
3521              $xfIndex = self::getInt2d($recordData, 6);
3522  
3523              // offset: 8; size: 2; option flags
3524              // bit: 0; mask: 0x0001; 1= columns are hidden
3525              $isHidden = (0x0001 & self::getInt2d($recordData, 8)) >> 0;
3526  
3527              // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline)
3528              $level = (0x0700 & self::getInt2d($recordData, 8)) >> 8;
3529  
3530              // bit: 12; mask: 0x1000; 1 = collapsed
3531              $isCollapsed = (0x1000 & self::getInt2d($recordData, 8)) >> 12;
3532  
3533              // offset: 10; size: 2; not used
3534  
3535              for ($i = $fc; $i <= $lc; ++$i) {
3536                  if ($lc == 255 || $lc == 256) {
3537                      $this->phpSheet->getDefaultColumnDimension()->setWidth($width / 256);
3538                      break;
3539                  }
3540                  $this->phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256);
3541                  $this->phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden);
3542                  $this->phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level);
3543                  $this->phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed);
3544                  $this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3545              }
3546          }
3547      }
3548  
3549  
3550      /**
3551       * ROW
3552       *
3553       * This record contains the properties of a single row in a
3554       * sheet. Rows and cells in a sheet are divided into blocks
3555       * of 32 rows.
3556       *
3557       * --    "OpenOffice.org's Documentation of the Microsoft
3558       *         Excel File Format"
3559       */
3560      private function readRow()
3561      {
3562          $length = self::getInt2d($this->data, $this->pos + 2);
3563          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3564  
3565          // move stream pointer to next record
3566          $this->pos += 4 + $length;
3567  
3568          if (!$this->readDataOnly) {
3569              // offset: 0; size: 2; index of this row
3570              $r = self::getInt2d($recordData, 0);
3571  
3572              // offset: 2; size: 2; index to column of the first cell which is described by a cell record
3573  
3574              // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1
3575  
3576              // offset: 6; size: 2;
3577  
3578              // bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point
3579              $height = (0x7FFF & self::getInt2d($recordData, 6)) >> 0;
3580  
3581              // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height
3582              $useDefaultHeight = (0x8000 & self::getInt2d($recordData, 6)) >> 15;
3583  
3584              if (!$useDefaultHeight) {
3585                  $this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
3586              }
3587  
3588              // offset: 8; size: 2; not used
3589  
3590              // offset: 10; size: 2; not used in BIFF5-BIFF8
3591  
3592              // offset: 12; size: 4; option flags and default row formatting
3593  
3594              // bit: 2-0: mask: 0x00000007; outline level of the row
3595              $level = (0x00000007 & self::getInt4d($recordData, 12)) >> 0;
3596              $this->phpSheet->getRowDimension($r + 1)->setOutlineLevel($level);
3597  
3598              // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed
3599              $isCollapsed = (0x00000010 & self::getInt4d($recordData, 12)) >> 4;
3600              $this->phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed);
3601  
3602              // bit: 5; mask: 0x00000020; 1 = row is hidden
3603              $isHidden = (0x00000020 & self::getInt4d($recordData, 12)) >> 5;
3604              $this->phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden);
3605  
3606              // bit: 7; mask: 0x00000080; 1 = row has explicit format
3607              $hasExplicitFormat = (0x00000080 & self::getInt4d($recordData, 12)) >> 7;
3608  
3609              // bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record
3610              $xfIndex = (0x0FFF0000 & self::getInt4d($recordData, 12)) >> 16;
3611  
3612              if ($hasExplicitFormat) {
3613                  $this->phpSheet->getRowDimension($r + 1)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3614              }
3615          }
3616      }
3617  
3618  
3619      /**
3620       * Read RK record
3621       * This record represents a cell that contains an RK value
3622       * (encoded integer or floating-point value). If a
3623       * floating-point value cannot be encoded to an RK value,
3624       * a NUMBER record will be written. This record replaces the
3625       * record INTEGER written in BIFF2.
3626       *
3627       * --    "OpenOffice.org's Documentation of the Microsoft
3628       *         Excel File Format"
3629       */
3630      private function readRk()
3631      {
3632          $length = self::getInt2d($this->data, $this->pos + 2);
3633          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3634  
3635          // move stream pointer to next record
3636          $this->pos += 4 + $length;
3637  
3638          // offset: 0; size: 2; index to row
3639          $row = self::getInt2d($recordData, 0);
3640  
3641          // offset: 2; size: 2; index to column
3642          $column = self::getInt2d($recordData, 2);
3643          $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3644  
3645          // Read cell?
3646          if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3647              // offset: 4; size: 2; index to XF record
3648              $xfIndex = self::getInt2d($recordData, 4);
3649  
3650              // offset: 6; size: 4; RK value
3651              $rknum = self::getInt4d($recordData, 6);
3652              $numValue = self::getIEEE754($rknum);
3653  
3654              $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3655              if (!$this->readDataOnly) {
3656                  // add style information
3657                  $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3658              }
3659  
3660              // add cell
3661              $cell->setValueExplicit($numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC);
3662          }
3663      }
3664  
3665  
3666      /**
3667       * Read LABELSST record
3668       * This record represents a cell that contains a string. It
3669       * replaces the LABEL record and RSTRING record used in
3670       * BIFF2-BIFF5.
3671       *
3672       * --    "OpenOffice.org's Documentation of the Microsoft
3673       *         Excel File Format"
3674       */
3675      private function readLabelSst()
3676      {
3677          $length = self::getInt2d($this->data, $this->pos + 2);
3678          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3679  
3680          // move stream pointer to next record
3681          $this->pos += 4 + $length;
3682  
3683          // offset: 0; size: 2; index to row
3684          $row = self::getInt2d($recordData, 0);
3685  
3686          // offset: 2; size: 2; index to column
3687          $column = self::getInt2d($recordData, 2);
3688          $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3689  
3690          // Read cell?
3691          if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3692              // offset: 4; size: 2; index to XF record
3693              $xfIndex = self::getInt2d($recordData, 4);
3694  
3695              // offset: 6; size: 4; index to SST record
3696              $index = self::getInt4d($recordData, 6);
3697  
3698              // add cell
3699              if (($fmtRuns = $this->sst[$index]['fmtRuns']) && !$this->readDataOnly) {
3700                  // then we should treat as rich text
3701                  $richText = new PHPExcel_RichText();
3702                  $charPos = 0;
3703                  $sstCount = count($this->sst[$index]['fmtRuns']);
3704                  for ($i = 0; $i <= $sstCount; ++$i) {
3705                      if (isset($fmtRuns[$i])) {
3706                          $text = PHPExcel_Shared_String::Substring($this->sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos);
3707                          $charPos = $fmtRuns[$i]['charPos'];
3708                      } else {
3709                          $text = PHPExcel_Shared_String::Substring($this->sst[$index]['value'], $charPos, PHPExcel_Shared_String::CountCharacters($this->sst[$index]['value']));
3710                      }
3711  
3712                      if (PHPExcel_Shared_String::CountCharacters($text) > 0) {
3713                          if ($i == 0) { // first text run, no style
3714                              $richText->createText($text);
3715                          } else {
3716                              $textRun = $richText->createTextRun($text);
3717                              if (isset($fmtRuns[$i - 1])) {
3718                                  if ($fmtRuns[$i - 1]['fontIndex'] < 4) {
3719                                      $fontIndex = $fmtRuns[$i - 1]['fontIndex'];
3720                                  } else {
3721                                      // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
3722                                      // check the OpenOffice documentation of the FONT record
3723                                      $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1;
3724                                  }
3725                                  $textRun->setFont(clone $this->objFonts[$fontIndex]);
3726                              }
3727                          }
3728                      }
3729                  }
3730                  $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3731                  $cell->setValueExplicit($richText, PHPExcel_Cell_DataType::TYPE_STRING);
3732              } else {
3733                  $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3734                  $cell->setValueExplicit($this->sst[$index]['value'], PHPExcel_Cell_DataType::TYPE_STRING);
3735              }
3736  
3737              if (!$this->readDataOnly) {
3738                  // add style information
3739                  $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3740              }
3741          }
3742      }
3743  
3744  
3745      /**
3746       * Read MULRK record
3747       * This record represents a cell range containing RK value
3748       * cells. All cells are located in the same row.
3749       *
3750       * --    "OpenOffice.org's Documentation of the Microsoft
3751       *         Excel File Format"
3752       */
3753      private function readMulRk()
3754      {
3755          $length = self::getInt2d($this->data, $this->pos + 2);
3756          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3757  
3758          // move stream pointer to next record
3759          $this->pos += 4 + $length;
3760  
3761          // offset: 0; size: 2; index to row
3762          $row = self::getInt2d($recordData, 0);
3763  
3764          // offset: 2; size: 2; index to first column
3765          $colFirst = self::getInt2d($recordData, 2);
3766  
3767          // offset: var; size: 2; index to last column
3768          $colLast = self::getInt2d($recordData, $length - 2);
3769          $columns = $colLast - $colFirst + 1;
3770  
3771          // offset within record data
3772          $offset = 4;
3773  
3774          for ($i = 0; $i < $columns; ++$i) {
3775              $columnString = PHPExcel_Cell::stringFromColumnIndex($colFirst + $i);
3776  
3777              // Read cell?
3778              if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3779                  // offset: var; size: 2; index to XF record
3780                  $xfIndex = self::getInt2d($recordData, $offset);
3781  
3782                  // offset: var; size: 4; RK value
3783                  $numValue = self::getIEEE754(self::getInt4d($recordData, $offset + 2));
3784                  $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3785                  if (!$this->readDataOnly) {
3786                      // add style
3787                      $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3788                  }
3789  
3790                  // add cell value
3791                  $cell->setValueExplicit($numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC);
3792              }
3793  
3794              $offset += 6;
3795          }
3796      }
3797  
3798  
3799      /**
3800       * Read NUMBER record
3801       * This record represents a cell that contains a
3802       * floating-point value.
3803       *
3804       * --    "OpenOffice.org's Documentation of the Microsoft
3805       *         Excel File Format"
3806       */
3807      private function readNumber()
3808      {
3809          $length = self::getInt2d($this->data, $this->pos + 2);
3810          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3811  
3812          // move stream pointer to next record
3813          $this->pos += 4 + $length;
3814  
3815          // offset: 0; size: 2; index to row
3816          $row = self::getInt2d($recordData, 0);
3817  
3818          // offset: 2; size 2; index to column
3819          $column = self::getInt2d($recordData, 2);
3820          $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3821  
3822          // Read cell?
3823          if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3824              // offset 4; size: 2; index to XF record
3825              $xfIndex = self::getInt2d($recordData, 4);
3826  
3827              $numValue = self::extractNumber(substr($recordData, 6, 8));
3828  
3829              $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3830              if (!$this->readDataOnly) {
3831                  // add cell style
3832                  $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3833              }
3834  
3835              // add cell value
3836              $cell->setValueExplicit($numValue, PHPExcel_Cell_DataType::TYPE_NUMERIC);
3837          }
3838      }
3839  
3840  
3841      /**
3842       * Read FORMULA record + perhaps a following STRING record if formula result is a string
3843       * This record contains the token array and the result of a
3844       * formula cell.
3845       *
3846       * --    "OpenOffice.org's Documentation of the Microsoft
3847       *         Excel File Format"
3848       */
3849      private function readFormula()
3850      {
3851          $length = self::getInt2d($this->data, $this->pos + 2);
3852          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3853  
3854          // move stream pointer to next record
3855          $this->pos += 4 + $length;
3856  
3857          // offset: 0; size: 2; row index
3858          $row = self::getInt2d($recordData, 0);
3859  
3860          // offset: 2; size: 2; col index
3861          $column = self::getInt2d($recordData, 2);
3862          $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
3863  
3864          // offset: 20: size: variable; formula structure
3865          $formulaStructure = substr($recordData, 20);
3866  
3867          // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc.
3868          $options = self::getInt2d($recordData, 14);
3869  
3870          // bit: 0; mask: 0x0001; 1 = recalculate always
3871          // bit: 1; mask: 0x0002; 1 = calculate on open
3872          // bit: 2; mask: 0x0008; 1 = part of a shared formula
3873          $isPartOfSharedFormula = (bool) (0x0008 & $options);
3874  
3875          // WARNING:
3876          // We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true
3877          // the formula data may be ordinary formula data, therefore we need to check
3878          // explicitly for the tExp token (0x01)
3879          $isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure{2}) == 0x01;
3880  
3881          if ($isPartOfSharedFormula) {
3882              // part of shared formula which means there will be a formula with a tExp token and nothing else
3883              // get the base cell, grab tExp token
3884              $baseRow = self::getInt2d($formulaStructure, 3);
3885              $baseCol = self::getInt2d($formulaStructure, 5);
3886              $this->_baseCell = PHPExcel_Cell::stringFromColumnIndex($baseCol). ($baseRow + 1);
3887          }
3888  
3889          // Read cell?
3890          if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3891              if ($isPartOfSharedFormula) {
3892                  // formula is added to this cell after the sheet has been read
3893                  $this->sharedFormulaParts[$columnString . ($row + 1)] = $this->_baseCell;
3894              }
3895  
3896              // offset: 16: size: 4; not used
3897  
3898              // offset: 4; size: 2; XF index
3899              $xfIndex = self::getInt2d($recordData, 4);
3900  
3901              // offset: 6; size: 8; result of the formula
3902              if ((ord($recordData{6}) == 0) && (ord($recordData{12}) == 255) && (ord($recordData{13}) == 255)) {
3903                  // String formula. Result follows in appended STRING record
3904                  $dataType = PHPExcel_Cell_DataType::TYPE_STRING;
3905  
3906                  // read possible SHAREDFMLA record
3907                  $code = self::getInt2d($this->data, $this->pos);
3908                  if ($code == self::XLS_TYPE_SHAREDFMLA) {
3909                      $this->readSharedFmla();
3910                  }
3911  
3912                  // read STRING record
3913                  $value = $this->readString();
3914              } elseif ((ord($recordData{6}) == 1)
3915                  && (ord($recordData{12}) == 255)
3916                  && (ord($recordData{13}) == 255)) {
3917                  // Boolean formula. Result is in +2; 0=false, 1=true
3918                  $dataType = PHPExcel_Cell_DataType::TYPE_BOOL;
3919                  $value = (bool) ord($recordData{8});
3920              } elseif ((ord($recordData{6}) == 2)
3921                  && (ord($recordData{12}) == 255)
3922                  && (ord($recordData{13}) == 255)) {
3923                  // Error formula. Error code is in +2
3924                  $dataType = PHPExcel_Cell_DataType::TYPE_ERROR;
3925                  $value = self::mapErrorCode(ord($recordData{8}));
3926              } elseif ((ord($recordData{6}) == 3)
3927                  && (ord($recordData{12}) == 255)
3928                  && (ord($recordData{13}) == 255)) {
3929                  // Formula result is a null string
3930                  $dataType = PHPExcel_Cell_DataType::TYPE_NULL;
3931                  $value = '';
3932              } else {
3933                  // forumla result is a number, first 14 bytes like _NUMBER record
3934                  $dataType = PHPExcel_Cell_DataType::TYPE_NUMERIC;
3935                  $value = self::extractNumber(substr($recordData, 6, 8));
3936              }
3937  
3938              $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3939              if (!$this->readDataOnly) {
3940                  // add cell style
3941                  $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3942              }
3943  
3944              // store the formula
3945              if (!$isPartOfSharedFormula) {
3946                  // not part of shared formula
3947                  // add cell value. If we can read formula, populate with formula, otherwise just used cached value
3948                  try {
3949                      if ($this->version != self::XLS_BIFF8) {
3950                          throw new PHPExcel_Reader_Exception('Not BIFF8. Can only read BIFF8 formulas');
3951                      }
3952                      $formula = $this->getFormulaFromStructure($formulaStructure); // get formula in human language
3953                      $cell->setValueExplicit('=' . $formula, PHPExcel_Cell_DataType::TYPE_FORMULA);
3954  
3955                  } catch (PHPExcel_Exception $e) {
3956                      $cell->setValueExplicit($value, $dataType);
3957                  }
3958              } else {
3959                  if ($this->version == self::XLS_BIFF8) {
3960                      // do nothing at this point, formula id added later in the code
3961                  } else {
3962                      $cell->setValueExplicit($value, $dataType);
3963                  }
3964              }
3965  
3966              // store the cached calculated value
3967              $cell->setCalculatedValue($value);
3968          }
3969      }
3970  
3971  
3972      /**
3973       * Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader,
3974       * which usually contains relative references.
3975       * These will be used to construct the formula in each shared formula part after the sheet is read.
3976       */
3977      private function readSharedFmla()
3978      {
3979          $length = self::getInt2d($this->data, $this->pos + 2);
3980          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3981  
3982          // move stream pointer to next record
3983          $this->pos += 4 + $length;
3984  
3985          // offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything
3986          $cellRange = substr($recordData, 0, 6);
3987          $cellRange = $this->readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax
3988  
3989          // offset: 6, size: 1; not used
3990  
3991          // offset: 7, size: 1; number of existing FORMULA records for this shared formula
3992          $no = ord($recordData{7});
3993  
3994          // offset: 8, size: var; Binary token array of the shared formula
3995          $formula = substr($recordData, 8);
3996  
3997          // at this point we only store the shared formula for later use
3998          $this->sharedFormulas[$this->_baseCell] = $formula;
3999      }
4000  
4001  
4002      /**
4003       * Read a STRING record from current stream position and advance the stream pointer to next record
4004       * This record is used for storing result from FORMULA record when it is a string, and
4005       * it occurs directly after the FORMULA record
4006       *
4007       * @return string The string contents as UTF-8
4008       */
4009      private function readString()
4010      {
4011          $length = self::getInt2d($this->data, $this->pos + 2);
4012          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4013  
4014          // move stream pointer to next record
4015          $this->pos += 4 + $length;
4016  
4017          if ($this->version == self::XLS_BIFF8) {
4018              $string = self::readUnicodeStringLong($recordData);
4019              $value = $string['value'];
4020          } else {
4021              $string = $this->readByteStringLong($recordData);
4022              $value = $string['value'];
4023          }
4024  
4025          return $value;
4026      }
4027  
4028  
4029      /**
4030       * Read BOOLERR record
4031       * This record represents a Boolean value or error value
4032       * cell.
4033       *
4034       * --    "OpenOffice.org's Documentation of the Microsoft
4035       *         Excel File Format"
4036       */
4037      private function readBoolErr()
4038      {
4039          $length = self::getInt2d($this->data, $this->pos + 2);
4040          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4041  
4042          // move stream pointer to next record
4043          $this->pos += 4 + $length;
4044  
4045          // offset: 0; size: 2; row index
4046          $row = self::getInt2d($recordData, 0);
4047  
4048          // offset: 2; size: 2; column index
4049          $column = self::getInt2d($recordData, 2);
4050          $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
4051  
4052          // Read cell?
4053          if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4054              // offset: 4; size: 2; index to XF record
4055              $xfIndex = self::getInt2d($recordData, 4);
4056  
4057              // offset: 6; size: 1; the boolean value or error value
4058              $boolErr = ord($recordData{6});
4059  
4060              // offset: 7; size: 1; 0=boolean; 1=error
4061              $isError = ord($recordData{7});
4062  
4063              $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4064              switch ($isError) {
4065                  case 0: // boolean
4066                      $value = (bool) $boolErr;
4067  
4068                      // add cell value
4069                      $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_BOOL);
4070                      break;
4071                  case 1: // error type
4072                      $value = self::mapErrorCode($boolErr);
4073  
4074                      // add cell value
4075                      $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_ERROR);
4076                      break;
4077              }
4078  
4079              if (!$this->readDataOnly) {
4080                  // add cell style
4081                  $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4082              }
4083          }
4084      }
4085  
4086  
4087      /**
4088       * Read MULBLANK record
4089       * This record represents a cell range of empty cells. All
4090       * cells are located in the same row
4091       *
4092       * --    "OpenOffice.org's Documentation of the Microsoft
4093       *         Excel File Format"
4094       */
4095      private function readMulBlank()
4096      {
4097          $length = self::getInt2d($this->data, $this->pos + 2);
4098          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4099  
4100          // move stream pointer to next record
4101          $this->pos += 4 + $length;
4102  
4103          // offset: 0; size: 2; index to row
4104          $row = self::getInt2d($recordData, 0);
4105  
4106          // offset: 2; size: 2; index to first column
4107          $fc = self::getInt2d($recordData, 2);
4108  
4109          // offset: 4; size: 2 x nc; list of indexes to XF records
4110          // add style information
4111          if (!$this->readDataOnly) {
4112              for ($i = 0; $i < $length / 2 - 3; ++$i) {
4113                  $columnString = PHPExcel_Cell::stringFromColumnIndex($fc + $i);
4114  
4115                  // Read cell?
4116                  if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4117                      $xfIndex = self::getInt2d($recordData, 4 + 2 * $i);
4118                      $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4119                  }
4120              }
4121          }
4122  
4123          // offset: 6; size 2; index to last column (not needed)
4124      }
4125  
4126  
4127      /**
4128       * Read LABEL record
4129       * This record represents a cell that contains a string. In
4130       * BIFF8 it is usually replaced by the LABELSST record.
4131       * Excel still uses this record, if it copies unformatted
4132       * text cells to the clipboard.
4133       *
4134       * --    "OpenOffice.org's Documentation of the Microsoft
4135       *         Excel File Format"
4136       */
4137      private function readLabel()
4138      {
4139          $length = self::getInt2d($this->data, $this->pos + 2);
4140          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4141  
4142          // move stream pointer to next record
4143          $this->pos += 4 + $length;
4144  
4145          // offset: 0; size: 2; index to row
4146          $row = self::getInt2d($recordData, 0);
4147  
4148          // offset: 2; size: 2; index to column
4149          $column = self::getInt2d($recordData, 2);
4150          $columnString = PHPExcel_Cell::stringFromColumnIndex($column);
4151  
4152          // Read cell?
4153          if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4154              // offset: 4; size: 2; XF index
4155              $xfIndex = self::getInt2d($recordData, 4);
4156  
4157              // add cell value
4158              // todo: what if string is very long? continue record
4159              if ($this->version == self::XLS_BIFF8) {
4160                  $string = self::readUnicodeStringLong(substr($recordData, 6));
4161                  $value = $string['value'];
4162              } else {
4163                  $string = $this->readByteStringLong(substr($recordData, 6));
4164                  $value = $string['value'];
4165              }
4166              $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4167              $cell->setValueExplicit($value, PHPExcel_Cell_DataType::TYPE_STRING);
4168  
4169              if (!$this->readDataOnly) {
4170                  // add cell style
4171                  $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4172              }
4173          }
4174      }
4175  
4176  
4177      /**
4178       * Read BLANK record
4179       */
4180      private function readBlank()
4181      {
4182          $length = self::getInt2d($this->data, $this->pos + 2);
4183          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4184  
4185          // move stream pointer to next record
4186          $this->pos += 4 + $length;
4187  
4188          // offset: 0; size: 2; row index
4189          $row = self::getInt2d($recordData, 0);
4190  
4191          // offset: 2; size: 2; col index
4192          $col = self::getInt2d($recordData, 2);
4193          $columnString = PHPExcel_Cell::stringFromColumnIndex($col);
4194  
4195          // Read cell?
4196          if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4197              // offset: 4; size: 2; XF index
4198              $xfIndex = self::getInt2d($recordData, 4);
4199  
4200              // add style information
4201              if (!$this->readDataOnly) {
4202                  $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4203              }
4204          }
4205  
4206      }
4207  
4208  
4209      /**
4210       * Read MSODRAWING record
4211       */
4212      private function readMsoDrawing()
4213      {
4214          $length = self::getInt2d($this->data, $this->pos + 2);
4215  
4216          // get spliced record data
4217          $splicedRecordData = $this->getSplicedRecordData();
4218          $recordData = $splicedRecordData['recordData'];
4219  
4220          $this->drawingData .= $recordData;
4221      }
4222  
4223  
4224      /**
4225       * Read OBJ record
4226       */
4227      private function readObj()
4228      {
4229          $length = self::getInt2d($this->data, $this->pos + 2);
4230          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4231  
4232          // move stream pointer to next record
4233          $this->pos += 4 + $length;
4234  
4235          if ($this->readDataOnly || $this->version != self::XLS_BIFF8) {
4236              return;
4237          }
4238  
4239          // recordData consists of an array of subrecords looking like this:
4240          //    ft: 2 bytes; ftCmo type (0x15)
4241          //    cb: 2 bytes; size in bytes of ftCmo data
4242          //    ot: 2 bytes; Object Type
4243          //    id: 2 bytes; Object id number
4244          //    grbit: 2 bytes; Option Flags
4245          //    data: var; subrecord data
4246  
4247          // for now, we are just interested in the second subrecord containing the object type
4248          $ftCmoType  = self::getInt2d($recordData, 0);
4249          $cbCmoSize  = self::getInt2d($recordData, 2);
4250          $otObjType  = self::getInt2d($recordData, 4);
4251          $idObjID    = self::getInt2d($recordData, 6);
4252          $grbitOpts  = self::getInt2d($recordData, 6);
4253  
4254          $this->objs[] = array(
4255              'ftCmoType' => $ftCmoType,
4256              'cbCmoSize' => $cbCmoSize,
4257              'otObjType' => $otObjType,
4258              'idObjID'   => $idObjID,
4259              'grbitOpts' => $grbitOpts
4260          );
4261          $this->textObjRef = $idObjID;
4262  
4263  //        echo '<b>_readObj()</b><br />';
4264  //        var_dump(end($this->objs));
4265  //        echo '<br />';
4266      }
4267  
4268  
4269      /**
4270       * Read WINDOW2 record
4271       */
4272      private function readWindow2()
4273      {
4274          $length = self::getInt2d($this->data, $this->pos + 2);
4275          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4276  
4277          // move stream pointer to next record
4278          $this->pos += 4 + $length;
4279  
4280          // offset: 0; size: 2; option flags
4281          $options = self::getInt2d($recordData, 0);
4282  
4283          // offset: 2; size: 2; index to first visible row
4284          $firstVisibleRow = self::getInt2d($recordData, 2);
4285  
4286          // offset: 4; size: 2; index to first visible colum
4287          $firstVisibleColumn = self::getInt2d($recordData, 4);
4288          if ($this->version === self::XLS_BIFF8) {
4289              // offset:  8; size: 2; not used
4290              // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%)
4291              // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%)
4292              // offset: 14; size: 4; not used
4293              $zoomscaleInPageBreakPreview = self::getInt2d($recordData, 10);
4294              if ($zoomscaleInPageBreakPreview === 0) {
4295                  $zoomscaleInPageBreakPreview = 60;
4296              }
4297              $zoomscaleInNormalView = self::getInt2d($recordData, 12);
4298              if ($zoomscaleInNormalView === 0) {
4299                  $zoomscaleInNormalView = 100;
4300              }
4301          }
4302  
4303          // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines
4304          $showGridlines = (bool) ((0x0002 & $options) >> 1);
4305          $this->phpSheet->setShowGridlines($showGridlines);
4306  
4307          // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers
4308          $showRowColHeaders = (bool) ((0x0004 & $options) >> 2);
4309          $this->phpSheet->setShowRowColHeaders($showRowColHeaders);
4310  
4311          // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen
4312          $this->frozen = (bool) ((0x0008 & $options) >> 3);
4313  
4314          // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left
4315          $this->phpSheet->setRightToLeft((bool)((0x0040 & $options) >> 6));
4316  
4317          // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active
4318          $isActive = (bool) ((0x0400 & $options) >> 10);
4319          if ($isActive) {
4320              $this->phpExcel->setActiveSheetIndex($this->phpExcel->getIndex($this->phpSheet));
4321          }
4322  
4323          // bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view
4324          $isPageBreakPreview = (bool) ((0x0800 & $options) >> 11);
4325  
4326          //FIXME: set $firstVisibleRow and $firstVisibleColumn
4327  
4328          if ($this->phpSheet->getSheetView()->getView() !== PHPExcel_Worksheet_SheetView::SHEETVIEW_PAGE_LAYOUT) {
4329              //NOTE: this setting is inferior to page layout view(Excel2007-)
4330              $view = $isPageBreakPreview ? PHPExcel_Worksheet_SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW : PHPExcel_Worksheet_SheetView::SHEETVIEW_NORMAL;
4331              $this->phpSheet->getSheetView()->setView($view);
4332              if ($this->version === self::XLS_BIFF8) {
4333                  $zoomScale = $isPageBreakPreview ? $zoomscaleInPageBreakPreview : $zoomscaleInNormalView;
4334                  $this->phpSheet->getSheetView()->setZoomScale($zoomScale);
4335                  $this->phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView);
4336              }
4337          }
4338      }
4339  
4340      /**
4341       * Read PLV Record(Created by Excel2007 or upper)
4342       */
4343      private function readPageLayoutView()
4344      {
4345          $length = self::getInt2d($this->data, $this->pos + 2);
4346          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4347  
4348          // move stream pointer to next record
4349          $this->pos += 4 + $length;
4350  
4351          //var_dump(unpack("vrt/vgrbitFrt/V2reserved/vwScalePLV/vgrbit", $recordData));
4352  
4353          // offset: 0; size: 2; rt
4354          //->ignore
4355          $rt = self::getInt2d($recordData, 0);
4356          // offset: 2; size: 2; grbitfr
4357          //->ignore
4358          $grbitFrt = self::getInt2d($recordData, 2);
4359          // offset: 4; size: 8; reserved
4360          //->ignore
4361  
4362          // offset: 12; size 2; zoom scale
4363          $wScalePLV = self::getInt2d($recordData, 12);
4364          // offset: 14; size 2; grbit
4365          $grbit = self::getInt2d($recordData, 14);
4366  
4367          // decomprise grbit
4368          $fPageLayoutView   = $grbit & 0x01;
4369          $fRulerVisible     = ($grbit >> 1) & 0x01; //no support
4370          $fWhitespaceHidden = ($grbit >> 3) & 0x01; //no support
4371  
4372          if ($fPageLayoutView === 1) {
4373              $this->phpSheet->getSheetView()->setView(PHPExcel_Worksheet_SheetView::SHEETVIEW_PAGE_LAYOUT);
4374              $this->phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT
4375          }
4376          //otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW.
4377      }
4378  
4379      /**
4380       * Read SCL record
4381       */
4382      private function readScl()
4383      {
4384          $length = self::getInt2d($this->data, $this->pos + 2);
4385          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4386  
4387          // move stream pointer to next record
4388          $this->pos += 4 + $length;
4389  
4390          // offset: 0; size: 2; numerator of the view magnification
4391          $numerator = self::getInt2d($recordData, 0);
4392  
4393          // offset: 2; size: 2; numerator of the view magnification
4394          $denumerator = self::getInt2d($recordData, 2);
4395  
4396          // set the zoom scale (in percent)
4397          $this->phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator);
4398      }
4399  
4400  
4401      /**
4402       * Read PANE record
4403       */
4404      private function readPane()
4405      {
4406          $length = self::getInt2d($this->data, $this->pos + 2);
4407          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4408  
4409          // move stream pointer to next record
4410          $this->pos += 4 + $length;
4411  
4412          if (!$this->readDataOnly) {
4413              // offset: 0; size: 2; position of vertical split
4414              $px = self::getInt2d($recordData, 0);
4415  
4416              // offset: 2; size: 2; position of horizontal split
4417              $py = self::getInt2d($recordData, 2);
4418  
4419              if ($this->frozen) {
4420                  // frozen panes
4421                  $this->phpSheet->freezePane(PHPExcel_Cell::stringFromColumnIndex($px) . ($py + 1));
4422              } else {
4423                  // unfrozen panes; split windows; not supported by PHPExcel core
4424              }
4425          }
4426      }
4427  
4428  
4429      /**
4430       * Read SELECTION record. There is one such record for each pane in the sheet.
4431       */
4432      private function readSelection()
4433      {
4434          $length = self::getInt2d($this->data, $this->pos + 2);
4435          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4436  
4437          // move stream pointer to next record
4438          $this->pos += 4 + $length;
4439  
4440          if (!$this->readDataOnly) {
4441              // offset: 0; size: 1; pane identifier
4442              $paneId = ord($recordData{0});
4443  
4444              // offset: 1; size: 2; index to row of the active cell
4445              $r = self::getInt2d($recordData, 1);
4446  
4447              // offset: 3; size: 2; index to column of the active cell
4448              $c = self::getInt2d($recordData, 3);
4449  
4450              // offset: 5; size: 2; index into the following cell range list to the
4451              //  entry that contains the active cell
4452              $index = self::getInt2d($recordData, 5);
4453  
4454              // offset: 7; size: var; cell range address list containing all selected cell ranges
4455              $data = substr($recordData, 7);
4456              $cellRangeAddressList = $this->readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax
4457  
4458              $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0];
4459  
4460              // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!)
4461              if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) {
4462                  $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '$1}1048576', $selectedCells);
4463              }
4464  
4465              // first row '1' + last row '65536' indicates that full column is selected
4466              if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) {
4467                  $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '$1}1048576', $selectedCells);
4468              }
4469  
4470              // first column 'A' + last column 'IV' indicates that full row is selected
4471              if (preg_match('/^(A[0-9]+\:)IV([0-9]+)$/', $selectedCells)) {
4472                  $selectedCells = preg_replace('/^(A[0-9]+\:)IV([0-9]+)$/', '$1}XFD$2}', $selectedCells);
4473              }
4474  
4475              $this->phpSheet->setSelectedCells($selectedCells);
4476          }
4477      }
4478  
4479  
4480      private function includeCellRangeFiltered($cellRangeAddress)
4481      {
4482          $includeCellRange = true;
4483          if ($this->getReadFilter() !== null) {
4484              $includeCellRange = false;
4485              $rangeBoundaries = PHPExcel_Cell::getRangeBoundaries($cellRangeAddress);
4486              $rangeBoundaries[1][0]++;
4487              for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; $row++) {
4488                  for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; $column++) {
4489                      if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
4490                          $includeCellRange = true;
4491                          break 2;
4492                      }
4493                  }
4494              }
4495          }
4496          return $includeCellRange;
4497      }
4498  
4499  
4500      /**
4501       * MERGEDCELLS
4502       *
4503       * This record contains the addresses of merged cell ranges
4504       * in the current sheet.
4505       *
4506       * --    "OpenOffice.org's Documentation of the Microsoft
4507       *         Excel File Format"
4508       */
4509      private function readMergedCells()
4510      {
4511          $length = self::getInt2d($this->data, $this->pos + 2);
4512          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4513  
4514          // move stream pointer to next record
4515          $this->pos += 4 + $length;
4516  
4517          if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
4518              $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData);
4519              foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) {
4520                  if ((strpos($cellRangeAddress, ':') !== false) &&
4521                      ($this->includeCellRangeFiltered($cellRangeAddress))) {
4522                      $this->phpSheet->mergeCells($cellRangeAddress);
4523                  }
4524              }
4525          }
4526      }
4527  
4528  
4529      /**
4530       * Read HYPERLINK record
4531       */
4532      private function readHyperLink()
4533      {
4534          $length = self::getInt2d($this->data, $this->pos + 2);
4535          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4536  
4537          // move stream pointer forward to next record
4538          $this->pos += 4 + $length;
4539  
4540          if (!$this->readDataOnly) {
4541              // offset: 0; size: 8; cell range address of all cells containing this hyperlink
4542              try {
4543                  $cellRange = $this->readBIFF8CellRangeAddressFixed($recordData, 0, 8);
4544              } catch (PHPExcel_Exception $e) {
4545                  return;
4546              }
4547  
4548              // offset: 8, size: 16; GUID of StdLink
4549  
4550              // offset: 24, size: 4; unknown value
4551  
4552              // offset: 28, size: 4; option flags
4553              // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL
4554              $isFileLinkOrUrl = (0x00000001 & self::getInt2d($recordData, 28)) >> 0;
4555  
4556              // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL
4557              $isAbsPathOrUrl = (0x00000001 & self::getInt2d($recordData, 28)) >> 1;
4558  
4559              // bit: 2 (and 4); mask: 0x00000014; 0 = no description
4560              $hasDesc = (0x00000014 & self::getInt2d($recordData, 28)) >> 2;
4561  
4562              // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text
4563              $hasText = (0x00000008 & self::getInt2d($recordData, 28)) >> 3;
4564  
4565              // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame
4566              $hasFrame = (0x00000080 & self::getInt2d($recordData, 28)) >> 7;
4567  
4568              // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name)
4569              $isUNC = (0x00000100 & self::getInt2d($recordData, 28)) >> 8;
4570  
4571              // offset within record data
4572              $offset = 32;
4573  
4574              if ($hasDesc) {
4575                  // offset: 32; size: var; character count of description text
4576                  $dl = self::getInt4d($recordData, 32);
4577                  // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated
4578                  $desc = self::encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false);
4579                  $offset += 4 + 2 * $dl;
4580              }
4581              if ($hasFrame) {
4582                  $fl = self::getInt4d($recordData, $offset);
4583                  $offset += 4 + 2 * $fl;
4584              }
4585  
4586              // detect type of hyperlink (there are 4 types)
4587              $hyperlinkType = null;
4588  
4589              if ($isUNC) {
4590                  $hyperlinkType = 'UNC';
4591              } elseif (!$isFileLinkOrUrl) {
4592                  $hyperlinkType = 'workbook';
4593              } elseif (ord($recordData{$offset}) == 0x03) {
4594                  $hyperlinkType = 'local';
4595              } elseif (ord($recordData{$offset}) == 0xE0) {
4596                  $hyperlinkType = 'URL';
4597              }
4598  
4599              switch ($hyperlinkType) {
4600                  case 'URL':
4601                      // section 5.58.2: Hyperlink containing a URL
4602                      // e.g. http://example.org/index.php
4603  
4604                      // offset: var; size: 16; GUID of URL Moniker
4605                      $offset += 16;
4606                      // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word
4607                      $us = self::getInt4d($recordData, $offset);
4608                      $offset += 4;
4609                      // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated
4610                      $url = self::encodeUTF16(substr($recordData, $offset, $us - 2), false);
4611                      $nullOffset = strpos($url, 0x00);
4612                      if ($nullOffset) {
4613                          $url = substr($url, 0, $nullOffset);
4614                      }
4615                      $url .= $hasText ? '#' : '';
4616                      $offset += $us;
4617                      break;
4618                  case 'local':
4619                      // section 5.58.3: Hyperlink to local file
4620                      // examples:
4621                      //   mydoc.txt
4622                      //   ../../somedoc.xls#Sheet!A1
4623  
4624                      // offset: var; size: 16; GUI of File Moniker
4625                      $offset += 16;
4626  
4627                      // offset: var; size: 2; directory up-level count.
4628                      $upLevelCount = self::getInt2d($recordData, $offset);
4629                      $offset += 2;
4630  
4631                      // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word
4632                      $sl = self::getInt4d($recordData, $offset);
4633                      $offset += 4;
4634  
4635                      // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string)
4636                      $shortenedFilePath = substr($recordData, $offset, $sl);
4637                      $shortenedFilePath = self::encodeUTF16($shortenedFilePath, true);
4638                      $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero
4639  
4640                      $offset += $sl;
4641  
4642                      // offset: var; size: 24; unknown sequence
4643                      $offset += 24;
4644  
4645                      // extended file path
4646                      // offset: var; size: 4; size of the following file link field including string lenth mark
4647                      $sz = self::getInt4d($recordData, $offset);
4648                      $offset += 4;
4649  
4650                      // only present if $sz > 0
4651                      if ($sz > 0) {
4652                          // offset: var; size: 4; size of the character array of the extended file path and name
4653                          $xl = self::getInt4d($recordData, $offset);
4654                          $offset += 4;
4655  
4656                          // offset: var; size 2; unknown
4657                          $offset += 2;
4658  
4659                          // offset: var; size $xl; character array of the extended file path and name.
4660                          $extendedFilePath = substr($recordData, $offset, $xl);
4661                          $extendedFilePath = self::encodeUTF16($extendedFilePath, false);
4662                          $offset += $xl;
4663                      }
4664  
4665                      // construct the path
4666                      $url = str_repeat('..\\', $upLevelCount);
4667                      $url .= ($sz > 0) ? $extendedFilePath : $shortenedFilePath; // use extended path if available
4668                      $url .= $hasText ? '#' : '';
4669  
4670                      break;
4671                  case 'UNC':
4672                      // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path
4673                      // todo: implement
4674                      return;
4675                  case 'workbook':
4676                      // section 5.58.5: Hyperlink to the Current Workbook
4677                      // e.g. Sheet2!B1:C2, stored in text mark field
4678                      $url = 'sheet://';
4679                      break;
4680                  default:
4681                      return;
4682              }
4683  
4684              if ($hasText) {
4685                  // offset: var; size: 4; character count of text mark including trailing zero word
4686                  $tl = self::getInt4d($recordData, $offset);
4687                  $offset += 4;
4688                  // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated
4689                  $text = self::encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false);
4690                  $url .= $text;
4691              }
4692  
4693              // apply the hyperlink to all the relevant cells
4694              foreach (PHPExcel_Cell::extractAllCellReferencesInRange($cellRange) as $coordinate) {
4695                  $this->phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url);
4696              }
4697          }
4698      }
4699  
4700  
4701      /**
4702       * Read DATAVALIDATIONS record
4703       */
4704      private function readDataValidations()
4705      {
4706          $length = self::getInt2d($this->data, $this->pos + 2);
4707          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4708  
4709          // move stream pointer forward to next record
4710          $this->pos += 4 + $length;
4711      }
4712  
4713  
4714      /**
4715       * Read DATAVALIDATION record
4716       */
4717      private function readDataValidation()
4718      {
4719          $length = self::getInt2d($this->data, $this->pos + 2);
4720          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4721  
4722          // move stream pointer forward to next record
4723          $this->pos += 4 + $length;
4724  
4725          if ($this->readDataOnly) {
4726              return;
4727          }
4728  
4729          // offset: 0; size: 4; Options
4730          $options = self::getInt4d($recordData, 0);
4731  
4732          // bit: 0-3; mask: 0x0000000F; type
4733          $type = (0x0000000F & $options) >> 0;
4734          switch ($type) {
4735              case 0x00:
4736                  $type = PHPExcel_Cell_DataValidation::TYPE_NONE;
4737                  break;
4738              case 0x01:
4739                  $type = PHPExcel_Cell_DataValidation::TYPE_WHOLE;
4740                  break;
4741              case 0x02:
4742                  $type = PHPExcel_Cell_DataValidation::TYPE_DECIMAL;
4743                  break;
4744              case 0x03:
4745                  $type = PHPExcel_Cell_DataValidation::TYPE_LIST;
4746                  break;
4747              case 0x04:
4748                  $type = PHPExcel_Cell_DataValidation::TYPE_DATE;
4749                  break;
4750              case 0x05:
4751                  $type = PHPExcel_Cell_DataValidation::TYPE_TIME;
4752                  break;
4753              case 0x06:
4754                  $type = PHPExcel_Cell_DataValidation::TYPE_TEXTLENGTH;
4755                  break;
4756              case 0x07:
4757                  $type = PHPExcel_Cell_DataValidation::TYPE_CUSTOM;
4758                  break;
4759          }
4760  
4761          // bit: 4-6; mask: 0x00000070; error type
4762          $errorStyle = (0x00000070 & $options) >> 4;
4763          switch ($errorStyle) {
4764              case 0x00:
4765                  $errorStyle = PHPExcel_Cell_DataValidation::STYLE_STOP;
4766                  break;
4767              case 0x01:
4768                  $errorStyle = PHPExcel_Cell_DataValidation::STYLE_WARNING;
4769                  break;
4770              case 0x02:
4771                  $errorStyle = PHPExcel_Cell_DataValidation::STYLE_INFORMATION;
4772                  break;
4773          }
4774  
4775          // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)
4776          // I have only seen cases where this is 1
4777          $explicitFormula = (0x00000080 & $options) >> 7;
4778  
4779          // bit: 8; mask: 0x00000100; 1= empty cells allowed
4780          $allowBlank = (0x00000100 & $options) >> 8;
4781  
4782          // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity
4783          $suppressDropDown = (0x00000200 & $options) >> 9;
4784  
4785          // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected
4786          $showInputMessage = (0x00040000 & $options) >> 18;
4787  
4788          // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered
4789          $showErrorMessage = (0x00080000 & $options) >> 19;
4790  
4791          // bit: 20-23; mask: 0x00F00000; condition operator
4792          $operator = (0x00F00000 & $options) >> 20;
4793          switch ($operator) {
4794              case 0x00:
4795                  $operator = PHPExcel_Cell_DataValidation::OPERATOR_BETWEEN;
4796                  break;
4797              case 0x01:
4798                  $operator = PHPExcel_Cell_DataValidation::OPERATOR_NOTBETWEEN;
4799                  break;
4800              case 0x02:
4801                  $operator = PHPExcel_Cell_DataValidation::OPERATOR_EQUAL;
4802                  break;
4803              case 0x03:
4804                  $operator = PHPExcel_Cell_DataValidation::OPERATOR_NOTEQUAL;
4805                  break;
4806              case 0x04:
4807                  $operator = PHPExcel_Cell_DataValidation::OPERATOR_GREATERTHAN;
4808                  break;
4809              case 0x05:
4810                  $operator = PHPExcel_Cell_DataValidation::OPERATOR_LESSTHAN;
4811                  break;
4812              case 0x06:
4813                  $operator = PHPExcel_Cell_DataValidation::OPERATOR_GREATERTHANOREQUAL;
4814                  break;
4815              case 0x07:
4816                  $operator = PHPExcel_Cell_DataValidation::OPERATOR_LESSTHANOREQUAL;
4817                  break;
4818          }
4819  
4820          // offset: 4; size: var; title of the prompt box
4821          $offset = 4;
4822          $string = self::readUnicodeStringLong(substr($recordData, $offset));
4823          $promptTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4824          $offset += $string['size'];
4825  
4826          // offset: var; size: var; title of the error box
4827          $string = self::readUnicodeStringLong(substr($recordData, $offset));
4828          $errorTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4829          $offset += $string['size'];
4830  
4831          // offset: var; size: var; text of the prompt box
4832          $string = self::readUnicodeStringLong(substr($recordData, $offset));
4833          $prompt = $string['value'] !== chr(0) ? $string['value'] : '';
4834          $offset += $string['size'];
4835  
4836          // offset: var; size: var; text of the error box
4837          $string = self::readUnicodeStringLong(substr($recordData, $offset));
4838          $error = $string['value'] !== chr(0) ? $string['value'] : '';
4839          $offset += $string['size'];
4840  
4841          // offset: var; size: 2; size of the formula data for the first condition
4842          $sz1 = self::getInt2d($recordData, $offset);
4843          $offset += 2;
4844  
4845          // offset: var; size: 2; not used
4846          $offset += 2;
4847  
4848          // offset: var; size: $sz1; formula data for first condition (without size field)
4849          $formula1 = substr($recordData, $offset, $sz1);
4850          $formula1 = pack('v', $sz1) . $formula1; // prepend the length
4851          try {
4852              $formula1 = $this->getFormulaFromStructure($formula1);
4853  
4854              // in list type validity, null characters are used as item separators
4855              if ($type == PHPExcel_Cell_DataValidation::TYPE_LIST) {
4856                  $formula1 = str_replace(chr(0), ',', $formula1);
4857              }
4858          } catch (PHPExcel_Exception $e) {
4859              return;
4860          }
4861          $offset += $sz1;
4862  
4863          // offset: var; size: 2; size of the formula data for the first condition
4864          $sz2 = self::getInt2d($recordData, $offset);
4865          $offset += 2;
4866  
4867          // offset: var; size: 2; not used
4868          $offset += 2;
4869  
4870          // offset: var; size: $sz2; formula data for second condition (without size field)
4871          $formula2 = substr($recordData, $offset, $sz2);
4872          $formula2 = pack('v', $sz2) . $formula2; // prepend the length
4873          try {
4874              $formula2 = $this->getFormulaFromStructure($formula2);
4875          } catch (PHPExcel_Exception $e) {
4876              return;
4877          }
4878          $offset += $sz2;
4879  
4880          // offset: var; size: var; cell range address list with
4881          $cellRangeAddressList = $this->readBIFF8CellRangeAddressList(substr($recordData, $offset));
4882          $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
4883  
4884          foreach ($cellRangeAddresses as $cellRange) {
4885              $stRange = $this->phpSheet->shrinkRangeToFit($cellRange);
4886              foreach (PHPExcel_Cell::extractAllCellReferencesInRange($stRange) as $coordinate) {
4887                  $objValidation = $this->phpSheet->getCell($coordinate)->getDataValidation();
4888                  $objValidation->setType($type);
4889                  $objValidation->setErrorStyle($errorStyle);
4890                  $objValidation->setAllowBlank((bool)$allowBlank);
4891                  $objValidation->setShowInputMessage((bool)$showInputMessage);
4892                  $objValidation->setShowErrorMessage((bool)$showErrorMessage);
4893                  $objValidation->setShowDropDown(!$suppressDropDown);
4894                  $objValidation->setOperator($operator);
4895                  $objValidation->setErrorTitle($errorTitle);
4896                  $objValidation->setError($error);
4897                  $objValidation->setPromptTitle($promptTitle);
4898                  $objValidation->setPrompt($prompt);
4899                  $objValidation->setFormula1($formula1);
4900                  $objValidation->setFormula2($formula2);
4901              }
4902          }
4903      }
4904  
4905      /**
4906       * Read SHEETLAYOUT record. Stores sheet tab color information.
4907       */
4908      private function readSheetLayout()
4909      {
4910          $length = self::getInt2d($this->data, $this->pos + 2);
4911          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4912  
4913          // move stream pointer to next record
4914          $this->pos += 4 + $length;
4915  
4916          // local pointer in record data
4917          $offset = 0;
4918  
4919          if (!$this->readDataOnly) {
4920              // offset: 0; size: 2; repeated record identifier 0x0862
4921  
4922              // offset: 2; size: 10; not used
4923  
4924              // offset: 12; size: 4; size of record data
4925              // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?)
4926              $sz = self::getInt4d($recordData, 12);
4927  
4928              switch ($sz) {
4929                  case 0x14:
4930                      // offset: 16; size: 2; color index for sheet tab
4931                      $colorIndex = self::getInt2d($recordData, 16);
4932                      $color = self::readColor($colorIndex, $this->palette, $this->version);
4933                      $this->phpSheet->getTabColor()->setRGB($color['rgb']);
4934                      break;
4935                  case 0x28:
4936                      // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007
4937                      return;
4938                      break;
4939              }
4940          }
4941      }
4942  
4943  
4944      /**
4945       * Read SHEETPROTECTION record (FEATHEADR)
4946       */
4947      private function readSheetProtection()
4948      {
4949          $length = self::getInt2d($this->data, $this->pos + 2);
4950          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4951  
4952          // move stream pointer to next record
4953          $this->pos += 4 + $length;
4954  
4955          if ($this->readDataOnly) {
4956              return;
4957          }
4958  
4959          // offset: 0; size: 2; repeated record header
4960  
4961          // offset: 2; size: 2; FRT cell reference flag (=0 currently)
4962  
4963          // offset: 4; size: 8; Currently not used and set to 0
4964  
4965          // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag)
4966          $isf = self::getInt2d($recordData, 12);
4967          if ($isf != 2) {
4968              return;
4969          }
4970  
4971          // offset: 14; size: 1; =1 since this is a feat header
4972  
4973          // offset: 15; size: 4; size of rgbHdrSData
4974  
4975          // rgbHdrSData, assume "Enhanced Protection"
4976          // offset: 19; size: 2; option flags
4977          $options = self::getInt2d($recordData, 19);
4978  
4979          // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects
4980          $bool = (0x0001 & $options) >> 0;
4981          $this->phpSheet->getProtection()->setObjects(!$bool);
4982  
4983          // bit: 1; mask 0x0002; edit scenarios
4984          $bool = (0x0002 & $options) >> 1;
4985          $this->phpSheet->getProtection()->setScenarios(!$bool);
4986  
4987          // bit: 2; mask 0x0004; format cells
4988          $bool = (0x0004 & $options) >> 2;
4989          $this->phpSheet->getProtection()->setFormatCells(!$bool);
4990  
4991          // bit: 3; mask 0x0008; format columns
4992          $bool = (0x0008 & $options) >> 3;
4993          $this->phpSheet->getProtection()->setFormatColumns(!$bool);
4994  
4995          // bit: 4; mask 0x0010; format rows
4996          $bool = (0x0010 & $options) >> 4;
4997          $this->phpSheet->getProtection()->setFormatRows(!$bool);
4998  
4999          // bit: 5; mask 0x0020; insert columns
5000          $bool = (0x0020 & $options) >> 5;
5001          $this->phpSheet->getProtection()->setInsertColumns(!$bool);
5002  
5003          // bit: 6; mask 0x0040; insert rows
5004          $bool = (0x0040 & $options) >> 6;
5005          $this->phpSheet->getProtection()->setInsertRows(!$bool);
5006  
5007          // bit: 7; mask 0x0080; insert hyperlinks
5008          $bool = (0x0080 & $options) >> 7;
5009          $this->phpSheet->getProtection()->setInsertHyperlinks(!$bool);
5010  
5011          // bit: 8; mask 0x0100; delete columns
5012          $bool = (0x0100 & $options) >> 8;
5013          $this->phpSheet->getProtection()->setDeleteColumns(!$bool);
5014  
5015          // bit: 9; mask 0x0200; delete rows
5016          $bool = (0x0200 & $options) >> 9;
5017          $this->phpSheet->getProtection()->setDeleteRows(!$bool);
5018  
5019          // bit: 10; mask 0x0400; select locked cells
5020          $bool = (0x0400 & $options) >> 10;
5021          $this->phpSheet->getProtection()->setSelectLockedCells(!$bool);
5022  
5023          // bit: 11; mask 0x0800; sort cell range
5024          $bool = (0x0800 & $options) >> 11;
5025          $this->phpSheet->getProtection()->setSort(!$bool);
5026  
5027          // bit: 12; mask 0x1000; auto filter
5028          $bool = (0x1000 & $options) >> 12;
5029          $this->phpSheet->getProtection()->setAutoFilter(!$bool);
5030  
5031          // bit: 13; mask 0x2000; pivot tables
5032          $bool = (0x2000 & $options) >> 13;
5033          $this->phpSheet->getProtection()->setPivotTables(!$bool);
5034  
5035          // bit: 14; mask 0x4000; select unlocked cells
5036          $bool = (0x4000 & $options) >> 14;
5037          $this->phpSheet->getProtection()->setSelectUnlockedCells(!$bool);
5038  
5039          // offset: 21; size: 2; not used
5040      }
5041  
5042  
5043      /**
5044       * Read RANGEPROTECTION record
5045       * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification,
5046       * where it is referred to as FEAT record
5047       */
5048      private function readRangeProtection()
5049      {
5050          $length = self::getInt2d($this->data, $this->pos + 2);
5051          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5052  
5053          // move stream pointer to next record
5054          $this->pos += 4 + $length;
5055  
5056          // local pointer in record data
5057          $offset = 0;
5058  
5059          if (!$this->readDataOnly) {
5060              $offset += 12;
5061  
5062              // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag
5063              $isf = self::getInt2d($recordData, 12);
5064              if ($isf != 2) {
5065                  // we only read FEAT records of type 2
5066                  return;
5067              }
5068              $offset += 2;
5069  
5070              $offset += 5;
5071  
5072              // offset: 19; size: 2; count of ref ranges this feature is on
5073              $cref = self::getInt2d($recordData, 19);
5074              $offset += 2;
5075  
5076              $offset += 6;
5077  
5078              // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record)
5079              $cellRanges = array();
5080              for ($i = 0; $i < $cref; ++$i) {
5081                  try {
5082                      $cellRange = $this->readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8));
5083                  } catch (PHPExcel_Exception $e) {
5084                      return;
5085                  }
5086                  $cellRanges[] = $cellRange;
5087                  $offset += 8;
5088              }
5089  
5090              // offset: var; size: var; variable length of feature specific data
5091              $rgbFeat = substr($recordData, $offset);
5092              $offset += 4;
5093  
5094              // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit)
5095              $wPassword = self::getInt4d($recordData, $offset);
5096              $offset += 4;
5097  
5098              // Apply range protection to sheet
5099              if ($cellRanges) {
5100                  $this->phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true);
5101              }
5102          }
5103      }
5104  
5105  
5106      /**
5107       * Read IMDATA record
5108       */
5109      private function readImData()
5110      {
5111          $length = self::getInt2d($this->data, $this->pos + 2);
5112  
5113          // get spliced record data
5114          $splicedRecordData = $this->getSplicedRecordData();
5115          $recordData = $splicedRecordData['recordData'];
5116  
5117          // UNDER CONSTRUCTION
5118  
5119          // offset: 0; size: 2; image format
5120          $cf = self::getInt2d($recordData, 0);
5121  
5122          // offset: 2; size: 2; environment from which the file was written
5123          $env = self::getInt2d($recordData, 2);
5124  
5125          // offset: 4; size: 4; length of the image data
5126          $lcb = self::getInt4d($recordData, 4);
5127  
5128          // offset: 8; size: var; image data
5129          $iData = substr($recordData, 8);
5130  
5131          switch ($cf) {
5132              case 0x09: // Windows bitmap format
5133                  // BITMAPCOREINFO
5134                  // 1. BITMAPCOREHEADER
5135                  // offset: 0; size: 4; bcSize, Specifies the number of bytes required by the structure
5136                  $bcSize = self::getInt4d($iData, 0);
5137      //            var_dump($bcSize);
5138  
5139                  // offset: 4; size: 2; bcWidth, specifies the width of the bitmap, in pixels
5140                  $bcWidth = self::getInt2d($iData, 4);
5141      //            var_dump($bcWidth);
5142  
5143                  // offset: 6; size: 2; bcHeight, specifies the height of the bitmap, in pixels.
5144                  $bcHeight = self::getInt2d($iData, 6);
5145      //            var_dump($bcHeight);
5146                  $ih = imagecreatetruecolor($bcWidth, $bcHeight);
5147  
5148                  // offset: 8; size: 2; bcPlanes, specifies the number of planes for the target device. This value must be 1
5149  
5150                  // offset: 10; size: 2; bcBitCount specifies the number of bits-per-pixel. This value must be 1, 4, 8, or 24
5151                  $bcBitCount = self::getInt2d($iData, 10);
5152      //            var_dump($bcBitCount);
5153  
5154                  $rgbString = substr($iData, 12);
5155                  $rgbTriples = array();
5156                  while (strlen($rgbString) > 0) {
5157                      $rgbTriples[] = unpack('Cb/Cg/Cr', $rgbString);
5158                      $rgbString = substr($rgbString, 3);
5159                  }
5160                  $x = 0;
5161                  $y = 0;
5162                  foreach ($rgbTriples as $i => $rgbTriple) {
5163                      $color = imagecolorallocate($ih, $rgbTriple['r'], $rgbTriple['g'], $rgbTriple['b']);
5164                      imagesetpixel($ih, $x, $bcHeight - 1 - $y, $color);
5165                      $x = ($x + 1) % $bcWidth;
5166                      $y = $y + floor(($x + 1) / $bcWidth);
5167                  }
5168                  //imagepng($ih, 'image.png');
5169  
5170                  $drawing = new PHPExcel_Worksheet_Drawing();
5171                  $drawing->setPath($filename);
5172                  $drawing->setWorksheet($this->phpSheet);
5173                  break;
5174              case 0x02: // Windows metafile or Macintosh PICT format
5175              case 0x0e: // native format
5176              default:
5177                  break;
5178          }
5179  
5180          // getSplicedRecordData() takes care of moving current position in data stream
5181      }
5182  
5183  
5184      /**
5185       * Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record
5186       * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented.
5187       * In this case, we must treat the CONTINUE record as a MSODRAWING record
5188       */
5189      private function readContinue()
5190      {
5191          $length = self::getInt2d($this->data, $this->pos + 2);
5192          $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5193  
5194          // check if we are reading drawing data
5195          // this is in case a free CONTINUE record occurs in other circumstances we are unaware of
5196          if ($this->drawingData == '') {
5197              // move stream pointer to next record
5198              $this->pos += 4 + $length;
5199  
5200              return;
5201          }
5202  
5203          // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data
5204          if ($length < 4) {
5205              // move stream pointer to next record
5206              $this->pos += 4 + $length;
5207  
5208              return;
5209          }
5210  
5211          // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record
5212          // look inside CONTINUE record to see if it looks like a part of an Escher stream
5213          // we know that Escher stream may be split at least at
5214          //        0xF003 MsofbtSpgrContainer
5215          //        0xF004 MsofbtSpContainer
5216          //        0xF00D MsofbtClientTextbox
5217          $validSplitPoints = array(0xF003, 0xF004, 0xF00D); // add identifiers if we find more
5218  
5219          $splitPoint = self::getInt2d($recordData, 2);
5220          if (in_array($splitPoint, $validSplitPoints)) {
5221              // get spliced record data (and move pointer to next record)
5222              $splicedRecordData = $this->getSplicedRecordData();
5223              $this->drawingData .= $splicedRecordData['recordData'];
5224  
5225              return;
5226          }
5227  
5228          // move stream pointer to next record
5229          $this->pos += 4 + $length;
5230      }
5231  
5232  
5233      /**
5234       * Reads a record from current position in data stream and continues reading data as long as CONTINUE
5235       * records are found. Splices the record data pieces and returns the combined string as if record data
5236       * is in one piece.
5237       * Moves to next current position in data stream to start of next record different from a CONtINUE record
5238       *
5239       * @return array
5240       */
5241      private function getSplicedRecordData()
5242      {
5243          $data = '';
5244          $spliceOffsets = array();
5245  
5246          $i = 0;
5247          $spliceOffsets[0] = 0;
5248  
5249          do {
5250              ++$i;
5251  
5252              // offset: 0; size: 2; identifier
5253              $identifier = self::getInt2d($this->data, $this->pos);
5254              // offset: 2; size: 2; length
5255              $length = self::getInt2d($this->data, $this->pos + 2);
5256              $data .= $this->readRecordData($this->data, $this->pos + 4, $length);
5257  
5258              $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length;
5259  
5260              $this->pos += 4 + $length;
5261              $nextIdentifier = self::getInt2d($this->data, $this->pos);
5262          } while ($nextIdentifier == self::XLS_TYPE_CONTINUE);
5263  
5264          $splicedData = array(
5265              'recordData' => $data,
5266              'spliceOffsets' => $spliceOffsets,
5267          );
5268  
5269          return $splicedData;
5270  
5271      }
5272  
5273  
5274      /**
5275       * Convert formula structure into human readable Excel formula like 'A3+A5*5'
5276       *
5277       * @param string $formulaStructure The complete binary data for the formula
5278       * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5279       * @return string Human readable formula
5280       */
5281      private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1')
5282      {
5283          // offset: 0; size: 2; size of the following formula data
5284          $sz = self::getInt2d($formulaStructure, 0);
5285  
5286          // offset: 2; size: sz
5287          $formulaData = substr($formulaStructure, 2, $sz);
5288  
5289          // for debug: dump the formula data
5290          //echo '<xmp>';
5291          //echo 'size: ' . $sz . "\n";
5292          //echo 'the entire formula data: ';
5293          //Debug::dump($formulaData);
5294          //echo "\n----\n";
5295  
5296          // offset: 2 + sz; size: variable (optional)
5297          if (strlen($formulaStructure) > 2 + $sz) {
5298              $additionalData = substr($formulaStructure, 2 + $sz);
5299  
5300              // for debug: dump the additional data
5301              //echo 'the entire additional data: ';
5302              //Debug::dump($additionalData);
5303              //echo "\n----\n";
5304          } else {
5305              $additionalData = '';
5306          }
5307  
5308          return $this->getFormulaFromData($formulaData, $additionalData, $baseCell);
5309      }
5310  
5311  
5312      /**
5313       * Take formula data and additional data for formula and return human readable formula
5314       *
5315       * @param string $formulaData The binary data for the formula itself
5316       * @param string $additionalData Additional binary data going with the formula
5317       * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5318       * @return string Human readable formula
5319       */
5320      private function getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1')
5321      {
5322          // start parsing the formula data
5323          $tokens = array();
5324  
5325          while (strlen($formulaData) > 0 and $token = $this->getNextToken($formulaData, $baseCell)) {
5326              $tokens[] = $token;
5327              $formulaData = substr($formulaData, $token['size']);
5328  
5329              // for debug: dump the token
5330              //var_dump($token);
5331          }
5332  
5333          $formulaString = $this->createFormulaFromTokens($tokens, $additionalData);
5334  
5335          return $formulaString;
5336      }
5337  
5338  
5339      /**
5340       * Take array of tokens together with additional data for formula and return human readable formula
5341       *
5342       * @param array $tokens
5343       * @param array $additionalData Additional binary data going with the formula
5344       * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5345       * @return string Human readable formula
5346       */
5347      private function createFormulaFromTokens($tokens, $additionalData)
5348      {
5349          // empty formula?
5350          if (empty($tokens)) {
5351              return '';
5352          }
5353  
5354          $formulaStrings = array();
5355          foreach ($tokens as $token) {
5356              // initialize spaces
5357              $space0 = isset($space0) ? $space0 : ''; // spaces before next token, not tParen
5358              $space1 = isset($space1) ? $space1 : ''; // carriage returns before next token, not tParen
5359              $space2 = isset($space2) ? $space2 : ''; // spaces before opening parenthesis
5360              $space3 = isset($space3) ? $space3 : ''; // carriage returns before opening parenthesis
5361              $space4 = isset($space4) ? $space4 : ''; // spaces before closing parenthesis
5362              $space5 = isset($space5) ? $space5 : ''; // carriage returns before closing parenthesis
5363  
5364              switch ($token['name']) {
5365                  case 'tAdd': // addition
5366                  case 'tConcat': // addition
5367                  case 'tDiv': // division
5368                  case 'tEQ': // equality
5369                  case 'tGE': // greater than or equal
5370                  case 'tGT': // greater than
5371                  case 'tIsect': // intersection
5372                  case 'tLE': // less than or equal
5373                  case 'tList': // less than or equal
5374                  case 'tLT': // less than
5375                  case 'tMul': // multiplication
5376                  case 'tNE': // multiplication
5377                  case 'tPower': // power
5378                  case 'tRange': // range
5379                  case 'tSub': // subtraction
5380                      $op2 = array_pop($formulaStrings);
5381                      $op1 = array_pop($formulaStrings);
5382                      $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2";
5383                      unset($space0, $space1);
5384                      break;
5385                  case 'tUplus': // unary plus
5386                  case 'tUminus': // unary minus
5387                      $op = array_pop($formulaStrings);
5388                      $formulaStrings[] = "$space1$space0{$token['data']}$op";
5389                      unset($space0, $space1);
5390                      break;
5391                  case 'tPercent': // percent sign
5392                      $op = array_pop($formulaStrings);
5393                      $formulaStrings[] = "$op$space1$space0{$token['data']}";
5394                      unset($space0, $space1);
5395                      break;
5396                  case 'tAttrVolatile': // indicates volatile function
5397                  case 'tAttrIf':
5398                  case 'tAttrSkip':
5399                  case 'tAttrChoose':
5400                      // token is only important for Excel formula evaluator
5401                      // do nothing
5402                      break;
5403                  case 'tAttrSpace': // space / carriage return
5404                      // space will be used when next token arrives, do not alter formulaString stack
5405                      switch ($token['data']['spacetype']) {
5406                          case 'type0':
5407                              $space0 = str_repeat(' ', $token['data']['spacecount']);
5408                              break;
5409                          case 'type1':
5410                              $space1 = str_repeat("\n", $token['data']['spacecount']);
5411                              break;
5412                          case 'type2':
5413                              $space2 = str_repeat(' ', $token['data']['spacecount']);
5414                              break;
5415                          case 'type3':
5416                              $space3 = str_repeat("\n", $token['data']['spacecount']);
5417                              break;
5418                          case 'type4':
5419                              $space4 = str_repeat(' ', $token['data']['spacecount']);
5420                              break;
5421                          case 'type5':
5422                              $space5 = str_repeat("\n", $token['data']['spacecount']);
5423                              break;
5424                      }
5425                      break;
5426                  case 'tAttrSum': // SUM function with one parameter
5427                      $op = array_pop($formulaStrings);
5428                      $formulaStrings[] = "{$space1}{$space0}SUM($op)";
5429                      unset($space0, $space1);
5430                      break;
5431                  case 'tFunc': // function with fixed number of arguments
5432                  case 'tFuncV': // function with variable number of arguments
5433                      if ($token['data']['function'] != '') {
5434                          // normal function
5435                          $ops = array(); // array of operators
5436                          for ($i = 0; $i < $token['data']['args']; ++$i) {
5437                              $ops[] = array_pop($formulaStrings);
5438                          }
5439                          $ops = array_reverse($ops);
5440                          $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ")";
5441                          unset($space0, $space1);
5442                      } else {
5443                          // add-in function
5444                          $ops = array(); // array of operators
5445                          for ($i = 0; $i < $token['data']['args'] - 1; ++$i) {
5446                              $ops[] = array_pop($formulaStrings);
5447                          }
5448                          $ops = array_reverse($ops);
5449                          $function = array_pop($formulaStrings);
5450                          $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ")";
5451                          unset($space0, $space1);
5452                      }
5453                      break;
5454                  case 'tParen': // parenthesis
5455                      $expression = array_pop($formulaStrings);
5456                      $formulaStrings[] = "$space3$space2($expression$space5$space4)";
5457                      unset($space2, $space3, $space4, $space5);
5458                      break;
5459                  case 'tArray': // array constant
5460                      $constantArray = self::_readBIFF8ConstantArray($additionalData);
5461                      $formulaStrings[] = $space1 . $space0 . $constantArray['value'];
5462                      $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data
5463                      unset($space0, $space1);
5464                      break;
5465                  case 'tMemArea':
5466                      // bite off chunk of additional data
5467                      $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($additionalData);
5468                      $additionalData = substr($additionalData, $cellRangeAddressList['size']);
5469                      $formulaStrings[] = "$space1$space0{$token['data']}";
5470                      unset($space0, $space1);
5471                      break;
5472                  case 'tArea': // cell range address
5473                  case 'tBool': // boolean
5474                  case 'tErr': // error code
5475                  case 'tInt': // integer
5476                  case 'tMemErr':
5477                  case 'tMemFunc':
5478                  case 'tMissArg':
5479                  case 'tName':
5480                  case 'tNameX':
5481                  case 'tNum': // number
5482                  case 'tRef': // single cell reference
5483                  case 'tRef3d': // 3d cell reference
5484                  case 'tArea3d': // 3d cell range reference
5485                  case 'tRefN':
5486                  case 'tAreaN':
5487                  case 'tStr': // string
5488                      $formulaStrings[] = "$space1$space0{$token['data']}";
5489                      unset($space0, $space1);
5490                      break;
5491              }
5492          }
5493          $formulaString = $formulaStrings[0];
5494  
5495          // for debug: dump the human readable formula
5496          //echo '----' . "\n";
5497          //echo 'Formula: ' . $formulaString;
5498  
5499          return $formulaString;
5500      }
5501  
5502  
5503      /**
5504       * Fetch next token from binary formula data
5505       *
5506       * @param string Formula data
5507       * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5508       * @return array
5509       * @throws PHPExcel_Reader_Exception
5510       */
5511      private function getNextToken($formulaData, $baseCell = 'A1')
5512      {
5513          // offset: 0; size: 1; token id
5514          $id = ord($formulaData[0]); // token id
5515          $name = false; // initialize token name
5516  
5517          switch ($id) {
5518              case 0x03:
5519                  $name = 'tAdd';
5520                  $size = 1;
5521                  $data = '+';
5522                  break;
5523              case 0x04:
5524                  $name = 'tSub';
5525                  $size = 1;
5526                  $data = '-';
5527                  break;
5528              case 0x05:
5529                  $name = 'tMul';
5530                  $size = 1;
5531                  $data = '*';
5532                  break;
5533              case 0x06:
5534                  $name = 'tDiv';
5535                  $size = 1;
5536                  $data = '/';
5537                  break;
5538              case 0x07:
5539                  $name = 'tPower';
5540                  $size = 1;
5541                  $data = '^';
5542                  break;
5543              case 0x08:
5544                  $name = 'tConcat';
5545                  $size = 1;
5546                  $data = '&';
5547                  break;
5548              case 0x09:
5549                  $name = 'tLT';
5550                  $size = 1;
5551                  $data = '<';
5552                  break;
5553              case 0x0A:
5554                  $name = 'tLE';
5555                  $size = 1;
5556                  $data = '<=';
5557                  break;
5558              case 0x0B:
5559                  $name = 'tEQ';
5560                  $size = 1;
5561                  $data = '=';
5562                  break;
5563              case 0x0C:
5564                  $name = 'tGE';
5565                  $size = 1;
5566                  $data = '>=';
5567                  break;
5568              case 0x0D:
5569                  $name = 'tGT';
5570                  $size = 1;
5571                  $data = '>';
5572                  break;
5573              case 0x0E:
5574                  $name = 'tNE';
5575                  $size = 1;
5576                  $data = '<>';
5577                  break;
5578              case 0x0F:
5579                  $name = 'tIsect';
5580                  $size = 1;
5581                  $data = ' ';
5582                  break;
5583              case 0x10:
5584                  $name = 'tList';
5585                  $size = 1;
5586                  $data = ',';
5587                  break;
5588              case 0x11:
5589                  $name = 'tRange';
5590                  $size = 1;
5591                  $data = ':';
5592                  break;
5593              case 0x12:
5594                  $name = 'tUplus';
5595                  $size = 1;
5596                  $data = '+';
5597                  break;
5598              case 0x13:
5599                  $name = 'tUminus';
5600                  $size = 1;
5601                  $data = '-';
5602                  break;
5603              case 0x14:
5604                  $name = 'tPercent';
5605                  $size = 1;
5606                  $data = '%';
5607                  break;
5608              case 0x15:    //    parenthesis
5609                  $name  = 'tParen';
5610                  $size  = 1;
5611                  $data = null;
5612                  break;
5613              case 0x16:    //    missing argument
5614                  $name = 'tMissArg';
5615                  $size = 1;
5616                  $data = '';
5617                  break;
5618              case 0x17:    //    string
5619                  $name = 'tStr';
5620                  // offset: 1; size: var; Unicode string, 8-bit string length
5621                  $string = self::readUnicodeStringShort(substr($formulaData, 1));
5622                  $size = 1 + $string['size'];
5623                  $data = self::UTF8toExcelDoubleQuoted($string['value']);
5624                  break;
5625              case 0x19:    //    Special attribute
5626                  // offset: 1; size: 1; attribute type flags:
5627                  switch (ord($formulaData[1])) {
5628                      case 0x01:
5629                          $name = 'tAttrVolatile';
5630                          $size = 4;
5631                          $data = null;
5632                          break;
5633                      case 0x02:
5634                          $name = 'tAttrIf';
5635                          $size = 4;
5636                          $data = null;
5637                          break;
5638                      case 0x04:
5639                          $name = 'tAttrChoose';
5640                          // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1)
5641                          $nc = self::getInt2d($formulaData, 2);
5642                          // offset: 4; size: 2 * $nc
5643                          // offset: 4 + 2 * $nc; size: 2
5644                          $size = 2 * $nc + 6;
5645                          $data = null;
5646                          break;
5647                      case 0x08:
5648                          $name = 'tAttrSkip';
5649                          $size = 4;
5650                          $data = null;
5651                          break;
5652                      case 0x10:
5653                          $name = 'tAttrSum';
5654                          $size = 4;
5655                          $data = null;
5656                          break;
5657                      case 0x40:
5658                      case 0x41:
5659                          $name = 'tAttrSpace';
5660                          $size = 4;
5661                          // offset: 2; size: 2; space type and position
5662                          switch (ord($formulaData[2])) {
5663                              case 0x00:
5664                                  $spacetype = 'type0';
5665                                  break;
5666                              case 0x01:
5667                                  $spacetype = 'type1';
5668                                  break;
5669                              case 0x02:
5670                                  $spacetype = 'type2';
5671                                  break;
5672                              case 0x03:
5673                                  $spacetype = 'type3';
5674                                  break;
5675                              case 0x04:
5676                                  $spacetype = 'type4';
5677                                  break;
5678                              case 0x05:
5679                                  $spacetype = 'type5';
5680                                  break;
5681                              default:
5682                                  throw new PHPExcel_Reader_Exception('Unrecognized space type in tAttrSpace token');
5683                                  break;
5684                          }
5685                          // offset: 3; size: 1; number of inserted spaces/carriage returns
5686                          $spacecount = ord($formulaData[3]);
5687  
5688                          $data = array('spacetype' => $spacetype, 'spacecount' => $spacecount);
5689                          break;
5690                      default:
5691                          throw new PHPExcel_Reader_Exception('Unrecognized attribute flag in tAttr token');
5692                          break;
5693                  }
5694                  break;
5695              case 0x1C:    //    error code
5696                  // offset: 1; size: 1; error code
5697                  $name = 'tErr';
5698                  $size = 2;
5699                  $data = self::mapErrorCode(ord($formulaData[1]));
5700                  break;
5701              case 0x1D:    //    boolean
5702                  // offset: 1; size: 1; 0 = false, 1 = true;
5703                  $name = 'tBool';
5704                  $size = 2;
5705                  $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE';
5706                  break;
5707              case 0x1E:    //    integer
5708                  // offset: 1; size: 2; unsigned 16-bit integer
5709                  $name = 'tInt';
5710                  $size = 3;
5711                  $data = self::getInt2d($formulaData, 1);
5712                  break;
5713              case 0x1F:    //    number
5714                  // offset: 1; size: 8;
5715                  $name = 'tNum';
5716                  $size = 9;
5717                  $data = self::extractNumber(substr($formulaData, 1));
5718                  $data = str_replace(',', '.', (string)$data); // in case non-English locale
5719                  break;
5720              case 0x20:    //    array constant
5721              case 0x40:
5722              case 0x60:
5723                  // offset: 1; size: 7; not used
5724                  $name = 'tArray';
5725                  $size = 8;
5726                  $data = null;
5727                  break;
5728              case 0x21:    //    function with fixed number of arguments
5729              case 0x41:
5730              case 0x61:
5731                  $name = 'tFunc';
5732                  $size = 3;
5733                  // offset: 1; size: 2; index to built-in sheet function
5734                  switch (self::getInt2d($formulaData, 1)) {
5735                      case 2:
5736                          $function = 'ISNA';
5737                          $args = 1;
5738                          break;
5739                      case 3:
5740                          $function = 'ISERROR';
5741                          $args = 1;
5742                          break;
5743                      case 10:
5744                          $function = 'NA';
5745                          $args = 0;
5746                          break;
5747                      case 15:
5748                          $function = 'SIN';
5749                          $args = 1;
5750                          break;
5751                      case 16:
5752                          $function = 'COS';
5753                          $args = 1;
5754                          break;
5755                      case 17:
5756                          $function = 'TAN';
5757                          $args = 1;
5758                          break;
5759                      case 18:
5760                          $function = 'ATAN';
5761                          $args = 1;
5762                          break;
5763                      case 19:
5764                          $function = 'PI';
5765                          $args = 0;
5766                          break;
5767                      case 20:
5768                          $function = 'SQRT';
5769                          $args = 1;
5770                          break;
5771                      case 21:
5772                          $function = 'EXP';
5773                          $args = 1;
5774                          break;
5775                      case 22:
5776                          $function = 'LN';
5777                          $args = 1;
5778                          break;
5779                      case 23:
5780                          $function = 'LOG10';
5781                          $args = 1;
5782                          break;
5783                      case 24:
5784                          $function = 'ABS';
5785                          $args = 1;
5786                          break;
5787                      case 25:
5788                          $function = 'INT';
5789                          $args = 1;
5790                          break;
5791                      case 26:
5792                          $function = 'SIGN';
5793                          $args = 1;
5794                          break;
5795                      case 27:
5796                          $function = 'ROUND';
5797                          $args = 2;
5798                          break;
5799                      case 30:
5800                          $function = 'REPT';
5801                          $args = 2;
5802                          break;
5803                      case 31:
5804                          $function = 'MID';
5805                          $args = 3;
5806                          break;
5807                      case 32:
5808                          $function = 'LEN';
5809                          $args = 1;
5810                          break;
5811                      case 33:
5812                          $function = 'VALUE';
5813                          $args = 1;
5814                          break;
5815                      case 34:
5816                          $function = 'TRUE';
5817                          $args = 0;
5818                          break;
5819                      case 35:
5820                          $function = 'FALSE';
5821                          $args = 0;
5822                          break;
5823                      case 38:
5824                          $function = 'NOT';
5825                          $args = 1;
5826                          break;
5827                      case 39:
5828                          $function = 'MOD';
5829                          $args = 2;
5830                          break;
5831                      case 40:
5832                          $function = 'DCOUNT';
5833                          $args = 3;
5834                          break;
5835                      case 41:
5836                          $function = 'DSUM';
5837                          $args = 3;
5838                          break;
5839                      case 42:
5840                          $function = 'DAVERAGE';
5841                          $args = 3;
5842                          break;
5843                      case 43:
5844                          $function = 'DMIN';
5845                          $args = 3;
5846                          break;
5847                      case 44:
5848                          $function = 'DMAX';
5849                          $args = 3;
5850                          break;
5851                      case 45:
5852                          $function = 'DSTDEV';
5853                          $args = 3;
5854                          break;
5855                      case 48:
5856                          $function = 'TEXT';
5857                          $args = 2;
5858                          break;
5859                      case 61:
5860                          $function = 'MIRR';
5861                          $args = 3;
5862                          break;
5863                      case 63:
5864                          $function = 'RAND';
5865                          $args = 0;
5866                          break;
5867                      case 65:
5868                          $function = 'DATE';
5869                          $args = 3;
5870                          break;
5871                      case 66:
5872                          $function = 'TIME';
5873                          $args = 3;
5874                          break;
5875                      case 67:
5876                          $function = 'DAY';
5877                          $args = 1;
5878                          break;
5879                      case 68:
5880                          $function = 'MONTH';
5881                          $args = 1;
5882                          break;
5883                      case 69:
5884                          $function = 'YEAR';
5885                          $args = 1;
5886                          break;
5887                      case 71:
5888                          $function = 'HOUR';
5889                          $args = 1;
5890                          break;
5891                      case 72:
5892                          $function = 'MINUTE';
5893                          $args = 1;
5894                          break;
5895                      case 73:
5896                          $function = 'SECOND';
5897                          $args = 1;
5898                          break;
5899                      case 74:
5900                          $function = 'NOW';
5901                          $args = 0;
5902                          break;
5903                      case 75:
5904                          $function = 'AREAS';
5905                          $args = 1;
5906                          break;
5907                      case 76:
5908                          $function = 'ROWS';
5909                          $args = 1;
5910                          break;
5911                      case 77:
5912                          $function = 'COLUMNS';
5913                          $args = 1;
5914                          break;
5915                      case 83:
5916                          $function = 'TRANSPOSE';
5917                          $args = 1;
5918                          break;
5919                      case 86:
5920                          $function = 'TYPE';
5921                          $args = 1;
5922                          break;
5923                      case 97:
5924                          $function = 'ATAN2';
5925                          $args = 2;
5926                          break;
5927                      case 98:
5928                          $function = 'ASIN';
5929                          $args = 1;
5930                          break;
5931                      case 99:
5932                          $function = 'ACOS';
5933                          $args = 1;
5934                          break;
5935                      case 105:
5936                          $function = 'ISREF';
5937                          $args = 1;
5938                          break;
5939                      case 111:
5940                          $function = 'CHAR';
5941                          $args = 1;
5942                          break;
5943                      case 112:
5944                          $function = 'LOWER';
5945                          $args = 1;
5946                          break;
5947                      case 113:
5948                          $function = 'UPPER';
5949                          $args = 1;
5950                          break;
5951                      case 114:
5952                          $function = 'PROPER';
5953                          $args = 1;
5954                          break;
5955                      case 117:
5956                          $function = 'EXACT';
5957                          $args = 2;
5958                          break;
5959                      case 118:
5960                          $function = 'TRIM';
5961                          $args = 1;
5962                          break;
5963                      case 119:
5964                          $function = 'REPLACE';
5965                          $args = 4;
5966                          break;
5967                      case 121:
5968                          $function = 'CODE';
5969                          $args = 1;
5970                          break;
5971                      case 126:
5972                          $function = 'ISERR';
5973                          $args = 1;
5974                          break;
5975                      case 127:
5976                          $function = 'ISTEXT';
5977                          $args = 1;
5978                          break;
5979                      case 128:
5980                          $function = 'ISNUMBER';
5981                          $args = 1;
5982                          break;
5983                      case 129:
5984                          $function = 'ISBLANK';
5985                          $args = 1;
5986                          break;
5987                      case 130:
5988                          $function = 'T';
5989                          $args = 1;
5990                          break;
5991                      case 131:
5992                          $function = 'N';
5993                          $args = 1;
5994                          break;
5995                      case 140:
5996                          $function = 'DATEVALUE';
5997                          $args = 1;
5998                          break;
5999                      case 141:
6000                          $function = 'TIMEVALUE';
6001                          $args = 1;
6002                          break;
6003                      case 142:
6004                          $function = 'SLN';
6005                          $args = 3;
6006                          break;
6007                      case 143:
6008                          $function = 'SYD';
6009                          $args = 4;
6010                          break;
6011                      case 162:
6012                          $function = 'CLEAN';
6013                          $args = 1;
6014                          break;
6015                      case 163:
6016                          $function = 'MDETERM';
6017                          $args = 1;
6018                          break;
6019                      case 164:
6020                          $function = 'MINVERSE';
6021                          $args = 1;
6022                          break;
6023                      case 165:
6024                          $function = 'MMULT';
6025                          $args = 2;
6026                          break;
6027                      case 184:
6028                          $function = 'FACT';
6029                          $args = 1;
6030                          break;
6031                      case 189:
6032                          $function = 'DPRODUCT';
6033                          $args = 3;
6034                          break;
6035                      case 190:
6036                          $function = 'ISNONTEXT';
6037                          $args = 1;
6038                          break;
6039                      case 195:
6040                          $function = 'DSTDEVP';
6041                          $args = 3;
6042                          break;
6043                      case 196:
6044                          $function = 'DVARP';
6045                          $args = 3;
6046                          break;
6047                      case 198:
6048                          $function = 'ISLOGICAL';
6049                          $args = 1;
6050                          break;
6051                      case 199:
6052                          $function = 'DCOUNTA';
6053                          $args = 3;
6054                          break;
6055                      case 207:
6056                          $function = 'REPLACEB';
6057                          $args = 4;
6058                          break;
6059                      case 210:
6060                          $function = 'MIDB';
6061                          $args = 3;
6062                          break;
6063                      case 211:
6064                          $function = 'LENB';
6065                          $args = 1;
6066                          break;
6067                      case 212:
6068                          $function = 'ROUNDUP';
6069                          $args = 2;
6070                          break;
6071                      case 213:
6072                          $function = 'ROUNDDOWN';
6073                          $args = 2;
6074                          break;
6075                      case 214:
6076                          $function = 'ASC';
6077                          $args = 1;
6078                          break;
6079                      case 215:
6080                          $function = 'DBCS';
6081                          $args = 1;
6082                          break;
6083                      case 221:
6084                          $function = 'TODAY';
6085                          $args = 0;
6086                          break;
6087                      case 229:
6088                          $function = 'SINH';
6089                          $args = 1;
6090                          break;
6091                      case 230:
6092                          $function = 'COSH';
6093                          $args = 1;
6094                          break;
6095                      case 231:
6096                          $function = 'TANH';
6097                          $args = 1;
6098                          break;
6099                      case 232:
6100                          $function = 'ASINH';
6101                          $args = 1;
6102                          break;
6103                      case 233:
6104                          $function = 'ACOSH';
6105                          $args = 1;
6106                          break;
6107                      case 234:
6108                          $function = 'ATANH';
6109                          $args = 1;
6110                          break;
6111                      case 235:
6112                          $function = 'DGET';
6113                          $args = 3;
6114                          break;
6115                      case 244:
6116                          $function = 'INFO';
6117                          $args = 1;
6118                          break;
6119                      case 252:
6120                          $function = 'FREQUENCY';
6121                          $args = 2;
6122                          break;
6123                      case 261:
6124                          $function = 'ERROR.TYPE';
6125                          $args = 1;
6126                          break;
6127                      case 271:
6128                          $function = 'GAMMALN';
6129                          $args = 1;
6130                          break;
6131                      case 273:
6132                          $function = 'BINOMDIST';
6133                          $args = 4;
6134                          break;
6135                      case 274:
6136                          $function = 'CHIDIST';
6137                          $args = 2;
6138                          break;
6139                      case 275:
6140                          $function = 'CHIINV';
6141                          $args = 2;
6142                          break;
6143                      case 276:
6144                          $function = 'COMBIN';
6145                          $args = 2;
6146                          break;
6147                      case 277:
6148                          $function = 'CONFIDENCE';
6149                          $args = 3;
6150                          break;
6151                      case 278:
6152                          $function = 'CRITBINOM';
6153                          $args = 3;
6154                          break;
6155                      case 279:
6156                          $function = 'EVEN';
6157                          $args = 1;
6158                          break;
6159                      case 280:
6160                          $function = 'EXPONDIST';
6161                          $args = 3;
6162                          break;
6163                      case 281:
6164                          $function = 'FDIST';
6165                          $args = 3;
6166                          break;
6167                      case 282:
6168                          $function = 'FINV';
6169                          $args = 3;
6170                          break;
6171                      case 283:
6172                          $function = 'FISHER';
6173                          $args = 1;
6174                          break;
6175                      case 284:
6176                          $function = 'FISHERINV';
6177                          $args = 1;
6178                          break;
6179                      case 285:
6180                          $function = 'FLOOR';
6181                          $args = 2;
6182                          break;
6183                      case 286:
6184                          $function = 'GAMMADIST';
6185                          $args = 4;
6186                          break;
6187                      case 287:
6188                          $function = 'GAMMAINV';
6189                          $args = 3;
6190                          break;
6191                      case 288:
6192                          $function = 'CEILING';
6193                          $args = 2;
6194                          break;
6195                      case 289:
6196                          $function = 'HYPGEOMDIST';
6197                          $args = 4;
6198                          break;
6199                      case 290:
6200                          $function = 'LOGNORMDIST';
6201                          $args = 3;
6202                          break;
6203                      case 291:
6204                          $function = 'LOGINV';
6205                          $args = 3;
6206                          break;
6207                      case 292:
6208                          $function = 'NEGBINOMDIST';
6209                          $args = 3;
6210                          break;
6211                      case 293:
6212                          $function = 'NORMDIST';
6213                          $args = 4;
6214                          break;
6215                      case 294:
6216                          $function = 'NORMSDIST';
6217                          $args = 1;
6218                          break;
6219                      case 295:
6220                          $function = 'NORMINV';
6221                          $args = 3;
6222                          break;
6223                      case 296:
6224                          $function = 'NORMSINV';
6225                          $args = 1;
6226                          break;
6227                      case 297:
6228                          $function = 'STANDARDIZE';
6229                          $args = 3;
6230                          break;
6231                      case 298:
6232                          $function = 'ODD';
6233                          $args = 1;
6234                          break;
6235                      case 299:
6236                          $function = 'PERMUT';
6237                          $args = 2;
6238                          break;
6239                      case 300:
6240                          $function = 'POISSON';
6241                          $args = 3;
6242                          break;
6243                      case 301:
6244                          $function = 'TDIST';
6245                          $args = 3;
6246                          break;
6247                      case 302:
6248                          $function = 'WEIBULL';
6249                          $args = 4;
6250                          break;
6251                      case 303:
6252                          $function = 'SUMXMY2';
6253                          $args = 2;
6254                          break;
6255                      case 304:
6256                          $function = 'SUMX2MY2';
6257                          $args = 2;
6258                          break;
6259                      case 305:
6260                          $function = 'SUMX2PY2';
6261                          $args = 2;
6262                          break;
6263                      case 306:
6264                          $function = 'CHITEST';
6265                          $args = 2;
6266                          break;
6267                      case 307:
6268                          $function = 'CORREL';
6269                          $args = 2;
6270                          break;
6271                      case 308:
6272                          $function = 'COVAR';
6273                          $args = 2;
6274                          break;
6275                      case 309:
6276                          $function = 'FORECAST';
6277                          $args = 3;
6278                          break;
6279                      case 310:
6280                          $function = 'FTEST';
6281                          $args = 2;
6282                          break;
6283                      case 311:
6284                          $function = 'INTERCEPT';
6285                          $args = 2;
6286                          break;
6287                      case 312:
6288                          $function = 'PEARSON';
6289                          $args = 2;
6290                          break;
6291                      case 313:
6292                          $function = 'RSQ';
6293                          $args = 2;
6294                          break;
6295                      case 314:
6296                          $function = 'STEYX';
6297                          $args = 2;
6298                          break;
6299                      case 315:
6300                          $function = 'SLOPE';
6301                          $args = 2;
6302                          break;
6303                      case 316:
6304                          $function = 'TTEST';
6305                          $args = 4;
6306                          break;
6307                      case 325:
6308                          $function = 'LARGE';
6309                          $args = 2;
6310                          break;
6311                      case 326:
6312                          $function = 'SMALL';
6313                          $args = 2;
6314                          break;
6315                      case 327:
6316                          $function = 'QUARTILE';
6317                          $args = 2;
6318                          break;
6319                      case 328:
6320                          $function = 'PERCENTILE';
6321                          $args = 2;
6322                          break;
6323                      case 331:
6324                          $function = 'TRIMMEAN';
6325                          $args = 2;
6326                          break;
6327                      case 332:
6328                          $function = 'TINV';
6329                          $args = 2;
6330                          break;
6331                      case 337:
6332                          $function = 'POWER';
6333                          $args = 2;
6334                          break;
6335                      case 342:
6336                          $function = 'RADIANS';
6337                          $args = 1;
6338                          break;
6339                      case 343:
6340                          $function = 'DEGREES';
6341                          $args = 1;
6342                          break;
6343                      case 346:
6344                          $function = 'COUNTIF';
6345                          $args = 2;
6346                          break;
6347                      case 347:
6348                          $function = 'COUNTBLANK';
6349                          $args = 1;
6350                          break;
6351                      case 350:
6352                          $function = 'ISPMT';
6353                          $args = 4;
6354                          break;
6355                      case 351:
6356                          $function = 'DATEDIF';
6357                          $args = 3;
6358                          break;
6359                      case 352:
6360                          $function = 'DATESTRING';
6361                          $args = 1;
6362                          break;
6363                      case 353:
6364                          $function = 'NUMBERSTRING';
6365                          $args = 2;
6366                          break;
6367                      case 360:
6368                          $function = 'PHONETIC';
6369                          $args = 1;
6370                          break;
6371                      case 368:
6372                          $function = 'BAHTTEXT';
6373                          $args = 1;
6374                          break;
6375                      default:
6376                          throw new PHPExcel_Reader_Exception('Unrecognized function in formula');
6377                          break;
6378                  }
6379                  $data = array('function' => $function, 'args' => $args);
6380                  break;
6381              case 0x22:    //    function with variable number of arguments
6382              case 0x42:
6383              case 0x62:
6384                  $name = 'tFuncV';
6385                  $size = 4;
6386                  // offset: 1; size: 1; number of arguments
6387                  $args = ord($formulaData[1]);
6388                  // offset: 2: size: 2; index to built-in sheet function
6389                  $index = self::getInt2d($formulaData, 2);
6390                  switch ($index) {
6391                      case 0:
6392                          $function = 'COUNT';
6393                          break;
6394                      case 1:
6395                          $function = 'IF';
6396                          break;
6397                      case 4:
6398                          $function = 'SUM';
6399                          break;
6400                      case 5:
6401                          $function = 'AVERAGE';
6402                          break;
6403                      case 6:
6404                          $function = 'MIN';
6405                          break;
6406                      case 7:
6407                          $function = 'MAX';
6408                          break;
6409                      case 8:
6410                          $function = 'ROW';
6411                          break;
6412                      case 9:
6413                          $function = 'COLUMN';
6414                          break;
6415                      case 11:
6416                          $function = 'NPV';
6417                          break;
6418                      case 12:
6419                          $function = 'STDEV';
6420                          break;
6421                      case 13:
6422                          $function = 'DOLLAR';
6423                          break;
6424                      case 14:
6425                          $function = 'FIXED';
6426                          break;
6427                      case 28:
6428                          $function = 'LOOKUP';
6429                          break;
6430                      case 29:
6431                          $function = 'INDEX';
6432                          break;
6433                      case 36:
6434                          $function = 'AND';
6435                          break;
6436                      case 37:
6437                          $function = 'OR';
6438                          break;
6439                      case 46:
6440                          $function = 'VAR';
6441                          break;
6442                      case 49:
6443                          $function = 'LINEST';
6444                          break;
6445                      case 50:
6446                          $function = 'TREND';
6447                          break;
6448                      case 51:
6449                          $function = 'LOGEST';
6450                          break;
6451                      case 52:
6452                          $function = 'GROWTH';
6453                          break;
6454                      case 56:
6455                          $function = 'PV';
6456                          break;
6457                      case 57:
6458                          $function = 'FV';
6459                          break;
6460                      case 58:
6461                          $function = 'NPER';
6462                          break;
6463                      case 59:
6464                          $function = 'PMT';
6465                          break;
6466                      case 60:
6467                          $function = 'RATE';
6468                          break;
6469                      case 62:
6470                          $function = 'IRR';
6471                          break;
6472                      case 64:
6473                          $function = 'MATCH';
6474                          break;
6475                      case 70:
6476                          $function = 'WEEKDAY';
6477                          break;
6478                      case 78:
6479                          $function = 'OFFSET';
6480                          break;
6481                      case 82:
6482                          $function = 'SEARCH';
6483                          break;
6484                      case 100:
6485                          $function = 'CHOOSE';
6486                          break;
6487                      case 101:
6488                          $function = 'HLOOKUP';
6489                          break;
6490                      case 102:
6491                          $function = 'VLOOKUP';
6492                          break;
6493                      case 109:
6494                          $function = 'LOG';
6495                          break;
6496                      case 115:
6497                          $function = 'LEFT';
6498                          break;
6499                      case 116:
6500                          $function = 'RIGHT';
6501                          break;
6502                      case 120:
6503                          $function = 'SUBSTITUTE';
6504                          break;
6505                      case 124:
6506                          $function = 'FIND';
6507                          break;
6508                      case 125:
6509                          $function = 'CELL';
6510                          break;
6511                      case 144:
6512                          $function = 'DDB';
6513                          break;
6514                      case 148:
6515                          $function = 'INDIRECT';
6516                          break;
6517                      case 167:
6518                          $function = 'IPMT';
6519                          break;
6520                      case 168:
6521                          $function = 'PPMT';
6522                          break;
6523                      case 169:
6524                          $function = 'COUNTA';
6525                          break;
6526                      case 183:
6527                          $function = 'PRODUCT';
6528                          break;
6529                      case 193:
6530                          $function = 'STDEVP';
6531                          break;
6532                      case 194:
6533                          $function = 'VARP';
6534                          break;
6535                      case 197:
6536                          $function = 'TRUNC';
6537                          break;
6538                      case 204:
6539                          $function = 'USDOLLAR';
6540                          break;
6541                      case 205:
6542                          $function = 'FINDB';
6543                          break;
6544                      case 206:
6545                          $function = 'SEARCHB';
6546                          break;
6547                      case 208:
6548                          $function = 'LEFTB';
6549                          break;
6550                      case 209:
6551                          $function = 'RIGHTB';
6552                          break;
6553                      case 216:
6554                          $function = 'RANK';
6555                          break;
6556                      case 219:
6557                          $function = 'ADDRESS';
6558                          break;
6559                      case 220:
6560                          $function = 'DAYS360';
6561                          break;
6562                      case 222:
6563                          $function = 'VDB';
6564                          break;
6565                      case 227:
6566                          $function = 'MEDIAN';
6567                          break;
6568                      case 228:
6569                          $function = 'SUMPRODUCT';
6570                          break;
6571                      case 247:
6572                          $function = 'DB';
6573                          break;
6574                      case 255:
6575                          $function = '';
6576                          break;
6577                      case 269:
6578                          $function = 'AVEDEV';
6579                          break;
6580                      case 270:
6581                          $function = 'BETADIST';
6582                          break;
6583                      case 272:
6584                          $function = 'BETAINV';
6585                          break;
6586                      case 317:
6587                          $function = 'PROB';
6588                          break;
6589                      case 318:
6590                          $function = 'DEVSQ';
6591                          break;
6592                      case 319:
6593                          $function = 'GEOMEAN';
6594                          break;
6595                      case 320:
6596                          $function = 'HARMEAN';
6597                          break;
6598                      case 321:
6599                          $function = 'SUMSQ';
6600                          break;
6601                      case 322:
6602                          $function = 'KURT';
6603                          break;
6604                      case 323:
6605                          $function = 'SKEW';
6606                          break;
6607                      case 324:
6608                          $function = 'ZTEST';
6609                          break;
6610                      case 329:
6611                          $function = 'PERCENTRANK';
6612                          break;
6613                      case 330:
6614                          $function = 'MODE';
6615                          break;
6616                      case 336:
6617                          $function = 'CONCATENATE';
6618                          break;
6619                      case 344:
6620                          $function = 'SUBTOTAL';
6621                          break;
6622                      case 345:
6623                          $function = 'SUMIF';
6624                          break;
6625                      case 354:
6626                          $function = 'ROMAN';
6627                          break;
6628                      case 358:
6629                          $function = 'GETPIVOTDATA';
6630                          break;
6631                      case 359:
6632                          $function = 'HYPERLINK';
6633                          break;
6634                      case 361:
6635                          $function = 'AVERAGEA';
6636                          break;
6637                      case 362:
6638                          $function = 'MAXA';
6639                          break;
6640                      case 363:
6641                          $function = 'MINA';
6642                          break;
6643                      case 364:
6644                          $function = 'STDEVPA';
6645                          break;
6646                      case 365:
6647                          $function = 'VARPA';
6648                          break;
6649                      case 366:
6650                          $function = 'STDEVA';
6651                          break;
6652                      case 367:
6653                          $function = 'VARA';
6654                          break;
6655                      default:
6656                          throw new PHPExcel_Reader_Exception('Unrecognized function in formula');
6657                          break;
6658                  }
6659                  $data = array('function' => $function, 'args' => $args);
6660                  break;
6661              case 0x23:    //    index to defined name
6662              case 0x43:
6663              case 0x63:
6664                  $name = 'tName';
6665                  $size = 5;
6666                  // offset: 1; size: 2; one-based index to definedname record
6667                  $definedNameIndex = self::getInt2d($formulaData, 1) - 1;
6668                  // offset: 2; size: 2; not used
6669                  $data = $this->definedname[$definedNameIndex]['name'];
6670                  break;
6671              case 0x24:    //    single cell reference e.g. A5
6672              case 0x44:
6673              case 0x64:
6674                  $name = 'tRef';
6675                  $size = 5;
6676                  $data = $this->readBIFF8CellAddress(substr($formulaData, 1, 4));
6677                  break;
6678              case 0x25:    //    cell range reference to cells in the same sheet (2d)
6679              case 0x45:
6680              case 0x65:
6681                  $name = 'tArea';
6682                  $size = 9;
6683                  $data = $this->readBIFF8CellRangeAddress(substr($formulaData, 1, 8));
6684                  break;
6685              case 0x26:    //    Constant reference sub-expression
6686              case 0x46:
6687              case 0x66:
6688                  $name = 'tMemArea';
6689                  // offset: 1; size: 4; not used
6690                  // offset: 5; size: 2; size of the following subexpression
6691                  $subSize = self::getInt2d($formulaData, 5);
6692                  $size = 7 + $subSize;
6693                  $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
6694                  break;
6695              case 0x27:    //    Deleted constant reference sub-expression
6696              case 0x47:
6697              case 0x67:
6698                  $name = 'tMemErr';
6699                  // offset: 1; size: 4; not used
6700                  // offset: 5; size: 2; size of the following subexpression
6701                  $subSize = self::getInt2d($formulaData, 5);
6702                  $size = 7 + $subSize;
6703                  $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
6704                  break;
6705              case 0x29:    //    Variable reference sub-expression
6706              case 0x49:
6707              case 0x69:
6708                  $name = 'tMemFunc';
6709                  // offset: 1; size: 2; size of the following sub-expression
6710                  $subSize = self::getInt2d($formulaData, 1);
6711                  $size = 3 + $subSize;
6712                  $data = $this->getFormulaFromData(substr($formulaData, 3, $subSize));
6713                  break;
6714              case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places
6715              case 0x4C:
6716              case 0x6C:
6717                  $name = 'tRefN';
6718                  $size = 5;
6719                  $data = $this->readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell);
6720                  break;
6721              case 0x2D:    //    Relative 2d range reference
6722              case 0x4D:
6723              case 0x6D:
6724                  $name = 'tAreaN';
6725                  $size = 9;
6726                  $data = $this->readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell);
6727                  break;
6728              case 0x39:    //    External name
6729              case 0x59:
6730              case 0x79:
6731                  $name = 'tNameX';
6732                  $size = 7;
6733                  // offset: 1; size: 2; index to REF entry in EXTERNSHEET record
6734                  // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record
6735                  $index = self::getInt2d($formulaData, 3);
6736                  // assume index is to EXTERNNAME record
6737                  $data = $this->externalNames[$index - 1]['name'];
6738                  // offset: 5; size: 2; not used
6739                  break;
6740              case 0x3A:    //    3d reference to cell
6741              case 0x5A:
6742              case 0x7A:
6743                  $name = 'tRef3d';
6744                  $size = 7;
6745  
6746                  try {
6747                      // offset: 1; size: 2; index to REF entry
6748                      $sheetRange = $this->readSheetRangeByRefIndex(self::getInt2d($formulaData, 1));
6749                      // offset: 3; size: 4; cell address
6750                      $cellAddress = $this->readBIFF8CellAddress(substr($formulaData, 3, 4));
6751  
6752                      $data = "$sheetRange!$cellAddress";
6753                  } catch (PHPExcel_Exception $e) {
6754                      // deleted sheet reference
6755                      $data = '#REF!';
6756                  }
6757                  break;
6758              case 0x3B:    //    3d reference to cell range
6759              case 0x5B:
6760              case 0x7B:
6761                  $name = 'tArea3d';
6762                  $size = 11;
6763  
6764                  try {
6765                      // offset: 1; size: 2; index to REF entry
6766                      $sheetRange = $this->readSheetRangeByRefIndex(self::getInt2d($formulaData, 1));
6767                      // offset: 3; size: 8; cell address
6768                      $cellRangeAddress = $this->readBIFF8CellRangeAddress(substr($formulaData, 3, 8));
6769  
6770                      $data = "$sheetRange!$cellRangeAddress";
6771                  } catch (PHPExcel_Exception $e) {
6772                      // deleted sheet reference
6773                      $data = '#REF!';
6774                  }
6775                  break;
6776              // Unknown cases    // don't know how to deal with
6777              default:
6778                  throw new PHPExcel_Reader_Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula');
6779                  break;
6780          }
6781  
6782          return array(
6783              'id' => $id,
6784              'name' => $name,
6785              'size' => $size,
6786              'data' => $data,
6787          );
6788      }
6789  
6790  
6791      /**
6792       * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2'
6793       * section 3.3.4
6794       *
6795       * @param string $cellAddressStructure
6796       * @return string
6797       */
6798      private function readBIFF8CellAddress($cellAddressStructure)
6799      {
6800          // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
6801          $row = self::getInt2d($cellAddressStructure, 0) + 1;
6802  
6803          // offset: 2; size: 2; index to column or column offset + relative flags
6804          // bit: 7-0; mask 0x00FF; column index
6805          $column = PHPExcel_Cell::stringFromColumnIndex(0x00FF & self::getInt2d($cellAddressStructure, 2));
6806  
6807          // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6808          if (!(0x4000 & self::getInt2d($cellAddressStructure, 2))) {
6809              $column = '$' . $column;
6810          }
6811          // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6812          if (!(0x8000 & self::getInt2d($cellAddressStructure, 2))) {
6813              $row = '$' . $row;
6814          }
6815  
6816          return $column . $row;
6817      }
6818  
6819  
6820      /**
6821       * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column
6822       * to indicate offsets from a base cell
6823       * section 3.3.4
6824       *
6825       * @param string $cellAddressStructure
6826       * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
6827       * @return string
6828       */
6829      private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1')
6830      {
6831          list($baseCol, $baseRow) = PHPExcel_Cell::coordinateFromString($baseCell);
6832          $baseCol = PHPExcel_Cell::columnIndexFromString($baseCol) - 1;
6833  
6834          // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
6835          $rowIndex = self::getInt2d($cellAddressStructure, 0);
6836          $row = self::getInt2d($cellAddressStructure, 0) + 1;
6837  
6838          // offset: 2; size: 2; index to column or column offset + relative flags
6839          // bit: 7-0; mask 0x00FF; column index
6840          $colIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2);
6841  
6842          // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6843          if (!(0x4000 & self::getInt2d($cellAddressStructure, 2))) {
6844              $column = PHPExcel_Cell::stringFromColumnIndex($colIndex);
6845              $column = '$' . $column;
6846          } else {
6847              $colIndex = ($colIndex <= 127) ? $colIndex : $colIndex - 256;
6848              $column = PHPExcel_Cell::stringFromColumnIndex($baseCol + $colIndex);
6849          }
6850  
6851          // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6852          if (!(0x8000 & self::getInt2d($cellAddressStructure, 2))) {
6853              $row = '$' . $row;
6854          } else {
6855              $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536;
6856              $row = $baseRow + $rowIndex;
6857          }
6858  
6859          return $column . $row;
6860      }
6861  
6862  
6863      /**
6864       * Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1'
6865       * always fixed range
6866       * section 2.5.14
6867       *
6868       * @param string $subData
6869       * @return string
6870       * @throws PHPExcel_Reader_Exception
6871       */
6872      private function readBIFF5CellRangeAddressFixed($subData)
6873      {
6874          // offset: 0; size: 2; index to first row
6875          $fr = self::getInt2d($subData, 0) + 1;
6876  
6877          // offset: 2; size: 2; index to last row
6878          $lr = self::getInt2d($subData, 2) + 1;
6879  
6880          // offset: 4; size: 1; index to first column
6881          $fc = ord($subData{4});
6882  
6883          // offset: 5; size: 1; index to last column
6884          $lc = ord($subData{5});
6885  
6886          // check values
6887          if ($fr > $lr || $fc > $lc) {
6888              throw new PHPExcel_Reader_Exception('Not a cell range address');
6889          }
6890  
6891          // column index to letter
6892          $fc = PHPExcel_Cell::stringFromColumnIndex($fc);
6893          $lc = PHPExcel_Cell::stringFromColumnIndex($lc);
6894  
6895          if ($fr == $lr and $fc == $lc) {
6896              return "$fc$fr";
6897          }
6898          return "$fc$fr:$lc$lr";
6899      }
6900  
6901  
6902      /**
6903       * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1'
6904       * always fixed range
6905       * section 2.5.14
6906       *
6907       * @param string $subData
6908       * @return string
6909       * @throws PHPExcel_Reader_Exception
6910       */
6911      private function readBIFF8CellRangeAddressFixed($subData)
6912      {
6913          // offset: 0; size: 2; index to first row
6914          $fr = self::getInt2d($subData, 0) + 1;
6915  
6916          // offset: 2; size: 2; index to last row
6917          $lr = self::getInt2d($subData, 2) + 1;
6918  
6919          // offset: 4; size: 2; index to first column
6920          $fc = self::getInt2d($subData, 4);
6921  
6922          // offset: 6; size: 2; index to last column
6923          $lc = self::getInt2d($subData, 6);
6924  
6925          // check values
6926          if ($fr > $lr || $fc > $lc) {
6927              throw new PHPExcel_Reader_Exception('Not a cell range address');
6928          }
6929  
6930          // column index to letter
6931          $fc = PHPExcel_Cell::stringFromColumnIndex($fc);
6932          $lc = PHPExcel_Cell::stringFromColumnIndex($lc);
6933  
6934          if ($fr == $lr and $fc == $lc) {
6935              return "$fc$fr";
6936          }
6937          return "$fc$fr:$lc$lr";
6938      }
6939  
6940  
6941      /**
6942       * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6'
6943       * there are flags indicating whether column/row index is relative
6944       * section 3.3.4
6945       *
6946       * @param string $subData
6947       * @return string
6948       */
6949      private function readBIFF8CellRangeAddress($subData)
6950      {
6951          // todo: if cell range is just a single cell, should this funciton
6952          // not just return e.g. 'A1' and not 'A1:A1' ?
6953  
6954          // offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767))
6955              $fr = self::getInt2d($subData, 0) + 1;
6956  
6957          // offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767))
6958              $lr = self::getInt2d($subData, 2) + 1;
6959  
6960          // offset: 4; size: 2; index to first column or column offset + relative flags
6961  
6962          // bit: 7-0; mask 0x00FF; column index
6963          $fc = PHPExcel_Cell::stringFromColumnIndex(0x00FF & self::getInt2d($subData, 4));
6964  
6965          // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6966          if (!(0x4000 & self::getInt2d($subData, 4))) {
6967              $fc = '$' . $fc;
6968          }
6969  
6970          // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6971          if (!(0x8000 & self::getInt2d($subData, 4))) {
6972              $fr = '$' . $fr;
6973          }
6974  
6975          // offset: 6; size: 2; index to last column or column offset + relative flags
6976  
6977          // bit: 7-0; mask 0x00FF; column index
6978          $lc = PHPExcel_Cell::stringFromColumnIndex(0x00FF & self::getInt2d($subData, 6));
6979  
6980          // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
6981          if (!(0x4000 & self::getInt2d($subData, 6))) {
6982              $lc = '$' . $lc;
6983          }
6984  
6985          // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
6986          if (!(0x8000 & self::getInt2d($subData, 6))) {
6987              $lr = '$' . $lr;
6988          }
6989  
6990          return "$fc$fr:$lc$lr";
6991      }
6992  
6993  
6994      /**
6995       * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column
6996       * to indicate offsets from a base cell
6997       * section 3.3.4
6998       *
6999       * @param string $subData
7000       * @param string $baseCell Base cell
7001       * @return string Cell range address
7002       */
7003      private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1')
7004      {
7005          list($baseCol, $baseRow) = PHPExcel_Cell::coordinateFromString($baseCell);
7006          $baseCol = PHPExcel_Cell::columnIndexFromString($baseCol) - 1;
7007  
7008          // TODO: if cell range is just a single cell, should this funciton
7009          // not just return e.g. 'A1' and not 'A1:A1' ?
7010  
7011          // offset: 0; size: 2; first row
7012          $frIndex = self::getInt2d($subData, 0); // adjust below
7013  
7014          // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767)
7015          $lrIndex = self::getInt2d($subData, 2); // adjust below
7016  
7017          // offset: 4; size: 2; first column with relative/absolute flags
7018  
7019          // bit: 7-0; mask 0x00FF; column index
7020          $fcIndex = 0x00FF & self::getInt2d($subData, 4);
7021  
7022          // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7023          if (!(0x4000 & self::getInt2d($subData, 4))) {
7024              // absolute column index
7025              $fc = PHPExcel_Cell::stringFromColumnIndex($fcIndex);
7026              $fc = '$' . $fc;
7027          } else {
7028              // column offset
7029              $fcIndex = ($fcIndex <= 127) ? $fcIndex : $fcIndex - 256;
7030              $fc = PHPExcel_Cell::stringFromColumnIndex($baseCol + $fcIndex);
7031          }
7032  
7033          // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7034          if (!(0x8000 & self::getInt2d($subData, 4))) {
7035              // absolute row index
7036              $fr = $frIndex + 1;
7037              $fr = '$' . $fr;
7038          } else {
7039              // row offset
7040              $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536;
7041              $fr = $baseRow + $frIndex;
7042          }
7043  
7044          // offset: 6; size: 2; last column with relative/absolute flags
7045  
7046          // bit: 7-0; mask 0x00FF; column index
7047          $lcIndex = 0x00FF & self::getInt2d($subData, 6);
7048          $lcIndex = ($lcIndex <= 127) ? $lcIndex : $lcIndex - 256;
7049          $lc = PHPExcel_Cell::stringFromColumnIndex($baseCol + $lcIndex);
7050  
7051          // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7052          if (!(0x4000 & self::getInt2d($subData, 6))) {
7053              // absolute column index
7054              $lc = PHPExcel_Cell::stringFromColumnIndex($lcIndex);
7055              $lc = '$' . $lc;
7056          } else {
7057              // column offset
7058              $lcIndex = ($lcIndex <= 127) ? $lcIndex : $lcIndex - 256;
7059              $lc = PHPExcel_Cell::stringFromColumnIndex($baseCol + $lcIndex);
7060          }
7061  
7062          // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7063          if (!(0x8000 & self::getInt2d($subData, 6))) {
7064              // absolute row index
7065              $lr = $lrIndex + 1;
7066              $lr = '$' . $lr;
7067          } else {
7068              // row offset
7069              $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536;
7070              $lr = $baseRow + $lrIndex;
7071          }
7072  
7073          return "$fc$fr:$lc$lr";
7074      }
7075  
7076  
7077      /**
7078       * Read BIFF8 cell range address list
7079       * section 2.5.15
7080       *
7081       * @param string $subData
7082       * @return array
7083       */
7084      private function readBIFF8CellRangeAddressList($subData)
7085      {
7086          $cellRangeAddresses = array();
7087  
7088          // offset: 0; size: 2; number of the following cell range addresses
7089          $nm = self::getInt2d($subData, 0);
7090  
7091          $offset = 2;
7092          // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses
7093          for ($i = 0; $i < $nm; ++$i) {
7094              $cellRangeAddresses[] = $this->readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
7095              $offset += 8;
7096          }
7097  
7098          return array(
7099              'size' => 2 + 8 * $nm,
7100              'cellRangeAddresses' => $cellRangeAddresses,
7101          );
7102      }
7103  
7104  
7105      /**
7106       * Read BIFF5 cell range address list
7107       * section 2.5.15
7108       *
7109       * @param string $subData
7110       * @return array
7111       */
7112      private function readBIFF5CellRangeAddressList($subData)
7113      {
7114          $cellRangeAddresses = array();
7115  
7116          // offset: 0; size: 2; number of the following cell range addresses
7117          $nm = self::getInt2d($subData, 0);
7118  
7119          $offset = 2;
7120          // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses
7121          for ($i = 0; $i < $nm; ++$i) {
7122              $cellRangeAddresses[] = $this->readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6));
7123              $offset += 6;
7124          }
7125  
7126          return array(
7127              'size' => 2 + 6 * $nm,
7128              'cellRangeAddresses' => $cellRangeAddresses,
7129          );
7130      }
7131  
7132  
7133      /**
7134       * Get a sheet range like Sheet1:Sheet3 from REF index
7135       * Note: If there is only one sheet in the range, one gets e.g Sheet1
7136       * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets,
7137       * in which case an PHPExcel_Reader_Exception is thrown
7138       *
7139       * @param int $index
7140       * @return string|false
7141       * @throws PHPExcel_Reader_Exception
7142       */
7143      private function readSheetRangeByRefIndex($index)
7144      {
7145          if (isset($this->ref[$index])) {
7146              $type = $this->externalBooks[$this->ref[$index]['externalBookIndex']]['type'];
7147  
7148              switch ($type) {
7149                  case 'internal':
7150                      // check if we have a deleted 3d reference
7151                      if ($this->ref[$index]['firstSheetIndex'] == 0xFFFF or $this->ref[$index]['lastSheetIndex'] == 0xFFFF) {
7152                          throw new PHPExcel_Reader_Exception('Deleted sheet reference');
7153                      }
7154  
7155                      // we have normal sheet range (collapsed or uncollapsed)
7156                      $firstSheetName = $this->sheets[$this->ref[$index]['firstSheetIndex']]['name'];
7157                      $lastSheetName = $this->sheets[$this->ref[$index]['lastSheetIndex']]['name'];
7158  
7159                      if ($firstSheetName == $lastSheetName) {
7160                          // collapsed sheet range
7161                          $sheetRange = $firstSheetName;
7162                      } else {
7163                          $sheetRange = "$firstSheetName:$lastSheetName";
7164                      }
7165  
7166                      // escape the single-quotes
7167                      $sheetRange = str_replace("'", "''", $sheetRange);
7168  
7169                      // if there are special characters, we need to enclose the range in single-quotes
7170                      // todo: check if we have identified the whole set of special characters
7171                      // it seems that the following characters are not accepted for sheet names
7172                      // and we may assume that they are not present: []*/:\?
7173                      if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/", $sheetRange)) {
7174                          $sheetRange = "'$sheetRange'";
7175                      }
7176  
7177                      return $sheetRange;
7178                      break;
7179                  default:
7180                      // TODO: external sheet support
7181                      throw new PHPExcel_Reader_Exception('Excel5 reader only supports internal sheets in fomulas');
7182                      break;
7183              }
7184          }
7185          return false;
7186      }
7187  
7188  
7189      /**
7190       * read BIFF8 constant value array from array data
7191       * returns e.g. array('value' => '{1,2;3,4}', 'size' => 40}
7192       * section 2.5.8
7193       *
7194       * @param string $arrayData
7195       * @return array
7196       */
7197      private static function readBIFF8ConstantArray($arrayData)
7198      {
7199          // offset: 0; size: 1; number of columns decreased by 1
7200          $nc = ord($arrayData[0]);
7201  
7202          // offset: 1; size: 2; number of rows decreased by 1
7203          $nr = self::getInt2d($arrayData, 1);
7204          $size = 3; // initialize
7205          $arrayData = substr($arrayData, 3);
7206  
7207          // offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values
7208          $matrixChunks = array();
7209          for ($r = 1; $r <= $nr + 1; ++$r) {
7210              $items = array();
7211              for ($c = 1; $c <= $nc + 1; ++$c) {
7212                  $constant = self::_readBIFF8Constant($arrayData);
7213                  $items[] = $constant['value'];
7214                  $arrayData = substr($arrayData, $constant['size']);
7215                  $size += $constant['size'];
7216              }
7217              $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'
7218          }
7219          $matrix = '{' . implode(';', $matrixChunks) . '}';
7220  
7221          return array(
7222              'value' => $matrix,
7223              'size' => $size,
7224          );
7225      }
7226  
7227  
7228      /**
7229       * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value'
7230       * section 2.5.7
7231       * returns e.g. array('value' => '5', 'size' => 9)
7232       *
7233       * @param string $valueData
7234       * @return array
7235       */
7236      private static function readBIFF8Constant($valueData)
7237      {
7238          // offset: 0; size: 1; identifier for type of constant
7239          $identifier = ord($valueData[0]);
7240  
7241          switch ($identifier) {
7242              case 0x00: // empty constant (what is this?)
7243                  $value = '';
7244                  $size = 9;
7245                  break;
7246              case 0x01: // number
7247                  // offset: 1; size: 8; IEEE 754 floating-point value
7248                  $value = self::extractNumber(substr($valueData, 1, 8));
7249                  $size = 9;
7250                  break;
7251              case 0x02: // string value
7252                  // offset: 1; size: var; Unicode string, 16-bit string length
7253                  $string = self::readUnicodeStringLong(substr($valueData, 1));
7254                  $value = '"' . $string['value'] . '"';
7255                  $size = 1 + $string['size'];
7256                  break;
7257              case 0x04: // boolean
7258                  // offset: 1; size: 1; 0 = FALSE, 1 = TRUE
7259                  if (ord($valueData[1])) {
7260                      $value = 'TRUE';
7261                  } else {
7262                      $value = 'FALSE';
7263                  }
7264                  $size = 9;
7265                  break;
7266              case 0x10: // error code
7267                  // offset: 1; size: 1; error code
7268                  $value = self::mapErrorCode(ord($valueData[1]));
7269                  $size = 9;
7270                  break;
7271          }
7272          return array(
7273              'value' => $value,
7274              'size' => $size,
7275          );
7276      }
7277  
7278  
7279      /**
7280       * Extract RGB color
7281       * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4
7282       *
7283       * @param string $rgb Encoded RGB value (4 bytes)
7284       * @return array
7285       */
7286      private static function readRGB($rgb)
7287      {
7288          // offset: 0; size 1; Red component
7289          $r = ord($rgb{0});
7290  
7291          // offset: 1; size: 1; Green component
7292          $g = ord($rgb{1});
7293  
7294          // offset: 2; size: 1; Blue component
7295          $b = ord($rgb{2});
7296  
7297          // HEX notation, e.g. 'FF00FC'
7298          $rgb = sprintf('%02X%02X%02X', $r, $g, $b);
7299  
7300          return array('rgb' => $rgb);
7301      }
7302  
7303  
7304      /**
7305       * Read byte string (8-bit string length)
7306       * OpenOffice documentation: 2.5.2
7307       *
7308       * @param string $subData
7309       * @return array
7310       */
7311      private function readByteStringShort($subData)
7312      {
7313          // offset: 0; size: 1; length of the string (character count)
7314          $ln = ord($subData[0]);
7315  
7316          // offset: 1: size: var; character array (8-bit characters)
7317          $value = $this->decodeCodepage(substr($subData, 1, $ln));
7318  
7319          return array(
7320              'value' => $value,
7321              'size' => 1 + $ln, // size in bytes of data structure
7322          );
7323      }
7324  
7325  
7326      /**
7327       * Read byte string (16-bit string length)
7328       * OpenOffice documentation: 2.5.2
7329       *
7330       * @param string $subData
7331       * @return array
7332       */
7333      private function readByteStringLong($subData)
7334      {
7335          // offset: 0; size: 2; length of the string (character count)
7336          $ln = self::getInt2d($subData, 0);
7337  
7338          // offset: 2: size: var; character array (8-bit characters)
7339          $value = $this->decodeCodepage(substr($subData, 2));
7340  
7341          //return $string;
7342          return array(
7343              'value' => $value,
7344              'size' => 2 + $ln, // size in bytes of data structure
7345          );
7346      }
7347  
7348  
7349      /**
7350       * Extracts an Excel Unicode short string (8-bit string length)
7351       * OpenOffice documentation: 2.5.3
7352       * function will automatically find out where the Unicode string ends.
7353       *
7354       * @param string $subData
7355       * @return array
7356       */
7357      private static function readUnicodeStringShort($subData)
7358      {
7359          $value = '';
7360  
7361          // offset: 0: size: 1; length of the string (character count)
7362          $characterCount = ord($subData[0]);
7363  
7364          $string = self::readUnicodeString(substr($subData, 1), $characterCount);
7365  
7366          // add 1 for the string length
7367          $string['size'] += 1;
7368  
7369          return $string;
7370      }
7371  
7372  
7373      /**
7374       * Extracts an Excel Unicode long string (16-bit string length)
7375       * OpenOffice documentation: 2.5.3
7376       * this function is under construction, needs to support rich text, and Asian phonetic settings
7377       *
7378       * @param string $subData
7379       * @return array
7380       */
7381      private static function readUnicodeStringLong($subData)
7382      {
7383          $value = '';
7384  
7385          // offset: 0: size: 2; length of the string (character count)
7386          $characterCount = self::getInt2d($subData, 0);
7387  
7388          $string = self::readUnicodeString(substr($subData, 2), $characterCount);
7389  
7390          // add 2 for the string length
7391          $string['size'] += 2;
7392  
7393          return $string;
7394      }
7395  
7396  
7397      /**
7398       * Read Unicode string with no string length field, but with known character count
7399       * this function is under construction, needs to support rich text, and Asian phonetic settings
7400       * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3
7401       *
7402       * @param string $subData
7403       * @param int $characterCount
7404       * @return array
7405       */
7406      private static function readUnicodeString($subData, $characterCount)
7407      {
7408          $value = '';
7409  
7410          // offset: 0: size: 1; option flags
7411          // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)
7412          $isCompressed = !((0x01 & ord($subData[0])) >> 0);
7413  
7414          // bit: 2; mask: 0x04; Asian phonetic settings
7415          $hasAsian = (0x04) & ord($subData[0]) >> 2;
7416  
7417          // bit: 3; mask: 0x08; Rich-Text settings
7418          $hasRichText = (0x08) & ord($subData[0]) >> 3;
7419  
7420          // offset: 1: size: var; character array
7421          // this offset assumes richtext and Asian phonetic settings are off which is generally wrong
7422          // needs to be fixed
7423          $value = self::encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed);
7424  
7425          return array(
7426              'value' => $value,
7427              'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags
7428          );
7429      }
7430  
7431  
7432      /**
7433       * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas.
7434       * Example:  hello"world  -->  "hello""world"
7435       *
7436       * @param string $value UTF-8 encoded string
7437       * @return string
7438       */
7439      private static function UTF8toExcelDoubleQuoted($value)
7440      {
7441          return '"' . str_replace('"', '""', $value) . '"';
7442      }
7443  
7444  
7445      /**
7446       * Reads first 8 bytes of a string and return IEEE 754 float
7447       *
7448       * @param string $data Binary string that is at least 8 bytes long
7449       * @return float
7450       */
7451      private static function extractNumber($data)
7452      {
7453          $rknumhigh = self::getInt4d($data, 4);
7454          $rknumlow = self::getInt4d($data, 0);
7455          $sign = ($rknumhigh & 0x80000000) >> 31;
7456          $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023;
7457          $mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
7458          $mantissalow1 = ($rknumlow & 0x80000000) >> 31;
7459          $mantissalow2 = ($rknumlow & 0x7fffffff);
7460          $value = $mantissa / pow(2, (20 - $exp));
7461  
7462          if ($mantissalow1 != 0) {
7463              $value += 1 / pow(2, (21 - $exp));
7464          }
7465  
7466          $value += $mantissalow2 / pow(2, (52 - $exp));
7467          if ($sign) {
7468              $value *= -1;
7469          }
7470  
7471          return $value;
7472      }
7473  
7474  
7475      private static function getIEEE754($rknum)
7476      {
7477          if (($rknum & 0x02) != 0) {
7478              $value = $rknum >> 2;
7479          } else {
7480              // changes by mmp, info on IEEE754 encoding from
7481              // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
7482              // The RK format calls for using only the most significant 30 bits
7483              // of the 64 bit floating point value. The other 34 bits are assumed
7484              // to be 0 so we use the upper 30 bits of $rknum as follows...
7485              $sign = ($rknum & 0x80000000) >> 31;
7486              $exp = ($rknum & 0x7ff00000) >> 20;
7487              $mantissa = (0x100000 | ($rknum & 0x000ffffc));
7488              $value = $mantissa / pow(2, (20- ($exp - 1023)));
7489              if ($sign) {
7490                  $value = -1 * $value;
7491              }
7492              //end of changes by mmp
7493          }
7494          if (($rknum & 0x01) != 0) {
7495              $value /= 100;
7496          }
7497          return $value;
7498      }
7499  
7500  
7501      /**
7502       * Get UTF-8 string from (compressed or uncompressed) UTF-16 string
7503       *
7504       * @param string $string
7505       * @param bool $compressed
7506       * @return string
7507       */
7508      private static function encodeUTF16($string, $compressed = '')
7509      {
7510          if ($compressed) {
7511              $string = self::uncompressByteString($string);
7512          }
7513  
7514          return PHPExcel_Shared_String::ConvertEncoding($string, 'UTF-8', 'UTF-16LE');
7515      }
7516  
7517  
7518      /**
7519       * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8.
7520       *
7521       * @param string $string
7522       * @return string
7523       */
7524      private static function uncompressByteString($string)
7525      {
7526          $uncompressedString = '';
7527          $strLen = strlen($string);
7528          for ($i = 0; $i < $strLen; ++$i) {
7529              $uncompressedString .= $string[$i] . "\0";
7530          }
7531  
7532          return $uncompressedString;
7533      }
7534  
7535  
7536      /**
7537       * Convert string to UTF-8. Only used for BIFF5.
7538       *
7539       * @param string $string
7540       * @return string
7541       */
7542      private function decodeCodepage($string)
7543      {
7544          return PHPExcel_Shared_String::ConvertEncoding($string, 'UTF-8', $this->codepage);
7545      }
7546  
7547  
7548      /**
7549       * Read 16-bit unsigned integer
7550       *
7551       * @param string $data
7552       * @param int $pos
7553       * @return int
7554       */
7555      public static function getInt2d($data, $pos)
7556      {
7557          return ord($data[$pos]) | (ord($data[$pos+1]) << 8);
7558      }
7559  
7560  
7561      /**
7562       * Read 32-bit signed integer
7563       *
7564       * @param string $data
7565       * @param int $pos
7566       * @return int
7567       */
7568      public static function getInt4d($data, $pos)
7569      {
7570          // FIX: represent numbers correctly on 64-bit system
7571          // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
7572          // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
7573          $_or_24 = ord($data[$pos + 3]);
7574          if ($_or_24 >= 128) {
7575              // negative number
7576              $_ord_24 = -abs((256 - $_or_24) << 24);
7577          } else {
7578              $_ord_24 = ($_or_24 & 127) << 24;
7579          }
7580          return ord($data[$pos]) | (ord($data[$pos+1]) << 8) | (ord($data[$pos+2]) << 16) | $_ord_24;
7581      }
7582  
7583  
7584      /**
7585       * Read color
7586       *
7587       * @param int $color Indexed color
7588       * @param array $palette Color palette
7589       * @return array RGB color value, example: array('rgb' => 'FF0000')
7590       */
7591      private static function readColor($color, $palette, $version)
7592      {
7593          if ($color <= 0x07 || $color >= 0x40) {
7594              // special built-in color
7595              return self::mapBuiltInColor($color);
7596          } elseif (isset($palette) && isset($palette[$color - 8])) {
7597              // palette color, color index 0x08 maps to pallete index 0
7598              return $palette[$color - 8];
7599          } else {
7600              // default color table
7601              if ($version == self::XLS_BIFF8) {
7602                  return self::mapColor($color);
7603              } else {
7604                  // BIFF5
7605                  return self::mapColorBIFF5($color);
7606              }
7607          }
7608  
7609          return $color;
7610      }
7611  
7612  
7613      /**
7614       * Map border style
7615       * OpenOffice documentation: 2.5.11
7616       *
7617       * @param int $index
7618       * @return string
7619       */
7620      private static function mapBorderStyle($index)
7621      {
7622          switch ($index) {
7623              case 0x00:
7624                  return PHPExcel_Style_Border::BORDER_NONE;
7625              case 0x01:
7626                  return PHPExcel_Style_Border::BORDER_THIN;
7627              case 0x02:
7628                  return PHPExcel_Style_Border::BORDER_MEDIUM;
7629              case 0x03:
7630                  return PHPExcel_Style_Border::BORDER_DASHED;
7631              case 0x04:
7632                  return PHPExcel_Style_Border::BORDER_DOTTED;
7633              case 0x05:
7634                  return PHPExcel_Style_Border::BORDER_THICK;
7635              case 0x06:
7636                  return PHPExcel_Style_Border::BORDER_DOUBLE;
7637              case 0x07:
7638                  return PHPExcel_Style_Border::BORDER_HAIR;
7639              case 0x08:
7640                  return PHPExcel_Style_Border::BORDER_MEDIUMDASHED;
7641              case 0x09:
7642                  return PHPExcel_Style_Border::BORDER_DASHDOT;
7643              case 0x0A:
7644                  return PHPExcel_Style_Border::BORDER_MEDIUMDASHDOT;
7645              case 0x0B:
7646                  return PHPExcel_Style_Border::BORDER_DASHDOTDOT;
7647              case 0x0C:
7648                  return PHPExcel_Style_Border::BORDER_MEDIUMDASHDOTDOT;
7649              case 0x0D:
7650                  return PHPExcel_Style_Border::BORDER_SLANTDASHDOT;
7651              default:
7652                  return PHPExcel_Style_Border::BORDER_NONE;
7653          }
7654      }
7655  
7656  
7657      /**
7658       * Get fill pattern from index
7659       * OpenOffice documentation: 2.5.12
7660       *
7661       * @param int $index
7662       * @return string
7663       */
7664      private static function mapFillPattern($index)
7665      {
7666          switch ($index) {
7667              case 0x00:
7668                  return PHPExcel_Style_Fill::FILL_NONE;
7669              case 0x01:
7670                  return PHPExcel_Style_Fill::FILL_SOLID;
7671              case 0x02:
7672                  return PHPExcel_Style_Fill::FILL_PATTERN_MEDIUMGRAY;
7673              case 0x03:
7674                  return PHPExcel_Style_Fill::FILL_PATTERN_DARKGRAY;
7675              case 0x04:
7676                  return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTGRAY;
7677              case 0x05:
7678                  return PHPExcel_Style_Fill::FILL_PATTERN_DARKHORIZONTAL;
7679              case 0x06:
7680                  return PHPExcel_Style_Fill::FILL_PATTERN_DARKVERTICAL;
7681              case 0x07:
7682                  return PHPExcel_Style_Fill::FILL_PATTERN_DARKDOWN;
7683              case 0x08:
7684                  return PHPExcel_Style_Fill::FILL_PATTERN_DARKUP;
7685              case 0x09:
7686                  return PHPExcel_Style_Fill::FILL_PATTERN_DARKGRID;
7687              case 0x0A:
7688                  return PHPExcel_Style_Fill::FILL_PATTERN_DARKTRELLIS;
7689              case 0x0B:
7690                  return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTHORIZONTAL;
7691              case 0x0C:
7692                  return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTVERTICAL;
7693              case 0x0D:
7694                  return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTDOWN;
7695              case 0x0E:
7696                  return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTUP;
7697              case 0x0F:
7698                  return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTGRID;
7699              case 0x10:
7700                  return PHPExcel_Style_Fill::FILL_PATTERN_LIGHTTRELLIS;
7701              case 0x11:
7702                  return PHPExcel_Style_Fill::FILL_PATTERN_GRAY125;
7703              case 0x12:
7704                  return PHPExcel_Style_Fill::FILL_PATTERN_GRAY0625;
7705              default:
7706                  return PHPExcel_Style_Fill::FILL_NONE;
7707          }
7708      }
7709  
7710  
7711      /**
7712       * Map error code, e.g. '#N/A'
7713       *
7714       * @param int $subData
7715       * @return string
7716       */
7717      private static function mapErrorCode($subData)
7718      {
7719          switch ($subData) {
7720              case 0x00:
7721                  return '#NULL!';
7722                  break;
7723              case 0x07:
7724                  return '#DIV/0!';
7725                  break;
7726              case 0x0F:
7727                  return '#VALUE!';
7728                  break;
7729              case 0x17:
7730                  return '#REF!';
7731                  break;
7732              case 0x1D:
7733                  return '#NAME?';
7734                  break;
7735              case 0x24:
7736                  return '#NUM!';
7737                  break;
7738              case 0x2A:
7739                  return '#N/A';
7740                  break;
7741              default:
7742                  return false;
7743          }
7744      }
7745  
7746  
7747      /**
7748       * Map built-in color to RGB value
7749       *
7750       * @param int $color Indexed color
7751       * @return array
7752       */
7753      private static function mapBuiltInColor($color)
7754      {
7755          switch ($color) {
7756              case 0x00:
7757                  return array('rgb' => '000000');
7758              case 0x01:
7759                  return array('rgb' => 'FFFFFF');
7760              case 0x02:
7761                  return array('rgb' => 'FF0000');
7762              case 0x03:
7763                  return array('rgb' => '00FF00');
7764              case 0x04:
7765                  return array('rgb' => '0000FF');
7766              case 0x05:
7767                  return array('rgb' => 'FFFF00');
7768              case 0x06:
7769                  return array('rgb' => 'FF00FF');
7770              case 0x07:
7771                  return array('rgb' => '00FFFF');
7772              case 0x40:
7773                  return array('rgb' => '000000'); // system window text color
7774              case 0x41:
7775                  return array('rgb' => 'FFFFFF'); // system window background color
7776              default:
7777                  return array('rgb' => '000000');
7778          }
7779      }
7780  
7781  
7782      /**
7783       * Map color array from BIFF5 built-in color index
7784       *
7785       * @param int $subData
7786       * @return array
7787       */
7788      private static function mapColorBIFF5($subData)
7789      {
7790          switch ($subData) {
7791              case 0x08:
7792                  return array('rgb' => '000000');
7793              case 0x09:
7794                  return array('rgb' => 'FFFFFF');
7795              case 0x0A:
7796                  return array('rgb' => 'FF0000');
7797              case 0x0B:
7798                  return array('rgb' => '00FF00');
7799              case 0x0C:
7800                  return array('rgb' => '0000FF');
7801              case 0x0D:
7802                  return array('rgb' => 'FFFF00');
7803              case 0x0E:
7804                  return array('rgb' => 'FF00FF');
7805              case 0x0F:
7806                  return array('rgb' => '00FFFF');
7807              case 0x10:
7808                  return array('rgb' => '800000');
7809              case 0x11:
7810                  return array('rgb' => '008000');
7811              case 0x12:
7812                  return array('rgb' => '000080');
7813              case 0x13:
7814                  return array('rgb' => '808000');
7815              case 0x14:
7816                  return array('rgb' => '800080');
7817              case 0x15:
7818                  return array('rgb' => '008080');
7819              case 0x16:
7820                  return array('rgb' => 'C0C0C0');
7821              case 0x17:
7822                  return array('rgb' => '808080');
7823              case 0x18:
7824                  return array('rgb' => '8080FF');
7825              case 0x19:
7826                  return array('rgb' => '802060');
7827              case 0x1A:
7828                  return array('rgb' => 'FFFFC0');
7829              case 0x1B:
7830                  return array('rgb' => 'A0E0F0');
7831              case 0x1C:
7832                  return array('rgb' => '600080');
7833              case 0x1D:
7834                  return array('rgb' => 'FF8080');
7835              case 0x1E:
7836                  return array('rgb' => '0080C0');
7837              case 0x1F:
7838                  return array('rgb' => 'C0C0FF');
7839              case 0x20:
7840                  return array('rgb' => '000080');
7841              case 0x21:
7842                  return array('rgb' => 'FF00FF');
7843              case 0x22:
7844                  return array('rgb' => 'FFFF00');
7845              case 0x23:
7846                  return array('rgb' => '00FFFF');
7847              case 0x24:
7848                  return array('rgb' => '800080');
7849              case 0x25:
7850                  return array('rgb' => '800000');
7851              case 0x26:
7852                  return array('rgb' => '008080');
7853              case 0x27:
7854                  return array('rgb' => '0000FF');
7855              case 0x28:
7856                  return array('rgb' => '00CFFF');
7857              case 0x29:
7858                  return array('rgb' => '69FFFF');
7859              case 0x2A:
7860                  return array('rgb' => 'E0FFE0');
7861              case 0x2B:
7862                  return array('rgb' => 'FFFF80');
7863              case 0x2C:
7864                  return array('rgb' => 'A6CAF0');
7865              case 0x2D:
7866                  return array('rgb' => 'DD9CB3');
7867              case 0x2E:
7868                  return array('rgb' => 'B38FEE');
7869              case 0x2F:
7870                  return array('rgb' => 'E3E3E3');
7871              case 0x30:
7872                  return array('rgb' => '2A6FF9');
7873              case 0x31:
7874                  return array('rgb' => '3FB8CD');
7875              case 0x32:
7876                  return array('rgb' => '488436');
7877              case 0x33:
7878                  return array('rgb' => '958C41');
7879              case 0x34:
7880                  return array('rgb' => '8E5E42');
7881              case 0x35:
7882                  return array('rgb' => 'A0627A');
7883              case 0x36:
7884                  return array('rgb' => '624FAC');
7885              case 0x37:
7886                  return array('rgb' => '969696');
7887              case 0x38:
7888                  return array('rgb' => '1D2FBE');
7889              case 0x39:
7890                  return array('rgb' => '286676');
7891              case 0x3A:
7892                  return array('rgb' => '004500');
7893              case 0x3B:
7894                  return array('rgb' => '453E01');
7895              case 0x3C:
7896                  return array('rgb' => '6A2813');
7897              case 0x3D:
7898                  return array('rgb' => '85396A');
7899              case 0x3E:
7900                  return array('rgb' => '4A3285');
7901              case 0x3F:
7902                  return array('rgb' => '424242');
7903              default:
7904                  return array('rgb' => '000000');
7905          }
7906      }
7907  
7908  
7909      /**
7910       * Map color array from BIFF8 built-in color index
7911       *
7912       * @param int $subData
7913       * @return array
7914       */
7915      private static function mapColor($subData)
7916      {
7917          switch ($subData) {
7918              case 0x08:
7919                  return array('rgb' => '000000');
7920              case 0x09:
7921                  return array('rgb' => 'FFFFFF');
7922              case 0x0A:
7923                  return array('rgb' => 'FF0000');
7924              case 0x0B:
7925                  return array('rgb' => '00FF00');
7926              case 0x0C:
7927                  return array('rgb' => '0000FF');
7928              case 0x0D:
7929                  return array('rgb' => 'FFFF00');
7930              case 0x0E:
7931                  return array('rgb' => 'FF00FF');
7932              case 0x0F:
7933                  return array('rgb' => '00FFFF');
7934              case 0x10:
7935                  return array('rgb' => '800000');
7936              case 0x11:
7937                  return array('rgb' => '008000');
7938              case 0x12:
7939                  return array('rgb' => '000080');
7940              case 0x13:
7941                  return array('rgb' => '808000');
7942              case 0x14:
7943                  return array('rgb' => '800080');
7944              case 0x15:
7945                  return array('rgb' => '008080');
7946              case 0x16:
7947                  return array('rgb' => 'C0C0C0');
7948              case 0x17:
7949                  return array('rgb' => '808080');
7950              case 0x18:
7951                  return array('rgb' => '9999FF');
7952              case 0x19:
7953                  return array('rgb' => '993366');
7954              case 0x1A:
7955                  return array('rgb' => 'FFFFCC');
7956              case 0x1B:
7957                  return array('rgb' => 'CCFFFF');
7958              case 0x1C:
7959                  return array('rgb' => '660066');
7960              case 0x1D:
7961                  return array('rgb' => 'FF8080');
7962              case 0x1E:
7963                  return array('rgb' => '0066CC');
7964              case 0x1F:
7965                  return array('rgb' => 'CCCCFF');
7966              case 0x20:
7967                  return array('rgb' => '000080');
7968              case 0x21:
7969                  return array('rgb' => 'FF00FF');
7970              case 0x22:
7971                  return array('rgb' => 'FFFF00');
7972              case 0x23:
7973                  return array('rgb' => '00FFFF');
7974              case 0x24:
7975                  return array('rgb' => '800080');
7976              case 0x25:
7977                  return array('rgb' => '800000');
7978              case 0x26:
7979                  return array('rgb' => '008080');
7980              case 0x27:
7981                  return array('rgb' => '0000FF');
7982              case 0x28:
7983                  return array('rgb' => '00CCFF');
7984              case 0x29:
7985                  return array('rgb' => 'CCFFFF');
7986              case 0x2A:
7987                  return array('rgb' => 'CCFFCC');
7988              case 0x2B:
7989                  return array('rgb' => 'FFFF99');
7990              case 0x2C:
7991                  return array('rgb' => '99CCFF');
7992              case 0x2D:
7993                  return array('rgb' => 'FF99CC');
7994              case 0x2E:
7995                  return array('rgb' => 'CC99FF');
7996              case 0x2F:
7997                  return array('rgb' => 'FFCC99');
7998              case 0x30:
7999                  return array('rgb' => '3366FF');
8000              case 0x31:
8001                  return array('rgb' => '33CCCC');
8002              case 0x32:
8003                  return array('rgb' => '99CC00');
8004              case 0x33:
8005                  return array('rgb' => 'FFCC00');
8006              case 0x34:
8007                  return array('rgb' => 'FF9900');
8008              case 0x35:
8009                  return array('rgb' => 'FF6600');
8010              case 0x36:
8011                  return array('rgb' => '666699');
8012              case 0x37:
8013                  return array('rgb' => '969696');
8014              case 0x38:
8015                  return array('rgb' => '003366');
8016              case 0x39:
8017                  return array('rgb' => '339966');
8018              case 0x3A:
8019                  return array('rgb' => '003300');
8020              case 0x3B:
8021                  return array('rgb' => '333300');
8022              case 0x3C:
8023                  return array('rgb' => '993300');
8024              case 0x3D:
8025                  return array('rgb' => '993366');
8026              case 0x3E:
8027                  return array('rgb' => '333399');
8028              case 0x3F:
8029                  return array('rgb' => '333333');
8030              default:
8031                  return array('rgb' => '000000');
8032          }
8033      }
8034  
8035      private function parseRichText($is = '')
8036      {
8037          $value = new PHPExcel_RichText();
8038          $value->createText($is);
8039  
8040          return $value;
8041      }
8042  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1