[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |