PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
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 All 45 releases
tablepress / libraries / excel-reader.class.php

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

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