Completed
Push — develop ( c96e2d...d2f55f )
by Adrien
48:43 queued 44:11
created

Xls::verifyPassword()   C

Complexity

Conditions 8
Paths 52

Size

Total Lines 73
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 48
nc 52
nop 5
dl 0
loc 73
ccs 0
cts 49
cp 0
crap 72
rs 6.3384
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6
use PhpOffice\PhpSpreadsheet\Cell\DataType;
7
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
8
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
9
use PhpOffice\PhpSpreadsheet\NamedRange;
10
use PhpOffice\PhpSpreadsheet\RichText\RichText;
11
use PhpOffice\PhpSpreadsheet\Shared\CodePage;
12
use PhpOffice\PhpSpreadsheet\Shared\Date;
13
use PhpOffice\PhpSpreadsheet\Shared\Escher;
14
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
15
use PhpOffice\PhpSpreadsheet\Shared\File;
16
use PhpOffice\PhpSpreadsheet\Shared\OLE;
17
use PhpOffice\PhpSpreadsheet\Shared\OLERead;
18
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
19
use PhpOffice\PhpSpreadsheet\Spreadsheet;
20
use PhpOffice\PhpSpreadsheet\Style\Alignment;
21
use PhpOffice\PhpSpreadsheet\Style\Borders;
22
use PhpOffice\PhpSpreadsheet\Style\Font;
23
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
24
use PhpOffice\PhpSpreadsheet\Style\Protection;
25
use PhpOffice\PhpSpreadsheet\Style\Style;
26
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
27
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
28
use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
29
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
30
31
// Original file header of ParseXL (used as the base for this class):
32
// --------------------------------------------------------------------------------
33
// Adapted from Excel_Spreadsheet_Reader developed by users bizon153,
34
// trex005, and mmp11 (SourceForge.net)
35
// http://sourceforge.net/projects/phpexcelreader/
36
// Primary changes made by canyoncasa (dvc) for ParseXL 1.00 ...
37
//     Modelled moreso after Perl Excel Parse/Write modules
38
//     Added Parse_Excel_Spreadsheet object
39
//         Reads a whole worksheet or tab as row,column array or as
40
//         associated hash of indexed rows and named column fields
41
//     Added variables for worksheet (tab) indexes and names
42
//     Added an object call for loading individual woorksheets
43
//     Changed default indexing defaults to 0 based arrays
44
//     Fixed date/time and percent formats
45
//     Includes patches found at SourceForge...
46
//         unicode patch by nobody
47
//         unpack("d") machine depedency patch by matchy
48
//         boundsheet utf16 patch by bjaenichen
49
//     Renamed functions for shorter names
50
//     General code cleanup and rigor, including <80 column width
51
//     Included a testcase Excel file and PHP example calls
52
//     Code works for PHP 5.x
53
54
// Primary changes made by canyoncasa (dvc) for ParseXL 1.10 ...
55
// http://sourceforge.net/tracker/index.php?func=detail&aid=1466964&group_id=99160&atid=623334
56
//     Decoding of formula conditions, results, and tokens.
57
//     Support for user-defined named cells added as an array "namedcells"
58
//         Patch code for user-defined named cells supports single cells only.
59
//         NOTE: this patch only works for BIFF8 as BIFF5-7 use a different
60
//         external sheet reference structure
61
class Xls extends BaseReader
62
{
63
    // ParseXL definitions
64
    const XLS_BIFF8 = 0x0600;
65
    const XLS_BIFF7 = 0x0500;
66
    const XLS_WORKBOOKGLOBALS = 0x0005;
67
    const XLS_WORKSHEET = 0x0010;
68
69
    // record identifiers
70
    const XLS_TYPE_FORMULA = 0x0006;
71
    const XLS_TYPE_EOF = 0x000a;
72
    const XLS_TYPE_PROTECT = 0x0012;
73
    const XLS_TYPE_OBJECTPROTECT = 0x0063;
74
    const XLS_TYPE_SCENPROTECT = 0x00dd;
75
    const XLS_TYPE_PASSWORD = 0x0013;
76
    const XLS_TYPE_HEADER = 0x0014;
77
    const XLS_TYPE_FOOTER = 0x0015;
78
    const XLS_TYPE_EXTERNSHEET = 0x0017;
79
    const XLS_TYPE_DEFINEDNAME = 0x0018;
80
    const XLS_TYPE_VERTICALPAGEBREAKS = 0x001a;
81
    const XLS_TYPE_HORIZONTALPAGEBREAKS = 0x001b;
82
    const XLS_TYPE_NOTE = 0x001c;
83
    const XLS_TYPE_SELECTION = 0x001d;
84
    const XLS_TYPE_DATEMODE = 0x0022;
85
    const XLS_TYPE_EXTERNNAME = 0x0023;
86
    const XLS_TYPE_LEFTMARGIN = 0x0026;
87
    const XLS_TYPE_RIGHTMARGIN = 0x0027;
88
    const XLS_TYPE_TOPMARGIN = 0x0028;
89
    const XLS_TYPE_BOTTOMMARGIN = 0x0029;
90
    const XLS_TYPE_PRINTGRIDLINES = 0x002b;
91
    const XLS_TYPE_FILEPASS = 0x002f;
92
    const XLS_TYPE_FONT = 0x0031;
93
    const XLS_TYPE_CONTINUE = 0x003c;
94
    const XLS_TYPE_PANE = 0x0041;
95
    const XLS_TYPE_CODEPAGE = 0x0042;
96
    const XLS_TYPE_DEFCOLWIDTH = 0x0055;
97
    const XLS_TYPE_OBJ = 0x005d;
98
    const XLS_TYPE_COLINFO = 0x007d;
99
    const XLS_TYPE_IMDATA = 0x007f;
100
    const XLS_TYPE_SHEETPR = 0x0081;
101
    const XLS_TYPE_HCENTER = 0x0083;
102
    const XLS_TYPE_VCENTER = 0x0084;
103
    const XLS_TYPE_SHEET = 0x0085;
104
    const XLS_TYPE_PALETTE = 0x0092;
105
    const XLS_TYPE_SCL = 0x00a0;
106
    const XLS_TYPE_PAGESETUP = 0x00a1;
107
    const XLS_TYPE_MULRK = 0x00bd;
108
    const XLS_TYPE_MULBLANK = 0x00be;
109
    const XLS_TYPE_DBCELL = 0x00d7;
110
    const XLS_TYPE_XF = 0x00e0;
111
    const XLS_TYPE_MERGEDCELLS = 0x00e5;
112
    const XLS_TYPE_MSODRAWINGGROUP = 0x00eb;
113
    const XLS_TYPE_MSODRAWING = 0x00ec;
114
    const XLS_TYPE_SST = 0x00fc;
115
    const XLS_TYPE_LABELSST = 0x00fd;
116
    const XLS_TYPE_EXTSST = 0x00ff;
117
    const XLS_TYPE_EXTERNALBOOK = 0x01ae;
118
    const XLS_TYPE_DATAVALIDATIONS = 0x01b2;
119
    const XLS_TYPE_TXO = 0x01b6;
120
    const XLS_TYPE_HYPERLINK = 0x01b8;
121
    const XLS_TYPE_DATAVALIDATION = 0x01be;
122
    const XLS_TYPE_DIMENSION = 0x0200;
123
    const XLS_TYPE_BLANK = 0x0201;
124
    const XLS_TYPE_NUMBER = 0x0203;
125
    const XLS_TYPE_LABEL = 0x0204;
126
    const XLS_TYPE_BOOLERR = 0x0205;
127
    const XLS_TYPE_STRING = 0x0207;
128
    const XLS_TYPE_ROW = 0x0208;
129
    const XLS_TYPE_INDEX = 0x020b;
130
    const XLS_TYPE_ARRAY = 0x0221;
131
    const XLS_TYPE_DEFAULTROWHEIGHT = 0x0225;
132
    const XLS_TYPE_WINDOW2 = 0x023e;
133
    const XLS_TYPE_RK = 0x027e;
134
    const XLS_TYPE_STYLE = 0x0293;
135
    const XLS_TYPE_FORMAT = 0x041e;
136
    const XLS_TYPE_SHAREDFMLA = 0x04bc;
137
    const XLS_TYPE_BOF = 0x0809;
138
    const XLS_TYPE_SHEETPROTECTION = 0x0867;
139
    const XLS_TYPE_RANGEPROTECTION = 0x0868;
140
    const XLS_TYPE_SHEETLAYOUT = 0x0862;
141
    const XLS_TYPE_XFEXT = 0x087d;
142
    const XLS_TYPE_PAGELAYOUTVIEW = 0x088b;
143
    const XLS_TYPE_UNKNOWN = 0xffff;
144
145
    // Encryption type
146
    const MS_BIFF_CRYPTO_NONE = 0;
147
    const MS_BIFF_CRYPTO_XOR = 1;
148
    const MS_BIFF_CRYPTO_RC4 = 2;
149
150
    // Size of stream blocks when using RC4 encryption
151
    const REKEY_BLOCK = 0x400;
152
153
    /**
154
     * Summary Information stream data.
155
     *
156
     * @var string
157
     */
158
    private $summaryInformation;
159
160
    /**
161
     * Extended Summary Information stream data.
162
     *
163
     * @var string
164
     */
165
    private $documentSummaryInformation;
166
167
    /**
168
     * Workbook stream data. (Includes workbook globals substream as well as sheet substreams).
169
     *
170
     * @var string
171
     */
172
    private $data;
173
174
    /**
175
     * Size in bytes of $this->data.
176
     *
177
     * @var int
178
     */
179
    private $dataSize;
180
181
    /**
182
     * Current position in stream.
183
     *
184
     * @var int
185
     */
186
    private $pos;
187
188
    /**
189
     * Workbook to be returned by the reader.
190
     *
191
     * @var Spreadsheet
192
     */
193
    private $spreadsheet;
194
195
    /**
196
     * Worksheet that is currently being built by the reader.
197
     *
198
     * @var Worksheet
199
     */
200
    private $phpSheet;
201
202
    /**
203
     * BIFF version.
204
     *
205
     * @var int
206
     */
207
    private $version;
208
209
    /**
210
     * Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95)
211
     * For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'.
212
     *
213
     * @var string
214
     */
215
    private $codepage;
216
217
    /**
218
     * Shared formats.
219
     *
220
     * @var array
221
     */
222
    private $formats;
223
224
    /**
225
     * Shared fonts.
226
     *
227
     * @var array
228
     */
229
    private $objFonts;
230
231
    /**
232
     * Color palette.
233
     *
234
     * @var array
235
     */
236
    private $palette;
237
238
    /**
239
     * Worksheets.
240
     *
241
     * @var array
242
     */
243
    private $sheets;
244
245
    /**
246
     * External books.
247
     *
248
     * @var array
249
     */
250
    private $externalBooks;
251
252
    /**
253
     * REF structures. Only applies to BIFF8.
254
     *
255
     * @var array
256
     */
257
    private $ref;
258
259
    /**
260
     * External names.
261
     *
262
     * @var array
263
     */
264
    private $externalNames;
265
266
    /**
267
     * Defined names.
268
     *
269
     * @var array
270
     */
271
    private $definedname;
272
273
    /**
274
     * Shared strings. Only applies to BIFF8.
275
     *
276
     * @var array
277
     */
278
    private $sst;
279
280
    /**
281
     * Panes are frozen? (in sheet currently being read). See WINDOW2 record.
282
     *
283
     * @var bool
284
     */
285
    private $frozen;
286
287
    /**
288
     * Fit printout to number of pages? (in sheet currently being read). See SHEETPR record.
289
     *
290
     * @var bool
291
     */
292
    private $isFitToPages;
293
294
    /**
295
     * Objects. One OBJ record contributes with one entry.
296
     *
297
     * @var array
298
     */
299
    private $objs;
300
301
    /**
302
     * Text Objects. One TXO record corresponds with one entry.
303
     *
304
     * @var array
305
     */
306
    private $textObjects;
307
308
    /**
309
     * Cell Annotations (BIFF8).
310
     *
311
     * @var array
312
     */
313
    private $cellNotes;
314
315
    /**
316
     * The combined MSODRAWINGGROUP data.
317
     *
318
     * @var string
319
     */
320
    private $drawingGroupData;
321
322
    /**
323
     * The combined MSODRAWING data (per sheet).
324
     *
325
     * @var string
326
     */
327
    private $drawingData;
328
329
    /**
330
     * Keep track of XF index.
331
     *
332
     * @var int
333
     */
334
    private $xfIndex;
335
336
    /**
337
     * Mapping of XF index (that is a cell XF) to final index in cellXf collection.
338
     *
339
     * @var array
340
     */
341
    private $mapCellXfIndex;
342
343
    /**
344
     * Mapping of XF index (that is a style XF) to final index in cellStyleXf collection.
345
     *
346
     * @var array
347
     */
348
    private $mapCellStyleXfIndex;
349
350
    /**
351
     * The shared formulas in a sheet. One SHAREDFMLA record contributes with one value.
352
     *
353
     * @var array
354
     */
355
    private $sharedFormulas;
356
357
    /**
358
     * The shared formula parts in a sheet. One FORMULA record contributes with one value if it
359
     * refers to a shared formula.
360
     *
361
     * @var array
362
     */
363
    private $sharedFormulaParts;
364
365
    /**
366
     * The type of encryption in use.
367
     *
368
     * @var int
369
     */
370
    private $encryption = 0;
371
372
    /**
373
     * The position in the stream after which contents are encrypted.
374
     *
375
     * @var int
376
     */
377
    private $encryptionStartPos = false;
378
379
    /**
380
     * The current RC4 decryption object.
381
     *
382
     * @var Xls\RC4
383
     */
384
    private $rc4Key;
385
386
    /**
387
     * The position in the stream that the RC4 decryption object was left at.
388
     *
389
     * @var int
390
     */
391
    private $rc4Pos = 0;
392
393
    /**
394
     * The current MD5 context state.
395
     *
396
     * @var string
397
     */
398
    private $md5Ctxt;
399
400
    /**
401
     * @var int
402
     */
403
    private $textObjRef;
404
405
    /**
406
     * @var string
407
     */
408
    private $baseCell;
409
410
    /**
411
     * Create a new Xls Reader instance.
412
     */
413 27
    public function __construct()
414
    {
415 27
        $this->readFilter = new DefaultReadFilter();
416 27
    }
417
418
    /**
419
     * Can the current IReader read the file?
420
     *
421
     * @param string $pFilename
422
     *
423
     * @return bool
424
     */
425 6
    public function canRead($pFilename)
426
    {
427 6
        File::assertFile($pFilename);
428
429
        try {
430
            // Use ParseXL for the hard work.
431 6
            $ole = new OLERead();
432
433
            // get excel data
434 6
            $ole->read($pFilename);
435
436 6
            return true;
437
        } catch (PhpSpreadsheetException $e) {
438
            return false;
439
        }
440
    }
441
442
    /**
443
     * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object.
444
     *
445
     * @param string $pFilename
446
     *
447
     * @throws Exception
448
     *
449
     * @return array
450
     */
451 2
    public function listWorksheetNames($pFilename)
452
    {
453 2
        File::assertFile($pFilename);
454
455 2
        $worksheetNames = [];
456
457
        // Read the OLE file
458 2
        $this->loadOLE($pFilename);
459
460
        // total byte size of Excel data (workbook global substream + sheet substreams)
461 2
        $this->dataSize = strlen($this->data);
462
463 2
        $this->pos = 0;
464 2
        $this->sheets = [];
465
466
        // Parse Workbook Global Substream
467 2
        while ($this->pos < $this->dataSize) {
468 2
            $code = self::getUInt2d($this->data, $this->pos);
469
470
            switch ($code) {
471 2
                case self::XLS_TYPE_BOF:
472 2
                    $this->readBof();
473
474 2
                    break;
475 2
                case self::XLS_TYPE_SHEET:
476 2
                    $this->readSheet();
477
478 2
                    break;
479 2
                case self::XLS_TYPE_EOF:
480 2
                    $this->readDefault();
481
482 2
                    break 2;
483
                default:
484 2
                    $this->readDefault();
485
486 2
                    break;
487
            }
488
        }
489
490 2
        foreach ($this->sheets as $sheet) {
491 2
            if ($sheet['sheetType'] != 0x00) {
492
                // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
493
                continue;
494
            }
495
496 2
            $worksheetNames[] = $sheet['name'];
497
        }
498
499 2
        return $worksheetNames;
500
    }
501
502
    /**
503
     * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
504
     *
505
     * @param string $pFilename
506
     *
507
     * @throws Exception
508
     *
509
     * @return array
510
     */
511 1
    public function listWorksheetInfo($pFilename)
512
    {
513 1
        File::assertFile($pFilename);
514
515 1
        $worksheetInfo = [];
516
517
        // Read the OLE file
518 1
        $this->loadOLE($pFilename);
519
520
        // total byte size of Excel data (workbook global substream + sheet substreams)
521 1
        $this->dataSize = strlen($this->data);
522
523
        // initialize
524 1
        $this->pos = 0;
525 1
        $this->sheets = [];
526
527
        // Parse Workbook Global Substream
528 1
        while ($this->pos < $this->dataSize) {
529 1
            $code = self::getUInt2d($this->data, $this->pos);
530
531
            switch ($code) {
532 1
                case self::XLS_TYPE_BOF:
533 1
                    $this->readBof();
534
535 1
                    break;
536 1
                case self::XLS_TYPE_SHEET:
537 1
                    $this->readSheet();
538
539 1
                    break;
540 1
                case self::XLS_TYPE_EOF:
541 1
                    $this->readDefault();
542
543 1
                    break 2;
544
                default:
545 1
                    $this->readDefault();
546
547 1
                    break;
548
            }
549
        }
550
551
        // Parse the individual sheets
552 1
        foreach ($this->sheets as $sheet) {
553 1
            if ($sheet['sheetType'] != 0x00) {
554
                // 0x00: Worksheet
555
                // 0x02: Chart
556
                // 0x06: Visual Basic module
557
                continue;
558
            }
559
560 1
            $tmpInfo = [];
561 1
            $tmpInfo['worksheetName'] = $sheet['name'];
562 1
            $tmpInfo['lastColumnLetter'] = 'A';
563 1
            $tmpInfo['lastColumnIndex'] = 0;
564 1
            $tmpInfo['totalRows'] = 0;
565 1
            $tmpInfo['totalColumns'] = 0;
566
567 1
            $this->pos = $sheet['offset'];
568
569 1
            while ($this->pos <= $this->dataSize - 4) {
570 1
                $code = self::getUInt2d($this->data, $this->pos);
571
572
                switch ($code) {
573 1
                    case self::XLS_TYPE_RK:
574 1
                    case self::XLS_TYPE_LABELSST:
575 1
                    case self::XLS_TYPE_NUMBER:
576 1
                    case self::XLS_TYPE_FORMULA:
577 1
                    case self::XLS_TYPE_BOOLERR:
578 1
                    case self::XLS_TYPE_LABEL:
579 1
                        $length = self::getUInt2d($this->data, $this->pos + 2);
580 1
                        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
581
582
                        // move stream pointer to next record
583 1
                        $this->pos += 4 + $length;
584
585 1
                        $rowIndex = self::getUInt2d($recordData, 0) + 1;
586 1
                        $columnIndex = self::getUInt2d($recordData, 2);
587
588 1
                        $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
589 1
                        $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
590
591 1
                        break;
592 1
                    case self::XLS_TYPE_BOF:
593 1
                        $this->readBof();
594
595 1
                        break;
596 1
                    case self::XLS_TYPE_EOF:
597 1
                        $this->readDefault();
598
599 1
                        break 2;
600
                    default:
601 1
                        $this->readDefault();
602
603 1
                        break;
604
                }
605
            }
606
607 1
            $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
608 1
            $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
609
610 1
            $worksheetInfo[] = $tmpInfo;
611
        }
612
613 1
        return $worksheetInfo;
614
    }
615
616
    /**
617
     * Loads PhpSpreadsheet from file.
618
     *
619
     * @param string $pFilename
620
     *
621
     * @throws Exception
622
     *
623
     * @return Spreadsheet
624
     */
625 21
    public function load($pFilename)
626
    {
627
        // Read the OLE file
628 21
        $this->loadOLE($pFilename);
629
630
        // Initialisations
631 21
        $this->spreadsheet = new Spreadsheet();
632 21
        $this->spreadsheet->removeSheetByIndex(0); // remove 1st sheet
633 21
        if (!$this->readDataOnly) {
634 20
            $this->spreadsheet->removeCellStyleXfByIndex(0); // remove the default style
635 20
            $this->spreadsheet->removeCellXfByIndex(0); // remove the default style
636
        }
637
638
        // Read the summary information stream (containing meta data)
639 21
        $this->readSummaryInformation();
640
641
        // Read the Additional document summary information stream (containing application-specific meta data)
642 21
        $this->readDocumentSummaryInformation();
643
644
        // total byte size of Excel data (workbook global substream + sheet substreams)
645 21
        $this->dataSize = strlen($this->data);
646
647
        // initialize
648 21
        $this->pos = 0;
649 21
        $this->codepage = 'CP1252';
650 21
        $this->formats = [];
651 21
        $this->objFonts = [];
652 21
        $this->palette = [];
653 21
        $this->sheets = [];
654 21
        $this->externalBooks = [];
655 21
        $this->ref = [];
656 21
        $this->definedname = [];
657 21
        $this->sst = [];
658 21
        $this->drawingGroupData = '';
659 21
        $this->xfIndex = '';
660 21
        $this->mapCellXfIndex = [];
661 21
        $this->mapCellStyleXfIndex = [];
662
663
        // Parse Workbook Global Substream
664 21
        while ($this->pos < $this->dataSize) {
665 21
            $code = self::getUInt2d($this->data, $this->pos);
666
667
            switch ($code) {
668 21
                case self::XLS_TYPE_BOF:
669 21
                    $this->readBof();
670
671 21
                    break;
672 21
                case self::XLS_TYPE_FILEPASS:
673
                    $this->readFilepass();
674
675
                    break;
676 21
                case self::XLS_TYPE_CODEPAGE:
677 21
                    $this->readCodepage();
678
679 21
                    break;
680 21
                case self::XLS_TYPE_DATEMODE:
681 21
                    $this->readDateMode();
682
683 21
                    break;
684 21
                case self::XLS_TYPE_FONT:
685 21
                    $this->readFont();
686
687 21
                    break;
688 21
                case self::XLS_TYPE_FORMAT:
689 18
                    $this->readFormat();
690
691 18
                    break;
692 21
                case self::XLS_TYPE_XF:
693 21
                    $this->readXf();
694
695 21
                    break;
696 21
                case self::XLS_TYPE_XFEXT:
697 3
                    $this->readXfExt();
698
699 3
                    break;
700 21
                case self::XLS_TYPE_STYLE:
701 21
                    $this->readStyle();
702
703 21
                    break;
704 21
                case self::XLS_TYPE_PALETTE:
705 7
                    $this->readPalette();
706
707 7
                    break;
708 21
                case self::XLS_TYPE_SHEET:
709 21
                    $this->readSheet();
710
711 21
                    break;
712 21
                case self::XLS_TYPE_EXTERNALBOOK:
713 7
                    $this->readExternalBook();
714
715 7
                    break;
716 21
                case self::XLS_TYPE_EXTERNNAME:
717
                    $this->readExternName();
718
719
                    break;
720 21
                case self::XLS_TYPE_EXTERNSHEET:
721 7
                    $this->readExternSheet();
722
723 7
                    break;
724 21
                case self::XLS_TYPE_DEFINEDNAME:
725 1
                    $this->readDefinedName();
726
727 1
                    break;
728 21
                case self::XLS_TYPE_MSODRAWINGGROUP:
729 3
                    $this->readMsoDrawingGroup();
730
731 3
                    break;
732 21
                case self::XLS_TYPE_SST:
733 21
                    $this->readSst();
734
735 21
                    break;
736 21
                case self::XLS_TYPE_EOF:
737 21
                    $this->readDefault();
738
739 21
                    break 2;
740
                default:
741 21
                    $this->readDefault();
742
743 21
                    break;
744
            }
745
        }
746
747
        // Resolve indexed colors for font, fill, and border colors
748
        // Cannot be resolved already in XF record, because PALETTE record comes afterwards
749 21
        if (!$this->readDataOnly) {
750 20
            foreach ($this->objFonts as $objFont) {
751 20
                if (isset($objFont->colorIndex)) {
752 20
                    $color = Xls\Color::map($objFont->colorIndex, $this->palette, $this->version);
753 20
                    $objFont->getColor()->setRGB($color['rgb']);
754
                }
755
            }
756
757 20
            foreach ($this->spreadsheet->getCellXfCollection() as $objStyle) {
758
                // fill start and end color
759 20
                $fill = $objStyle->getFill();
760
761 20
                if (isset($fill->startcolorIndex)) {
762 20
                    $startColor = Xls\Color::map($fill->startcolorIndex, $this->palette, $this->version);
763 20
                    $fill->getStartColor()->setRGB($startColor['rgb']);
764
                }
765 20
                if (isset($fill->endcolorIndex)) {
766 20
                    $endColor = Xls\Color::map($fill->endcolorIndex, $this->palette, $this->version);
767 20
                    $fill->getEndColor()->setRGB($endColor['rgb']);
768
                }
769
770
                // border colors
771 20
                $top = $objStyle->getBorders()->getTop();
772 20
                $right = $objStyle->getBorders()->getRight();
773 20
                $bottom = $objStyle->getBorders()->getBottom();
774 20
                $left = $objStyle->getBorders()->getLeft();
775 20
                $diagonal = $objStyle->getBorders()->getDiagonal();
776
777 20
                if (isset($top->colorIndex)) {
778 20
                    $borderTopColor = Xls\Color::map($top->colorIndex, $this->palette, $this->version);
779 20
                    $top->getColor()->setRGB($borderTopColor['rgb']);
780
                }
781 20
                if (isset($right->colorIndex)) {
782 20
                    $borderRightColor = Xls\Color::map($right->colorIndex, $this->palette, $this->version);
783 20
                    $right->getColor()->setRGB($borderRightColor['rgb']);
784
                }
785 20
                if (isset($bottom->colorIndex)) {
786 20
                    $borderBottomColor = Xls\Color::map($bottom->colorIndex, $this->palette, $this->version);
787 20
                    $bottom->getColor()->setRGB($borderBottomColor['rgb']);
788
                }
789 20
                if (isset($left->colorIndex)) {
790 20
                    $borderLeftColor = Xls\Color::map($left->colorIndex, $this->palette, $this->version);
791 20
                    $left->getColor()->setRGB($borderLeftColor['rgb']);
792
                }
793 20
                if (isset($diagonal->colorIndex)) {
794 20
                    $borderDiagonalColor = Xls\Color::map($diagonal->colorIndex, $this->palette, $this->version);
795 20
                    $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']);
796
                }
797
            }
798
        }
799
800
        // treat MSODRAWINGGROUP records, workbook-level Escher
801 21
        if (!$this->readDataOnly && $this->drawingGroupData) {
802 3
            $escherWorkbook = new Escher();
803 3
            $reader = new Xls\Escher($escherWorkbook);
804 3
            $escherWorkbook = $reader->load($this->drawingGroupData);
805
        }
806
807
        // Parse the individual sheets
808 21
        foreach ($this->sheets as $sheet) {
809 21
            if ($sheet['sheetType'] != 0x00) {
810
                // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
811
                continue;
812
            }
813
814
            // check if sheet should be skipped
815 21
            if (isset($this->loadSheetsOnly) && !in_array($sheet['name'], $this->loadSheetsOnly)) {
816 4
                continue;
817
            }
818
819
            // add sheet to PhpSpreadsheet object
820 21
            $this->phpSheet = $this->spreadsheet->createSheet();
821
            //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
822
            //        cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
823
            //        name in line with the formula, not the reverse
824 21
            $this->phpSheet->setTitle($sheet['name'], false, false);
825 21
            $this->phpSheet->setSheetState($sheet['sheetState']);
826
827 21
            $this->pos = $sheet['offset'];
828
829
            // Initialize isFitToPages. May change after reading SHEETPR record.
830 21
            $this->isFitToPages = false;
831
832
            // Initialize drawingData
833 21
            $this->drawingData = '';
834
835
            // Initialize objs
836 21
            $this->objs = [];
837
838
            // Initialize shared formula parts
839 21
            $this->sharedFormulaParts = [];
840
841
            // Initialize shared formulas
842 21
            $this->sharedFormulas = [];
843
844
            // Initialize text objs
845 21
            $this->textObjects = [];
846
847
            // Initialize cell annotations
848 21
            $this->cellNotes = [];
849 21
            $this->textObjRef = -1;
850
851 21
            while ($this->pos <= $this->dataSize - 4) {
852 21
                $code = self::getUInt2d($this->data, $this->pos);
853
854
                switch ($code) {
855 21
                    case self::XLS_TYPE_BOF:
856 21
                        $this->readBof();
857
858 21
                        break;
859 21
                    case self::XLS_TYPE_PRINTGRIDLINES:
860 21
                        $this->readPrintGridlines();
861
862 21
                        break;
863 21
                    case self::XLS_TYPE_DEFAULTROWHEIGHT:
864 17
                        $this->readDefaultRowHeight();
865
866 17
                        break;
867 21
                    case self::XLS_TYPE_SHEETPR:
868 21
                        $this->readSheetPr();
869
870 21
                        break;
871 21
                    case self::XLS_TYPE_HORIZONTALPAGEBREAKS:
872
                        $this->readHorizontalPageBreaks();
873
874
                        break;
875 21
                    case self::XLS_TYPE_VERTICALPAGEBREAKS:
876
                        $this->readVerticalPageBreaks();
877
878
                        break;
879 21
                    case self::XLS_TYPE_HEADER:
880 21
                        $this->readHeader();
881
882 21
                        break;
883 21
                    case self::XLS_TYPE_FOOTER:
884 21
                        $this->readFooter();
885
886 21
                        break;
887 21
                    case self::XLS_TYPE_HCENTER:
888 21
                        $this->readHcenter();
889
890 21
                        break;
891 21
                    case self::XLS_TYPE_VCENTER:
892 21
                        $this->readVcenter();
893
894 21
                        break;
895 21
                    case self::XLS_TYPE_LEFTMARGIN:
896 8
                        $this->readLeftMargin();
897
898 8
                        break;
899 21
                    case self::XLS_TYPE_RIGHTMARGIN:
900 8
                        $this->readRightMargin();
901
902 8
                        break;
903 21
                    case self::XLS_TYPE_TOPMARGIN:
904 8
                        $this->readTopMargin();
905
906 8
                        break;
907 21
                    case self::XLS_TYPE_BOTTOMMARGIN:
908 8
                        $this->readBottomMargin();
909
910 8
                        break;
911 21
                    case self::XLS_TYPE_PAGESETUP:
912 21
                        $this->readPageSetup();
913
914 21
                        break;
915 21
                    case self::XLS_TYPE_PROTECT:
916 1
                        $this->readProtect();
917
918 1
                        break;
919 21
                    case self::XLS_TYPE_SCENPROTECT:
920
                        $this->readScenProtect();
921
922
                        break;
923 21
                    case self::XLS_TYPE_OBJECTPROTECT:
924
                        $this->readObjectProtect();
925
926
                        break;
927 21
                    case self::XLS_TYPE_PASSWORD:
928
                        $this->readPassword();
929
930
                        break;
931 21
                    case self::XLS_TYPE_DEFCOLWIDTH:
932 21
                        $this->readDefColWidth();
933
934 21
                        break;
935 21
                    case self::XLS_TYPE_COLINFO:
936 17
                        $this->readColInfo();
937
938 17
                        break;
939 21
                    case self::XLS_TYPE_DIMENSION:
940 21
                        $this->readDefault();
941
942 21
                        break;
943 21
                    case self::XLS_TYPE_ROW:
944 17
                        $this->readRow();
945
946 17
                        break;
947 21
                    case self::XLS_TYPE_DBCELL:
948 16
                        $this->readDefault();
949
950 16
                        break;
951 21
                    case self::XLS_TYPE_RK:
952 12
                        $this->readRk();
953
954 12
                        break;
955 21
                    case self::XLS_TYPE_LABELSST:
956 18
                        $this->readLabelSst();
957
958 18
                        break;
959 21
                    case self::XLS_TYPE_MULRK:
960 11
                        $this->readMulRk();
961
962 11
                        break;
963 21
                    case self::XLS_TYPE_NUMBER:
964 12
                        $this->readNumber();
965
966 12
                        break;
967 21
                    case self::XLS_TYPE_FORMULA:
968 10
                        $this->readFormula();
969
970 10
                        break;
971 21
                    case self::XLS_TYPE_SHAREDFMLA:
972
                        $this->readSharedFmla();
973
974
                        break;
975 21
                    case self::XLS_TYPE_BOOLERR:
976 8
                        $this->readBoolErr();
977
978 8
                        break;
979 21
                    case self::XLS_TYPE_MULBLANK:
980 11
                        $this->readMulBlank();
981
982 11
                        break;
983 21
                    case self::XLS_TYPE_LABEL:
984
                        $this->readLabel();
985
986
                        break;
987 21
                    case self::XLS_TYPE_BLANK:
988 2
                        $this->readBlank();
989
990 2
                        break;
991 21
                    case self::XLS_TYPE_MSODRAWING:
992 3
                        $this->readMsoDrawing();
993
994 3
                        break;
995 21
                    case self::XLS_TYPE_OBJ:
996 3
                        $this->readObj();
997
998 3
                        break;
999 21
                    case self::XLS_TYPE_WINDOW2:
1000 21
                        $this->readWindow2();
1001
1002 21
                        break;
1003 21
                    case self::XLS_TYPE_PAGELAYOUTVIEW:
1004 7
                        $this->readPageLayoutView();
1005
1006 7
                        break;
1007 21
                    case self::XLS_TYPE_SCL:
1008
                        $this->readScl();
1009
1010
                        break;
1011 21
                    case self::XLS_TYPE_PANE:
1012 1
                        $this->readPane();
1013
1014 1
                        break;
1015 21
                    case self::XLS_TYPE_SELECTION:
1016 21
                        $this->readSelection();
1017
1018 21
                        break;
1019 21
                    case self::XLS_TYPE_MERGEDCELLS:
1020 13
                        $this->readMergedCells();
1021
1022 13
                        break;
1023 21
                    case self::XLS_TYPE_HYPERLINK:
1024 2
                        $this->readHyperLink();
1025
1026 2
                        break;
1027 21
                    case self::XLS_TYPE_DATAVALIDATIONS:
1028
                        $this->readDataValidations();
1029
1030
                        break;
1031 21
                    case self::XLS_TYPE_DATAVALIDATION:
1032
                        $this->readDataValidation();
1033
1034
                        break;
1035 21
                    case self::XLS_TYPE_SHEETLAYOUT:
1036 2
                        $this->readSheetLayout();
1037
1038 2
                        break;
1039 21
                    case self::XLS_TYPE_SHEETPROTECTION:
1040 8
                        $this->readSheetProtection();
1041
1042 8
                        break;
1043 21
                    case self::XLS_TYPE_RANGEPROTECTION:
1044 1
                        $this->readRangeProtection();
1045
1046 1
                        break;
1047 21
                    case self::XLS_TYPE_NOTE:
1048 1
                        $this->readNote();
1049
1050 1
                        break;
1051 21
                    case self::XLS_TYPE_TXO:
1052 1
                        $this->readTextObject();
1053
1054 1
                        break;
1055 21
                    case self::XLS_TYPE_CONTINUE:
1056
                        $this->readContinue();
1057
1058
                        break;
1059 21
                    case self::XLS_TYPE_EOF:
1060 21
                        $this->readDefault();
1061
1062 21
                        break 2;
1063
                    default:
1064 21
                        $this->readDefault();
1065
1066 21
                        break;
1067
                }
1068
            }
1069
1070
            // treat MSODRAWING records, sheet-level Escher
1071 21
            if (!$this->readDataOnly && $this->drawingData) {
1072 3
                $escherWorksheet = new Escher();
1073 3
                $reader = new Xls\Escher($escherWorksheet);
1074 3
                $escherWorksheet = $reader->load($this->drawingData);
1075
1076
                // get all spContainers in one long array, so they can be mapped to OBJ records
1077 3
                $allSpContainers = $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers();
1078
            }
1079
1080
            // treat OBJ records
1081 21
            foreach ($this->objs as $n => $obj) {
1082
                // the first shape container never has a corresponding OBJ record, hence $n + 1
1083 3
                if (isset($allSpContainers[$n + 1]) && is_object($allSpContainers[$n + 1])) {
1084 3
                    $spContainer = $allSpContainers[$n + 1];
1085
1086
                    // we skip all spContainers that are a part of a group shape since we cannot yet handle those
1087 3
                    if ($spContainer->getNestingLevel() > 1) {
1088
                        continue;
1089
                    }
1090
1091
                    // calculate the width and height of the shape
1092 3
                    list($startColumn, $startRow) = Coordinate::coordinateFromString($spContainer->getStartCoordinates());
1093 3
                    list($endColumn, $endRow) = Coordinate::coordinateFromString($spContainer->getEndCoordinates());
1094
1095 3
                    $startOffsetX = $spContainer->getStartOffsetX();
1096 3
                    $startOffsetY = $spContainer->getStartOffsetY();
1097 3
                    $endOffsetX = $spContainer->getEndOffsetX();
1098 3
                    $endOffsetY = $spContainer->getEndOffsetY();
1099
1100 3
                    $width = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceX($this->phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX);
1101 3
                    $height = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceY($this->phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY);
1102
1103
                    // calculate offsetX and offsetY of the shape
1104 3
                    $offsetX = $startOffsetX * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeCol($this->phpSheet, $startColumn) / 1024;
1105 3
                    $offsetY = $startOffsetY * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeRow($this->phpSheet, $startRow) / 256;
1106
1107 3
                    switch ($obj['otObjType']) {
1108 3
                        case 0x19:
1109
                            // Note
1110 1
                            if (isset($this->cellNotes[$obj['idObjID']])) {
1111 1
                                $cellNote = $this->cellNotes[$obj['idObjID']];
1112
1113 1
                                if (isset($this->textObjects[$obj['idObjID']])) {
1114 1
                                    $textObject = $this->textObjects[$obj['idObjID']];
1115 1
                                    $this->cellNotes[$obj['idObjID']]['objTextData'] = $textObject;
1116
                                }
1117
                            }
1118
1119 1
                            break;
1120 3
                        case 0x08:
1121
                            // picture
1122
                            // get index to BSE entry (1-based)
1123 3
                            $BSEindex = $spContainer->getOPT(0x0104);
1124
1125
                            // If there is no BSE Index, we will fail here and other fields are not read.
1126
                            // Fix by checking here.
1127
                            // TODO: Why is there no BSE Index? Is this a new Office Version? Password protected field?
1128
                            // More likely : a uncompatible picture
1129 3
                            if (!$BSEindex) {
1130
                                continue;
1131
                            }
1132
1133 3
                            $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection();
1134 3
                            $BSE = $BSECollection[$BSEindex - 1];
1135 3
                            $blipType = $BSE->getBlipType();
1136
1137
                            // need check because some blip types are not supported by Escher reader such as EMF
1138 3
                            if ($blip = $BSE->getBlip()) {
1139 3
                                $ih = imagecreatefromstring($blip->getData());
1140 3
                                $drawing = new MemoryDrawing();
1141 3
                                $drawing->setImageResource($ih);
1142
1143
                                // width, height, offsetX, offsetY
1144 3
                                $drawing->setResizeProportional(false);
1145 3
                                $drawing->setWidth($width);
1146 3
                                $drawing->setHeight($height);
1147 3
                                $drawing->setOffsetX($offsetX);
1148 3
                                $drawing->setOffsetY($offsetY);
1149
1150
                                switch ($blipType) {
1151 3
                                    case BSE::BLIPTYPE_JPEG:
1152 3
                                        $drawing->setRenderingFunction(MemoryDrawing::RENDERING_JPEG);
1153 3
                                        $drawing->setMimeType(MemoryDrawing::MIMETYPE_JPEG);
1154
1155 3
                                        break;
1156 3
                                    case BSE::BLIPTYPE_PNG:
1157 3
                                        $drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG);
1158 3
                                        $drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG);
1159
1160 3
                                        break;
1161
                                }
1162
1163 3
                                $drawing->setWorksheet($this->phpSheet);
1164 3
                                $drawing->setCoordinates($spContainer->getStartCoordinates());
1165
                            }
1166
1167 3
                            break;
1168
                        default:
1169
                            // other object type
1170 3
                            break;
1171
                    }
1172
                }
1173
            }
1174
1175
            // treat SHAREDFMLA records
1176 21
            if ($this->version == self::XLS_BIFF8) {
1177 21
                foreach ($this->sharedFormulaParts as $cell => $baseCell) {
1178
                    list($column, $row) = Coordinate::coordinateFromString($cell);
1179
                    if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
1180
                        $formula = $this->getFormulaFromStructure($this->sharedFormulas[$baseCell], $cell);
1181
                        $this->phpSheet->getCell($cell)->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA);
1182
                    }
1183
                }
