PluginProbe
TablePress – Tables in WordPress made easy / 3.3.4
TablePress – Tables in WordPress made easy v3.3.4
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 3.3.4, at libraries/excel-reader.class.php

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