PageRenderTime 54ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 1ms

/Classes/PHPExcel/Reader/Excel5.php

https://github.com/iGroup/PHPExcel
PHP | 6771 lines | 3844 code | 1123 blank | 1804 comment | 502 complexity | c57009d79814586976c523f55568e084 MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * PHPExcel
  4. *
  5. * Copyright (c) 2006 - 2012 PHPExcel
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  20. *
  21. * @category PHPExcel
  22. * @package PHPExcel_Reader_Excel5
  23. * @copyright Copyright (c) 2006 - 2012 PHPExcel (http://www.codeplex.com/PHPExcel)
  24. * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
  25. * @version ##VERSION##, ##DATE##
  26. */
  27. // Original file header of ParseXL (used as the base for this class):
  28. // --------------------------------------------------------------------------------
  29. // Adapted from Excel_Spreadsheet_Reader developed by users bizon153,
  30. // trex005, and mmp11 (SourceForge.net)
  31. // http://sourceforge.net/projects/phpexcelreader/
  32. // Primary changes made by canyoncasa (dvc) for ParseXL 1.00 ...
  33. // Modelled moreso after Perl Excel Parse/Write modules
  34. // Added Parse_Excel_Spreadsheet object
  35. // Reads a whole worksheet or tab as row,column array or as
  36. // associated hash of indexed rows and named column fields
  37. // Added variables for worksheet (tab) indexes and names
  38. // Added an object call for loading individual woorksheets
  39. // Changed default indexing defaults to 0 based arrays
  40. // Fixed date/time and percent formats
  41. // Includes patches found at SourceForge...
  42. // unicode patch by nobody
  43. // unpack("d") machine depedency patch by matchy
  44. // boundsheet utf16 patch by bjaenichen
  45. // Renamed functions for shorter names
  46. // General code cleanup and rigor, including <80 column width
  47. // Included a testcase Excel file and PHP example calls
  48. // Code works for PHP 5.x
  49. // Primary changes made by canyoncasa (dvc) for ParseXL 1.10 ...
  50. // http://sourceforge.net/tracker/index.php?func=detail&aid=1466964&group_id=99160&atid=623334
  51. // Decoding of formula conditions, results, and tokens.
  52. // Support for user-defined named cells added as an array "namedcells"
  53. // Patch code for user-defined named cells supports single cells only.
  54. // NOTE: this patch only works for BIFF8 as BIFF5-7 use a different
  55. // external sheet reference structure
  56. /** PHPExcel root directory */
  57. if (!defined('PHPEXCEL_ROOT')) {
  58. /**
  59. * @ignore
  60. */
  61. define('PHPEXCEL_ROOT', dirname(__FILE__) . '/../../');
  62. require(PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php');
  63. }
  64. /**
  65. * PHPExcel_Reader_Excel5
  66. *
  67. * This class uses {@link http://sourceforge.net/projects/phpexcelreader/parseXL}
  68. *
  69. * @category PHPExcel
  70. * @package PHPExcel_Reader_Excel5
  71. * @copyright Copyright (c) 2006 - 2012 PHPExcel (http://www.codeplex.com/PHPExcel)
  72. */
  73. class PHPExcel_Reader_Excel5 extends PHPExcel_Reader_Abstract implements PHPExcel_Reader_IReader
  74. {
  75. // ParseXL definitions
  76. const XLS_BIFF8 = 0x0600;
  77. const XLS_BIFF7 = 0x0500;
  78. const XLS_WorkbookGlobals = 0x0005;
  79. const XLS_Worksheet = 0x0010;
  80. // record identifiers
  81. const XLS_Type_FORMULA = 0x0006;
  82. const XLS_Type_EOF = 0x000a;
  83. const XLS_Type_PROTECT = 0x0012;
  84. const XLS_Type_OBJECTPROTECT = 0x0063;
  85. const XLS_Type_SCENPROTECT = 0x00dd;
  86. const XLS_Type_PASSWORD = 0x0013;
  87. const XLS_Type_HEADER = 0x0014;
  88. const XLS_Type_FOOTER = 0x0015;
  89. const XLS_Type_EXTERNSHEET = 0x0017;
  90. const XLS_Type_DEFINEDNAME = 0x0018;
  91. const XLS_Type_VERTICALPAGEBREAKS = 0x001a;
  92. const XLS_Type_HORIZONTALPAGEBREAKS = 0x001b;
  93. const XLS_Type_NOTE = 0x001c;
  94. const XLS_Type_SELECTION = 0x001d;
  95. const XLS_Type_DATEMODE = 0x0022;
  96. const XLS_Type_EXTERNNAME = 0x0023;
  97. const XLS_Type_LEFTMARGIN = 0x0026;
  98. const XLS_Type_RIGHTMARGIN = 0x0027;
  99. const XLS_Type_TOPMARGIN = 0x0028;
  100. const XLS_Type_BOTTOMMARGIN = 0x0029;
  101. const XLS_Type_PRINTGRIDLINES = 0x002b;
  102. const XLS_Type_FILEPASS = 0x002f;
  103. const XLS_Type_FONT = 0x0031;
  104. const XLS_Type_CONTINUE = 0x003c;
  105. const XLS_Type_PANE = 0x0041;
  106. const XLS_Type_CODEPAGE = 0x0042;
  107. const XLS_Type_DEFCOLWIDTH = 0x0055;
  108. const XLS_Type_OBJ = 0x005d;
  109. const XLS_Type_COLINFO = 0x007d;
  110. const XLS_Type_IMDATA = 0x007f;
  111. const XLS_Type_SHEETPR = 0x0081;
  112. const XLS_Type_HCENTER = 0x0083;
  113. const XLS_Type_VCENTER = 0x0084;
  114. const XLS_Type_SHEET = 0x0085;
  115. const XLS_Type_PALETTE = 0x0092;
  116. const XLS_Type_SCL = 0x00a0;
  117. const XLS_Type_PAGESETUP = 0x00a1;
  118. const XLS_Type_MULRK = 0x00bd;
  119. const XLS_Type_MULBLANK = 0x00be;
  120. const XLS_Type_DBCELL = 0x00d7;
  121. const XLS_Type_XF = 0x00e0;
  122. const XLS_Type_MERGEDCELLS = 0x00e5;
  123. const XLS_Type_MSODRAWINGGROUP = 0x00eb;
  124. const XLS_Type_MSODRAWING = 0x00ec;
  125. const XLS_Type_SST = 0x00fc;
  126. const XLS_Type_LABELSST = 0x00fd;
  127. const XLS_Type_EXTSST = 0x00ff;
  128. const XLS_Type_EXTERNALBOOK = 0x01ae;
  129. const XLS_Type_DATAVALIDATIONS = 0x01b2;
  130. const XLS_Type_TXO = 0x01b6;
  131. const XLS_Type_HYPERLINK = 0x01b8;
  132. const XLS_Type_DATAVALIDATION = 0x01be;
  133. const XLS_Type_DIMENSION = 0x0200;
  134. const XLS_Type_BLANK = 0x0201;
  135. const XLS_Type_NUMBER = 0x0203;
  136. const XLS_Type_LABEL = 0x0204;
  137. const XLS_Type_BOOLERR = 0x0205;
  138. const XLS_Type_STRING = 0x0207;
  139. const XLS_Type_ROW = 0x0208;
  140. const XLS_Type_INDEX = 0x020b;
  141. const XLS_Type_ARRAY = 0x0221;
  142. const XLS_Type_DEFAULTROWHEIGHT = 0x0225;
  143. const XLS_Type_WINDOW2 = 0x023e;
  144. const XLS_Type_RK = 0x027e;
  145. const XLS_Type_STYLE = 0x0293;
  146. const XLS_Type_FORMAT = 0x041e;
  147. const XLS_Type_SHAREDFMLA = 0x04bc;
  148. const XLS_Type_BOF = 0x0809;
  149. const XLS_Type_SHEETPROTECTION = 0x0867;
  150. const XLS_Type_RANGEPROTECTION = 0x0868;
  151. const XLS_Type_SHEETLAYOUT = 0x0862;
  152. const XLS_Type_XFEXT = 0x087d;
  153. const XLS_Type_UNKNOWN = 0xffff;
  154. /**
  155. * Summary Information stream data.
  156. *
  157. * @var string
  158. */
  159. private $_summaryInformation;
  160. /**
  161. * Extended Summary Information stream data.
  162. *
  163. * @var string
  164. */
  165. private $_documentSummaryInformation;
  166. /**
  167. * User-Defined Properties stream data.
  168. *
  169. * @var string
  170. */
  171. private $_userDefinedProperties;
  172. /**
  173. * Workbook stream data. (Includes workbook globals substream as well as sheet substreams)
  174. *
  175. * @var string
  176. */
  177. private $_data;
  178. /**
  179. * Size in bytes of $this->_data
  180. *
  181. * @var int
  182. */
  183. private $_dataSize;
  184. /**
  185. * Current position in stream
  186. *
  187. * @var integer
  188. */
  189. private $_pos;
  190. /**
  191. * Workbook to be returned by the reader.
  192. *
  193. * @var PHPExcel
  194. */
  195. private $_phpExcel;
  196. /**
  197. * Worksheet that is currently being built by the reader.
  198. *
  199. * @var PHPExcel_Worksheet
  200. */
  201. private $_phpSheet;
  202. /**
  203. * BIFF version
  204. *
  205. * @var int
  206. */
  207. private $_version;
  208. /**
  209. * Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95)
  210. * For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'
  211. *
  212. * @var string
  213. */
  214. private $_codepage;
  215. /**
  216. * Shared formats
  217. *
  218. * @var array
  219. */
  220. private $_formats;
  221. /**
  222. * Shared fonts
  223. *
  224. * @var array
  225. */
  226. private $_objFonts;
  227. /**
  228. * Color palette
  229. *
  230. * @var array
  231. */
  232. private $_palette;
  233. /**
  234. * Worksheets
  235. *
  236. * @var array
  237. */
  238. private $_sheets;
  239. /**
  240. * External books
  241. *
  242. * @var array
  243. */
  244. private $_externalBooks;
  245. /**
  246. * REF structures. Only applies to BIFF8.
  247. *
  248. * @var array
  249. */
  250. private $_ref;
  251. /**
  252. * External names
  253. *
  254. * @var array
  255. */
  256. private $_externalNames;
  257. /**
  258. * Defined names
  259. *
  260. * @var array
  261. */
  262. private $_definedname;
  263. /**
  264. * Shared strings. Only applies to BIFF8.
  265. *
  266. * @var array
  267. */
  268. private $_sst;
  269. /**
  270. * Panes are frozen? (in sheet currently being read). See WINDOW2 record.
  271. *
  272. * @var boolean
  273. */
  274. private $_frozen;
  275. /**
  276. * Fit printout to number of pages? (in sheet currently being read). See SHEETPR record.
  277. *
  278. * @var boolean
  279. */
  280. private $_isFitToPages;
  281. /**
  282. * Objects. One OBJ record contributes with one entry.
  283. *
  284. * @var array
  285. */
  286. private $_objs;
  287. /**
  288. * Text Objects. One TXO record corresponds with one entry.
  289. *
  290. * @var array
  291. */
  292. private $_textObjects;
  293. /**
  294. * Cell Annotations (BIFF8)
  295. *
  296. * @var array
  297. */
  298. private $_cellNotes;
  299. /**
  300. * The combined MSODRAWINGGROUP data
  301. *
  302. * @var string
  303. */
  304. private $_drawingGroupData;
  305. /**
  306. * The combined MSODRAWING data (per sheet)
  307. *
  308. * @var string
  309. */
  310. private $_drawingData;
  311. /**
  312. * Keep track of XF index
  313. *
  314. * @var int
  315. */
  316. private $_xfIndex;
  317. /**
  318. * Mapping of XF index (that is a cell XF) to final index in cellXf collection
  319. *
  320. * @var array
  321. */
  322. private $_mapCellXfIndex;
  323. /**
  324. * Mapping of XF index (that is a style XF) to final index in cellStyleXf collection
  325. *
  326. * @var array
  327. */
  328. private $_mapCellStyleXfIndex;
  329. /**
  330. * The shared formulas in a sheet. One SHAREDFMLA record contributes with one value.
  331. *
  332. * @var array
  333. */
  334. private $_sharedFormulas;
  335. /**
  336. * The shared formula parts in a sheet. One FORMULA record contributes with one value if it
  337. * refers to a shared formula.
  338. *
  339. * @var array
  340. */
  341. private $_sharedFormulaParts;
  342. /**
  343. * Create a new PHPExcel_Reader_Excel5 instance
  344. */
  345. public function __construct() {
  346. $this->_readFilter = new PHPExcel_Reader_DefaultReadFilter();
  347. }
  348. /**
  349. * Can the current PHPExcel_Reader_IReader read the file?
  350. *
  351. * @param string $pFileName
  352. * @return boolean
  353. * @throws PHPExcel_Reader_Exception
  354. */
  355. public function canRead($pFilename)
  356. {
  357. // Check if file exists
  358. if (!file_exists($pFilename)) {
  359. throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist.");
  360. }
  361. try {
  362. // Use ParseXL for the hard work.
  363. $ole = new PHPExcel_Shared_OLERead();
  364. // get excel data
  365. $res = $ole->read($pFilename);
  366. return true;
  367. } catch (PHPExcel_Reader_Exception $e) {
  368. return false;
  369. }
  370. }
  371. /**
  372. * Reads names of the worksheets from a file, without parsing the whole file to a PHPExcel object
  373. *
  374. * @param string $pFilename
  375. * @throws PHPExcel_Reader_Exception
  376. */
  377. public function listWorksheetNames($pFilename)
  378. {
  379. // Check if file exists
  380. if (!file_exists($pFilename)) {
  381. throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist.");
  382. }
  383. $worksheetNames = array();
  384. // Read the OLE file
  385. $this->_loadOLE($pFilename);
  386. // total byte size of Excel data (workbook global substream + sheet substreams)
  387. $this->_dataSize = strlen($this->_data);
  388. $this->_pos = 0;
  389. $this->_sheets = array();
  390. // Parse Workbook Global Substream
  391. while ($this->_pos < $this->_dataSize) {
  392. $code = self::_GetInt2d($this->_data, $this->_pos);
  393. switch ($code) {
  394. case self::XLS_Type_BOF: $this->_readBof(); break;
  395. case self::XLS_Type_SHEET: $this->_readSheet(); break;
  396. case self::XLS_Type_EOF: $this->_readDefault(); break 2;
  397. default: $this->_readDefault(); break;
  398. }
  399. }
  400. foreach ($this->_sheets as $sheet) {
  401. if ($sheet['sheetType'] != 0x00) {
  402. // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
  403. continue;
  404. }
  405. $worksheetNames[] = $sheet['name'];
  406. }
  407. return $worksheetNames;
  408. }
  409. /**
  410. * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns)
  411. *
  412. * @param string $pFilename
  413. * @throws PHPExcel_Reader_Exception
  414. */
  415. public function listWorksheetInfo($pFilename)
  416. {
  417. // Check if file exists
  418. if (!file_exists($pFilename)) {
  419. throw new PHPExcel_Reader_Exception("Could not open " . $pFilename . " for reading! File does not exist.");
  420. }
  421. $worksheetInfo = array();
  422. // Read the OLE file
  423. $this->_loadOLE($pFilename);
  424. // total byte size of Excel data (workbook global substream + sheet substreams)
  425. $this->_dataSize = strlen($this->_data);
  426. // initialize
  427. $this->_pos = 0;
  428. $this->_sheets = array();
  429. // Parse Workbook Global Substream
  430. while ($this->_pos < $this->_dataSize) {
  431. $code = self::_GetInt2d($this->_data, $this->_pos);
  432. switch ($code) {
  433. case self::XLS_Type_BOF: $this->_readBof(); break;
  434. case self::XLS_Type_SHEET: $this->_readSheet(); break;
  435. case self::XLS_Type_EOF: $this->_readDefault(); break 2;
  436. default: $this->_readDefault(); break;
  437. }
  438. }
  439. // Parse the individual sheets
  440. foreach ($this->_sheets as $sheet) {
  441. if ($sheet['sheetType'] != 0x00) {
  442. // 0x00: Worksheet
  443. // 0x02: Chart
  444. // 0x06: Visual Basic module
  445. continue;
  446. }
  447. $tmpInfo = array();
  448. $tmpInfo['worksheetName'] = $sheet['name'];
  449. $tmpInfo['lastColumnLetter'] = 'A';
  450. $tmpInfo['lastColumnIndex'] = 0;
  451. $tmpInfo['totalRows'] = 0;
  452. $tmpInfo['totalColumns'] = 0;
  453. $this->_pos = $sheet['offset'];
  454. while ($this->_pos <= $this->_dataSize - 4) {
  455. $code = self::_GetInt2d($this->_data, $this->_pos);
  456. switch ($code) {
  457. case self::XLS_Type_RK:
  458. case self::XLS_Type_LABELSST:
  459. case self::XLS_Type_NUMBER:
  460. case self::XLS_Type_FORMULA:
  461. case self::XLS_Type_BOOLERR:
  462. case self::XLS_Type_LABEL:
  463. $length = self::_GetInt2d($this->_data, $this->_pos + 2);
  464. $recordData = substr($this->_data, $this->_pos + 4, $length);
  465. // move stream pointer to next record
  466. $this->_pos += 4 + $length;
  467. $rowIndex = self::_GetInt2d($recordData, 0) + 1;
  468. $columnIndex = self::_GetInt2d($recordData, 2);
  469. $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
  470. $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
  471. break;
  472. case self::XLS_Type_BOF: $this->_readBof(); break;
  473. case self::XLS_Type_EOF: $this->_readDefault(); break 2;
  474. default: $this->_readDefault(); break;
  475. }
  476. }
  477. $tmpInfo['lastColumnLetter'] = PHPExcel_Cell::stringFromColumnIndex($tmpInfo['lastColumnIndex']);
  478. $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
  479. $worksheetInfo[] = $tmpInfo;
  480. }
  481. return $worksheetInfo;
  482. }
  483. /**
  484. * Loads PHPExcel from file
  485. *
  486. * @param string $pFilename
  487. * @return PHPExcel
  488. * @throws PHPExcel_Reader_Exception
  489. */
  490. public function load($pFilename)
  491. {
  492. // Read the OLE file
  493. $this->_loadOLE($pFilename);
  494. // Initialisations
  495. $this->_phpExcel = new PHPExcel;
  496. $this->_phpExcel->removeSheetByIndex(0); // remove 1st sheet
  497. if (!$this->_readDataOnly) {
  498. $this->_phpExcel->removeCellStyleXfByIndex(0); // remove the default style
  499. $this->_phpExcel->removeCellXfByIndex(0); // remove the default style
  500. }
  501. // Read the summary information stream (containing meta data)
  502. $this->_readSummaryInformation();
  503. // Read the Additional document summary information stream (containing application-specific meta data)
  504. $this->_readDocumentSummaryInformation();
  505. // total byte size of Excel data (workbook global substream + sheet substreams)
  506. $this->_dataSize = strlen($this->_data);
  507. // initialize
  508. $this->_pos = 0;
  509. $this->_codepage = 'CP1252';
  510. $this->_formats = array();
  511. $this->_objFonts = array();
  512. $this->_palette = array();
  513. $this->_sheets = array();
  514. $this->_externalBooks = array();
  515. $this->_ref = array();
  516. $this->_definedname = array();
  517. $this->_sst = array();
  518. $this->_drawingGroupData = '';
  519. $this->_xfIndex = '';
  520. $this->_mapCellXfIndex = array();
  521. $this->_mapCellStyleXfIndex = array();
  522. // Parse Workbook Global Substream
  523. while ($this->_pos < $this->_dataSize) {
  524. $code = self::_GetInt2d($this->_data, $this->_pos);
  525. switch ($code) {
  526. case self::XLS_Type_BOF: $this->_readBof(); break;
  527. case self::XLS_Type_FILEPASS: $this->_readFilepass(); break;
  528. case self::XLS_Type_CODEPAGE: $this->_readCodepage(); break;
  529. case self::XLS_Type_DATEMODE: $this->_readDateMode(); break;
  530. case self::XLS_Type_FONT: $this->_readFont(); break;
  531. case self::XLS_Type_FORMAT: $this->_readFormat(); break;
  532. case self::XLS_Type_XF: $this->_readXf(); break;
  533. case self::XLS_Type_XFEXT: $this->_readXfExt(); break;
  534. case self::XLS_Type_STYLE: $this->_readStyle(); break;
  535. case self::XLS_Type_PALETTE: $this->_readPalette(); break;
  536. case self::XLS_Type_SHEET: $this->_readSheet(); break;
  537. case self::XLS_Type_EXTERNALBOOK: $this->_readExternalBook(); break;
  538. case self::XLS_Type_EXTERNNAME: $this->_readExternName(); break;
  539. case self::XLS_Type_EXTERNSHEET: $this->_readExternSheet(); break;
  540. case self::XLS_Type_DEFINEDNAME: $this->_readDefinedName(); break;
  541. case self::XLS_Type_MSODRAWINGGROUP: $this->_readMsoDrawingGroup(); break;
  542. case self::XLS_Type_SST: $this->_readSst(); break;
  543. case self::XLS_Type_EOF: $this->_readDefault(); break 2;
  544. default: $this->_readDefault(); break;
  545. }
  546. }
  547. // Resolve indexed colors for font, fill, and border colors
  548. // Cannot be resolved already in XF record, because PALETTE record comes afterwards
  549. if (!$this->_readDataOnly) {
  550. foreach ($this->_objFonts as $objFont) {
  551. if (isset($objFont->colorIndex)) {
  552. $color = self::_readColor($objFont->colorIndex,$this->_palette,$this->_version);
  553. $objFont->getColor()->setRGB($color['rgb']);
  554. }
  555. }
  556. foreach ($this->_phpExcel->getCellXfCollection() as $objStyle) {
  557. // fill start and end color
  558. $fill = $objStyle->getFill();
  559. if (isset($fill->startcolorIndex)) {
  560. $startColor = self::_readColor($fill->startcolorIndex,$this->_palette,$this->_version);
  561. $fill->getStartColor()->setRGB($startColor['rgb']);
  562. }
  563. if (isset($fill->endcolorIndex)) {
  564. $endColor = self::_readColor($fill->endcolorIndex,$this->_palette,$this->_version);
  565. $fill->getEndColor()->setRGB($endColor['rgb']);
  566. }
  567. // border colors
  568. $top = $objStyle->getBorders()->getTop();
  569. $right = $objStyle->getBorders()->getRight();
  570. $bottom = $objStyle->getBorders()->getBottom();
  571. $left = $objStyle->getBorders()->getLeft();
  572. $diagonal = $objStyle->getBorders()->getDiagonal();
  573. if (isset($top->colorIndex)) {
  574. $borderTopColor = self::_readColor($top->colorIndex,$this->_palette,$this->_version);
  575. $top->getColor()->setRGB($borderTopColor['rgb']);
  576. }
  577. if (isset($right->colorIndex)) {
  578. $borderRightColor = self::_readColor($right->colorIndex,$this->_palette,$this->_version);
  579. $right->getColor()->setRGB($borderRightColor['rgb']);
  580. }
  581. if (isset($bottom->colorIndex)) {
  582. $borderBottomColor = self::_readColor($bottom->colorIndex,$this->_palette,$this->_version);
  583. $bottom->getColor()->setRGB($borderBottomColor['rgb']);
  584. }
  585. if (isset($left->colorIndex)) {
  586. $borderLeftColor = self::_readColor($left->colorIndex,$this->_palette,$this->_version);
  587. $left->getColor()->setRGB($borderLeftColor['rgb']);
  588. }
  589. if (isset($diagonal->colorIndex)) {
  590. $borderDiagonalColor = self::_readColor($diagonal->colorIndex,$this->_palette,$this->_version);
  591. $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']);
  592. }
  593. }
  594. }
  595. // treat MSODRAWINGGROUP records, workbook-level Escher
  596. if (!$this->_readDataOnly && $this->_drawingGroupData) {
  597. $escherWorkbook = new PHPExcel_Shared_Escher();
  598. $reader = new PHPExcel_Reader_Excel5_Escher($escherWorkbook);
  599. $escherWorkbook = $reader->load($this->_drawingGroupData);
  600. // debug Escher stream
  601. //$debug = new Debug_Escher(new PHPExcel_Shared_Escher());
  602. //$debug->load($this->_drawingGroupData);
  603. }
  604. // Parse the individual sheets
  605. foreach ($this->_sheets as $sheet) {
  606. if ($sheet['sheetType'] != 0x00) {
  607. // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
  608. continue;
  609. }
  610. // check if sheet should be skipped
  611. if (isset($this->_loadSheetsOnly) && !in_array($sheet['name'], $this->_loadSheetsOnly)) {
  612. continue;
  613. }
  614. // add sheet to PHPExcel object
  615. $this->_phpSheet = $this->_phpExcel->createSheet();
  616. // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
  617. // cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
  618. // name in line with the formula, not the reverse
  619. $this->_phpSheet->setTitle($sheet['name'],false);
  620. $this->_phpSheet->setSheetState($sheet['sheetState']);
  621. $this->_pos = $sheet['offset'];
  622. // Initialize isFitToPages. May change after reading SHEETPR record.
  623. $this->_isFitToPages = false;
  624. // Initialize drawingData
  625. $this->_drawingData = '';
  626. // Initialize objs
  627. $this->_objs = array();
  628. // Initialize shared formula parts
  629. $this->_sharedFormulaParts = array();
  630. // Initialize shared formulas
  631. $this->_sharedFormulas = array();
  632. // Initialize text objs
  633. $this->_textObjects = array();
  634. // Initialize cell annotations
  635. $this->_cellNotes = array();
  636. $this->textObjRef = -1;
  637. while ($this->_pos <= $this->_dataSize - 4) {
  638. $code = self::_GetInt2d($this->_data, $this->_pos);
  639. switch ($code) {
  640. case self::XLS_Type_BOF: $this->_readBof(); break;
  641. case self::XLS_Type_PRINTGRIDLINES: $this->_readPrintGridlines(); break;
  642. case self::XLS_Type_DEFAULTROWHEIGHT: $this->_readDefaultRowHeight(); break;
  643. case self::XLS_Type_SHEETPR: $this->_readSheetPr(); break;
  644. case self::XLS_Type_HORIZONTALPAGEBREAKS: $this->_readHorizontalPageBreaks(); break;
  645. case self::XLS_Type_VERTICALPAGEBREAKS: $this->_readVerticalPageBreaks(); break;
  646. case self::XLS_Type_HEADER: $this->_readHeader(); break;
  647. case self::XLS_Type_FOOTER: $this->_readFooter(); break;
  648. case self::XLS_Type_HCENTER: $this->_readHcenter(); break;
  649. case self::XLS_Type_VCENTER: $this->_readVcenter(); break;
  650. case self::XLS_Type_LEFTMARGIN: $this->_readLeftMargin(); break;
  651. case self::XLS_Type_RIGHTMARGIN: $this->_readRightMargin(); break;
  652. case self::XLS_Type_TOPMARGIN: $this->_readTopMargin(); break;
  653. case self::XLS_Type_BOTTOMMARGIN: $this->_readBottomMargin(); break;
  654. case self::XLS_Type_PAGESETUP: $this->_readPageSetup(); break;
  655. case self::XLS_Type_PROTECT: $this->_readProtect(); break;
  656. case self::XLS_Type_SCENPROTECT: $this->_readScenProtect(); break;
  657. case self::XLS_Type_OBJECTPROTECT: $this->_readObjectProtect(); break;
  658. case self::XLS_Type_PASSWORD: $this->_readPassword(); break;
  659. case self::XLS_Type_DEFCOLWIDTH: $this->_readDefColWidth(); break;
  660. case self::XLS_Type_COLINFO: $this->_readColInfo(); break;
  661. case self::XLS_Type_DIMENSION: $this->_readDefault(); break;
  662. case self::XLS_Type_ROW: $this->_readRow(); break;
  663. case self::XLS_Type_DBCELL: $this->_readDefault(); break;
  664. case self::XLS_Type_RK: $this->_readRk(); break;
  665. case self::XLS_Type_LABELSST: $this->_readLabelSst(); break;
  666. case self::XLS_Type_MULRK: $this->_readMulRk(); break;
  667. case self::XLS_Type_NUMBER: $this->_readNumber(); break;
  668. case self::XLS_Type_FORMULA: $this->_readFormula(); break;
  669. case self::XLS_Type_SHAREDFMLA: $this->_readSharedFmla(); break;
  670. case self::XLS_Type_BOOLERR: $this->_readBoolErr(); break;
  671. case self::XLS_Type_MULBLANK: $this->_readMulBlank(); break;
  672. case self::XLS_Type_LABEL: $this->_readLabel(); break;
  673. case self::XLS_Type_BLANK: $this->_readBlank(); break;
  674. case self::XLS_Type_MSODRAWING: $this->_readMsoDrawing(); break;
  675. case self::XLS_Type_OBJ: $this->_readObj(); break;
  676. case self::XLS_Type_WINDOW2: $this->_readWindow2(); break;
  677. case self::XLS_Type_SCL: $this->_readScl(); break;
  678. case self::XLS_Type_PANE: $this->_readPane(); break;
  679. case self::XLS_Type_SELECTION: $this->_readSelection(); break;
  680. case self::XLS_Type_MERGEDCELLS: $this->_readMergedCells(); break;
  681. case self::XLS_Type_HYPERLINK: $this->_readHyperLink(); break;
  682. case self::XLS_Type_DATAVALIDATIONS: $this->_readDataValidations(); break;
  683. case self::XLS_Type_DATAVALIDATION: $this->_readDataValidation(); break;
  684. case self::XLS_Type_SHEETLAYOUT: $this->_readSheetLayout(); break;
  685. case self::XLS_Type_SHEETPROTECTION: $this->_readSheetProtection(); break;
  686. case self::XLS_Type_RANGEPROTECTION: $this->_readRangeProtection(); break;
  687. case self::XLS_Type_NOTE: $this->_readNote(); break;
  688. //case self::XLS_Type_IMDATA: $this->_readImData(); break;
  689. case self::XLS_Type_TXO: $this->_readTextObject(); break;
  690. case self::XLS_Type_CONTINUE: $this->_readContinue(); break;
  691. case self::XLS_Type_EOF: $this->_readDefault(); break 2;
  692. default: $this->_readDefault(); break;
  693. }
  694. }
  695. // treat MSODRAWING records, sheet-level Escher
  696. if (!$this->_readDataOnly && $this->_drawingData) {
  697. $escherWorksheet = new PHPExcel_Shared_Escher();
  698. $reader = new PHPExcel_Reader_Excel5_Escher($escherWorksheet);
  699. $escherWorksheet = $reader->load($this->_drawingData);
  700. // debug Escher stream
  701. //$debug = new Debug_Escher(new PHPExcel_Shared_Escher());
  702. //$debug->load($this->_drawingData);
  703. // get all spContainers in one long array, so they can be mapped to OBJ records
  704. $allSpContainers = $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers();
  705. }
  706. // treat OBJ records
  707. foreach ($this->_objs as $n => $obj) {
  708. // echo '<hr /><b>Object</b> reference is ',$n,'<br />';
  709. // var_dump($obj);
  710. // echo '<br />';
  711. // the first shape container never has a corresponding OBJ record, hence $n + 1
  712. if (isset($allSpContainers[$n + 1]) && is_object($allSpContainers[$n + 1])) {
  713. $spContainer = $allSpContainers[$n + 1];
  714. // we skip all spContainers that are a part of a group shape since we cannot yet handle those
  715. if ($spContainer->getNestingLevel() > 1) {
  716. continue;
  717. }
  718. // calculate the width and height of the shape
  719. list($startColumn, $startRow) = PHPExcel_Cell::coordinateFromString($spContainer->getStartCoordinates());
  720. list($endColumn, $endRow) = PHPExcel_Cell::coordinateFromString($spContainer->getEndCoordinates());
  721. $startOffsetX = $spContainer->getStartOffsetX();
  722. $startOffsetY = $spContainer->getStartOffsetY();
  723. $endOffsetX = $spContainer->getEndOffsetX();
  724. $endOffsetY = $spContainer->getEndOffsetY();
  725. $width = PHPExcel_Shared_Excel5::getDistanceX($this->_phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX);
  726. $height = PHPExcel_Shared_Excel5::getDistanceY($this->_phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY);
  727. // calculate offsetX and offsetY of the shape
  728. $offsetX = $startOffsetX * PHPExcel_Shared_Excel5::sizeCol($this->_phpSheet, $startColumn) / 1024;
  729. $offsetY = $startOffsetY * PHPExcel_Shared_Excel5::sizeRow($this->_phpSheet, $startRow) / 256;
  730. switch ($obj['otObjType']) {
  731. case 0x19:
  732. // Note
  733. // echo 'Cell Annotation Object<br />';
  734. // echo 'Object ID is ',$obj['idObjID'],'<br />';
  735. //
  736. if (isset($this->_cellNotes[$obj['idObjID']])) {
  737. $cellNote = $this->_cellNotes[$obj['idObjID']];
  738. if (isset($this->_textObjects[$obj['idObjID']])) {
  739. $textObject = $this->_textObjects[$obj['idObjID']];
  740. $this->_cellNotes[$obj['idObjID']]['objTextData'] = $textObject;
  741. }
  742. }
  743. break;
  744. case 0x08:
  745. // echo 'Picture Object<br />';
  746. // picture
  747. // get index to BSE entry (1-based)
  748. $BSEindex = $spContainer->getOPT(0x0104);
  749. $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection();
  750. $BSE = $BSECollection[$BSEindex - 1];
  751. $blipType = $BSE->getBlipType();
  752. // need check because some blip types are not supported by Escher reader such as EMF
  753. if ($blip = $BSE->getBlip()) {
  754. $ih = imagecreatefromstring($blip->getData());
  755. $drawing = new PHPExcel_Worksheet_MemoryDrawing();
  756. $drawing->setImageResource($ih);
  757. // width, height, offsetX, offsetY
  758. $drawing->setResizeProportional(false);
  759. $drawing->setWidth($width);
  760. $drawing->setHeight($height);
  761. $drawing->setOffsetX($offsetX);
  762. $drawing->setOffsetY($offsetY);
  763. switch ($blipType) {
  764. case PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_JPEG:
  765. $drawing->setRenderingFunction(PHPExcel_Worksheet_MemoryDrawing::RENDERING_JPEG);
  766. $drawing->setMimeType(PHPExcel_Worksheet_MemoryDrawing::MIMETYPE_JPEG);
  767. break;
  768. case PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG:
  769. $drawing->setRenderingFunction(PHPExcel_Worksheet_MemoryDrawing::RENDERING_PNG);
  770. $drawing->setMimeType(PHPExcel_Worksheet_MemoryDrawing::MIMETYPE_PNG);
  771. break;
  772. }
  773. $drawing->setWorksheet($this->_phpSheet);
  774. $drawing->setCoordinates($spContainer->getStartCoordinates());
  775. }
  776. break;
  777. default:
  778. // other object type
  779. break;
  780. }
  781. }
  782. }
  783. // treat SHAREDFMLA records
  784. if ($this->_version == self::XLS_BIFF8) {
  785. foreach ($this->_sharedFormulaParts as $cell => $baseCell) {
  786. list($column, $row) = PHPExcel_Cell::coordinateFromString($cell);
  787. if (($this->getReadFilter() !== NULL) && $this->getReadFilter()->readCell($column, $row, $this->_phpSheet->getTitle()) ) {
  788. $formula = $this->_getFormulaFromStructure($this->_sharedFormulas[$baseCell], $cell);
  789. $this->_phpSheet->getCell($cell)->setValueExplicit('=' . $formula, PHPExcel_Cell_DataType::TYPE_FORMULA);
  790. }
  791. }
  792. }
  793. if (!empty($this->_cellNotes)) {
  794. foreach($this->_cellNotes as $note => $noteDetails) {
  795. if (!isset($noteDetails['objTextData'])) {
  796. if (isset($this->_textObjects[$note])) {
  797. $textObject = $this->_textObjects[$note];
  798. $noteDetails['objTextData'] = $textObject;
  799. } else {
  800. $noteDetails['objTextData']['text'] = '';
  801. }
  802. }
  803. // echo '<b>Cell annotation ',$note,'</b><br />';
  804. // var_dump($noteDetails);
  805. // echo '<br />';
  806. $cellAddress = str_replace('$','',$noteDetails['cellRef']);
  807. $this->_phpSheet->getComment( $cellAddress )
  808. ->setAuthor( $noteDetails['author'] )
  809. ->setText($this->_parseRichText($noteDetails['objTextData']['text']) );
  810. }
  811. }
  812. }
  813. // add the named ranges (defined names)
  814. foreach ($this->_definedname as $definedName) {
  815. if ($definedName['isBuiltInName']) {
  816. switch ($definedName['name']) {
  817. case pack('C', 0x06):
  818. // print area
  819. // in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2
  820. $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
  821. $extractedRanges = array();
  822. foreach ($ranges as $range) {
  823. // $range should look like one of these
  824. // Foo!$C$7:$J$66
  825. // Bar!$A$1:$IV$2
  826. $explodes = explode('!', $range); // FIXME: what if sheetname contains exclamation mark?
  827. $sheetName = $explodes[0];
  828. if (count($explodes) == 2) {
  829. $extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66
  830. }
  831. }
  832. if ($docSheet = $this->_phpExcel->getSheetByName($sheetName)) {
  833. $docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2
  834. }
  835. break;
  836. case pack('C', 0x07):
  837. // print titles (repeating rows)
  838. // Assuming BIFF8, there are 3 cases
  839. // 1. repeating rows
  840. // formula looks like this: Sheet!$A$1:$IV$2
  841. // rows 1-2 repeat
  842. // 2. repeating columns
  843. // formula looks like this: Sheet!$A$1:$B$65536
  844. // columns A-B repeat
  845. // 3. both repeating rows and repeating columns
  846. // formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2
  847. $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
  848. foreach ($ranges as $range) {
  849. // $range should look like this one of these
  850. // Sheet!$A$1:$B$65536
  851. // Sheet!$A$1:$IV$2
  852. $explodes = explode('!', $range);
  853. if (count($explodes) == 2) {
  854. if ($docSheet = $this->_phpExcel->getSheetByName($explodes[0])) {
  855. $extractedRange = $explodes[1];
  856. $extractedRange = str_replace('$', '', $extractedRange);
  857. $coordinateStrings = explode(':', $extractedRange);
  858. if (count($coordinateStrings) == 2) {
  859. list($firstColumn, $firstRow) = PHPExcel_Cell::coordinateFromString($coordinateStrings[0]);
  860. list($lastColumn, $lastRow) = PHPExcel_Cell::coordinateFromString($coordinateStrings[1]);
  861. if ($firstColumn == 'A' and $lastColumn == 'IV') {
  862. // then we have repeating rows
  863. $docSheet->getPageSetup()->setRowsToRepeatAtTop(array($firstRow, $lastRow));
  864. } elseif ($firstRow == 1 and $lastRow == 65536) {
  865. // then we have repeating columns
  866. $docSheet->getPageSetup()->setColumnsToRepeatAtLeft(array($firstColumn, $lastColumn));
  867. }
  868. }
  869. }
  870. }
  871. }
  872. break;
  873. }
  874. } else {
  875. // Extract range
  876. $explodes = explode('!', $definedName['formula']);
  877. if (count($explodes) == 2) {
  878. if (($docSheet = $this->_phpExcel->getSheetByName($explodes[0])) ||
  879. ($docSheet = $this->_phpExcel->getSheetByName(trim($explodes[0],"'")))) {
  880. $extractedRange = $explodes[1];
  881. $extractedRange = str_replace('$', '', $extractedRange);
  882. $localOnly = ($definedName['scope'] == 0) ? false : true;
  883. $scope = ($definedName['scope'] == 0) ?
  884. null : $this->_phpExcel->getSheetByName($this->_sheets[$definedName['scope'] - 1]['name']);
  885. $this->_phpExcel->addNamedRange( new PHPExcel_NamedRange((string)$definedName['name'], $docSheet, $extractedRange, $localOnly, $scope) );
  886. }
  887. } else {
  888. // Named Value
  889. // TODO Provide support for named values
  890. }
  891. }
  892. }
  893. return $this->_phpExcel;
  894. }
  895. /**
  896. * Use OLE reader to extract the relevant data streams from the OLE file
  897. *
  898. * @param string $pFilename
  899. */
  900. private function _loadOLE($pFilename)
  901. {
  902. // OLE reader
  903. $ole = new PHPExcel_Shared_OLERead();
  904. // get excel data,
  905. $res = $ole->read($pFilename);
  906. // Get workbook data: workbook stream + sheet streams
  907. $this->_data = $ole->getStream($ole->wrkbook);
  908. // Get summary information data
  909. $this->_summaryInformation = $ole->getStream($ole->summaryInformation);
  910. // Get additional document summary information data
  911. $this->_documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation);
  912. // Get user-defined property data
  913. // $this->_userDefinedProperties = $ole->getUserDefinedProperties();
  914. }
  915. /**
  916. * Read summary information
  917. */
  918. private function _readSummaryInformation()
  919. {
  920. if (!isset($this->_summaryInformation)) {
  921. return;
  922. }
  923. // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
  924. // offset: 2; size: 2;
  925. // offset: 4; size: 2; OS version
  926. // offset: 6; size: 2; OS indicator
  927. // offset: 8; size: 16
  928. // offset: 24; size: 4; section count
  929. $secCount = self::_GetInt4d($this->_summaryInformation, 24);
  930. // 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
  931. // offset: 44; size: 4
  932. $secOffset = self::_GetInt4d($this->_summaryInformation, 44);
  933. // section header
  934. // offset: $secOffset; size: 4; section length
  935. $secLength = self::_GetInt4d($this->_summaryInformation, $secOffset);
  936. // offset: $secOffset+4; size: 4; property count
  937. $countProperties = self::_GetInt4d($this->_summaryInformation, $secOffset+4);
  938. // initialize code page (used to resolve string values)
  939. $codePage = 'CP1252';
  940. // offset: ($secOffset+8); size: var
  941. // loop through property decarations and properties
  942. for ($i = 0; $i < $countProperties; ++$i) {
  943. // offset: ($secOffset+8) + (8 * $i); size: 4; property ID
  944. $id = self::_GetInt4d($this->_summaryInformation, ($secOffset+8) + (8 * $i));
  945. // Use value of property id as appropriate
  946. // offset: ($secOffset+12) + (8 * $i); size: 4; offset from beginning of section (48)
  947. $offset = self::_GetInt4d($this->_summaryInformation, ($secOffset+12) + (8 * $i));
  948. $type = self::_GetInt4d($this->_summaryInformation, $secOffset + $offset);
  949. // initialize property value
  950. $value = null;
  951. // extract property value based on property type
  952. switch ($type) {
  953. case 0x02: // 2 byte signed integer
  954. $value = self::_GetInt2d($this->_summaryInformation, $secOffset + 4 + $offset);
  955. break;
  956. case 0x03: // 4 byte signed integer
  957. $value = self::_GetInt4d($this->_summaryInformation, $secOffset + 4 + $offset);
  958. break;
  959. case 0x13: // 4 byte unsigned integer
  960. // not needed yet, fix later if necessary
  961. break;
  962. case 0x1E: // null-terminated string prepended by dword string length
  963. $byteLength = self::_GetInt4d($this->_summaryInformation, $secOffset + 4 + $offset);
  964. $value = substr($this->_summaryInformation, $secOffset + 8 + $offset, $byteLength);
  965. $value = PHPExcel_Shared_String::ConvertEncoding($value, 'UTF-8', $codePage);
  966. $value = rtrim($value);
  967. break;
  968. case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
  969. // PHP-time
  970. $value = PHPExcel_Shared_OLE::OLE2LocalDate(substr($this->_summaryInformation, $secOffset + 4 + $offset, 8));
  971. break;
  972. case 0x47: // Clipboard format
  973. // not needed yet, fix later if necessary
  974. break;
  975. }
  976. switch ($id) {
  977. case 0x01: // Code Page
  978. $codePage = PHPExcel_Shared_CodePage::NumberToName($value);
  979. break;
  980. case 0x02: // Title
  981. $this->_phpExcel->getProperties()->setTitle($value);
  982. break;
  983. case 0x03: // Subject
  984. $this->_phpExcel->getProperties()->setSubject($value);
  985. break;
  986. case 0x04: // Author (Creator)
  987. $this->_phpExcel->getProperties()->setCreator($value);
  988. break;
  989. case 0x05: // Keywords
  990. $this->_phpExcel->getProperties()->setKeywords($value);
  991. break;
  992. case 0x06: // Comments (Description)
  993. $this->_phpExcel->getProperties()->setDescription($value);
  994. break;
  995. case 0x07: // Template
  996. // Not supported by PHPExcel
  997. break;
  998. case 0x08: // Last Saved By (LastModifiedBy)
  999. $this->_phpExcel->getProperties()->setLastModifiedBy($value);
  1000. break;
  1001. case 0x09: // Revision
  1002. // Not supported by PHPExcel
  1003. break;
  1004. case 0x0A: // Total Editing Time
  1005. // Not supported by PHPExcel
  1006. break;
  1007. case 0x0B: // Last Printed
  1008. // Not supported by PHPExcel
  1009. break;
  1010. case 0x0C: // Created Date/Time
  1011. $this->_phpExcel->getProperties()->setCreated($value);
  1012. break;
  1013. case 0x0D: // Modified Date/Time
  1014. $this->_phpExcel->getProperties()->setModified($value);
  1015. break;
  1016. case 0x0E: // Number of Pages
  1017. // Not supported by PHPExcel
  1018. break;
  1019. case 0x0F: // Number of Words
  1020. // Not supported by PHPExcel
  1021. break;
  1022. case 0x10: // Number of Characters
  1023. // Not supported by PHPExcel
  1024. break;
  1025. case 0x11: // Thumbnail
  1026. // Not supported by PHPExcel
  1027. break;
  1028. case 0x12: // Name of creating application
  1029. // Not supported by PHPExcel
  1030. break;
  1031. case 0x13: // Security
  1032. // Not supported by PHPExcel
  1033. break;
  1034. }
  1035. }
  1036. }
  1037. /**
  1038. * Read additional document summary information
  1039. */
  1040. private function _readDocumentSummaryInformation()
  1041. {
  1042. if (!isset($this->_documentSummaryInformation)) {
  1043. return;
  1044. }
  1045. // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
  1046. // offset: 2; size: 2;
  1047. // offset: 4; size: 2; OS version
  1048. // offset: 6; size: 2; OS indicator
  1049. // offset: 8; size: 16
  1050. // offset: 24; size: 4; section count
  1051. $secCount = self::_GetInt4d($this->_documentSummaryInformation, 24);
  1052. // echo '$secCount = ',$secCount,'<br />';
  1053. // 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
  1054. // offset: 44; size: 4; first section offset
  1055. $secOffset = self::_GetInt4d($this->_documentSummaryInformation, 44);
  1056. // echo '$secOffset = ',$secOffset,'<br />';
  1057. // section header
  1058. // offset: $secOffset; size: 4; section length
  1059. $secLength = self::_GetInt4d($this->_documentSummaryInformation, $secOffset);
  1060. // echo '$secLength = ',$secLength,'<br />';
  1061. // offset: $secOffset+4; size: 4; property count
  1062. $countProperties = self::_GetInt4d($this->_documentSummaryInformation, $secOffset+4);
  1063. // echo '$countProperties = ',$countProperties,'<br />';
  1064. // initialize code page (used to resolve string values)
  1065. $codePage = 'CP1252';
  1066. // offset: ($secOffset+8); size: var
  1067. // loop through property decarations and properties
  1068. for ($i = 0; $i < $countProperties; ++$i) {
  1069. // echo 'Property ',$i,'<br />';
  1070. // offset: ($secOffset+8) + (8 * $i); size: 4; property ID
  1071. $id = self::_GetInt4d($this->_documentSummaryInformation, ($secOffset+8) + (8 * $i));
  1072. // echo 'ID is ',$id,'<br />';
  1073. // Use value of property id as appropriate
  1074. // offset: 60 + 8 * $i; size: 4; offset from beginning of section (48)
  1075. $offset = self::_GetInt4d($this->_documentSummaryInformation, ($secOffset+12) + (8 * $i));
  1076. $type = self::_GetInt4d($this->_documentSummaryInformation, $secOffset + $offset);
  1077. // echo 'Type is ',$type,', ';
  1078. // initialize property value
  1079. $value = null;
  1080. // extract property value based on property type
  1081. switch ($type) {
  1082. case 0x02: // 2 byte signed integer
  1083. $value = self::_GetInt2d($this->_documentSummaryInformation, $secOffset + 4 + $offset);
  1084. break;
  1085. case 0x03: // 4 byte signed integer
  1086. $value = self::_GetInt4d($this->_documentSummaryInformation, $secOffset + 4 + $offset);
  1087. break;
  1088. case 0x0B: // Boolean
  1089. $value = self::_GetInt2d($this->_documentSummaryInformation, $secOffset + 4 + $offset);
  1090. $value = ($value == 0 ? false : true);
  1091. break;
  1092. case 0x13: // 4 byte unsigned integer
  1093. // not needed yet, fix later if necessary
  1094. break;
  1095. case 0x1E: // null-terminated string prepended by dword string length
  1096. $byteLength = self::_GetInt4d($this->_documentSummaryInformation, $secOffset + 4 + $offset);
  1097. $value = substr($this->_documentSummaryInformation, $secOffset + 8 + $offset, $byteLength);
  1098. $value = PHPExcel_Shared_String::ConvertEncoding($value, 'UTF-8', $codePage);
  1099. $value = rtrim($value);
  1100. break;
  1101. case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
  1102. // PHP-Time
  1103. $value = PHPExcel_Shared_OLE::OLE2LocalDate(substr($this->_documentSummaryInformation, $secOffset + 4 + $offset, 8));
  1104. break;
  1105. case 0x47: // Clipboard format
  1106. // not needed yet, fix later if necessary
  1107. break;
  1108. }
  1109. switch ($id) {
  1110. case 0x01: // Code Page
  1111. $codePage = PHPExcel_Shared_CodePage::NumberToName($value);
  1112. break;
  1113. case 0x02: // Category
  1114. $this->_phpExcel->getProperties()->setCategory($value);
  1115. break;
  1116. case 0x03: // Presentation Target
  1117. // Not supported by PHPExcel
  1118. break;
  1119. case 0x04: // Bytes
  1120. // Not supported by PHPExcel
  1121. break;
  1122. case 0x05: // Lines
  1123. // Not supported by PHPExcel
  1124. break;
  1125. case 0x06: // Paragraphs
  1126. // Not supported by PHPExcel
  1127. break;
  1128. case 0x07: // Slides
  1129. // Not supported by PHPExcel
  1130. break;
  1131. case 0x08: // Notes
  1132. // Not supported by PHPExcel
  1133. break;
  1134. case 0x09: // Hidden Slides
  1135. // Not supported by PHPExcel
  1136. break;
  1137. case 0x0A: // MM Clips
  1138. // Not supported by PHPExcel
  1139. break;
  1140. case 0x0B: // Scale Crop
  1141. // Not supported by PHPExcel
  1142. break;
  1143. case 0x0C: // Heading Pairs
  1144. // Not supported by PHPExcel
  1145. break;
  1146. case 0x0D: // Titles of Parts
  1147. // Not supported by PHPExcel
  1148. break;
  1149. case 0x0E: // Manager
  1150. $this->_phpExcel->getProperties()->setManager($value);
  1151. break;
  1152. case 0x0F: // Company
  1153. $this->_phpExcel->getProperties()->setCompany($value);
  1154. break;
  1155. case 0x10: // Links up-to-date
  1156. // Not supported by PHPExcel
  1157. break;
  1158. }
  1159. }
  1160. }
  1161. /**
  1162. * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record.
  1163. */
  1164. private function _readDefault()
  1165. {
  1166. $length = self::_GetInt2d($this->_data, $this->_pos + 2);
  1167. // $recordData = substr($this->_data, $this->_pos + 4, $length);
  1168. // move stream pointer to next record
  1169. $this->_pos += 4 + $length;
  1170. }
  1171. /**
  1172. * The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions,
  1173. * this record stores a note (cell note). This feature was significantly enhanced in Excel 97.
  1174. */
  1175. private function _readNote()
  1176. {
  1177. // echo '<b>Read Cell Annotation</b><br />';
  1178. $length = self::_GetInt2d($this->_data, $this->_pos + 2);
  1179. $recordData = substr($this->_data, $this->_pos + 4, $length);
  1180. // move stream pointer to next record
  1181. $this->_pos += 4 + $length;
  1182. if ($this->_readDataOnly) {
  1183. return;
  1184. }
  1185. $cellAddress = $this->_readBIFF8CellAddress(substr($recordData, 0, 4));
  1186. if ($this->_version == self::XLS_BIFF8) {
  1187. $noteObjID = self::_GetInt2d($recordData, 6);
  1188. $noteAuthor = self::_readUnicodeStringLong(substr($recordData, 8));
  1189. $noteAuthor = $noteAuthor['value'];
  1190. // echo 'Note Address=',$cellAddress,'<br />';
  1191. // echo 'Note Object ID=',$noteObjID,'<br />';
  1192. // echo 'Note Author=',$noteAuthor,'<hr />';
  1193. //
  1194. $this->_cellNotes[$noteObjID] = array('cellRef' => $cellAddress,
  1195. 'objectID' => $noteObjID,
  1196. 'author' => $noteAuthor
  1197. );
  1198. } else {
  1199. $extension = false;
  1200. if ($cellAddress == '$B$65536') {
  1201. // If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation
  1202. // note from the previous cell annotation. We're not yet handling this, so annotations longer than the
  1203. // max 2048 bytes will probably throw a wobbly.
  1204. $row = self::_GetInt2d($recordData, 0);
  1205. $extension = true;
  1206. $cellAddress = array_pop(array_keys($this->_phpSheet->getComments()));
  1207. }
  1208. // echo 'Note Address=',$cellAddress,'<br />';
  1209. $cellAddress = str_replace('$','',$cellAddress);
  1210. $noteLength = self::_GetInt2d($recordData, 4);
  1211. $noteText = trim(substr($recordData, 6));
  1212. // echo 'Note Length=',$noteLength,'<br />';
  1213. // echo 'Note Text=',$noteText,'<br />';
  1214. if ($extension) {
  1215. // Concatenate this extension with the currently set comment for the cell
  1216. $comment = $this->_phpSheet->getComment( $cellAddress );
  1217. $commentText = $comment->getText()->getPlainText();
  1218. $comment->setText($this->_parseRichText($commentText.$noteText) );
  1219. } else {
  1220. // Set comment for the cell
  1221. $this->_phpSheet->getComment( $cellAddress )
  1222. // ->setAuthor( $author )
  1223. ->setText($this->_parseRichText($noteText) );
  1224. }
  1225. }
  1226. }
  1227. /**
  1228. * The TEXT Object record contains the text associated with a cell annotation.
  1229. */
  1230. private function _readTextObject()
  1231. {
  1232. $length = self::_GetInt2d($this->_data, $this->_pos + 2);
  1233. $recordData = substr($this->_data, $this->_pos + 4, $length);
  1234. // move stream pointer to next record
  1235. $this->_pos += 4 + $length;
  1236. if ($this->_readDataOnly) {
  1237. return;
  1238. }
  1239. // recordData consists of an array of subrecords looking like this:
  1240. // grbit: 2 bytes; Option Flags
  1241. // rot: 2 bytes; rotation
  1242. // cchText: 2 bytes; length of the text (in the first continue record)
  1243. // cbRuns: 2 bytes; length of the formatting (in the second continue record)
  1244. // followed by the continuation records containing the actual text and formatting
  1245. $grbitOpts = self::_GetInt2d($recordData, 0);
  1246. $rot = self::_GetInt2d($recordData, 2);
  1247. $cchText = self::_GetInt2d($recordData, 10);
  1248. $cbRuns = self::_GetInt2d($recordData, 12);
  1249. $text = $this->_getSplicedRecordData();
  1250. $this->_textObjects[$this->textObjRef] = array(
  1251. 'text' => substr($text["recordData"],$text["spliceOffsets"][0]+1,$cchText),
  1252. 'format' =

Large files files are truncated, but you can click here to view the full file