1184
            }
1185
1186 21
            if (!empty($this->cellNotes)) {
1187 1
                foreach ($this->cellNotes as $note => $noteDetails) {
1188 1
                    if (!isset($noteDetails['objTextData'])) {
1189
                        if (isset($this->textObjects[$note])) {
1190
                            $textObject = $this->textObjects[$note];
1191
                            $noteDetails['objTextData'] = $textObject;
1192
                        } else {
1193
                            $noteDetails['objTextData']['text'] = '';
1194
                        }
1195
                    }
1196 1
                    $cellAddress = str_replace('$', '', $noteDetails['cellRef']);
1197 21
                    $this->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($this->parseRichText($noteDetails['objTextData']['text']));
1198
                }
1199
            }
1200
        }
1201
1202
        // add the named ranges (defined names)
1203 21
        foreach ($this->definedname as $definedName) {
1204 1
            if ($definedName['isBuiltInName']) {
1205
                switch ($definedName['name']) {
1206
                    case pack('C', 0x06):
1207
                        // print area
1208
                        //    in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1209
                        $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
1210
1211
                        $extractedRanges = [];
1212
                        foreach ($ranges as $range) {
1213
                            // $range should look like one of these
1214
                            //        Foo!$C$7:$J$66
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1215
                            //        Bar!$A$1:$IV$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1216
                            $explodes = explode('!', $range); // FIXME: what if sheetname contains exclamation mark?
1217
                            $sheetName = trim($explodes[0], "'");
1218
                            if (count($explodes) == 2) {
1219
                                if (strpos($explodes[1], ':') === false) {
1220
                                    $explodes[1] = $explodes[1] . ':' . $explodes[1];
1221
                                }
1222
                                $extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66
1223
                            }
1224
                        }
1225
                        if ($docSheet = $this->spreadsheet->getSheetByName($sheetName)) {
1226
                            $docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2
1227
                        }
1228
1229
                        break;
1230
                    case pack('C', 0x07):
1231
                        // print titles (repeating rows)
1232
                        // Assuming BIFF8, there are 3 cases
1233
                        // 1. repeating rows
1234
                        //        formula looks like this: Sheet!$A$1:$IV$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1235
                        //        rows 1-2 repeat
1236
                        // 2. repeating columns
1237
                        //        formula looks like this: Sheet!$A$1:$B$65536
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1238
                        //        columns A-B repeat
1239
                        // 3. both repeating rows and repeating columns
1240
                        //        formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
61% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1241
                        $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
1242
                        foreach ($ranges as $range) {
1243
                            // $range should look like this one of these
1244
                            //        Sheet!$A$1:$B$65536
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1245
                            //        Sheet!$A$1:$IV$2
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1246
                            $explodes = explode('!', $range);
1247
                            if (count($explodes) == 2) {
1248
                                if ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) {
1249
                                    $extractedRange = $explodes[1];
1250
                                    $extractedRange = str_replace('$', '', $extractedRange);
1251
1252
                                    $coordinateStrings = explode(':', $extractedRange);
1253
                                    if (count($coordinateStrings) == 2) {
1254
                                        list($firstColumn, $firstRow) = Coordinate::coordinateFromString($coordinateStrings[0]);
1255
                                        list($lastColumn, $lastRow) = Coordinate::coordinateFromString($coordinateStrings[1]);
1256
1257
                                        if ($firstColumn == 'A' and $lastColumn == 'IV') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1258
                                            // then we have repeating rows
1259
                                            $docSheet->getPageSetup()->setRowsToRepeatAtTop([$firstRow, $lastRow]);
1260
                                        } elseif ($firstRow == 1 and $lastRow == 65536) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
1261
                                            // then we have repeating columns
1262
                                            $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$firstColumn, $lastColumn]);
1263
                                        }
1264
                                    }
1265
                                }
1266
                            }
1267
                        }
1268
1269
                        break;
1270
                }
1271
            } else {
1272
                // Extract range
1273 1
                $explodes = explode('!', $definedName['formula']);
1274
1275 1
                if (count($explodes) == 2) {
1276 1
                    if (($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) ||
1277 1
                        ($docSheet = $this->spreadsheet->getSheetByName(trim($explodes[0], "'")))) {
1278 1
                        $extractedRange = $explodes[1];
1279 1
                        $extractedRange = str_replace('$', '', $extractedRange);
1280
1281 1
                        $localOnly = ($definedName['scope'] == 0) ? false : true;
1282
1283 1
                        $scope = ($definedName['scope'] == 0) ? null : $this->spreadsheet->getSheetByName($this->sheets[$definedName['scope'] - 1]['name']);
1284
1285 1
                        $this->spreadsheet->addNamedRange(new NamedRange((string) $definedName['name'], $docSheet, $extractedRange, $localOnly, $scope));
1286
                    }
1287
                }
1288
                //    Named Value
1289
                    //    TODO Provide support for named values
1290
            }
1291
        }
1292 21
        $this->data = null;
1293
1294 21
        return $this->spreadsheet;
1295
    }
1296
1297
    /**
1298
     * Read record data from stream, decrypting as required.
1299
     *
1300
     * @param string $data Data stream to read from
1301
     * @param int $pos Position to start reading from
1302
     * @param int $len Record data length
1303
     *
1304
     * @return string Record data
1305
     */
1306 24
    private function readRecordData($data, $pos, $len)
1307
    {
1308 24
        $data = substr($data, $pos, $len);
1309
1310
        // File not encrypted, or record before encryption start point
1311 24
        if ($this->encryption == self::MS_BIFF_CRYPTO_NONE || $pos < $this->encryptionStartPos) {
1312 24
            return $data;
1313
        }
1314
1315
        $recordData = '';
1316
        if ($this->encryption == self::MS_BIFF_CRYPTO_RC4) {
1317
            $oldBlock = floor($this->rc4Pos / self::REKEY_BLOCK);
1318
            $block = floor($pos / self::REKEY_BLOCK);
1319
            $endBlock = floor(($pos + $len) / self::REKEY_BLOCK);
1320
1321
            // Spin an RC4 decryptor to the right spot. If we have a decryptor sitting
1322
            // at a point earlier in the current block, re-use it as we can save some time.
1323
            if ($block != $oldBlock || $pos < $this->rc4Pos || !$this->rc4Key) {
1324
                $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
1325
                $step = $pos % self::REKEY_BLOCK;
1326
            } else {
1327
                $step = $pos - $this->rc4Pos;
1328
            }
1329
            $this->rc4Key->RC4(str_repeat("\0", $step));
1330
1331
            // Decrypt record data (re-keying at the end of every block)
1332
            while ($block != $endBlock) {
1333
                $step = self::REKEY_BLOCK - ($pos % self::REKEY_BLOCK);
1334
                $recordData .= $this->rc4Key->RC4(substr($data, 0, $step));
1335
                $data = substr($data, $step);
1336
                $pos += $step;
1337
                $len -= $step;
1338
                ++$block;
1339
                $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
1340
            }
1341
            $recordData .= $this->rc4Key->RC4(substr($data, 0, $len));
1342
1343
            // Keep track of the position of this decryptor.
1344
            // We'll try and re-use it later if we can to speed things up
1345
            $this->rc4Pos = $pos + $len;
1346
        } elseif ($this->encryption == self::MS_BIFF_CRYPTO_XOR) {
1347
            throw new Exception('XOr encryption not supported');
1348
        }
1349
1350
        return $recordData;
1351
    }
1352
1353
    /**
1354
     * Use OLE reader to extract the relevant data streams from the OLE file.
1355
     *
1356
     * @param string $pFilename
1357
     */
1358 24
    private function loadOLE($pFilename)
1359
    {
1360
        // OLE reader
1361 24
        $ole = new OLERead();
1362
        // get excel data,
1363 24
        $ole->read($pFilename);
1364
        // Get workbook data: workbook stream + sheet streams
1365 24
        $this->data = $ole->getStream($ole->wrkbook);
1366
        // Get summary information data
1367 24
        $this->summaryInformation = $ole->getStream($ole->summaryInformation);
1368
        // Get additional document summary information data
1369 24
        $this->documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation);
1370 24
    }
1371
1372
    /**
1373
     * Read summary information.
1374
     */
1375 21
    private function readSummaryInformation()
1376
    {
1377 21
        if (!isset($this->summaryInformation)) {
1378
            return;
1379
        }
1380
1381
        // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
1382
        // offset: 2; size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1383
        // offset: 4; size: 2; OS version
1384
        // offset: 6; size: 2; OS indicator
1385
        // offset: 8; size: 16
1386
        // offset: 24; size: 4; section count
1387 21
        $secCount = self::getInt4d($this->summaryInformation, 24);
1388
1389
        // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9
1390
        // offset: 44; size: 4
1391 21
        $secOffset = self::getInt4d($this->summaryInformation, 44);
1392
1393
        // section header
1394
        // offset: $secOffset; size: 4; section length
1395 21
        $secLength = self::getInt4d($this->summaryInformation, $secOffset);
1396
1397
        // offset: $secOffset+4; size: 4; property count
1398 21
        $countProperties = self::getInt4d($this->summaryInformation, $secOffset + 4);
1399
1400
        // initialize code page (used to resolve string values)
1401 21
        $codePage = 'CP1252';
1402
1403
        // offset: ($secOffset+8); size: var
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1404
        // loop through property decarations and properties
1405 21
        for ($i = 0; $i < $countProperties; ++$i) {
1406
            // offset: ($secOffset+8) + (8 * $i); size: 4; property ID
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1407 21
            $id = self::getInt4d($this->summaryInformation, ($secOffset + 8) + (8 * $i));
1408
1409
            // Use value of property id as appropriate
1410
            // offset: ($secOffset+12) + (8 * $i); size: 4; offset from beginning of section (48)
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1411 21
            $offset = self::getInt4d($this->summaryInformation, ($secOffset + 12) + (8 * $i));
1412
1413 21
            $type = self::getInt4d($this->summaryInformation, $secOffset + $offset);
1414
1415
            // initialize property value
1416 21
            $value = null;
1417
1418
            // extract property value based on property type
1419
            switch ($type) {
1420 21
                case 0x02: // 2 byte signed integer
1421 21
                    $value = self::getUInt2d($this->summaryInformation, $secOffset + 4 + $offset);
1422
1423 21
                    break;
1424 21
                case 0x03: // 4 byte signed integer
1425 20
                    $value = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
1426
1427 20
                    break;
1428 21
                case 0x13: // 4 byte unsigned integer
1429
                    // not needed yet, fix later if necessary
1430
                    break;
1431 21
                case 0x1E: // null-terminated string prepended by dword string length
1432 21
                    $byteLength = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
1433 21
                    $value = substr($this->summaryInformation, $secOffset + 8 + $offset, $byteLength);
1434 21
                    $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage);
1435 21
                    $value = rtrim($value);
1436
1437 21
                    break;
1438 21
                case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
1439
                    // PHP-time
1440 21
                    $value = OLE::OLE2LocalDate(substr($this->summaryInformation, $secOffset + 4 + $offset, 8));
1441
1442 21
                    break;
1443
                case 0x47: // Clipboard format
1444
                    // not needed yet, fix later if necessary
1445
                    break;
1446
            }
1447
1448
            switch ($id) {
1449 21
                case 0x01:    //    Code Page
1450 21
                    $codePage = CodePage::numberToName($value);
1451
1452 21
                    break;
1453 21
                case 0x02:    //    Title
1454 7
                    $this->spreadsheet->getProperties()->setTitle($value);
1455
1456 7
                    break;
1457 21
                case 0x03:    //    Subject
1458 4
                    $this->spreadsheet->getProperties()->setSubject($value);
1459
1460 4
                    break;
1461 21
                case 0x04:    //    Author (Creator)
1462 19
                    $this->spreadsheet->getProperties()->setCreator($value);
1463
1464 19
                    break;
1465 21
                case 0x05:    //    Keywords
1466 4
                    $this->spreadsheet->getProperties()->setKeywords($value);
1467
1468 4
                    break;
1469 21
                case 0x06:    //    Comments (Description)
1470 4
                    $this->spreadsheet->getProperties()->setDescription($value);
1471
1472 4
                    break;
1473 21
                case 0x07:    //    Template
1474
                    //    Not supported by PhpSpreadsheet
1475
                    break;
1476 21
                case 0x08:    //    Last Saved By (LastModifiedBy)
1477 20
                    $this->spreadsheet->getProperties()->setLastModifiedBy($value);
1478
1479 20
                    break;
1480 21
                case 0x09:    //    Revision
1481
                    //    Not supported by PhpSpreadsheet
1482 1
                    break;
1483 21
                case 0x0A:    //    Total Editing Time
1484
                    //    Not supported by PhpSpreadsheet
1485 1
                    break;
1486 21
                case 0x0B:    //    Last Printed
1487
                    //    Not supported by PhpSpreadsheet
1488 1
                    break;
1489 21
                case 0x0C:    //    Created Date/Time
1490 21
                    $this->spreadsheet->getProperties()->setCreated($value);
1491
1492 21
                    break;
1493 21
                case 0x0D:    //    Modified Date/Time
1494 21
                    $this->spreadsheet->getProperties()->setModified($value);
1495
1496 21
                    break;
1497 20
                case 0x0E:    //    Number of Pages
1498
                    //    Not supported by PhpSpreadsheet
1499
                    break;
1500 20
                case 0x0F:    //    Number of Words
1501
                    //    Not supported by PhpSpreadsheet
1502
                    break;
1503 20
                case 0x10:    //    Number of Characters
1504
                    //    Not supported by PhpSpreadsheet
1505
                    break;
1506 20
                case 0x11:    //    Thumbnail
1507
                    //    Not supported by PhpSpreadsheet
1508
                    break;
1509 20
                case 0x12:    //    Name of creating application
1510
                    //    Not supported by PhpSpreadsheet
1511 12
                    break;
1512 20
                case 0x13:    //    Security
1513
                    //    Not supported by PhpSpreadsheet
1514 20
                    break;
1515
            }
1516
        }
1517 21
    }
1518
1519
    /**
1520
     * Read additional document summary information.
1521
     */
1522 21
    private function readDocumentSummaryInformation()
1523
    {
1524 21
        if (!isset($this->documentSummaryInformation)) {
1525
            return;
1526
        }
1527
1528
        //    offset: 0;    size: 2;    must be 0xFE 0xFF (UTF-16 LE byte order mark)
1529
        //    offset: 2;    size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1530
        //    offset: 4;    size: 2;    OS version
1531
        //    offset: 6;    size: 2;    OS indicator
1532
        //    offset: 8;    size: 16
1533
        //    offset: 24;    size: 4;    section count
1534 21
        $secCount = self::getInt4d($this->documentSummaryInformation, 24);
1535
1536
        // offset: 28;    size: 16;    first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae
1537
        // offset: 44;    size: 4;    first section offset
1538 21
        $secOffset = self::getInt4d($this->documentSummaryInformation, 44);
1539
1540
        //    section header
1541
        //    offset: $secOffset;    size: 4;    section length
1542 21
        $secLength = self::getInt4d($this->documentSummaryInformation, $secOffset);
1543
1544
        //    offset: $secOffset+4;    size: 4;    property count
1545 21
        $countProperties = self::getInt4d($this->documentSummaryInformation, $secOffset + 4);
1546
1547
        // initialize code page (used to resolve string values)
1548 21
        $codePage = 'CP1252';
1549
1550
        //    offset: ($secOffset+8);    size: var
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1551
        //    loop through property decarations and properties
1552 21
        for ($i = 0; $i < $countProperties; ++$i) {
1553
            //    offset: ($secOffset+8) + (8 * $i);    size: 4;    property ID
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1554 21
            $id = self::getInt4d($this->documentSummaryInformation, ($secOffset + 8) + (8 * $i));
1555
1556
            // Use value of property id as appropriate
1557
            // offset: 60 + 8 * $i;    size: 4;    offset from beginning of section (48)
1558 21
            $offset = self::getInt4d($this->documentSummaryInformation, ($secOffset + 12) + (8 * $i));
1559
1560 21
            $type = self::getInt4d($this->documentSummaryInformation, $secOffset + $offset);
1561
1562
            // initialize property value
1563 21
            $value = null;
1564
1565
            // extract property value based on property type
1566
            switch ($type) {
1567 21
                case 0x02:    //    2 byte signed integer
1568 21
                    $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1569
1570 21
                    break;
1571 20
                case 0x03:    //    4 byte signed integer
1572 20
                    $value = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1573
1574 20
                    break;
1575 20
                case 0x0B:  // Boolean
1576 20
                    $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1577 20
                    $value = ($value == 0 ? false : true);
1578
1579 20
                    break;
1580 20
                case 0x13:    //    4 byte unsigned integer
1581
                    // not needed yet, fix later if necessary
1582
                    break;
1583 20
                case 0x1E:    //    null-terminated string prepended by dword string length
1584 15
                    $byteLength = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1585 15
                    $value = substr($this->documentSummaryInformation, $secOffset + 8 + $offset, $byteLength);
1586 15
                    $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage);
1587 15
                    $value = rtrim($value);
1588
1589 15
                    break;
1590 20
                case 0x40:    //    Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
1591
                    // PHP-Time
1592
                    $value = OLE::OLE2LocalDate(substr($this->documentSummaryInformation, $secOffset + 4 + $offset, 8));
1593
1594
                    break;
1595 20
                case 0x47:    //    Clipboard format
1596
                    // not needed yet, fix later if necessary
1597
                    break;
1598
            }
1599
1600
            switch ($id) {
1601 21
                case 0x01:    //    Code Page
1602 21
                    $codePage = CodePage::numberToName($value);
1603
1604 21
                    break;
1605 20
                case 0x02:    //    Category
1606 4
                    $this->spreadsheet->getProperties()->setCategory($value);
1607
1608 4
                    break;
1609 20
                case 0x03:    //    Presentation Target
1610
                    //    Not supported by PhpSpreadsheet
1611
                    break;
1612 20
                case 0x04:    //    Bytes
1613
                    //    Not supported by PhpSpreadsheet
1614
                    break;
1615 20
                case 0x05:    //    Lines
1616
                    //    Not supported by PhpSpreadsheet
1617
                    break;
1618 20
                case 0x06:    //    Paragraphs
1619
                    //    Not supported by PhpSpreadsheet
1620
                    break;
1621 20
                case 0x07:    //    Slides
1622
                    //    Not supported by PhpSpreadsheet
1623
                    break;
1624 20
                case 0x08:    //    Notes
1625
                    //    Not supported by PhpSpreadsheet
1626
                    break;
1627 20
                case 0x09:    //    Hidden Slides
1628
                    //    Not supported by PhpSpreadsheet
1629
                    break;
1630 20
                case 0x0A:    //    MM Clips
1631
                    //    Not supported by PhpSpreadsheet
1632
                    break;
1633 20
                case 0x0B:    //    Scale Crop
1634
                    //    Not supported by PhpSpreadsheet
1635 20
                    break;
1636 20
                case 0x0C:    //    Heading Pairs
1637
                    //    Not supported by PhpSpreadsheet
1638 20
                    break;
1639 20
                case 0x0D:    //    Titles of Parts
1640
                    //    Not supported by PhpSpreadsheet
1641 20
                    break;
1642 20
                case 0x0E:    //    Manager
1643 2
                    $this->spreadsheet->getProperties()->setManager($value);
1644
1645 2
                    break;
1646 20
                case 0x0F:    //    Company
1647 14
                    $this->spreadsheet->getProperties()->setCompany($value);
1648
1649 14
                    break;
1650 20
                case 0x10:    //    Links up-to-date
1651
                    //    Not supported by PhpSpreadsheet
1652 20
                    break;
1653
            }
1654
        }
1655 21
    }
1656
1657
    /**
1658
     * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record.
1659
     */
1660 24
    private function readDefault()
1661
    {
1662 24
        $length = self::getUInt2d($this->data, $this->pos + 2);
1663
1664
        // move stream pointer to next record
1665 24
        $this->pos += 4 + $length;
1666 24
    }
1667
1668
    /**
1669
     *    The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions,
1670
     *        this record stores a note (cell note). This feature was significantly enhanced in Excel 97.
1671
     */
1672 1
    private function readNote()
1673
    {
1674 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
1675 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1676
1677
        // move stream pointer to next record
1678 1
        $this->pos += 4 + $length;
1679
1680 1
        if ($this->readDataOnly) {
1681
            return;
1682
        }
1683
1684 1
        $cellAddress = $this->readBIFF8CellAddress(substr($recordData, 0, 4));
1685 1
        if ($this->version == self::XLS_BIFF8) {
1686 1
            $noteObjID = self::getUInt2d($recordData, 6);
1687 1
            $noteAuthor = self::readUnicodeStringLong(substr($recordData, 8));
1688 1
            $noteAuthor = $noteAuthor['value'];
1689 1
            $this->cellNotes[$noteObjID] = [
1690 1
                'cellRef' => $cellAddress,
1691 1
                'objectID' => $noteObjID,
1692 1
                'author' => $noteAuthor,
1693
            ];
1694
        } else {
1695
            $extension = false;
1696
            if ($cellAddress == '$B$65536') {
1697
                //    If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation
1698
                //        note from the previous cell annotation. We're not yet handling this, so annotations longer than the
1699
                //        max 2048 bytes will probably throw a wobbly.
1700
                $row = self::getUInt2d($recordData, 0);
1701
                $extension = true;
1702
                $cellAddress = array_pop(array_keys($this->phpSheet->getComments()));
1703
            }
1704
1705
            $cellAddress = str_replace('$', '', $cellAddress);
1706
            $noteLength = self::getUInt2d($recordData, 4);
1707
            $noteText = trim(substr($recordData, 6));
1708
1709
            if ($extension) {
1710
                //    Concatenate this extension with the currently set comment for the cell
1711
                $comment = $this->phpSheet->getComment($cellAddress);
1712
                $commentText = $comment->getText()->getPlainText();
1713
                $comment->setText($this->parseRichText($commentText . $noteText));
1714
            } else {
1715
                //    Set comment for the cell
1716
                $this->phpSheet->getComment($cellAddress)->setText($this->parseRichText($noteText));
1717
//                                                    ->setAuthor($author)
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1718
            }
1719
        }
1720 1
    }
1721
1722
    /**
1723
     * The TEXT Object record contains the text associated with a cell annotation.
1724
     */
1725 1
    private function readTextObject()
1726
    {
1727 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
1728 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1729
1730
        // move stream pointer to next record
1731 1
        $this->pos += 4 + $length;
1732
1733 1
        if ($this->readDataOnly) {
1734
            return;
1735
        }
1736
1737
        // recordData consists of an array of subrecords looking like this:
1738
        //    grbit: 2 bytes; Option Flags
1739
        //    rot: 2 bytes; rotation
1740
        //    cchText: 2 bytes; length of the text (in the first continue record)
1741
        //    cbRuns: 2 bytes; length of the formatting (in the second continue record)
1742
        // followed by the continuation records containing the actual text and formatting
1743 1
        $grbitOpts = self::getUInt2d($recordData, 0);
1744 1
        $rot = self::getUInt2d($recordData, 2);
1745 1
        $cchText = self::getUInt2d($recordData, 10);
1746 1
        $cbRuns = self::getUInt2d($recordData, 12);
1747 1
        $text = $this->getSplicedRecordData();
1748
1749 1
        $textByte = $text['spliceOffsets'][1] - $text['spliceOffsets'][0] - 1;
1750 1
        $textStr = substr($text['recordData'], $text['spliceOffsets'][0] + 1, $textByte);
1751
        // get 1 byte
1752 1
        $is16Bit = ord($text['recordData'][0]);
1753
        // it is possible to use a compressed format,
1754
        // which omits the high bytes of all characters, if they are all zero
1755 1
        if (($is16Bit & 0x01) === 0) {
1756 1
            $textStr = StringHelper::ConvertEncoding($textStr, 'UTF-8', 'ISO-8859-1');
1757
        } else {
1758
            $textStr = $this->decodeCodepage($textStr);
1759
        }
1760
1761 1
        $this->textObjects[$this->textObjRef] = [
1762 1
            'text' => $textStr,
1763 1
            'format' => substr($text['recordData'], $text['spliceOffsets'][1], $cbRuns),
1764 1
            'alignment' => $grbitOpts,
1765 1
            'rotation' => $rot,
1766
        ];
1767 1
    }
1768
1769
    /**
1770
     * Read BOF.
1771
     */
1772 24
    private function readBof()
1773
    {
1774 24
        $length = self::getUInt2d($this->data, $this->pos + 2);
1775 24
        $recordData = substr($this->data, $this->pos + 4, $length);
1776
1777
        // move stream pointer to next record
1778 24
        $this->pos += 4 + $length;
1779
1780
        // offset: 2; size: 2; type of the following data
1781 24
        $substreamType = self::getUInt2d($recordData, 2);
1782
1783
        switch ($substreamType) {
1784 24
            case self::XLS_WORKBOOKGLOBALS:
1785 24
                $version = self::getUInt2d($recordData, 0);
1786 24
                if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) {
1787
                    throw new Exception('Cannot read this Excel file. Version is too old.');
1788
                }
1789 24
                $this->version = $version;
1790
1791 24
                break;
1792 22
            case self::XLS_WORKSHEET:
1793
                // do not use this version information for anything
1794
                // it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream
1795 22
                break;
1796
            default:
1797
                // substream, e.g. chart
1798
                // just skip the entire substream
1799
                do {
1800
                    $code = self::getUInt2d($this->data, $this->pos);
1801
                    $this->readDefault();
1802
                } while ($code != self::XLS_TYPE_EOF && $this->pos < $this->dataSize);
1803
1804
                break;
1805
        }
1806 24
    }
1807
1808
    /**
1809
     * FILEPASS.
1810
     *
1811
     * This record is part of the File Protection Block. It
1812
     * contains information about the read/write password of the
1813
     * file. All record contents following this record will be
1814
     * encrypted.
1815
     *
1816
     * --    "OpenOffice.org's Documentation of the Microsoft
1817
     *         Excel File Format"
1818
     *
1819
     * The decryption functions and objects used from here on in
1820
     * are based on the source of Spreadsheet-ParseExcel:
1821
     * http://search.cpan.org/~jmcnamara/Spreadsheet-ParseExcel/
1822
     */
1823
    private function readFilepass()
