For God so loved the world, that He gave His only begotten Son, that all who believe in Him should not perish but have everlasting life
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7594 lines
286 KiB

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