PluginProbe
TablePress – Tables in WordPress made easy / 2.2.3
TablePress – Tables in WordPress made easy v2.2.3
3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 All 44 releases
tablepress / libraries / excel-reader.class.php

excel-reader.class.php in TablePress – Tables in WordPress made easy 2.2.3, at libraries/excel-reader.class.php

2,536 lines 73.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Excel 97/2003 Reader Class
4 *
5 * Based on PHP Excel Reader 2.21.
6 *
7 * @link https://code.google.com/archive/p/php-excel-reader/
8 *
9 * @package TablePress
10 * @subpackage Import
11 * @author Matt Kruse, Matt Roxburgh, Vadim Tkachenko, Tobias Bäthge
12 * @since 1.1.0
13 */
14
15 // Prohibit direct script loading.
16 defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
17
18 /**
19 * A class for reading Microsoft Excel (97/2003) Spreadsheets.
20 *
21 * Version 2.21
22 *
23 * Enhanced and maintained by Matt Kruse <https://mattkruse.com/>
24 * Maintained at https://code.google.com/archive/p/php-excel-reader/
25 * Licensed under MIT license
26 *
27 * Format parsing and MUCH more contributed by Matt Roxburgh
28 *
29 * Cleanup and changes for TablePress by Tobias Bäthge
30 * --------------------------------------------------------------------------
31 */
32
33 define( 'NUM_BIG_BLOCK_DEPOT_BLOCKS_POS', 0x2c );
34 define( 'SMALL_BLOCK_DEPOT_BLOCK_POS', 0x3c );
35 define( 'ROOT_START_BLOCK_POS', 0x30 );
36 define( 'BIG_BLOCK_SIZE', 0x200 );
37 define( 'SMALL_BLOCK_SIZE', 0x40 );
38 define( 'EXTENSION_BLOCK_POS', 0x44 );
39 define( 'NUM_EXTENSION_BLOCK_POS', 0x48 );
40 define( 'PROPERTY_STORAGE_BLOCK_SIZE', 0x80 );
41 define( 'BIG_BLOCK_DEPOT_BLOCKS_POS', 0x4c );
42 define( 'SMALL_BLOCK_THRESHOLD', 0x1000 );
43
44 // property storage offsets
45 define( 'SIZE_OF_NAME_POS', 0x40 );
46 define( 'TYPE_POS', 0x42 );
47 define( 'START_BLOCK_POS', 0x74 );
48 define( 'SIZE_POS', 0x78 );
49
50 define( 'IDENTIFIER_OLE', pack( 'CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 ) );
51
52 /**
53 * OLERead class
54 */
55 class OLERead {
56
57 /**
58 * [$data description]
59 *
60 * @since 1.0.0
61 * @var string
62 */
63 protected $data = '';
64
65 /**
66 * [$error description]
67 *
68 * @since 1.0.0
69 * @var int
70 */
71 public $error;
72
73 /**
74 * [$bigBlockChain description]
75 *
76 * @since 1.0.0
77 * @var array
78 */
79 protected $bigBlockChain = array();
80
81 /**
82 * [$smallBlockChain description]
83 *
84 * @since 1.0.0
85 * @var array
86 */
87 protected $smallBlockChain = array();
88
89 /**
90 * [$entry description]
91 *
92 * @since 1.0.0
93 * @var [type]
94 */
95 protected $entry;
96
97 /**
98 * [$props description]
99 *
100 * @since 1.0.0
101 * @var [type]
102 */
103 protected $props;
104
105 /**
106 * [$wrkbook description]
107 *
108 * @since 1.0.0
109 * @var [type]
110 */
111 protected $wrkbook;
112
113 /**
114 * [$rootentry description]
115 *
116 * @since 1.0.0
117 * @var [type]
118 */
119 protected $rootentry;
120
121 /**
122 * Class constructor.
123 *
124 * @since 1.0.0
125 */
126 public function __construct() {
127 // Unused.
128 }
129
130 /**
131 * [read description]
132 *
133 * @since 1.0.0
134 *
135 * @param string $data [description]
136 * @return [type] [description]
137 */
138 public function read( $data ) {
139 $this->data = $data;
140 if ( ! $this->data ) {
141 $this->error = 1;
142 return false;
143 }
144 if ( str_starts_with( $this->data, IDENTIFIER_OLE ) ) {
145 $this->error = 2;
146 return false;
147 }
148 $numBigBlockDepotBlocks = $this->_GetInt4d( $this->data, NUM_BIG_BLOCK_DEPOT_BLOCKS_POS );
149 $sbdStartBlock = $this->_GetInt4d( $this->data, SMALL_BLOCK_DEPOT_BLOCK_POS );
150 $rootStartBlock = $this->_GetInt4d( $this->data, ROOT_START_BLOCK_POS );
151 $extensionBlock = $this->_GetInt4d( $this->data, EXTENSION_BLOCK_POS );
152 $numExtensionBlocks = $this->_GetInt4d( $this->data, NUM_EXTENSION_BLOCK_POS );
153
154 $bigBlockDepotBlocks = array();
155 $pos = BIG_BLOCK_DEPOT_BLOCKS_POS;
156 $bbdBlocks = $numBigBlockDepotBlocks;
157 if ( 0 !== $numExtensionBlocks ) {
158 $bbdBlocks = ( BIG_BLOCK_SIZE - BIG_BLOCK_DEPOT_BLOCKS_POS ) / 4;
159 }
160
161 for ( $i = 0; $i < $bbdBlocks; $i++ ) {
162 $bigBlockDepotBlocks[ $i ] = $this->_GetInt4d( $this->data, $pos );
163 $pos += 4;
164 }
165
166 for ( $j = 0; $j < $numExtensionBlocks; $j++ ) {
167 $pos = ( $extensionBlock + 1 ) * BIG_BLOCK_SIZE;
168 $blocksToRead = min( $numBigBlockDepotBlocks - $bbdBlocks, BIG_BLOCK_SIZE / 4 - 1 );
169
170 for ( $i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; $i++ ) {
171 $bigBlockDepotBlocks[ $i ] = $this->_GetInt4d( $this->data, $pos );
172 $pos += 4;
173 }
174
175 $bbdBlocks += $blocksToRead;
176 if ( $bbdBlocks < $numBigBlockDepotBlocks ) {
177 $extensionBlock = $this->_GetInt4d( $this->data, $pos );
178 }
179 }
180
181 // readBigBlockDepot()
182 $index = 0;
183 $this->bigBlockChain = array();
184
185 for ( $i = 0; $i < $numBigBlockDepotBlocks; $i++ ) {
186 $pos = ( $bigBlockDepotBlocks[ $i ] + 1 ) * BIG_BLOCK_SIZE;
187 for ( $j = 0; $j < BIG_BLOCK_SIZE / 4; $j++ ) {
188 $this->bigBlockChain[ $index ] = $this->_GetInt4d( $this->data, $pos );
189 $pos += 4;
190 ++$index;
191 }
192 }
193
194 // readSmallBlockDepot();
195 $index = 0;
196 $sbdBlock = $sbdStartBlock;
197 $this->smallBlockChain = array();
198
199 while ( -2 !== $sbdBlock ) {
200 $pos = ( $sbdBlock + 1 ) * BIG_BLOCK_SIZE;
201 for ( $j = 0; $j < BIG_BLOCK_SIZE / 4; $j++ ) {
202 $this->smallBlockChain[ $index ] = $this->_GetInt4d( $this->data, $pos );
203 $pos += 4;
204 ++$index;
205 }
206 $sbdBlock = $this->bigBlockChain[ $sbdBlock ];
207 }
208
209 // readData(rootStartBlock)
210 $block = $rootStartBlock;
211 $this->entry = $this->_readData( $block );
212 $this->_readPropertySets();
213 }
214
215 /**
216 * [_readData description]
217 *
218 * @since 1.0.0
219 *
220 * @param [type] $bl [description]
221 * @return string [description]
222 */
223 protected function _readData( $bl ): string {
224 $block = $bl;
225 $data = '';
226 while ( -2 !== $block ) {
227 $pos = ( $block + 1 ) * BIG_BLOCK_SIZE;
228 $data = $data . substr( $this->data, $pos, BIG_BLOCK_SIZE );
229 $block = $this->bigBlockChain[ $block ];
230 }
231 return $data;
232 }
233
234 /**
235 * [_readPropertySets description]
236 *
237 * @since 1.0.0
238 *
239 * @return [type] [description]
240 */
241 protected function _readPropertySets() {
242 $offset = 0;
243 while ( $offset < strlen( $this->entry ) ) {
244 $d = substr( $this->entry, $offset, PROPERTY_STORAGE_BLOCK_SIZE );
245 $nameSize = ord( $d[ SIZE_OF_NAME_POS ] ) | ( ord( $d[ SIZE_OF_NAME_POS + 1 ] ) << 8 );
246 $type = ord( $d[ TYPE_POS ] );
247 $startBlock = $this->_GetInt4d( $d, START_BLOCK_POS );
248 $size = $this->_GetInt4d( $d, SIZE_POS );
249 $name = '';
250 for ( $i = 0; $i < $nameSize; $i++ ) {
251 $name .= $d[ $i ];
252 }
253 $name = str_replace( "\x00", '', $name );
254 $this->props[] = array(
255 'name' => $name,
256 'type' => $type,
257 'startBlock' => $startBlock,
258 'size' => $size,
259 );
260 if ( 'workbook' === strtolower( $name ) || 'book' === strtolower( $name ) ) {
261 $this->wrkbook = count( $this->props ) - 1;
262 }
263 if ( 'Root Entry' === $name ) {
264 $this->rootentry = count( $this->props ) - 1;
265 }
266 $offset += PROPERTY_STORAGE_BLOCK_SIZE;
267 }
268 }
269
270 /**
271 * [getWorkBook description]
272 *
273 * @since 1.0.0
274 *
275 * @return [type] [description]
276 */
277 public function getWorkBook() {
278 if ( $this->props[ $this->wrkbook ]['size'] < SMALL_BLOCK_THRESHOLD ) {
279 $rootdata = $this->_readData( $this->props[ $this->rootentry ]['startBlock'] );
280 $streamData = '';
281 $block = $this->props[ $this->wrkbook ]['startBlock'];
282 while ( -2 !== $block ) {
283 $pos = $block * SMALL_BLOCK_SIZE;
284 $streamData .= substr( $rootdata, $pos, SMALL_BLOCK_SIZE );
285 $block = $this->smallBlockChain[ $block ];
286 }
287 return $streamData;
288 } else {
289 $numBlocks = $this->props[ $this->wrkbook ]['size'] / BIG_BLOCK_SIZE;
290 if ( 0 !== $this->props[ $this->wrkbook ]['size'] % BIG_BLOCK_SIZE ) {
291 ++$numBlocks;
292 }
293
294 if ( 0 === $numBlocks ) {
295 return '';
296 }
297 $streamData = '';
298 $block = $this->props[ $this->wrkbook ]['startBlock'];
299 while ( -2 !== $block ) {
300 $pos = ( $block + 1 ) * BIG_BLOCK_SIZE;
301 $streamData .= substr( $this->data, $pos, BIG_BLOCK_SIZE );
302 $block = $this->bigBlockChain[ $block ];
303 }
304 return $streamData;
305 }
306 }
307
308 /**
309 * [_GetInt4d description]
310 *
311 * @since 1.0.0
312 *
313 * @param [type] $data [description]
314 * @param [type] $pos [description]
315 * @return int [description]
316 */
317 protected function _GetInt4d( $data, $pos ): int {
318 $value = ord( $data[ $pos ] ) | ( ord( $data[ $pos + 1 ] ) << 8 ) | ( ord( $data[ $pos + 2 ] ) << 16 ) | ( ord( $data[ $pos + 3 ] ) << 24 );
319 if ( $value >= 4294967294 ) {
320 $value = -2;
321 }
322 return $value;
323 }
324
325 } // class OLERead
326
327 define( 'SPREADSHEET_EXCEL_READER_BIFF8', 0x600 );
328 define( 'SPREADSHEET_EXCEL_READER_BIFF7', 0x500 );
329 define( 'SPREADSHEET_EXCEL_READER_WORKBOOKGLOBALS', 0x5 );
330 define( 'SPREADSHEET_EXCEL_READER_WORKSHEET', 0x10 );
331 define( 'SPREADSHEET_EXCEL_READER_TYPE_BOF', 0x809 );
332 define( 'SPREADSHEET_EXCEL_READER_TYPE_EOF', 0x0a );
333 define( 'SPREADSHEET_EXCEL_READER_TYPE_BOUNDSHEET', 0x85 );
334 define( 'SPREADSHEET_EXCEL_READER_TYPE_DIMENSION', 0x200 );
335 define( 'SPREADSHEET_EXCEL_READER_TYPE_ROW', 0x208 );
336 define( 'SPREADSHEET_EXCEL_READER_TYPE_DBCELL', 0xd7 );
337 define( 'SPREADSHEET_EXCEL_READER_TYPE_FILEPASS', 0x2f );
338 define( 'SPREADSHEET_EXCEL_READER_TYPE_NOTE', 0x1c );
339 define( 'SPREADSHEET_EXCEL_READER_TYPE_TXO', 0x1b6 );
340 define( 'SPREADSHEET_EXCEL_READER_TYPE_RK', 0x7e );
341 define( 'SPREADSHEET_EXCEL_READER_TYPE_RK2', 0x27e );
342 define( 'SPREADSHEET_EXCEL_READER_TYPE_MULRK', 0xbd );
343 define( 'SPREADSHEET_EXCEL_READER_TYPE_MULBLANK', 0xbe );
344 define( 'SPREADSHEET_EXCEL_READER_TYPE_INDEX', 0x20b );
345 define( 'SPREADSHEET_EXCEL_READER_TYPE_SST', 0xfc );
346 define( 'SPREADSHEET_EXCEL_READER_TYPE_EXTSST', 0xff );
347 define( 'SPREADSHEET_EXCEL_READER_TYPE_CONTINUE', 0x3c );
348 define( 'SPREADSHEET_EXCEL_READER_TYPE_LABEL', 0x204 );
349 define( 'SPREADSHEET_EXCEL_READER_TYPE_LABELSST', 0xfd );
350 define( 'SPREADSHEET_EXCEL_READER_TYPE_NUMBER', 0x203 );
351 define( 'SPREADSHEET_EXCEL_READER_TYPE_NAME', 0x18 );
352 define( 'SPREADSHEET_EXCEL_READER_TYPE_ARRAY', 0x221 );
353 define( 'SPREADSHEET_EXCEL_READER_TYPE_STRING', 0x207 );
354 define( 'SPREADSHEET_EXCEL_READER_TYPE_FORMULA', 0x406 );
355 define( 'SPREADSHEET_EXCEL_READER_TYPE_FORMULA2', 0x6 );
356 define( 'SPREADSHEET_EXCEL_READER_TYPE_FORMAT', 0x41e );
357 define( 'SPREADSHEET_EXCEL_READER_TYPE_XF', 0xe0 );
358 define( 'SPREADSHEET_EXCEL_READER_TYPE_BOOLERR', 0x205 );
359 define( 'SPREADSHEET_EXCEL_READER_TYPE_FONT', 0x0031 );
360 define( 'SPREADSHEET_EXCEL_READER_TYPE_PALETTE', 0x0092 );
361 define( 'SPREADSHEET_EXCEL_READER_TYPE_UNKNOWN', 0xffff );
362 define( 'SPREADSHEET_EXCEL_READER_TYPE_NINETEENFOUR', 0x22 );
363 define( 'SPREADSHEET_EXCEL_READER_TYPE_MERGEDCELLS', 0xE5 );
364 define( 'SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS', 25569 );
365 define( 'SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS1904', 24107 );
366 define( 'SPREADSHEET_EXCEL_READER_MSINADAY', 86400 );
367 define( 'SPREADSHEET_EXCEL_READER_TYPE_HYPER', 0x01b8 );
368 define( 'SPREADSHEET_EXCEL_READER_TYPE_COLINFO', 0x7d );
369 define( 'SPREADSHEET_EXCEL_READER_TYPE_DEFCOLWIDTH', 0x55 );
370 define( 'SPREADSHEET_EXCEL_READER_TYPE_STANDARDWIDTH', 0x99 );
371 define( 'SPREADSHEET_EXCEL_READER_DEF_NUM_FORMAT', '%s' );
372
373 /**
374 * Main Class
375 */
376 class Spreadsheet_Excel_Reader {
377
378 /*
379 * The following four public constants were added to make data retrieval easier.
380 */
381
382 /**
383 * [$colnames description]
384 *
385 * @since 1.0.0
386 * @var array
387 */
388 public $colnames = array();
389
390 /**
391 * [$colindexes description]
392 *
393 * @since 1.0.0
394 * @var array
395 */
396 public $colindexes = array();
397
398 /**
399 * [$standardColWidth description]
400 *
401 * @since 1.0.0
402 * @var int
403 */
404 public $standardColWidth = 0;
405
406 /**
407 * [$defaultColWidth description]
408 *
409 * @since 1.0.0
410 * @var int
411 */
412 public $defaultColWidth = 0;
413
414 /**
415 * [$store_extended_info description]
416 *
417 * @since 1.0.0
418 * @var [type]
419 */
420 protected $store_extended_info;
421
422 /**
423 * [$_encoderFunction description]
424 *
425 * @since 1.0.0
426 * @var [type]
427 */
428 protected $_encoderFunction;
429
430 /**
431 * [$nineteenFour description]
432 *
433 * @since 1.0.0
434 * @var [type]
435 */
436 protected $nineteenFour;
437
438 /**
439 * [$sn description]
440 *
441 * @since 1.0.0
442 * @var [type]
443 */
444 protected $sn;
445
446 /**
447 * [myHex description]
448 *
449 * @since 1.0.0
450 *
451 * @param [type] $d [description]
452 * @return string [description]
453 */
454 protected function myHex( $d ): string {
455 if ( $d < 16 ) {
456 return '0' . dechex( $d );
457 }
458 return dechex( $d );
459 }
460
461 /**
462 * [dumpHexData description]
463 *
464 * @since 1.0.0
465 *
466 * @param [type] $data [description]
467 * @param [type] $pos [description]
468 * @param [type] $length [description]
469 * @return string [description]
470 */
471 protected function dumpHexData( $data, $pos, $length ): string {
472 $info = '';
473 for ( $i = 0; $i <= $length; $i++ ) {
474 if ( 0 !== $i ) {
475 $info .= ' ';
476 }
477 $info .= $this->myHex( ord( $data[ $pos + $i ] ) ) . ( ord( $data[ $pos + $i ] ) > 31 ? '[' . $data[ $pos + $i ] . ']' : '' );
478 }
479 return $info;
480 }
481
482 /**
483 * [getCol description]
484 *
485 * @since 1.0.0
486 *
487 * @param [type] $col [description]
488 * @return [type] [description]
489 */
490 protected function getCol( $col ) {
491 if ( is_string( $col ) ) {
492 $col = strtolower( $col );
493 if ( array_key_exists( $col, $this->colnames ) ) {
494 $col = $this->colnames[ $col ];
495 }
496 }
497 return $col;
498 }
499
500 // PUBLIC API FUNCTIONS
501 // --------------------
502
503 /**
504 * [val description]
505 *
506 * @since 1.0.0
507 *
508 * @param [type] $row [description]
509 * @param [type] $col [description]
510 * @param int $sheet Optional. [description]
511 * @return [type] [description]
512 */
513 public function val( $row, $col, $sheet = 0 ) {
514 $col = $this->getCol( $col );
515 if ( array_key_exists( $row, $this->sheets[ $sheet ]['cells'] ) && array_key_exists( $col, $this->sheets[ $sheet ]['cells'][ $row ] ) ) {
516 return $this->sheets[ $sheet ]['cells'][ $row ][ $col ];
517 }
518 return '';
519 }
520
521 /**
522 * [value description]
523 *
524 * @since 1.0.0
525 *
526 * @param [type] $row [description]
527 * @param [type] $col [description]
528 * @param int $sheet Optional. [description]
529 * @return [type] [description]
530 */
531 public function value( $row, $col, $sheet = 0 ) {
532 return $this->val( $row, $col, $sheet );
533 }
534
535 /**
536 * [info description]
537 *
538 * @since 1.0.0
539 *
540 * @param [type] $row [description]
541 * @param [type] $col [description]
542 * @param string $type Optional. [description]
543 * @param int $sheet Optional. [description]
544 * @return [type] [description]
545 */
546 public function info( $row, $col, $type = '', $sheet = 0 ) {
547 $col = $this->getCol( $col );
548 if ( array_key_exists( 'cellsInfo', $this->sheets[ $sheet ] )
549 && array_key_exists( $row, $this->sheets[ $sheet ]['cellsInfo'] )
550 && array_key_exists( $col, $this->sheets[ $sheet ]['cellsInfo'][ $row ] )
551 && array_key_exists( $type, $this->sheets[ $sheet ]['cellsInfo'][ $row ][ $col ] ) ) {
552 return $this->sheets[ $sheet ]['cellsInfo'][ $row ][ $col ][ $type ];
553 }
554 return '';
555 }
556
557 /**
558 * [type description]
559 *
560 * @since 1.0.0
561 *
562 * @param [type] $row [description]
563 * @param [type] $col [description]
564 * @param int $sheet Optional. [description]
565 * @return [type] [description]
566 */
567 public function type( $row, $col, $sheet = 0 ) {
568 return $this->info( $row, $col, 'type', $sheet );
569 }
570
571 /**
572 * [raw description]
573 *
574 * @since 1.0.0
575 *
576 * @param [type] $row [description]
577 * @param [type] $col [description]
578 * @param int $sheet Optional. [description]
579 * @return [type] [description]
580 */
581 public function raw( $row, $col, $sheet = 0 ) {
582 return $this->info( $row, $col, 'raw', $sheet );
583 }
584
585 /**
586 * [rowspan description]
587 *
588 * @since 1.0.0
589 *
590 * @param [type] $row [description]
591 * @param [type] $col [description]
592 * @param int $sheet Optional. [description]
593 * @return int [description]
594 */
595 public function rowspan( $row, $col, $sheet = 0 ): int {
596 $value = $this->info( $row, $col, 'rowspan', $sheet );
597 if ( '' === $value ) {
598 return 1;
599 } else {
600 $value = (int) $value;
601 }
602 return $value;
603 }
604
605 /**
606 * [colspan description]
607 *
608 * @since 1.0.0
609 *
610 * @param [type] $row [description]
611 * @param [type] $col [description]
612 * @param int $sheet Optional. [description]
613 * @return int [description]
614 */
615 public function colspan( $row, $col, $sheet = 0 ): int {
616 $value = $this->info( $row, $col, 'colspan', $sheet );
617 if ( '' === $value ) {
618 return 1;
619 } else {
620 $value = (int) $value;
621 }
622 return $value;
623 }
624
625 /**
626 * [hyperlink description]
627 *
628 * @since 1.0.0
629 *
630 * @param [type] $row [description]
631 * @param [type] $col [description]
632 * @param int $sheet Optional. [description]
633 * @return [type] [description]
634 */
635 public function hyperlink( $row, $col, $sheet = 0 ) {
636 $link = $this->sheets[ $sheet ]['cellsInfo'][ $row ][ $col ]['hyperlink'];
637 if ( $link ) {
638 return $link['link'];
639 }
640 return '';
641 }
642
643 /**
644 * [rowcount description]
645 *
646 * @since 1.0.0
647 *
648 * @param int $sheet Optional. [description]
649 * @return [type] [description]
650 */
651 public function rowcount( $sheet = 0 ) {
652 return $this->sheets[ $sheet ]['numRows'];
653 }
654
655 /**
656 * [colcount description]
657 *
658 * @since 1.0.0
659 *
660 * @param int $sheet Optional. [description]
661 * @return [type] [description]
662 */
663 public function colcount( $sheet = 0 ) {
664 return $this->sheets[ $sheet ]['numCols'];
665 }
666
667 /**
668 * [colwidth description]
669 *
670 * @since 1.0.0
671 *
672 * @param [type] $col [description]
673 * @param int $sheet Optional. [description]
674 * @return [type] [description]
675 */
676 public function colwidth( $col, $sheet = 0 ) {
677 // Col width is actually the width of the number 0. So we have to estimate and come close
678 return $this->colInfo[ $sheet ][ $col ]['width'] / 9142 * 200;
679 }
680
681 /**
682 * [colhidden description]
683 *
684 * @since 1.0.0
685 *
686 * @param [type] $col [description]
687 * @param int $sheet Optional. [description]
688 * @return bool [description]
689 */
690 public function colhidden( $col, $sheet = 0 ): bool {
691 return (bool) $this->colInfo[ $sheet ][ $col ]['hidden'];
692 }
693
694 /**
695 * [rowheight description]
696 *
697 * @since 1.0.0
698 *
699 * @param [type] $row [description]
700 * @param int $sheet Optional. [description]
701 * @return [type] [description]
702 */
703 public function rowheight( $row, $sheet = 0 ) {
704 return $this->rowInfo[ $sheet ][ $row ]['height'];
705 }
706
707 /**
708 * [rowhidden description]
709 *
710 * @since 1.0.0
711 *
712 * @param [type] $row [description]
713 * @param int $sheet Optional. [description]
714 * @return bool [description]
715 */
716 public function rowhidden( $row, $sheet = 0 ): bool {
717 return (bool) $this->rowInfo[ $sheet ][ $row ]['hidden'];
718 }
719
720 // GET THE CSS FOR FORMATTING
721 // ==========================
722
723 /**
724 * [style description]
725 *
726 * @since 1.0.0
727 *
728 * @param [type] $row [description]
729 * @param [type] $col [description]
730 * @param int $sheet Optional. [description]
731 * @return string [description]
732 */
733 public function style( $row, $col, $sheet = 0 ): string {
734 $css = '';
735 $font = $this->font( $row, $col, $sheet );
736 if ( '' !== $font ) {
737 $css .= "font-family:{$font};";
738 }
739 $align = $this->align( $row, $col, $sheet );
740 if ( '' !== $align ) {
741 $css .= "text-align:{$align};";
742 }
743 $height = $this->height( $row, $col, $sheet );
744 if ( '' !== $height ) {
745 $css .= "font-size:{$height}px;";
746 }
747 $bgcolor = $this->bgColor( $row, $col, $sheet );
748 if ( '' !== $bgcolor ) {
749 $bgcolor = $this->colors[ $bgcolor ];
750 $css .= "background-color:{$bgcolor};";
751 }
752 $color = $this->color( $row, $col, $sheet );
753 if ( '' !== $color ) {
754 $css .= "color:{$color};";
755 }
756 $bold = $this->bold( $row, $col, $sheet );
757 if ( $bold ) {
758 $css .= 'font-weight:bold;';
759 }
760 $italic = $this->italic( $row, $col, $sheet );
761 if ( $italic ) {
762 $css .= 'font-style:italic;';
763 }
764 $underline = $this->underline( $row, $col, $sheet );
765 if ( $underline ) {
766 $css .= 'text-decoration:underline;';
767 }
768 // Borders
769 $bLeft = $this->borderLeft( $row, $col, $sheet );
770 $bRight = $this->borderRight( $row, $col, $sheet );
771 $bTop = $this->borderTop( $row, $col, $sheet );
772 $bBottom = $this->borderBottom( $row, $col, $sheet );
773 $bLeftCol = $this->borderLeftColor( $row, $col, $sheet );
774 $bRightCol = $this->borderRightColor( $row, $col, $sheet );
775 $bTopCol = $this->borderTopColor( $row, $col, $sheet );
776 $bBottomCol = $this->borderBottomColor( $row, $col, $sheet );
777 // Try to output the minimal required style.
778 if ( '' !== $bLeft && $bLeft === $bRight && $bRight === $bTop && $bTop === $bBottom ) {
779 $css .= 'border:' . $this->lineStylesCss[ $bLeft ] . ';';
780 } else {
781 if ( '' !== $bLeft ) {
782 $css .= 'border-left:' . $this->lineStylesCss[ $bLeft ] . ';';
783 }
784 if ( '' !== $bRight ) {
785 $css .= 'border-right:' . $this->lineStylesCss[ $bRight ] . ';';
786 }
787 if ( '' !== $bTop ) {
788 $css .= 'border-top:' . $this->lineStylesCss[ $bTop ] . ';';
789 }
790 if ( '' !== $bBottom ) {
791 $css .= 'border-bottom:' . $this->lineStylesCss[ $bBottom ] . ';';
792 }
793 }
794 // Only output border colors if there is an actual border specified.
795 if ( '' !== $bLeft && '' !== $bLeftCol ) {
796 $css .= "border-left-color:{$bLeftCol};";
797 }
798 if ( '' !== $bRight && '' !== $bRightCol ) {
799 $css .= "border-right-color:{$bRightCol};";
800 }
801 if ( '' !== $bTop && '' !== $bTopCol ) {
802 $css .= "border-top-color:{$bTopCol};";
803 }
804 if ( '' !== $bBottom && '' !== $bBottomCol ) {
805 $css .= "border-bottom-color:{$bBottomCol};";
806 }
807
808 return $css;
809 }
810
811 // FORMAT PROPERTIES
812 // =================
813
814 /**
815 * [format description]
816 *
817 * @since 1.0.0
818 *
819 * @param [type] $row [description]
820 * @param [type] $col [description]
821 * @param int $sheet Optional. [description]
822 * @return [type] [description]
823 */
824 public function format( $row, $col, $sheet = 0 ) {
825 return $this->info( $row, $col, 'format', $sheet );
826 }
827
828 /**
829 * [formatIndex description]
830 *
831 * @since 1.0.0
832 *
833 * @param [type] $row [description]
834 * @param [type] $col [description]
835 * @param int $sheet Optional. [description]
836 * @return [type] [description]
837 */
838 public function formatIndex( $row, $col, $sheet = 0 ) {
839 return $this->info( $row, $col, 'formatIndex', $sheet );
840 }
841
842 /**
843 * [formatColor description]
844 *
845 * @since 1.0.0
846 *
847 * @param [type] $row [description]
848 * @param [type] $col [description]
849 * @param int $sheet Optional. [description]
850 * @return [type] [description]
851 */
852 public function formatColor( $row, $col, $sheet = 0 ) {
853 return $this->info( $row, $col, 'formatColor', $sheet );
854 }
855
856 // CELL (XF) PROPERTIES
857 // ====================
858
859 /**
860 * [xfRecord description]
861 *
862 * @since 1.0.0
863 *
864 * @param [type] $row [description]
865 * @param [type] $col [description]
866 * @param int $sheet Optional. [description]
867 * @return [type] [description]
868 */
869 public function xfRecord( $row, $col, $sheet = 0 ) {
870 $xfIndex = $this->info( $row, $col, 'xfIndex', $sheet );
871 if ( '' !== $xfIndex ) {
872 return $this->xfRecords[ $xfIndex ];
873 }
874 return null;
875 }
876
877 /**
878 * [xfProperty description]
879 *
880 * @since 1.0.0
881 *
882 * @param [type] $row [description]
883 * @param [type] $col [description]
884 * @param [type] $sheet [description]
885 * @param [type] $prop [description]
886 * @return [type] [description]
887 */
888 public function xfProperty( $row, $col, $sheet, $prop ) {
889 $xfRecord = $this->xfRecord( $row, $col, $sheet );
890 if ( null !== $xfRecord ) {
891 return $xfRecord[ $prop ];
892 }
893 return '';
894 }
895
896 /**
897 * [align description]
898 *
899 * @since 1.0.0
900 *
901 * @param [type] $row [description]
902 * @param [type] $col [description]
903 * @param int $sheet Optional. [description]
904 * @return [type] [description]
905 */
906 public function align( $row, $col, $sheet = 0 ) {
907 return $this->xfProperty( $row, $col, $sheet, 'align' );
908 }
909
910 /**
911 * [bgColor description]
912 *
913 * @since 1.0.0
914 *
915 * @param [type] $row [description]
916 * @param [type] $col [description]
917 * @param int $sheet Optional. [description]
918 * @return [type] [description]
919 */
920 public function bgColor( $row, $col, $sheet = 0 ) {
921 return $this->xfProperty( $row, $col, $sheet, 'bgColor' );
922 }
923
924 /**
925 * [borderLeft description]
926 *
927 * @since 1.0.0
928 *
929 * @param [type] $row [description]
930 * @param [type] $col [description]
931 * @param int $sheet Optional. [description]
932 * @return [type] [description]
933 */
934 public function borderLeft( $row, $col, $sheet = 0 ) {
935 return $this->xfProperty( $row, $col, $sheet, 'borderLeft' );
936 }
937
938 /**
939 * [borderRight description]
940 *
941 * @since 1.0.0
942 *
943 * @param [type] $row [description]
944 * @param [type] $col [description]
945 * @param int $sheet Optional. [description]
946 * @return [type] [description]
947 */
948 public function borderRight( $row, $col, $sheet = 0 ) {
949 return $this->xfProperty( $row, $col, $sheet, 'borderRight' );
950 }
951
952 /**
953 * [borderTop description]
954 *
955 * @since 1.0.0
956 *
957 * @param [type] $row [description]
958 * @param [type] $col [description]
959 * @param int $sheet Optional. [description]
960 * @return [type] [description]
961 */
962 public function borderTop( $row, $col, $sheet = 0 ) {
963 return $this->xfProperty( $row, $col, $sheet, 'borderTop' );
964 }
965
966 /**
967 * [borderBottom description]
968 *
969 * @since 1.0.0
970 *
971 * @param [type] $row [description]
972 * @param [type] $col [description]
973 * @param int $sheet Optional. [description]
974 * @return [type] [description]
975 */
976 public function borderBottom( $row, $col, $sheet = 0 ) {
977 return $this->xfProperty( $row, $col, $sheet, 'borderBottom' );
978 }
979
980 /**
981 * [borderLeftColor description]
982 *
983 * @since 1.0.0
984 *
985 * @param [type] $row [description]
986 * @param [type] $col [description]
987 * @param int $sheet Optional. [description]
988 * @return [type] [description]
989 */
990 public function borderLeftColor( $row, $col, $sheet = 0 ) {
991 return $this->colors[ $this->xfProperty( $row, $col, $sheet, 'borderLeftColor' ) ];
992 }
993
994 /**
995 * [borderRightColor description]
996 *
997 * @since 1.0.0
998 *
999 * @param [type] $row [description]
1000 * @param [type] $col [description]
1001 * @param int $sheet Optional. [description]
1002 * @return [type] [description]
1003 */
1004 public function borderRightColor( $row, $col, $sheet = 0 ) {
1005 return $this->colors[ $this->xfProperty( $row, $col, $sheet, 'borderRightColor' ) ];
1006 }
1007
1008 /**
1009 * [borderTopColor description]
1010 *
1011 * @since 1.0.0
1012 *
1013 * @param [type] $row [description]
1014 * @param [type] $col [description]
1015 * @param int $sheet Optional. [description]
1016 * @return [type] [description]
1017 */
1018 public function borderTopColor( $row, $col, $sheet = 0 ) {
1019 return $this->colors[ $this->xfProperty( $row, $col, $sheet, 'borderTopColor' ) ];
1020 }
1021
1022 /**
1023 * [borderBottomColor description]
1024 *
1025 * @since 1.0.0
1026 *
1027 * @param [type] $row [description]
1028 * @param [type] $col [description]
1029 * @param int $sheet Optional. [description]
1030 * @return [type] [description]
1031 */
1032 public function borderBottomColor( $row, $col, $sheet = 0 ) {
1033 return $this->colors[ $this->xfProperty( $row, $col, $sheet, 'borderBottomColor' ) ];
1034 }
1035
1036 // FONT PROPERTIES
1037 // ===============
1038
1039 /**
1040 * [fontRecord description]
1041 *
1042 * @since 1.0.0
1043 *
1044 * @param [type] $row [description]
1045 * @param [type] $col [description]
1046 * @param int $sheet Optional. [description]
1047 * @return [type] [description]
1048 */
1049 public function fontRecord( $row, $col, $sheet = 0 ) {
1050 $xfRecord = $this->xfRecord( $row, $col, $sheet );
1051 if ( null !== $xfRecord ) {
1052 $font = $xfRecord['fontIndex'];
1053 if ( null !== $font ) {
1054 return $this->fontRecords[ $font ];
1055 }
1056 }
1057 return null;
1058 }
1059
1060 /**
1061 * [fontProperty description]
1062 *
1063 * @since 1.0.0
1064 *
1065 * @param [type] $row [description]
1066 * @param [type] $col [description]
1067 * @param int $sheet [description]
1068 * @param string $prop [description]
1069 * @return [type] [description]
1070 */
1071 public function fontProperty( $row, $col, $sheet, $prop ) {
1072 $font = $this->fontRecord( $row, $col, $sheet );
1073 if ( null !== $font ) {
1074 return $font[ $prop ];
1075 }
1076 return false;
1077 }
1078
1079 /**
1080 * [fontIndex description]
1081 *
1082 * @since 1.0.0
1083 *
1084 * @param [type] $row [description]
1085 * @param [type] $col [description]
1086 * @param int $sheet Optional. [description]
1087 * @return [type] [description]
1088 */
1089 public function fontIndex( $row, $col, $sheet = 0 ) {
1090 return $this->xfProperty( $row, $col, $sheet, 'fontIndex' );
1091 }
1092
1093 /**
1094 * [color description]
1095 *
1096 * @since 1.0.0
1097 *
1098 * @param [type] $row [description]
1099 * @param [type] $col [description]
1100 * @param int $sheet Optional. [description]
1101 * @return [type] [description]
1102 */
1103 public function color( $row, $col, $sheet = 0 ) {
1104 $formatColor = $this->formatColor( $row, $col, $sheet );
1105 if ( '' !== $formatColor ) {
1106 return $formatColor;
1107 }
1108 $ci = $this->fontProperty( $row, $col, $sheet, 'color' );
1109 return $this->rawColor( $ci );
1110 }
1111
1112 /**
1113 * [rawColor description]
1114 *
1115 * @since 1.0.0
1116 *
1117 * @param [type] $ci [description]
1118 * @return [type] [description]
1119 */
1120 public function rawColor( $ci ) {
1121 if ( 0x7FFF !== $ci && '' !== $ci ) {
1122 return $this->colors[ $ci ];
1123 }
1124 return '';
1125 }
1126
1127 /**
1128 * [bold description]
1129 *
1130 * @since 1.0.0
1131 *
1132 * @param [type] $row [description]
1133 * @param [type] $col [description]
1134 * @param int $sheet Optional. [description]
1135 * @return [type] [description]
1136 */
1137 public function bold( $row, $col, $sheet = 0 ) {
1138 return $this->fontProperty( $row, $col, $sheet, 'bold' );
1139 }
1140
1141 /**
1142 * [italic description]
1143 *
1144 * @since 1.0.0
1145 *
1146 * @param [type] $row [description]
1147 * @param [type] $col [description]
1148 * @param int $sheet Optional. [description]
1149 * @return [type] [description]
1150 */
1151 public function italic( $row, $col, $sheet = 0 ) {
1152 return $this->fontProperty( $row, $col, $sheet, 'italic' );
1153 }
1154
1155 /**
1156 * [underline description]
1157 *
1158 * @since 1.0.0
1159 *
1160 * @param [type] $row [description]
1161 * @param [type] $col [description]
1162 * @param int $sheet Optional. [description]
1163 * @return [type] [description]
1164 */
1165 public function underline( $row, $col, $sheet = 0 ) {
1166 return $this->fontProperty( $row, $col, $sheet, 'under' );
1167 }
1168
1169 /**
1170 * [height description]
1171 *
1172 * @since 1.0.0
1173 *
1174 * @param [type] $row [description]
1175 * @param [type] $col [description]
1176 * @param int $sheet Optional. [description]
1177 * @return [type] [description]
1178 */
1179 public function height( $row, $col, $sheet = 0 ) {
1180 return $this->fontProperty( $row, $col, $sheet, 'height' );
1181 }
1182
1183 /**
1184 * [font description]
1185 *
1186 * @since 1.0.0
1187 *
1188 * @param [type] $row [description]
1189 * @param [type] $col [description]
1190 * @param int $sheet Optional. [description]
1191 * @return [type] [description]
1192 */
1193 public function font( $row, $col, $sheet = 0 ) {
1194 return $this->fontProperty( $row, $col, $sheet, 'font' );
1195 }
1196
1197 // DUMP AN HTML TABLE OF THE ENTIRE XLS DATA
1198 // =========================================
1199
1200 /**
1201 * [dump description]
1202 *
1203 * @since 1.0.0
1204 *
1205 * @param bool $row_numbers Optional. [description]
1206 * @param bool $col_letters Optional. [description]
1207 * @param int $sheet Optional. [description]
1208 * @param string $table_class Optional. [description]
1209 * @return string [description]
1210 */
1211 public function dump( $row_numbers = false, $col_letters = false, $sheet = 0, $table_class = 'excel' ): string {
1212 $out = "<table class=\"$table_class\" cellspacing=0>";
1213 if ( $col_letters ) {
1214 $out .= "<thead>\n\t<tr>";
1215 if ( $row_numbers ) {
1216 $out .= "\n\t\t<th>&nbsp</th>";
1217 }
1218 for ( $i = 1; $i <= $this->colcount( $sheet ); $i++ ) {
1219 $style = 'width:' . ( $this->colwidth( $i, $sheet ) ) . 'px;';
1220 if ( $this->colhidden( $i, $sheet ) ) {
1221 $style .= 'display:none;';
1222 }
1223 $out .= "\n\t\t<th style=\"$style\">" . strtoupper( $this->colindexes[ $i ] ) . '</th>';
1224 }
1225 $out .= "</tr></thead>\n";
1226 }
1227
1228 $out .= "<tbody>\n";
1229 for ( $row = 1; $row <= $this->rowcount( $sheet ); $row++ ) {
1230 $rowheight = $this->rowheight( $row, $sheet );
1231 $style = 'height:' . ( $rowheight * ( 4 / 3 ) ) . 'px;';
1232 if ( $this->rowhidden( $row, $sheet ) ) {
1233 $style .= 'display:none;';
1234 }
1235 $out .= "\n\t<tr style=\"$style\">";
1236 if ( $row_numbers ) {
1237 $out .= "\n\t\t<th>{$row}</th>";
1238 }
1239 for ( $col = 1; $col <= $this->colcount( $sheet ); $col++ ) {
1240 // Account for Rowspans/Colspans
1241 $rowspan = $this->rowspan( $row, $col, $sheet );
1242 $colspan = $this->colspan( $row, $col, $sheet );
1243 for ( $i = 0; $i < $rowspan; $i++ ) {
1244 for ( $j = 0; $j < $colspan; $j++ ) {
1245 if ( $i > 0 || $j > 0 ) {
1246 $this->sheets[ $sheet ]['cellsInfo'][ $row + $i ][ $col + $j ]['dontprint'] = 1;
1247 }
1248 }
1249 }
1250 if ( ! $this->sheets[ $sheet ]['cellsInfo'][ $row ][ $col ]['dontprint'] ) {
1251 $style = $this->style( $row, $col, $sheet );
1252 if ( $this->colhidden( $col, $sheet ) ) {
1253 $style .= 'display:none;';
1254 }
1255 $out .= "\n\t\t<td style=\"$style\"" . ( $colspan > 1 ? " colspan={$colspan}" : '' ) . ( $rowspan > 1 ? " rowspan={$rowspan}" : '' ) . '>';
1256 $val = $this->val( $row, $col, $sheet );
1257 if ( '' === $val ) {
1258 $val = '&nbsp;';
1259 } else {
1260 $val = htmlentities( $val );
1261 $link = $this->hyperlink( $row, $col, $sheet );
1262 if ( '' !== $link ) {
1263 $val = "<a href=\"$link\">{$val}</a>";
1264 }
1265 }
1266 $out .= '<nobr>' . nl2br( $val ) . '</nobr>';
1267 $out .= '</td>';
1268 }
1269 }
1270 $out .= "</tr>\n";
1271 }
1272 $out .= '</tbody></table>';
1273 return $out;
1274 }
1275
1276 // --------------
1277 // END PUBLIC API
1278
1279 protected $boundsheets = array();
1280 protected $formatRecords = array();
1281 protected $fontRecords = array();
1282 protected $xfRecords = array();
1283 protected $colInfo = array();
1284 protected $rowInfo = array();
1285
1286 protected $sst = array();
1287 protected $sheets = array();
1288
1289 protected $data;
1290 protected $_ole;
1291 protected $_defaultEncoding = 'UTF-8';
1292 protected $_defaultFormat = SPREADSHEET_EXCEL_READER_DEF_NUM_FORMAT;
1293 protected $_columnsFormat = array();
1294 protected $_rowoffset = 1;
1295 protected $_coloffset = 1;
1296
1297 /**
1298 * List of default date formats used by Excel
1299 *
1300 * @since 1.0.0
1301 * @var array
1302 */
1303 protected $dateFormats = array(
1304 0xe => 'm/d/Y',
1305 0xf => 'M-d-Y',
1306 0x10 => 'd-M',
1307 0x11 => 'M-Y',
1308 0x12 => 'h:i a',
1309 0x13 => 'h:i:s a',
1310 0x14 => 'H:i',
1311 0x15 => 'H:i:s',
1312 0x16 => 'd/m/Y H:i',
1313 0x2d => 'i:s',
1314 0x2e => 'H:i:s',
1315 0x2f => 'i:s.S',
1316 );
1317
1318 /**
1319 * Default number formats used by Excel
1320 *
1321 * @since 1.0.0
1322 * @var array
1323 */
1324 protected $numberFormats = array(
1325 0x1 => '0',
1326 0x2 => '0.00',
1327 0x3 => '#,##0',
1328 0x4 => '#,##0.00',
1329 0x5 => '\$#,##0;(\$#,##0)',
1330 0x6 => '\$#,##0;[Red](\$#,##0)',
1331 0x7 => '\$#,##0.00;(\$#,##0.00)',
1332 0x8 => '\$#,##0.00;[Red](\$#,##0.00)',
1333 0x9 => '0%',
1334 0xa => '0.00%',
1335 0xb => '0.00E+00',
1336 0x25 => '#,##0;(#,##0)',
1337 0x26 => '#,##0;[Red](#,##0)',
1338 0x27 => '#,##0.00;(#,##0.00)',
1339 0x28 => '#,##0.00;[Red](#,##0.00)',
1340 0x29 => '#,##0;(#,##0)', // Not exact
1341 0x2a => '\$#,##0;(\$#,##0)', // Not exact
1342 0x2b => '#,##0.00;(#,##0.00)', // Not exact
1343 0x2c => '\$#,##0.00;(\$#,##0.00)', // Not exact
1344 0x30 => '##0.0E+0',
1345 );
1346
1347 /**
1348 * [$colors description]
1349 *
1350 * @since 1.0.0
1351 * @var array
1352 */
1353 protected $colors = array(
1354 0x00 => '#000000',
1355 0x01 => '#FFFFFF',
1356 0x02 => '#FF0000',
1357 0x03 => '#00FF00',
1358 0x04 => '#0000FF',
1359 0x05 => '#FFFF00',
1360 0x06 => '#FF00FF',
1361 0x07 => '#00FFFF',
1362 0x08 => '#000000',
1363 0x09 => '#FFFFFF',
1364 0x0A => '#FF0000',
1365 0x0B => '#00FF00',
1366 0x0C => '#0000FF',
1367 0x0D => '#FFFF00',
1368 0x0E => '#FF00FF',
1369 0x0F => '#00FFFF',
1370 0x10 => '#800000',
1371 0x11 => '#008000',
1372 0x12 => '#000080',
1373 0x13 => '#808000',
1374 0x14 => '#800080',
1375 0x15 => '#008080',
1376 0x16 => '#C0C0C0',
1377 0x17 => '#808080',
1378 0x18 => '#9999FF',
1379 0x19 => '#993366',
1380 0x1A => '#FFFFCC',
1381 0x1B => '#CCFFFF',
1382 0x1C => '#660066',
1383 0x1D => '#FF8080',
1384 0x1E => '#0066CC',
1385 0x1F => '#CCCCFF',
1386 0x20 => '#000080',
1387 0x21 => '#FF00FF',
1388 0x22 => '#FFFF00',
1389 0x23 => '#00FFFF',
1390 0x24 => '#800080',
1391 0x25 => '#800000',
1392 0x26 => '#008080',
1393 0x27 => '#0000FF',
1394 0x28 => '#00CCFF',
1395 0x29 => '#CCFFFF',
1396 0x2A => '#CCFFCC',
1397 0x2B => '#FFFF99',
1398 0x2C => '#99CCFF',
1399 0x2D => '#FF99CC',
1400 0x2E => '#CC99FF',
1401 0x2F => '#FFCC99',
1402 0x30 => '#3366FF',
1403 0x31 => '#33CCCC',
1404 0x32 => '#99CC00',
1405 0x33 => '#FFCC00',
1406 0x34 => '#FF9900',
1407 0x35 => '#FF6600',
1408 0x36 => '#666699',
1409 0x37 => '#969696',
1410 0x38 => '#003366',
1411 0x39 => '#339966',
1412 0x3A => '#003300',
1413 0x3B => '#333300',
1414 0x3C => '#993300',
1415 0x3D => '#993366',
1416 0x3E => '#333399',
1417 0x3F => '#333333',
1418 0x40 => '#000000',
1419 0x41 => '#FFFFFF',
1420 0x43 => '#000000',
1421 0x4D => '#000000',
1422 0x4E => '#FFFFFF',
1423 0x4F => '#000000',
1424 0x50 => '#FFFFFF',
1425 0x51 => '#000000',
1426 0x7FFF => '#000000',
1427 );
1428
1429 /**
1430 * [$lineStyles description]
1431 *
1432 * @since 1.0.0
1433 * @var array
1434 */
1435 protected $lineStyles = array(
1436 0x00 => '',
1437 0x01 => 'Thin',
1438 0x02 => 'Medium',
1439 0x03 => 'Dashed',
1440 0x04 => 'Dotted',
1441 0x05 => 'Thick',
1442 0x06 => 'Double',
1443 0x07 => 'Hair',
1444 0x08 => 'Medium dashed',
1445 0x09 => 'Thin dash-dotted',
1446 0x0A => 'Medium dash-dotted',
1447 0x0B => 'Thin dash-dot-dotted',
1448 0x0C => 'Medium dash-dot-dotted',
1449 0x0D => 'Slanted medium dash-dotted',
1450 );
1451
1452 /**
1453 * [$lineStylesCss description]
1454 *
1455 * @since 1.0.0
1456 * @var array
1457 */
1458 protected $lineStylesCss = array(
1459 'Thin' => '1px solid',
1460 'Medium' => '2px solid',
1461 'Dashed' => '1px dashed',
1462 'Dotted' => '1px dotted',
1463 'Thick' => '3px solid',
1464 'Double' => 'double',
1465 'Hair' => '1px solid',
1466 'Medium dashed' => '2px dashed',
1467 'Thin dash-dotted' => '1px dashed',
1468 'Medium dash-dotted' => '2px dashed',
1469 'Thin dash-dot-dotted' => '1px dashed',
1470 'Medium dash-dot-dotted' => '2px dashed',
1471 'Slanted medium dash-dotted' => '2px dashed',
1472 );
1473
1474 /**
1475 * [read16bitstring description]
1476 *
1477 * @since 1.0.0
1478 *
1479 * @param [type] $data [description]
1480 * @param [type] $start [description]
1481 * @return string [description]
1482 */
1483 protected function read16bitstring( $data, $start ): string {
1484 $len = 0;
1485 while ( ord( $data[ $start + $len ] ) + ord( $data[ $start + $len + 1 ] ) > 0 ) {
1486 ++$len;
1487 }
1488 return substr( $data, $start, $len );
1489 }
1490
1491 /**
1492 * [_format_value description]
1493 * ADDED by Matt Kruse for better formatting
1494 *
1495 * @since 1.0.0
1496 *
1497 * @param [type] $format [description]
1498 * @param [type] $num [description]
1499 * @param [type] $f [description]
1500 * @return array<string, mixed> [description]
1501 */
1502 protected function _format_value( $format, $num, $f ): array {
1503 // 49 = TEXT format
1504 // https://code.google.com/archive/p/php-excel-reader/issues/7
1505 if ( ( ! $f && '%s' === $format ) || ( 49 === (int) $f ) || ( 'GENERAL' === $format ) ) {
1506 return array(
1507 'string' => $num,
1508 'formatColor' => null,
1509 );
1510 }
1511
1512 // Custom pattern can be POSITIVE;NEGATIVE;ZERO
1513 // The "text" option as 4th parameter is not handled
1514 $parts = explode( ';', $format );
1515 $pattern = $parts[0];
1516 // Negative pattern
1517 if ( count( $parts ) > 2 && 0 === (int) $num ) {
1518 $pattern = $parts[2];
1519 }
1520 // Zero pattern
1521 if ( count( $parts ) > 1 && $num < 0 ) {
1522 $pattern = $parts[1];
1523 $num = abs( $num );
1524 }
1525
1526 $color = '';
1527 $matches = array();
1528 $color_regex = '/^\[(BLACK|BLUE|CYAN|GREEN|MAGENTA|RED|WHITE|YELLOW)\]/i';
1529 if ( preg_match( $color_regex, $pattern, $matches ) ) {
1530 $color = strtolower( $matches[1] );
1531 $pattern = preg_replace( $color_regex, '', $pattern );
1532 }
1533
1534 // In Excel formats, "_" is used to add spacing, which we can't do in HTML.
1535 $pattern = preg_replace( '/_./', '', $pattern );
1536
1537 // Some non-number characters are escaped with \, which we don't need.
1538 $pattern = preg_replace( '/\\\/', '', $pattern );
1539
1540 // Some non-number strings are quoted, so we'll get rid of the quotes.
1541 $pattern = preg_replace( '/"/', '', $pattern );
1542
1543 // TEMPORARY - Convert # to 0.
1544 $pattern = preg_replace( '/\#/', '0', $pattern );
1545
1546 // Find out if we need comma formatting.
1547 $has_commas = preg_match( '/,/', $pattern );
1548 if ( $has_commas ) {
1549 $pattern = preg_replace( '/,/', '', $pattern );
1550 }
1551
1552 // Handle Percentages.
1553 if ( preg_match( '/\d(\%)([^\%]|$)/', $pattern, $matches ) ) {
1554 $num *= 100;
1555 $pattern = preg_replace( '/(\d)(\%)([^\%]|$)/', '$1%$3', $pattern );
1556 }
1557
1558 // Handle the number itself.
1559 $number_regex = '/(\d+)(\.?)(\d*)/';
1560 if ( preg_match( $number_regex, $pattern, $matches ) ) {
1561 // $left = $matches[1];
1562 // $dec = $matches[2];
1563 $right = $matches[3];
1564 if ( $has_commas ) {
1565 $formatted = number_format( $num, strlen( $right ) );
1566 } else {
1567 $sprintf_pattern = '%1.' . strlen( $right ) . 'f';
1568 $formatted = sprintf( $sprintf_pattern, $num );
1569 }
1570 $pattern = preg_replace( $number_regex, $formatted, $pattern );
1571 }
1572
1573 return array(
1574 'string' => $pattern,
1575 'formatColor' => $color,
1576 );
1577 }
1578
1579 /**
1580 * [__construct description]
1581 *
1582 * @since 1.0.0
1583 *
1584 * @param string $data Optional. [description]
1585 * @param bool $store_extended_info Optional. [description]
1586 * @param string $outputEncoding Optional. [description]
1587 */
1588 public function __construct( $data = '', $store_extended_info = false, $outputEncoding = '' ) {
1589 $this->_ole = new OLERead();
1590 $this->setUTFEncoder( 'iconv' );
1591 if ( '' !== $outputEncoding ) {
1592 $this->setOutputEncoding( $outputEncoding );
1593 }
1594 for ( $i = 1; $i < 245; $i++ ) {
1595 $name = strtolower( ( ( ( $i - 1 ) / 26 >= 1 ) ? chr( ( $i - 1 ) / 26 + 64 ) : '' ) . chr( ( $i - 1 ) % 26 + 65 ) );
1596 $this->colnames[ $name ] = $i;
1597 $this->colindexes[ $i ] = $name;
1598 }
1599 $this->store_extended_info = $store_extended_info;
1600 if ( '' !== $data ) {
1601 $this->read( $data );
1602 }
1603 }
1604
1605 /**
1606 * Set the encoding method.
1607 *
1608 * @since 1.0.0
1609 *
1610 * @param [type] $encoding [description]
1611 */
1612 public function setOutputEncoding( $encoding ): void {
1613 $this->_defaultEncoding = $encoding;
1614 }
1615
1616 /**
1617 * [setUTFEncoder description]
1618 * $encoder = 'iconv' or 'mb'
1619 * set iconv if you would like use 'iconv' for encode UTF-16LE to your encoding
1620 * set mb if you would like use 'mb_convert_encoding' for encode UTF-16LE to your encoding
1621 *
1622 * @since 1.0.0
1623 *
1624 * @param string $encoder Optional. [description]
1625 */
1626 public function setUTFEncoder( $encoder = 'iconv' ): void {
1627 $this->_encoderFunction = '';
1628 if ( 'iconv' === $encoder ) {
1629 $this->_encoderFunction = function_exists( 'iconv' ) ? 'iconv' : '';
1630 } elseif ( 'mb' === $encoder ) {
1631 $this->_encoderFunction = function_exists( 'mb_convert_encoding' ) ? 'mb_convert_encoding' : '';
1632 }
1633 }
1634
1635 /**
1636 * [setRowColOffset description]
1637 *
1638 * @since 1.0.0
1639 *
1640 * @param [type] $iOffset [description]
1641 */
1642 public function setRowColOffset( $iOffset ): void {
1643 $this->_rowoffset = $iOffset;
1644 $this->_coloffset = $iOffset;
1645 }
1646
1647 /**
1648 * Set the default number format.
1649 *
1650 * @since 1.0.0
1651 *
1652 * @param [type] $sFormat [description]
1653 */
1654 public function setDefaultFormat( $sFormat ): void {
1655 $this->_defaultFormat = $sFormat;
1656 }
1657
1658 /**
1659 * Force a column to use a certain format.
1660 *
1661 * @since 1.0.0
1662 *
1663 * @param [type] $column [description]
1664 * @param [type] $sFormat [description]
1665 */
1666 public function setColumnFormat( $column, $sFormat ): void {
1667 $this->_columnsFormat[ $column ] = $sFormat;
1668 }
1669
1670 /**
1671 * Read the spreadsheet file using OLE, then parse.
1672 *
1673 * @since 1.0.0
1674 *
1675 * @param string $data [description]
1676 */
1677 public function read( $data ): void {
1678 $res = $this->_ole->read( $data );
1679
1680 // oops, something goes wrong (Darko Miljanovic)
1681 if ( false === $res ) {
1682 // check error code
1683 if ( 1 === $this->_ole->error ) {
1684 die( 'Data is not readable' );
1685 } elseif ( 2 === $this->_ole->error ) {
1686 die( 'OLE error' );
1687 }
1688 // check other error codes here (e.g. bad fileformat, etc...)
1689 }
1690 $this->data = $this->_ole->getWorkBook();
1691 $this->_parse();
1692 }
1693
1694 /**
1695 * Parse a workbook.
1696 *
1697 * @since 1.0.0
1698 *
1699 * @return [type] [description]
1700 */
1701 protected function _parse() {
1702 $pos = 0;
1703 $data = $this->data;
1704
1705 // $code = $this->v( $data, $pos );
1706 $length = $this->v( $data, $pos + 2 );
1707 $version = $this->v( $data, $pos + 4 );
1708 $substreamType = $this->v( $data, $pos + 6 );
1709 if ( SPREADSHEET_EXCEL_READER_BIFF8 !== $version && SPREADSHEET_EXCEL_READER_BIFF7 !== $version ) {
1710 return false;
1711 }
1712
1713 if ( SPREADSHEET_EXCEL_READER_WORKBOOKGLOBALS !== $substreamType ) {
1714 return false;
1715 }
1716
1717 $pos += $length + 4;
1718
1719 $code = $this->v( $data, $pos );
1720 $length = $this->v( $data, $pos + 2 );
1721
1722 while ( SPREADSHEET_EXCEL_READER_TYPE_EOF !== $code ) {
1723 switch ( $code ) {
1724 case SPREADSHEET_EXCEL_READER_TYPE_SST:
1725 $spos = $pos + 4;
1726 $limitpos = $spos + $length;
1727 $uniqueStrings = $this->_GetInt4d( $data, $spos + 4 );
1728 $spos += 8;
1729 for ( $i = 0; $i < $uniqueStrings; $i++ ) {
1730 // Read in the number of characters
1731 if ( $spos === $limitpos ) {
1732 $opcode = $this->v( $data, $spos );
1733 $conlength = $this->v( $data, $spos + 2 );
1734 if ( 0x3c !== $opcode ) {
1735 return -1;
1736 }
1737 $spos += 4;
1738 $limitpos = $spos + $conlength;
1739 }
1740 $numChars = ord( $data[ $spos ] ) | ( ord( $data[ $spos + 1 ] ) << 8 );
1741 $spos += 2;
1742 $optionFlags = ord( $data[ $spos ] );
1743 ++$spos;
1744 $asciiEncoding = ( 0 === ( $optionFlags & 0x01 ) );
1745 $extendedString = ( 0 !== ( $optionFlags & 0x04 ) );
1746
1747 // See if string contains formatting information.
1748 $richString = ( 0 !== ( $optionFlags & 0x08 ) );
1749
1750 if ( $richString ) {
1751 // Read in the crun
1752 $formattingRuns = $this->v( $data, $spos );
1753 $spos += 2;
1754 }
1755
1756 if ( $extendedString ) {
1757 // Read in cchExtRst
1758 $extendedRunLength = $this->_GetInt4d( $data, $spos );
1759 $spos += 4;
1760 }
1761
1762 $len = ( $asciiEncoding ) ? $numChars : $numChars * 2;
1763 if ( $spos + $len < $limitpos ) {
1764 $retstr = substr( $data, $spos, $len );
1765 $spos += $len;
1766 } else {
1767 // found continue
1768 $retstr = substr( $data, $spos, $limitpos - $spos );
1769 $bytesRead = $limitpos - $spos;
1770 $charsLeft = $numChars - ( ( $asciiEncoding ) ? $bytesRead : ( $bytesRead / 2 ) );
1771 $spos = $limitpos;
1772
1773 while ( $charsLeft > 0 ) {
1774 $opcode = $this->v( $data, $spos );
1775 $conlength = $this->v( $data, $spos + 2 );
1776 if ( 0x3c !== $opcode ) {
1777 return -1;
1778 }
1779 $spos += 4;
1780 $limitpos = $spos + $conlength;
1781 $option = ord( $data[ $spos ] );
1782 $spos += 1;
1783 if ( $asciiEncoding && 0 === $option ) {
1784 $len = min( $charsLeft, $limitpos - $spos ); // min( $charsLeft, $conlength );
1785 $retstr .= substr( $data, $spos, $len );
1786 $charsLeft -= $len;
1787 $asciiEncoding = true;
1788 } elseif ( ! $asciiEncoding && 0 !== $option ) {
1789 $len = min( $charsLeft * 2, $limitpos - $spos ); // min( $charsLeft, $conlength );
1790 $retstr .= substr( $data, $spos, $len );
1791 $charsLeft -= $len / 2;
1792 $asciiEncoding = false;
1793 } elseif ( ! $asciiEncoding && 0 === $option ) {
1794 // Bummer - the string starts off as Unicode, but after the
1795 // continuation it is in straightforward ASCII encoding
1796 $len = min( $charsLeft, $limitpos - $spos ); // min( $charsLeft, $conlength );
1797 for ( $j = 0; $j < $len; $j++ ) {
1798 $retstr .= $data[ $spos + $j ] . chr( 0 );
1799 }
1800 $charsLeft -= $len;
1801 $asciiEncoding = false;
1802 } else {
1803 $newstr = '';
1804 for ( $j = 0; $j < strlen( $retstr ); $j++ ) {
1805 $newstr = $retstr[ $j ] . chr( 0 );
1806 }
1807 $retstr = $newstr;
1808 $len = min( $charsLeft * 2, $limitpos - $spos ); // min( $charsLeft, $conlength );
1809 $retstr .= substr( $data, $spos, $len );
1810 $charsLeft -= $len / 2;
1811 $asciiEncoding = false;
1812 }
1813 $spos += $len;
1814 }
1815 }
1816 $retstr = ( $asciiEncoding ) ? $retstr : $this->_encodeUTF16( $retstr );
1817
1818 if ( $richString ) {
1819 $spos += 4 * $formattingRuns;
1820 }
1821
1822 // For extended strings, skip over the extended string data
1823 if ( $extendedString ) {
1824 $spos += $extendedRunLength;
1825 }
1826 $this->sst[] = $retstr;
1827 }
1828 break;
1829 case SPREADSHEET_EXCEL_READER_TYPE_FILEPASS:
1830 return false;
1831 // break; // unreachable
1832 case SPREADSHEET_EXCEL_READER_TYPE_NAME:
1833 break;
1834 case SPREADSHEET_EXCEL_READER_TYPE_FORMAT:
1835 $indexCode = $this->v( $data, $pos + 4 );
1836 if ( SPREADSHEET_EXCEL_READER_BIFF8 === $version ) {
1837 $numchars = $this->v( $data, $pos + 6 );
1838 if ( 0 === ord( $data[ $pos + 8 ] ) ) {
1839 $formatString = substr( $data, $pos + 9, $numchars );
1840 } else {
1841 $formatString = substr( $data, $pos + 9, $numchars * 2 );
1842 }
1843 } else {
1844 $numchars = ord( $data[ $pos + 6 ] );
1845 $formatString = substr( $data, $pos + 7, $numchars * 2 );
1846 }
1847 $this->formatRecords[ $indexCode ] = $formatString;
1848 break;
1849 case SPREADSHEET_EXCEL_READER_TYPE_FONT:
1850 $height = $this->v( $data, $pos + 4 );
1851 $option = $this->v( $data, $pos + 6 );
1852 $color = $this->v( $data, $pos + 8 );
1853 $weight = $this->v( $data, $pos + 10 );
1854 $under = ord( $data[ $pos + 14 ] );
1855 // Font name
1856 $numchars = ord( $data[ $pos + 18 ] );
1857 if ( 0 === ( ord( $data[ $pos + 19 ] ) & 1 ) ) {
1858 $font = substr( $data, $pos + 20, $numchars );
1859 } else {
1860 $font = substr( $data, $pos + 20, $numchars * 2 );
1861 $font = $this->_encodeUTF16( $font );
1862 }
1863 $this->fontRecords[] = array(
1864 'height' => $height / 20,
1865 'italic' => (bool) ( $option & 2 ),
1866 'color' => $color,
1867 'under' => ( 0 !== $under ),
1868 'bold' => ( 700 === $weight ),
1869 'font' => $font,
1870 'raw' => $this->dumpHexData( $data, $pos + 3, $length ),
1871 );
1872 break;
1873 case SPREADSHEET_EXCEL_READER_TYPE_PALETTE:
1874 $colors = ord( $data[ $pos + 4 ] ) | ord( $data[ $pos + 5 ] ) << 8;
1875 for ( $coli = 0; $coli < $colors; $coli++ ) {
1876 $colOff = $pos + 2 + ( $coli * 4 );
1877 $colr = ord( $data[ $colOff ] );
1878 $colg = ord( $data[ $colOff + 1 ] );
1879 $colb = ord( $data[ $colOff + 2 ] );
1880 $this->colors[ 0x07 + $coli ] = '#' . $this->myhex( $colr ) . $this->myhex( $colg ) . $this->myhex( $colb );
1881 }
1882 break;
1883 case SPREADSHEET_EXCEL_READER_TYPE_XF:
1884 $fontIndexCode = ( ord( $data[ $pos + 4 ] ) | ord( $data[ $pos + 5 ] ) << 8 ) - 1;
1885 $fontIndexCode = max( 0, $fontIndexCode );
1886 $indexCode = ord( $data[ $pos + 6 ] ) | ord( $data[ $pos + 7 ] ) << 8;
1887 $alignbit = ord( $data[ $pos + 10 ] ) & 3;
1888 $bgi = ( ord( $data[ $pos + 22 ] ) | ord( $data[ $pos + 23 ] ) << 8 ) & 0x3FFF;
1889 $bgcolor = ( $bgi & 0x7F );
1890 // $bgcolor = ( $bgi & 0x3f80 ) >> 7;
1891 $align = '';
1892 if ( 3 === $alignbit ) {
1893 $align = 'right';
1894 } elseif ( 2 === $alignbit ) {
1895 $align = 'center';
1896 }
1897 $fillPattern = ( ord( $data[ $pos + 21 ] ) & 0xFC ) >> 2;
1898 if ( 0 === $fillPattern ) {
1899 $bgcolor = '';
1900 }
1901
1902 $xf = array();
1903 $xf['formatIndex'] = $indexCode;
1904 $xf['align'] = $align;
1905 $xf['fontIndex'] = $fontIndexCode;
1906 $xf['bgColor'] = $bgcolor;
1907 $xf['fillPattern'] = $fillPattern;
1908
1909 $border = ord( $data[ $pos + 14 ] ) | ( ord( $data[ $pos + 15 ] ) << 8 ) | ( ord( $data[ $pos + 16 ] ) << 16 ) | ( ord( $data[ $pos + 17 ] ) << 24 );
1910 $xf['borderLeft'] = $this->lineStyles[ ( $border & 0xF ) ];
1911 $xf['borderRight'] = $this->lineStyles[ ( $border & 0xF0 ) >> 4 ];
1912 $xf['borderTop'] = $this->lineStyles[ ( $border & 0xF00 ) >> 8 ];
1913 $xf['borderBottom'] = $this->lineStyles[ ( $border & 0xF000 ) >> 12 ];
1914
1915 $xf['borderLeftColor'] = ( $border & 0x7F0000 ) >> 16;
1916 $xf['borderRightColor'] = ( $border & 0x3F800000 ) >> 23;
1917 $border = ( ord( $data[ $pos + 18 ] ) | ord( $data[ $pos + 19 ] ) << 8 );
1918 $xf['borderTopColor'] = ( $border & 0x7F );
1919 $xf['borderBottomColor'] = ( $border & 0x3F80 ) >> 7;
1920 if ( array_key_exists( $indexCode, $this->dateFormats ) ) {
1921 $xf['type'] = 'date';
1922 $xf['format'] = $this->dateFormats[ $indexCode ];
1923 if ( '' === $align ) {
1924 $xf['align'] = 'right';
1925 }
1926 } elseif ( array_key_exists( $indexCode, $this->numberFormats ) ) {
1927 $xf['type'] = 'number';
1928 $xf['format'] = $this->numberFormats[ $indexCode ];
1929 if ( '' === $align ) {
1930 $xf['align'] = 'right';
1931 }
1932 } else {
1933 $isdate = false;
1934 $formatstr = '';
1935 if ( $indexCode > 0 ) {
1936 if ( isset( $this->formatRecords[ $indexCode ] ) ) {
1937 $formatstr = $this->formatRecords[ $indexCode ];
1938 }
1939 if ( '' !== $formatstr ) {
1940 $tmp = preg_replace( '/\;.*/', '', $formatstr );
1941 $tmp = preg_replace( '/^\[[^\]]*\]/', '', $tmp );
1942 if ( 0 === preg_match( '/[^hmsday\/\-:\s\\\,AMP]/i', $tmp ) ) { // found day and time format
1943 $isdate = true;
1944 $formatstr = $tmp;
1945 $formatstr = str_replace( array( 'AM/PM', 'mmmm', 'mmm' ), array( 'a', 'F', 'M' ), $formatstr );
1946 // m/mm are used for both minutes and months - oh SNAP!
1947 // This mess tries to fix for that.
1948 // 'm' = minutes only if following h/hh or preceding s/ss
1949 $formatstr = preg_replace( '/(h:?)mm?/', '$1i', $formatstr );
1950 $formatstr = preg_replace( '/mm?(:?s)/', '1$1', $formatstr );
1951 // A single 'm' = n in PHP
1952 $formatstr = preg_replace( '/(^|[^m])m([^m]|$)/', '$1n$2', $formatstr );
1953 $formatstr = preg_replace( '/(^|[^m])m([^m]|$)/', '$1n$2', $formatstr );
1954 // else it's months
1955 $formatstr = str_replace( 'mm', 'm', $formatstr );
1956 // Convert single 'd' to 'j'
1957 $formatstr = preg_replace( '/(^|[^d])d([^d]|$)/', '$1j$2', $formatstr );
1958 $formatstr = str_replace( array( 'dddd', 'ddd', 'dd', 'yyyy', 'yy', 'hh', 'h' ), array( 'l', 'D', 'd', 'Y', 'y', 'H', 'g' ), $formatstr );
1959 $formatstr = preg_replace( '/ss?/', 's', $formatstr );
1960 }
1961 }
1962 }
1963 if ( $isdate ) {
1964 $xf['type'] = 'date';
1965 $xf['format'] = $formatstr;
1966 if ( '' === $align ) {
1967 $xf['align'] = 'right';
1968 }
1969 } else {
1970 // If the format string has a 0 or # in it, we'll assume it's a number.
1971 if ( preg_match( '/[0#]/', $formatstr ) ) {
1972 $xf['type'] = 'number';
1973 if ( '' === $align ) {
1974 $xf['align'] = 'right';
1975 }
1976 } else {
1977 $xf['type'] = 'other';
1978 }
1979 $xf['format'] = $formatstr;
1980 $xf['code'] = $indexCode;
1981 }
1982 }
1983 $this->xfRecords[] = $xf;
1984 break;
1985 case SPREADSHEET_EXCEL_READER_TYPE_NINETEENFOUR:
1986 $this->nineteenFour = ( 1 === ord( $data[ $pos + 4 ] ) );
1987 break;
1988 case SPREADSHEET_EXCEL_READER_TYPE_BOUNDSHEET:
1989 $rec_offset = $this->_GetInt4d( $data, $pos + 4 );
1990 // $rec_typeFlag = ord( $data[ $pos + 8 ] );
1991 // $rec_visibilityFlag = ord( $data[ $pos + 9 ] );
1992 $rec_length = ord( $data[ $pos + 10 ] );
1993
1994 if ( SPREADSHEET_EXCEL_READER_BIFF8 === $version ) {
1995 $chartype = ord( $data[ $pos + 11 ] );
1996 if ( 0 === $chartype ) {
1997 $rec_name = substr( $data, $pos + 12, $rec_length );
1998 } else {
1999 $rec_name = $this->_encodeUTF16( substr( $data, $pos + 12, 2 * $rec_length ) );
2000 }
2001 } elseif ( SPREADSHEET_EXCEL_READER_BIFF7 === $version ) {
2002 $rec_name = substr( $data, $pos + 11, $rec_length );
2003 }
2004 $this->boundsheets[] = array(
2005 'name' => $rec_name,
2006 'offset' => $rec_offset,
2007 );
2008 break;
2009 } // switch
2010
2011 $pos += $length + 4;
2012 $code = ord( $data[ $pos ] ) | ord( $data[ $pos + 1 ] ) << 8;
2013 $length = ord( $data[ $pos + 2 ] ) | ord( $data[ $pos + 3 ] ) << 8;
2014 } // while
2015
2016 foreach ( $this->boundsheets as $key => $val ) {
2017 $this->sn = $key;
2018 $this->_parsesheet( $val['offset'] );
2019 }
2020 return true;
2021 }
2022
2023 /**
2024 * Parse a worksheet.
2025 *
2026 * @since 1.0.0
2027 *
2028 * @param [type] $spos [description]
2029 * @return [type] [description]
2030 */
2031 protected function _parsesheet( $spos ) {
2032 $cont = true;
2033 $data = $this->data;
2034 // read BOF
2035 // $code = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8;
2036 $length = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8;
2037
2038 $version = ord( $data[ $spos + 4 ] ) | ord( $data[ $spos + 5 ] ) << 8;
2039 $substreamType = ord( $data[ $spos + 6 ] ) | ord( $data[ $spos + 7 ] ) << 8;
2040
2041 if ( SPREADSHEET_EXCEL_READER_BIFF8 !== $version && SPREADSHEET_EXCEL_READER_BIFF7 !== $version ) {
2042 return -1;
2043 }
2044
2045 if ( SPREADSHEET_EXCEL_READER_WORKSHEET !== $substreamType ) {
2046 return -2;
2047 }
2048
2049 $spos += $length + 4;
2050 while ( $cont ) {
2051 $lowcode = ord( $data[ $spos ] );
2052 if ( SPREADSHEET_EXCEL_READER_TYPE_EOF === $lowcode ) {
2053 break;
2054 }
2055 $code = $lowcode | ord( $data[ $spos + 1 ] ) << 8;
2056 $length = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8;
2057 $spos += 4;
2058 $this->sheets[ $this->sn ]['maxrow'] = $this->_rowoffset - 1;
2059 $this->sheets[ $this->sn ]['maxcol'] = $this->_coloffset - 1;
2060 unset( $this->rectype );
2061 switch ( $code ) {
2062 case SPREADSHEET_EXCEL_READER_TYPE_DIMENSION:
2063 if ( ! isset( $this->numRows ) ) {
2064 if ( 10 === $length || SPREADSHEET_EXCEL_READER_BIFF7 === $version ) {
2065 $this->sheets[ $this->sn ]['numRows'] = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8;
2066 $this->sheets[ $this->sn ]['numCols'] = ord( $data[ $spos + 6 ] ) | ord( $data[ $spos + 7 ] ) << 8;
2067 } else {
2068 $this->sheets[ $this->sn ]['numRows'] = ord( $data[ $spos + 4 ] ) | ord( $data[ $spos + 5 ] ) << 8;
2069 $this->sheets[ $this->sn ]['numCols'] = ord( $data[ $spos + 10 ] ) | ord( $data[ $spos + 11 ] ) << 8;
2070 }
2071 }
2072 break;
2073 case SPREADSHEET_EXCEL_READER_TYPE_MERGEDCELLS:
2074 $cellRanges = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8;
2075 for ( $i = 0; $i < $cellRanges; $i++ ) {
2076 $fr = ord( $data[ $spos + 8 * $i + 2 ] ) | ord( $data[ $spos + 8 * $i + 3 ] ) << 8;
2077 $lr = ord( $data[ $spos + 8 * $i + 4 ] ) | ord( $data[ $spos + 8 * $i + 5 ] ) << 8;
2078 $fc = ord( $data[ $spos + 8 * $i + 6 ] ) | ord( $data[ $spos + 8 * $i + 7 ] ) << 8;
2079 $lc = ord( $data[ $spos + 8 * $i + 8 ] ) | ord( $data[ $spos + 8 * $i + 9 ] ) << 8;
2080 if ( $lr - $fr > 0 ) {
2081 $this->sheets[ $this->sn ]['cellsInfo'][ $fr + 1 ][ $fc + 1 ]['rowspan'] = $lr - $fr + 1;
2082 }
2083 if ( $lc - $fc > 0 ) {
2084 $this->sheets[ $this->sn ]['cellsInfo'][ $fr + 1 ][ $fc + 1 ]['colspan'] = $lc - $fc + 1;
2085 }
2086 }
2087 break;
2088 case SPREADSHEET_EXCEL_READER_TYPE_RK:
2089 case SPREADSHEET_EXCEL_READER_TYPE_RK2:
2090 $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8;
2091 $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8;
2092 $rknum = $this->_GetInt4d( $data, $spos + 6 );
2093 $numValue = $this->_GetIEEE754( $rknum );
2094 $info = $this->_getCellDetails( $spos, $numValue, $column );
2095 $this->addcell( $row, $column, $info['string'], $info );
2096 break;
2097 case SPREADSHEET_EXCEL_READER_TYPE_LABELSST:
2098 $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8;
2099 $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8;
2100 $xfindex = ord( $data[ $spos + 4 ] ) | ord( $data[ $spos + 5 ] ) << 8;
2101 $index = $this->_GetInt4d( $data, $spos + 6 );
2102 $this->addcell( $row, $column, $this->sst[ $index ], array( 'xfIndex' => $xfindex ) );
2103 break;
2104 case SPREADSHEET_EXCEL_READER_TYPE_MULRK:
2105 $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8;
2106 $colFirst = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8;
2107 $colLast = ord( $data[ $spos + $length - 2 ] ) | ord( $data[ $spos + $length - 1 ] ) << 8;
2108 $columns = $colLast - $colFirst + 1;
2109 $tmppos = $spos + 4;
2110 for ( $i = 0; $i < $columns; $i++ ) {
2111 $numValue = $this->_GetIEEE754( $this->_GetInt4d( $data, $tmppos + 2 ) );
2112 $info = $this->_getCellDetails( $tmppos - 4, $numValue, $colFirst + $i + 1 );
2113 $tmppos += 6;
2114 $this->addcell( $row, $colFirst + $i, $info['string'], $info );
2115 }
2116 break;
2117 case SPREADSHEET_EXCEL_READER_TYPE_NUMBER:
2118 $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8;
2119 $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8;
2120 $tmp = unpack( 'ddouble', substr( $data, $spos + 6, 8 ) ); // It machine machine dependent
2121 if ( $this->isDate( $spos ) ) {
2122 $numValue = $tmp['double'];
2123 } else {
2124 $numValue = $this->createNumber( $spos );
2125 }
2126 $info = $this->_getCellDetails( $spos, $numValue, $column );
2127 $this->addcell( $row, $column, $info['string'], $info );
2128 break;
2129
2130 case SPREADSHEET_EXCEL_READER_TYPE_FORMULA:
2131 case SPREADSHEET_EXCEL_READER_TYPE_FORMULA2:
2132 $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8;
2133 $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8;
2134 if ( 0 === ord( $data[ $spos + 6 ] ) && 255 === ord( $data[ $spos + 12 ] ) && 255 === ord( $data[ $spos + 13 ] ) ) {
2135 // String formula. Result follows in a STRING record
2136 // This row/col are stored to be referenced in that record
2137 // https://code.google.com/archive/p/php-excel-reader/issues/4
2138 $previousRow = $row;
2139 $previousCol = $column;
2140 } elseif ( 1 === ord( $data[ $spos + 6 ] ) && 255 === ord( $data[ $spos + 12 ] ) && 255 === ord( $data[ $spos + 13 ] ) ) {
2141 // Boolean formula. Result is in +2; 0 = false,1 = true
2142 // https://code.google.com/archive/p/php-excel-reader/issues/4
2143 if ( 1 === ord( $this->data[ $spos + 8 ] ) ) {
2144 $this->addcell( $row, $column, 'TRUE' );
2145 } else {
2146 $this->addcell( $row, $column, 'FALSE' );
2147 }
2148 } elseif ( 2 === ord( $data[ $spos + 6 ] ) && 255 === ord( $data[ $spos + 12 ] ) && 255 === ord( $data[ $spos + 13 ] ) ) {
2149 // Error formula. Error code is in +2;
2150 } elseif ( 3 === ord( $data[ $spos + 6 ] ) && 255 === ord( $data[ $spos + 12 ] ) && 255 === ord( $data[ $spos + 13 ] ) ) {
2151 // Formula result is a null string.
2152 $this->addcell( $row, $column, '' );
2153 } else {
2154 // Result is a number, so first 14 bytes are just like a _NUMBER record
2155 $tmp = unpack( 'ddouble', substr( $data, $spos + 6, 8 ) ); // machine dependent
2156 if ( $this->isDate( $spos ) ) {
2157 $numValue = $tmp['double'];
2158 } else {
2159 $numValue = $this->createNumber( $spos );
2160 }
2161 $info = $this->_getCellDetails( $spos, $numValue, $column );
2162 $this->addcell( $row, $column, $info['string'], $info );
2163 }
2164 break;
2165 case SPREADSHEET_EXCEL_READER_TYPE_BOOLERR:
2166 $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8;
2167 $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8;
2168 $a_string = ord( $data[ $spos + 6 ] );
2169 $this->addcell( $row, $column, $a_string );
2170 break;
2171 case SPREADSHEET_EXCEL_READER_TYPE_STRING:
2172 // https://code.google.com/archive/p/php-excel-reader/issues/4
2173 if ( SPREADSHEET_EXCEL_READER_BIFF8 === $version ) {
2174 // Unicode 16 string, like an SST record
2175 $xpos = $spos;
2176 $numChars = ord( $data[ $xpos ] ) | ( ord( $data[ $xpos + 1 ] ) << 8 );
2177 $xpos += 2;
2178 $optionFlags = ord( $data[ $xpos ] );
2179 ++$xpos;
2180 $asciiEncoding = ( 0 === ( $optionFlags & 0x01 ) );
2181 $extendedString = ( 0 !== ( $optionFlags & 0x04 ) );
2182 // See if string contains formatting information
2183 $richString = ( 0 !== ( $optionFlags & 0x08 ) );
2184 if ( $richString ) {
2185 // Read in the crun
2186 // $formattingRuns = ord( $data[ $xpos ] ) | ( ord( $data[ $xpos + 1 ] ) << 8 );
2187 $xpos += 2;
2188 }
2189 if ( $extendedString ) {
2190 // Read in cchExtRst
2191 // $extendedRunLength =$this->_GetInt4d( $this->data, $xpos );
2192 $xpos += 4;
2193 }
2194 $len = ( $asciiEncoding ) ? $numChars : $numChars * 2;
2195 $retstr = substr( $data, $xpos, $len );
2196 $xpos += $len;
2197 $retstr = ( $asciiEncoding ) ? $retstr : $this->_encodeUTF16( $retstr );
2198 } elseif ( SPREADSHEET_EXCEL_READER_BIFF7 === $version ) {
2199 // Simple byte string
2200 $xpos = $spos;
2201 $numChars = ord( $data[ $xpos ] ) | ( ord( $data[ $xpos + 1 ] ) << 8 );
2202 $xpos += 2;
2203 $retstr = substr( $data, $xpos, $numChars );
2204 }
2205 $this->addcell( $previousRow, $previousCol, $retstr );
2206 break;
2207 case SPREADSHEET_EXCEL_READER_TYPE_ROW:
2208 $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8;
2209 $rowInfo = ord( $data[ $spos + 6 ] ) | ( ( ord( $data[ $spos + 7 ] ) << 8 ) & 0x7FFF );
2210 if ( ( $rowInfo & 0x8000 ) > 0 ) {
2211 $rowHeight = -1;
2212 } else {
2213 $rowHeight = $rowInfo & 0x7FFF;
2214 }
2215 $rowHidden = ( ord( $data[ $spos + 12 ] ) & 0x20 ) >> 5;
2216 $this->rowInfo[ $this->sn ][ $row + 1 ] = array(
2217 'height' => $rowHeight / 20,
2218 'hidden' => $rowHidden,
2219 );
2220 break;
2221 case SPREADSHEET_EXCEL_READER_TYPE_DBCELL:
2222 break;
2223 case SPREADSHEET_EXCEL_READER_TYPE_MULBLANK:
2224 $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8;
2225 $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8;
2226 $cols = ( $length / 2 ) - 3;
2227 for ( $c = 0; $c < $cols; $c++ ) {
2228 $xfindex = ord( $data[ $spos + 4 + ( $c * 2 ) ] ) | ord( $data[ $spos + 5 + ( $c * 2 ) ] ) << 8;
2229 $this->addcell( $row, $column + $c, '', array( 'xfIndex' => $xfindex ) );
2230 }
2231 break;
2232 case SPREADSHEET_EXCEL_READER_TYPE_LABEL:
2233 $row = ord( $data[ $spos ] ) | ord( $data[ $spos + 1 ] ) << 8;
2234 $column = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8;
2235 $this->addcell( $row, $column, substr( $data, $spos + 8, ord( $data[ $spos + 6 ] ) | ord( $data[ $spos + 7 ] ) << 8 ) );
2236 break;
2237 case SPREADSHEET_EXCEL_READER_TYPE_EOF:
2238 $cont = false;
2239 break;
2240 case SPREADSHEET_EXCEL_READER_TYPE_HYPER:
2241 // Only handle hyperlinks to a URL
2242 $row = ord( $this->data[ $spos ] ) | ord( $this->data[ $spos + 1 ] ) << 8;
2243 $row2 = ord( $this->data[ $spos + 2 ] ) | ord( $this->data[ $spos + 3 ] ) << 8;
2244 $column = ord( $this->data[ $spos + 4 ] ) | ord( $this->data[ $spos + 5 ] ) << 8;
2245 $column2 = ord( $this->data[ $spos + 6 ] ) | ord( $this->data[ $spos + 7 ] ) << 8;
2246 $linkdata = array();
2247 $flags = ord( $this->data[ $spos + 28 ] );
2248 $udesc = '';
2249 $ulink = '';
2250 $uloc = 32;
2251 $linkdata['flags'] = $flags;
2252 if ( ( $flags & 1 ) > 0 ) { // is a type we understand
2253 // is there a description ?
2254 if ( 0x14 === ( $flags & 0x14 ) ) { // has a description
2255 $uloc += 4;
2256 $descLen = ord( $this->data[ $spos + 32 ] ) | ord( $this->data[ $spos + 33 ] ) << 8;
2257 $udesc = substr( $this->data, $spos + $uloc, $descLen * 2 );
2258 $uloc += 2 * $descLen;
2259 }
2260 $ulink = $this->read16bitstring( $this->data, $spos + $uloc + 20 );
2261 if ( '' === $udesc ) {
2262 $udesc = $ulink;
2263 }
2264 }
2265 $linkdata['desc'] = $udesc;
2266 $linkdata['link'] = $this->_encodeUTF16( $ulink );
2267 for ( $r = $row; $r <= $row2; $r++ ) {
2268 for ( $c = $column; $c <= $column2; $c++ ) {
2269 $this->sheets[ $this->sn ]['cellsInfo'][ $r + 1 ][ $c + 1 ]['hyperlink'] = $linkdata;
2270 }
2271 }
2272 break;
2273 case SPREADSHEET_EXCEL_READER_TYPE_DEFCOLWIDTH:
2274 $this->defaultColWidth = ord( $data[ $spos + 4 ] ) | ord( $data[ $spos + 5 ] ) << 8;
2275 break;
2276 case SPREADSHEET_EXCEL_READER_TYPE_STANDARDWIDTH:
2277 $this->standardColWidth = ord( $data[ $spos + 4 ] ) | ord( $data[ $spos + 5 ] ) << 8;
2278 break;
2279 case SPREADSHEET_EXCEL_READER_TYPE_COLINFO:
2280 $colfrom = ord( $data[ $spos + 0 ] ) | ord( $data[ $spos + 1 ] ) << 8;
2281 $colto = ord( $data[ $spos + 2 ] ) | ord( $data[ $spos + 3 ] ) << 8;
2282 $cw = ord( $data[ $spos + 4 ] ) | ord( $data[ $spos + 5 ] ) << 8;
2283 $cxf = ord( $data[ $spos + 6 ] ) | ord( $data[ $spos + 7 ] ) << 8;
2284 $co = ord( $data[ $spos + 8 ] );
2285 for ( $coli = $colfrom; $coli <= $colto; $coli++ ) {
2286 $this->colInfo[ $this->sn ][ $coli + 1 ] = array(
2287 'width' => $cw,
2288 'xf' => $cxf,
2289 'hidden' => ( $co & 0x01 ),
2290 'collapsed' => ( $co & 0x1000 ) >> 12,
2291 );
2292 }
2293 break;
2294 default:
2295 break;
2296 } // switch
2297 $spos += $length;
2298 } // while
2299
2300 if ( ! isset( $this->sheets[ $this->sn ]['numRows'] ) ) {
2301 $this->sheets[ $this->sn ]['numRows'] = $this->sheets[ $this->sn ]['maxrow'];
2302 }
2303 if ( ! isset( $this->sheets[ $this->sn ]['numCols'] ) ) {
2304 $this->sheets[ $this->sn ]['numCols'] = $this->sheets[ $this->sn ]['maxcol'];
2305 }
2306 }
2307
2308 /**
2309 * [isDate description]
2310 *
2311 * @since 1.0.0
2312 *
2313 * @param [type] $spos [description]
2314 * @return bool [description]
2315 */
2316 protected function isDate( $spos ): bool {
2317 $xfindex = ord( $this->data[ $spos + 4 ] ) | ord( $this->data[ $spos + 5 ] ) << 8;
2318 return ( 'date' === $this->xfRecords[ $xfindex ]['type'] );
2319 }
2320 /**
2321 * [gmgetdate description]
2322 *
2323 * @since 1.0.0
2324 *
2325 * @param int $ts Optional. Timestamp. Defaults to current Unix timestamp.
2326 * @return array<string, string> [description]
2327 */
2328 protected function gmgetdate( $ts = null ): array {
2329 $k = array( 'seconds', 'minutes', 'hours', 'mday', 'wday', 'mon', 'year', 'yday', 'weekday', 'month', 0 );
2330 return array_combine( $k, explode( ':', gmdate( 's:i:G:j:w:n:Y:z:l:F:U', is_null( $ts ) ? time() : $ts ) ) );
2331 }
2332
2333 /**
2334 * Gets the details for a particular cell.
2335 *
2336 * @since 1.0.0
2337 *
2338 * @param [type] $spos [description]
2339 * @param [type] $numValue [description]
2340 * @param [type] $column [description]
2341 * @return array<string, mixed> [description]
2342 */
2343 protected function _getCellDetails( $spos, $numValue, $column ): array {
2344 $xfindex = ord( $this->data[ $spos + 4 ] ) | ord( $this->data[ $spos + 5 ] ) << 8;
2345 $xfrecord = $this->xfRecords[ $xfindex ];
2346 $type = $xfrecord['type'];
2347
2348 $format = $xfrecord['format'];
2349 $formatIndex = $xfrecord['formatIndex'];
2350 $fontIndex = $xfrecord['fontIndex'];
2351 $formatColor = '';
2352
2353 if ( isset( $this->_columnsFormat[ $column + 1 ] ) ) {
2354 $format = $this->_columnsFormat[ $column + 1 ];
2355 }
2356
2357 if ( 'date' === $type ) {
2358 // See https://groups.google.com/forum/#!topic/php-excel-reader-discuss/nD-XkNEtjhA
2359 $rectype = 'date';
2360 // Convert numeric value into a date
2361 $utcDays = floor( $numValue - ( $this->nineteenFour ? SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS1904 : SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS ) );
2362 $utcValue = ( $utcDays ) * SPREADSHEET_EXCEL_READER_MSINADAY;
2363 $dateinfo = $this->gmgetdate( $utcValue );
2364
2365 $raw = $numValue;
2366 $fractionalDay = $numValue - floor( $numValue ) + 0.0000001; // The 0.0000001 is to fix for php/excel fractional diffs
2367
2368 $totalseconds = floor( SPREADSHEET_EXCEL_READER_MSINADAY * $fractionalDay );
2369 $secs = $totalseconds % 60;
2370 $totalseconds -= $secs;
2371 $hours = intdiv( $totalseconds, 60 * 60 );
2372 $mins = intdiv( $totalseconds, 60 ) % 60;
2373 $a_string = date( $format, mktime( $hours, $mins, $secs, $dateinfo['mon'], $dateinfo['mday'], $dateinfo['year'] ) );
2374 } elseif ( 'number' === $type ) {
2375 $rectype = 'number';
2376 $formatted = $this->_format_value( $format, $numValue, $formatIndex );
2377 $a_string = $formatted['string'];
2378 $formatColor = $formatted['formatColor'];
2379 $raw = $numValue;
2380 } else {
2381 if ( '' === $format ) {
2382 $format = $this->_defaultFormat;
2383 }
2384 $rectype = 'unknown';
2385 $formatted = $this->_format_value( $format, $numValue, $formatIndex );
2386 $a_string = $formatted['string'];
2387 $formatColor = $formatted['formatColor'];
2388 $raw = $numValue;
2389 }
2390
2391 return array(
2392 'string' => $string,
2393 'raw' => $raw,
2394 'rectype' => $rectype,
2395 'format' => $format,
2396 'formatIndex' => $formatIndex,
2397 'fontIndex' => $fontIndex,
2398 'formatColor' => $formatColor,
2399 'xfIndex' => $xfindex,
2400 );
2401 }
2402
2403 /**
2404 * [createNumber description]
2405 *
2406 * @since 1.0.0
2407 *
2408 * @param [type] $spos [description]
2409 * @return [type] [description]
2410 */
2411 protected function createNumber( $spos ) {
2412 $rknumhigh = $this->_GetInt4d( $this->data, $spos + 10 );
2413 $rknumlow = $this->_GetInt4d( $this->data, $spos + 6 );
2414 $sign = ( $rknumhigh & 0x80000000 ) >> 31;
2415 $exp = ( $rknumhigh & 0x7ff00000 ) >> 20;
2416 $mantissa = ( 0x100000 | ( $rknumhigh & 0x000fffff ) );
2417 $mantissalow1 = ( $rknumlow & 0x80000000 ) >> 31;
2418 $mantissalow2 = ( $rknumlow & 0x7fffffff );
2419 $value = $mantissa / pow( 2, ( 20 - ( $exp - 1023 ) ) );
2420 if ( 0 !== $mantissalow1 ) {
2421 $value += 1 / pow( 2, ( 21 - ( $exp - 1023 ) ) );
2422 }
2423 $value += $mantissalow2 / pow( 2, ( 52 - ( $exp - 1023 ) ) );
2424 if ( $sign ) {
2425 $value *= -1;
2426 }
2427 return $value;
2428 }
2429
2430 /**
2431 * [addcell description]
2432 *
2433 * @since 1.0.0
2434 *
2435 * @param [type] $row [description]
2436 * @param [type] $col [description]
2437 * @param [type] $a_string [description]
2438 * @param array<string, mixed> $info Optional. [description]
2439 * @return [type] [description]
2440 */
2441 protected function addcell( $row, $col, $string, $info = null ) {
2442 $this->sheets[ $this->sn ]['maxrow'] = max( $this->sheets[ $this->sn ]['maxrow'], $row + $this->_rowoffset );
2443 $this->sheets[ $this->sn ]['maxcol'] = max( $this->sheets[ $this->sn ]['maxcol'], $col + $this->_coloffset );
2444 $this->sheets[ $this->sn ]['cells'][ $row + $this->_rowoffset ][ $col + $this->_coloffset ] = $string;
2445 if ( $this->store_extended_info && $info ) {
2446 foreach ( $info as $key => $val ) {
2447 $this->sheets[ $this->sn ]['cellsInfo'][ $row + $this->_rowoffset ][ $col + $this->_coloffset ][ $key ] = $val;
2448 }
2449 }
2450 }
2451
2452 /**
2453 * [_GetIEEE754 description]
2454 *
2455 * @since 1.0.0
2456 *
2457 * @param [type] $rknum [description]
2458 * @return [type] [description]
2459 */
2460 protected function _GetIEEE754( $rknum ) {
2461 if ( 0 !== ( $rknum & 0x02 ) ) {
2462 $value = $rknum >> 2;
2463 } else {
2464 // info on IEEE754 encoding from
2465 // http://research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
2466 // The RK format calls for using only the most significant 30 bits of the
2467 // 64 bit floating point value. The other 34 bits are assumed to be 0
2468 // So, we use the upper 30 bits of $rknum as follows...
2469 $sign = ( $rknum & 0x80000000 ) >> 31;
2470 $exp = ( $rknum & 0x7ff00000 ) >> 20;
2471 $mantissa = ( 0x100000 | ( $rknum & 0x000ffffc ) );
2472 $value = $mantissa / pow( 2, ( 20 - ( $exp - 1023 ) ) );
2473 if ( $sign ) {
2474 $value *= -1;
2475 }
2476 }
2477 if ( 0 !== ( $rknum & 0x01 ) ) {
2478 $value /= 100;
2479 }
2480 return $value;
2481 }
2482
2483 /**
2484 * [_encodeUTF16 description]
2485 *
2486 * @since 1.0.0
2487 *
2488 * @param [type] $a_string [description]
2489 * @return [type] [description]
2490 */
2491 protected function _encodeUTF16( $a_string ) {
2492 if ( $this->_defaultEncoding ) {
2493 switch ( $this->_encoderFunction ) {
2494 case 'iconv':
2495 $a_string = iconv( 'UTF-16LE', $this->_defaultEncoding, $a_string );
2496 break;
2497 case 'mb_convert_encoding':
2498 $a_string = mb_convert_encoding( $string, $this->_defaultEncoding, 'UTF-16LE' );
2499 break;
2500 }
2501 }
2502 return $string;
2503 }
2504
2505 /**
2506 * [_GetInt4d description]
2507 *
2508 * @since 1.0.0
2509 *
2510 * @param [type] $data [description]
2511 * @param [type] $pos [description]
2512 * @return int [description]
2513 */
2514 protected function _GetInt4d( $data, $pos ): int {
2515 $value = ord( $data[ $pos ] ) | ( ord( $data[ $pos + 1 ] ) << 8 ) | ( ord( $data[ $pos + 2 ] ) << 16 ) | ( ord( $data[ $pos + 3 ] ) << 24 );
2516 if ( $value >= 4294967294 ) {
2517 $value = -2;
2518 }
2519 return $value;
2520 }
2521
2522 /**
2523 * [v description]
2524 *
2525 * @since 1.0.0
2526 *
2527 * @param [type] $data [description]
2528 * @param [type] $pos [description]
2529 * @return int [description]
2530 */
2531 protected function v( $data, $pos ): int {
2532 return ord( $data[ $pos ] ) | ord( $data[ $pos + 1 ] ) << 8;
2533 }
2534
2535 } // class Spreadsheet_Excel_Reader
2536