1824
    {
1825
        $length = self::getUInt2d($this->data, $this->pos + 2);
1826
1827
        if ($length != 54) {
1828
            throw new Exception('Unexpected file pass record length');
1829
        }
1830
1831
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1832
1833
        // move stream pointer to next record
1834
        $this->pos += 4 + $length;
1835
1836
        if (!$this->verifyPassword('VelvetSweatshop', substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) {
1837
            throw new Exception('Decryption password incorrect');
1838
        }
1839
1840
        $this->encryption = self::MS_BIFF_CRYPTO_RC4;
1841
1842
        // Decryption required from the record after next onwards
1843
        $this->encryptionStartPos = $this->pos + self::getUInt2d($this->data, $this->pos + 2);
1844
    }
1845
1846
    /**
1847
     * Make an RC4 decryptor for the given block.
1848
     *
1849
     * @param int $block Block for which to create decrypto
1850
     * @param string $valContext MD5 context state
1851
     *
1852
     * @return Xls\RC4
1853
     */
1854
    private function makeKey($block, $valContext)
1855
    {
1856
        $pwarray = str_repeat("\0", 64);
1857
1858
        for ($i = 0; $i < 5; ++$i) {
1859
            $pwarray[$i] = $valContext[$i];
1860
        }
1861
1862
        $pwarray[5] = chr($block & 0xff);
1863
        $pwarray[6] = chr(($block >> 8) & 0xff);
1864
        $pwarray[7] = chr(($block >> 16) & 0xff);
1865
        $pwarray[8] = chr(($block >> 24) & 0xff);
1866
1867
        $pwarray[9] = "\x80";
1868
        $pwarray[56] = "\x48";
1869
1870
        $md5 = new Xls\MD5();
1871
        $md5->add($pwarray);
1872
1873
        $s = $md5->getContext();
1874
1875
        return new Xls\RC4($s);
1876
    }
1877
1878
    /**
1879
     * Verify RC4 file password.
1880
     *
1881
     * @param string $password Password to check
1882
     * @param string $docid Document id
1883
     * @param string $salt_data Salt data
1884
     * @param string $hashedsalt_data Hashed salt data
1885
     * @param string $valContext Set to the MD5 context of the value
1886
     *
1887
     * @return bool Success
1888
     */
1889
    private function verifyPassword($password, $docid, $salt_data, $hashedsalt_data, &$valContext)
1890
    {
1891
        $pwarray = str_repeat("\0", 64);
1892
1893
        $iMax = strlen($password);
1894
        for ($i = 0; $i < $iMax; ++$i) {
1895
            $o = ord(substr($password, $i, 1));
1896
            $pwarray[2 * $i] = chr($o & 0xff);
1897
            $pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff);
1898
        }
1899
        $pwarray[2 * $i] = chr(0x80);
1900
        $pwarray[56] = chr(($i << 4) & 0xff);
1901
1902
        $md5 = new Xls\MD5();
1903
        $md5->add($pwarray);
1904
1905
        $mdContext1 = $md5->getContext();
1906
1907
        $offset = 0;
1908
        $keyoffset = 0;
1909
        $tocopy = 5;
1910
1911
        $md5->reset();
1912
1913
        while ($offset != 16) {
1914
            if ((64 - $offset) < 5) {
1915
                $tocopy = 64 - $offset;
1916
            }
1917
            for ($i = 0; $i <= $tocopy; ++$i) {
1918
                $pwarray[$offset + $i] = $mdContext1[$keyoffset + $i];
1919
            }
1920
            $offset += $tocopy;
1921
1922
            if ($offset == 64) {
1923
                $md5->add($pwarray);
1924
                $keyoffset = $tocopy;
1925
                $tocopy = 5 - $tocopy;
1926
                $offset = 0;
1927
1928
                continue;
1929
            }
1930
1931
            $keyoffset = 0;
1932
            $tocopy = 5;
1933
            for ($i = 0; $i < 16; ++$i) {
1934
                $pwarray[$offset + $i] = $docid[$i];
1935
            }
1936
            $offset += 16;
1937
        }
1938
1939
        $pwarray[16] = "\x80";
1940
        for ($i = 0; $i < 47; ++$i) {
1941
            $pwarray[17 + $i] = "\0";
1942
        }
1943
        $pwarray[56] = "\x80";
1944
        $pwarray[57] = "\x0a";
1945
1946
        $md5->add($pwarray);
1947
        $valContext = $md5->getContext();
1948
1949
        $key = $this->makeKey(0, $valContext);
1950
1951
        $salt = $key->RC4($salt_data);
1952
        $hashedsalt = $key->RC4($hashedsalt_data);
1953
1954
        $salt .= "\x80" . str_repeat("\0", 47);
1955
        $salt[56] = "\x80";
1956
1957
        $md5->reset();
1958
        $md5->add($salt);
1959
        $mdContext2 = $md5->getContext();
1960
1961
        return $mdContext2 == $hashedsalt;
1962
    }
1963
1964
    /**
1965
     * CODEPAGE.
1966
     *
1967
     * This record stores the text encoding used to write byte
1968
     * strings, stored as MS Windows code page identifier.
1969
     *
1970
     * --    "OpenOffice.org's Documentation of the Microsoft
1971
     *         Excel File Format"
1972
     */
1973 21
    private function readCodepage()
1974
    {
1975 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
1976 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1977
1978
        // move stream pointer to next record
1979 21
        $this->pos += 4 + $length;
1980
1981
        // offset: 0; size: 2; code page identifier
1982 21
        $codepage = self::getUInt2d($recordData, 0);
1983
1984 21
        $this->codepage = CodePage::numberToName($codepage);
1985 21
    }
1986
1987
    /**
1988
     * DATEMODE.
1989
     *
1990
     * This record specifies the base date for displaying date
1991
     * values. All dates are stored as count of days past this
1992
     * base date. In BIFF2-BIFF4 this record is part of the
1993
     * Calculation Settings Block. In BIFF5-BIFF8 it is
1994
     * stored in the Workbook Globals Substream.
1995
     *
1996
     * --    "OpenOffice.org's Documentation of the Microsoft
1997
     *         Excel File Format"
1998
     */
1999 21
    private function readDateMode()
2000
    {
2001 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
2002 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2003
2004
        // move stream pointer to next record
2005 21
        $this->pos += 4 + $length;
2006
2007
        // offset: 0; size: 2; 0 = base 1900, 1 = base 1904
2008 21
        Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
2009 21
        if (ord($recordData[0]) == 1) {
2010
            Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
2011
        }
2012 21
    }
2013
2014
    /**
2015
     * Read a FONT record.
2016
     */
2017 21
    private function readFont()
2018
    {
2019 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
2020 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2021
2022
        // move stream pointer to next record
2023 21
        $this->pos += 4 + $length;
2024
2025 21
        if (!$this->readDataOnly) {
2026 20
            $objFont = new Font();
2027
2028
            // offset: 0; size: 2; height of the font (in twips = 1/20 of a point)
2029 20
            $size = self::getUInt2d($recordData, 0);
2030 20
            $objFont->setSize($size / 20);
2031
2032
            // offset: 2; size: 2; option flags
2033
            // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8)
2034
            // bit: 1; mask 0x0002; italic
2035 20
            $isItalic = (0x0002 & self::getUInt2d($recordData, 2)) >> 1;
2036 20
            if ($isItalic) {
2037 5
                $objFont->setItalic(true);
2038
            }
2039
2040
            // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8)
2041
            // bit: 3; mask 0x0008; strikethrough
2042 20
            $isStrike = (0x0008 & self::getUInt2d($recordData, 2)) >> 3;
2043 20
            if ($isStrike) {
2044
                $objFont->setStrikethrough(true);
2045
            }
2046
2047
            // offset: 4; size: 2; colour index
2048 20
            $colorIndex = self::getUInt2d($recordData, 4);
2049 20
            $objFont->colorIndex = $colorIndex;
2050
2051
            // offset: 6; size: 2; font weight
2052 20
            $weight = self::getUInt2d($recordData, 6);
2053
            switch ($weight) {
2054 20
                case 0x02BC:
2055 17
                    $objFont->setBold(true);
2056
2057 17
                    break;
2058
            }
2059
2060
            // offset: 8; size: 2; escapement type
2061 20
            $escapement = self::getUInt2d($recordData, 8);
2062
            switch ($escapement) {
2063 20
                case 0x0001:
2064
                    $objFont->setSuperscript(true);
2065
2066
                    break;
2067 20
                case 0x0002:
2068
                    $objFont->setSubscript(true);
2069
2070
                    break;
2071
            }
2072
2073
            // offset: 10; size: 1; underline type
2074 20
            $underlineType = ord($recordData[10]);
2075
            switch ($underlineType) {
2076 20
                case 0x00:
2077 20
                    break; // no underline
2078 2
                case 0x01:
2079 2
                    $objFont->setUnderline(Font::UNDERLINE_SINGLE);
2080
2081 2
                    break;
2082
                case 0x02:
2083
                    $objFont->setUnderline(Font::UNDERLINE_DOUBLE);
2084
2085
                    break;
2086
                case 0x21:
2087
                    $objFont->setUnderline(Font::UNDERLINE_SINGLEACCOUNTING);
2088
2089
                    break;
2090
                case 0x22:
2091
                    $objFont->setUnderline(Font::UNDERLINE_DOUBLEACCOUNTING);
2092
2093
                    break;
2094
            }
2095
2096
            // offset: 11; size: 1; font family
2097
            // offset: 12; size: 1; character set
2098
            // offset: 13; size: 1; not used
2099
            // offset: 14; size: var; font name
2100 20
            if ($this->version == self::XLS_BIFF8) {
2101 20
                $string = self::readUnicodeStringShort(substr($recordData, 14));
2102
            } else {
2103
                $string = $this->readByteStringShort(substr($recordData, 14));
2104
            }
2105 20
            $objFont->setName($string['value']);
2106
2107 20
            $this->objFonts[] = $objFont;
2108
        }
2109 21
    }
2110
2111
    /**
2112
     * FORMAT.
2113
     *
2114
     * This record contains information about a number format.
2115
     * All FORMAT records occur together in a sequential list.
2116
     *
2117
     * In BIFF2-BIFF4 other records referencing a FORMAT record
2118
     * contain a zero-based index into this list. From BIFF5 on
2119
     * the FORMAT record contains the index itself that will be
2120
     * used by other records.
2121
     *
2122
     * --    "OpenOffice.org's Documentation of the Microsoft
2123
     *         Excel File Format"
2124
     */
2125 18
    private function readFormat()
2126
    {
2127 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
2128 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2129
2130
        // move stream pointer to next record
2131 18
        $this->pos += 4 + $length;
2132
2133 18
        if (!$this->readDataOnly) {
2134 17
            $indexCode = self::getUInt2d($recordData, 0);
2135
2136 17
            if ($this->version == self::XLS_BIFF8) {
2137 17
                $string = self::readUnicodeStringLong(substr($recordData, 2));
2138
            } else {
2139
                // BIFF7
2140
                $string = $this->readByteStringShort(substr($recordData, 2));
2141
            }
2142
2143 17
            $formatString = $string['value'];
2144 17
            $this->formats[$indexCode] = $formatString;
2145
        }
2146 18
    }
2147
2148
    /**
2149
     * XF - Extended Format.
2150
     *
2151
     * This record contains formatting information for cells, rows, columns or styles.
2152
     * According to http://support.microsoft.com/kb/147732 there are always at least 15 cell style XF
2153
     * and 1 cell XF.
2154
     * Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF
2155
     * and XF record 15 is a cell XF
2156
     * We only read the first cell style XF and skip the remaining cell style XF records
2157
     * We read all cell XF records.
2158
     *
2159
     * --    "OpenOffice.org's Documentation of the Microsoft
2160
     *         Excel File Format"
2161
     */
2162 21
    private function readXf()
2163
    {
2164 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
2165 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2166
2167
        // move stream pointer to next record
2168 21
        $this->pos += 4 + $length;
2169
2170 21
        $objStyle = new Style();
2171
2172 21
        if (!$this->readDataOnly) {
2173
            // offset:  0; size: 2; Index to FONT record
2174 20
            if (self::getUInt2d($recordData, 0) < 4) {
2175 20
                $fontIndex = self::getUInt2d($recordData, 0);
2176
            } else {
2177
                // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
2178
                // check the OpenOffice documentation of the FONT record
2179 17
                $fontIndex = self::getUInt2d($recordData, 0) - 1;
2180
            }
2181 20
            $objStyle->setFont($this->objFonts[$fontIndex]);
2182
2183
            // offset:  2; size: 2; Index to FORMAT record
2184 20
            $numberFormatIndex = self::getUInt2d($recordData, 2);
2185 20
            if (isset($this->formats[$numberFormatIndex])) {
2186
                // then we have user-defined format code
2187 15
                $numberFormat = ['formatCode' => $this->formats[$numberFormatIndex]];
2188 20
            } elseif (($code = NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') {
2189
                // then we have built-in format code
2190 20
                $numberFormat = ['formatCode' => $code];
2191
            } else {
2192
                // we set the general format code
2193 1
                $numberFormat = ['formatCode' => 'General'];
2194
            }
2195 20
            $objStyle->getNumberFormat()->setFormatCode($numberFormat['formatCode']);
2196
2197
            // offset:  4; size: 2; XF type, cell protection, and parent style XF
2198
            // bit 2-0; mask 0x0007; XF_TYPE_PROT
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2199 20
            $xfTypeProt = self::getUInt2d($recordData, 4);
2200
            // bit 0; mask 0x01; 1 = cell is locked
2201 20
            $isLocked = (0x01 & $xfTypeProt) >> 0;
2202 20
            $objStyle->getProtection()->setLocked($isLocked ? Protection::PROTECTION_INHERIT : Protection::PROTECTION_UNPROTECTED);
2203
2204
            // bit 1; mask 0x02; 1 = Formula is hidden
2205 20
            $isHidden = (0x02 & $xfTypeProt) >> 1;
2206 20
            $objStyle->getProtection()->setHidden($isHidden ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED);
2207
2208
            // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF
2209 20
            $isCellStyleXf = (0x04 & $xfTypeProt) >> 2;
2210
2211
            // offset:  6; size: 1; Alignment and text break
2212
            // bit 2-0, mask 0x07; horizontal alignment
2213 20
            $horAlign = (0x07 & ord($recordData[6])) >> 0;
2214
            switch ($horAlign) {
2215 20
                case 0:
2216 20
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_GENERAL);
2217
2218 20
                    break;
2219 12
                case 1:
2220 2
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
2221
2222 2
                    break;
2223 12
                case 2:
2224 10
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
2225
2226 10
                    break;
2227 2
                case 3:
2228 2
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
2229
2230 2
                    break;
2231 2
                case 4:
2232
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_FILL);
2233
2234
                    break;
2235 2
                case 5:
2236 2
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_JUSTIFY);
2237
2238 2
                    break;
2239
                case 6:
2240
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER_CONTINUOUS);
2241
2242
                    break;
2243
            }
2244
            // bit 3, mask 0x08; wrap text
2245 20
            $wrapText = (0x08 & ord($recordData[6])) >> 3;
2246
            switch ($wrapText) {
2247 20
                case 0:
2248 20
                    $objStyle->getAlignment()->setWrapText(false);
2249
2250 20
                    break;
2251 2
                case 1:
2252 2
                    $objStyle->getAlignment()->setWrapText(true);
2253
2254 2
                    break;
2255
            }
2256
            // bit 6-4, mask 0x70; vertical alignment
2257 20
            $vertAlign = (0x70 & ord($recordData[6])) >> 4;
2258
            switch ($vertAlign) {
2259 20
                case 0:
2260
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_TOP);
2261
2262
                    break;
2263 20
                case 1:
2264 2
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
2265
2266 2
                    break;
2267 20
                case 2:
2268 20
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_BOTTOM);
2269
2270 20
                    break;
2271
                case 3:
2272
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_JUSTIFY);
2273
2274
                    break;
2275
            }
2276
2277 20
            if ($this->version == self::XLS_BIFF8) {
2278
                // offset:  7; size: 1; XF_ROTATION: Text rotation angle
2279 20
                $angle = ord($recordData[7]);
2280 20
                $rotation = 0;
2281 20
                if ($angle <= 90) {
2282 20
                    $rotation = $angle;
2283
                } elseif ($angle <= 180) {
2284
                    $rotation = 90 - $angle;
2285
                } elseif ($angle == 255) {
2286
                    $rotation = -165;
2287
                }
2288 20
                $objStyle->getAlignment()->setTextRotation($rotation);
2289
2290
                // offset:  8; size: 1; Indentation, shrink to cell size, and text direction
2291
                // bit: 3-0; mask: 0x0F; indent level
2292 20
                $indent = (0x0F & ord($recordData[8])) >> 0;
2293 20
                $objStyle->getAlignment()->setIndent($indent);
2294
2295
                // bit: 4; mask: 0x10; 1 = shrink content to fit into cell
2296 20
                $shrinkToFit = (0x10 & ord($recordData[8])) >> 4;
2297
                switch ($shrinkToFit) {
2298 20
                    case 0:
2299 20
                        $objStyle->getAlignment()->setShrinkToFit(false);
2300
2301 20
                        break;
2302 1
                    case 1:
2303 1
                        $objStyle->getAlignment()->setShrinkToFit(true);
2304
2305 1
                        break;
2306
                }
2307
2308
                // offset:  9; size: 1; Flags used for attribute groups
2309
2310
                // offset: 10; size: 4; Cell border lines and background area
2311
                // bit: 3-0; mask: 0x0000000F; left style
2312 20
                if ($bordersLeftStyle = Xls\Style\Border::lookup((0x0000000F & self::getInt4d($recordData, 10)) >> 0)) {
2313 20
                    $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle);
2314
                }
2315
                // bit: 7-4; mask: 0x000000F0; right style
2316 20
                if ($bordersRightStyle = Xls\Style\Border::lookup((0x000000F0 & self::getInt4d($recordData, 10)) >> 4)) {
2317 20
                    $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle);
2318
                }
2319
                // bit: 11-8; mask: 0x00000F00; top style
2320 20
                if ($bordersTopStyle = Xls\Style\Border::lookup((0x00000F00 & self::getInt4d($recordData, 10)) >> 8)) {
2321 20
                    $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle);
2322
                }
2323
                // bit: 15-12; mask: 0x0000F000; bottom style
2324 20
                if ($bordersBottomStyle = Xls\Style\Border::lookup((0x0000F000 & self::getInt4d($recordData, 10)) >> 12)) {
2325 20
                    $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle);
2326
                }
2327
                // bit: 22-16; mask: 0x007F0000; left color
2328 20
                $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::getInt4d($recordData, 10)) >> 16;
2329
2330
                // bit: 29-23; mask: 0x3F800000; right color
2331 20
                $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::getInt4d($recordData, 10)) >> 23;
2332
2333
                // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom
2334 20
                $diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false;
2335
2336
                // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right
2337 20
                $diagonalUp = (0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false;
2338
2339 20
                if ($diagonalUp == false && $diagonalDown == false) {
2340 20
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE);
2341
                } elseif ($diagonalUp == true && $diagonalDown == false) {
2342
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP);
2343
                } elseif ($diagonalUp == false && $diagonalDown == true) {
2344
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN);
2345
                } elseif ($diagonalUp == true && $diagonalDown == true) {
2346
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH);
2347
                }
2348
2349
                // offset: 14; size: 4;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2350
                // bit: 6-0; mask: 0x0000007F; top color
2351 20
                $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::getInt4d($recordData, 14)) >> 0;
2352
2353
                // bit: 13-7; mask: 0x00003F80; bottom color
2354 20
                $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::getInt4d($recordData, 14)) >> 7;
2355
2356
                // bit: 20-14; mask: 0x001FC000; diagonal color
2357 20
                $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::getInt4d($recordData, 14)) >> 14;
2358
2359
                // bit: 24-21; mask: 0x01E00000; diagonal style
2360 20
                if ($bordersDiagonalStyle = Xls\Style\Border::lookup((0x01E00000 & self::getInt4d($recordData, 14)) >> 21)) {
2361 20
                    $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle);
2362
                }
2363
2364
                // bit: 31-26; mask: 0xFC000000 fill pattern
2365 20
                if ($fillType = Xls\Style\FillPattern::lookup((0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) {
2366 20
                    $objStyle->getFill()->setFillType($fillType);
2367
                }
2368
                // offset: 18; size: 2; pattern and background colour
2369
                // bit: 6-0; mask: 0x007F; color index for pattern color
2370 20
                $objStyle->getFill()->startcolorIndex = (0x007F & self::getUInt2d($recordData, 18)) >> 0;
2371
2372
                // bit: 13-7; mask: 0x3F80; color index for pattern background
2373 20
                $objStyle->getFill()->endcolorIndex = (0x3F80 & self::getUInt2d($recordData, 18)) >> 7;
2374
            } else {
2375
                // BIFF5
2376
2377
                // offset: 7; size: 1; Text orientation and flags
2378
                $orientationAndFlags = ord($recordData[7]);
2379
2380
                // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation
2381
                $xfOrientation = (0x03 & $orientationAndFlags) >> 0;
2382
                switch ($xfOrientation) {
2383
                    case 0:
2384
                        $objStyle->getAlignment()->setTextRotation(0);
2385
2386
                        break;
2387
                    case 1:
2388
                        $objStyle->getAlignment()->setTextRotation(-165);
2389
2390
                        break;
2391
                    case 2:
2392
                        $objStyle->getAlignment()->setTextRotation(90);
2393
2394
                        break;
2395
                    case 3:
2396
                        $objStyle->getAlignment()->setTextRotation(-90);
2397
2398
                        break;
2399
                }
2400
2401
                // offset: 8; size: 4; cell border lines and background area
2402
                $borderAndBackground = self::getInt4d($recordData, 8);
2403
2404
                // bit: 6-0; mask: 0x0000007F; color index for pattern color
2405
                $objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0;
2406
2407
                // bit: 13-7; mask: 0x00003F80; color index for pattern background
2408
                $objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7;
2409
2410
                // bit: 21-16; mask: 0x003F0000; fill pattern
2411
                $objStyle->getFill()->setFillType(Xls\Style\FillPattern::lookup((0x003F0000 & $borderAndBackground) >> 16));
2412
2413
                // bit: 24-22; mask: 0x01C00000; bottom line style
2414
                $objStyle->getBorders()->getBottom()->setBorderStyle(Xls\Style\Border::lookup((0x01C00000 & $borderAndBackground) >> 22));
2415
2416
                // bit: 31-25; mask: 0xFE000000; bottom line color
2417
                $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25;
2418
2419
                // offset: 12; size: 4; cell border lines
2420
                $borderLines = self::getInt4d($recordData, 12);
2421
2422
                // bit: 2-0; mask: 0x00000007; top line style
2423
                $objStyle->getBorders()->getTop()->setBorderStyle(Xls\Style\Border::lookup((0x00000007 & $borderLines) >> 0));
2424
2425
                // bit: 5-3; mask: 0x00000038; left line style
2426
                $objStyle->getBorders()->getLeft()->setBorderStyle(Xls\Style\Border::lookup((0x00000038 & $borderLines) >> 3));
2427
2428
                // bit: 8-6; mask: 0x000001C0; right line style
2429
                $objStyle->getBorders()->getRight()->setBorderStyle(Xls\Style\Border::lookup((0x000001C0 & $borderLines) >> 6));
2430
2431
                // bit: 15-9; mask: 0x0000FE00; top line color index
2432
                $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9;
2433
2434
                // bit: 22-16; mask: 0x007F0000; left line color index
2435
                $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16;
2436
2437
                // bit: 29-23; mask: 0x3F800000; right line color index
2438
                $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23;
2439
            }
2440
2441
            // add cellStyleXf or cellXf and update mapping
2442 20
            if ($isCellStyleXf) {
2443
                // we only read one style XF record which is always the first
2444 20
                if ($this->xfIndex == 0) {
2445 20
                    $this->spreadsheet->addCellStyleXf($objStyle);
2446 20
                    $this->mapCellStyleXfIndex[$this->xfIndex] = 0;
2447
                }
2448
            } else {
2449
                // we read all cell XF records
2450 20
                $this->spreadsheet->addCellXf($objStyle);
2451 20
                $this->mapCellXfIndex[$this->xfIndex] = count($this->spreadsheet->getCellXfCollection()) - 1;
2452
            }
2453
2454
            // update XF index for when we read next record
2455 20
            ++$this->xfIndex;
2456
        }
2457 21
    }
2458
2459 3
    private function readXfExt()
2460
    {
2461 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
2462 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2463
2464
        // move stream pointer to next record
2465 3
        $this->pos += 4 + $length;
2466
2467 3
        if (!$this->readDataOnly) {
2468
            // offset: 0; size: 2; 0x087D = repeated header
2469
2470
            // offset: 2; size: 2
2471
2472
            // offset: 4; size: 8; not used
2473
2474
            // offset: 12; size: 2; record version
2475
2476
            // offset: 14; size: 2; index to XF record which this record modifies
2477 3
            $ixfe = self::getUInt2d($recordData, 14);
2478
2479
            // offset: 16; size: 2; not used
2480
2481
            // offset: 18; size: 2; number of extension properties that follow
2482 3
            $cexts = self::getUInt2d($recordData, 18);
2483
2484
            // start reading the actual extension data
2485 3
            $offset = 20;
2486 3
            while ($offset < $length) {
2487
                // extension type
2488 3
                $extType = self::getUInt2d($recordData, $offset);
2489
2490
                // extension length
2491 3
                $cb = self::getUInt2d($recordData, $offset + 2);
2492
2493
                // extension data
2494 3
                $extData = substr($recordData, $offset + 4, $cb);
2495
2496
                switch ($extType) {
2497 3
                    case 4:        // fill start color
2498 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2499 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2500
2501 3
                        if ($xclfType == 2) {
2502 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2503
2504
                            // modify the relevant style property
2505 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2506 1
                                $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
2507 1
                                $fill->getStartColor()->setRGB($rgb);
2508 1
                                unset($fill->startcolorIndex); // normal color index does not apply, discard
2509
                            }
2510
                        }
2511
2512 3
                        break;
2513 3
                    case 5:        // fill end color
2514 1
                        $xclfType = self::getUInt2d($extData, 0); // color type
2515 1
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2516
2517 1
                        if ($xclfType == 2) {
2518 1
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2519
2520
                            // modify the relevant style property
2521 1
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2522 1
                                $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
2523 1
                                $fill->getEndColor()->setRGB($rgb);
2524 1
                                unset($fill->endcolorIndex); // normal color index does not apply, discard
2525
                            }
2526
                        }
2527
2528 1
                        break;
2529 3
                    case 7:        // border color top
2530 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2531 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2532
2533 3
                        if ($xclfType == 2) {
2534 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2535
2536
                            // modify the relevant style property
2537 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2538 1
                                $top = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getTop();
2539 1
                                $top->getColor()->setRGB($rgb);
2540 1
                                unset($top->colorIndex); // normal color index does not apply, discard
2541
                            }
2542
                        }
2543
2544 3
                        break;
2545 3
                    case 8:        // border color bottom
2546 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2547 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2548
2549 3
                        if ($xclfType == 2) {
2550 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2551
2552
                            // modify the relevant style property
2553 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2554 1
                                $bottom = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getBottom();
2555 1
                                $bottom->getColor()->setRGB($rgb);
2556 1
                                unset($bottom->colorIndex); // normal color index does not apply, discard
2557
                            }
2558
                        }
2559
2560 3
                        break;
2561 3
                    case 9:        // border color left
2562 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2563 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2564
2565 3
                        if ($xclfType == 2) {
2566 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2567
2568
                            // modify the relevant style property
2569 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2570 1
                                $left = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getLeft();
2571 1
                                $left->getColor()->setRGB($rgb);
2572 1
                                unset($left->colorIndex); // normal color index does not apply, discard
2573
                            }
2574
                        }
2575
2576 3
                        break;
2577 3
                    case 10:        // border color right
2578 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2579 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2580
2581 3
                        if ($xclfType == 2) {
2582 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2583
2584
                            // modify the relevant style property
2585 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2586 1
                                $right = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getRight();
2587 1
                                $right->getColor()->setRGB($rgb);
2588 1
                                unset($right->colorIndex); // normal color index does not apply, discard
2589
                            }
2590
                        }
2591
2592 3
                        break;
2593 3
                    case 11:        // border color diagonal
2594
                        $xclfType = self::getUInt2d($extData, 0); // color type
2595
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2596
2597
                        if ($xclfType == 2) {
2598
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2599
2600
                            // modify the relevant style property
2601
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2602
                                $diagonal = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getDiagonal();
2603
                                $diagonal->getColor()->setRGB($rgb);
2604
                                unset($diagonal->colorIndex); // normal color index does not apply, discard
2605
                            }
2606
                        }
2607
2608
                        break;
2609 3
                    case 13:    // font color
2610 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2611 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2612
2613 3
                        if ($xclfType == 2) {
2614 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2615
2616
                            // modify the relevant style property
2617 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2618 1
                                $font = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont();
2619 1
                                $font->getColor()->setRGB($rgb);
2620 1
                                unset($font->colorIndex); // normal color index does not apply, discard
2621
                            }
2622
                        }
2623
2624 3
                        break;
2625
                }
2626
2627 3
                $offset += $cb;
2628
            }
2629
        }
2630 3
    }
2631
2632
    /**
2633
     * Read STYLE record.
2634
     */
2635 21
    private function readStyle()
2636
    {
2637 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
2638 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2639
2640
        // move stream pointer to next record
2641 21
        $this->pos += 4 + $length;
2642
2643 21
        if (!$this->readDataOnly) {
2644
            // offset: 0; size: 2; index to XF record and flag for built-in style
2645 20
            $ixfe = self::getUInt2d($recordData, 0);
2646
2647
            // bit: 11-0; mask 0x0FFF; index to XF record
2648 20
            $xfIndex = (0x0FFF & $ixfe) >> 0;
2649
2650
            // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style
2651 20
            $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15);
2652
2653 20
            if ($isBuiltIn) {
2654
                // offset: 2; size: 1; identifier for built-in style
2655 20
                $builtInId = ord($recordData[2]);
2656
2657
                switch ($builtInId) {
2658 20
                    case 0x00:
2659
                        // currently, we are not using this for anything
2660 20
                        break;
2661
                    default:
2662 14
                        break;
2663
                }
2664
            }
2665
            // user-defined; not supported by PhpSpreadsheet
2666
        }
2667 21
    }
2668
2669
    /**
2670
     * Read PALETTE record.
2671
     */
2672 7
    private function readPalette()
2673
    {
2674 7
        $length = self::getUInt2d($this->data, $this->pos + 2);
2675 7
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2676
2677
        // move stream pointer to next record
2678 7
        $this->pos += 4 + $length;
2679
2680 7
        if (!$this->readDataOnly) {
2681
            // offset: 0; size: 2; number of following colors
2682 7
            $nm = self::getUInt2d($recordData, 0);
2683
2684
            // list of RGB colors
2685 7
            for ($i = 0; $i < $nm; ++$i) {
2686 7
                $rgb = substr($recordData, 2 + 4 * $i, 4);
2687 7
                $this->palette[] = self::readRGB($rgb);
2688
            }
2689
        }
2690 7
    }
2691
2692
    /**
2693
     * SHEET.
2694
     *
2695
     * This record is  located in the  Workbook Globals
2696
     * Substream  and represents a sheet inside the workbook.
2697
     * One SHEET record is written for each sheet. It stores the
2698
     * sheet name and a stream offset to the BOF record of the
2699
     * respective Sheet Substream within the Workbook Stream.
2700
     *
2701
     * --    "OpenOffice.org's Documentation of the Microsoft
2702
     *         Excel File Format"
2703
     */
2704 24
    private function readSheet()
2705
    {
2706 24
        $length = self::getUInt2d($this->data, $this->pos + 2);
2707 24
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2708
2709
        // offset: 0; size: 4; absolute stream position of the BOF record of the sheet
2710
        // NOTE: not encrypted
2711 24
        $rec_offset = self::getInt4d($this->data, $this->pos + 4);
2712
2713
        // move stream pointer to next record
2714 24
        $this->pos += 4 + $length;
2715
2716
        // offset: 4; size: 1; sheet state
2717 24
        switch (ord($recordData[4])) {
2718 24
            case 0x00:
2719 24
                $sheetState = Worksheet::SHEETSTATE_VISIBLE;
2720
2721 24
                break;
2722
            case 0x01:
2723
                $sheetState = Worksheet::SHEETSTATE_HIDDEN;
2724
2725
                break;
2726
            case 0x02:
2727
                $sheetState = Worksheet::SHEETSTATE_VERYHIDDEN;
2728
2729
                break;
2730
            default:
2731
                $sheetState = Worksheet::SHEETSTATE_VISIBLE;
2732
2733
                break;
2734
        }
2735
2736
        // offset: 5; size: 1; sheet type
2737 24
        $sheetType = ord($recordData[5]);
2738
2739
        // offset: 6; size: var; sheet name
2740 24
        if ($this->version == self::XLS_BIFF8) {
2741 24
            $string = self::readUnicodeStringShort(substr($recordData, 6));
2742 24
            $rec_name = $string['value'];
2743
        } elseif ($this->version == self::XLS_BIFF7) {
2744
            $string = $this->readByteStringShort(substr($recordData, 6));
2745
            $rec_name = $string['value'];
2746
        }
2747
2748 24
        $this->sheets[] = [
2749 24
            'name' => $rec_name,
2750 24
            'offset' => $rec_offset,
2751 24
            'sheetState' => $sheetState,
2752 24
            'sheetType' => $sheetType,
2753
        ];
2754 24
    }
2755
2756
    /**
2757
     * Read EXTERNALBOOK record.
2758
     */
2759 7
    private function readExternalBook()
