PageRenderTime 70ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/Vendor/SpreadsheetExcelReader/SpreadsheetExcelReader.php

https://github.com/Dismounted/tools
PHP | 1752 lines | 1532 code | 98 blank | 122 comment | 91 complexity | d53458ecb40a40c9c01ac3a8e9fef666 MD5 | raw file

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

  1. <?php
  2. /**
  3. * A class for reading Microsoft Excel (97/2003) Spreadsheets.
  4. *
  5. * Version 2.21
  6. *
  7. * Enhanced and maintained by Matt Kruse < http://mattkruse.com >
  8. * Maintained at http://code.google.com/p/php-excel-reader/
  9. *
  10. * Format parsing and MUCH more contributed by:
  11. * Matt Roxburgh < http://www.roxburgh.me.uk >
  12. *
  13. * DOCUMENTATION
  14. * =============
  15. * http://code.google.com/p/php-excel-reader/wiki/Documentation
  16. *
  17. * CHANGE LOG
  18. * ==========
  19. * http://code.google.com/p/php-excel-reader/wiki/ChangeHistory
  20. *
  21. * DISCUSSION/SUPPORT
  22. * ==================
  23. * http://groups.google.com/group/php-excel-reader-discuss/topics
  24. *
  25. * --------------------------------------------------------------------------
  26. *
  27. * Originally developed by Vadim Tkachenko under the name PHPExcelReader.
  28. * (http://sourceforge.net/projects/phpexcelreader)
  29. * Based on the Java version by Andy Khan (http://www.andykhan.com). Now
  30. * maintained by David Sanders. Reads only Biff 7 and Biff 8 formats.
  31. *
  32. * PHP version 5
  33. *
  34. * LICENSE: This source file is subject to version 3.0 of the PHP license
  35. * that is available through the world-wide-web at the following URI:
  36. * http://www.php.net/license/3_0.txt. If you did not receive a copy of
  37. * the PHP License and are unable to obtain it through the web, please
  38. * send a note to license@php.net so we can mail you a copy immediately.
  39. *
  40. * @category Spreadsheet
  41. * @package Spreadsheet_Excel_Reader
  42. * @author Vadim Tkachenko <vt@apachephp.com>
  43. * @license http://www.php.net/license/3_0.txt PHP License 3.0
  44. * @version CVS: $Id: reader.php 19 2007-03-13 12:42:41Z shangxiao $
  45. * @link http://pear.php.net/package/Spreadsheet_Excel_Reader
  46. * @see OLE, Spreadsheet_Excel_Writer
  47. *
  48. * 2013-01-24: Ported from PHP4 to PHP5 by Mark Scherer - www.dereuromark.de
  49. * - PGR0/1 namespace now "SpreadsheetExcelReader", also the classname then
  50. * - Coding Standards
  51. * - PHP5 E_STRICT (partly)
  52. * - Cleanup and no non-class functions anymore (static OLERead ones to avoid conflicts)
  53. * - read() for files and readFromBlob() to directly process binary input
  54. * --------------------------------------------------------------------------
  55. */
  56. define('NUM_BIG_BLOCK_DEPOT_BLOCKS_POS', 0x2c);
  57. define('SMALL_BLOCK_DEPOT_BLOCK_POS', 0x3c);
  58. define('ROOT_START_BLOCK_POS', 0x30);
  59. define('BIG_BLOCK_SIZE', 0x200);
  60. define('SMALL_BLOCK_SIZE', 0x40);
  61. define('EXTENSION_BLOCK_POS', 0x44);
  62. define('NUM_EXTENSION_BLOCK_POS', 0x48);
  63. define('PROPERTY_STORAGE_BLOCK_SIZE', 0x80);
  64. define('BIG_BLOCK_DEPOT_BLOCKS_POS', 0x4c);
  65. define('SMALL_BLOCK_THRESHOLD', 0x1000);
  66. // property storage offsets
  67. define('SIZE_OF_NAME_POS', 0x40);
  68. define('TYPE_POS', 0x42);
  69. define('START_BLOCK_POS', 0x74);
  70. define('SIZE_POS', 0x78);
  71. define('IDENTIFIER_OLE', pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1));
  72. /**
  73. * Helper class
  74. *
  75. */
  76. class OLERead {
  77. public $data = '';
  78. public static function getInt4d($data, $pos) {
  79. $value = ord($data[$pos]) | (ord($data[$pos+1]) << 8) | (ord($data[$pos+2]) << 16) | (ord($data[$pos+3]) << 24);
  80. if ($value>=4294967294) {
  81. $value=-2;
  82. }
  83. return $value;
  84. }
  85. // http://uk.php.net/manual/en/function.getdate.php
  86. public static function gmgetdate($ts = null) {
  87. $k = array('seconds', 'minutes', 'hours', 'mday', 'wday', 'mon', 'year', 'yday', 'weekday', 'month',0);
  88. return(array_combine($k, explode(':', gmdate('s:i:G:j:w:n:Y:z:l:F:U', is_null($ts)?time():$ts))));
  89. }
  90. public static function v($data, $pos) {
  91. return ord($data[$pos]) | ord($data[$pos+1])<<8;
  92. }
  93. /**
  94. * Read from file
  95. * @param strin $filename
  96. */
  97. public function read($fileName) {
  98. // check if file exist and is readable (Darko Miljanovic)
  99. if (!is_readable($fileName)) {
  100. $this->error = 1;
  101. return false;
  102. }
  103. $data = @file_get_contents($fileName);
  104. if (!$data) {
  105. $this->error = 1;
  106. return false;
  107. }
  108. return $this->readFromBlob($data);
  109. }
  110. /**
  111. * Read content
  112. * @param binary $data
  113. */
  114. public function readFromBlob($data) {
  115. $this->data = $data;
  116. if (substr($this->data, 0, 8) != IDENTIFIER_OLE) {
  117. $this->error = 1;
  118. return false;
  119. }
  120. $this->numBigBlockDepotBlocks = OLERead::getInt4d($this->data, NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
  121. $this->sbdStartBlock = OLERead::getInt4d($this->data, SMALL_BLOCK_DEPOT_BLOCK_POS);
  122. $this->rootStartBlock = OLERead::getInt4d($this->data, ROOT_START_BLOCK_POS);
  123. $this->extensionBlock = OLERead::getInt4d($this->data, EXTENSION_BLOCK_POS);
  124. $this->numExtensionBlocks = OLERead::getInt4d($this->data, NUM_EXTENSION_BLOCK_POS);
  125. $bigBlockDepotBlocks = array();
  126. $pos = BIG_BLOCK_DEPOT_BLOCKS_POS;
  127. $bbdBlocks = $this->numBigBlockDepotBlocks;
  128. if ($this->numExtensionBlocks != 0) {
  129. $bbdBlocks = (BIG_BLOCK_SIZE - BIG_BLOCK_DEPOT_BLOCKS_POS)/4;
  130. }
  131. for ($i = 0; $i < $bbdBlocks; $i++) {
  132. $bigBlockDepotBlocks[$i] = OLERead::getInt4d($this->data, $pos);
  133. $pos += 4;
  134. }
  135. for ($j = 0; $j < $this->numExtensionBlocks; $j++) {
  136. $pos = ($this->extensionBlock + 1) * BIG_BLOCK_SIZE;
  137. $blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, BIG_BLOCK_SIZE / 4 - 1);
  138. for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; $i++) {
  139. $bigBlockDepotBlocks[$i] = OLERead::getInt4d($this->data, $pos);
  140. $pos += 4;
  141. }
  142. $bbdBlocks += $blocksToRead;
  143. if ($bbdBlocks < $this->numBigBlockDepotBlocks) {
  144. $this->extensionBlock = OLERead::getInt4d($this->data, $pos);
  145. }
  146. }
  147. // readBigBlockDepot
  148. $pos = 0;
  149. $index = 0;
  150. $this->bigBlockChain = array();
  151. for ($i = 0; $i < $this->numBigBlockDepotBlocks; $i++) {
  152. $pos = ($bigBlockDepotBlocks[$i] + 1) * BIG_BLOCK_SIZE;
  153. //echo "pos = $pos";
  154. for ($j = 0 ; $j < BIG_BLOCK_SIZE / 4; $j++) {
  155. $this->bigBlockChain[$index] = OLERead::getInt4d($this->data, $pos);
  156. $pos += 4 ;
  157. $index++;
  158. }
  159. }
  160. // readSmallBlockDepot();
  161. $pos = 0;
  162. $index = 0;
  163. $sbdBlock = $this->sbdStartBlock;
  164. $this->smallBlockChain = array();
  165. while ($sbdBlock != -2) {
  166. $pos = ($sbdBlock + 1) * BIG_BLOCK_SIZE;
  167. for ($j = 0; $j < BIG_BLOCK_SIZE / 4; $j++) {
  168. $this->smallBlockChain[$index] = OLERead::getInt4d($this->data, $pos);
  169. $pos += 4;
  170. $index++;
  171. }
  172. $sbdBlock = $this->bigBlockChain[$sbdBlock];
  173. }
  174. // readData(rootStartBlock)
  175. $block = $this->rootStartBlock;
  176. $pos = 0;
  177. $this->entry = $this->_readData($block);
  178. $this->_readPropertySets();
  179. }
  180. protected function _readData($bl) {
  181. $block = $bl;
  182. $pos = 0;
  183. $data = '';
  184. while ($block != -2) {
  185. $pos = ($block + 1) * BIG_BLOCK_SIZE;
  186. $data = $data.substr($this->data, $pos, BIG_BLOCK_SIZE);
  187. $block = $this->bigBlockChain[$block];
  188. }
  189. return $data;
  190. }
  191. protected function _readPropertySets() {
  192. $offset = 0;
  193. while ($offset < strlen($this->entry)) {
  194. $d = substr($this->entry, $offset, PROPERTY_STORAGE_BLOCK_SIZE);
  195. $nameSize = ord($d[SIZE_OF_NAME_POS]) | (ord($d[SIZE_OF_NAME_POS+1]) << 8);
  196. $type = ord($d[TYPE_POS]);
  197. $startBlock = OLERead::getInt4d($d, START_BLOCK_POS);
  198. $size = OLERead::getInt4d($d, SIZE_POS);
  199. $name = '';
  200. for ($i = 0; $i < $nameSize ; $i++) {
  201. $name .= $d[$i];
  202. }
  203. $name = str_replace("\x00", '', $name);
  204. $this->props[] = array (
  205. 'name' => $name,
  206. 'type' => $type,
  207. 'startBlock' => $startBlock,
  208. 'size' => $size);
  209. if ((strtolower($name) === "workbook") || ( strtolower($name) === "book")) {
  210. $this->wrkbook = count($this->props) - 1;
  211. }
  212. if ($name === "Root Entry") {
  213. $this->rootentry = count($this->props) - 1;
  214. }
  215. $offset += PROPERTY_STORAGE_BLOCK_SIZE;
  216. }
  217. }
  218. public function getWorkBook() {
  219. if ($this->props[$this->wrkbook]['size'] < SMALL_BLOCK_THRESHOLD) {
  220. $rootdata = $this->_readData($this->props[$this->rootentry]['startBlock']);
  221. $streamData = '';
  222. $block = $this->props[$this->wrkbook]['startBlock'];
  223. $pos = 0;
  224. while ($block != -2) {
  225. $pos = $block * SMALL_BLOCK_SIZE;
  226. $streamData .= substr($rootdata, $pos, SMALL_BLOCK_SIZE);
  227. $block = $this->smallBlockChain[$block];
  228. }
  229. return $streamData;
  230. } else {
  231. $numBlocks = $this->props[$this->wrkbook]['size'] / BIG_BLOCK_SIZE;
  232. if ($this->props[$this->wrkbook]['size'] % BIG_BLOCK_SIZE != 0) {
  233. $numBlocks++;
  234. }
  235. if ($numBlocks == 0) return '';
  236. $streamData = '';
  237. $block = $this->props[$this->wrkbook]['startBlock'];
  238. $pos = 0;
  239. while ($block != -2) {
  240. $pos = ($block + 1) * BIG_BLOCK_SIZE;
  241. $streamData .= substr($this->data, $pos, BIG_BLOCK_SIZE);
  242. $block = $this->bigBlockChain[$block];
  243. }
  244. return $streamData;
  245. }
  246. }
  247. }
  248. define('SPREADSHEET_EXCEL_READER_BIFF8', 0x600);
  249. define('SPREADSHEET_EXCEL_READER_BIFF7', 0x500);
  250. define('SPREADSHEET_EXCEL_READER_WORKBOOKGLOBALS', 0x5);
  251. define('SPREADSHEET_EXCEL_READER_WORKSHEET', 0x10);
  252. define('SPREADSHEET_EXCEL_READER_TYPE_BOF', 0x809);
  253. define('SPREADSHEET_EXCEL_READER_TYPE_EOF', 0x0a);
  254. define('SPREADSHEET_EXCEL_READER_TYPE_BOUNDSHEET', 0x85);
  255. define('SPREADSHEET_EXCEL_READER_TYPE_DIMENSION', 0x200);
  256. define('SPREADSHEET_EXCEL_READER_TYPE_ROW', 0x208);
  257. define('SPREADSHEET_EXCEL_READER_TYPE_DBCELL', 0xd7);
  258. define('SPREADSHEET_EXCEL_READER_TYPE_FILEPASS', 0x2f);
  259. define('SPREADSHEET_EXCEL_READER_TYPE_NOTE', 0x1c);
  260. define('SPREADSHEET_EXCEL_READER_TYPE_TXO', 0x1b6);
  261. define('SPREADSHEET_EXCEL_READER_TYPE_RK', 0x7e);
  262. define('SPREADSHEET_EXCEL_READER_TYPE_RK2', 0x27e);
  263. define('SPREADSHEET_EXCEL_READER_TYPE_MULRK', 0xbd);
  264. define('SPREADSHEET_EXCEL_READER_TYPE_MULBLANK', 0xbe);
  265. define('SPREADSHEET_EXCEL_READER_TYPE_INDEX', 0x20b);
  266. define('SPREADSHEET_EXCEL_READER_TYPE_SST', 0xfc);
  267. define('SPREADSHEET_EXCEL_READER_TYPE_EXTSST', 0xff);
  268. define('SPREADSHEET_EXCEL_READER_TYPE_CONTINUE', 0x3c);
  269. define('SPREADSHEET_EXCEL_READER_TYPE_LABEL', 0x204);
  270. define('SPREADSHEET_EXCEL_READER_TYPE_LABELSST', 0xfd);
  271. define('SPREADSHEET_EXCEL_READER_TYPE_NUMBER', 0x203);
  272. define('SPREADSHEET_EXCEL_READER_TYPE_NAME', 0x18);
  273. define('SPREADSHEET_EXCEL_READER_TYPE_ARRAY', 0x221);
  274. define('SPREADSHEET_EXCEL_READER_TYPE_STRING', 0x207);
  275. define('SPREADSHEET_EXCEL_READER_TYPE_FORMULA', 0x406);
  276. define('SPREADSHEET_EXCEL_READER_TYPE_FORMULA2', 0x6);
  277. define('SPREADSHEET_EXCEL_READER_TYPE_FORMAT', 0x41e);
  278. define('SPREADSHEET_EXCEL_READER_TYPE_XF', 0xe0);
  279. define('SPREADSHEET_EXCEL_READER_TYPE_BOOLERR', 0x205);
  280. define('SPREADSHEET_EXCEL_READER_TYPE_FONT', 0x0031);
  281. define('SPREADSHEET_EXCEL_READER_TYPE_PALETTE', 0x0092);
  282. define('SPREADSHEET_EXCEL_READER_TYPE_UNKNOWN', 0xffff);
  283. define('SPREADSHEET_EXCEL_READER_TYPE_NINETEENFOUR', 0x22);
  284. define('SPREADSHEET_EXCEL_READER_TYPE_MERGEDCELLS', 0xE5);
  285. define('SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS' ,25569);
  286. define('SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS1904', 24107);
  287. define('SPREADSHEET_EXCEL_READER_MSINADAY', 86400);
  288. define('SPREADSHEET_EXCEL_READER_TYPE_HYPER', 0x01b8);
  289. define('SPREADSHEET_EXCEL_READER_TYPE_COLINFO', 0x7d);
  290. define('SPREADSHEET_EXCEL_READER_TYPE_DEFCOLWIDTH', 0x55);
  291. define('SPREADSHEET_EXCEL_READER_TYPE_STANDARDWIDTH', 0x99);
  292. define('SPREADSHEET_EXCEL_READER_DEF_NUM_FORMAT', "%s");
  293. /**
  294. * Main Class
  295. *
  296. */
  297. class SpreadsheetExcelReader {
  298. // MK: Added to make data retrieval easier
  299. public $colnames = array();
  300. public $colindexes = array();
  301. public $standardColWidth = 0;
  302. public $defaultColWidth = 0;
  303. public function myHex($d) {
  304. if ($d < 16) return "0" . dechex($d);
  305. return dechex($d);
  306. }
  307. public function dumpHexData($data, $pos, $length) {
  308. $info = '';
  309. for ($i = 0; $i <= $length; $i++) {
  310. $info .= ($i==0?'':" ") . $this->myHex(ord($data[$pos + $i])) . (ord($data[$pos + $i])>31? "[" . $data[$pos + $i] . "]":'');
  311. }
  312. return $info;
  313. }
  314. public function getCol($col) {
  315. if (is_string($col)) {
  316. $col = strtolower($col);
  317. if (array_key_exists($col, $this->colnames)) {
  318. $col = $this->colnames[$col];
  319. }
  320. }
  321. return $col;
  322. }
  323. // PUBLIC API FUNCTIONS
  324. // --------------------
  325. public function val($row, $col, $sheet = 0) {
  326. $col = $this->getCol($col);
  327. if (array_key_exists($row, $this->sheets[$sheet]['cells']) && array_key_exists($col, $this->sheets[$sheet]['cells'][$row])) {
  328. return $this->sheets[$sheet]['cells'][$row][$col];
  329. }
  330. return '';
  331. }
  332. //really necessary?
  333. public function value($row, $col, $sheet = 0) {
  334. return $this->val($row, $col, $sheet);
  335. }
  336. public function info($row, $col, $type = '', $sheet = 0) {
  337. $col = $this->getCol($col);
  338. if (array_key_exists('cellsInfo', $this->sheets[$sheet])
  339. && array_key_exists($row, $this->sheets[$sheet]['cellsInfo'])
  340. && array_key_exists($col, $this->sheets[$sheet]['cellsInfo'][$row])
  341. && array_key_exists($type, $this->sheets[$sheet]['cellsInfo'][$row][$col])) {
  342. return $this->sheets[$sheet]['cellsInfo'][$row][$col][$type];
  343. }
  344. return '';
  345. }
  346. public function type($row, $col, $sheet = 0) {
  347. return $this->info($row, $col, 'type', $sheet);
  348. }
  349. public function raw($row, $col, $sheet = 0) {
  350. return $this->info($row, $col, 'raw', $sheet);
  351. }
  352. public function rowspan($row, $col, $sheet = 0) {
  353. $val = $this->info($row, $col, 'rowspan', $sheet);
  354. if ($val=='') { return 1; }
  355. return $val;
  356. }
  357. public function colspan($row, $col, $sheet = 0) {
  358. $val = $this->info($row, $col, 'colspan', $sheet);
  359. if ($val=='') { return 1; }
  360. return $val;
  361. }
  362. public function hyperlink($row, $col, $sheet = 0) {
  363. $link = $this->sheets[$sheet]['cellsInfo'][$row][$col]['hyperlink'];
  364. if ($link) {
  365. return $link['link'];
  366. }
  367. return '';
  368. }
  369. public function rowcount($sheet = 0) {
  370. return $this->sheets[$sheet]['numRows'];
  371. }
  372. public function colcount($sheet = 0) {
  373. return $this->sheets[$sheet]['numCols'];
  374. }
  375. public function colwidth($col, $sheet = 0) {
  376. // Col width is actually the width of the number 0. So we have to estimate and come close
  377. return $this->colInfo[$sheet][$col]['width']/9142*200;
  378. }
  379. public function colhidden($col, $sheet = 0) {
  380. return (bool)$this->colInfo[$sheet][$col]['hidden'];
  381. }
  382. public function rowheight($row, $sheet = 0) {
  383. return $this->rowInfo[$sheet][$row]['height'];
  384. }
  385. public function rowhidden($row, $sheet = 0) {
  386. return (bool)$this->rowInfo[$sheet][$row]['hidden'];
  387. }
  388. /**
  389. * @return int Number of sheets
  390. */
  391. public function sheets() {
  392. return count($this->sheets);
  393. }
  394. // GET THE CSS FOR FORMATTING
  395. // ==========================
  396. public function style($row, $col, $sheet = 0, $properties='') {
  397. $css = '';
  398. $font=$this->font($row, $col, $sheet);
  399. if ($font!='') {
  400. $css .= "font-family:$font;";
  401. }
  402. $align=$this->align($row, $col, $sheet);
  403. if ($align!='') {
  404. $css .= "text-align:$align;";
  405. }
  406. $height=$this->height($row, $col, $sheet);
  407. if ($height!='') {
  408. $css .= "font-size:$height"."px;";
  409. }
  410. $bgcolor=$this->bgColor($row, $col, $sheet);
  411. if ($bgcolor!='') {
  412. $bgcolor = $this->colors[$bgcolor];
  413. $css .= "background-color:$bgcolor;";
  414. }
  415. $color=$this->color($row, $col, $sheet);
  416. if ($color!='') {
  417. $css .= "color:$color;";
  418. }
  419. $bold=$this->bold($row, $col, $sheet);
  420. if ($bold) {
  421. $css .= "font-weight:bold;";
  422. }
  423. $italic=$this->italic($row, $col, $sheet);
  424. if ($italic) {
  425. $css .= "font-style:italic;";
  426. }
  427. $underline=$this->underline($row, $col, $sheet);
  428. if ($underline) {
  429. $css .= "text-decoration:underline;";
  430. }
  431. // Borders
  432. $bLeft = $this->borderLeft($row, $col, $sheet);
  433. $bRight = $this->borderRight($row, $col, $sheet);
  434. $bTop = $this->borderTop($row, $col, $sheet);
  435. $bBottom = $this->borderBottom($row, $col, $sheet);
  436. $bLeftCol = $this->borderLeftColor($row, $col, $sheet);
  437. $bRightCol = $this->borderRightColor($row, $col, $sheet);
  438. $bTopCol = $this->borderTopColor($row, $col, $sheet);
  439. $bBottomCol = $this->borderBottomColor($row, $col, $sheet);
  440. // Try to output the minimal required style
  441. if ($bLeft!='' && $bLeft==$bRight && $bRight==$bTop && $bTop==$bBottom) {
  442. $css .= "border:" . $this->lineStylesCss[$bLeft] .";";
  443. } else {
  444. if ($bLeft!='') { $css .= "border-left:" . $this->lineStylesCss[$bLeft] .";"; }
  445. if ($bRight!='') { $css .= "border-right:" . $this->lineStylesCss[$bRight] .";"; }
  446. if ($bTop!='') { $css .= "border-top:" . $this->lineStylesCss[$bTop] .";"; }
  447. if ($bBottom!='') { $css .= "border-bottom:" . $this->lineStylesCss[$bBottom] .";"; }
  448. }
  449. // Only output border colors if there is an actual border specified
  450. if ($bLeft!='' && $bLeftCol!='') { $css .= "border-left-color:" . $bLeftCol .";"; }
  451. if ($bRight!='' && $bRightCol!='') { $css .= "border-right-color:" . $bRightCol .";"; }
  452. if ($bTop!='' && $bTopCol!='') { $css .= "border-top-color:" . $bTopCol . ";"; }
  453. if ($bBottom!='' && $bBottomCol!='') { $css .= "border-bottom-color:" . $bBottomCol .";"; }
  454. return $css;
  455. }
  456. // FORMAT PROPERTIES
  457. // =================
  458. public function format($row, $col, $sheet = 0) {
  459. return $this->info($row, $col, 'format', $sheet);
  460. }
  461. public function formatIndex($row, $col, $sheet = 0) {
  462. return $this->info($row, $col, 'formatIndex', $sheet);
  463. }
  464. public function formatColor($row, $col, $sheet = 0) {
  465. return $this->info($row, $col, 'formatColor', $sheet);
  466. }
  467. // CELL (XF) PROPERTIES
  468. // ====================
  469. public function xfRecord($row, $col, $sheet = 0) {
  470. $xfIndex = $this->info($row, $col, 'xfIndex', $sheet);
  471. if ($xfIndex!='') {
  472. return $this->xfRecords[$xfIndex];
  473. }
  474. return null;
  475. }
  476. public function xfProperty($row, $col, $sheet, $prop) {
  477. $xfRecord = $this->xfRecord($row, $col, $sheet);
  478. if ($xfRecord!=null) {
  479. return $xfRecord[$prop];
  480. }
  481. return '';
  482. }
  483. public function align($row, $col, $sheet = 0) {
  484. return $this->xfProperty($row, $col, $sheet, 'align');
  485. }
  486. public function bgColor($row, $col, $sheet = 0) {
  487. return $this->xfProperty($row, $col, $sheet, 'bgColor');
  488. }
  489. public function borderLeft($row, $col, $sheet = 0) {
  490. return $this->xfProperty($row, $col, $sheet, 'borderLeft');
  491. }
  492. public function borderRight($row, $col, $sheet = 0) {
  493. return $this->xfProperty($row, $col, $sheet, 'borderRight');
  494. }
  495. public function borderTop($row, $col, $sheet = 0) {
  496. return $this->xfProperty($row, $col, $sheet, 'borderTop');
  497. }
  498. public function borderBottom($row, $col, $sheet = 0) {
  499. return $this->xfProperty($row, $col, $sheet, 'borderBottom');
  500. }
  501. public function borderLeftColor($row, $col, $sheet = 0) {
  502. return $this->colors[$this->xfProperty($row, $col, $sheet, 'borderLeftColor')];
  503. }
  504. public function borderRightColor($row, $col, $sheet = 0) {
  505. return $this->colors[$this->xfProperty($row, $col, $sheet, 'borderRightColor')];
  506. }
  507. public function borderTopColor($row, $col, $sheet = 0) {
  508. return $this->colors[$this->xfProperty($row, $col, $sheet, 'borderTopColor')];
  509. }
  510. public function borderBottomColor($row, $col, $sheet = 0) {
  511. return $this->colors[$this->xfProperty($row, $col, $sheet, 'borderBottomColor')];
  512. }
  513. // FONT PROPERTIES
  514. // ===============
  515. public function fontRecord($row, $col, $sheet = 0) {
  516. $xfRecord = $this->xfRecord($row, $col, $sheet);
  517. if ($xfRecord!=null) {
  518. $font = $xfRecord['fontIndex'];
  519. if ($font!=null) {
  520. return $this->fontRecords[$font];
  521. }
  522. }
  523. return null;
  524. }
  525. public function fontProperty($row, $col, $sheet = 0, $prop) {
  526. $font = $this->fontRecord($row, $col, $sheet);
  527. if ($font!=null) {
  528. return $font[$prop];
  529. }
  530. return false;
  531. }
  532. public function fontIndex($row, $col, $sheet = 0) {
  533. return $this->xfProperty($row, $col, $sheet, 'fontIndex');
  534. }
  535. public function color($row, $col, $sheet = 0) {
  536. $formatColor = $this->formatColor($row, $col, $sheet);
  537. if ($formatColor!='') {
  538. return $formatColor;
  539. }
  540. $ci = $this->fontProperty($row, $col, $sheet, 'color');
  541. return $this->rawColor($ci);
  542. }
  543. public function rawColor($ci) {
  544. if (($ci <> 0x7FFF) && ($ci <> '')) {
  545. return $this->colors[$ci];
  546. }
  547. return '';
  548. }
  549. public function bold($row, $col, $sheet = 0) {
  550. return $this->fontProperty($row, $col, $sheet, 'bold');
  551. }
  552. public function italic($row, $col, $sheet = 0) {
  553. return $this->fontProperty($row, $col, $sheet, 'italic');
  554. }
  555. public function underline($row, $col, $sheet = 0) {
  556. return $this->fontProperty($row, $col, $sheet, 'under');
  557. }
  558. public function height($row, $col, $sheet = 0) {
  559. return $this->fontProperty($row, $col, $sheet, 'height');
  560. }
  561. public function font($row, $col, $sheet = 0) {
  562. return $this->fontProperty($row, $col, $sheet, 'font');
  563. }
  564. // DUMP AN HTML TABLE OF THE ENTIRE XLS DATA
  565. // =========================================
  566. /**
  567. * Read the excel content into an array
  568. *
  569. * @param $sheet - Sheet number in the excel file
  570. * @return $arr - array containing the excel content
  571. */
  572. public function dumpToArray($sheet = 0) {
  573. $arr = array();
  574. for ($row = 0; $row < $this->rowcount($sheet); $row++) {
  575. for ($col = 0; $col < $this->colcount($sheet); $col++) {
  576. $arr[$row][$col] = iconv(null, 'utf-8', $this->val($row + 1, $col + 1, $sheet));
  577. }
  578. }
  579. return $arr;
  580. }
  581. // --------------
  582. // END PUBLIC API
  583. public $boundsheets = array();
  584. public $formatRecords = array();
  585. public $fontRecords = array();
  586. public $xfRecords = array();
  587. public $colInfo = array();
  588. public $rowInfo = array();
  589. public $sst = array();
  590. public $sheets = array();
  591. public $data;
  592. public $_ole;
  593. public $_defaultEncoding = "UTF-8";
  594. public $_defaultFormat = SPREADSHEET_EXCEL_READER_DEF_NUM_FORMAT;
  595. public $_columnsFormat = array();
  596. public $_rowoffset = 1;
  597. public $_coloffset = 1;
  598. /**
  599. * List of default date formats used by Excel
  600. */
  601. public $dateFormats = array (
  602. 0xe => "m/d/Y",
  603. 0xf => "M-d-Y",
  604. 0x10 => "d-M",
  605. 0x11 => "M-Y",
  606. 0x12 => "h:i a",
  607. 0x13 => "h:i:s a",
  608. 0x14 => "H:i",
  609. 0x15 => "H:i:s",
  610. 0x16 => "d/m/Y H:i",
  611. 0x2d => "i:s",
  612. 0x2e => "H:i:s",
  613. 0x2f => "i:s.S"
  614. );
  615. /**
  616. * Default number formats used by Excel
  617. */
  618. public $numberFormats = array(
  619. 0x1 => "0",
  620. 0x2 => "0.00",
  621. 0x3 => "#,##0",
  622. 0x4 => "#,##0.00",
  623. 0x5 => "\$#,##0;(\$#,##0)",
  624. 0x6 => "\$#,##0;[Red](\$#,##0)",
  625. 0x7 => "\$#,##0.00;(\$#,##0.00)",
  626. 0x8 => "\$#,##0.00;[Red](\$#,##0.00)",
  627. 0x9 => "0%",
  628. 0xa => "0.00%",
  629. 0xb => "0.00E+00",
  630. 0x25 => "#,##0;(#,##0)",
  631. 0x26 => "#,##0;[Red](#,##0)",
  632. 0x27 => "#,##0.00;(#,##0.00)",
  633. 0x28 => "#,##0.00;[Red](#,##0.00)",
  634. 0x29 => "#,##0;(#,##0)", // Not exactly
  635. 0x2a => "\$#,##0;(\$#,##0)", // Not exactly
  636. 0x2b => "#,##0.00;(#,##0.00)", // Not exactly
  637. 0x2c => "\$#,##0.00;(\$#,##0.00)", // Not exactly
  638. 0x30 => "##0.0E+0"
  639. );
  640. public $colors = Array(
  641. 0x00 => "#000000",
  642. 0x01 => "#FFFFFF",
  643. 0x02 => "#FF0000",
  644. 0x03 => "#00FF00",
  645. 0x04 => "#0000FF",
  646. 0x05 => "#FFFF00",
  647. 0x06 => "#FF00FF",
  648. 0x07 => "#00FFFF",
  649. 0x08 => "#000000",
  650. 0x09 => "#FFFFFF",
  651. 0x0A => "#FF0000",
  652. 0x0B => "#00FF00",
  653. 0x0C => "#0000FF",
  654. 0x0D => "#FFFF00",
  655. 0x0E => "#FF00FF",
  656. 0x0F => "#00FFFF",
  657. 0x10 => "#800000",
  658. 0x11 => "#008000",
  659. 0x12 => "#000080",
  660. 0x13 => "#808000",
  661. 0x14 => "#800080",
  662. 0x15 => "#008080",
  663. 0x16 => "#C0C0C0",
  664. 0x17 => "#808080",
  665. 0x18 => "#9999FF",
  666. 0x19 => "#993366",
  667. 0x1A => "#FFFFCC",
  668. 0x1B => "#CCFFFF",
  669. 0x1C => "#660066",
  670. 0x1D => "#FF8080",
  671. 0x1E => "#0066CC",
  672. 0x1F => "#CCCCFF",
  673. 0x20 => "#000080",
  674. 0x21 => "#FF00FF",
  675. 0x22 => "#FFFF00",
  676. 0x23 => "#00FFFF",
  677. 0x24 => "#800080",
  678. 0x25 => "#800000",
  679. 0x26 => "#008080",
  680. 0x27 => "#0000FF",
  681. 0x28 => "#00CCFF",
  682. 0x29 => "#CCFFFF",
  683. 0x2A => "#CCFFCC",
  684. 0x2B => "#FFFF99",
  685. 0x2C => "#99CCFF",
  686. 0x2D => "#FF99CC",
  687. 0x2E => "#CC99FF",
  688. 0x2F => "#FFCC99",
  689. 0x30 => "#3366FF",
  690. 0x31 => "#33CCCC",
  691. 0x32 => "#99CC00",
  692. 0x33 => "#FFCC00",
  693. 0x34 => "#FF9900",
  694. 0x35 => "#FF6600",
  695. 0x36 => "#666699",
  696. 0x37 => "#969696",
  697. 0x38 => "#003366",
  698. 0x39 => "#339966",
  699. 0x3A => "#003300",
  700. 0x3B => "#333300",
  701. 0x3C => "#993300",
  702. 0x3D => "#993366",
  703. 0x3E => "#333399",
  704. 0x3F => "#333333",
  705. 0x40 => "#000000",
  706. 0x41 => "#FFFFFF",
  707. 0x43 => "#000000",
  708. 0x4D => "#000000",
  709. 0x4E => "#FFFFFF",
  710. 0x4F => "#000000",
  711. 0x50 => "#FFFFFF",
  712. 0x51 => "#000000",
  713. 0x7FFF => "#000000"
  714. );
  715. public $lineStyles = array(
  716. 0x00 => '',
  717. 0x01 => "Thin",
  718. 0x02 => "Medium",
  719. 0x03 => "Dashed",
  720. 0x04 => "Dotted",
  721. 0x05 => "Thick",
  722. 0x06 => "Double",
  723. 0x07 => "Hair",
  724. 0x08 => "Medium dashed",
  725. 0x09 => "Thin dash-dotted",
  726. 0x0A => "Medium dash-dotted",
  727. 0x0B => "Thin dash-dot-dotted",
  728. 0x0C => "Medium dash-dot-dotted",
  729. 0x0D => "Slanted medium dash-dotted"
  730. );
  731. public $lineStylesCss = array(
  732. "Thin" => "1px solid",
  733. "Medium" => "2px solid",
  734. "Dashed" => "1px dashed",
  735. "Dotted" => "1px dotted",
  736. "Thick" => "3px solid",
  737. "Double" => "double",
  738. "Hair" => "1px solid",
  739. "Medium dashed" => "2px dashed",
  740. "Thin dash-dotted" => "1px dashed",
  741. "Medium dash-dotted" => "2px dashed",
  742. "Thin dash-dot-dotted" => "1px dashed",
  743. "Medium dash-dot-dotted" => "2px dashed",
  744. "Slanted medium dash-dotte" => "2px dashed"
  745. );
  746. public function read16bitstring($data, $start) {
  747. $len = 0;
  748. while (ord($data[$start + $len]) + ord($data[$start + $len + 1]) > 0) $len++;
  749. return substr($data, $start, $len);
  750. }
  751. // ADDED by Matt Kruse for better formatting
  752. protected function _formatValue($format, $num, $f) {
  753. // 49==TEXT format
  754. // http://code.google.com/p/php-excel-reader/issues/detail?id=7
  755. if ( (!$f && $format=="%s") || ($f==49) || ($format=="GENERAL") ) {
  756. return array('string' => $num, 'formatColor'=>null);
  757. }
  758. // Custom pattern can be POSITIVE;NEGATIVE;ZERO
  759. // The "text" option as 4th parameter is not handled
  760. $parts = explode(";", $format);
  761. $pattern = $parts[0];
  762. // Negative pattern
  763. if (count($parts)>2 && $num==0) {
  764. $pattern = $parts[2];
  765. }
  766. // Zero pattern
  767. if (count($parts)>1 && $num<0) {
  768. $pattern = $parts[1];
  769. $num = abs($num);
  770. }
  771. $color = '';
  772. $matches = array();
  773. $color_regex = "/^\[(BLACK|BLUE|CYAN|GREEN|MAGENTA|RED|WHITE|YELLOW)\]/i";
  774. if (preg_match($color_regex, $pattern, $matches)) {
  775. $color = strtolower($matches[1]);
  776. $pattern = preg_replace($color_regex, '', $pattern);
  777. }
  778. // In Excel formats, "_" is used to add spacing, which we can't do in HTML
  779. $pattern = preg_replace("/_./", '', $pattern);
  780. // Some non-number characters are escaped with \, which we don't need
  781. $pattern = preg_replace("/\\\/", '', $pattern);
  782. // Some non-number strings are quoted, so we'll get rid of the quotes
  783. $pattern = preg_replace("/\"/", '', $pattern);
  784. // TEMPORARY - Convert # to 0
  785. $pattern = preg_replace("/\#/", "0", $pattern);
  786. // Find out if we need comma formatting
  787. $has_commas = preg_match("/,/", $pattern);
  788. if ($has_commas) {
  789. $pattern = preg_replace("/,/", '', $pattern);
  790. }
  791. // Handle Percentages
  792. if (preg_match("/\d(\%)([^\%]|$)/", $pattern, $matches)) {
  793. $num = $num * 100;
  794. $pattern = preg_replace("/(\d)(\%)([^\%]|$)/", "$1%$3", $pattern);
  795. }
  796. // Handle the number itself
  797. $number_regex = "/(\d+)(\.?)(\d*)/";
  798. if (preg_match($number_regex, $pattern, $matches)) {
  799. $left = $matches[1];
  800. $dec = $matches[2];
  801. $right = $matches[3];
  802. if ($has_commas) {
  803. $formatted = number_format($num,strlen($right));
  804. } else {
  805. $sprintf_pattern = "%1.".strlen($right)."f";
  806. $formatted = sprintf($sprintf_pattern, $num);
  807. }
  808. $pattern = preg_replace($number_regex, $formatted, $pattern);
  809. }
  810. return array(
  811. 'string' => $pattern,
  812. 'formatColor' => $color
  813. );
  814. }
  815. /**
  816. * Constructor
  817. *
  818. * Some basic initialisation
  819. */
  820. public function __construct($file = null, $store_extended_info = true, $outputEncoding = null) {
  821. $this->_ole = new OLERead();
  822. $this->setUTFEncoder('iconv');
  823. if ($outputEncoding) {
  824. $this->setOutputEncoding($outputEncoding);
  825. }
  826. for ($i=1; $i<245; $i++) {
  827. $name = strtolower(( (($i-1)/26>=1)?chr(($i-1)/26+64):'') . chr(($i-1)%26+65));
  828. $this->colnames[$name] = $i;
  829. $this->colindexes[$i] = $name;
  830. }
  831. $this->store_extended_info = $store_extended_info;
  832. if ($file) {
  833. $this->read($file);
  834. }
  835. }
  836. /**
  837. * Set the encoding method
  838. */
  839. public function setOutputEncoding($encoding) {
  840. $this->_defaultEncoding = $encoding;
  841. }
  842. /**
  843. * $encoder = 'iconv' or 'mb'
  844. * set iconv if you would like use 'iconv' for encode UTF-16LE to your encoding
  845. * set mb if you would like use 'mb_convert_encoding' for encode UTF-16LE to your encoding
  846. */
  847. public function setUTFEncoder($encoder = 'iconv') {
  848. $this->_encoderFunction = '';
  849. if ($encoder === 'iconv') {
  850. $this->_encoderFunction = function_exists('iconv') ? 'iconv' : '';
  851. } elseif ($encoder === 'mb') {
  852. $this->_encoderFunction = function_exists('mb_convert_encoding') ? 'mb_convert_encoding' : '';
  853. }
  854. }
  855. public function setRowColOffset($iOffset) {
  856. $this->_rowoffset = $iOffset;
  857. $this->_coloffset = $iOffset;
  858. }
  859. /**
  860. * Set the default number format
  861. */
  862. public function setDefaultFormat($sFormat) {
  863. $this->_defaultFormat = $sFormat;
  864. }
  865. /**
  866. * Force a column to use a certain format
  867. */
  868. public function setColumnFormat($column, $sFormat) {
  869. $this->_columnsFormat[$column] = $sFormat;
  870. }
  871. /**
  872. * Read the spreadsheet file using OLE, then parse
  873. */
  874. public function read($sFileName) {
  875. $res = $this->_ole->read($sFileName);
  876. // oops, something goes wrong (Darko Miljanovic)
  877. if ($res === false) {
  878. // check error code
  879. if ($this->_ole->error == 1) {
  880. // bad file
  881. throw new Exception('The filename ' . $sFileName . ' is not readable');
  882. }
  883. // check other error codes here (eg bad fileformat, etc...)
  884. }
  885. $this->data = $this->_ole->getWorkBook();
  886. $this->_parse();
  887. }
  888. public function readFromBlob($data) {
  889. $res = $this->_ole->readFromBlob($data);
  890. // oops, something goes wrong (Darko Miljanovic)
  891. if ($res === false) {
  892. // check error code
  893. if ($this->_ole->error == 1) {
  894. // bad file
  895. throw new Exception('The filename ' . $sFileName . ' is not readable');
  896. }
  897. // check other error codes here (eg bad fileformat, etc...)
  898. }
  899. $this->data = $this->_ole->getWorkBook();
  900. $this->_parse();
  901. }
  902. /**
  903. * Parse a workbook
  904. *
  905. * @access private
  906. * @return bool
  907. */
  908. protected function _parse() {
  909. $pos = 0;
  910. $data = $this->data;
  911. $code = $this->_v($data, $pos);
  912. $length = $this->_v($data, $pos+2);
  913. $version = $this->_v($data, $pos+4);
  914. $substreamType = $this->_v($data, $pos+6);
  915. $this->version = $version;
  916. if (($version != SPREADSHEET_EXCEL_READER_BIFF8) &&
  917. ($version != SPREADSHEET_EXCEL_READER_BIFF7)) {
  918. return false;
  919. }
  920. if ($substreamType != SPREADSHEET_EXCEL_READER_WORKBOOKGLOBALS) {
  921. return false;
  922. }
  923. $pos += $length + 4;
  924. $code = $this->_v($data, $pos);
  925. $length = $this->_v($data, $pos+2);
  926. while ($code != SPREADSHEET_EXCEL_READER_TYPE_EOF) {
  927. switch ($code) {
  928. case SPREADSHEET_EXCEL_READER_TYPE_SST:
  929. $spos = $pos + 4;
  930. $limitpos = $spos + $length;
  931. $uniqueStrings = $this->_getInt4d($data, $spos+4);
  932. $spos += 8;
  933. for ($i = 0; $i < $uniqueStrings; $i++) {
  934. // Read in the number of characters
  935. if ($spos == $limitpos) {
  936. $opcode = $this->_v($data, $spos);
  937. $conlength = $this->_v($data, $spos+2);
  938. if ($opcode != 0x3c) {
  939. return -1;
  940. }
  941. $spos += 4;
  942. $limitpos = $spos + $conlength;
  943. }
  944. $numChars = ord($data[$spos]) | (ord($data[$spos+1]) << 8);
  945. $spos += 2;
  946. $optionFlags = ord($data[$spos]);
  947. $spos++;
  948. $asciiEncoding = (($optionFlags & 0x01) == 0) ;
  949. $extendedString = ( ($optionFlags & 0x04) != 0);
  950. // See if string contains formatting information
  951. $richString = ( ($optionFlags & 0x08) != 0);
  952. if ($richString) {
  953. // Read in the crun
  954. $formattingRuns = $this->_v($data, $spos);
  955. $spos += 2;
  956. }
  957. if ($extendedString) {
  958. // Read in cchExtRst
  959. $extendedRunLength = $this->_getInt4d($data, $spos);
  960. $spos += 4;
  961. }
  962. $len = ($asciiEncoding)? $numChars : $numChars*2;
  963. if ($spos + $len < $limitpos) {
  964. $retstr = substr($data, $spos, $len);
  965. $spos += $len;
  966. } else {
  967. // found countinue
  968. $retstr = substr($data, $spos, $limitpos - $spos);
  969. $bytesRead = $limitpos - $spos;
  970. $charsLeft = $numChars - (($asciiEncoding) ? $bytesRead : ($bytesRead / 2));
  971. $spos = $limitpos;
  972. while ($charsLeft > 0) {
  973. $opcode = $this->_v($data, $spos);
  974. $conlength = $this->_v($data, $spos+2);
  975. if ($opcode != 0x3c) {
  976. return -1;
  977. }
  978. $spos += 4;
  979. $limitpos = $spos + $conlength;
  980. $option = ord($data[$spos]);
  981. $spos += 1;
  982. if ($asciiEncoding && ($option == 0)) {
  983. $len = min($charsLeft, $limitpos - $spos); // min($charsLeft, $conlength);
  984. $retstr .= substr($data, $spos, $len);
  985. $charsLeft -= $len;
  986. $asciiEncoding = true;
  987. } elseif (!$asciiEncoding && ($option != 0)) {
  988. $len = min($charsLeft * 2, $limitpos - $spos); // min($charsLeft, $conlength);
  989. $retstr .= substr($data, $spos, $len);
  990. $charsLeft -= $len/2;
  991. $asciiEncoding = false;
  992. } elseif (!$asciiEncoding && ($option == 0)) {
  993. // Bummer - the string starts off as Unicode, but after the
  994. // continuation it is in straightforward ASCII encoding
  995. $len = min($charsLeft, $limitpos - $spos); // min($charsLeft, $conlength);
  996. for ($j = 0; $j < $len; $j++) {
  997. $retstr .= $data[$spos + $j].chr(0);
  998. }
  999. $charsLeft -= $len;
  1000. $asciiEncoding = false;
  1001. } else {
  1002. $newstr = '';
  1003. for ($j = 0; $j < strlen($retstr); $j++) {
  1004. $newstr = $retstr[$j].chr(0);
  1005. }
  1006. $retstr = $newstr;
  1007. $len = min($charsLeft * 2, $limitpos - $spos); // min($charsLeft, $conlength);
  1008. $retstr .= substr($data, $spos, $len);
  1009. $charsLeft -= $len/2;
  1010. $asciiEncoding = false;
  1011. }
  1012. $spos += $len;
  1013. }
  1014. }
  1015. $retstr = ($asciiEncoding) ? $retstr : $this->_encodeUTF16($retstr);
  1016. if ($richString) {
  1017. $spos += 4 * $formattingRuns;
  1018. }
  1019. // For extended strings, skip over the extended string data
  1020. if ($extendedString) {
  1021. $spos += $extendedRunLength;
  1022. }
  1023. $this->sst[]=$retstr;
  1024. }
  1025. break;
  1026. case SPREADSHEET_EXCEL_READER_TYPE_FILEPASS:
  1027. return false;
  1028. break;
  1029. case SPREADSHEET_EXCEL_READER_TYPE_NAME:
  1030. break;
  1031. case SPREADSHEET_EXCEL_READER_TYPE_FORMAT:
  1032. $indexCode = $this->_v($data, $pos+4);
  1033. if ($version == SPREADSHEET_EXCEL_READER_BIFF8) {
  1034. $numchars = $this->_v($data, $pos+6);
  1035. if (ord($data[$pos+8]) == 0) {
  1036. $formatString = substr($data, $pos+9, $numchars);
  1037. } else {
  1038. $formatString = substr($data, $pos+9, $numchars*2);
  1039. }
  1040. } else {
  1041. $numchars = ord($data[$pos+6]);
  1042. $formatString = substr($data, $pos+7, $numchars*2);
  1043. }
  1044. $this->formatRecords[$indexCode] = $formatString;
  1045. break;
  1046. case SPREADSHEET_EXCEL_READER_TYPE_FONT:
  1047. $height = $this->_v($data, $pos+4);
  1048. $option = $this->_v($data, $pos+6);
  1049. $color = $this->_v($data, $pos+8);
  1050. $weight = $this->_v($data, $pos+10);
  1051. $under = ord($data[$pos+14]);
  1052. $font = '';
  1053. // Font name
  1054. $numchars = ord($data[$pos+18]);
  1055. if ((ord($data[$pos+19]) & 1) == 0) {
  1056. $font = substr($data, $pos+20, $numchars);
  1057. } else {
  1058. $font = substr($data, $pos+20, $numchars*2);
  1059. $font = $this->_encodeUTF16($font);
  1060. }
  1061. $this->fontRecords[] = array(
  1062. 'height' => $height / 20,
  1063. 'italic' => (bool)($option & 2),
  1064. 'color' => $color,
  1065. 'under' => !($under==0),
  1066. 'bold' => ($weight==700),
  1067. 'font' => $font,
  1068. 'raw' => $this->dumpHexData($data, $pos+3, $length)
  1069. );
  1070. break;
  1071. case SPREADSHEET_EXCEL_READER_TYPE_PALETTE:
  1072. $colors = ord($data[$pos+4]) | ord($data[$pos+5]) << 8;
  1073. for ($coli = 0; $coli < $colors; $coli++) {
  1074. $colOff = $pos + 2 + ($coli * 4);
  1075. $colr = ord($data[$colOff]);
  1076. $colg = ord($data[$colOff+1]);
  1077. $colb = ord($data[$colOff+2]);
  1078. $this->colors[0x07 + $coli] = '#' . $this->myhex($colr) . $this->myhex($colg) . $this->myhex($colb);
  1079. }
  1080. break;
  1081. case SPREADSHEET_EXCEL_READER_TYPE_XF:
  1082. $fontIndexCode = (ord($data[$pos+4]) | ord($data[$pos+5]) << 8) - 1;
  1083. $fontIndexCode = max(0, $fontIndexCode);
  1084. $indexCode = ord($data[$pos+6]) | ord($data[$pos+7]) << 8;
  1085. $alignbit = ord($data[$pos+10]) & 3;
  1086. $bgi = (ord($data[$pos+22]) | ord($data[$pos+23]) << 8) & 0x3FFF;
  1087. $bgcolor = ($bgi & 0x7F);
  1088. // $bgcolor = ($bgi & 0x3f80) >> 7;
  1089. $align = '';
  1090. if ($alignbit==3) { $align="right"; }
  1091. if ($alignbit==2) { $align="center"; }
  1092. $fillPattern = (ord($data[$pos+21]) & 0xFC) >> 2;
  1093. if ($fillPattern == 0) {
  1094. $bgcolor = '';
  1095. }
  1096. $xf = array();
  1097. $xf['formatIndex'] = $indexCode;
  1098. $xf['align'] = $align;
  1099. $xf['fontIndex'] = $fontIndexCode;
  1100. $xf['bgColor'] = $bgcolor;
  1101. $xf['fillPattern'] = $fillPattern;
  1102. $border = ord($data[$pos+14]) | (ord($data[$pos+15]) << 8) | (ord($data[$pos+16]) << 16) | (ord($data[$pos+17]) << 24);
  1103. $xf['borderLeft'] = $this->lineStyles[($border & 0xF)];
  1104. $xf['borderRight'] = $this->lineStyles[($border & 0xF0) >> 4];
  1105. $xf['borderTop'] = $this->lineStyles[($border & 0xF00) >> 8];
  1106. $xf['borderBottom'] = $this->lineStyles[($border & 0xF000) >> 12];
  1107. $xf['borderLeftColor'] = ($border & 0x7F0000) >> 16;
  1108. $xf['borderRightColor'] = ($border & 0x3F800000) >> 23;
  1109. $border = (ord($data[$pos+18]) | ord($data[$pos+19]) << 8);
  1110. $xf['borderTopColor'] = ($border & 0x7F);
  1111. $xf['borderBottomColor'] = ($border & 0x3F80) >> 7;
  1112. if (array_key_exists($indexCode, $this->dateFormats)) {
  1113. $xf['type'] = 'date';
  1114. $xf['format'] = $this->dateFormats[$indexCode];
  1115. if ($align=='') { $xf['align'] = 'right'; }
  1116. } elseif (array_key_exists($indexCode, $this->numberFormats)) {
  1117. $xf['type'] = 'number';
  1118. $xf['format'] = $this->numberFormats[$indexCode];
  1119. if ($align=='') { $xf['align'] = 'right'; }
  1120. } else {
  1121. $isdate = FALSE;
  1122. $formatstr = '';
  1123. if ($indexCode > 0) {
  1124. if (isset($this->formatRecords[$indexCode]))
  1125. $formatstr = $this->formatRecords[$indexCode];
  1126. if ($formatstr!='') {
  1127. $tmp = preg_replace("/\;.*/", '', $formatstr);
  1128. $tmp = preg_replace("/^\[[^\]]*\]/", '', $tmp);
  1129. if (preg_match("/[^hmsday\/\-:\s\\\,AMP]/i", $tmp) == 0) { // found day and time format
  1130. $isdate = TRUE;
  1131. $formatstr = $tmp;
  1132. $formatstr = str_replace(array('AM/PM', 'mmmm', 'mmm'), array('a', 'F', 'M'), $formatstr);
  1133. // m/mm are used for both minutes and months - oh SNAP!
  1134. // This mess tries to fix for that.
  1135. // 'm' == minutes only if following h/hh or preceding s/ss
  1136. $formatstr = preg_replace("/(h:?)mm?/", "$1i", $formatstr);
  1137. $formatstr = preg_replace("/mm?(:?s)/", "i$1", $formatstr);
  1138. // A single 'm' = n in PHP
  1139. $formatstr = preg_replace("/(^|[^m])m([^m]|$)/", '$1n$2', $formatstr);
  1140. $formatstr = preg_replace("/(^|[^m])m([^m]|$)/", '$1n$2', $formatstr);
  1141. // else it's months
  1142. $formatstr = str_replace('mm', 'm', $formatstr);
  1143. // Convert single 'd' to 'j'
  1144. $formatstr = preg_replace("/(^|[^d])d([^d]|$)/", '$1j$2', $formatstr);
  1145. $formatstr = str_replace(array('dddd', 'ddd', 'dd', 'yyyy', 'yy', 'hh', 'h'), array('l', 'D', 'd', 'Y', 'y', 'H', 'g'), $formatstr);
  1146. $formatstr = preg_replace("/ss?/", 's', $formatstr);
  1147. }
  1148. }
  1149. }
  1150. if ($isdate) {
  1151. $xf['type'] = 'date';
  1152. $xf['format'] = $formatstr;
  1153. if ($align=='') { $xf['align'] = 'right'; }
  1154. } else {
  1155. // If the format string has a 0 or # in it, we'll assume it's a number
  1156. if (preg_match("/[0#]/", $formatstr)) {
  1157. $xf['type'] = 'number';
  1158. if ($align=='') { $xf['align']='right'; }
  1159. } else {
  1160. $xf['type'] = 'other';
  1161. }
  1162. $xf['format'] = $formatstr;
  1163. $xf['code'] = $indexCode;
  1164. }
  1165. }
  1166. $this->xfRecords[] = $xf;
  1167. break;
  1168. case SPREADSHEET_EXCEL_READER_TYPE_NINETEENFOUR:
  1169. $this->nineteenFour = (ord($data[$pos+4]) == 1);
  1170. break;
  1171. case SPREADSHEET_EXCEL_READER_TYPE_BOUNDSHEET:
  1172. $rec_offset = $this->_getInt4d($data, $pos+4);
  1173. $rec_typeFlag = ord($data[$pos+8]);
  1174. $rec_visibilityFlag = ord($data[$pos+9]);
  1175. $rec_length = ord($data[$pos+10]);
  1176. if ($version == SPREADSHEET_EXCEL_READER_BIFF8) {
  1177. $chartype = ord($data[$pos+11]);
  1178. if ($chartype == 0) {
  1179. $rec_name = substr($data, $pos+12, $rec_length);
  1180. } else {
  1181. $rec_name = $this->_encodeUTF16(substr($data, $pos+12, $rec_length*2));
  1182. }
  1183. } elseif ($version == SPREADSHEET_EXCEL_READER_BIFF7) {
  1184. $rec_name = substr($data, $pos+11, $rec_length);
  1185. }
  1186. $this->boundsheets[] = array('name' => $rec_name, 'offset' => $rec_offset);
  1187. break;
  1188. }
  1189. $pos += $length + 4;
  1190. $code = ord($data[$pos]) | ord($data[$pos+1])<<8;
  1191. $length = ord($data[$pos+2]) | ord($data[$pos+3])<<8;
  1192. }
  1193. foreach ($this->boundsheets as $key => $val) {
  1194. $this->sn = $key;
  1195. $this->_parsesheet($val['offset']);
  1196. }
  1197. return true;
  1198. }
  1199. /**
  1200. * Parse a worksheet
  1201. */
  1202. protected function _parsesheet($spos) {
  1203. $cont = true;
  1204. $data = $this->data;
  1205. // read BOF
  1206. $code = ord($data[$spos]) | ord($data[$spos+1])<<8;
  1207. $length = ord($data[$spos+2]) | ord($data[$spos+3])<<8;
  1208. $version = ord($data[$spos + 4]) | ord($data[$spos + 5])<<8;
  1209. $substreamType = ord($data[$spos + 6]) | ord($data[$spos + 7])<<8;
  1210. if (($version != SPREADSHEET_EXCEL_READER_BIFF8) && ($version != SPREADSHEET_EXCEL_READER_BIFF7)) {
  1211. return -1;
  1212. }
  1213. if ($substreamType != SPREADSHEET_EXCEL_READER_WORKSHEET) {
  1214. return -2;
  1215. }
  1216. $spos += $length + 4;
  1217. while ($cont) {
  1218. $lowcode = ord($data[$spos]);
  1219. if ($lowcode == SPREADSHEET_EXCEL_READER_TYPE_EOF) break;
  1220. $code = $lowcode | ord($data[$spos+1])<<8;
  1221. $length = ord($data[$spos+2]) | ord($data[$spos+3])<<8;
  1222. $spos += 4;
  1223. $this->sheets[$this->sn]['maxrow'] = $this->_rowoffset - 1;
  1224. $this->sheets[$this->sn]['maxcol'] = $this->_coloffset - 1;
  1225. unset($this->rectype);
  1226. switch ($code) {
  1227. case SPREADSHEET_EXCEL_READER_TYPE_DIMENSION:
  1228. if (!isset($this->numRows)) {
  1229. if (($length == 10) || ($version == SPREADSHEET_EXCEL_READER_BIFF7)) {
  1230. $this->sheets[$this->sn]['numRows'] = ord($data[$spos+2]) | ord($data[$spos+3]) << 8;
  1231. $this->sheets[$this->sn]['numCols'] = ord($data[$spos+6]) | ord($data[$spos+7]) << 8;
  1232. } else {
  1233. $this->sheets[$this->sn]['numRows'] = ord($data[$spos+4]) | ord($data[$spos+5]) << 8;
  1234. $this->sheets[$this->sn]['numCols'] = ord($data[$spos+10]) | ord($data[$spos+11]) << 8;
  1235. }
  1236. }
  1237. break;
  1238. case SPREADSHEET_EXCEL_READER_TYPE_MERGEDCELLS:
  1239. $cellRanges = ord($data[$spos]) | ord($data[$spos+1])<<8;
  1240. for ($i = 0; $i < $cellRanges; $i++) {
  1241. $fr = ord($data[$spos + 8*$i + 2]) | ord($data[$spos + 8*$i + 3])<<8;
  1242. $lr = ord($data[$spos + 8*$i + 4]) | ord($data[$spos + 8*$i + 5])<<8;
  1243. $fc = ord($data[$spos + 8*$i + 6]) | ord($data[$spos + 8*$i + 7])<<8;
  1244. $lc = ord($data[$spos + 8*$i + 8]) | ord($data[$spos + 8*$i + 9])<<8;
  1245. if ($lr - $fr > 0) {
  1246. $this->sheets[$this->sn]['cellsInfo'][$fr+1][$fc+1]['rowspan'] = $lr - $fr + 1;
  1247. }
  1248. if ($lc - $fc > 0) {
  1249. $this->sheets[$this->sn]['cellsInfo'][$fr+1][$fc+1]['colspan'] = $lc - $fc + 1;
  1250. }
  1251. }
  1252. break;
  1253. case SPREADSHEET_EXCEL_READER_TYPE_RK:
  1254. case SPREADSHEET_EXCEL_READER_TYPE_RK2:
  1255. $row = ord($data[$spos]) | ord($data[$spos+1])<<8;
  1256. $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8;
  1257. $rknum = $this->_getInt4d($data, $spos + 6);
  1258. $numValue = $this->_GetIEEE754($rknum);
  1259. $info = $this->_getCellDetails($spos, $numValue, $column);
  1260. $this->addcell($row, $column, $info['string'], $info);
  1261. break;
  1262. case SPREADSHEET_EXCEL_READER_TYPE_LABELSST:
  1263. $row = ord($data[$spos]) | ord($data[$spos+1])<<8;
  1264. $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8;
  1265. $xfindex = ord($data[$spos+4]) | ord($data[$spos+5])<<8;
  1266. $index = $this->_getInt4d($data, $spos + 6);
  1267. $this->addcell($row, $column, $this->sst[$index], array('xfIndex' => $xfindex) );
  1268. break;
  1269. case SPREADSHEET_EXCEL_READER_TYPE_MULRK:
  1270. $row = ord($data[$spos]) | ord($data[$spos+1])<<8;
  1271. $colFirst = ord($data[$spos+2]) | ord($data[$spos+3])<<8;
  1272. $colLast = ord($data[$spos + $length - 2]) | ord($data[$spos + $length - 1])<<8;
  1273. $columns = $colLast - $colFirst + 1;
  1274. $tmppos = $spos+4;
  1275. for ($i = 0; $i < $columns; $i++) {
  1276. $numValue = $this->_GetIEEE754($this->_getInt4d($data, $tmppos + 2));
  1277. $info = $this->_getCellDetails($tmppos-4, $numValue, $colFirst + $i + 1);
  1278. $tmppos += 6;
  1279. $this->addcell($row, $colFirst + $i, $info['string'], $info);
  1280. }
  1281. break;
  1282. case SPREADSHEET_EXCEL_READER_TYPE_NUMBER:
  1283. $row = ord($data[$spos]) | ord($data[$spos+1])<<8;
  1284. $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8;
  1285. $tmp = unpack("ddouble", substr($data, $spos + 6, 8)); // It machine machine dependent
  1286. if ($this->isDate($spos)) {
  1287. $numValue = $tmp['double'];
  1288. } else {
  1289. $numValue = $this->createNumber($spos);
  1290. }
  1291. $info = $this->_getCellDetails($spos, $numValue, $column);
  1292. $this->addcell($row, $column, $info['string'], $info);
  1293. break;
  1294. case SPREADSHEET_EXCEL_READER_TYPE_FORMULA:
  1295. case SPREADSHEET_EXCEL_READER_TYPE_FORMULA2:
  1296. $row = ord($data[$spos]) | ord($data[$spos+1])<<8;
  1297. $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8;
  1298. if ((ord($data[$spos+6])==0) && (ord($data[$spos+12])==255) && (ord($data[$spos+13])==255)) {
  1299. //String formula. Result follows in a STRING record
  1300. // This row/col are stored to be referenced in that record
  1301. // http://code.google.com/p/php-excel-reader/issues/detail?id=4
  1302. $previousRow = $row;
  1303. $previousCol = $column;
  1304. } elseif ((ord($data[$spos+6])==1) && (ord($data[$spos+12])==255) && (ord($data[$spos+13])==255)) {
  1305. //Boolean formula. Result is in +2; 0=false,1=true
  1306. // http://code.google.com/p/php-excel-reader/issues/detail?id=4
  1307. if (ord($this->data[$spos+8])==1) {
  1308. $this->addcell($row, $column, "TRUE");
  1309. } else {
  1310. $this->addcell($row, $column, "FALSE");
  1311. }
  1312. } elseif ((ord($data[$spos+6])==2) && (ord($data[$spos+12])==255) && (ord($data[$spos+13])==255)) {
  1313. //Error formula. Error code is in +2;
  1314. } elseif ((ord($data[$spos+6])==3) && (ord($data[$spos+12])==255) && (ord($data[$spos+13])==255)) {
  1315. //Formula result is a null string.
  1316. $this->addcell($row, $column, '');
  1317. } else {
  1318. // result is a number, so first 14 bytes are just like a _NUMBER record
  1319. $tmp = unpack("ddouble", substr($data, $spos + 6, 8)); // It machine machine dependent
  1320. if ($this->isDate($spos)) {
  1321. $numValue = $tmp['double'];
  1322. } else {
  1323. $numValue = $this->createNumber($spos);
  1324. }
  1325. $info = $this->_getCellDetails($spos, $numValue, $column);
  1326. $this->addcell($row, $column, $info['string'], $info);
  1327. }
  1328. break;
  1329. case SPREADSHEET_EXCEL_READER_TYPE_BOOLERR:
  1330. $row = ord($data[$spos]) | ord($data[$spos+1])<<8;
  1331. $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8;
  1332. $string = ord($data[$spos+6]);
  1333. $this->addcell($row, $column, $string);
  1334. break;
  1335. case SPREADSHEET_EXCEL_READER_TYPE_STRING:
  1336. // http://code.google.com/p/php-excel-reader/issues/detail?id=4
  1337. if ($version == SPREADSHEET_EXCEL_READER_BIFF8) {
  1338. // Unicode 16 string, like an SST record
  1339. $xpos = $spos;
  1340. $numChars =ord($data[$xpos]) | (ord($data[$xpos+1]) << 8);
  1341. $xpos += 2;
  1342. $optionFlags =ord($data[$xpos]);
  1343. $xpos++;
  1344. $asciiEncoding = (($optionFlags &0x01) == 0) ;
  1345. $extendedString = (($optionFlags & 0x04) != 0);
  1346. // See if string contains formatting information
  1347. $richString = (($optionFlags & 0x08) != 0);
  1348. if ($richString) {
  1349. // Read in the crun
  1350. $formattingRuns =ord($data[$xpos]) | (ord($data[$xpos+1]) << 8);
  1351. $xpos += 2;
  1352. }
  1353. if ($extendedString) {
  1354. // Read in cchExtRst
  1355. $extendedRunLength =$this->_getInt4d($this->data, $xpos);
  1356. $xpos += 4;
  1357. }
  1358. $len = ($asciiEncoding)?$numChars : $numChars*2;
  1359. $retstr =substr($data, $xpos, $len);
  1360. $xpos += $len;
  1361. $retstr = ($asciiEncoding)? $retstr : $this->_encodeUTF16($retstr);
  1362. } elseif ($version == SPREADSHEET_EXCEL_READER_BIFF7) {
  1363. // Simple byte string
  1364. $xpos = $spos;
  1365. $numChars =ord($data[$xpos]) | (ord($data[$xpos+1]) << 8);
  1366. $xpos += 2;
  1367. $retstr =substr($data, $xpos, $numChars);
  1368. }
  1369. $this->addcell($previousRow, $previousCol, $retstr);
  1370. break;
  1371. case SPREADSHEET_EXCEL_READER_TYPE_ROW:
  1372. $row = ord($data[$spos]) | ord($data[$spos+1])<<8;
  1373. $rowInfo = ord($data[$spos + 6]) | ((ord($data[$spos+7]) << 8) & 0x7FFF);
  1374. if (($rowInfo & 0x8000) > 0) {
  1375. $rowHeight = -1;
  1376. } else {
  1377. $rowHeight = $rowInfo & 0x7FFF;
  1378. }
  1379. $rowHidden = (ord($data[$spos + 12]) & 0x20) >> 5;
  1380. $this->rowInfo[$this->sn][$row+1] = Array('height' => $rowHeight / 20, 'hidden' => $rowHidden );
  1381. break;
  1382. case SPREADSHEET_EXCEL_READER_TYPE_DBCELL:
  1383. break;
  1384. case SPREADSHEET_EXCEL_READER_TYPE_MULBLANK:
  1385. $row = ord($data[$spos]) | ord($data[$spos+1])<<8;
  1386. $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8;
  1387. $cols = ($length / 2) - 3;
  1388. for ($c = 0; $c < $cols; $c++) {
  1389. $xfindex = ord($data[$spos + 4 + ($c * 2)]) | ord($data[$spos + 5 + ($c * 2)])<<8;
  1390. $this->addcell($row, $column + $c, '', array('xfIndex' => $xfindex));
  1391. }
  1392. break;
  1393. case SPREADSHEET_EXCEL_READER_TYPE_LABEL:
  1394. $row = ord($data[$spos]) | ord($data[$spos+1])<<8;
  1395. $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8;
  1396. $this->addcell($row, $column, substr($data, $spos + 8, ord($data[$spos + 6]) | ord

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