2760
    {
2761 7
        $length = self::getUInt2d($this->data, $this->pos + 2);
2762 7
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2763
2764
        // move stream pointer to next record
2765 7
        $this->pos += 4 + $length;
2766
2767
        // offset within record data
2768 7
        $offset = 0;
2769
2770
        // there are 4 types of records
2771 7
        if (strlen($recordData) > 4) {
2772
            // external reference
2773
            // offset: 0; size: 2; number of sheet names ($nm)
2774
            $nm = self::getUInt2d($recordData, 0);
2775
            $offset += 2;
2776
2777
            // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length)
2778
            $encodedUrlString = self::readUnicodeStringLong(substr($recordData, 2));
2779
            $offset += $encodedUrlString['size'];
2780
2781
            // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length)
2782
            $externalSheetNames = [];
2783
            for ($i = 0; $i < $nm; ++$i) {
2784
                $externalSheetNameString = self::readUnicodeStringLong(substr($recordData, $offset));
2785
                $externalSheetNames[] = $externalSheetNameString['value'];
2786
                $offset += $externalSheetNameString['size'];
2787
            }
2788
2789
            // store the record data
2790
            $this->externalBooks[] = [
2791
                'type' => 'external',
2792
                'encodedUrl' => $encodedUrlString['value'],
2793
                'externalSheetNames' => $externalSheetNames,
2794
            ];
2795 7
        } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) {
2796
            // internal reference
2797
            // offset: 0; size: 2; number of sheet in this document
2798
            // offset: 2; size: 2; 0x01 0x04
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2799 7
            $this->externalBooks[] = [
2800 7
                'type' => 'internal',
2801
            ];
2802
        } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) {
2803
            // add-in function
2804
            // offset: 0; size: 2; 0x0001
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2805
            $this->externalBooks[] = [
2806
                'type' => 'addInFunction',
2807
            ];
2808
        } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) {
2809
            // DDE links, OLE links
2810
            // offset: 0; size: 2; 0x0000
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2811
            // offset: 2; size: var; encoded source document name
2812
            $this->externalBooks[] = [
2813
                'type' => 'DDEorOLE',
2814
            ];
2815
        }
2816 7
    }
2817
2818
    /**
2819
     * Read EXTERNNAME record.
2820
     */
2821
    private function readExternName()
2822
    {
2823
        $length = self::getUInt2d($this->data, $this->pos + 2);
2824
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2825
2826
        // move stream pointer to next record
2827
        $this->pos += 4 + $length;
2828
2829
        // external sheet references provided for named cells
2830
        if ($this->version == self::XLS_BIFF8) {
2831
            // offset: 0; size: 2; options
2832
            $options = self::getUInt2d($recordData, 0);
2833
2834
            // offset: 2; size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2835
2836
            // offset: 4; size: 2; not used
2837
2838
            // offset: 6; size: var
2839
            $nameString = self::readUnicodeStringShort(substr($recordData, 6));
2840
2841
            // offset: var; size: var; formula data
2842
            $offset = 6 + $nameString['size'];
2843
            $formula = $this->getFormulaFromStructure(substr($recordData, $offset));
2844
2845
            $this->externalNames[] = [
2846
                'name' => $nameString['value'],
2847
                'formula' => $formula,
2848
            ];
2849
        }
2850
    }
2851
2852
    /**
2853
     * Read EXTERNSHEET record.
2854
     */
2855 7
    private function readExternSheet()
2856
    {
2857 7
        $length = self::getUInt2d($this->data, $this->pos + 2);
2858 7
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2859
2860
        // move stream pointer to next record
2861 7
        $this->pos += 4 + $length;
2862
2863
        // external sheet references provided for named cells
2864 7
        if ($this->version == self::XLS_BIFF8) {
2865
            // offset: 0; size: 2; number of following ref structures
2866 7
            $nm = self::getUInt2d($recordData, 0);
2867 7
            for ($i = 0; $i < $nm; ++$i) {
2868 7
                $this->ref[] = [
2869
                    // offset: 2 + 6 * $i; index to EXTERNALBOOK record
2870 7
                    'externalBookIndex' => self::getUInt2d($recordData, 2 + 6 * $i),
2871
                    // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record
2872 7
                    'firstSheetIndex' => self::getUInt2d($recordData, 4 + 6 * $i),
2873
                    // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record
2874 7
                    'lastSheetIndex' => self::getUInt2d($recordData, 6 + 6 * $i),
2875
                ];
2876
            }
2877
        }
2878 7
    }
2879
2880
    /**
2881
     * DEFINEDNAME.
2882
     *
2883
     * This record is part of a Link Table. It contains the name
2884
     * and the token array of an internal defined name. Token
2885
     * arrays of defined names contain tokens with aberrant
2886
     * token classes.
2887
     *
2888
     * --    "OpenOffice.org's Documentation of the Microsoft
2889
     *         Excel File Format"
2890
     */
2891 1
    private function readDefinedName()
2892
    {
2893 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
2894 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2895
2896
        // move stream pointer to next record
2897 1
        $this->pos += 4 + $length;
2898
2899 1
        if ($this->version == self::XLS_BIFF8) {
2900
            // retrieves named cells
2901
2902
            // offset: 0; size: 2; option flags
2903 1
            $opts = self::getUInt2d($recordData, 0);
2904
2905
            // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name
2906 1
            $isBuiltInName = (0x0020 & $opts) >> 5;
2907
2908
            // offset: 2; size: 1; keyboard shortcut
2909
2910
            // offset: 3; size: 1; length of the name (character count)
2911 1
            $nlen = ord($recordData[3]);
2912
2913
            // offset: 4; size: 2; size of the formula data (it can happen that this is zero)
2914
            // note: there can also be additional data, this is not included in $flen
2915 1
            $flen = self::getUInt2d($recordData, 4);
2916
2917
            // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based)
2918 1
            $scope = self::getUInt2d($recordData, 8);
2919
2920
            // offset: 14; size: var; Name (Unicode string without length field)
2921 1
            $string = self::readUnicodeString(substr($recordData, 14), $nlen);
2922
2923
            // offset: var; size: $flen; formula data
2924 1
            $offset = 14 + $string['size'];
2925 1
            $formulaStructure = pack('v', $flen) . substr($recordData, $offset);
2926
2927
            try {
2928 1
                $formula = $this->getFormulaFromStructure($formulaStructure);
2929
            } catch (PhpSpreadsheetException $e) {
2930
                $formula = '';
2931
            }
2932
2933 1
            $this->definedname[] = [
2934 1
                'isBuiltInName' => $isBuiltInName,
2935 1
                'name' => $string['value'],
2936 1
                'formula' => $formula,
2937 1
                'scope' => $scope,
2938
            ];
2939
        }
2940 1
    }
2941
2942
    /**
2943
     * Read MSODRAWINGGROUP record.
2944
     */
2945 3
    private function readMsoDrawingGroup()
2946
    {
2947 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
2948
2949
        // get spliced record data
2950 3
        $splicedRecordData = $this->getSplicedRecordData();
2951 3
        $recordData = $splicedRecordData['recordData'];
2952
2953 3
        $this->drawingGroupData .= $recordData;
2954 3
    }
2955
2956
    /**
2957
     * SST - Shared String Table.
2958
     *
2959
     * This record contains a list of all strings used anywhere
2960
     * in the workbook. Each string occurs only once. The
2961
     * workbook uses indexes into the list to reference the
2962
     * strings.
2963
     *
2964
     * --    "OpenOffice.org's Documentation of the Microsoft
2965
     *         Excel File Format"
2966
     */
2967 21
    private function readSst()
2968
    {
2969
        // offset within (spliced) record data
2970 21
        $pos = 0;
2971
2972
        // get spliced record data
2973 21
        $splicedRecordData = $this->getSplicedRecordData();
2974
2975 21
        $recordData = $splicedRecordData['recordData'];
2976 21
        $spliceOffsets = $splicedRecordData['spliceOffsets'];
2977
2978
        // offset: 0; size: 4; total number of strings in the workbook
2979 21
        $pos += 4;
2980
2981
        // offset: 4; size: 4; number of following strings ($nm)
2982 21
        $nm = self::getInt4d($recordData, 4);
2983 21
        $pos += 4;
2984
2985
        // loop through the Unicode strings (16-bit length)
2986 21
        for ($i = 0; $i < $nm; ++$i) {
2987
            // number of characters in the Unicode string
2988 19
            $numChars = self::getUInt2d($recordData, $pos);
2989 19
            $pos += 2;
2990
2991
            // option flags
2992 19
            $optionFlags = ord($recordData[$pos]);
2993 19
            ++$pos;
2994
2995
            // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed
2996 19
            $isCompressed = (($optionFlags & 0x01) == 0);
2997
2998
            // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic
2999 19
            $hasAsian = (($optionFlags & 0x04) != 0);
3000
3001
            // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text
3002 19
            $hasRichText = (($optionFlags & 0x08) != 0);
3003
3004 19
            if ($hasRichText) {
3005
                // number of Rich-Text formatting runs
3006 2
                $formattingRuns = self::getUInt2d($recordData, $pos);
3007 2
                $pos += 2;
3008
            }
3009
3010 19
            if ($hasAsian) {
3011
                // size of Asian phonetic setting
3012
                $extendedRunLength = self::getInt4d($recordData, $pos);
3013
                $pos += 4;
3014
            }
3015
3016
            // expected byte length of character array if not split
3017 19
            $len = ($isCompressed) ? $numChars : $numChars * 2;
3018
3019
            // look up limit position
3020 19
            foreach ($spliceOffsets as $spliceOffset) {
3021
                // it can happen that the string is empty, therefore we need
3022
                // <= and not just <
3023 19
                if ($pos <= $spliceOffset) {
3024 19
                    $limitpos = $spliceOffset;
3025
3026 19
                    break;
3027
                }
3028
            }
3029
3030 19
            if ($pos + $len <= $limitpos) {
3031
                // character array is not split between records
3032
3033 19
                $retstr = substr($recordData, $pos, $len);
3034 19
                $pos += $len;
3035
            } else {
3036
                // character array is split between records
3037
3038
                // first part of character array
3039
                $retstr = substr($recordData, $pos, $limitpos - $pos);
3040
3041
                $bytesRead = $limitpos - $pos;
3042
3043
                // remaining characters in Unicode string
3044
                $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));
3045
3046
                $pos = $limitpos;
3047
3048
                // keep reading the characters
3049
                while ($charsLeft > 0) {
3050
                    // look up next limit position, in case the string span more than one continue record
3051
                    foreach ($spliceOffsets as $spliceOffset) {
3052
                        if ($pos < $spliceOffset) {
3053
                            $limitpos = $spliceOffset;
3054
3055
                            break;
3056
                        }
3057
                    }
3058
3059
                    // repeated option flags
3060
                    // OpenOffice.org documentation 5.21
3061
                    $option = ord($recordData[$pos]);
3062
                    ++$pos;
3063
3064
                    if ($isCompressed && ($option == 0)) {
3065
                        // 1st fragment compressed
3066
                        // this fragment compressed
3067
                        $len = min($charsLeft, $limitpos - $pos);
3068
                        $retstr .= substr($recordData, $pos, $len);
3069
                        $charsLeft -= $len;
3070
                        $isCompressed = true;
3071
                    } elseif (!$isCompressed && ($option != 0)) {
3072
                        // 1st fragment uncompressed
3073
                        // this fragment uncompressed
3074
                        $len = min($charsLeft * 2, $limitpos - $pos);
3075
                        $retstr .= substr($recordData, $pos, $len);
3076
                        $charsLeft -= $len / 2;
3077
                        $isCompressed = false;
3078
                    } elseif (!$isCompressed && ($option == 0)) {
3079
                        // 1st fragment uncompressed
3080
                        // this fragment compressed
3081
                        $len = min($charsLeft, $limitpos - $pos);
3082
                        for ($j = 0; $j < $len; ++$j) {
3083
                            $retstr .= $recordData[$pos + $j]
3084
                            . chr(0);
3085
                        }
3086
                        $charsLeft -= $len;
3087
                        $isCompressed = false;
3088
                    } else {
3089
                        // 1st fragment compressed
3090
                        // this fragment uncompressed
3091
                        $newstr = '';
3092
                        $jMax = strlen($retstr);
3093
                        for ($j = 0; $j < $jMax; ++$j) {
3094
                            $newstr .= $retstr[$j] . chr(0);
3095
                        }
3096
                        $retstr = $newstr;
3097
                        $len = min($charsLeft * 2, $limitpos - $pos);
3098
                        $retstr .= substr($recordData, $pos, $len);
3099
                        $charsLeft -= $len / 2;
3100
                        $isCompressed = false;
3101
                    }
3102
3103
                    $pos += $len;
3104
                }
3105
            }
3106
3107
            // convert to UTF-8
3108 19
            $retstr = self::encodeUTF16($retstr, $isCompressed);
3109
3110
            // read additional Rich-Text information, if any
3111 19
            $fmtRuns = [];
3112 19
            if ($hasRichText) {
3113
                // list of formatting runs
3114 2
                for ($j = 0; $j < $formattingRuns; ++$j) {
3115
                    // first formatted character; zero-based
3116 2
                    $charPos = self::getUInt2d($recordData, $pos + $j * 4);
3117
3118
                    // index to font record
3119 2
                    $fontIndex = self::getUInt2d($recordData, $pos + 2 + $j * 4);
3120
3121 2
                    $fmtRuns[] = [
3122 2
                        'charPos' => $charPos,
3123 2
                        'fontIndex' => $fontIndex,
3124
                    ];
3125
                }
3126 2
                $pos += 4 * $formattingRuns;
3127
            }
3128
3129
            // read additional Asian phonetics information, if any
3130 19
            if ($hasAsian) {
3131
                // For Asian phonetic settings, we skip the extended string data
3132
                $pos += $extendedRunLength;
3133
            }
3134
3135
            // store the shared sting
3136 19
            $this->sst[] = [
3137 19
                'value' => $retstr,
3138 19
                'fmtRuns' => $fmtRuns,
3139
            ];
3140
        }
3141
3142
        // getSplicedRecordData() takes care of moving current position in data stream
3143 21
    }
3144
3145
    /**
3146
     * Read PRINTGRIDLINES record.
3147
     */
3148 21
    private function readPrintGridlines()
3149
    {
3150 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
3151 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3152
3153
        // move stream pointer to next record
3154 21
        $this->pos += 4 + $length;
3155
3156 21
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3157
            // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines
3158 20
            $printGridlines = (bool) self::getUInt2d($recordData, 0);
3159 20
            $this->phpSheet->setPrintGridlines($printGridlines);
3160
        }
3161 21
    }
3162
3163
    /**
3164
     * Read DEFAULTROWHEIGHT record.
3165
     */
3166 17
    private function readDefaultRowHeight()
3167
    {
3168 17
        $length = self::getUInt2d($this->data, $this->pos + 2);
3169 17
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3170
3171
        // move stream pointer to next record
3172 17
        $this->pos += 4 + $length;
3173
3174
        // offset: 0; size: 2; option flags
3175
        // offset: 2; size: 2; default height for unused rows, (twips 1/20 point)
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3176 17
        $height = self::getUInt2d($recordData, 2);
3177 17
        $this->phpSheet->getDefaultRowDimension()->setRowHeight($height / 20);
3178 17
    }
3179
3180
    /**
3181
     * Read SHEETPR record.
3182
     */
3183 21
    private function readSheetPr()
3184
    {
3185 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
3186 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3187
3188
        // move stream pointer to next record
3189 21
        $this->pos += 4 + $length;
3190
3191
        // offset: 0; size: 2
3192
3193
        // bit: 6; mask: 0x0040; 0 = outline buttons above outline group
3194 21
        $isSummaryBelow = (0x0040 & self::getUInt2d($recordData, 0)) >> 6;
3195 21
        $this->phpSheet->setShowSummaryBelow($isSummaryBelow);
3196
3197
        // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group
3198 21
        $isSummaryRight = (0x0080 & self::getUInt2d($recordData, 0)) >> 7;
3199 21
        $this->phpSheet->setShowSummaryRight($isSummaryRight);
3200
3201
        // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages
3202
        // this corresponds to radio button setting in page setup dialog in Excel
3203 21
        $this->isFitToPages = (bool) ((0x0100 & self::getUInt2d($recordData, 0)) >> 8);
3204 21
    }
3205
3206
    /**
3207
     * Read HORIZONTALPAGEBREAKS record.
3208
     */
3209
    private function readHorizontalPageBreaks()
3210
    {
3211
        $length = self::getUInt2d($this->data, $this->pos + 2);
3212
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3213
3214
        // move stream pointer to next record
3215
        $this->pos += 4 + $length;
3216
3217
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3218
            // offset: 0; size: 2; number of the following row index structures
3219
            $nm = self::getUInt2d($recordData, 0);
3220
3221
            // offset: 2; size: 6 * $nm; list of $nm row index structures
3222
            for ($i = 0; $i < $nm; ++$i) {
3223
                $r = self::getUInt2d($recordData, 2 + 6 * $i);
3224
                $cf = self::getUInt2d($recordData, 2 + 6 * $i + 2);
3225
                $cl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
3226
3227
                // not sure why two column indexes are necessary?
3228
                $this->phpSheet->setBreakByColumnAndRow($cf + 1, $r, Worksheet::BREAK_ROW);
3229
            }
3230
        }
3231
    }
3232
3233
    /**
3234
     * Read VERTICALPAGEBREAKS record.
3235
     */
3236
    private function readVerticalPageBreaks()
3237
    {
3238
        $length = self::getUInt2d($this->data, $this->pos + 2);
3239
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3240
3241
        // move stream pointer to next record
3242
        $this->pos += 4 + $length;
3243
3244
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3245
            // offset: 0; size: 2; number of the following column index structures
3246
            $nm = self::getUInt2d($recordData, 0);
3247
3248
            // offset: 2; size: 6 * $nm; list of $nm row index structures
3249
            for ($i = 0; $i < $nm; ++$i) {
3250
                $c = self::getUInt2d($recordData, 2 + 6 * $i);
3251
                $rf = self::getUInt2d($recordData, 2 + 6 * $i + 2);
3252
                $rl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
3253
3254
                // not sure why two row indexes are necessary?
3255
                $this->phpSheet->setBreakByColumnAndRow($c + 1, $rf, Worksheet::BREAK_COLUMN);
3256
            }
3257
        }
3258
    }
3259
3260
    /**
3261
     * Read HEADER record.
3262
     */
3263 21
    private function readHeader()
3264
    {
3265 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
3266 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3267
3268
        // move stream pointer to next record
3269 21
        $this->pos += 4 + $length;
3270
3271 21
        if (!$this->readDataOnly) {
3272
            // offset: 0; size: var
3273
            // realized that $recordData can be empty even when record exists
3274 20
            if ($recordData) {
3275 6
                if ($this->version == self::XLS_BIFF8) {
3276 6
                    $string = self::readUnicodeStringLong($recordData);
3277
                } else {
3278
                    $string = $this->readByteStringShort($recordData);
3279
                }
3280
3281 6
                $this->phpSheet->getHeaderFooter()->setOddHeader($string['value']);
3282 6
                $this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']);
3283
            }
3284
        }
3285 21
    }
3286
3287
    /**
3288
     * Read FOOTER record.
3289
     */
3290 21
    private function readFooter()
3291
    {
3292 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
3293 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3294
3295
        // move stream pointer to next record
3296 21
        $this->pos += 4 + $length;
3297
3298 21
        if (!$this->readDataOnly) {
3299
            // offset: 0; size: var
3300
            // realized that $recordData can be empty even when record exists
3301 20
            if ($recordData) {
3302 6
                if ($this->version == self::XLS_BIFF8) {
3303 6
                    $string = self::readUnicodeStringLong($recordData);
3304
                } else {
3305
                    $string = $this->readByteStringShort($recordData);
3306
                }
3307 6
                $this->phpSheet->getHeaderFooter()->setOddFooter($string['value']);
3308 6
                $this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']);
3309
            }
3310
        }
3311 21
    }
3312
3313
    /**
3314
     * Read HCENTER record.
3315
     */
3316 21
    private function readHcenter()
3317
    {
3318 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
3319 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3320
3321
        // move stream pointer to next record
3322 21
        $this->pos += 4 + $length;
3323
3324 21
        if (!$this->readDataOnly) {
3325
            // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally
3326 20
            $isHorizontalCentered = (bool) self::getUInt2d($recordData, 0);
3327
3328 20
            $this->phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered);
3329
        }
3330 21
    }
3331
3332
    /**
3333
     * Read VCENTER record.
3334
     */
3335 21
    private function readVcenter()
3336
    {
3337 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
3338 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3339
3340
        // move stream pointer to next record
3341 21
        $this->pos += 4 + $length;
3342
3343 21
        if (!$this->readDataOnly) {
3344
            // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered
3345 20
            $isVerticalCentered = (bool) self::getUInt2d($recordData, 0);
3346
3347 20
            $this->phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered);
3348
        }
3349 21
    }
3350
3351
    /**
3352
     * Read LEFTMARGIN record.
3353
     */
3354 8
    private function readLeftMargin()
3355
    {
3356 8
        $length = self::getUInt2d($this->data, $this->pos + 2);
3357 8
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3358
3359
        // move stream pointer to next record
3360 8
        $this->pos += 4 + $length;
3361
3362 8
        if (!$this->readDataOnly) {
3363
            // offset: 0; size: 8
3364 8
            $this->phpSheet->getPageMargins()->setLeft(self::extractNumber($recordData));
3365
        }
3366 8
    }
3367
3368
    /**
3369
     * Read RIGHTMARGIN record.
3370
     */
3371 8
    private function readRightMargin()
3372
    {
3373 8
        $length = self::getUInt2d($this->data, $this->pos + 2);
3374 8
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3375
3376
        // move stream pointer to next record
3377 8
        $this->pos += 4 + $length;
3378
3379 8
        if (!$this->readDataOnly) {
3380
            // offset: 0; size: 8
3381 8
            $this->phpSheet->getPageMargins()->setRight(self::extractNumber($recordData));
3382
        }
3383 8
    }
3384
3385
    /**
3386
     * Read TOPMARGIN record.
3387
     */
3388 8
    private function readTopMargin()
3389
    {
3390 8
        $length = self::getUInt2d($this->data, $this->pos + 2);
3391 8
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3392
3393
        // move stream pointer to next record
3394 8
        $this->pos += 4 + $length;
3395
3396 8
        if (!$this->readDataOnly) {
3397
            // offset: 0; size: 8
3398 8
            $this->phpSheet->getPageMargins()->setTop(self::extractNumber($recordData));
3399
        }
3400 8
    }
3401
3402
    /**
3403
     * Read BOTTOMMARGIN record.
3404
     */
3405 8
    private function readBottomMargin()
3406
    {
3407 8
        $length = self::getUInt2d($this->data, $this->pos + 2);
3408 8
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3409
3410
        // move stream pointer to next record
3411 8
        $this->pos += 4 + $length;
3412
3413 8
        if (!$this->readDataOnly) {
3414
            // offset: 0; size: 8
3415 8
            $this->phpSheet->getPageMargins()->setBottom(self::extractNumber($recordData));
3416
        }
3417 8
    }
3418
3419
    /**
3420
     * Read PAGESETUP record.
3421
     */
3422 21
    private function readPageSetup()
3423
    {
3424 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
3425 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3426
3427
        // move stream pointer to next record
3428 21
        $this->pos += 4 + $length;
3429
3430 21
        if (!$this->readDataOnly) {
3431
            // offset: 0; size: 2; paper size
3432 20
            $paperSize = self::getUInt2d($recordData, 0);
3433
3434
            // offset: 2; size: 2; scaling factor
3435 20
            $scale = self::getUInt2d($recordData, 2);
3436
3437
            // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed
3438 20
            $fitToWidth = self::getUInt2d($recordData, 6);
3439
3440
            // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed
3441 20
            $fitToHeight = self::getUInt2d($recordData, 8);
3442
3443
            // offset: 10; size: 2; option flags
3444
3445
            // bit: 1; mask: 0x0002; 0=landscape, 1=portrait
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3446 20
            $isPortrait = (0x0002 & self::getUInt2d($recordData, 10)) >> 1;
3447
3448
            // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init
3449
            // when this bit is set, do not use flags for those properties
3450 20
            $isNotInit = (0x0004 & self::getUInt2d($recordData, 10)) >> 2;
3451
3452 20
            if (!$isNotInit) {
3453 19
                $this->phpSheet->getPageSetup()->setPaperSize($paperSize);
3454
                switch ($isPortrait) {
3455 19
                    case 0:
3456 2
                        $this->phpSheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE);
3457
3458 2
                        break;
3459 19
                    case 1:
3460 19
                        $this->phpSheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_PORTRAIT);
3461
3462 19
                        break;
3463
                }
3464
3465 19
                $this->phpSheet->getPageSetup()->setScale($scale, false);
3466 19
                $this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages);
3467 19
                $this->phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false);
3468 19
                $this->phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false);
3469
            }
3470
3471
            // offset: 16; size: 8; header margin (IEEE 754 floating-point value)
3472 20
            $marginHeader = self::extractNumber(substr($recordData, 16, 8));
3473 20
            $this->phpSheet->getPageMargins()->setHeader($marginHeader);
3474
3475
            // offset: 24; size: 8; footer margin (IEEE 754 floating-point value)
3476 20
            $marginFooter = self::extractNumber(substr($recordData, 24, 8));
3477 20
            $this->phpSheet->getPageMargins()->setFooter($marginFooter);
3478
        }
3479 21
    }
3480
3481
    /**
3482
     * PROTECT - Sheet protection (BIFF2 through BIFF8)
3483
     *   if this record is omitted, then it also means no sheet protection.
3484
     */
3485 1
    private function readProtect()
3486
    {
3487 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
3488 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3489
3490
        // move stream pointer to next record
3491 1
        $this->pos += 4 + $length;
3492
3493 1
        if ($this->readDataOnly) {
3494
            return;
3495
        }
3496
3497
        // offset: 0; size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3498
3499
        // bit 0, mask 0x01; 1 = sheet is protected
3500 1
        $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
3501 1
        $this->phpSheet->getProtection()->setSheet((bool) $bool);
3502 1
    }
3503
3504
    /**
3505
     * SCENPROTECT.
3506
     */
3507
    private function readScenProtect()
3508
    {
3509
        $length = self::getUInt2d($this->data, $this->pos + 2);
3510
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3511
3512
        // move stream pointer to next record
3513
        $this->pos += 4 + $length;
3514
3515
        if ($this->readDataOnly) {
3516
            return;
3517
        }
3518
3519
        // offset: 0; size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3520
3521
        // bit: 0, mask 0x01; 1 = scenarios are protected
3522
        $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
3523
3524
        $this->phpSheet->getProtection()->setScenarios((bool) $bool);
3525
    }
3526
3527
    /**
3528
     * OBJECTPROTECT.
3529
     */
3530
    private function readObjectProtect()
3531
    {
3532
        $length = self::getUInt2d($this->data, $this->pos + 2);
3533
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3534
3535
        // move stream pointer to next record
3536
        $this->pos += 4 + $length;
3537
3538
        if ($this->readDataOnly) {
3539
            return;
3540
        }
3541
3542
        // offset: 0; size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3543
3544
        // bit: 0, mask 0x01; 1 = objects are protected
3545
        $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
3546
3547
        $this->phpSheet->getProtection()->setObjects((bool) $bool);
3548
    }
3549
3550
    /**
3551
     * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8).
3552
     */
3553
    private function readPassword()
3554
    {
3555
        $length = self::getUInt2d($this->data, $this->pos + 2);
3556
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3557
3558
        // move stream pointer to next record
3559
        $this->pos += 4 + $length;
3560
3561
        if (!$this->readDataOnly) {
3562
            // offset: 0; size: 2; 16-bit hash value of password
3563
            $password = strtoupper(dechex(self::getUInt2d($recordData, 0))); // the hashed password
3564
            $this->phpSheet->getProtection()->setPassword($password, true);
3565
        }
3566
    }
3567
3568
    /**
3569
     * Read DEFCOLWIDTH record.
3570
     */
3571 21
    private function readDefColWidth()
3572
    {
3573 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
3574 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3575
3576
        // move stream pointer to next record
3577 21
        $this->pos += 4 + $length;
3578
3579
        // offset: 0; size: 2; default column width
3580 21
        $width = self::getUInt2d($recordData, 0);
3581 21
        if ($width != 8) {
3582 1
            $this->phpSheet->getDefaultColumnDimension()->setWidth($width);
3583
        }
3584 21
    }
3585
3586
    /**
3587
     * Read COLINFO record.
3588
     */
3589 17
    private function readColInfo()
3590
    {
3591 17
        $length = self::getUInt2d($this->data, $this->pos + 2);
3592 17
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3593
3594
        // move stream pointer to next record
3595 17
        $this->pos += 4 + $length;
3596
3597 17
        if (!$this->readDataOnly) {
3598
            // offset: 0; size: 2; index to first column in range
3599 16
            $firstColumnIndex = self::getUInt2d($recordData, 0);
3600
3601
            // offset: 2; size: 2; index to last column in range
3602 16
            $lastColumnIndex = self::getUInt2d($recordData, 2);
3603
3604
            // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character
3605 16
            $width = self::getUInt2d($recordData, 4);
3606
3607
            // offset: 6; size: 2; index to XF record for default column formatting
3608 16
            $xfIndex = self::getUInt2d($recordData, 6);
3609
3610
            // offset: 8; size: 2; option flags
3611
            // bit: 0; mask: 0x0001; 1= columns are hidden
3612 16
            $isHidden = (0x0001 & self::getUInt2d($recordData, 8)) >> 0;
3613
3614
            // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline)
3615 16
            $level = (0x0700 & self::getUInt2d($recordData, 8)) >> 8;
3616
3617
            // bit: 12; mask: 0x1000; 1 = collapsed
3618 16
            $isCollapsed = (0x1000 & self::getUInt2d($recordData, 8)) >> 12;
3619
3620
            // offset: 10; size: 2; not used
3621
3622 16
            for ($i = $firstColumnIndex + 1; $i <= $lastColumnIndex + 1; ++$i) {
3623 16
                if ($lastColumnIndex == 255 || $lastColumnIndex == 256) {
3624 1
                    $this->phpSheet->getDefaultColumnDimension()->setWidth($width / 256);
3625
3626 1
                    break;
3627
                }
3628 15
                $this->phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256);
3629 15
                $this->phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden);
3630 15
                $this->phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level);
3631 15
                $this->phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed);
3632 15
                $this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3633
            }
3634
        }
3635 17
    }
3636
3637
    /**
3638
     * ROW.
3639
     *
3640
     * This record contains the properties of a single row in a
3641
     * sheet. Rows and cells in a sheet are divided into blocks
3642
     * of 32 rows.
3643
     *
3644
     * --    "OpenOffice.org's Documentation of the Microsoft
3645
     *         Excel File Format"
3646
     */
3647 17
    private function readRow()
3648
    {
3649 17
        $length = self::getUInt2d($this->data, $this->pos + 2);
3650 17
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3651
3652
        // move stream pointer to next record
3653 17
        $this->pos += 4 + $length;
3654
3655 17
        if (!$this->readDataOnly) {
3656
            // offset: 0; size: 2; index of this row
3657 16
            $r = self::getUInt2d($recordData, 0);
3658
3659
            // offset: 2; size: 2; index to column of the first cell which is described by a cell record
3660
3661
            // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1
3662
3663
            // offset: 6; size: 2;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3664
3665
            // bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point
3666 16
            $height = (0x7FFF & self::getUInt2d($recordData, 6)) >> 0;
3667
3668
            // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height
3669 16
            $useDefaultHeight = (0x8000 & self::getUInt2d($recordData, 6)) >> 15;
3670
3671 16
            if (!$useDefaultHeight) {
3672 16
                $this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
3673
            }
3674
3675
            // offset: 8; size: 2; not used
3676
3677
            // offset: 10; size: 2; not used in BIFF5-BIFF8
3678
3679
            // offset: 12; size: 4; option flags and default row formatting
3680
3681
            // bit: 2-0: mask: 0x00000007; outline level of the row
3682 16
            $level = (0x00000007 & self::getInt4d($recordData, 12)) >> 0;
3683 16
            $this->phpSheet->getRowDimension($r + 1)->setOutlineLevel($level);
3684
3685
            // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed
3686 16
            $isCollapsed = (0x00000010 & self::getInt4d($recordData, 12)) >> 4;
3687 16
            $this->phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed);
3688
3689
            // bit: 5; mask: 0x00000020; 1 = row is hidden
3690 16
            $isHidden = (0x00000020 & self::getInt4d($recordData, 12)) >> 5;
3691 16
            $this->phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden);
3692
3693
            // bit: 7; mask: 0x00000080; 1 = row has explicit format
3694 16
            $hasExplicitFormat = (0x00000080 & self::getInt4d($recordData, 12)) >> 7;
3695
3696
            // bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record
3697 16
            $xfIndex = (0x0FFF0000 & self::getInt4d($recordData, 12)) >> 16;
3698
3699 16
            if ($hasExplicitFormat && isset($this->mapCellXfIndex[$xfIndex])) {
3700 2
                $this->phpSheet->getRowDimension($r + 1)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3701
            }
3702
        }
3703 17
    }
3704
3705
    /**
3706
     * Read RK record
3707
     * This record represents a cell that contains an RK value
3708
     * (encoded integer or floating-point value). If a
3709
     * floating-point value cannot be encoded to an RK value,
3710
     * a NUMBER record will be written. This record replaces the
3711
     * record INTEGER written in BIFF2.
3712
     *
3713
     * --    "OpenOffice.org's Documentation of the Microsoft
3714
     *         Excel File Format"
3715
     */
3716 12
    private function readRk()
3717
    {
3718 12
        $length = self::getUInt2d($this->data, $this->pos + 2);
3719 12
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3720
3721
        // move stream pointer to next record
3722 12
        $this->pos += 4 + $length;
3723
3724
        // offset: 0; size: 2; index to row
3725 12
        $row = self::getUInt2d($recordData, 0);
3726
3727
        // offset: 2; size: 2; index to column
3728 12
        $column = self::getUInt2d($recordData, 2);
3729 12
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
3730
3731
        // Read cell?
3732 12
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3733
            // offset: 4; size: 2; index to XF record
3734 12
            $xfIndex = self::getUInt2d($recordData, 4);
3735
3736
            // offset: 6; size: 4; RK value
3737 12
            $rknum = self::getInt4d($recordData, 6);
3738 12
            $numValue = self::getIEEE754($rknum);
3739
3740 12
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3741 12
            if (!$this->readDataOnly) {
3742
                // add style information
3743 11
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3744
            }
3745
3746
            // add cell
3747 12
            $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
3748
        }
3749 12
    }
3750
3751
    /**
3752
     * Read LABELSST record
3753
     * This record represents a cell that contains a string. It
3754
     * replaces the LABEL record and RSTRING record used in
3755
     * BIFF2-BIFF5.
3756
     *
3757
     * --    "OpenOffice.org's Documentation of the Microsoft
3758
     *         Excel File Format"
3759
     */
3760 18
    private function readLabelSst()
3761
    {
3762 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3763 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3764
3765
        // move stream pointer to next record
3766 18
        $this->pos += 4 + $length;
3767
3768
        // offset: 0; size: 2; index to row
3769 18
        $row = self::getUInt2d($recordData, 0);
3770
3771
        // offset: 2; size: 2; index to column
3772 18
        $column = self::getUInt2d($recordData, 2);
3773 18
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
3774
3775 18
        $emptyCell = true;
3776
        // Read cell?
3777 18
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3778
            // offset: 4; size: 2; index to XF record
3779 18
            $xfIndex = self::getUInt2d($recordData, 4);
3780
3781
            // offset: 6; size: 4; index to SST record
3782 18
            $index = self::getInt4d($recordData, 6);
3783
3784
            // add cell
3785 18
            if (($fmtRuns = $this->sst[$index]['fmtRuns']) && !$this->readDataOnly) {
3786
                // then we should treat as rich text
3787 2
                $richText = new RichText();
3788 2
                $charPos = 0;
3789 2
                $sstCount = count($this->sst[$index]['fmtRuns']);
3790 2
                for ($i = 0; $i <= $sstCount; ++$i) {
3791 2
                    if (isset($fmtRuns[$i])) {
3792 2
                        $text = StringHelper::substring($this->sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos);
3793 2
                        $charPos = $fmtRuns[$i]['charPos'];
3794
                    } else {
3795 2
                        $text = StringHelper::substring($this->sst[$index]['value'], $charPos, StringHelper::countCharacters($this->sst[$index]['value']));
3796
                    }
3797
3798 2
                    if (StringHelper::countCharacters($text) > 0) {
3799 2
                        if ($i == 0) { // first text run, no style
3800 1
                            $richText->createText($text);
3801
                        } else {
3802 2
                            $textRun = $richText->createTextRun($text);
3803 2
                            if (isset($fmtRuns[$i - 1])) {
3804 2
                                if ($fmtRuns[$i - 1]['fontIndex'] < 4) {
3805 2
                                    $fontIndex = $fmtRuns[$i - 1]['fontIndex'];
3806
                                } else {
3807
                                    // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
3808
                                    // check the OpenOffice documentation of the FONT record
3809 2
                                    $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1;
3810
                                }
3811 2
                                $textRun->setFont(clone $this->objFonts[$fontIndex]);
3812
                            }
3813
                        }
3814
                    }
3815
                }
3816 2
                if ($this->readEmptyCells || trim($richText->getPlainText()) !== '') {
3817 2
                    $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3818 2
                    $cell->setValueExplicit($richText, DataType::TYPE_STRING);
3819 2
                    $emptyCell = false;
3820
                }
3821
            } else {
3822 18
                if ($this->readEmptyCells || trim($this->sst[$index]['value']) !== '') {
3823 18
                    $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3824 18
                    $cell->setValueExplicit($this->sst[$index]['value'], DataType::TYPE_STRING);
3825 18
                    $emptyCell = false;
3826
                }
3827
            }
3828
3829 18
            if (!$this->readDataOnly && !$emptyCell) {
3830
                // add style information
3831 17
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3832
            }
3833
        }
3834 18
    }
3835
3836
    /**
3837
     * Read MULRK record
3838
     * This record represents a cell range containing RK value
3839
     * cells. All cells are located in the same row.
3840
     *
3841
     * --    "OpenOffice.org's Documentation of the Microsoft
3842
     *         Excel File Format"
3843
     */
3844 11
    private function readMulRk()
3845
    {
3846 11
        $length = self::getUInt2d($this->data, $this->pos + 2);
3847 11
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3848
3849
        // move stream pointer to next record
3850 11
        $this->pos += 4 + $length;
3851
3852
        // offset: 0; size: 2; index to row
3853 11
        $row = self::getUInt2d($recordData, 0);
3854
3855
        // offset: 2; size: 2; index to first column
3856 11
        $colFirst = self::getUInt2d($recordData, 2);
3857
3858
        // offset: var; size: 2; index to last column
3859 11
        $colLast = self::getUInt2d($recordData, $length - 2);
3860 11
        $columns = $colLast - $colFirst + 1;
3861
3862
        // offset within record data
3863 11
        $offset = 4;
3864
3865 11
        for ($i = 1; $i <= $columns; ++$i) {
3866 11
            $columnString = Coordinate::stringFromColumnIndex($colFirst + $i);
3867
3868
            // Read cell?
3869 11
            if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3870
                // offset: var; size: 2; index to XF record
3871 11
                $xfIndex = self::getUInt2d($recordData, $offset);
3872
3873
                // offset: var; size: 4; RK value
3874 11
                $numValue = self::getIEEE754(self::getInt4d($recordData, $offset + 2));
3875 11
                $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3876 11
                if (!$this->readDataOnly) {
3877
                    // add style
3878 10
                    $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3879
                }
3880
3881
                // add cell value
3882 11
                $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
3883
            }
3884
3885 11
            $offset += 6;
3886
        }
3887 11
    }
3888
3889
    /**
3890
     * Read NUMBER record
3891
     * This record represents a cell that contains a
3892
     * floating-point value.
3893
     *
3894
     * --    "OpenOffice.org's Documentation of the Microsoft
3895
     *         Excel File Format"
3896
     */
3897 12
    private function readNumber()
3898
    {
3899 12
        $length = self::getUInt2d($this->data, $this->pos + 2);
3900 12
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3901
3902
        // move stream pointer to next record
3903 12
        $this->pos += 4 + $length;
3904
3905
        // offset: 0; size: 2; index to row
3906 12
        $row = self::getUInt2d($recordData, 0);
3907
3908
        // offset: 2; size 2; index to column
3909 12
        $column = self::getUInt2d($recordData, 2);
3910 12
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
3911
3912
        // Read cell?
3913 12
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3914
            // offset 4; size: 2; index to XF record
3915 12
            $xfIndex = self::getUInt2d($recordData, 4);
3916
3917 12
            $numValue = self::extractNumber(substr($recordData, 6, 8));
3918
3919 12
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3920 12
            if (!$this->readDataOnly) {
3921
                // add cell style
3922 11
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3923
            }
3924
3925
            // add cell value
3926 12
            $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
3927
        }
3928 12
    }
3929
3930
    /**
3931
     * Read FORMULA record + perhaps a following STRING record if formula result is a string
3932
     * This record contains the token array and the result of a
3933
     * formula cell.
3934
     *
3935
     * --    "OpenOffice.org's Documentation of the Microsoft
3936
     *         Excel File Format"
3937
     */
3938 10
    private function readFormula()
3939
    {
3940 10
        $length = self::getUInt2d($this->data, $this->pos + 2);
3941 10
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3942
3943
        // move stream pointer to next record
3944 10
        $this->pos += 4 + $length;
3945
3946
        // offset: 0; size: 2; row index
3947 10
        $row = self::getUInt2d($recordData, 0);
3948
3949
        // offset: 2; size: 2; col index
3950 10
        $column = self::getUInt2d($recordData, 2);
3951 10
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
3952
3953
        // offset: 20: size: variable; formula structure
3954 10
        $formulaStructure = substr($recordData, 20);
3955
3956
        // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc.
3957 10
        $options = self::getUInt2d($recordData, 14);
3958
3959
        // bit: 0; mask: 0x0001; 1 = recalculate always
3960
        // bit: 1; mask: 0x0002; 1 = calculate on open
3961
        // bit: 2; mask: 0x0008; 1 = part of a shared formula
3962 10
        $isPartOfSharedFormula = (bool) (0x0008 & $options);
3963
3964
        // WARNING:
3965
        // We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true
3966
        // the formula data may be ordinary formula data, therefore we need to check
3967
        // explicitly for the tExp token (0x01)
3968 10
        $isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure[2]) == 0x01;
3969
3970 10
        if ($isPartOfSharedFormula) {
3971
            // part of shared formula which means there will be a formula with a tExp token and nothing else
3972
            // get the base cell, grab tExp token
3973
            $baseRow = self::getUInt2d($formulaStructure, 3);
3974
            $baseCol = self::getUInt2d($formulaStructure, 5);
3975
            $this->baseCell = Coordinate::stringFromColumnIndex($baseCol + 1) . ($baseRow + 1);
3976
        }
3977
3978
        // Read cell?
3979 10
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3980 10
            if ($isPartOfSharedFormula) {
3981
                // formula is added to this cell after the sheet has been read
3982
                $this->sharedFormulaParts[$columnString . ($row + 1)] = $this->baseCell;
3983
            }
3984
3985
            // offset: 16: size: 4; not used
3986
3987
            // offset: 4; size: 2; XF index
3988 10
            $xfIndex = self::getUInt2d($recordData, 4);
3989
3990
            // offset: 6; size: 8; result of the formula
3991 10
            if ((ord($recordData[6]) == 0) && (ord($recordData[12]) == 255) && (ord($recordData[13]) == 255)) {
3992
                // String formula. Result follows in appended STRING record
3993
                $dataType = DataType::TYPE_STRING;
3994
3995
                // read possible SHAREDFMLA record
3996
                $code = self::getUInt2d($this->data, $this->pos);
3997
                if ($code == self::XLS_TYPE_SHAREDFMLA) {
3998
                    $this->readSharedFmla();
3999
                }
4000
4001
                // read STRING record
4002
                $value = $this->readString();
4003 10
            } elseif ((ord($recordData[6]) == 1)
4004 10
                && (ord($recordData[12]) == 255)
4005 10
                && (ord($recordData[13]) == 255)) {
4006
                // Boolean formula. Result is in +2; 0=false, 1=true
4007
                $dataType = DataType::TYPE_BOOL;
4008
                $value = (bool) ord($recordData[8]);
4009 10
            } elseif ((ord($recordData[6]) == 2)
4010 10
                && (ord($recordData[12]) == 255)
4011 10
                && (ord($recordData[13]) == 255)) {
4012
                // Error formula. Error code is in +2
4013 8
                $dataType = DataType::TYPE_ERROR;
4014 8
                $value = Xls\ErrorCode::lookup(ord($recordData[8]));
4015 10
            } elseif ((ord($recordData[6]) == 3)
4016 10
                && (ord($recordData[12]) == 255)
4017 10
                && (ord($recordData[13]) == 255)) {
4018
                // Formula result is a null string
4019 1
                $dataType = DataType::TYPE_NULL;
4020 1
                $value = '';
4021
            } else {
4022
                // forumla result is a number, first 14 bytes like _NUMBER record
4023 10
                $dataType = DataType::TYPE_NUMERIC;
4024 10
                $value = self::extractNumber(substr($recordData, 6, 8));
4025
            }
4026
4027 10
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4028 10
            if (!$this->readDataOnly) {
4029
                // add cell style
4030 9
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4031
            }
4032
4033
            // store the formula
4034 10
            if (!$isPartOfSharedFormula) {
4035
                // not part of shared formula
4036
                // add cell value. If we can read formula, populate with formula, otherwise just used cached value
4037
                try {
4038 10
                    if ($this->version != self::XLS_BIFF8) {
4039
                        throw new Exception('Not BIFF8. Can only read BIFF8 formulas');
4040
                    }
4041 10
                    $formula = $this->getFormulaFromStructure($formulaStructure); // get formula in human language
4042 10
                    $cell->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA);
4043
                } catch (PhpSpreadsheetException $e) {
4044 10
                    $cell->setValueExplicit($value, $dataType);
4045
                }
4046
            } else {
4047
                if ($this->version == self::XLS_BIFF8) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
4048
                    // do nothing at this point, formula id added later in the code
4049
                } else {
4050
                    $cell->setValueExplicit($value, $dataType);
4051
                }
4052
            }
4053
4054
            // store the cached calculated value
4055 10
            $cell->setCalculatedValue($value);
4056
        }
4057 10
    }
4058
4059
    /**
4060
     * Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader,
4061
     * which usually contains relative references.
4062
     * These will be used to construct the formula in each shared formula part after the sheet is read.
4063
     */
4064
    private function readSharedFmla()
4065
    {
4066
        $length = self::getUInt2d($this->data, $this->pos + 2);
4067
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4068
4069
        // move stream pointer to next record
4070
        $this->pos += 4 + $length;
4071
4072
        // offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything
4073
        $cellRange = substr($recordData, 0, 6);
4074
        $cellRange = $this->readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax
4075
4076
        // offset: 6, size: 1; not used
4077
4078
        // offset: 7, size: 1; number of existing FORMULA records for this shared formula
4079
        $no = ord($recordData[7]);
4080
4081
        // offset: 8, size: var; Binary token array of the shared formula
4082
        $formula = substr($recordData, 8);
4083
4084
        // at this point we only store the shared formula for later use
4085
        $this->sharedFormulas[$this->baseCell] = $formula;
4086
    }
4087
4088
    /**
4089
     * Read a STRING record from current stream position and advance the stream pointer to next record
4090
     * This record is used for storing result from FORMULA record when it is a string, and
4091
     * it occurs directly after the FORMULA record.
4092
     *
4093
     * @return string The string contents as UTF-8
4094
     */
4095
    private function readString()
4096
    {
4097
        $length = self::getUInt2d($this->data, $this->pos + 2);
4098
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4099
4100
        // move stream pointer to next record
4101
        $this->pos += 4 + $length;
4102
4103
        if ($this->version == self::XLS_BIFF8) {
4104
            $string = self::readUnicodeStringLong($recordData);
4105
            $value = $string['value'];
4106
        } else {
4107
            $string = $this->readByteStringLong($recordData);
4108
            $value = $string['value'];
4109
        }
4110
4111
        return $value;
4112
    }
4113
4114
    /**
4115
     * Read BOOLERR record
4116
     * This record represents a Boolean value or error value
4117
     * cell.
4118
     *
4119
     * --    "OpenOffice.org's Documentation of the Microsoft
4120
     *         Excel File Format"
4121
     */
4122 8
    private function readBoolErr()
4123
    {
4124 8
        $length = self::getUInt2d($this->data, $this->pos + 2);
4125 8
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4126
4127
        // move stream pointer to next record
4128 8
        $this->pos += 4 + $length;
4129
4130
        // offset: 0; size: 2; row index
4131 8
        $row = self::getUInt2d($recordData, 0);
4132
4133
        // offset: 2; size: 2; column index
4134 8
        $column = self::getUInt2d($recordData, 2);
4135 8
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
4136
4137
        // Read cell?
4138 8
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4139
            // offset: 4; size: 2; index to XF record
4140 8
            $xfIndex = self::getUInt2d($recordData, 4);
4141
4142
            // offset: 6; size: 1; the boolean value or error value
4143 8
            $boolErr = ord($recordData[6]);
4144
4145
            // offset: 7; size: 1; 0=boolean; 1=error
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
4146 8
            $isError = ord($recordData[7]);
4147
4148 8
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4149
            switch ($isError) {
4150 8
                case 0: // boolean
4151 8
                    $value = (bool) $boolErr;
4152
4153
                    // add cell value
4154 8
                    $cell->setValueExplicit($value, DataType::TYPE_BOOL);
4155
4156 8
                    break;
4157
                case 1: // error type
4158
                    $value = Xls\ErrorCode::lookup($boolErr);
4159
4160
                    // add cell value
4161
                    $cell->setValueExplicit($value, DataType::TYPE_ERROR);
4162
4163
                    break;
4164
            }
4165
4166 8
            if (!$this->readDataOnly) {
4167
                // add cell style
4168 7
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4169
            }
4170
        }
4171 8
    }
4172
4173
    /**
4174
     * Read MULBLANK record
4175
     * This record represents a cell range of empty cells. All
4176
     * cells are located in the same row.
4177
     *
4178
     * --    "OpenOffice.org's Documentation of the Microsoft
4179
     *         Excel File Format"
4180
     */
4181 11
    private function readMulBlank()
4182
    {
4183 11
        $length = self::getUInt2d($this->data, $this->pos + 2);
4184 11
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4185
4186
        // move stream pointer to next record
4187 11
        $this->pos += 4 + $length;
4188
4189
        // offset: 0; size: 2; index to row
4190 11
        $row = self::getUInt2d($recordData, 0);
4191
4192
        // offset: 2; size: 2; index to first column
4193 11
        $fc = self::getUInt2d($recordData, 2);
4194
4195
        // offset: 4; size: 2 x nc; list of indexes to XF records
4196
        // add style information
4197 11
        if (!$this->readDataOnly && $this->readEmptyCells) {
4198 10
            for ($i = 0; $i < $length / 2 - 3; ++$i) {
4199 10
                $columnString = Coordinate::stringFromColumnIndex($fc + $i + 1);
4200
4201
                // Read cell?
4202 10
                if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4203 10
                    $xfIndex = self::getUInt2d($recordData, 4 + 2 * $i);
4204 10
                    $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4205
                }
4206
            }
4207
        }
4208
4209
        // offset: 6; size 2; index to last column (not needed)
4210 11
    }
4211
4212
    /**
4213
     * Read LABEL record
4214
     * This record represents a cell that contains a string. In
4215
     * BIFF8 it is usually replaced by the LABELSST record.
4216
     * Excel still uses this record, if it copies unformatted
4217
     * text cells to the clipboard.
4218
     *
4219
     * --    "OpenOffice.org's Documentation of the Microsoft
4220
     *         Excel File Format"
4221
     */
4222
    private function readLabel()
4223
    {
4224
        $length = self::getUInt2d($this->data, $this->pos + 2);
4225
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4226
4227
        // move stream pointer to next record
4228
        $this->pos += 4 + $length;
4229
4230
        // offset: 0; size: 2; index to row
4231
        $row = self::getUInt2d($recordData, 0);
4232
4233
        // offset: 2; size: 2; index to column
4234
        $column = self::getUInt2d($recordData, 2);
4235
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
4236
4237
        // Read cell?
4238
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4239
            // offset: 4; size: 2; XF index
4240
            $xfIndex = self::getUInt2d($recordData, 4);
4241
4242
            // add cell value
4243
            // todo: what if string is very long? continue record
4244
            if ($this->version == self::XLS_BIFF8) {
4245
                $string = self::readUnicodeStringLong(substr($recordData, 6));
4246
                $value = $string['value'];
4247
            } else {
4248
                $string = $this->readByteStringLong(substr($recordData, 6));
4249
                $value = $string['value'];
4250
            }
4251
            if ($this->readEmptyCells || trim($value) !== '') {
4252
                $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4253
                $cell->setValueExplicit($value, DataType::TYPE_STRING);
4254
4255
                if (!$this->readDataOnly) {
4256
                    // add cell style
4257
                    $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4258
                }
4259
            }
4260
        }
4261
    }
4262
4263
    /**
4264
     * Read BLANK record.
4265
     */
4266 2
    private function readBlank()
4267
    {
4268 2
        $length = self::getUInt2d($this->data, $this->pos + 2);
4269 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4270
4271
        // move stream pointer to next record
4272 2
        $this->pos += 4 + $length;
4273
4274
        // offset: 0; size: 2; row index
4275 2
        $row = self::getUInt2d($recordData, 0);
4276
4277
        // offset: 2; size: 2; col index
4278 2
        $col = self::getUInt2d($recordData, 2);
4279 2
        $columnString = Coordinate::stringFromColumnIndex($col + 1);
4280
4281
        // Read cell?
4282 2
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4283
            // offset: 4; size: 2; XF index
4284 2
            $xfIndex = self::getUInt2d($recordData, 4);
4285
4286
            // add style information
4287 2
            if (!$this->readDataOnly && $this->readEmptyCells) {
4288 2
                $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4289
            }
4290
        }
4291 2
    }
4292
4293
    /**
4294
     * Read MSODRAWING record.
4295
     */
4296 3
    private function readMsoDrawing()
4297
    {
4298 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
4299
4300
        // get spliced record data
4301 3
        $splicedRecordData = $this->getSplicedRecordData();
4302 3
        $recordData = $splicedRecordData['recordData'];
4303
4304 3
        $this->drawingData .= $recordData;
4305 3
    }
4306
4307
    /**
4308
     * Read OBJ record.
4309
     */
4310 3
    private function readObj()
4311
    {
4312 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
4313 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4314
4315
        // move stream pointer to next record
4316 3
        $this->pos += 4 + $length;
4317
4318 3
        if ($this->readDataOnly || $this->version != self::XLS_BIFF8) {
4319
            return;
4320
        }
4321
4322
        // recordData consists of an array of subrecords looking like this:
4323
        //    ft: 2 bytes; ftCmo type (0x15)
4324
        //    cb: 2 bytes; size in bytes of ftCmo data
4325
        //    ot: 2 bytes; Object Type
4326
        //    id: 2 bytes; Object id number
4327
        //    grbit: 2 bytes; Option Flags
4328
        //    data: var; subrecord data
4329
4330
        // for now, we are just interested in the second subrecord containing the object type
4331 3
        $ftCmoType = self::getUInt2d($recordData, 0);
4332 3
        $cbCmoSize = self::getUInt2d($recordData, 2);
4333 3
        $otObjType = self::getUInt2d($recordData, 4);
4334 3
        $idObjID = self::getUInt2d($recordData, 6);
4335 3
        $grbitOpts = self::getUInt2d($recordData, 6);
4336
4337 3
        $this->objs[] = [
4338 3
            'ftCmoType' => $ftCmoType,
4339 3
            'cbCmoSize' => $cbCmoSize,
4340 3
            'otObjType' => $otObjType,
4341 3
            'idObjID' => $idObjID,
4342 3
            'grbitOpts' => $grbitOpts,
4343
        ];
4344 3
        $this->textObjRef = $idObjID;
4345 3
    }
4346
4347
    /**
4348
     * Read WINDOW2 record.
4349
     */
4350 21
    private function readWindow2()
4351
    {
4352 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
4353 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4354
4355
        // move stream pointer to next record
4356 21
        $this->pos += 4 + $length;
4357
4358
        // offset: 0; size: 2; option flags
4359 21
        $options = self::getUInt2d($recordData, 0);
4360
4361
        // offset: 2; size: 2; index to first visible row
4362 21
        $firstVisibleRow = self::getUInt2d($recordData, 2);
4363
4364
        // offset: 4; size: 2; index to first visible colum
4365 21
        $firstVisibleColumn = self::getUInt2d($recordData, 4);
4366 21
        if ($this->version === self::XLS_BIFF8) {
4367
            // offset:  8; size: 2; not used
4368
            // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%)
4369
            // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%)
4370
            // offset: 14; size: 4; not used
4371 21
            $zoomscaleInPageBreakPreview = self::getUInt2d($recordData, 10);
4372 21
            if ($zoomscaleInPageBreakPreview === 0) {
4373 21
                $zoomscaleInPageBreakPreview = 60;
4374
            }
4375 21
            $zoomscaleInNormalView = self::getUInt2d($recordData, 12);
4376 21
            if ($zoomscaleInNormalView === 0) {
4377 17
                $zoomscaleInNormalView = 100;
4378
            }
4379
        }
4380
4381
        // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines
4382 21
        $showGridlines = (bool) ((0x0002 & $options) >> 1);
4383 21
        $this->phpSheet->setShowGridlines($showGridlines);
4384
4385
        // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers
4386 21
        $showRowColHeaders = (bool) ((0x0004 & $options) >> 2);
4387 21
        $this->phpSheet->setShowRowColHeaders($showRowColHeaders);
4388
4389
        // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen
4390 21
        $this->frozen = (bool) ((0x0008 & $options) >> 3);
4391
4392
        // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left
4393 21
        $this->phpSheet->setRightToLeft((bool) ((0x0040 & $options) >> 6));
4394
4395
        // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active
4396 21
        $isActive = (bool) ((0x0400 & $options) >> 10);
4397 21
        if ($isActive) {
4398 18
            $this->spreadsheet->setActiveSheetIndex($this->spreadsheet->getIndex($this->phpSheet));
4399
        }
4400
4401
        // bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view
4402 21
        $isPageBreakPreview = (bool) ((0x0800 & $options) >> 11);
4403
4404
        //FIXME: set $firstVisibleRow and $firstVisibleColumn
4405
4406 21
        if ($this->phpSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_PAGE_LAYOUT) {
4407
            //NOTE: this setting is inferior to page layout view(Excel2007-)
4408 21
            $view = $isPageBreakPreview ? SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW : SheetView::SHEETVIEW_NORMAL;
4409 21
            $this->phpSheet->getSheetView()->setView($view);
4410 21
            if ($this->version === self::XLS_BIFF8) {
4411 21
                $zoomScale = $isPageBreakPreview ? $zoomscaleInPageBreakPreview : $zoomscaleInNormalView;
4412 21
                $this->phpSheet->getSheetView()->setZoomScale($zoomScale);
4413 21
                $this->phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView);
4414
            }
4415
        }
4416 21
    }
4417
4418
    /**
4419
     * Read PLV Record(Created by Excel2007 or upper).
4420
     */
4421 7
    private function readPageLayoutView()
4422
    {
4423 7
        $length = self::getUInt2d($this->data, $this->pos + 2);
4424 7
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4425
4426
        // move stream pointer to next record
4427 7
        $this->pos += 4 + $length;
4428
4429
        // offset: 0; size: 2; rt
4430
        //->ignore
4431 7
        $rt = self::getUInt2d($recordData, 0);
4432
        // offset: 2; size: 2; grbitfr
4433
        //->ignore
4434 7
        $grbitFrt = self::getUInt2d($recordData, 2);
4435
        // offset: 4; size: 8; reserved
4436
        //->ignore
4437
4438
        // offset: 12; size 2; zoom scale
4439 7
        $wScalePLV = self::getUInt2d($recordData, 12);
4440
        // offset: 14; size 2; grbit
4441 7
        $grbit = self::getUInt2d($recordData, 14);
4442
4443
        // decomprise grbit
4444 7
        $fPageLayoutView = $grbit & 0x01;
4445 7
        $fRulerVisible = ($grbit >> 1) & 0x01; //no support
4446 7
        $fWhitespaceHidden = ($grbit >> 3) & 0x01; //no support
4447
4448 7
        if ($fPageLayoutView === 1) {
4449
            $this->phpSheet->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_LAYOUT);
4450
            $this->phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT
4451
        }
4452
        //otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW.
4453 7
    }
4454
4455
    /**
4456
     * Read SCL record.
4457
     */
4458
    private function readScl()
4459
    {
4460
        $length = self::getUInt2d($this->data, $this->pos + 2);
4461
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4462
4463
        // move stream pointer to next record
4464
        $this->pos += 4 + $length;
4465
4466
        // offset: 0; size: 2; numerator of the view magnification
4467
        $numerator = self::getUInt2d($recordData, 0);
4468
4469
        // offset: 2; size: 2; numerator of the view magnification
4470
        $denumerator = self::getUInt2d($recordData, 2);
4471
4472
        // set the zoom scale (in percent)
4473
        $this->phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator);
4474
    }
4475
4476
    /**
4477
     * Read PANE record.
4478
     */
4479 1
    private function readPane()
4480
    {
4481 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
4482 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4483
4484
        // move stream pointer to next record
4485 1
        $this->pos += 4 + $length;
4486
4487 1
        if (!$this->readDataOnly) {
4488
            // offset: 0; size: 2; position of vertical split
4489 1
            $px = self::getUInt2d($recordData, 0);
4490
4491
            // offset: 2; size: 2; position of horizontal split
4492 1
            $py = self::getUInt2d($recordData, 2);
4493
4494
            // offset: 4; size: 2; top most visible row in the bottom pane
4495 1
            $rwTop = self::getUInt2d($recordData, 4);
4496
4497
            // offset: 6; size: 2; first visible left column in the right pane
4498 1
            $colLeft = self::getUInt2d($recordData, 6);
4499
4500 1
            if ($this->frozen) {
4501
                // frozen panes
4502 1
                $cell = Coordinate::stringFromColumnIndex($px + 1) . ($py + 1);
4503 1
                $topLeftCell = Coordinate::stringFromColumnIndex($colLeft + 1) . ($rwTop + 1);
4504 1
                $this->phpSheet->freezePane($cell, $topLeftCell);
4505
            }
4506
            // unfrozen panes; split windows; not supported by PhpSpreadsheet core
4507
        }
4508 1
    }
4509
4510
    /**
4511
     * Read SELECTION record. There is one such record for each pane in the sheet.
4512
     */
4513 21
    private function readSelection()
4514
    {
4515 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
4516 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4517
4518
        // move stream pointer to next record
4519 21
        $this->pos += 4 + $length;
4520
4521 21
        if (!$this->readDataOnly) {
4522
            // offset: 0; size: 1; pane identifier
4523 20
            $paneId = ord($recordData[0]);
4524
4525
            // offset: 1; size: 2; index to row of the active cell
4526 20
            $r = self::getUInt2d($recordData, 1);
4527
4528
            // offset: 3; size: 2; index to column of the active cell
4529 20
            $c = self::getUInt2d($recordData, 3);
4530
4531
            // offset: 5; size: 2; index into the following cell range list to the
4532
            //  entry that contains the active cell
4533 20
            $index = self::getUInt2d($recordData, 5);
4534
4535
            // offset: 7; size: var; cell range address list containing all selected cell ranges
4536 20
            $data = substr($recordData, 7);
4537 20
            $cellRangeAddressList = $this->readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax
4538
4539 20
            $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0];
4540
4541
            // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!)
4542 20
            if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) {
4543
                $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells);
4544
            }
4545
4546
            // first row '1' + last row '65536' indicates that full column is selected
4547 20
            if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) {
4548
                $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells);
4549
            }
4550
4551
            // first column 'A' + last column 'IV' indicates that full row is selected
4552 20
            if (preg_match('/^(A\d+\:)IV(\d+)$/', $selectedCells)) {
4553 2
                $selectedCells = preg_replace('/^(A\d+\:)IV(\d+)$/', '${1}XFD${2}', $selectedCells);
4554
            }
4555
4556 20
            $this->phpSheet->setSelectedCells($selectedCells);
4557
        }
4558 21
    }
4559
4560 12
    private function includeCellRangeFiltered($cellRangeAddress)
4561
    {
4562 12
        $includeCellRange = true;
4563 12
        if ($this->getReadFilter() !== null) {
4564 12
            $includeCellRange = false;
4565 12
            $rangeBoundaries = Coordinate::getRangeBoundaries($cellRangeAddress);
4566 12
            ++$rangeBoundaries[1][0];
4567 12
            for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; ++$row) {
4568 12
                for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; ++$column) {
4569 12
                    if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
4570 12
                        $includeCellRange = true;
4571
4572 12
                        break 2;
4573
                    }
4574
                }
4575
            }
4576
        }
4577
4578 12
        return $includeCellRange;
4579
    }
4580
4581
    /**
4582
     * MERGEDCELLS.
4583
     *
4584
     * This record contains the addresses of merged cell ranges
4585
     * in the current sheet.
4586
     *
4587
     * --    "OpenOffice.org's Documentation of the Microsoft
4588
     *         Excel File Format"
4589
     */
4590 13
    private function readMergedCells()
4591
    {
4592 13
        $length = self::getUInt2d($this->data, $this->pos + 2);
4593 13
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4594
4595
        // move stream pointer to next record
4596 13
        $this->pos += 4 + $length;
4597
4598 13
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
4599 12
            $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData);
4600 12
            foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) {
4601 12
                if ((strpos($cellRangeAddress, ':') !== false) &&
4602 12
                    ($this->includeCellRangeFiltered($cellRangeAddress))) {
4603 12
                    $this->phpSheet->mergeCells($cellRangeAddress);
4604
                }
4605
            }
4606
        }
4607 13
    }
4608
4609
    /**
4610
     * Read HYPERLINK record.
4611
     */
4612 2
    private function readHyperLink()
4613
    {
4614 2
        $length = self::getUInt2d($this->data, $this->pos + 2);
4615 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4616
4617
        // move stream pointer forward to next record
4618 2
        $this->pos += 4 + $length;
4619
4620 2
        if (!$this->readDataOnly) {
4621
            // offset: 0; size: 8; cell range address of all cells containing this hyperlink
4622
            try {
4623 2
                $cellRange = $this->readBIFF8CellRangeAddressFixed($recordData);
4624
            } catch (PhpSpreadsheetException $e) {
4625
                return;
4626
            }
4627
4628
            // offset: 8, size: 16; GUID of StdLink
4629
4630
            // offset: 24, size: 4; unknown value
4631
4632
            // offset: 28, size: 4; option flags
4633
            // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL
4634 2
            $isFileLinkOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 0;
4635
4636
            // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL
4637 2
            $isAbsPathOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 1;
4638
4639
            // bit: 2 (and 4); mask: 0x00000014; 0 = no description
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
4640 2
            $hasDesc = (0x00000014 & self::getUInt2d($recordData, 28)) >> 2;
4641
4642
            // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text
4643 2
            $hasText = (0x00000008 & self::getUInt2d($recordData, 28)) >> 3;
4644
4645
            // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame
4646 2
            $hasFrame = (0x00000080 & self::getUInt2d($recordData, 28)) >> 7;
4647
4648
            // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name)
4649 2
            $isUNC = (0x00000100 & self::getUInt2d($recordData, 28)) >> 8;
4650
4651
            // offset within record data
4652 2
            $offset = 32;
4653
4654 2
            if ($hasDesc) {
4655
                // offset: 32; size: var; character count of description text
4656 1
                $dl = self::getInt4d($recordData, 32);
4657
                // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated
4658 1
                $desc = self::encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false);
4659 1
                $offset += 4 + 2 * $dl;
4660
            }
4661 2
            if ($hasFrame) {
4662
                $fl = self::getInt4d($recordData, $offset);
4663
                $offset += 4 + 2 * $fl;
4664
            }
4665
4666
            // detect type of hyperlink (there are 4 types)
4667 2
            $hyperlinkType = null;
4668
4669 2
            if ($isUNC) {
4670
                $hyperlinkType = 'UNC';
4671 2
            } elseif (!$isFileLinkOrUrl) {
4672 2
                $hyperlinkType = 'workbook';
4673 2
            } elseif (ord($recordData[$offset]) == 0x03) {
4674
                $hyperlinkType = 'local';
4675 2
            } elseif (ord($recordData[$offset]) == 0xE0) {
4676 2
                $hyperlinkType = 'URL';
4677
            }
4678
4679
            switch ($hyperlinkType) {
4680 2
                case 'URL':
4681
                    // section 5.58.2: Hyperlink containing a URL
4682
                    // e.g. http://example.org/index.php
4683
4684
                    // offset: var; size: 16; GUID of URL Moniker
4685 2
                    $offset += 16;
4686
                    // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word
4687 2
                    $us = self::getInt4d($recordData, $offset);
4688 2
                    $offset += 4;
4689
                    // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated
4690 2
                    $url = self::encodeUTF16(substr($recordData, $offset, $us - 2), false);
4691 2
                    $nullOffset = strpos($url, 0x00);
4692 2
                    if ($nullOffset) {
4693 1
                        $url = substr($url, 0, $nullOffset);
4694
                    }
4695 2
                    $url .= $hasText ? '#' : '';
4696 2
                    $offset += $us;
4697
4698 2
                    break;
4699 2
                case 'local':
4700
                    // section 5.58.3: Hyperlink to local file
4701
                    // examples:
4702
                    //   mydoc.txt
4703
                    //   ../../somedoc.xls#Sheet!A1
4704
4705
                    // offset: var; size: 16; GUI of File Moniker
4706
                    $offset += 16;
4707
4708
                    // offset: var; size: 2; directory up-level count.
4709
                    $upLevelCount = self::getUInt2d($recordData, $offset);
4710
                    $offset += 2;
4711
4712
                    // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word
4713
                    $sl = self::getInt4d($recordData, $offset);
4714
                    $offset += 4;
4715
4716
                    // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string)
4717
                    $shortenedFilePath = substr($recordData, $offset, $sl);
4718
                    $shortenedFilePath = self::encodeUTF16($shortenedFilePath, true);
4719
                    $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero
4720
4721
                    $offset += $sl;
4722
4723
                    // offset: var; size: 24; unknown sequence
4724
                    $offset += 24;
4725
4726
                    // extended file path
4727
                    // offset: var; size: 4; size of the following file link field including string lenth mark
4728
                    $sz = self::getInt4d($recordData, $offset);
4729
                    $offset += 4;
4730
4731
                    // only present if $sz > 0
4732
                    if ($sz > 0) {
4733
                        // offset: var; size: 4; size of the character array of the extended file path and name
4734
                        $xl = self::getInt4d($recordData, $offset);
4735
                        $offset += 4;
4736
4737
                        // offset: var; size 2; unknown
4738
                        $offset += 2;
4739
4740
                        // offset: var; size $xl; character array of the extended file path and name.
4741
                        $extendedFilePath = substr($recordData, $offset, $xl);
4742
                        $extendedFilePath = self::encodeUTF16($extendedFilePath, false);
4743
                        $offset += $xl;
4744
                    }
4745
4746
                    // construct the path
4747
                    $url = str_repeat('..\\', $upLevelCount);
4748
                    $url .= ($sz > 0) ? $extendedFilePath : $shortenedFilePath; // use extended path if available
4749
                    $url .= $hasText ? '#' : '';
4750
4751
                    break;
4752 2
                case 'UNC':
4753
                    // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path
4754
                    // todo: implement
4755
                    return;
4756 2
                case 'workbook':
4757
                    // section 5.58.5: Hyperlink to the Current Workbook
4758
                    // e.g. Sheet2!B1:C2, stored in text mark field
4759 2
                    $url = 'sheet://';
4760
4761 2
                    break;
4762
                default:
4763
                    return;
4764
            }
4765
4766 2
            if ($hasText) {
4767
                // offset: var; size: 4; character count of text mark including trailing zero word
4768 2
                $tl = self::getInt4d($recordData, $offset);
4769 2
                $offset += 4;
4770
                // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated
4771 2
                $text = self::encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false);
4772 2
                $url .= $text;
4773
            }
4774
4775
            // apply the hyperlink to all the relevant cells
4776 2
            foreach (Coordinate::extractAllCellReferencesInRange($cellRange) as $coordinate) {
4777 2
                $this->phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url);
4778
            }
4779
        }
4780 2
    }
4781
4782
    /**
4783
     * Read DATAVALIDATIONS record.
4784
     */
4785
    private function readDataValidations()
4786
    {
4787
        $length = self::getUInt2d($this->data, $this->pos + 2);
4788
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4789
4790
        // move stream pointer forward to next record
4791
        $this->pos += 4 + $length;
4792
    }
4793
4794
    /**
4795
     * Read DATAVALIDATION record.
4796
     */
4797
    private function readDataValidation()
4798
    {
4799
        $length = self::getUInt2d($this->data, $this->pos + 2);
4800
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4801
4802
        // move stream pointer forward to next record
4803
        $this->pos += 4 + $length;
4804
4805
        if ($this->readDataOnly) {
4806
            return;
4807
        }
4808
4809
        // offset: 0; size: 4; Options
4810
        $options = self::getInt4d($recordData, 0);
4811
4812
        // bit: 0-3; mask: 0x0000000F; type
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
4813
        $type = (0x0000000F & $options) >> 0;
4814
        switch ($type) {
4815
            case 0x00:
4816
                $type = DataValidation::TYPE_NONE;
4817
4818
                break;
4819
            case 0x01:
4820
                $type = DataValidation::TYPE_WHOLE;
4821
4822
                break;
4823
            case 0x02:
4824
                $type = DataValidation::TYPE_DECIMAL;
4825
4826
                break;
4827
            case 0x03:
4828
                $type = DataValidation::TYPE_LIST;
4829
4830
                break;
4831
            case 0x04:
4832
                $type = DataValidation::TYPE_DATE;
4833
4834
                break;
4835
            case 0x05:
4836
                $type = DataValidation::TYPE_TIME;
4837
4838
                break;
4839
            case 0x06:
4840
                $type = DataValidation::TYPE_TEXTLENGTH;
4841
4842
                break;
4843
            case 0x07:
4844
                $type = DataValidation::TYPE_CUSTOM;
4845
4846
                break;
4847
        }
4848
4849
        // bit: 4-6; mask: 0x00000070; error type
4850
        $errorStyle = (0x00000070 & $options) >> 4;
4851
        switch ($errorStyle) {
4852
            case 0x00:
4853
                $errorStyle = DataValidation::STYLE_STOP;
4854
4855
                break;
4856
            case 0x01:
4857
                $errorStyle = DataValidation::STYLE_WARNING;
4858
4859
                break;
4860
            case 0x02:
4861
                $errorStyle = DataValidation::STYLE_INFORMATION;
4862
4863
                break;
4864
        }
4865
4866
        // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)
4867
        // I have only seen cases where this is 1
4868
        $explicitFormula = (0x00000080 & $options) >> 7;
4869
4870
        // bit: 8; mask: 0x00000100; 1= empty cells allowed
4871
        $allowBlank = (0x00000100 & $options) >> 8;
4872
4873
        // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity
4874
        $suppressDropDown = (0x00000200 & $options) >> 9;
4875
4876
        // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected
4877
        $showInputMessage = (0x00040000 & $options) >> 18;
4878
4879
        // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered
4880
        $showErrorMessage = (0x00080000 & $options) >> 19;
4881
4882
        // bit: 20-23; mask: 0x00F00000; condition operator
4883
        $operator = (0x00F00000 & $options) >> 20;
4884
        switch ($operator) {
4885
            case 0x00:
4886
                $operator = DataValidation::OPERATOR_BETWEEN;
4887
4888
                break;
4889
            case 0x01:
4890
                $operator = DataValidation::OPERATOR_NOTBETWEEN;
4891
4892
                break;
4893
            case 0x02:
4894
                $operator = DataValidation::OPERATOR_EQUAL;
4895
4896
                break;
4897
            case 0x03:
4898
                $operator = DataValidation::OPERATOR_NOTEQUAL;
4899
4900
                break;
4901
            case 0x04:
4902
                $operator = DataValidation::OPERATOR_GREATERTHAN;
4903
4904
                break;
4905
            case 0x05:
4906
                $operator = DataValidation::OPERATOR_LESSTHAN;
4907
4908
                break;
4909
            case 0x06:
4910
                $operator = DataValidation::OPERATOR_GREATERTHANOREQUAL;
4911
4912
                break;
4913
            case 0x07:
4914
                $operator = DataValidation::OPERATOR_LESSTHANOREQUAL;
4915
4916
                break;
4917
        }
4918
4919
        // offset: 4; size: var; title of the prompt box
4920
        $offset = 4;
4921
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4922
        $promptTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4923
        $offset += $string['size'];
4924
4925
        // offset: var; size: var; title of the error box
4926
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4927
        $errorTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4928
        $offset += $string['size'];
4929
4930
        // offset: var; size: var; text of the prompt box
4931
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4932
        $prompt = $string['value'] !== chr(0) ? $string['value'] : '';
4933
        $offset += $string['size'];
4934
4935
        // offset: var; size: var; text of the error box
4936
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4937
        $error = $string['value'] !== chr(0) ? $string['value'] : '';
4938
        $offset += $string['size'];
4939
4940
        // offset: var; size: 2; size of the formula data for the first condition
4941
        $sz1 = self::getUInt2d($recordData, $offset);
4942
        $offset += 2;
4943
4944
        // offset: var; size: 2; not used
4945
        $offset += 2;
4946
4947
        // offset: var; size: $sz1; formula data for first condition (without size field)
4948
        $formula1 = substr($recordData, $offset, $sz1);
4949
        $formula1 = pack('v', $sz1) . $formula1; // prepend the length
4950
        try {
4951
            $formula1 = $this->getFormulaFromStructure($formula1);
4952
4953
            // in list type validity, null characters are used as item separators
4954
            if ($type == DataValidation::TYPE_LIST) {
4955
                $formula1 = str_replace(chr(0), ',', $formula1);
4956
            }
4957
        } catch (PhpSpreadsheetException $e) {
4958
            return;
4959
        }
4960
        $offset += $sz1;
4961
4962
        // offset: var; size: 2; size of the formula data for the first condition
4963
        $sz2 = self::getUInt2d($recordData, $offset);
4964
        $offset += 2;
4965
4966
        // offset: var; size: 2; not used
4967
        $offset += 2;
4968
4969
        // offset: var; size: $sz2; formula data for second condition (without size field)
4970
        $formula2 = substr($recordData, $offset, $sz2);
4971
        $formula2 = pack('v', $sz2) . $formula2; // prepend the length
4972
        try {
4973
            $formula2 = $this->getFormulaFromStructure($formula2);
4974
        } catch (PhpSpreadsheetException $e) {
4975
            return;
4976
        }
4977
        $offset += $sz2;
4978
4979
        // offset: var; size: var; cell range address list with
4980
        $cellRangeAddressList = $this->readBIFF8CellRangeAddressList(substr($recordData, $offset));
4981
        $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
4982
4983
        foreach ($cellRangeAddresses as $cellRange) {
4984
            $stRange = $this->phpSheet->shrinkRangeToFit($cellRange);
4985
            foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $coordinate) {
4986
                $objValidation = $this->phpSheet->getCell($coordinate)->getDataValidation();
4987
                $objValidation->setType($type);
4988
                $objValidation->setErrorStyle($errorStyle);
4989
                $objValidation->setAllowBlank((bool) $allowBlank);
4990
                $objValidation->setShowInputMessage((bool) $showInputMessage);
4991
                $objValidation->setShowErrorMessage((bool) $showErrorMessage);
4992
                $objValidation->setShowDropDown(!$suppressDropDown);
4993
                $objValidation->setOperator($operator);
4994
                $objValidation->setErrorTitle($errorTitle);
4995
                $objValidation->setError($error);
4996
                $objValidation->setPromptTitle($promptTitle);
4997
                $objValidation->setPrompt($prompt);
4998
                $objValidation->setFormula1($formula1);
4999
                $objValidation->setFormula2($formula2);
5000
            }
5001
        }
5002
    }
5003
5004
    /**
5005
     * Read SHEETLAYOUT record. Stores sheet tab color information.
5006
     */
5007 2
    private function readSheetLayout()
5008
    {
5009 2
        $length = self::getUInt2d($this->data, $this->pos + 2);
5010 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5011
5012
        // move stream pointer to next record
5013 2
        $this->pos += 4 + $length;
5014
5015
        // local pointer in record data
5016 2
        $offset = 0;
5017
5018 2
        if (!$this->readDataOnly) {
5019
            // offset: 0; size: 2; repeated record identifier 0x0862
5020
5021
            // offset: 2; size: 10; not used
5022
5023
            // offset: 12; size: 4; size of record data
5024
            // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?)
5025 2
            $sz = self::getInt4d($recordData, 12);
5026
5027
            switch ($sz) {
5028 2
                case 0x14:
5029
                    // offset: 16; size: 2; color index for sheet tab
5030 1
                    $colorIndex = self::getUInt2d($recordData, 16);
5031 1
                    $color = Xls\Color::map($colorIndex, $this->palette, $this->version);
5032 1
                    $this->phpSheet->getTabColor()->setRGB($color['rgb']);
5033
5034 1
                    break;
5035 1
                case 0x28:
5036
                    // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007
5037 1
                    return;
5038
5039
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
5040
            }
5041
        }
5042 1
    }
5043
5044
    /**
5045
     * Read SHEETPROTECTION record (FEATHEADR).
5046
     */
5047 8
    private function readSheetProtection()
5048
    {
5049 8
        $length = self::getUInt2d($this->data, $this->pos + 2);
5050 8
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5051
5052
        // move stream pointer to next record
5053 8
        $this->pos += 4 + $length;
5054
5055 8
        if ($this->readDataOnly) {
5056
            return;
5057
        }
5058
5059
        // offset: 0; size: 2; repeated record header
5060
5061
        // offset: 2; size: 2; FRT cell reference flag (=0 currently)
5062
5063
        // offset: 4; size: 8; Currently not used and set to 0
5064
5065
        // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag)
5066 8
        $isf = self::getUInt2d($recordData, 12);
5067 8
        if ($isf != 2) {
5068
            return;
5069
        }
5070
5071
        // offset: 14; size: 1; =1 since this is a feat header
5072
5073
        // offset: 15; size: 4; size of rgbHdrSData
5074
5075
        // rgbHdrSData, assume "Enhanced Protection"
5076
        // offset: 19; size: 2; option flags
5077 8
        $options = self::getUInt2d($recordData, 19);
5078
5079
        // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects
5080 8
        $bool = (0x0001 & $options) >> 0;
5081 8
        $this->phpSheet->getProtection()->setObjects(!$bool);
5082
5083
        // bit: 1; mask 0x0002; edit scenarios
5084 8
        $bool = (0x0002 & $options) >> 1;
5085 8
        $this->phpSheet->getProtection()->setScenarios(!$bool);
5086
5087
        // bit: 2; mask 0x0004; format cells
5088 8
        $bool = (0x0004 & $options) >> 2;
5089 8
        $this->phpSheet->getProtection()->setFormatCells(!$bool);
5090
5091
        // bit: 3; mask 0x0008; format columns
5092 8
        $bool = (0x0008 & $options) >> 3;
5093 8
        $this->phpSheet->getProtection()->setFormatColumns(!$bool);
5094
5095
        // bit: 4; mask 0x0010; format rows
5096 8
        $bool = (0x0010 & $options) >> 4;
5097 8
        $this->phpSheet->getProtection()->setFormatRows(!$bool);
5098
5099
        // bit: 5; mask 0x0020; insert columns
5100 8
        $bool = (0x0020 & $options) >> 5;
5101 8
        $this->phpSheet->getProtection()->setInsertColumns(!$bool);
5102
5103
        // bit: 6; mask 0x0040; insert rows
5104 8
        $bool = (0x0040 & $options) >> 6;
5105 8
        $this->phpSheet->getProtection()->setInsertRows(!$bool);
5106
5107
        // bit: 7; mask 0x0080; insert hyperlinks
5108 8
        $bool = (0x0080 & $options) >> 7;
5109 8
        $this->phpSheet->getProtection()->setInsertHyperlinks(!$bool);
5110
5111
        // bit: 8; mask 0x0100; delete columns
5112 8
        $bool = (0x0100 & $options) >> 8;
5113 8
        $this->phpSheet->getProtection()->setDeleteColumns(!$bool);
5114
5115
        // bit: 9; mask 0x0200; delete rows
5116 8
        $bool = (0x0200 & $options) >> 9;
5117 8
        $this->phpSheet->getProtection()->setDeleteRows(!$bool);
5118
5119
        // bit: 10; mask 0x0400; select locked cells
5120 8
        $bool = (0x0400 & $options) >> 10;
5121 8
        $this->phpSheet->getProtection()->setSelectLockedCells(!$bool);
5122
5123
        // bit: 11; mask 0x0800; sort cell range
5124 8
        $bool = (0x0800 & $options) >> 11;
5125 8
        $this->phpSheet->getProtection()->setSort(!$bool);
5126
5127
        // bit: 12; mask 0x1000; auto filter
5128 8
        $bool = (0x1000 & $options) >> 12;
5129 8
        $this->phpSheet->getProtection()->setAutoFilter(!$bool);
5130
5131
        // bit: 13; mask 0x2000; pivot tables
5132 8
        $bool = (0x2000 & $options) >> 13;
5133 8
        $this->phpSheet->getProtection()->setPivotTables(!$bool);
5134
5135
        // bit: 14; mask 0x4000; select unlocked cells
5136 8
        $bool = (0x4000 & $options) >> 14;
5137 8
        $this->phpSheet->getProtection()->setSelectUnlockedCells(!$bool);
5138
5139
        // offset: 21; size: 2; not used
5140 8
    }
5141
5142
    /**
5143
     * Read RANGEPROTECTION record
5144
     * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification,
5145
     * where it is referred to as FEAT record.
5146
     */
5147 1
    private function readRangeProtection()
5148
    {
5149 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
5150 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5151
5152
        // move stream pointer to next record
5153 1
        $this->pos += 4 + $length;
5154
5155
        // local pointer in record data
5156 1
        $offset = 0;
5157
5158 1
        if (!$this->readDataOnly) {
5159 1
            $offset += 12;
5160
5161
            // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag
5162 1
            $isf = self::getUInt2d($recordData, 12);
5163 1
            if ($isf != 2) {
5164
                // we only read FEAT records of type 2
5165
                return;
5166
            }
5167 1
            $offset += 2;
5168
5169 1
            $offset += 5;
5170
5171
            // offset: 19; size: 2; count of ref ranges this feature is on
5172 1
            $cref = self::getUInt2d($recordData, 19);
5173 1
            $offset += 2;
5174
5175 1
            $offset += 6;
5176
5177
            // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record)
5178 1
            $cellRanges = [];
5179 1
            for ($i = 0; $i < $cref; ++$i) {
5180
                try {
5181 1
                    $cellRange = $this->readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8));
5182
                } catch (PhpSpreadsheetException $e) {
5183
                    return;
5184
                }
5185 1
                $cellRanges[] = $cellRange;
5186 1
                $offset += 8;
5187
            }
5188
5189
            // offset: var; size: var; variable length of feature specific data
5190 1
            $rgbFeat = substr($recordData, $offset);
5191 1
            $offset += 4;
5192
5193
            // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit)
5194 1
            $wPassword = self::getInt4d($recordData, $offset);
5195 1
            $offset += 4;
5196
5197
            // Apply range protection to sheet
5198 1
            if ($cellRanges) {
5199 1
                $this->phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true);
5200
            }
5201
        }
5202 1
    }
5203
5204
    /**
5205
     * Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record
5206
     * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented.
5207
     * In this case, we must treat the CONTINUE record as a MSODRAWING record.
5208
     */
5209
    private function readContinue()
5210
    {
5211
        $length = self::getUInt2d($this->data, $this->pos + 2);
5212
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5213
5214
        // check if we are reading drawing data
5215
        // this is in case a free CONTINUE record occurs in other circumstances we are unaware of
5216
        if ($this->drawingData == '') {
5217
            // move stream pointer to next record
5218
            $this->pos += 4 + $length;
5219
5220
            return;
5221
        }
5222
5223
        // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data
5224
        if ($length < 4) {
5225
            // move stream pointer to next record
5226
            $this->pos += 4 + $length;
5227
5228
            return;
5229
        }
5230
5231
        // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record
5232
        // look inside CONTINUE record to see if it looks like a part of an Escher stream
5233
        // we know that Escher stream may be split at least at
5234
        //        0xF003 MsofbtSpgrContainer
5235
        //        0xF004 MsofbtSpContainer
5236
        //        0xF00D MsofbtClientTextbox
5237
        $validSplitPoints = [0xF003, 0xF004, 0xF00D]; // add identifiers if we find more
5238
5239
        $splitPoint = self::getUInt2d($recordData, 2);
5240
        if (in_array($splitPoint, $validSplitPoints)) {
5241
            // get spliced record data (and move pointer to next record)
5242
            $splicedRecordData = $this->getSplicedRecordData();
5243
            $this->drawingData .= $splicedRecordData['recordData'];
5244
5245
            return;
5246
        }
5247
5248
        // move stream pointer to next record
5249
        $this->pos += 4 + $length;
5250
    }
5251
5252
    /**
5253
     * Reads a record from current position in data stream and continues reading data as long as CONTINUE
5254
     * records are found. Splices the record data pieces and returns the combined string as if record data
5255
     * is in one piece.
5256
     * Moves to next current position in data stream to start of next record different from a CONtINUE record.
5257
     *
5258
     * @return array
5259
     */
5260 21
    private function getSplicedRecordData()
5261
    {
5262 21
        $data = '';
5263 21
        $spliceOffsets = [];
5264
5265 21
        $i = 0;
5266 21
        $spliceOffsets[0] = 0;
5267
5268
        do {
5269 21
            ++$i;
5270
5271
            // offset: 0; size: 2; identifier
5272 21
            $identifier = self::getUInt2d($this->data, $this->pos);
5273
            // offset: 2; size: 2; length
5274 21
            $length = self::getUInt2d($this->data, $this->pos + 2);
5275 21
            $data .= $this->readRecordData($this->data, $this->pos + 4, $length);
5276
5277 21
            $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length;
5278
5279 21
            $this->pos += 4 + $length;
5280 21
            $nextIdentifier = self::getUInt2d($this->data, $this->pos);
5281 21
        } while ($nextIdentifier == self::XLS_TYPE_CONTINUE);
5282
5283
        $splicedData = [
5284 21
            'recordData' => $data,
5285 21
            'spliceOffsets' => $spliceOffsets,
5286
        ];
5287
5288 21
        return $splicedData;
5289
    }
5290
5291
    /**
5292
     * Convert formula structure into human readable Excel formula like 'A3+A5*5'.
5293
     *
5294
     * @param string $formulaStructure The complete binary data for the formula
5295
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5296
     *
5297
     * @return string Human readable formula
5298
     */
5299 10
    private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1')
5300
    {
5301
        // offset: 0; size: 2; size of the following formula data
5302 10
        $sz = self::getUInt2d($formulaStructure, 0);
5303
5304
        // offset: 2; size: sz
5305 10
        $formulaData = substr($formulaStructure, 2, $sz);
5306
5307
        // offset: 2 + sz; size: variable (optional)
5308 10
        if (strlen($formulaStructure) > 2 + $sz) {
5309
            $additionalData = substr($formulaStructure, 2 + $sz);
5310
        } else {
5311 10
            $additionalData = '';
5312
        }
5313
5314 10
        return $this->getFormulaFromData($formulaData, $additionalData, $baseCell);
5315
    }
5316
5317
    /**
5318
     * Take formula data and additional data for formula and return human readable formula.
5319
     *
5320
     * @param string $formulaData The binary data for the formula itself
5321
     * @param string $additionalData Additional binary data going with the formula
5322
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5323
     *
5324
     * @return string Human readable formula
5325
     */
5326 10
    private function getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1')
5327
    {
5328
        // start parsing the formula data
5329 10
        $tokens = [];
5330
5331 10
        while (strlen($formulaData) > 0 and $token = $this->getNextToken($formulaData, $baseCell)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
5332 10
            $tokens[] = $token;
5333 10
            $formulaData = substr($formulaData, $token['size']);
5334
        }
5335
5336 10
        $formulaString = $this->createFormulaFromTokens($tokens, $additionalData);
5337
5338 10
        return $formulaString;
5339
    }
5340
5341
    /**
5342
     * Take array of tokens together with additional data for formula and return human readable formula.
5343
     *
5344
     * @param array $tokens
5345
     * @param string $additionalData Additional binary data going with the formula
5346
     *
5347
     * @return string Human readable formula
5348
     */
5349 10
    private function createFormulaFromTokens($tokens, $additionalData)
5350
    {
5351
        // empty formula?
5352 10
        if (empty($tokens)) {
5353
            return '';
5354
        }
5355
5356 10
        $formulaStrings = [];
5357 10
        foreach ($tokens as $token) {
5358
            // initialize spaces
5359 10
            $space0 = isset($space0) ? $space0 : ''; // spaces before next token, not tParen
5360 10
            $space1 = isset($space1) ? $space1 : ''; // carriage returns before next token, not tParen
5361 10
            $space2 = isset($space2) ? $space2 : ''; // spaces before opening parenthesis
5362 10
            $space3 = isset($space3) ? $space3 : ''; // carriage returns before opening parenthesis
5363 10
            $space4 = isset($space4) ? $space4 : ''; // spaces before closing parenthesis
5364 10
            $space5 = isset($space5) ? $space5 : ''; // carriage returns before closing parenthesis
5365
5366 10
            switch ($token['name']) {
5367 10
                case 'tAdd': // addition
5368 10
                case 'tConcat': // addition
5369 10
                case 'tDiv': // division
5370 10
                case 'tEQ': // equality
5371 10
                case 'tGE': // greater than or equal
5372 10
                case 'tGT': // greater than
5373 10
                case 'tIsect': // intersection
5374 10
                case 'tLE': // less than or equal
5375 10
                case 'tList': // less than or equal
5376 10
                case 'tLT': // less than
5377 10
                case 'tMul': // multiplication
5378 10
                case 'tNE': // multiplication
5379 10
                case 'tPower': // power
5380 10
                case 'tRange': // range
5381 10
                case 'tSub': // subtraction
5382 10
                    $op2 = array_pop($formulaStrings);
5383 10
                    $op1 = array_pop($formulaStrings);
5384 10
                    $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2";
5385 10
                    unset($space0, $space1);
5386
5387 10
                    break;
5388 10
                case 'tUplus': // unary plus
5389 10
                case 'tUminus': // unary minus
5390
                    $op = array_pop($formulaStrings);
5391
                    $formulaStrings[] = "$space1$space0{$token['data']}$op";
5392
                    unset($space0, $space1);
5393
5394
                    break;
5395 10
                case 'tPercent': // percent sign
5396
                    $op = array_pop($formulaStrings);
5397
                    $formulaStrings[] = "$op$space1$space0{$token['data']}";
5398
                    unset($space0, $space1);
5399
5400
                    break;
5401 10
                case 'tAttrVolatile': // indicates volatile function
5402 10
                case 'tAttrIf':
5403 10
                case 'tAttrSkip':
5404 10
                case 'tAttrChoose':
5405
                    // token is only important for Excel formula evaluator
5406
                    // do nothing
5407
                    break;
5408 10
                case 'tAttrSpace': // space / carriage return
5409
                    // space will be used when next token arrives, do not alter formulaString stack
5410
                    switch ($token['data']['spacetype']) {
5411
                        case 'type0':
5412
                            $space0 = str_repeat(' ', $token['data']['spacecount']);
5413
5414
                            break;
5415
                        case 'type1':
5416
                            $space1 = str_repeat("\n", $token['data']['spacecount']);
5417
5418
                            break;
5419
                        case 'type2':
5420
                            $space2 = str_repeat(' ', $token['data']['spacecount']);
5421
5422
                            break;
5423
                        case 'type3':
5424
                            $space3 = str_repeat("\n", $token['data']['spacecount']);
5425
5426
                            break;
5427
                        case 'type4':
5428
                            $space4 = str_repeat(' ', $token['data']['spacecount']);
5429
5430
                            break;
5431
                        case 'type5':
5432
                            $space5 = str_repeat("\n", $token['data']['spacecount']);
5433
5434
                            break;
5435
                    }
5436
5437
                    break;
5438 10
                case 'tAttrSum': // SUM function with one parameter
5439 9
                    $op = array_pop($formulaStrings);
5440 9
                    $formulaStrings[] = "{$space1}{$space0}SUM($op)";
5441 9
                    unset($space0, $space1);
5442
5443 9
                    break;
5444 10
                case 'tFunc': // function with fixed number of arguments
5445 10
                case 'tFuncV': // function with variable number of arguments
5446 9
                    if ($token['data']['function'] != '') {
5447
                        // normal function
5448 9
                        $ops = []; // array of operators
5449 9
                        for ($i = 0; $i < $token['data']['args']; ++$i) {
5450 1
                            $ops[] = array_pop($formulaStrings);
5451
                        }
5452 9
                        $ops = array_reverse($ops);
5453 9
                        $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ')';
5454 9
                        unset($space0, $space1);
5455
                    } else {
5456
                        // add-in function
5457
                        $ops = []; // array of operators
5458
                        for ($i = 0; $i < $token['data']['args'] - 1; ++$i) {
5459
                            $ops[] = array_pop($formulaStrings);
5460
                        }
5461
                        $ops = array_reverse($ops);
5462
                        $function = array_pop($formulaStrings);
5463
                        $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ')';
5464
                        unset($space0, $space1);
5465
                    }
5466
5467 9
                    break;
5468 10
                case 'tParen': // parenthesis
5469
                    $expression = array_pop($formulaStrings);
5470
                    $formulaStrings[] = "$space3$space2($expression$space5$space4)";
5471
                    unset($space2, $space3, $space4, $space5);
5472
5473
                    break;
5474 10
                case 'tArray': // array constant
5475
                    $constantArray = self::readBIFF8ConstantArray($additionalData);
5476
                    $formulaStrings[] = $space1 . $space0 . $constantArray['value'];
5477
                    $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data
5478
                    unset($space0, $space1);
5479
5480
                    break;
5481 10
                case 'tMemArea':
5482
                    // bite off chunk of additional data
5483
                    $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($additionalData);
5484
                    $additionalData = substr($additionalData, $cellRangeAddressList['size']);
5485
                    $formulaStrings[] = "$space1$space0{$token['data']}";
5486
                    unset($space0, $space1);
5487
5488
                    break;
5489 10
                case 'tArea': // cell range address
5490 10
                case 'tBool': // boolean
5491 10
                case 'tErr': // error code
5492 10
                case 'tInt': // integer
5493 2
                case 'tMemErr':
5494 2
                case 'tMemFunc':
5495 2
                case 'tMissArg':
5496 2
                case 'tName':
5497 2
                case 'tNameX':
5498 2
                case 'tNum': // number
5499 2
                case 'tRef': // single cell reference
5500 2
                case 'tRef3d': // 3d cell reference
5501 2
                case 'tArea3d': // 3d cell range reference
5502 1
                case 'tRefN':
5503 1
                case 'tAreaN':
5504 1
                case 'tStr': // string
5505 10
                    $formulaStrings[] = "$space1$space0{$token['data']}";
5506 10
                    unset($space0, $space1);
5507
5508 10
                    break;
5509
            }
5510
        }
5511 10
        $formulaString = $formulaStrings[0];
5512
5513 10
        return $formulaString;
5514
    }
5515
5516
    /**
5517
     * Fetch next token from binary formula data.
5518
     *
5519
     * @param string $formulaData Formula data
5520
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5521
     *
5522
     * @throws Exception
5523
     *
5524
     * @return array
5525
     */
5526 10
    private function getNextToken($formulaData, $baseCell = 'A1')
5527
    {
5528
        // offset: 0; size: 1; token id
5529 10
        $id = ord($formulaData[0]); // token id
5530 10
        $name = false; // initialize token name
5531
5532
        switch ($id) {
5533 10
            case 0x03:
5534 1
                $name = 'tAdd';
5535 1
                $size = 1;
5536 1
                $data = '+';
5537
5538 1
                break;
5539 10
            case 0x04:
5540
                $name = 'tSub';
5541
                $size = 1;
5542
                $data = '-';
5543
5544
                break;
5545 10
            case 0x05:
5546 2
                $name = 'tMul';
5547 2
                $size = 1;
5548 2
                $data = '*';
5549
5550 2
                break;
5551 10
            case 0x06:
5552 8
                $name = 'tDiv';
5553 8
                $size = 1;
5554 8
                $data = '/';
5555
5556 8
                break;
5557 10
            case 0x07:
5558
                $name = 'tPower';
5559
                $size = 1;
5560
                $data = '^';
5561
5562
                break;
5563 10
            case 0x08:
5564
                $name = 'tConcat';
5565
                $size = 1;
5566
                $data = '&';
5567
5568
                break;
5569 10
            case 0x09:
5570
                $name = 'tLT';
5571
                $size = 1;
5572
                $data = '<';
5573
5574
                break;
5575 10
            case 0x0A:
5576
                $name = 'tLE';
5577
                $size = 1;
5578
                $data = '<=';
5579
5580
                break;
5581 10
            case 0x0B:
5582
                $name = 'tEQ';
5583
                $size = 1;
5584
                $data = '=';
5585
5586
                break;
5587 10
            case 0x0C:
5588
                $name = 'tGE';
5589
                $size = 1;
5590
                $data = '>=';
5591
5592
                break;
5593 10
            case 0x0D:
5594
                $name = 'tGT';
5595
                $size = 1;
5596
                $data = '>';
5597
5598
                break;
5599 10
            case 0x0E:
5600 1
                $name = 'tNE';
5601 1
                $size = 1;
5602 1
                $data = '<>';
5603
5604 1
                break;
5605 10
            case 0x0F:
5606
                $name = 'tIsect';
5607
                $size = 1;
5608
                $data = ' ';
5609
5610
                break;
5611 10
            case 0x10:
5612
                $name = 'tList';
5613
                $size = 1;
5614
                $data = ',';
5615
5616
                break;
5617 10
            case 0x11:
5618
                $name = 'tRange';
5619
                $size = 1;
5620
                $data = ':';
5621
5622
                break;
5623 10
            case 0x12:
5624
                $name = 'tUplus';
5625
                $size = 1;
5626
                $data = '+';
5627
5628
                break;
5629 10
            case 0x13:
5630
                $name = 'tUminus';
5631
                $size = 1;
5632
                $data = '-';
5633
5634
                break;
5635 10
            case 0x14:
5636
                $name = 'tPercent';
5637
                $size = 1;
5638
                $data = '%';
5639
5640
                break;
5641 10
            case 0x15:    //    parenthesis
5642
                $name = 'tParen';
5643
                $size = 1;
5644
                $data = null;
5645
5646
                break;
5647 10
            case 0x16:    //    missing argument
5648
                $name = 'tMissArg';
5649
                $size = 1;
5650
                $data = '';
5651
5652
                break;
5653 10
            case 0x17:    //    string
5654 1
                $name = 'tStr';
5655
                // offset: 1; size: var; Unicode string, 8-bit string length
5656 1
                $string = self::readUnicodeStringShort(substr($formulaData, 1));
5657 1
                $size = 1 + $string['size'];
5658 1
                $data = self::UTF8toExcelDoubleQuoted($string['value']);
5659
5660 1
                break;
5661 10
            case 0x19:    //    Special attribute
5662
                // offset: 1; size: 1; attribute type flags:
5663 9
                switch (ord($formulaData[1])) {
5664 9
                    case 0x01:
5665
                        $name = 'tAttrVolatile';
5666
                        $size = 4;
5667
                        $data = null;
5668
5669
                        break;
5670 9
                    case 0x02:
5671
                        $name = 'tAttrIf';
5672
                        $size = 4;
5673
                        $data = null;
5674
5675
                        break;
5676 9
                    case 0x04:
5677
                        $name = 'tAttrChoose';
5678
                        // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1)
5679
                        $nc = self::getUInt2d($formulaData, 2);
5680
                        // offset: 4; size: 2 * $nc
5681
                        // offset: 4 + 2 * $nc; size: 2
5682
                        $size = 2 * $nc + 6;
5683
                        $data = null;
5684
5685
                        break;
5686 9
                    case 0x08:
5687
                        $name = 'tAttrSkip';
5688
                        $size = 4;
5689
                        $data = null;
5690
5691
                        break;
5692 9
                    case 0x10:
5693 9
                        $name = 'tAttrSum';
5694 9
                        $size = 4;
5695 9
                        $data = null;
5696
5697 9
                        break;
5698
                    case 0x40:
5699
                    case 0x41:
5700
                        $name = 'tAttrSpace';
5701
                        $size = 4;
5702
                        // offset: 2; size: 2; space type and position
5703
                        switch (ord($formulaData[2])) {
5704
                            case 0x00:
5705
                                $spacetype = 'type0';
5706
5707
                                break;
5708
                            case 0x01:
5709
                                $spacetype = 'type1';
5710
5711
                                break;
5712
                            case 0x02:
5713
                                $spacetype = 'type2';
5714
5715
                                break;
5716
                            case 0x03:
5717
                                $spacetype = 'type3';
5718
5719
                                break;
5720
                            case 0x04:
5721
                                $spacetype = 'type4';
5722
5723
                                break;
5724
                            case 0x05:
5725
                                $spacetype = 'type5';
5726
5727
                                break;
5728
                            default:
5729
                                throw new Exception('Unrecognized space type in tAttrSpace token');
5730
5731
                                break;
5732
                        }
5733
                        // offset: 3; size: 1; number of inserted spaces/carriage returns
5734
                        $spacecount = ord($formulaData[3]);
5735
5736
                        $data = ['spacetype' => $spacetype, 'spacecount' => $spacecount];
5737
5738
                        break;
5739
                    default:
5740
                        throw new Exception('Unrecognized attribute flag in tAttr token');
5741
5742
                        break;
5743
                }
5744
5745 9
                break;
5746 10
            case 0x1C:    //    error code
5747
                // offset: 1; size: 1; error code
5748
                $name = 'tErr';
5749
                $size = 2;
5750
                $data = Xls\ErrorCode::lookup(ord($formulaData[1]));
5751
5752
                break;
5753 10
            case 0x1D:    //    boolean
5754
                // offset: 1; size: 1; 0 = false, 1 = true;
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
5755
                $name = 'tBool';
5756
                $size = 2;
5757
                $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE';
5758
5759
                break;
5760 10
            case 0x1E:    //    integer
5761
                // offset: 1; size: 2; unsigned 16-bit integer
5762 8
                $name = 'tInt';
5763 8
                $size = 3;
5764 8
                $data = self::getUInt2d($formulaData, 1);
5765
5766 8
                break;
5767 10
            case 0x1F:    //    number
5768
                // offset: 1; size: 8;
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
5769 2
                $name = 'tNum';
5770 2
                $size = 9;
5771 2
                $data = self::extractNumber(substr($formulaData, 1));
5772 2
                $data = str_replace(',', '.', (string) $data); // in case non-English locale
5773 2
                break;
5774 10
            case 0x20:    //    array constant
5775 10
            case 0x40:
5776 10
            case 0x60:
5777
                // offset: 1; size: 7; not used
5778
                $name = 'tArray';
5779
                $size = 8;
5780
                $data = null;
5781
5782
                break;
5783 10
            case 0x21:    //    function with fixed number of arguments
5784 10
            case 0x41:
5785 10
            case 0x61:
5786 8
                $name = 'tFunc';
5787 8
                $size = 3;
5788
                // offset: 1; size: 2; index to built-in sheet function
5789 8
                switch (self::getUInt2d($formulaData, 1)) {
5790 8
                    case 2:
5791
                        $function = 'ISNA';
5792
                        $args = 1;
5793
5794
                        break;
5795 8
                    case 3:
5796
                        $function = 'ISERROR';
5797
                        $args = 1;
5798
5799
                        break;
5800 8
                    case 10:
5801 8
                        $function = 'NA';
5802 8
                        $args = 0;
5803
5804 8
                        break;
5805
                    case 15:
5806
                        $function = 'SIN';
5807
                        $args = 1;
5808
5809
                        break;
5810
                    case 16:
5811
                        $function = 'COS';
5812
                        $args = 1;
5813
5814
                        break;
5815
                    case 17:
5816
                        $function = 'TAN';
5817
                        $args = 1;
5818
5819
                        break;
5820
                    case 18:
5821
                        $function = 'ATAN';
5822
                        $args = 1;
5823
5824
                        break;
5825
                    case 19:
5826
                        $function = 'PI';
5827
                        $args = 0;
5828
5829
                        break;
5830
                    case 20:
5831
                        $function = 'SQRT';
5832
                        $args = 1;
5833
5834
                        break;
5835
                    case 21:
5836
                        $function = 'EXP';
5837
                        $args = 1;
5838
5839
                        break;
5840
                    case 22:
5841
                        $function = 'LN';
5842
                        $args = 1;
5843
5844
                        break;
5845
                    case 23:
5846
                        $function = 'LOG10';
5847
                        $args = 1;
5848
5849
                        break;
5850
                    case 24:
5851
                        $function = 'ABS';
5852
                        $args = 1;
5853
5854
                        break;
5855
                    case 25:
5856
                        $function = 'INT';
5857
                        $args = 1;
5858
5859
                        break;
5860
                    case 26:
5861
                        $function = 'SIGN';
5862
                        $args = 1;
5863
5864
                        break;
5865
                    case 27:
5866
                        $function = 'ROUND';
5867
                        $args = 2;
5868
5869
                        break;
5870
                    case 30:
5871
                        $function = 'REPT';
5872
                        $args = 2;
5873
5874
                        break;
5875
                    case 31:
5876
                        $function = 'MID';
5877
                        $args = 3;
5878
5879
                        break;
5880
                    case 32:
5881
                        $function = 'LEN';
5882
                        $args = 1;
5883
5884
                        break;
5885
                    case 33:
5886
                        $function = 'VALUE';
5887
                        $args = 1;
5888
5889
                        break;
5890
                    case 34:
5891
                        $function = 'TRUE';
5892
                        $args = 0;
5893
5894
                        break;
5895
                    case 35:
5896
                        $function = 'FALSE';
5897
                        $args = 0;
5898
5899
                        break;
5900
                    case 38:
5901
                        $function = 'NOT';
5902
                        $args = 1;
5903
5904
                        break;
5905
                    case 39:
5906
                        $function = 'MOD';
5907
                        $args = 2;
5908
5909
                        break;
5910
                    case 40:
5911
                        $function = 'DCOUNT';
5912
                        $args = 3;
5913
5914
                        break;
5915
                    case 41:
5916
                        $function = 'DSUM';
5917
                        $args = 3;
5918
5919
                        break;
5920
                    case 42:
5921
                        $function = 'DAVERAGE';
5922
                        $args = 3;
5923
5924
                        break;
5925
                    case 43:
5926
                        $function = 'DMIN';
5927
                        $args = 3;
5928
5929
                        break;
5930
                    case 44:
5931
                        $function = 'DMAX';
5932
                        $args = 3;
5933
5934
                        break;
5935
                    case 45:
5936
                        $function = 'DSTDEV';
5937
                        $args = 3;
5938
5939
                        break;
5940
                    case 48:
5941
                        $function = 'TEXT';
5942
                        $args = 2;
5943
5944
                        break;
5945
                    case 61:
5946
                        $function = 'MIRR';
5947
                        $args = 3;
5948
5949
                        break;
5950
                    case 63:
5951
                        $function = 'RAND';
5952
                        $args = 0;
5953
5954
                        break;
5955
                    case 65:
5956
                        $function = 'DATE';
5957
                        $args = 3;
5958
5959
                        break;
5960
                    case 66:
5961
                        $function = 'TIME';
5962
                        $args = 3;
5963
5964
                        break;
5965
                    case 67:
5966
                        $function = 'DAY';
5967
                        $args = 1;
5968
5969
                        break;
5970
                    case 68:
5971
                        $function = 'MONTH';
5972
                        $args = 1;
5973
5974
                        break;
5975
                    case 69:
5976
                        $function = 'YEAR';
5977
                        $args = 1;
5978
5979
                        break;
5980
                    case 71:
5981
                        $function = 'HOUR';
5982
                        $args = 1;
5983
5984
                        break;
5985
                    case 72:
5986
                        $function = 'MINUTE';
5987
                        $args = 1;
5988
5989
                        break;
5990
                    case 73:
5991
                        $function = 'SECOND';
5992
                        $args = 1;
5993
5994
                        break;
5995
                    case 74:
5996
                        $function = 'NOW';
5997
                        $args = 0;
5998
5999
                        break;
6000
                    case 75:
6001
                        $function = 'AREAS';
6002
                        $args = 1;
6003
6004
                        break;
6005
                    case 76:
6006
                        $function = 'ROWS';
6007
                        $args = 1;
6008
6009
                        break;
6010
                    case 77:
6011
                        $function = 'COLUMNS';
6012
                        $args = 1;
6013
6014
                        break;
6015
                    case 83:
6016
                        $function = 'TRANSPOSE';
6017
                        $args = 1;
6018
6019
                        break;
6020
                    case 86:
6021
                        $function = 'TYPE';
6022
                        $args = 1;
6023
6024
                        break;
6025
                    case 97:
6026
                        $function = 'ATAN2';
6027
                        $args = 2;
6028
6029
                        break;
6030
                    case 98:
6031
                        $function = 'ASIN';
6032
                        $args = 1;
6033
6034
                        break;
6035
                    case 99:
6036
                        $function = 'ACOS';
6037
                        $args = 1;
6038
6039
                        break;
6040
                    case 105:
6041
                        $function = 'ISREF';
6042
                        $args = 1;
6043
6044
                        break;
6045
                    case 111:
6046
                        $function = 'CHAR';
6047
                        $args = 1;
6048
6049
                        break;
6050
                    case 112:
6051
                        $function = 'LOWER';
6052
                        $args = 1;
6053
6054
                        break;
6055
                    case 113:
6056
                        $function = 'UPPER';
6057
                        $args = 1;
6058
6059
                        break;
6060
                    case 114:
6061
                        $function = 'PROPER';
6062
                        $args = 1;
6063
6064
                        break;
6065
                    case 117:
6066
                        $function = 'EXACT';
6067
                        $args = 2;
6068
6069
                        break;
6070
                    case 118:
6071
                        $function = 'TRIM';
6072
                        $args = 1;
6073
6074
                        break;
6075
                    case 119:
6076
                        $function = 'REPLACE';
6077
                        $args = 4;
6078
6079
                        break;
6080
                    case 121:
6081
                        $function = 'CODE';
6082
                        $args = 1;
6083
6084
                        break;
6085
                    case 126:
6086
                        $function = 'ISERR';
6087
                        $args = 1;
6088
6089
                        break;
6090
                    case 127:
6091
                        $function = 'ISTEXT';
6092
                        $args = 1;
6093
6094
                        break;
6095
                    case 128:
6096
                        $function = 'ISNUMBER';
6097
                        $args = 1;
6098
6099
                        break;
6100
                    case 129:
6101
                        $function = 'ISBLANK';
6102
                        $args = 1;
6103
6104
                        break;
6105
                    case 130:
6106
                        $function = 'T';
6107
                        $args = 1;
6108
6109
                        break;
6110
                    case 131:
6111
                        $function = 'N';
6112
                        $args = 1;
6113
6114
                        break;
6115
                    case 140:
6116
                        $function = 'DATEVALUE';
6117
                        $args = 1;
6118
6119
                        break;
6120
                    case 141:
6121
                        $function = 'TIMEVALUE';
6122
                        $args = 1;
6123
6124
                        break;
6125
                    case 142:
6126
                        $function = 'SLN';
6127
                        $args = 3;
6128
6129
                        break;
6130
                    case 143:
6131
                        $function = 'SYD';
6132
                        $args = 4;
6133
6134
                        break;
6135
                    case 162:
6136
                        $function = 'CLEAN';
6137
                        $args = 1;
6138
6139
                        break;
6140
                    case 163:
6141
                        $function = 'MDETERM';
6142
                        $args = 1;
6143
6144
                        break;
6145
                    case 164:
6146
                        $function = 'MINVERSE';
6147
                        $args = 1;
6148
6149
                        break;
6150
                    case 165:
6151
                        $function = 'MMULT';
6152
                        $args = 2;
6153
6154
                        break;
6155
                    case 184:
6156
                        $function = 'FACT';
6157
                        $args = 1;
6158
6159
                        break;
6160
                    case 189:
6161
                        $function = 'DPRODUCT';
6162
                        $args = 3;
6163
6164
                        break;
6165
                    case 190:
6166
                        $function = 'ISNONTEXT';
6167
                        $args = 1;
6168
6169
                        break;
6170
                    case 195:
6171
                        $function = 'DSTDEVP';
6172
                        $args = 3;
6173
6174
                        break;
6175
                    case 196:
6176
                        $function = 'DVARP';
6177
                        $args = 3;
6178
6179
                        break;
6180
                    case 198:
6181
                        $function = 'ISLOGICAL';
6182
                        $args = 1;
6183
6184
                        break;
6185
                    case 199:
6186
                        $function = 'DCOUNTA';
6187
                        $args = 3;
6188
6189
                        break;
6190
                    case 207:
6191
                        $function = 'REPLACEB';
6192
                        $args = 4;
6193
6194
                        break;
6195
                    case 210:
6196
                        $function = 'MIDB';
6197
                        $args = 3;
6198
6199
                        break;
6200
                    case 211:
6201
                        $function = 'LENB';
6202
                        $args = 1;
6203
6204
                        break;
6205
                    case 212:
6206
                        $function = 'ROUNDUP';
6207
                        $args = 2;
6208
6209
                        break;
6210
                    case 213:
6211
                        $function = 'ROUNDDOWN';
6212
                        $args = 2;
6213
6214
                        break;
6215
                    case 214:
6216
                        $function = 'ASC';
6217
                        $args = 1;
6218
6219
                        break;
6220
                    case 215:
6221
                        $function = 'DBCS';
6222
                        $args = 1;
6223
6224
                        break;
6225
                    case 221:
6226
                        $function = 'TODAY';
6227
                        $args = 0;
6228
6229
                        break;
6230
                    case 229:
6231
                        $function = 'SINH';
6232
                        $args = 1;
6233
6234
                        break;
6235
                    case 230:
6236
                        $function = 'COSH';
6237
                        $args = 1;
6238
6239
                        break;
6240
                    case 231:
6241
                        $function = 'TANH';
6242
                        $args = 1;
6243
6244
                        break;
6245
                    case 232:
6246
                        $function = 'ASINH';
6247
                        $args = 1;
6248
6249
                        break;
6250
                    case 233:
6251
                        $function = 'ACOSH';
6252
                        $args = 1;
6253
6254
                        break;
6255
                    case 234:
6256
                        $function = 'ATANH';
6257
                        $args = 1;
6258
6259
                        break;
6260
                    case 235:
6261
                        $function = 'DGET';
6262
                        $args = 3;
6263
6264
                        break;
6265
                    case 244:
6266
                        $function = 'INFO';
6267
                        $args = 1;
6268
6269
                        break;
6270
                    case 252:
6271
                        $function = 'FREQUENCY';
6272
                        $args = 2;
6273
6274
                        break;
6275
                    case 261:
6276
                        $function = 'ERROR.TYPE';
6277
                        $args = 1;
6278
6279
                        break;
6280
                    case 271:
6281
                        $function = 'GAMMALN';
6282
                        $args = 1;
6283
6284
                        break;
6285
                    case 273:
6286
                        $function = 'BINOMDIST';
6287
                        $args = 4;
6288
6289
                        break;
6290
                    case 274:
6291
                        $function = 'CHIDIST';
6292
                        $args = 2;
6293
6294
                        break;
6295
                    case 275:
6296
                        $function = 'CHIINV';
6297
                        $args = 2;
6298
6299
                        break;
6300
                    case 276:
6301
                        $function = 'COMBIN';
6302
                        $args = 2;
6303
6304
                        break;
6305
                    case 277:
6306
                        $function = 'CONFIDENCE';
6307
                        $args = 3;
6308
6309
                        break;
6310
                    case 278:
6311
                        $function = 'CRITBINOM';
6312
                        $args = 3;
6313
6314
                        break;
6315
                    case 279:
6316
                        $function = 'EVEN';
6317
                        $args = 1;
6318
6319
                        break;
6320
                    case 280:
6321
                        $function = 'EXPONDIST';
6322
                        $args = 3;
6323
6324
                        break;
6325
                    case 281:
6326
                        $function = 'FDIST';
6327
                        $args = 3;
6328
6329
                        break;
6330
                    case 282:
6331
                        $function = 'FINV';
6332
                        $args = 3;
6333
6334
                        break;
6335
                    case 283:
6336
                        $function = 'FISHER';
6337
                        $args = 1;
6338
6339
                        break;
6340
                    case 284:
6341
                        $function = 'FISHERINV';
6342
                        $args = 1;
6343
6344
                        break;
6345
                    case 285:
6346
                        $function = 'FLOOR';
6347
                        $args = 2;
6348
6349
                        break;
6350
                    case 286:
6351
                        $function = 'GAMMADIST';
6352
                        $args = 4;
6353
6354
                        break;
6355
                    case 287:
6356
                        $function = 'GAMMAINV';
6357
                        $args = 3;
6358
6359
                        break;
6360
                    case 288:
6361
                        $function = 'CEILING';
6362
                        $args = 2;
6363
6364
                        break;
6365
                    case 289:
6366
                        $function = 'HYPGEOMDIST';
6367
                        $args = 4;
6368
6369
                        break;
6370
                    case 290:
6371
                        $function = 'LOGNORMDIST';
6372
                        $args = 3;
6373
6374
                        break;
6375
                    case 291:
6376
                        $function = 'LOGINV';
6377
                        $args = 3;
6378
6379
                        break;
6380
                    case 292:
6381
                        $function = 'NEGBINOMDIST';
6382
                        $args = 3;
6383
6384
                        break;
6385
                    case 293:
6386
                        $function = 'NORMDIST';
6387
                        $args = 4;
6388
6389
                        break;
6390
                    case 294:
6391
                        $function = 'NORMSDIST';
6392
                        $args = 1;
6393
6394
                        break;
6395
                    case 295:
6396
                        $function = 'NORMINV';
6397
                        $args = 3;
6398
6399
                        break;
6400
                    case 296:
6401
                        $function = 'NORMSINV';
6402
                        $args = 1;
6403
6404
                        break;
6405
                    case 297:
6406
                        $function = 'STANDARDIZE';
6407
                        $args = 3;
6408
6409
                        break;
6410
                    case 298:
6411
                        $function = 'ODD';
6412
                        $args = 1;
6413
6414
                        break;
6415
                    case 299:
6416
                        $function = 'PERMUT';
6417
                        $args = 2;
6418
6419
                        break;
6420
                    case 300:
6421
                        $function = 'POISSON';
6422
                        $args = 3;
6423
6424
                        break;
6425
                    case 301:
6426
                        $function = 'TDIST';
6427
                        $args = 3;
6428
6429
                        break;
6430
                    case 302:
6431
                        $function = 'WEIBULL';
6432
                        $args = 4;
6433
6434
                        break;
6435
                    case 303:
6436
                        $function = 'SUMXMY2';
6437
                        $args = 2;
6438
6439
                        break;
6440
                    case 304:
6441
                        $function = 'SUMX2MY2';
6442
                        $args = 2;
6443
6444
                        break;
6445
                    case 305:
6446
                        $function = 'SUMX2PY2';
6447
                        $args = 2;
6448
6449
                        break;
6450
                    case 306:
6451
                        $function = 'CHITEST';
6452
                        $args = 2;
6453
6454
                        break;
6455
                    case 307:
6456
                        $function = 'CORREL';
6457
                        $args = 2;
6458
6459
                        break;
6460
                    case 308:
6461
                        $function = 'COVAR';
6462
                        $args = 2;
6463
6464
                        break;
6465
                    case 309:
6466
                        $function = 'FORECAST';
6467
                        $args = 3;
6468
6469
                        break;
6470
                    case 310:
6471
                        $function = 'FTEST';
6472
                        $args = 2;
6473
6474
                        break;
6475
                    case 311:
6476
                        $function = 'INTERCEPT';
6477
                        $args = 2;
6478
6479
                        break;
6480
                    case 312:
6481
                        $function = 'PEARSON';
6482
                        $args = 2;
6483
6484
                        break;
6485
                    case 313:
6486
                        $function = 'RSQ';
6487
                        $args = 2;
6488
6489
                        break;
6490
                    case 314:
6491
                        $function = 'STEYX';
6492
                        $args = 2;
6493
6494
                        break;
6495
                    case 315:
6496
                        $function = 'SLOPE';
6497
                        $args = 2;
6498
6499
                        break;
6500
                    case 316:
6501
                        $function = 'TTEST';
6502
                        $args = 4;
6503
6504
                        break;
6505
                    case 325:
6506
                        $function = 'LARGE';
6507
                        $args = 2;
6508
6509
                        break;
6510
                    case 326:
6511
                        $function = 'SMALL';
6512
                        $args = 2;
6513
6514
                        break;
6515
                    case 327:
6516
                        $function = 'QUARTILE';
6517
                        $args = 2;
6518
6519
                        break;
6520
                    case 328:
6521
                        $function = 'PERCENTILE';
6522
                        $args = 2;
6523
6524
                        break;
6525
                    case 331:
6526
                        $function = 'TRIMMEAN';
6527
                        $args = 2;
6528
6529
                        break;
6530
                    case 332:
6531
                        $function = 'TINV';
6532
                        $args = 2;
6533
6534
                        break;
6535
                    case 337:
6536
                        $function = 'POWER';
6537
                        $args = 2;
6538
6539
                        break;
6540
                    case 342:
6541
                        $function = 'RADIANS';
6542
                        $args = 1;
6543
6544
                        break;
6545
                    case 343:
6546
                        $function = 'DEGREES';
6547
                        $args = 1;
6548
6549
                        break;
6550
                    case 346:
6551
                        $function = 'COUNTIF';
6552
                        $args = 2;
6553
6554
                        break;
6555
                    case 347:
6556
                        $function = 'COUNTBLANK';
6557
                        $args = 1;
6558
6559
                        break;
6560
                    case 350:
6561
                        $function = 'ISPMT';
6562
                        $args = 4;
6563
6564
                        break;
6565
                    case 351:
6566
                        $function = 'DATEDIF';
6567
                        $args = 3;
6568
6569
                        break;
6570
                    case 352:
6571
                        $function = 'DATESTRING';
6572
                        $args = 1;
6573
6574
                        break;
6575
                    case 353:
6576
                        $function = 'NUMBERSTRING';
6577
                        $args = 2;
6578
6579
                        break;
6580
                    case 360:
6581
                        $function = 'PHONETIC';
6582
                        $args = 1;
6583
6584
                        break;
6585
                    case 368:
6586
                        $function = 'BAHTTEXT';
6587
                        $args = 1;
6588
6589
                        break;
6590
                    default:
6591
                        throw new Exception('Unrecognized function in formula');
6592
6593
                        break;
6594
                }
6595 8
                $data = ['function' => $function, 'args' => $args];
6596
6597 8
                break;
6598 10
            case 0x22:    //    function with variable number of arguments
6599 10
            case 0x42:
6600 10
            case 0x62:
6601 1
                $name = 'tFuncV';
6602 1
                $size = 4;
6603
                // offset: 1; size: 1; number of arguments
6604 1
                $args = ord($formulaData[1]);
6605
                // offset: 2: size: 2; index to built-in sheet function
6606 1
                $index = self::getUInt2d($formulaData, 2);
6607
                switch ($index) {
6608 1
                    case 0:
6609
                        $function = 'COUNT';
6610
6611
                        break;
6612 1
                    case 1:
6613 1
                        $function = 'IF';
6614
6615 1
                        break;
6616 1
                    case 4:
6617 1
                        $function = 'SUM';
6618
6619 1
                        break;
6620
                    case 5:
6621
                        $function = 'AVERAGE';
6622
6623
                        break;
6624
                    case 6:
6625
                        $function = 'MIN';
6626
6627
                        break;
6628
                    case 7:
6629
                        $function = 'MAX';
6630
6631
                        break;
6632
                    case 8:
6633
                        $function = 'ROW';
6634
6635
                        break;
6636
                    case 9:
6637
                        $function = 'COLUMN';
6638
6639
                        break;
6640
                    case 11:
6641
                        $function = 'NPV';
6642
6643
                        break;
6644
                    case 12:
6645
                        $function = 'STDEV';
6646
6647
                        break;
6648
                    case 13:
6649
                        $function = 'DOLLAR';
6650
6651
                        break;
6652
                    case 14:
6653
                        $function = 'FIXED';
6654
6655
                        break;
6656
                    case 28:
6657
                        $function = 'LOOKUP';
6658
6659
                        break;
6660
                    case 29:
6661
                        $function = 'INDEX';
6662
6663
                        break;
6664
                    case 36:
6665
                        $function = 'AND';
6666
6667
                        break;
6668
                    case 37:
6669
                        $function = 'OR';
6670
6671
                        break;
6672
                    case 46:
6673
                        $function = 'VAR';
6674
6675
                        break;
6676
                    case 49:
6677
                        $function = 'LINEST';
6678
6679
                        break;
6680
                    case 50:
6681
                        $function = 'TREND';
6682
6683
                        break;
6684
                    case 51:
6685
                        $function = 'LOGEST';
6686
6687
                        break;
6688
                    case 52:
6689
                        $function = 'GROWTH';
6690
6691
                        break;
6692
                    case 56:
6693
                        $function = 'PV';
6694
6695
                        break;
6696
                    case 57:
6697
                        $function = 'FV';
6698
6699
                        break;
6700
                    case 58:
6701
                        $function = 'NPER';
6702
6703
                        break;
6704
                    case 59:
6705
                        $function = 'PMT';
6706
6707
                        break;
6708
                    case 60:
6709
                        $function = 'RATE';
6710
6711
                        break;
6712
                    case 62:
6713
                        $function = 'IRR';
6714
6715
                        break;
6716
                    case 64:
6717
                        $function = 'MATCH';
6718
6719
                        break;
6720
                    case 70:
6721
                        $function = 'WEEKDAY';
6722
6723
                        break;
6724
                    case 78:
6725
                        $function = 'OFFSET';
6726
6727
                        break;
6728
                    case 82:
6729
                        $function = 'SEARCH';
6730
6731
                        break;
6732
                    case 100:
6733
                        $function = 'CHOOSE';
6734
6735
                        break;
6736
                    case 101:
6737
                        $function = 'HLOOKUP';
6738
6739
                        break;
6740
                    case 102:
6741
                        $function = 'VLOOKUP';
6742
6743
                        break;
6744
                    case 109:
6745
                        $function = 'LOG';
6746
6747
                        break;
6748
                    case 115:
6749
                        $function = 'LEFT';
6750
6751
                        break;
6752
                    case 116:
6753
                        $function = 'RIGHT';
6754
6755
                        break;
6756
                    case 120:
6757
                        $function = 'SUBSTITUTE';
6758
6759
                        break;
6760
                    case 124:
6761
                        $function = 'FIND';
6762
6763
                        break;
6764
                    case 125:
6765
                        $function = 'CELL';
6766
6767
                        break;
6768
                    case 144:
6769
                        $function = 'DDB';
6770
6771
                        break;
6772
                    case 148:
6773
                        $function = 'INDIRECT';
6774
6775
                        break;
6776
                    case 167:
6777
                        $function = 'IPMT';
6778
6779
                        break;
6780
                    case 168:
6781
                        $function = 'PPMT';
6782
6783
                        break;
6784
                    case 169:
6785
                        $function = 'COUNTA';
6786
6787
                        break;
6788
                    case 183:
6789
                        $function = 'PRODUCT';
6790
6791
                        break;
6792
                    case 193:
6793
                        $function = 'STDEVP';
6794
6795
                        break;
6796
                    case 194:
6797
                        $function = 'VARP';
6798
6799
                        break;
6800
                    case 197:
6801
                        $function = 'TRUNC';
6802
6803
                        break;
6804
                    case 204:
6805
                        $function = 'USDOLLAR';
6806
6807
                        break;
6808
                    case 205:
6809
                        $function = 'FINDB';
6810
6811
                        break;
6812
                    case 206:
6813
                        $function = 'SEARCHB';
6814
6815
                        break;
6816
                    case 208:
6817
                        $function = 'LEFTB';
6818
6819
                        break;
6820
                    case 209:
6821
                        $function = 'RIGHTB';
6822
6823
                        break;
6824
                    case 216:
6825
                        $function = 'RANK';
6826
6827
                        break;
6828
                    case 219:
6829
                        $function = 'ADDRESS';
6830
6831
                        break;
6832
                    case 220:
6833
                        $function = 'DAYS360';
6834
6835
                        break;
6836
                    case 222:
6837
                        $function = 'VDB';
6838
6839
                        break;
6840
                    case 227:
6841
                        $function = 'MEDIAN';
6842
6843
                        break;
6844
                    case 228:
6845
                        $function = 'SUMPRODUCT';
6846
6847
                        break;
6848
                    case 247:
6849
                        $function = 'DB';
6850
6851
                        break;
6852
                    case 255:
6853
                        $function = '';
6854
6855
                        break;
6856
                    case 269:
6857
                        $function = 'AVEDEV';
6858
6859
                        break;
6860
                    case 270:
6861
                        $function = 'BETADIST';
6862
6863
                        break;
6864
                    case 272:
6865
                        $function = 'BETAINV';
6866
6867
                        break;
6868
                    case 317:
6869
                        $function = 'PROB';
6870
6871
                        break;
6872
                    case 318:
6873
                        $function = 'DEVSQ';
6874
6875
                        break;
6876
                    case 319:
6877
                        $function = 'GEOMEAN';
6878
6879
                        break;
6880
                    case 320:
6881
                        $function = 'HARMEAN';
6882
6883
                        break;
6884
                    case 321:
6885
                        $function = 'SUMSQ';
6886
6887
                        break;
6888
                    case 322:
6889
                        $function = 'KURT';
6890
6891
                        break;
6892
                    case 323:
6893
                        $function = 'SKEW';
6894
6895
                        break;
6896
                    case 324:
6897
                        $function = 'ZTEST';
6898
6899
                        break;
6900
                    case 329:
6901
                        $function = 'PERCENTRANK';
6902
6903
                        break;
6904
                    case 330:
6905
                        $function = 'MODE';
6906
6907
                        break;
6908
                    case 336:
6909
                        $function = 'CONCATENATE';
6910
6911
                        break;
6912
                    case 344:
6913
                        $function = 'SUBTOTAL';
6914
6915
                        break;
6916
                    case 345:
6917
                        $function = 'SUMIF';
6918
6919
                        break;
6920
                    case 354:
6921
                        $function = 'ROMAN';
6922
6923
                        break;
6924
                    case 358:
6925
                        $function = 'GETPIVOTDATA';
6926
6927
                        break;
6928
                    case 359:
6929
                        $function = 'HYPERLINK';
6930
6931
                        break;
6932
                    case 361:
6933
                        $function = 'AVERAGEA';
6934
6935
                        break;
6936
                    case 362:
6937
                        $function = 'MAXA';
6938
6939
                        break;
6940
                    case 363:
6941
                        $function = 'MINA';
6942
6943
                        break;
6944
                    case 364:
6945
                        $function = 'STDEVPA';
6946
6947
                        break;
6948
                    case 365:
6949
                        $function = 'VARPA';
6950
6951
                        break;
6952
                    case 366:
6953
                        $function = 'STDEVA';
6954
6955
                        break;
6956
                    case 367:
6957
                        $function = 'VARA';
6958
6959
                        break;
6960
                    default:
6961
                        throw new Exception('Unrecognized function in formula');
6962
6963
                        break;
6964
                }
6965 1
                $data = ['function' => $function, 'args' => $args];
6966
6967 1
                break;
6968 10
            case 0x23:    //    index to defined name
6969 10
            case 0x43:
6970 10
            case 0x63:
6971
                $name = 'tName';
6972
                $size = 5;
6973
                // offset: 1; size: 2; one-based index to definedname record
6974
                $definedNameIndex = self::getUInt2d($formulaData, 1) - 1;
6975
                // offset: 2; size: 2; not used
6976
                $data = $this->definedname[$definedNameIndex]['name'];
6977
6978
                break;
6979 10
            case 0x24:    //    single cell reference e.g. A5
6980 10
            case 0x44:
6981 10
            case 0x64:
6982 2
                $name = 'tRef';
6983 2
                $size = 5;
6984 2
                $data = $this->readBIFF8CellAddress(substr($formulaData, 1, 4));
6985
6986 2
                break;
6987 10
            case 0x25:    //    cell range reference to cells in the same sheet (2d)
6988 1
            case 0x45:
6989 1
            case 0x65:
6990 10
                $name = 'tArea';
6991 10
                $size = 9;
6992 10
                $data = $this->readBIFF8CellRangeAddress(substr($formulaData, 1, 8));
6993
6994 10
                break;
6995 1
            case 0x26:    //    Constant reference sub-expression
6996 1
            case 0x46:
6997 1
            case 0x66:
6998
                $name = 'tMemArea';
6999
                // offset: 1; size: 4; not used
7000
                // offset: 5; size: 2; size of the following subexpression
7001
                $subSize = self::getUInt2d($formulaData, 5);
7002
                $size = 7 + $subSize;
7003
                $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
7004
7005
                break;
7006 1
            case 0x27:    //    Deleted constant reference sub-expression
7007 1
            case 0x47:
7008 1
            case 0x67:
7009
                $name = 'tMemErr';
7010
                // offset: 1; size: 4; not used
7011
                // offset: 5; size: 2; size of the following subexpression
7012
                $subSize = self::getUInt2d($formulaData, 5);
7013
                $size = 7 + $subSize;
7014
                $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
7015
7016
                break;
7017 1
            case 0x29:    //    Variable reference sub-expression
7018 1
            case 0x49:
7019 1
            case 0x69:
7020
                $name = 'tMemFunc';
7021
                // offset: 1; size: 2; size of the following sub-expression
7022
                $subSize = self::getUInt2d($formulaData, 1);
7023
                $size = 3 + $subSize;
7024
                $data = $this->getFormulaFromData(substr($formulaData, 3, $subSize));
7025
7026
                break;
7027 1
            case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places
7028 1
            case 0x4C:
7029 1
            case 0x6C:
7030
                $name = 'tRefN';
7031
                $size = 5;
7032
                $data = $this->readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell);
7033
7034
                break;
7035 1
            case 0x2D:    //    Relative 2d range reference
7036 1
            case 0x4D:
7037 1
            case 0x6D:
7038
                $name = 'tAreaN';
7039
                $size = 9;
7040
                $data = $this->readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell);
7041
7042
                break;
7043 1
            case 0x39:    //    External name
7044 1
            case 0x59:
7045 1
            case 0x79:
7046
                $name = 'tNameX';
7047
                $size = 7;
7048
                // offset: 1; size: 2; index to REF entry in EXTERNSHEET record
7049
                // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record
7050
                $index = self::getUInt2d($formulaData, 3);
7051
                // assume index is to EXTERNNAME record
7052
                $data = $this->externalNames[$index - 1]['name'];
7053
                // offset: 5; size: 2; not used
7054
                break;
7055 1
            case 0x3A:    //    3d reference to cell
7056 1
            case 0x5A:
7057 1
            case 0x7A:
7058
                $name = 'tRef3d';
7059
                $size = 7;
7060
7061
                try {
7062
                    // offset: 1; size: 2; index to REF entry
7063
                    $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1));
7064
                    // offset: 3; size: 4; cell address
7065
                    $cellAddress = $this->readBIFF8CellAddress(substr($formulaData, 3, 4));
7066
7067
                    $data = "$sheetRange!$cellAddress";
7068
                } catch (PhpSpreadsheetException $e) {
7069
                    // deleted sheet reference
7070
                    $data = '#REF!';
7071
                }
7072
7073
                break;
7074 1
            case 0x3B:    //    3d reference to cell range
7075
            case 0x5B:
7076
            case 0x7B:
7077 1
                $name = 'tArea3d';
7078 1
                $size = 11;
7079
7080
                try {
7081
                    // offset: 1; size: 2; index to REF entry
7082 1
                    $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1));
7083
                    // offset: 3; size: 8; cell address
7084 1
                    $cellRangeAddress = $this->readBIFF8CellRangeAddress(substr($formulaData, 3, 8));
7085
7086 1
                    $data = "$sheetRange!$cellRangeAddress";
7087
                } catch (PhpSpreadsheetException $e) {
7088
                    // deleted sheet reference
7089
                    $data = '#REF!';
7090
                }
7091
7092 1
                break;
7093
            // Unknown cases    // don't know how to deal with
7094
            default:
7095
                throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula');
7096
7097
                break;
7098
        }
7099
7100
        return [
7101 10
            'id' => $id,
7102 10
            'name' => $name,
7103 10
            'size' => $size,
7104 10
            'data' => $data,
7105
        ];
7106
    }
7107
7108
    /**
7109
     * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2'
7110
     * section 3.3.4.
7111
     *
7112
     * @param string $cellAddressStructure
7113
     *
7114
     * @return string
7115
     */
7116 2
    private function readBIFF8CellAddress($cellAddressStructure)
7117
    {
7118
        // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7119 2
        $row = self::getUInt2d($cellAddressStructure, 0) + 1;
7120
7121
        // offset: 2; size: 2; index to column or column offset + relative flags
7122
        // bit: 7-0; mask 0x00FF; column index
7123 2
        $column = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($cellAddressStructure, 2)) + 1);
7124
7125
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7126 2
        if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) {
7127 1
            $column = '$' . $column;
7128
        }
7129
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7130 2
        if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
7131 1
            $row = '$' . $row;
7132
        }
7133
7134 2
        return $column . $row;
7135
    }
7136
7137
    /**
7138
     * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column
7139
     * to indicate offsets from a base cell
7140
     * section 3.3.4.
7141
     *
7142
     * @param string $cellAddressStructure
7143
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
7144
     *
7145
     * @return string
7146
     */
7147
    private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1')
7148
    {
7149
        list($baseCol, $baseRow) = Coordinate::coordinateFromString($baseCell);
7150
        $baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
7151
7152
        // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7153
        $rowIndex = self::getUInt2d($cellAddressStructure, 0);
7154
        $row = self::getUInt2d($cellAddressStructure, 0) + 1;
7155
7156
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7157
        if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) {
7158
            // offset: 2; size: 2; index to column or column offset + relative flags
7159
            // bit: 7-0; mask 0x00FF; column index
7160
            $colIndex = 0x00FF & self::getUInt2d($cellAddressStructure, 2);
7161
7162
            $column = Coordinate::stringFromColumnIndex($colIndex + 1);
7163
            $column = '$' . $column;
7164
        } else {
7165
            // offset: 2; size: 2; index to column or column offset + relative flags
7166
            // bit: 7-0; mask 0x00FF; column index
7167
            $relativeColIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2);
7168
            $colIndex = $baseCol + $relativeColIndex;
7169
            $colIndex = ($colIndex < 256) ? $colIndex : $colIndex - 256;
7170
            $colIndex = ($colIndex >= 0) ? $colIndex : $colIndex + 256;
7171
            $column = Coordinate::stringFromColumnIndex($colIndex + 1);
7172
        }
7173
7174
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7175
        if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
7176
            $row = '$' . $row;
7177
        } else {
7178
            $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536;
7179
            $row = $baseRow + $rowIndex;
7180
        }
7181
7182
        return $column . $row;
7183
    }
7184
7185
    /**
7186
     * Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1'
7187
     * always fixed range
7188
     * section 2.5.14.
7189
     *
7190
     * @param string $subData
7191
     *
7192
     * @throws Exception
7193
     *
7194
     * @return string
7195
     */
7196 20
    private function readBIFF5CellRangeAddressFixed($subData)
7197
    {
7198
        // offset: 0; size: 2; index to first row
7199 20
        $fr = self::getUInt2d($subData, 0) + 1;
7200
7201
        // offset: 2; size: 2; index to last row
7202 20
        $lr = self::getUInt2d($subData, 2) + 1;
7203
7204
        // offset: 4; size: 1; index to first column
7205 20
        $fc = ord($subData[4]);
7206
7207
        // offset: 5; size: 1; index to last column
7208 20
        $lc = ord($subData[5]);
7209
7210
        // check values
7211 20
        if ($fr > $lr || $fc > $lc) {
7212
            throw new Exception('Not a cell range address');
7213
        }
7214
7215
        // column index to letter
7216 20
        $fc = Coordinate::stringFromColumnIndex($fc + 1);
7217 20
        $lc = Coordinate::stringFromColumnIndex($lc + 1);
7218
7219 20
        if ($fr == $lr and $fc == $lc) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
7220 16
            return "$fc$fr";
7221
        }
7222
7223 12
        return "$fc$fr:$lc$lr";
7224
    }
7225
7226
    /**
7227
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1'
7228
     * always fixed range
7229
     * section 2.5.14.
7230
     *
7231
     * @param string $subData
7232
     *
7233
     * @throws Exception
7234
     *
7235
     * @return string
7236
     */
7237 12
    private function readBIFF8CellRangeAddressFixed($subData)
7238
    {
7239
        // offset: 0; size: 2; index to first row
7240 12
        $fr = self::getUInt2d($subData, 0) + 1;
7241
7242
        // offset: 2; size: 2; index to last row
7243 12
        $lr = self::getUInt2d($subData, 2) + 1;
7244
7245
        // offset: 4; size: 2; index to first column
7246 12
        $fc = self::getUInt2d($subData, 4);
7247
7248
        // offset: 6; size: 2; index to last column
7249 12
        $lc = self::getUInt2d($subData, 6);
7250
7251
        // check values
7252 12
        if ($fr > $lr || $fc > $lc) {
7253
            throw new Exception('Not a cell range address');
7254
        }
7255
7256
        // column index to letter
7257 12
        $fc = Coordinate::stringFromColumnIndex($fc + 1);
7258 12
        $lc = Coordinate::stringFromColumnIndex($lc + 1);
7259
7260 12
        if ($fr == $lr and $fc == $lc) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
7261 2
            return "$fc$fr";
7262
        }
7263
7264 12
        return "$fc$fr:$lc$lr";
7265
    }
7266
7267
    /**
7268
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6'
7269
     * there are flags indicating whether column/row index is relative
7270
     * section 3.3.4.
7271
     *
7272
     * @param string $subData
7273
     *
7274
     * @return string
7275
     */
7276 10
    private function readBIFF8CellRangeAddress($subData)
7277
    {
7278
        // todo: if cell range is just a single cell, should this funciton
7279
        // not just return e.g. 'A1' and not 'A1:A1' ?
7280
7281
        // offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767))
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7282 10
        $fr = self::getUInt2d($subData, 0) + 1;
7283
7284
        // offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767))
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7285 10
        $lr = self::getUInt2d($subData, 2) + 1;
7286
7287
        // offset: 4; size: 2; index to first column or column offset + relative flags
7288
7289
        // bit: 7-0; mask 0x00FF; column index
7290 10
        $fc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 4)) + 1);
7291
7292
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7293 10
        if (!(0x4000 & self::getUInt2d($subData, 4))) {
7294 1
            $fc = '$' . $fc;
7295
        }
7296
7297
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7298 10
        if (!(0x8000 & self::getUInt2d($subData, 4))) {
7299 1
            $fr = '$' . $fr;
7300
        }
7301
7302
        // offset: 6; size: 2; index to last column or column offset + relative flags
7303
7304
        // bit: 7-0; mask 0x00FF; column index
7305 10
        $lc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 6)) + 1);
7306
7307
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7308 10
        if (!(0x4000 & self::getUInt2d($subData, 6))) {
7309 1
            $lc = '$' . $lc;
7310
        }
7311
7312
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7313 10
        if (!(0x8000 & self::getUInt2d($subData, 6))) {
7314 1
            $lr = '$' . $lr;
7315
        }
7316
7317 10
        return "$fc$fr:$lc$lr";
7318
    }
7319
7320
    /**
7321
     * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column
7322
     * to indicate offsets from a base cell
7323
     * section 3.3.4.
7324
     *
7325
     * @param string $subData
7326
     * @param string $baseCell Base cell
7327
     *
7328
     * @return string Cell range address
7329
     */
7330
    private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1')
7331
    {
7332
        list($baseCol, $baseRow) = Coordinate::coordinateFromString($baseCell);
7333
        $baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
7334
7335
        // TODO: if cell range is just a single cell, should this funciton
7336
        // not just return e.g. 'A1' and not 'A1:A1' ?
7337
7338
        // offset: 0; size: 2; first row
7339
        $frIndex = self::getUInt2d($subData, 0); // adjust below
7340
7341
        // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767)
7342
        $lrIndex = self::getUInt2d($subData, 2); // adjust below
7343
7344
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7345
        if (!(0x4000 & self::getUInt2d($subData, 4))) {
7346
            // absolute column index
7347
            // offset: 4; size: 2; first column with relative/absolute flags
7348
            // bit: 7-0; mask 0x00FF; column index
7349
            $fcIndex = 0x00FF & self::getUInt2d($subData, 4);
7350
            $fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
7351
            $fc = '$' . $fc;
7352
        } else {
7353
            // column offset
7354
            // offset: 4; size: 2; first column with relative/absolute flags
7355
            // bit: 7-0; mask 0x00FF; column index
7356
            $relativeFcIndex = 0x00FF & self::getInt2d($subData, 4);
7357
            $fcIndex = $baseCol + $relativeFcIndex;
7358
            $fcIndex = ($fcIndex < 256) ? $fcIndex : $fcIndex - 256;
7359
            $fcIndex = ($fcIndex >= 0) ? $fcIndex : $fcIndex + 256;
7360
            $fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
7361
        }
7362
7363
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7364
        if (!(0x8000 & self::getUInt2d($subData, 4))) {
7365
            // absolute row index
7366
            $fr = $frIndex + 1;
7367
            $fr = '$' . $fr;
7368
        } else {
7369
            // row offset
7370
            $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536;
7371
            $fr = $baseRow + $frIndex;
7372
        }
7373
7374
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7375
        if (!(0x4000 & self::getUInt2d($subData, 6))) {
7376
            // absolute column index
7377
            // offset: 6; size: 2; last column with relative/absolute flags
7378
            // bit: 7-0; mask 0x00FF; column index
7379
            $lcIndex = 0x00FF & self::getUInt2d($subData, 6);
7380
            $lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
7381
            $lc = '$' . $lc;
7382
        } else {
7383
            // column offset
7384
            // offset: 4; size: 2; first column with relative/absolute flags
7385
            // bit: 7-0; mask 0x00FF; column index
7386
            $relativeLcIndex = 0x00FF & self::getInt2d($subData, 4);
7387
            $lcIndex = $baseCol + $relativeLcIndex;
7388
            $lcIndex = ($lcIndex < 256) ? $lcIndex : $lcIndex - 256;
7389
            $lcIndex = ($lcIndex >= 0) ? $lcIndex : $lcIndex + 256;
7390
            $lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
7391
        }
7392
7393
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7394
        if (!(0x8000 & self::getUInt2d($subData, 6))) {
7395
            // absolute row index
7396
            $lr = $lrIndex + 1;
7397
            $lr = '$' . $lr;
7398
        } else {
7399
            // row offset
7400
            $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536;
7401
            $lr = $baseRow + $lrIndex;
7402
        }
7403
7404
        return "$fc$fr:$lc$lr";
7405
    }
7406
7407
    /**
7408
     * Read BIFF8 cell range address list
7409
     * section 2.5.15.
7410
     *
7411
     * @param string $subData
7412
     *
7413
     * @return array
7414
     */
7415 12
    private function readBIFF8CellRangeAddressList($subData)
7416
    {
7417 12
        $cellRangeAddresses = [];
7418
7419
        // offset: 0; size: 2; number of the following cell range addresses
7420 12
        $nm = self::getUInt2d($subData, 0);
7421
7422 12
        $offset = 2;
7423
        // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses
7424 12
        for ($i = 0; $i < $nm; ++$i) {
7425 12
            $cellRangeAddresses[] = $this->readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
7426 12
            $offset += 8;
7427
        }
7428
7429
        return [
7430 12
            'size' => 2 + 8 * $nm,
7431 12
            'cellRangeAddresses' => $cellRangeAddresses,
7432
        ];
7433
    }
7434
7435
    /**
7436
     * Read BIFF5 cell range address list
7437
     * section 2.5.15.
7438
     *
7439
     * @param string $subData
7440
     *
7441
     * @return array
7442
     */
7443 20
    private function readBIFF5CellRangeAddressList($subData)
7444
    {
7445 20
        $cellRangeAddresses = [];
7446
7447
        // offset: 0; size: 2; number of the following cell range addresses
7448 20
        $nm = self::getUInt2d($subData, 0);
7449
7450 20
        $offset = 2;
7451
        // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses
7452 20
        for ($i = 0; $i < $nm; ++$i) {
7453 20
            $cellRangeAddresses[] = $this->readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6));
7454 20
            $offset += 6;
7455
        }
7456
7457
        return [
7458 20
            'size' => 2 + 6 * $nm,
7459 20
            'cellRangeAddresses' => $cellRangeAddresses,
7460
        ];
7461
    }
7462
7463
    /**
7464
     * Get a sheet range like Sheet1:Sheet3 from REF index
7465
     * Note: If there is only one sheet in the range, one gets e.g Sheet1
7466
     * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets,
7467
     * in which case an Exception is thrown.
7468
     *
7469
     * @param int $index
7470
     *
7471
     * @throws Exception
7472
     *
7473
     * @return false|string
7474
     */
7475 1
    private function readSheetRangeByRefIndex($index)
7476
    {
7477 1
        if (isset($this->ref[$index])) {
7478 1
            $type = $this->externalBooks[$this->ref[$index]['externalBookIndex']]['type'];
7479
7480
            switch ($type) {
7481 1
                case 'internal':
7482
                    // check if we have a deleted 3d reference
7483 1
                    if ($this->ref[$index]['firstSheetIndex'] == 0xFFFF or $this->ref[$index]['lastSheetIndex'] == 0xFFFF) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
7484
                        throw new Exception('Deleted sheet reference');
7485
                    }
7486
7487
                    // we have normal sheet range (collapsed or uncollapsed)
7488 1
                    $firstSheetName = $this->sheets[$this->ref[$index]['firstSheetIndex']]['name'];
7489 1
                    $lastSheetName = $this->sheets[$this->ref[$index]['lastSheetIndex']]['name'];
7490
7491 1
                    if ($firstSheetName == $lastSheetName) {
7492
                        // collapsed sheet range
7493 1
                        $sheetRange = $firstSheetName;
7494
                    } else {
7495
                        $sheetRange = "$firstSheetName:$lastSheetName";
7496
                    }
7497
7498
                    // escape the single-quotes
7499 1
                    $sheetRange = str_replace("'", "''", $sheetRange);
7500
7501
                    // if there are special characters, we need to enclose the range in single-quotes
7502
                    // todo: check if we have identified the whole set of special characters
7503
                    // it seems that the following characters are not accepted for sheet names
7504
                    // and we may assume that they are not present: []*/:\?
7505 1
                    if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/u", $sheetRange)) {
7506
                        $sheetRange = "'$sheetRange'";
7507
                    }
7508
7509 1
                    return $sheetRange;
7510
7511
                    break;
7512
                default:
7513
                    // TODO: external sheet support
7514
                    throw new Exception('Xls reader only supports internal sheets in formulas');
7515
7516
                    break;
7517
            }
7518
        }
7519
7520
        return false;
7521
    }
7522
7523
    /**
7524
     * read BIFF8 constant value array from array data
7525
     * returns e.g. array('value' => '{1,2;3,4}', 'size' => 40}
7526
     * section 2.5.8.
7527
     *
7528
     * @param string $arrayData
7529
     *
7530
     * @return array
7531
     */
7532
    private static function readBIFF8ConstantArray($arrayData)
7533
    {
7534
        // offset: 0; size: 1; number of columns decreased by 1
7535
        $nc = ord($arrayData[0]);
7536
7537
        // offset: 1; size: 2; number of rows decreased by 1
7538
        $nr = self::getUInt2d($arrayData, 1);
7539
        $size = 3; // initialize
7540
        $arrayData = substr($arrayData, 3);
7541
7542
        // offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7543
        $matrixChunks = [];
7544
        for ($r = 1; $r <= $nr + 1; ++$r) {
7545
            $items = [];
7546
            for ($c = 1; $c <= $nc + 1; ++$c) {
7547
                $constant = self::readBIFF8Constant($arrayData);
7548
                $items[] = $constant['value'];
7549
                $arrayData = substr($arrayData, $constant['size']);
7550
                $size += $constant['size'];
7551
            }
7552
            $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'
7553
        }
7554
        $matrix = '{' . implode(';', $matrixChunks) . '}';
7555
7556
        return [
7557
            'value' => $matrix,
7558
            'size' => $size,
7559
        ];
7560
    }
7561
7562
    /**
7563
     * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value'
7564
     * section 2.5.7
7565
     * returns e.g. array('value' => '5', 'size' => 9).
7566
     *
7567
     * @param string $valueData
7568
     *
7569
     * @return array
7570
     */
7571
    private static function readBIFF8Constant($valueData)
7572
    {
7573
        // offset: 0; size: 1; identifier for type of constant
7574
        $identifier = ord($valueData[0]);
7575
7576
        switch ($identifier) {
7577
            case 0x00: // empty constant (what is this?)
7578
                $value = '';
7579
                $size = 9;
7580
7581
                break;
7582
            case 0x01: // number
7583
                // offset: 1; size: 8; IEEE 754 floating-point value
7584
                $value = self::extractNumber(substr($valueData, 1, 8));
7585
                $size = 9;
7586
7587
                break;
7588
            case 0x02: // string value
7589
                // offset: 1; size: var; Unicode string, 16-bit string length
7590
                $string = self::readUnicodeStringLong(substr($valueData, 1));
7591
                $value = '"' . $string['value'] . '"';
7592
                $size = 1 + $string['size'];
7593
7594
                break;
7595
            case 0x04: // boolean
7596
                // offset: 1; size: 1; 0 = FALSE, 1 = TRUE
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7597
                if (ord($valueData[1])) {
7598
                    $value = 'TRUE';
7599
                } else {
7600
                    $value = 'FALSE';
7601
                }
7602
                $size = 9;
7603
7604
                break;
7605
            case 0x10: // error code
7606
                // offset: 1; size: 1; error code
7607
                $value = Xls\ErrorCode::lookup(ord($valueData[1]));
7608
                $size = 9;
7609
7610
                break;
7611
        }
7612
7613
        return [
7614
            'value' => $value,
7615
            'size' => $size,
7616
        ];
7617
    }
7618
7619
    /**
7620
     * Extract RGB color
7621
     * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4.
7622
     *
7623
     * @param string $rgb Encoded RGB value (4 bytes)
7624
     *
7625
     * @return array
7626
     */
7627 7
    private static function readRGB($rgb)
7628
    {
7629
        // offset: 0; size 1; Red component
7630 7
        $r = ord($rgb[0]);
7631
7632
        // offset: 1; size: 1; Green component
7633 7
        $g = ord($rgb[1]);
7634
7635
        // offset: 2; size: 1; Blue component
7636 7
        $b = ord($rgb[2]);
7637
7638
        // HEX notation, e.g. 'FF00FC'
7639 7
        $rgb = sprintf('%02X%02X%02X', $r, $g, $b);
7640
7641 7
        return ['rgb' => $rgb];
7642
    }
7643
7644
    /**
7645
     * Read byte string (8-bit string length)
7646
     * OpenOffice documentation: 2.5.2.
7647
     *
7648
     * @param string $subData
7649
     *
7650
     * @return array
7651
     */
7652
    private function readByteStringShort($subData)
7653
    {
7654
        // offset: 0; size: 1; length of the string (character count)
7655
        $ln = ord($subData[0]);
7656
7657
        // offset: 1: size: var; character array (8-bit characters)
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7658
        $value = $this->decodeCodepage(substr($subData, 1, $ln));
7659
7660
        return [
7661
            'value' => $value,
7662
            'size' => 1 + $ln, // size in bytes of data structure
7663
        ];
7664
    }
7665
7666
    /**
7667
     * Read byte string (16-bit string length)
7668
     * OpenOffice documentation: 2.5.2.
7669
     *
7670
     * @param string $subData
7671
     *
7672
     * @return array
7673
     */
7674
    private function readByteStringLong($subData)
7675
    {
7676
        // offset: 0; size: 2; length of the string (character count)
7677
        $ln = self::getUInt2d($subData, 0);
7678
7679
        // offset: 2: size: var; character array (8-bit characters)
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7680
        $value = $this->decodeCodepage(substr($subData, 2));
7681
7682
        //return $string;
7683
        return [
7684
            'value' => $value,
7685
            'size' => 2 + $ln, // size in bytes of data structure
7686
        ];
7687
    }
7688
7689
    /**
7690
     * Extracts an Excel Unicode short string (8-bit string length)
7691
     * OpenOffice documentation: 2.5.3
7692
     * function will automatically find out where the Unicode string ends.
7693
     *
7694
     * @param string $subData
7695
     *
7696
     * @return array
7697
     */
7698 24
    private static function readUnicodeStringShort($subData)
7699
    {
7700 24
        $value = '';
7701
7702
        // offset: 0: size: 1; length of the string (character count)
7703 24
        $characterCount = ord($subData[0]);
7704
7705 24
        $string = self::readUnicodeString(substr($subData, 1), $characterCount);
7706
7707
        // add 1 for the string length
7708 24
        $string['size'] += 1;
7709
7710 24
        return $string;
7711
    }
7712
7713
    /**
7714
     * Extracts an Excel Unicode long string (16-bit string length)
7715
     * OpenOffice documentation: 2.5.3
7716
     * this function is under construction, needs to support rich text, and Asian phonetic settings.
7717
     *
7718
     * @param string $subData
7719
     *
7720
     * @return array
7721
     */
7722 20
    private static function readUnicodeStringLong($subData)
7723
    {
7724 20
        $value = '';
7725
7726
        // offset: 0: size: 2; length of the string (character count)
7727 20
        $characterCount = self::getUInt2d($subData, 0);
7728
7729 20
        $string = self::readUnicodeString(substr($subData, 2), $characterCount);
7730
7731
        // add 2 for the string length
7732 20
        $string['size'] += 2;
7733
7734 20
        return $string;
7735
    }
7736
7737
    /**
7738
     * Read Unicode string with no string length field, but with known character count
7739
     * this function is under construction, needs to support rich text, and Asian phonetic settings
7740
     * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3.
7741
     *
7742
     * @param string $subData
7743
     * @param int $characterCount
7744
     *
7745
     * @return array
7746
     */
7747 24
    private static function readUnicodeString($subData, $characterCount)
7748
    {
7749 24
        $value = '';
7750
7751
        // offset: 0: size: 1; option flags
7752
        // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)
7753 24
        $isCompressed = !((0x01 & ord($subData[0])) >> 0);
7754
7755
        // bit: 2; mask: 0x04; Asian phonetic settings
7756 24
        $hasAsian = (0x04) & ord($subData[0]) >> 2;
7757
7758
        // bit: 3; mask: 0x08; Rich-Text settings
7759 24
        $hasRichText = (0x08) & ord($subData[0]) >> 3;
7760
7761
        // offset: 1: size: var; character array
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
7762
        // this offset assumes richtext and Asian phonetic settings are off which is generally wrong
7763
        // needs to be fixed
7764 24
        $value = self::encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed);
7765
7766
        return [
7767 24
            'value' => $value,
7768 24
            'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags
7769
        ];
7770
    }
7771
7772
    /**
7773
     * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas.
7774
     * Example:  hello"world  -->  "hello""world".
7775
     *
7776
     * @param string $value UTF-8 encoded string
7777
     *
7778
     * @return string
7779
     */
7780 1
    private static function UTF8toExcelDoubleQuoted($value)
7781
    {
7782 1
        return '"' . str_replace('"', '""', $value) . '"';
7783
    }
7784
7785
    /**
7786
     * Reads first 8 bytes of a string and return IEEE 754 float.
7787
     *
7788
     * @param string $data Binary string that is at least 8 bytes long
7789
     *
7790
     * @return float
7791
     */
7792 21
    private static function extractNumber($data)
7793
    {
7794 21
        $rknumhigh = self::getInt4d($data, 4);
7795 21
        $rknumlow = self::getInt4d($data, 0);
7796 21
        $sign = ($rknumhigh & 0x80000000) >> 31;
7797 21
        $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023;
7798 21
        $mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
7799 21
        $mantissalow1 = ($rknumlow & 0x80000000) >> 31;
7800 21
        $mantissalow2 = ($rknumlow & 0x7fffffff);
7801 21
        $value = $mantissa / pow(2, (20 - $exp));
7802
7803 21
        if ($mantissalow1 != 0) {
7804 12
            $value += 1 / pow(2, (21 - $exp));
7805
        }
7806
7807 21
        $value += $mantissalow2 / pow(2, (52 - $exp));
7808 21
        if ($sign) {
7809 10
            $value *= -1;
7810
        }
7811
7812 21
        return $value;
7813
    }
7814
7815
    /**
7816
     * @param int $rknum
7817
     *
7818
     * @return float
7819
     */
7820 12
    private static function getIEEE754($rknum)
7821
    {
7822 12
        if (($rknum & 0x02) != 0) {
7823
            $value = $rknum >> 2;
7824
        } else {
7825
            // changes by mmp, info on IEEE754 encoding from
7826
            // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
7827
            // The RK format calls for using only the most significant 30 bits
7828
            // of the 64 bit floating point value. The other 34 bits are assumed
7829
            // to be 0 so we use the upper 30 bits of $rknum as follows...
7830 12
            $sign = ($rknum & 0x80000000) >> 31;
7831 12
            $exp = ($rknum & 0x7ff00000) >> 20;
7832 12
            $mantissa = (0x100000 | ($rknum & 0x000ffffc));
7833 12
            $value = $mantissa / pow(2, (20 - ($exp - 1023)));
7834 12
            if ($sign) {
7835 10
                $value = -1 * $value;
7836
            }
7837
            //end of changes by mmp
7838
        }
7839 12
        if (($rknum & 0x01) != 0) {
7840 10
            $value /= 100;
7841
        }
7842
7843 12
        return $value;
7844
    }
7845
7846
    /**
7847
     * Get UTF-8 string from (compressed or uncompressed) UTF-16 string.
7848
     *
7849
     * @param string $string
7850
     * @param bool $compressed
7851
     *
7852
     * @return string
7853
     */
7854 24
    private static function encodeUTF16($string, $compressed = false)
7855
    {
7856 24
        if ($compressed) {
7857 20
            $string = self::uncompressByteString($string);
7858
        }
7859
7860 24
        return StringHelper::convertEncoding($string, 'UTF-8', 'UTF-16LE');
7861
    }
7862
7863
    /**
7864
     * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8.
7865
     *
7866
     * @param string $string
7867
     *
7868
     * @return string
7869
     */
7870 20
    private static function uncompressByteString($string)
7871
    {
7872 20
        $uncompressedString = '';
7873 20
        $strLen = strlen($string);
7874 20
        for ($i = 0; $i < $strLen; ++$i) {
7875 20
            $uncompressedString .= $string[$i] . "\0";
7876
        }
7877
7878 20
        return $uncompressedString;
7879
    }
7880
7881
    /**
7882
     * Convert string to UTF-8. Only used for BIFF5.
7883
     *
7884
     * @param string $string
7885
     *
7886
     * @return string
7887
     */
7888
    private function decodeCodepage($string)
7889
    {
7890
        return StringHelper::convertEncoding($string, 'UTF-8', $this->codepage);
7891
    }
7892
7893
    /**
7894
     * Read 16-bit unsigned integer.
7895
     *
7896
     * @param string $data
7897
     * @param int $pos
7898
     *
7899
     * @return int
7900
     */
7901 24
    public static function getUInt2d($data, $pos)
7902
    {
7903 24
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
7904
    }
7905
7906
    /**
7907
     * Read 16-bit signed integer.
7908
     *
7909
     * @param string $data
7910
     * @param int $pos
7911
     *
7912
     * @return int
7913
     */
7914
    public static function getInt2d($data, $pos)
7915
    {
7916
        return unpack('s', $data[$pos] . $data[$pos + 1])[1];
7917
    }
7918
7919
    /**
7920
     * Read 32-bit signed integer.
7921
     *
7922
     * @param string $data
7923
     * @param int $pos
7924
     *
7925
     * @return int
7926
     */
7927 24
    public static function getInt4d($data, $pos)
7928
    {
7929
        // FIX: represent numbers correctly on 64-bit system
7930
        // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
7931
        // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
7932 24
        $_or_24 = ord($data[$pos + 3]);
7933 24
        if ($_or_24 >= 128) {
7934
            // negative number
7935 12
            $_ord_24 = -abs((256 - $_or_24) << 24);
7936
        } else {
7937 24
            $_ord_24 = ($_or_24 & 127) << 24;
7938
        }
7939
7940 24
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
7941
    }
7942
7943 1
    private function parseRichText($is)
7944
    {
7945 1
        $value = new RichText();
7946 1
        $value->createText($is);
7947
7948 1
        return $value;
7949
    }
7950
}
7951