Failed Conditions
Push — develop ( eb5856...11b055 )
by Adrien
31:22
created

Xls::readPane()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 26
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 12
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 26
rs 8.8571
ccs 13
cts 13
cp 1
crap 3
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 25
    public function __construct()
414
    {
415 25
        $this->readFilter = new DefaultReadFilter();
416 25
    }
417
418
    /**
419
     * Can the current IReader read the file?
420
     *
421
     * @param string $pFilename
422
     *
423
     * @throws Exception
424
     *
425
     * @return bool
426
     */
427 6
    public function canRead($pFilename)
428
    {
429 6
        File::assertFile($pFilename);
430
431
        try {
432
            // Use ParseXL for the hard work.
433 6
            $ole = new OLERead();
434
435
            // get excel data
436 6
            $ole->read($pFilename);
437
438 6
            return true;
439
        } catch (PhpSpreadsheetException $e) {
440
            return false;
441
        }
442
    }
443
444
    /**
445
     * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object.
446
     *
447
     * @param string $pFilename
448
     *
449
     * @throws Exception
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 View Code Duplication
        while ($this->pos < $this->dataSize) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 1
    public function listWorksheetInfo($pFilename)
510
    {
511 1
        File::assertFile($pFilename);
512
513 1
        $worksheetInfo = [];
514
515
        // Read the OLE file
516 1
        $this->loadOLE($pFilename);
517
518
        // total byte size of Excel data (workbook global substream + sheet substreams)
519 1
        $this->dataSize = strlen($this->data);
520
521
        // initialize
522 1
        $this->pos = 0;
523 1
        $this->sheets = [];
524
525
        // Parse Workbook Global Substream
526 1 View Code Duplication
        while ($this->pos < $this->dataSize) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
527 1
            $code = self::getUInt2d($this->data, $this->pos);
528
529
            switch ($code) {
530 1
                case self::XLS_TYPE_BOF:
531 1
                    $this->readBof();
532
533 1
                    break;
534 1
                case self::XLS_TYPE_SHEET:
535 1
                    $this->readSheet();
536
537 1
                    break;
538 1
                case self::XLS_TYPE_EOF:
539 1
                    $this->readDefault();
540
541 1
                    break 2;
542
                default:
543 1
                    $this->readDefault();
544
545 1
                    break;
546
            }
547
        }
548
549
        // Parse the individual sheets
550 1
        foreach ($this->sheets as $sheet) {
551 1
            if ($sheet['sheetType'] != 0x00) {
552
                // 0x00: Worksheet
553
                // 0x02: Chart
554
                // 0x06: Visual Basic module
555
                continue;
556
            }
557
558 1
            $tmpInfo = [];
559 1
            $tmpInfo['worksheetName'] = $sheet['name'];
560 1
            $tmpInfo['lastColumnLetter'] = 'A';
561 1
            $tmpInfo['lastColumnIndex'] = 0;
562 1
            $tmpInfo['totalRows'] = 0;
563 1
            $tmpInfo['totalColumns'] = 0;
564
565 1
            $this->pos = $sheet['offset'];
566
567 1
            while ($this->pos <= $this->dataSize - 4) {
568 1
                $code = self::getUInt2d($this->data, $this->pos);
569
570
                switch ($code) {
571 1
                    case self::XLS_TYPE_RK:
572 1
                    case self::XLS_TYPE_LABELSST:
573 1
                    case self::XLS_TYPE_NUMBER:
574 1
                    case self::XLS_TYPE_FORMULA:
575 1
                    case self::XLS_TYPE_BOOLERR:
576 1
                    case self::XLS_TYPE_LABEL:
577 1
                        $length = self::getUInt2d($this->data, $this->pos + 2);
578 1
                        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
579
580
                        // move stream pointer to next record
581 1
                        $this->pos += 4 + $length;
582
583 1
                        $rowIndex = self::getUInt2d($recordData, 0) + 1;
584 1
                        $columnIndex = self::getUInt2d($recordData, 2);
585
586 1
                        $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
587 1
                        $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
588
589 1
                        break;
590 1
                    case self::XLS_TYPE_BOF:
591 1
                        $this->readBof();
592
593 1
                        break;
594 1
                    case self::XLS_TYPE_EOF:
595 1
                        $this->readDefault();
596
597 1
                        break 2;
598
                    default:
599 1
                        $this->readDefault();
600
601 1
                        break;
602
                }
603
            }
604
605 1
            $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
606 1
            $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
607
608 1
            $worksheetInfo[] = $tmpInfo;
609
        }
610
611 1
        return $worksheetInfo;
612
    }
613
614
    /**
615
     * Loads PhpSpreadsheet from file.
616
     *
617
     * @param string $pFilename
618
     *
619
     * @throws Exception
620
     *
621
     * @return Spreadsheet
622
     */
623 19
    public function load($pFilename)
624
    {
625
        // Read the OLE file
626 19
        $this->loadOLE($pFilename);
627
628
        // Initialisations
629 19
        $this->spreadsheet = new Spreadsheet();
630 19
        $this->spreadsheet->removeSheetByIndex(0); // remove 1st sheet
631 19
        if (!$this->readDataOnly) {
632 18
            $this->spreadsheet->removeCellStyleXfByIndex(0); // remove the default style
633 18
            $this->spreadsheet->removeCellXfByIndex(0); // remove the default style
634
        }
635
636
        // Read the summary information stream (containing meta data)
637 19
        $this->readSummaryInformation();
638
639
        // Read the Additional document summary information stream (containing application-specific meta data)
640 19
        $this->readDocumentSummaryInformation();
641
642
        // total byte size of Excel data (workbook global substream + sheet substreams)
643 19
        $this->dataSize = strlen($this->data);
644
645
        // initialize
646 19
        $this->pos = 0;
647 19
        $this->codepage = 'CP1252';
648 19
        $this->formats = [];
649 19
        $this->objFonts = [];
650 19
        $this->palette = [];
651 19
        $this->sheets = [];
652 19
        $this->externalBooks = [];
653 19
        $this->ref = [];
654 19
        $this->definedname = [];
655 19
        $this->sst = [];
656 19
        $this->drawingGroupData = '';
657 19
        $this->xfIndex = '';
658 19
        $this->mapCellXfIndex = [];
659 19
        $this->mapCellStyleXfIndex = [];
660
661
        // Parse Workbook Global Substream
662 19
        while ($this->pos < $this->dataSize) {
663 19
            $code = self::getUInt2d($this->data, $this->pos);
664
665
            switch ($code) {
666 19
                case self::XLS_TYPE_BOF:
667 19
                    $this->readBof();
668
669 19
                    break;
670 19
                case self::XLS_TYPE_FILEPASS:
671
                    $this->readFilepass();
672
673
                    break;
674 19
                case self::XLS_TYPE_CODEPAGE:
675 19
                    $this->readCodepage();
676
677 19
                    break;
678 19
                case self::XLS_TYPE_DATEMODE:
679 19
                    $this->readDateMode();
680
681 19
                    break;
682 19
                case self::XLS_TYPE_FONT:
683 19
                    $this->readFont();
684
685 19
                    break;
686 19
                case self::XLS_TYPE_FORMAT:
687 18
                    $this->readFormat();
688
689 18
                    break;
690 19
                case self::XLS_TYPE_XF:
691 19
                    $this->readXf();
692
693 19
                    break;
694 19
                case self::XLS_TYPE_XFEXT:
695 3
                    $this->readXfExt();
696
697 3
                    break;
698 19
                case self::XLS_TYPE_STYLE:
699 19
                    $this->readStyle();
700
701 19
                    break;
702 19
                case self::XLS_TYPE_PALETTE:
703 5
                    $this->readPalette();
704
705 5
                    break;
706 19
                case self::XLS_TYPE_SHEET:
707 19
                    $this->readSheet();
708
709 19
                    break;
710 19
                case self::XLS_TYPE_EXTERNALBOOK:
711 5
                    $this->readExternalBook();
712
713 5
                    break;
714 19
                case self::XLS_TYPE_EXTERNNAME:
715
                    $this->readExternName();
716
717
                    break;
718 19
                case self::XLS_TYPE_EXTERNSHEET:
719 5
                    $this->readExternSheet();
720
721 5
                    break;
722 19
                case self::XLS_TYPE_DEFINEDNAME:
723 1
                    $this->readDefinedName();
724
725 1
                    break;
726 19
                case self::XLS_TYPE_MSODRAWINGGROUP:
727 3
                    $this->readMsoDrawingGroup();
728
729 3
                    break;
730 19
                case self::XLS_TYPE_SST:
731 19
                    $this->readSst();
732
733 19
                    break;
734 19
                case self::XLS_TYPE_EOF:
735 19
                    $this->readDefault();
736
737 19
                    break 2;
738
                default:
739 19
                    $this->readDefault();
740
741 19
                    break;
742
            }
743
        }
744
745
        // Resolve indexed colors for font, fill, and border colors
746
        // Cannot be resolved already in XF record, because PALETTE record comes afterwards
747 19
        if (!$this->readDataOnly) {
748 18
            foreach ($this->objFonts as $objFont) {
749 18 View Code Duplication
                if (isset($objFont->colorIndex)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
750 18
                    $color = Xls\Color::map($objFont->colorIndex, $this->palette, $this->version);
751 18
                    $objFont->getColor()->setRGB($color['rgb']);
752
                }
753
            }
754
755 18
            foreach ($this->spreadsheet->getCellXfCollection() as $objStyle) {
756
                // fill start and end color
757 18
                $fill = $objStyle->getFill();
758
759 18
                if (isset($fill->startcolorIndex)) {
760 18
                    $startColor = Xls\Color::map($fill->startcolorIndex, $this->palette, $this->version);
761 18
                    $fill->getStartColor()->setRGB($startColor['rgb']);
762
                }
763 18
                if (isset($fill->endcolorIndex)) {
764 18
                    $endColor = Xls\Color::map($fill->endcolorIndex, $this->palette, $this->version);
765 18
                    $fill->getEndColor()->setRGB($endColor['rgb']);
766
                }
767
768
                // border colors
769 18
                $top = $objStyle->getBorders()->getTop();
770 18
                $right = $objStyle->getBorders()->getRight();
771 18
                $bottom = $objStyle->getBorders()->getBottom();
772 18
                $left = $objStyle->getBorders()->getLeft();
773 18
                $diagonal = $objStyle->getBorders()->getDiagonal();
774
775 18 View Code Duplication
                if (isset($top->colorIndex)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
776 18
                    $borderTopColor = Xls\Color::map($top->colorIndex, $this->palette, $this->version);
777 18
                    $top->getColor()->setRGB($borderTopColor['rgb']);
778
                }
779 18 View Code Duplication
                if (isset($right->colorIndex)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
780 18
                    $borderRightColor = Xls\Color::map($right->colorIndex, $this->palette, $this->version);
781 18
                    $right->getColor()->setRGB($borderRightColor['rgb']);
782
                }
783 18 View Code Duplication
                if (isset($bottom->colorIndex)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
784 18
                    $borderBottomColor = Xls\Color::map($bottom->colorIndex, $this->palette, $this->version);
785 18
                    $bottom->getColor()->setRGB($borderBottomColor['rgb']);
786
                }
787 18 View Code Duplication
                if (isset($left->colorIndex)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
788 18
                    $borderLeftColor = Xls\Color::map($left->colorIndex, $this->palette, $this->version);
789 18
                    $left->getColor()->setRGB($borderLeftColor['rgb']);
790
                }
791 18 View Code Duplication
                if (isset($diagonal->colorIndex)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
792 18
                    $borderDiagonalColor = Xls\Color::map($diagonal->colorIndex, $this->palette, $this->version);
793 18
                    $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']);
794
                }
795
            }
796
        }
797
798
        // treat MSODRAWINGGROUP records, workbook-level Escher
799 19
        if (!$this->readDataOnly && $this->drawingGroupData) {
800 3
            $escherWorkbook = new Escher();
801 3
            $reader = new Xls\Escher($escherWorkbook);
802 3
            $escherWorkbook = $reader->load($this->drawingGroupData);
803
        }
804
805
        // Parse the individual sheets
806 19
        foreach ($this->sheets as $sheet) {
807 19
            if ($sheet['sheetType'] != 0x00) {
808
                // 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...
809
                continue;
810
            }
811
812
            // check if sheet should be skipped
813 19
            if (isset($this->loadSheetsOnly) && !in_array($sheet['name'], $this->loadSheetsOnly)) {
814 4
                continue;
815
            }
816
817
            // add sheet to PhpSpreadsheet object
818 19
            $this->phpSheet = $this->spreadsheet->createSheet();
819
            //    Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
820
            //        cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
821
            //        name in line with the formula, not the reverse
822 19
            $this->phpSheet->setTitle($sheet['name'], false, false);
823 19
            $this->phpSheet->setSheetState($sheet['sheetState']);
824
825 19
            $this->pos = $sheet['offset'];
826
827
            // Initialize isFitToPages. May change after reading SHEETPR record.
828 19
            $this->isFitToPages = false;
829
830
            // Initialize drawingData
831 19
            $this->drawingData = '';
832
833
            // Initialize objs
834 19
            $this->objs = [];
835
836
            // Initialize shared formula parts
837 19
            $this->sharedFormulaParts = [];
838
839
            // Initialize shared formulas
840 19
            $this->sharedFormulas = [];
841
842
            // Initialize text objs
843 19
            $this->textObjects = [];
844
845
            // Initialize cell annotations
846 19
            $this->cellNotes = [];
847 19
            $this->textObjRef = -1;
848
849 19
            while ($this->pos <= $this->dataSize - 4) {
850 19
                $code = self::getUInt2d($this->data, $this->pos);
851
852
                switch ($code) {
853 19
                    case self::XLS_TYPE_BOF:
854 19
                        $this->readBof();
855
856 19
                        break;
857 19
                    case self::XLS_TYPE_PRINTGRIDLINES:
858 19
                        $this->readPrintGridlines();
859
860 19
                        break;
861 19
                    case self::XLS_TYPE_DEFAULTROWHEIGHT:
862 17
                        $this->readDefaultRowHeight();
863
864 17
                        break;
865 19
                    case self::XLS_TYPE_SHEETPR:
866 19
                        $this->readSheetPr();
867
868 19
                        break;
869 19
                    case self::XLS_TYPE_HORIZONTALPAGEBREAKS:
870
                        $this->readHorizontalPageBreaks();
871
872
                        break;
873 19
                    case self::XLS_TYPE_VERTICALPAGEBREAKS:
874
                        $this->readVerticalPageBreaks();
875
876
                        break;
877 19
                    case self::XLS_TYPE_HEADER:
878 19
                        $this->readHeader();
879
880 19
                        break;
881 19
                    case self::XLS_TYPE_FOOTER:
882 19
                        $this->readFooter();
883
884 19
                        break;
885 19
                    case self::XLS_TYPE_HCENTER:
886 19
                        $this->readHcenter();
887
888 19
                        break;
889 19
                    case self::XLS_TYPE_VCENTER:
890 19
                        $this->readVcenter();
891
892 19
                        break;
893 19
                    case self::XLS_TYPE_LEFTMARGIN:
894 6
                        $this->readLeftMargin();
895
896 6
                        break;
897 19
                    case self::XLS_TYPE_RIGHTMARGIN:
898 6
                        $this->readRightMargin();
899
900 6
                        break;
901 19
                    case self::XLS_TYPE_TOPMARGIN:
902 6
                        $this->readTopMargin();
903
904 6
                        break;
905 19
                    case self::XLS_TYPE_BOTTOMMARGIN:
906 6
                        $this->readBottomMargin();
907
908 6
                        break;
909 19
                    case self::XLS_TYPE_PAGESETUP:
910 19
                        $this->readPageSetup();
911
912 19
                        break;
913 19
                    case self::XLS_TYPE_PROTECT:
914 1
                        $this->readProtect();
915
916 1
                        break;
917 19
                    case self::XLS_TYPE_SCENPROTECT:
918
                        $this->readScenProtect();
919
920
                        break;
921 19
                    case self::XLS_TYPE_OBJECTPROTECT:
922
                        $this->readObjectProtect();
923
924
                        break;
925 19
                    case self::XLS_TYPE_PASSWORD:
926
                        $this->readPassword();
927
928
                        break;
929 19
                    case self::XLS_TYPE_DEFCOLWIDTH:
930 19
                        $this->readDefColWidth();
931
932 19
                        break;
933 19
                    case self::XLS_TYPE_COLINFO:
934 15
                        $this->readColInfo();
935
936 15
                        break;
937 19
                    case self::XLS_TYPE_DIMENSION:
938 19
                        $this->readDefault();
939
940 19
                        break;
941 19
                    case self::XLS_TYPE_ROW:
942 17
                        $this->readRow();
943
944 17
                        break;
945 19
                    case self::XLS_TYPE_DBCELL:
946 16
                        $this->readDefault();
947
948 16
                        break;
949 19
                    case self::XLS_TYPE_RK:
950 12
                        $this->readRk();
951
952 12
                        break;
953 19
                    case self::XLS_TYPE_LABELSST:
954 17
                        $this->readLabelSst();
955
956 17
                        break;
957 19
                    case self::XLS_TYPE_MULRK:
958 11
                        $this->readMulRk();
959
960 11
                        break;
961 19
                    case self::XLS_TYPE_NUMBER:
962 11
                        $this->readNumber();
963
964 11
                        break;
965 19
                    case self::XLS_TYPE_FORMULA:
966 10
                        $this->readFormula();
967
968 10
                        break;
969 19
                    case self::XLS_TYPE_SHAREDFMLA:
970
                        $this->readSharedFmla();
971
972
                        break;
973 19
                    case self::XLS_TYPE_BOOLERR:
974 8
                        $this->readBoolErr();
975
976 8
                        break;
977 19
                    case self::XLS_TYPE_MULBLANK:
978 11
                        $this->readMulBlank();
979
980 11
                        break;
981 19
                    case self::XLS_TYPE_LABEL:
982
                        $this->readLabel();
983
984
                        break;
985 19
                    case self::XLS_TYPE_BLANK:
986 2
                        $this->readBlank();
987
988 2
                        break;
989 19
                    case self::XLS_TYPE_MSODRAWING:
990 3
                        $this->readMsoDrawing();
991
992 3
                        break;
993 19
                    case self::XLS_TYPE_OBJ:
994 3
                        $this->readObj();
995
996 3
                        break;
997 19
                    case self::XLS_TYPE_WINDOW2:
998 19
                        $this->readWindow2();
999
1000 19
                        break;
1001 19
                    case self::XLS_TYPE_PAGELAYOUTVIEW:
1002 5
                        $this->readPageLayoutView();
1003
1004 5
                        break;
1005 19
                    case self::XLS_TYPE_SCL:
1006
                        $this->readScl();
1007
1008
                        break;
1009 19
                    case self::XLS_TYPE_PANE:
1010 1
                        $this->readPane();
1011
1012 1
                        break;
1013 19
                    case self::XLS_TYPE_SELECTION:
1014 19
                        $this->readSelection();
1015
1016 19
                        break;
1017 19
                    case self::XLS_TYPE_MERGEDCELLS:
1018 12
                        $this->readMergedCells();
1019
1020 12
                        break;
1021 19
                    case self::XLS_TYPE_HYPERLINK:
1022 2
                        $this->readHyperLink();
1023
1024 2
                        break;
1025 19
                    case self::XLS_TYPE_DATAVALIDATIONS:
1026
                        $this->readDataValidations();
1027
1028
                        break;
1029 19
                    case self::XLS_TYPE_DATAVALIDATION:
1030
                        $this->readDataValidation();
1031
1032
                        break;
1033 19
                    case self::XLS_TYPE_SHEETLAYOUT:
1034 2
                        $this->readSheetLayout();
1035
1036 2
                        break;
1037 19
                    case self::XLS_TYPE_SHEETPROTECTION:
1038 6
                        $this->readSheetProtection();
1039
1040 6
                        break;
1041 19
                    case self::XLS_TYPE_RANGEPROTECTION:
1042 1
                        $this->readRangeProtection();
1043
1044 1
                        break;
1045 19
                    case self::XLS_TYPE_NOTE:
1046 1
                        $this->readNote();
1047
1048 1
                        break;
1049 19
                    case self::XLS_TYPE_TXO:
1050 1
                        $this->readTextObject();
1051
1052 1
                        break;
1053 19
                    case self::XLS_TYPE_CONTINUE:
1054
                        $this->readContinue();
1055
1056
                        break;
1057 19
                    case self::XLS_TYPE_EOF:
1058 19
                        $this->readDefault();
1059
1060 19
                        break 2;
1061
                    default:
1062 19
                        $this->readDefault();
1063
1064 19
                        break;
1065
                }
1066
            }
1067
1068
            // treat MSODRAWING records, sheet-level Escher
1069 19
            if (!$this->readDataOnly && $this->drawingData) {
1070 3
                $escherWorksheet = new Escher();
1071 3
                $reader = new Xls\Escher($escherWorksheet);
1072 3
                $escherWorksheet = $reader->load($this->drawingData);
1073
1074
                // get all spContainers in one long array, so they can be mapped to OBJ records
1075 3
                $allSpContainers = $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers();
1076
            }
1077
1078
            // treat OBJ records
1079 19
            foreach ($this->objs as $n => $obj) {
1080
                // the first shape container never has a corresponding OBJ record, hence $n + 1
1081 3
                if (isset($allSpContainers[$n + 1]) && is_object($allSpContainers[$n + 1])) {
1082 3
                    $spContainer = $allSpContainers[$n + 1];
1083
1084
                    // we skip all spContainers that are a part of a group shape since we cannot yet handle those
1085 3
                    if ($spContainer->getNestingLevel() > 1) {
1086
                        continue;
1087
                    }
1088
1089
                    // calculate the width and height of the shape
1090 3
                    list($startColumn, $startRow) = Coordinate::coordinateFromString($spContainer->getStartCoordinates());
1091 3
                    list($endColumn, $endRow) = Coordinate::coordinateFromString($spContainer->getEndCoordinates());
1092
1093 3
                    $startOffsetX = $spContainer->getStartOffsetX();
1094 3
                    $startOffsetY = $spContainer->getStartOffsetY();
1095 3
                    $endOffsetX = $spContainer->getEndOffsetX();
1096 3
                    $endOffsetY = $spContainer->getEndOffsetY();
1097
1098 3
                    $width = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceX($this->phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX);
1099 3
                    $height = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceY($this->phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY);
1100
1101
                    // calculate offsetX and offsetY of the shape
1102 3
                    $offsetX = $startOffsetX * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeCol($this->phpSheet, $startColumn) / 1024;
1103 3
                    $offsetY = $startOffsetY * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeRow($this->phpSheet, $startRow) / 256;
1104
1105 3
                    switch ($obj['otObjType']) {
1106 3
                        case 0x19:
1107
                            // Note
1108 1
                            if (isset($this->cellNotes[$obj['idObjID']])) {
1109 1
                                $cellNote = $this->cellNotes[$obj['idObjID']];
1110
1111 1 View Code Duplication
                                if (isset($this->textObjects[$obj['idObjID']])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1112 1
                                    $textObject = $this->textObjects[$obj['idObjID']];
1113 1
                                    $this->cellNotes[$obj['idObjID']]['objTextData'] = $textObject;
1114
                                }
1115
                            }
1116
1117 1
                            break;
1118 3
                        case 0x08:
1119
                            // picture
1120
                            // get index to BSE entry (1-based)
1121 3
                            $BSEindex = $spContainer->getOPT(0x0104);
1122
1123
                            // If there is no BSE Index, we will fail here and other fields are not read.
1124
                            // Fix by checking here.
1125
                            // TODO: Why is there no BSE Index? Is this a new Office Version? Password protected field?
1126
                            // More likely : a uncompatible picture
1127 3
                            if (!$BSEindex) {
1128
                                continue;
1129
                            }
1130
1131 3
                            $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection();
1132 3
                            $BSE = $BSECollection[$BSEindex - 1];
1133 3
                            $blipType = $BSE->getBlipType();
1134
1135
                            // need check because some blip types are not supported by Escher reader such as EMF
1136 3
                            if ($blip = $BSE->getBlip()) {
1137 3
                                $ih = imagecreatefromstring($blip->getData());
1138 3
                                $drawing = new MemoryDrawing();
1139 3
                                $drawing->setImageResource($ih);
1140
1141
                                // width, height, offsetX, offsetY
1142 3
                                $drawing->setResizeProportional(false);
1143 3
                                $drawing->setWidth($width);
1144 3
                                $drawing->setHeight($height);
1145 3
                                $drawing->setOffsetX($offsetX);
1146 3
                                $drawing->setOffsetY($offsetY);
1147
1148
                                switch ($blipType) {
1149 3
                                    case BSE::BLIPTYPE_JPEG:
1150 3
                                        $drawing->setRenderingFunction(MemoryDrawing::RENDERING_JPEG);
1151 3
                                        $drawing->setMimeType(MemoryDrawing::MIMETYPE_JPEG);
1152
1153 3
                                        break;
1154 3
                                    case BSE::BLIPTYPE_PNG:
1155 3
                                        $drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG);
1156 3
                                        $drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG);
1157
1158 3
                                        break;
1159
                                }
1160
1161 3
                                $drawing->setWorksheet($this->phpSheet);
1162 3
                                $drawing->setCoordinates($spContainer->getStartCoordinates());
1163
                            }
1164
1165 3
                            break;
1166
                        default:
1167
                            // other object type
1168 3
                            break;
1169
                    }
1170
                }
1171
            }
1172
1173
            // treat SHAREDFMLA records
1174 19
            if ($this->version == self::XLS_BIFF8) {
1175 19
                foreach ($this->sharedFormulaParts as $cell => $baseCell) {
1176
                    list($column, $row) = Coordinate::coordinateFromString($cell);
1177
                    if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
1178
                        $formula = $this->getFormulaFromStructure($this->sharedFormulas[$baseCell], $cell);
1179
                        $this->phpSheet->getCell($cell)->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA);
1180
                    }
1181
                }
1182
            }
1183
1184 19
            if (!empty($this->cellNotes)) {
1185 1
                foreach ($this->cellNotes as $note => $noteDetails) {
1186 1
                    if (!isset($noteDetails['objTextData'])) {
1187 View Code Duplication
                        if (isset($this->textObjects[$note])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1188
                            $textObject = $this->textObjects[$note];
1189
                            $noteDetails['objTextData'] = $textObject;
1190
                        } else {
1191
                            $noteDetails['objTextData']['text'] = '';
1192
                        }
1193
                    }
1194 1
                    $cellAddress = str_replace('$', '', $noteDetails['cellRef']);
1195 19
                    $this->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($this->parseRichText($noteDetails['objTextData']['text']));
1196
                }
1197
            }
1198
        }
1199
1200
        // add the named ranges (defined names)
1201 19
        foreach ($this->definedname as $definedName) {
1202 1
            if ($definedName['isBuiltInName']) {
1203
                switch ($definedName['name']) {
1204
                    case pack('C', 0x06):
1205
                        // print area
1206
                        //    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...
1207
                        $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
1208
1209
                        $extractedRanges = [];
1210
                        foreach ($ranges as $range) {
1211
                            // $range should look like one of these
1212
                            //        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...
1213
                            //        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...
1214
                            $explodes = explode('!', $range); // FIXME: what if sheetname contains exclamation mark?
1215
                            $sheetName = trim($explodes[0], "'");
1216
                            if (count($explodes) == 2) {
1217
                                if (strpos($explodes[1], ':') === false) {
1218
                                    $explodes[1] = $explodes[1] . ':' . $explodes[1];
1219
                                }
1220
                                $extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66
1221
                            }
1222
                        }
1223
                        if ($docSheet = $this->spreadsheet->getSheetByName($sheetName)) {
1224
                            $docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2
1225
                        }
1226
1227
                        break;
1228
                    case pack('C', 0x07):
1229
                        // print titles (repeating rows)
1230
                        // Assuming BIFF8, there are 3 cases
1231
                        // 1. repeating rows
1232
                        //        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...
1233
                        //        rows 1-2 repeat
1234
                        // 2. repeating columns
1235
                        //        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...
1236
                        //        columns A-B repeat
1237
                        // 3. both repeating rows and repeating columns
1238
                        //        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...
1239
                        $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
1240
                        foreach ($ranges as $range) {
1241
                            // $range should look like this one of these
1242
                            //        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...
1243
                            //        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...
1244
                            $explodes = explode('!', $range);
1245
                            if (count($explodes) == 2) {
1246
                                if ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) {
1247
                                    $extractedRange = $explodes[1];
1248
                                    $extractedRange = str_replace('$', '', $extractedRange);
1249
1250
                                    $coordinateStrings = explode(':', $extractedRange);
1251
                                    if (count($coordinateStrings) == 2) {
1252
                                        list($firstColumn, $firstRow) = Coordinate::coordinateFromString($coordinateStrings[0]);
1253
                                        list($lastColumn, $lastRow) = Coordinate::coordinateFromString($coordinateStrings[1]);
1254
1255
                                        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...
1256
                                            // then we have repeating rows
1257
                                            $docSheet->getPageSetup()->setRowsToRepeatAtTop([$firstRow, $lastRow]);
1258
                                        } 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...
1259
                                            // then we have repeating columns
1260
                                            $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$firstColumn, $lastColumn]);
1261
                                        }
1262
                                    }
1263
                                }
1264
                            }
1265
                        }
1266
1267
                        break;
1268
                }
1269
            } else {
1270
                // Extract range
1271 1
                $explodes = explode('!', $definedName['formula']);
1272
1273 1
                if (count($explodes) == 2) {
1274 1
                    if (($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) ||
1275 1
                        ($docSheet = $this->spreadsheet->getSheetByName(trim($explodes[0], "'")))) {
1276 1
                        $extractedRange = $explodes[1];
1277 1
                        $extractedRange = str_replace('$', '', $extractedRange);
1278
1279 1
                        $localOnly = ($definedName['scope'] == 0) ? false : true;
1280
1281 1
                        $scope = ($definedName['scope'] == 0) ? null : $this->spreadsheet->getSheetByName($this->sheets[$definedName['scope'] - 1]['name']);
1282
1283 1
                        $this->spreadsheet->addNamedRange(new NamedRange((string) $definedName['name'], $docSheet, $extractedRange, $localOnly, $scope));
1284
                    }
1285
                }
1286
                //    Named Value
1287
                    //    TODO Provide support for named values
1288
            }
1289
        }
1290 19
        $this->data = null;
1291
1292 19
        return $this->spreadsheet;
1293
    }
1294
1295
    /**
1296
     * Read record data from stream, decrypting as required.
1297
     *
1298
     * @param string $data Data stream to read from
1299
     * @param int $pos Position to start reading from
1300
     * @param int $len Record data length
1301
     *
1302
     * @return string Record data
1303
     */
1304 22
    private function readRecordData($data, $pos, $len)
1305
    {
1306 22
        $data = substr($data, $pos, $len);
1307
1308
        // File not encrypted, or record before encryption start point
1309 22
        if ($this->encryption == self::MS_BIFF_CRYPTO_NONE || $pos < $this->encryptionStartPos) {
1310 22
            return $data;
1311
        }
1312
1313
        $recordData = '';
1314
        if ($this->encryption == self::MS_BIFF_CRYPTO_RC4) {
1315
            $oldBlock = floor($this->rc4Pos / self::REKEY_BLOCK);
1316
            $block = floor($pos / self::REKEY_BLOCK);
1317
            $endBlock = floor(($pos + $len) / self::REKEY_BLOCK);
1318
1319
            // Spin an RC4 decryptor to the right spot. If we have a decryptor sitting
1320
            // at a point earlier in the current block, re-use it as we can save some time.
1321
            if ($block != $oldBlock || $pos < $this->rc4Pos || !$this->rc4Key) {
1322
                $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
1323
                $step = $pos % self::REKEY_BLOCK;
1324
            } else {
1325
                $step = $pos - $this->rc4Pos;
1326
            }
1327
            $this->rc4Key->RC4(str_repeat("\0", $step));
1328
1329
            // Decrypt record data (re-keying at the end of every block)
1330
            while ($block != $endBlock) {
1331
                $step = self::REKEY_BLOCK - ($pos % self::REKEY_BLOCK);
1332
                $recordData .= $this->rc4Key->RC4(substr($data, 0, $step));
1333
                $data = substr($data, $step);
1334
                $pos += $step;
1335
                $len -= $step;
1336
                ++$block;
1337
                $this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
1338
            }
1339
            $recordData .= $this->rc4Key->RC4(substr($data, 0, $len));
1340
1341
            // Keep track of the position of this decryptor.
1342
            // We'll try and re-use it later if we can to speed things up
1343
            $this->rc4Pos = $pos + $len;
1344
        } elseif ($this->encryption == self::MS_BIFF_CRYPTO_XOR) {
1345
            throw new Exception('XOr encryption not supported');
1346
        }
1347
1348
        return $recordData;
1349
    }
1350
1351
    /**
1352
     * Use OLE reader to extract the relevant data streams from the OLE file.
1353
     *
1354
     * @param string $pFilename
1355
     */
1356 22
    private function loadOLE($pFilename)
1357
    {
1358
        // OLE reader
1359 22
        $ole = new OLERead();
1360
        // get excel data,
1361 22
        $ole->read($pFilename);
1362
        // Get workbook data: workbook stream + sheet streams
1363 22
        $this->data = $ole->getStream($ole->wrkbook);
1364
        // Get summary information data
1365 22
        $this->summaryInformation = $ole->getStream($ole->summaryInformation);
1366
        // Get additional document summary information data
1367 22
        $this->documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation);
1368 22
    }
1369
1370
    /**
1371
     * Read summary information.
1372
     */
1373 19
    private function readSummaryInformation()
1374
    {
1375 19
        if (!isset($this->summaryInformation)) {
1376
            return;
1377
        }
1378
1379
        // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
1380
        // 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...
1381
        // offset: 4; size: 2; OS version
1382
        // offset: 6; size: 2; OS indicator
1383
        // offset: 8; size: 16
1384
        // offset: 24; size: 4; section count
1385 19
        $secCount = self::getInt4d($this->summaryInformation, 24);
1386
1387
        // 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
1388
        // offset: 44; size: 4
1389 19
        $secOffset = self::getInt4d($this->summaryInformation, 44);
1390
1391
        // section header
1392
        // offset: $secOffset; size: 4; section length
1393 19
        $secLength = self::getInt4d($this->summaryInformation, $secOffset);
1394
1395
        // offset: $secOffset+4; size: 4; property count
1396 19
        $countProperties = self::getInt4d($this->summaryInformation, $secOffset + 4);
1397
1398
        // initialize code page (used to resolve string values)
1399 19
        $codePage = 'CP1252';
1400
1401
        // 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...
1402
        // loop through property decarations and properties
1403 19
        for ($i = 0; $i < $countProperties; ++$i) {
1404
            // 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...
1405 19
            $id = self::getInt4d($this->summaryInformation, ($secOffset + 8) + (8 * $i));
1406
1407
            // Use value of property id as appropriate
1408
            // 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...
1409 19
            $offset = self::getInt4d($this->summaryInformation, ($secOffset + 12) + (8 * $i));
1410
1411 19
            $type = self::getInt4d($this->summaryInformation, $secOffset + $offset);
1412
1413
            // initialize property value
1414 19
            $value = null;
1415
1416
            // extract property value based on property type
1417
            switch ($type) {
1418 19
                case 0x02: // 2 byte signed integer
1419 19
                    $value = self::getUInt2d($this->summaryInformation, $secOffset + 4 + $offset);
1420
1421 19
                    break;
1422 19
                case 0x03: // 4 byte signed integer
1423 18
                    $value = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
1424
1425 18
                    break;
1426 19
                case 0x13: // 4 byte unsigned integer
1427
                    // not needed yet, fix later if necessary
1428
                    break;
1429 19 View Code Duplication
                case 0x1E: // null-terminated string prepended by dword string length
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1430 19
                    $byteLength = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
1431 19
                    $value = substr($this->summaryInformation, $secOffset + 8 + $offset, $byteLength);
1432 19
                    $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage);
1433 19
                    $value = rtrim($value);
1434
1435 19
                    break;
1436 19 View Code Duplication
                case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1437
                    // PHP-time
1438 19
                    $value = OLE::OLE2LocalDate(substr($this->summaryInformation, $secOffset + 4 + $offset, 8));
1439
1440 19
                    break;
1441
                case 0x47: // Clipboard format
1442
                    // not needed yet, fix later if necessary
1443
                    break;
1444
            }
1445
1446
            switch ($id) {
1447 19
                case 0x01:    //    Code Page
1448 19
                    $codePage = CodePage::numberToName($value);
1449
1450 19
                    break;
1451 19
                case 0x02:    //    Title
1452 5
                    $this->spreadsheet->getProperties()->setTitle($value);
1453
1454 5
                    break;
1455 19
                case 0x03:    //    Subject
1456 4
                    $this->spreadsheet->getProperties()->setSubject($value);
1457
1458 4
                    break;
1459 19
                case 0x04:    //    Author (Creator)
1460 17
                    $this->spreadsheet->getProperties()->setCreator($value);
1461
1462 17
                    break;
1463 19
                case 0x05:    //    Keywords
1464 4
                    $this->spreadsheet->getProperties()->setKeywords($value);
1465
1466 4
                    break;
1467 19
                case 0x06:    //    Comments (Description)
1468 4
                    $this->spreadsheet->getProperties()->setDescription($value);
1469
1470 4
                    break;
1471 19
                case 0x07:    //    Template
1472
                    //    Not supported by PhpSpreadsheet
1473
                    break;
1474 19
                case 0x08:    //    Last Saved By (LastModifiedBy)
1475 18
                    $this->spreadsheet->getProperties()->setLastModifiedBy($value);
1476
1477 18
                    break;
1478 19
                case 0x09:    //    Revision
1479
                    //    Not supported by PhpSpreadsheet
1480 1
                    break;
1481 19
                case 0x0A:    //    Total Editing Time
1482
                    //    Not supported by PhpSpreadsheet
1483 1
                    break;
1484 19
                case 0x0B:    //    Last Printed
1485
                    //    Not supported by PhpSpreadsheet
1486 1
                    break;
1487 19
                case 0x0C:    //    Created Date/Time
1488 19
                    $this->spreadsheet->getProperties()->setCreated($value);
1489
1490 19
                    break;
1491 19
                case 0x0D:    //    Modified Date/Time
1492 19
                    $this->spreadsheet->getProperties()->setModified($value);
1493
1494 19
                    break;
1495 18
                case 0x0E:    //    Number of Pages
1496
                    //    Not supported by PhpSpreadsheet
1497
                    break;
1498 18
                case 0x0F:    //    Number of Words
1499
                    //    Not supported by PhpSpreadsheet
1500
                    break;
1501 18
                case 0x10:    //    Number of Characters
1502
                    //    Not supported by PhpSpreadsheet
1503
                    break;
1504 18
                case 0x11:    //    Thumbnail
1505
                    //    Not supported by PhpSpreadsheet
1506
                    break;
1507 18
                case 0x12:    //    Name of creating application
1508
                    //    Not supported by PhpSpreadsheet
1509 12
                    break;
1510 18
                case 0x13:    //    Security
1511
                    //    Not supported by PhpSpreadsheet
1512 18
                    break;
1513
            }
1514
        }
1515 19
    }
1516
1517
    /**
1518
     * Read additional document summary information.
1519
     */
1520 19
    private function readDocumentSummaryInformation()
1521
    {
1522 19
        if (!isset($this->documentSummaryInformation)) {
1523
            return;
1524
        }
1525
1526
        //    offset: 0;    size: 2;    must be 0xFE 0xFF (UTF-16 LE byte order mark)
1527
        //    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...
1528
        //    offset: 4;    size: 2;    OS version
1529
        //    offset: 6;    size: 2;    OS indicator
1530
        //    offset: 8;    size: 16
1531
        //    offset: 24;    size: 4;    section count
1532 19
        $secCount = self::getInt4d($this->documentSummaryInformation, 24);
1533
1534
        // 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
1535
        // offset: 44;    size: 4;    first section offset
1536 19
        $secOffset = self::getInt4d($this->documentSummaryInformation, 44);
1537
1538
        //    section header
1539
        //    offset: $secOffset;    size: 4;    section length
1540 19
        $secLength = self::getInt4d($this->documentSummaryInformation, $secOffset);
1541
1542
        //    offset: $secOffset+4;    size: 4;    property count
1543 19
        $countProperties = self::getInt4d($this->documentSummaryInformation, $secOffset + 4);
1544
1545
        // initialize code page (used to resolve string values)
1546 19
        $codePage = 'CP1252';
1547
1548
        //    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...
1549
        //    loop through property decarations and properties
1550 19
        for ($i = 0; $i < $countProperties; ++$i) {
1551
            //    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...
1552 19
            $id = self::getInt4d($this->documentSummaryInformation, ($secOffset + 8) + (8 * $i));
1553
1554
            // Use value of property id as appropriate
1555
            // offset: 60 + 8 * $i;    size: 4;    offset from beginning of section (48)
1556 19
            $offset = self::getInt4d($this->documentSummaryInformation, ($secOffset + 12) + (8 * $i));
1557
1558 19
            $type = self::getInt4d($this->documentSummaryInformation, $secOffset + $offset);
1559
1560
            // initialize property value
1561 19
            $value = null;
1562
1563
            // extract property value based on property type
1564
            switch ($type) {
1565 19
                case 0x02:    //    2 byte signed integer
1566 19
                    $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1567
1568 19
                    break;
1569 18
                case 0x03:    //    4 byte signed integer
1570 18
                    $value = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1571
1572 18
                    break;
1573 18
                case 0x0B:  // Boolean
1574 18
                    $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1575 18
                    $value = ($value == 0 ? false : true);
1576
1577 18
                    break;
1578 18
                case 0x13:    //    4 byte unsigned integer
1579
                    // not needed yet, fix later if necessary
1580
                    break;
1581 18 View Code Duplication
                case 0x1E:    //    null-terminated string prepended by dword string length
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1582 15
                    $byteLength = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1583 15
                    $value = substr($this->documentSummaryInformation, $secOffset + 8 + $offset, $byteLength);
1584 15
                    $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage);
1585 15
                    $value = rtrim($value);
1586
1587 15
                    break;
1588 18 View Code Duplication
                case 0x40:    //    Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1589
                    // PHP-Time
1590
                    $value = OLE::OLE2LocalDate(substr($this->documentSummaryInformation, $secOffset + 4 + $offset, 8));
1591
1592
                    break;
1593 18
                case 0x47:    //    Clipboard format
1594
                    // not needed yet, fix later if necessary
1595
                    break;
1596
            }
1597
1598
            switch ($id) {
1599 19
                case 0x01:    //    Code Page
1600 19
                    $codePage = CodePage::numberToName($value);
1601
1602 19
                    break;
1603 18
                case 0x02:    //    Category
1604 4
                    $this->spreadsheet->getProperties()->setCategory($value);
1605
1606 4
                    break;
1607 18
                case 0x03:    //    Presentation Target
1608
                    //    Not supported by PhpSpreadsheet
1609
                    break;
1610 18
                case 0x04:    //    Bytes
1611
                    //    Not supported by PhpSpreadsheet
1612
                    break;
1613 18
                case 0x05:    //    Lines
1614
                    //    Not supported by PhpSpreadsheet
1615
                    break;
1616 18
                case 0x06:    //    Paragraphs
1617
                    //    Not supported by PhpSpreadsheet
1618
                    break;
1619 18
                case 0x07:    //    Slides
1620
                    //    Not supported by PhpSpreadsheet
1621
                    break;
1622 18
                case 0x08:    //    Notes
1623
                    //    Not supported by PhpSpreadsheet
1624
                    break;
1625 18
                case 0x09:    //    Hidden Slides
1626
                    //    Not supported by PhpSpreadsheet
1627
                    break;
1628 18
                case 0x0A:    //    MM Clips
1629
                    //    Not supported by PhpSpreadsheet
1630
                    break;
1631 18
                case 0x0B:    //    Scale Crop
1632
                    //    Not supported by PhpSpreadsheet
1633 18
                    break;
1634 18
                case 0x0C:    //    Heading Pairs
1635
                    //    Not supported by PhpSpreadsheet
1636 18
                    break;
1637 18
                case 0x0D:    //    Titles of Parts
1638
                    //    Not supported by PhpSpreadsheet
1639 18
                    break;
1640 18
                case 0x0E:    //    Manager
1641 2
                    $this->spreadsheet->getProperties()->setManager($value);
1642
1643 2
                    break;
1644 18
                case 0x0F:    //    Company
1645 14
                    $this->spreadsheet->getProperties()->setCompany($value);
1646
1647 14
                    break;
1648 18
                case 0x10:    //    Links up-to-date
1649
                    //    Not supported by PhpSpreadsheet
1650 18
                    break;
1651
            }
1652
        }
1653 19
    }
1654
1655
    /**
1656
     * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record.
1657
     */
1658 22
    private function readDefault()
1659
    {
1660 22
        $length = self::getUInt2d($this->data, $this->pos + 2);
1661
1662
        // move stream pointer to next record
1663 22
        $this->pos += 4 + $length;
1664 22
    }
1665
1666
    /**
1667
     *    The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions,
1668
     *        this record stores a note (cell note). This feature was significantly enhanced in Excel 97.
1669
     */
1670 1
    private function readNote()
1671
    {
1672 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
1673 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1674
1675
        // move stream pointer to next record
1676 1
        $this->pos += 4 + $length;
1677
1678 1
        if ($this->readDataOnly) {
1679
            return;
1680
        }
1681
1682 1
        $cellAddress = $this->readBIFF8CellAddress(substr($recordData, 0, 4));
1683 1
        if ($this->version == self::XLS_BIFF8) {
1684 1
            $noteObjID = self::getUInt2d($recordData, 6);
1685 1
            $noteAuthor = self::readUnicodeStringLong(substr($recordData, 8));
1686 1
            $noteAuthor = $noteAuthor['value'];
1687 1
            $this->cellNotes[$noteObjID] = [
1688 1
                'cellRef' => $cellAddress,
1689 1
                'objectID' => $noteObjID,
1690 1
                'author' => $noteAuthor,
1691
            ];
1692
        } else {
1693
            $extension = false;
1694
            if ($cellAddress == '$B$65536') {
1695
                //    If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation
1696
                //        note from the previous cell annotation. We're not yet handling this, so annotations longer than the
1697
                //        max 2048 bytes will probably throw a wobbly.
1698
                $row = self::getUInt2d($recordData, 0);
1699
                $extension = true;
1700
                $cellAddress = array_pop(array_keys($this->phpSheet->getComments()));
1701
            }
1702
1703
            $cellAddress = str_replace('$', '', $cellAddress);
1704
            $noteLength = self::getUInt2d($recordData, 4);
1705
            $noteText = trim(substr($recordData, 6));
1706
1707
            if ($extension) {
1708
                //    Concatenate this extension with the currently set comment for the cell
1709
                $comment = $this->phpSheet->getComment($cellAddress);
1710
                $commentText = $comment->getText()->getPlainText();
1711
                $comment->setText($this->parseRichText($commentText . $noteText));
1712
            } else {
1713
                //    Set comment for the cell
1714
                $this->phpSheet->getComment($cellAddress)->setText($this->parseRichText($noteText));
1715
//                                                    ->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...
1716
            }
1717
        }
1718 1
    }
1719
1720
    /**
1721
     * The TEXT Object record contains the text associated with a cell annotation.
1722
     */
1723 1
    private function readTextObject()
1724
    {
1725 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
1726 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1727
1728
        // move stream pointer to next record
1729 1
        $this->pos += 4 + $length;
1730
1731 1
        if ($this->readDataOnly) {
1732
            return;
1733
        }
1734
1735
        // recordData consists of an array of subrecords looking like this:
1736
        //    grbit: 2 bytes; Option Flags
1737
        //    rot: 2 bytes; rotation
1738
        //    cchText: 2 bytes; length of the text (in the first continue record)
1739
        //    cbRuns: 2 bytes; length of the formatting (in the second continue record)
1740
        // followed by the continuation records containing the actual text and formatting
1741 1
        $grbitOpts = self::getUInt2d($recordData, 0);
1742 1
        $rot = self::getUInt2d($recordData, 2);
1743 1
        $cchText = self::getUInt2d($recordData, 10);
1744 1
        $cbRuns = self::getUInt2d($recordData, 12);
1745 1
        $text = $this->getSplicedRecordData();
1746
1747 1
        $textByte = $text['spliceOffsets'][1] - $text['spliceOffsets'][0] - 1;
1748 1
        $textStr = substr($text['recordData'], $text['spliceOffsets'][0] + 1, $textByte);
1749
        // get 1 byte
1750 1
        $is16Bit = ord($text['recordData'][0]);
1751
        // it is possible to use a compressed format,
1752
        // which omits the high bytes of all characters, if they are all zero
1753 1
        if (($is16Bit & 0x01) === 0) {
1754 1
            $textStr = StringHelper::ConvertEncoding($textStr, 'UTF-8', 'ISO-8859-1');
1755
        } else {
1756
            $textStr = $this->decodeCodepage($textStr);
1757
        }
1758
1759 1
        $this->textObjects[$this->textObjRef] = [
1760 1
            'text' => $textStr,
1761 1
            'format' => substr($text['recordData'], $text['spliceOffsets'][1], $cbRuns),
1762 1
            'alignment' => $grbitOpts,
1763 1
            'rotation' => $rot,
1764
        ];
1765 1
    }
1766
1767
    /**
1768
     * Read BOF.
1769
     */
1770 22
    private function readBof()
1771
    {
1772 22
        $length = self::getUInt2d($this->data, $this->pos + 2);
1773 22
        $recordData = substr($this->data, $this->pos + 4, $length);
1774
1775
        // move stream pointer to next record
1776 22
        $this->pos += 4 + $length;
1777
1778
        // offset: 2; size: 2; type of the following data
1779 22
        $substreamType = self::getUInt2d($recordData, 2);
1780
1781
        switch ($substreamType) {
1782 22
            case self::XLS_WORKBOOKGLOBALS:
1783 22
                $version = self::getUInt2d($recordData, 0);
1784 22
                if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) {
1785
                    throw new Exception('Cannot read this Excel file. Version is too old.');
1786
                }
1787 22
                $this->version = $version;
1788
1789 22
                break;
1790 20
            case self::XLS_WORKSHEET:
1791
                // do not use this version information for anything
1792
                // it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream
1793 20
                break;
1794
            default:
1795
                // substream, e.g. chart
1796
                // just skip the entire substream
1797
                do {
1798
                    $code = self::getUInt2d($this->data, $this->pos);
1799
                    $this->readDefault();
1800
                } while ($code != self::XLS_TYPE_EOF && $this->pos < $this->dataSize);
1801
1802
                break;
1803
        }
1804 22
    }
1805
1806
    /**
1807
     * FILEPASS.
1808
     *
1809
     * This record is part of the File Protection Block. It
1810
     * contains information about the read/write password of the
1811
     * file. All record contents following this record will be
1812
     * encrypted.
1813
     *
1814
     * --    "OpenOffice.org's Documentation of the Microsoft
1815
     *         Excel File Format"
1816
     *
1817
     * The decryption functions and objects used from here on in
1818
     * are based on the source of Spreadsheet-ParseExcel:
1819
     * http://search.cpan.org/~jmcnamara/Spreadsheet-ParseExcel/
1820
     */
1821
    private function readFilepass()
1822
    {
1823
        $length = self::getUInt2d($this->data, $this->pos + 2);
1824
1825
        if ($length != 54) {
1826
            throw new Exception('Unexpected file pass record length');
1827
        }
1828
1829
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1830
1831
        // move stream pointer to next record
1832
        $this->pos += 4 + $length;
1833
1834
        if (!$this->verifyPassword('VelvetSweatshop', substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) {
1835
            throw new Exception('Decryption password incorrect');
1836
        }
1837
1838
        $this->encryption = self::MS_BIFF_CRYPTO_RC4;
1839
1840
        // Decryption required from the record after next onwards
1841
        $this->encryptionStartPos = $this->pos + self::getUInt2d($this->data, $this->pos + 2);
1842
    }
1843
1844
    /**
1845
     * Make an RC4 decryptor for the given block.
1846
     *
1847
     * @param int Block for which to create decrypto
1848
     * @param string $valContext MD5 context state
1849
     * @param mixed $block
1850
     *
1851
     * @return Xls\RC4
1852
     */
1853
    private function makeKey($block, $valContext)
1854
    {
1855
        $pwarray = str_repeat("\0", 64);
1856
1857
        for ($i = 0; $i < 5; ++$i) {
1858
            $pwarray[$i] = $valContext[$i];
1859
        }
1860
1861
        $pwarray[5] = chr($block & 0xff);
1862
        $pwarray[6] = chr(($block >> 8) & 0xff);
1863
        $pwarray[7] = chr(($block >> 16) & 0xff);
1864
        $pwarray[8] = chr(($block >> 24) & 0xff);
1865
1866
        $pwarray[9] = "\x80";
1867
        $pwarray[56] = "\x48";
1868
1869
        $md5 = new Xls\MD5();
1870
        $md5->add($pwarray);
1871
1872
        $s = $md5->getContext();
1873
1874
        return new Xls\RC4($s);
1875
    }
1876
1877
    /**
1878
     * Verify RC4 file password.
1879
     *
1880
     * @param string $password Password to check
1881
     * @param string $docid Document id
1882
     * @param string $salt_data Salt data
1883
     * @param string $hashedsalt_data Hashed salt data
1884
     * @param string $valContext Set to the MD5 context of the value
1885
     *
1886
     * @return bool Success
1887
     */
1888
    private function verifyPassword($password, $docid, $salt_data, $hashedsalt_data, &$valContext)
1889
    {
1890
        $pwarray = str_repeat("\0", 64);
1891
1892
        for ($i = 0; $i < strlen($password); ++$i) {
1893
            $o = ord(substr($password, $i, 1));
1894
            $pwarray[2 * $i] = chr($o & 0xff);
1895
            $pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff);
1896
        }
1897
        $pwarray[2 * $i] = chr(0x80);
1898
        $pwarray[56] = chr(($i << 4) & 0xff);
1899
1900
        $md5 = new Xls\MD5();
1901
        $md5->add($pwarray);
1902
1903
        $mdContext1 = $md5->getContext();
1904
1905
        $offset = 0;
1906
        $keyoffset = 0;
1907
        $tocopy = 5;
1908
1909
        $md5->reset();
1910
1911
        while ($offset != 16) {
1912
            if ((64 - $offset) < 5) {
1913
                $tocopy = 64 - $offset;
1914
            }
1915
            for ($i = 0; $i <= $tocopy; ++$i) {
1916
                $pwarray[$offset + $i] = $mdContext1[$keyoffset + $i];
1917
            }
1918
            $offset += $tocopy;
1919
1920
            if ($offset == 64) {
1921
                $md5->add($pwarray);
1922
                $keyoffset = $tocopy;
1923
                $tocopy = 5 - $tocopy;
1924
                $offset = 0;
1925
1926
                continue;
1927
            }
1928
1929
            $keyoffset = 0;
1930
            $tocopy = 5;
1931
            for ($i = 0; $i < 16; ++$i) {
1932
                $pwarray[$offset + $i] = $docid[$i];
1933
            }
1934
            $offset += 16;
1935
        }
1936
1937
        $pwarray[16] = "\x80";
1938
        for ($i = 0; $i < 47; ++$i) {
1939
            $pwarray[17 + $i] = "\0";
1940
        }
1941
        $pwarray[56] = "\x80";
1942
        $pwarray[57] = "\x0a";
1943
1944
        $md5->add($pwarray);
1945
        $valContext = $md5->getContext();
1946
1947
        $key = $this->makeKey(0, $valContext);
1948
1949
        $salt = $key->RC4($salt_data);
1950
        $hashedsalt = $key->RC4($hashedsalt_data);
1951
1952
        $salt .= "\x80" . str_repeat("\0", 47);
1953
        $salt[56] = "\x80";
1954
1955
        $md5->reset();
1956
        $md5->add($salt);
1957
        $mdContext2 = $md5->getContext();
1958
1959
        return $mdContext2 == $hashedsalt;
1960
    }
1961
1962
    /**
1963
     * CODEPAGE.
1964
     *
1965
     * This record stores the text encoding used to write byte
1966
     * strings, stored as MS Windows code page identifier.
1967
     *
1968
     * --    "OpenOffice.org's Documentation of the Microsoft
1969
     *         Excel File Format"
1970
     */
1971 19
    private function readCodepage()
1972
    {
1973 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
1974 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1975
1976
        // move stream pointer to next record
1977 19
        $this->pos += 4 + $length;
1978
1979
        // offset: 0; size: 2; code page identifier
1980 19
        $codepage = self::getUInt2d($recordData, 0);
1981
1982 19
        $this->codepage = CodePage::numberToName($codepage);
1983 19
    }
1984
1985
    /**
1986
     * DATEMODE.
1987
     *
1988
     * This record specifies the base date for displaying date
1989
     * values. All dates are stored as count of days past this
1990
     * base date. In BIFF2-BIFF4 this record is part of the
1991
     * Calculation Settings Block. In BIFF5-BIFF8 it is
1992
     * stored in the Workbook Globals Substream.
1993
     *
1994
     * --    "OpenOffice.org's Documentation of the Microsoft
1995
     *         Excel File Format"
1996
     */
1997 19
    private function readDateMode()
1998
    {
1999 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
2000 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2001
2002
        // move stream pointer to next record
2003 19
        $this->pos += 4 + $length;
2004
2005
        // offset: 0; size: 2; 0 = base 1900, 1 = base 1904
2006 19
        Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
2007 19
        if (ord($recordData[0]) == 1) {
2008
            Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
2009
        }
2010 19
    }
2011
2012
    /**
2013
     * Read a FONT record.
2014
     */
2015 19
    private function readFont()
2016
    {
2017 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
2018 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2019
2020
        // move stream pointer to next record
2021 19
        $this->pos += 4 + $length;
2022
2023 19
        if (!$this->readDataOnly) {
2024 18
            $objFont = new Font();
2025
2026
            // offset: 0; size: 2; height of the font (in twips = 1/20 of a point)
2027 18
            $size = self::getUInt2d($recordData, 0);
2028 18
            $objFont->setSize($size / 20);
2029
2030
            // offset: 2; size: 2; option flags
2031
            // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8)
2032
            // bit: 1; mask 0x0002; italic
2033 18
            $isItalic = (0x0002 & self::getUInt2d($recordData, 2)) >> 1;
2034 18
            if ($isItalic) {
2035 5
                $objFont->setItalic(true);
2036
            }
2037
2038
            // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8)
2039
            // bit: 3; mask 0x0008; strikethrough
2040 18
            $isStrike = (0x0008 & self::getUInt2d($recordData, 2)) >> 3;
2041 18
            if ($isStrike) {
2042
                $objFont->setStrikethrough(true);
2043
            }
2044
2045
            // offset: 4; size: 2; colour index
2046 18
            $colorIndex = self::getUInt2d($recordData, 4);
2047 18
            $objFont->colorIndex = $colorIndex;
2048
2049
            // offset: 6; size: 2; font weight
2050 18
            $weight = self::getUInt2d($recordData, 6);
2051
            switch ($weight) {
2052 18
                case 0x02BC:
2053 17
                    $objFont->setBold(true);
2054
2055 17
                    break;
2056
            }
2057
2058
            // offset: 8; size: 2; escapement type
2059 18
            $escapement = self::getUInt2d($recordData, 8);
2060
            switch ($escapement) {
2061 18
                case 0x0001:
2062
                    $objFont->setSuperscript(true);
2063
2064
                    break;
2065 18
                case 0x0002:
2066
                    $objFont->setSubscript(true);
2067
2068
                    break;
2069
            }
2070
2071
            // offset: 10; size: 1; underline type
2072 18
            $underlineType = ord($recordData[10]);
2073
            switch ($underlineType) {
2074 18
                case 0x00:
2075 18
                    break; // no underline
2076 2
                case 0x01:
2077 2
                    $objFont->setUnderline(Font::UNDERLINE_SINGLE);
2078
2079 2
                    break;
2080
                case 0x02:
2081
                    $objFont->setUnderline(Font::UNDERLINE_DOUBLE);
2082
2083
                    break;
2084
                case 0x21:
2085
                    $objFont->setUnderline(Font::UNDERLINE_SINGLEACCOUNTING);
2086
2087
                    break;
2088
                case 0x22:
2089
                    $objFont->setUnderline(Font::UNDERLINE_DOUBLEACCOUNTING);
2090
2091
                    break;
2092
            }
2093
2094
            // offset: 11; size: 1; font family
2095
            // offset: 12; size: 1; character set
2096
            // offset: 13; size: 1; not used
2097
            // offset: 14; size: var; font name
2098 18 View Code Duplication
            if ($this->version == self::XLS_BIFF8) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2099 18
                $string = self::readUnicodeStringShort(substr($recordData, 14));
2100
            } else {
2101
                $string = $this->readByteStringShort(substr($recordData, 14));
2102
            }
2103 18
            $objFont->setName($string['value']);
2104
2105 18
            $this->objFonts[] = $objFont;
2106
        }
2107 19
    }
2108
2109
    /**
2110
     * FORMAT.
2111
     *
2112
     * This record contains information about a number format.
2113
     * All FORMAT records occur together in a sequential list.
2114
     *
2115
     * In BIFF2-BIFF4 other records referencing a FORMAT record
2116
     * contain a zero-based index into this list. From BIFF5 on
2117
     * the FORMAT record contains the index itself that will be
2118
     * used by other records.
2119
     *
2120
     * --    "OpenOffice.org's Documentation of the Microsoft
2121
     *         Excel File Format"
2122
     */
2123 18
    private function readFormat()
2124
    {
2125 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
2126 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2127
2128
        // move stream pointer to next record
2129 18
        $this->pos += 4 + $length;
2130
2131 18
        if (!$this->readDataOnly) {
2132 17
            $indexCode = self::getUInt2d($recordData, 0);
2133
2134 17 View Code Duplication
            if ($this->version == self::XLS_BIFF8) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2135 17
                $string = self::readUnicodeStringLong(substr($recordData, 2));
2136
            } else {
2137
                // BIFF7
2138
                $string = $this->readByteStringShort(substr($recordData, 2));
2139
            }
2140
2141 17
            $formatString = $string['value'];
2142 17
            $this->formats[$indexCode] = $formatString;
2143
        }
2144 18
    }
2145
2146
    /**
2147
     * XF - Extended Format.
2148
     *
2149
     * This record contains formatting information for cells, rows, columns or styles.
2150
     * According to http://support.microsoft.com/kb/147732 there are always at least 15 cell style XF
2151
     * and 1 cell XF.
2152
     * Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF
2153
     * and XF record 15 is a cell XF
2154
     * We only read the first cell style XF and skip the remaining cell style XF records
2155
     * We read all cell XF records.
2156
     *
2157
     * --    "OpenOffice.org's Documentation of the Microsoft
2158
     *         Excel File Format"
2159
     */
2160 19
    private function readXf()
2161
    {
2162 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
2163 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2164
2165
        // move stream pointer to next record
2166 19
        $this->pos += 4 + $length;
2167
2168 19
        $objStyle = new Style();
2169
2170 19
        if (!$this->readDataOnly) {
2171
            // offset:  0; size: 2; Index to FONT record
2172 18
            if (self::getUInt2d($recordData, 0) < 4) {
2173 18
                $fontIndex = self::getUInt2d($recordData, 0);
2174
            } else {
2175
                // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
2176
                // check the OpenOffice documentation of the FONT record
2177 17
                $fontIndex = self::getUInt2d($recordData, 0) - 1;
2178
            }
2179 18
            $objStyle->setFont($this->objFonts[$fontIndex]);
2180
2181
            // offset:  2; size: 2; Index to FORMAT record
2182 18
            $numberFormatIndex = self::getUInt2d($recordData, 2);
2183 18
            if (isset($this->formats[$numberFormatIndex])) {
2184
                // then we have user-defined format code
2185 15
                $numberFormat = ['formatCode' => $this->formats[$numberFormatIndex]];
2186 18
            } elseif (($code = NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') {
2187
                // then we have built-in format code
2188 18
                $numberFormat = ['formatCode' => $code];
2189
            } else {
2190
                // we set the general format code
2191 1
                $numberFormat = ['formatCode' => 'General'];
2192
            }
2193 18
            $objStyle->getNumberFormat()->setFormatCode($numberFormat['formatCode']);
2194
2195
            // offset:  4; size: 2; XF type, cell protection, and parent style XF
2196
            // 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...
2197 18
            $xfTypeProt = self::getUInt2d($recordData, 4);
2198
            // bit 0; mask 0x01; 1 = cell is locked
2199 18
            $isLocked = (0x01 & $xfTypeProt) >> 0;
2200 18
            $objStyle->getProtection()->setLocked($isLocked ? Protection::PROTECTION_INHERIT : Protection::PROTECTION_UNPROTECTED);
2201
2202
            // bit 1; mask 0x02; 1 = Formula is hidden
2203 18
            $isHidden = (0x02 & $xfTypeProt) >> 1;
2204 18
            $objStyle->getProtection()->setHidden($isHidden ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED);
2205
2206
            // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF
2207 18
            $isCellStyleXf = (0x04 & $xfTypeProt) >> 2;
2208
2209
            // offset:  6; size: 1; Alignment and text break
2210
            // bit 2-0, mask 0x07; horizontal alignment
2211 18
            $horAlign = (0x07 & ord($recordData[6])) >> 0;
2212
            switch ($horAlign) {
2213 18
                case 0:
2214 18
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_GENERAL);
2215
2216 18
                    break;
2217 12
                case 1:
2218 2
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
2219
2220 2
                    break;
2221 12
                case 2:
2222 10
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
2223
2224 10
                    break;
2225 2
                case 3:
2226 2
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
2227
2228 2
                    break;
2229 2
                case 4:
2230
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_FILL);
2231
2232
                    break;
2233 2
                case 5:
2234 2
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_JUSTIFY);
2235
2236 2
                    break;
2237
                case 6:
2238
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER_CONTINUOUS);
2239
2240
                    break;
2241
            }
2242
            // bit 3, mask 0x08; wrap text
2243 18
            $wrapText = (0x08 & ord($recordData[6])) >> 3;
2244
            switch ($wrapText) {
2245 18
                case 0:
2246 18
                    $objStyle->getAlignment()->setWrapText(false);
2247
2248 18
                    break;
2249 2
                case 1:
2250 2
                    $objStyle->getAlignment()->setWrapText(true);
2251
2252 2
                    break;
2253
            }
2254
            // bit 6-4, mask 0x70; vertical alignment
2255 18
            $vertAlign = (0x70 & ord($recordData[6])) >> 4;
2256
            switch ($vertAlign) {
2257 18
                case 0:
2258
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_TOP);
2259
2260
                    break;
2261 18
                case 1:
2262 2
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
2263
2264 2
                    break;
2265 18
                case 2:
2266 18
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_BOTTOM);
2267
2268 18
                    break;
2269
                case 3:
2270
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_JUSTIFY);
2271
2272
                    break;
2273
            }
2274
2275 18
            if ($this->version == self::XLS_BIFF8) {
2276
                // offset:  7; size: 1; XF_ROTATION: Text rotation angle
2277 18
                $angle = ord($recordData[7]);
2278 18
                $rotation = 0;
2279 18
                if ($angle <= 90) {
2280 18
                    $rotation = $angle;
2281
                } elseif ($angle <= 180) {
2282
                    $rotation = 90 - $angle;
2283
                } elseif ($angle == 255) {
2284
                    $rotation = -165;
2285
                }
2286 18
                $objStyle->getAlignment()->setTextRotation($rotation);
2287
2288
                // offset:  8; size: 1; Indentation, shrink to cell size, and text direction
2289
                // bit: 3-0; mask: 0x0F; indent level
2290 18
                $indent = (0x0F & ord($recordData[8])) >> 0;
2291 18
                $objStyle->getAlignment()->setIndent($indent);
2292
2293
                // bit: 4; mask: 0x10; 1 = shrink content to fit into cell
2294 18
                $shrinkToFit = (0x10 & ord($recordData[8])) >> 4;
2295
                switch ($shrinkToFit) {
2296 18
                    case 0:
2297 18
                        $objStyle->getAlignment()->setShrinkToFit(false);
2298
2299 18
                        break;
2300 1
                    case 1:
2301 1
                        $objStyle->getAlignment()->setShrinkToFit(true);
2302
2303 1
                        break;
2304
                }
2305
2306
                // offset:  9; size: 1; Flags used for attribute groups
2307
2308
                // offset: 10; size: 4; Cell border lines and background area
2309
                // bit: 3-0; mask: 0x0000000F; left style
2310 18 View Code Duplication
                if ($bordersLeftStyle = Xls\Style\Border::lookup((0x0000000F & self::getInt4d($recordData, 10)) >> 0)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2311 18
                    $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle);
2312
                }
2313
                // bit: 7-4; mask: 0x000000F0; right style
2314 18 View Code Duplication
                if ($bordersRightStyle = Xls\Style\Border::lookup((0x000000F0 & self::getInt4d($recordData, 10)) >> 4)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2315 18
                    $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle);
2316
                }
2317
                // bit: 11-8; mask: 0x00000F00; top style
2318 18 View Code Duplication
                if ($bordersTopStyle = Xls\Style\Border::lookup((0x00000F00 & self::getInt4d($recordData, 10)) >> 8)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2319 18
                    $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle);
2320
                }
2321
                // bit: 15-12; mask: 0x0000F000; bottom style
2322 18 View Code Duplication
                if ($bordersBottomStyle = Xls\Style\Border::lookup((0x0000F000 & self::getInt4d($recordData, 10)) >> 12)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2323 18
                    $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle);
2324
                }
2325
                // bit: 22-16; mask: 0x007F0000; left color
2326 18
                $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::getInt4d($recordData, 10)) >> 16;
2327
2328
                // bit: 29-23; mask: 0x3F800000; right color
2329 18
                $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::getInt4d($recordData, 10)) >> 23;
2330
2331
                // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom
2332 18
                $diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false;
2333
2334
                // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right
2335 18
                $diagonalUp = (0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false;
2336
2337 18
                if ($diagonalUp == false && $diagonalDown == false) {
2338 18
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE);
2339
                } elseif ($diagonalUp == true && $diagonalDown == false) {
2340
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP);
2341
                } elseif ($diagonalUp == false && $diagonalDown == true) {
2342
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN);
2343
                } elseif ($diagonalUp == true && $diagonalDown == true) {
2344
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH);
2345
                }
2346
2347
                // 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...
2348
                // bit: 6-0; mask: 0x0000007F; top color
2349 18
                $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::getInt4d($recordData, 14)) >> 0;
2350
2351
                // bit: 13-7; mask: 0x00003F80; bottom color
2352 18
                $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::getInt4d($recordData, 14)) >> 7;
2353
2354
                // bit: 20-14; mask: 0x001FC000; diagonal color
2355 18
                $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::getInt4d($recordData, 14)) >> 14;
2356
2357
                // bit: 24-21; mask: 0x01E00000; diagonal style
2358 18 View Code Duplication
                if ($bordersDiagonalStyle = Xls\Style\Border::lookup((0x01E00000 & self::getInt4d($recordData, 14)) >> 21)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2359 18
                    $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle);
2360
                }
2361
2362
                // bit: 31-26; mask: 0xFC000000 fill pattern
2363 18
                if ($fillType = Xls\Style\FillPattern::lookup((0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) {
2364 18
                    $objStyle->getFill()->setFillType($fillType);
2365
                }
2366
                // offset: 18; size: 2; pattern and background colour
2367
                // bit: 6-0; mask: 0x007F; color index for pattern color
2368 18
                $objStyle->getFill()->startcolorIndex = (0x007F & self::getUInt2d($recordData, 18)) >> 0;
2369
2370
                // bit: 13-7; mask: 0x3F80; color index for pattern background
2371 18
                $objStyle->getFill()->endcolorIndex = (0x3F80 & self::getUInt2d($recordData, 18)) >> 7;
2372
            } else {
2373
                // BIFF5
2374
2375
                // offset: 7; size: 1; Text orientation and flags
2376
                $orientationAndFlags = ord($recordData[7]);
2377
2378
                // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation
2379
                $xfOrientation = (0x03 & $orientationAndFlags) >> 0;
2380
                switch ($xfOrientation) {
2381
                    case 0:
2382
                        $objStyle->getAlignment()->setTextRotation(0);
2383
2384
                        break;
2385
                    case 1:
2386
                        $objStyle->getAlignment()->setTextRotation(-165);
2387
2388
                        break;
2389
                    case 2:
2390
                        $objStyle->getAlignment()->setTextRotation(90);
2391
2392
                        break;
2393
                    case 3:
2394
                        $objStyle->getAlignment()->setTextRotation(-90);
2395
2396
                        break;
2397
                }
2398
2399
                // offset: 8; size: 4; cell border lines and background area
2400
                $borderAndBackground = self::getInt4d($recordData, 8);
2401
2402
                // bit: 6-0; mask: 0x0000007F; color index for pattern color
2403
                $objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0;
2404
2405
                // bit: 13-7; mask: 0x00003F80; color index for pattern background
2406
                $objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7;
2407
2408
                // bit: 21-16; mask: 0x003F0000; fill pattern
2409
                $objStyle->getFill()->setFillType(Xls\Style\FillPattern::lookup((0x003F0000 & $borderAndBackground) >> 16));
2410
2411
                // bit: 24-22; mask: 0x01C00000; bottom line style
2412
                $objStyle->getBorders()->getBottom()->setBorderStyle(Xls\Style\Border::lookup((0x01C00000 & $borderAndBackground) >> 22));
2413
2414
                // bit: 31-25; mask: 0xFE000000; bottom line color
2415
                $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25;
2416
2417
                // offset: 12; size: 4; cell border lines
2418
                $borderLines = self::getInt4d($recordData, 12);
2419
2420
                // bit: 2-0; mask: 0x00000007; top line style
2421
                $objStyle->getBorders()->getTop()->setBorderStyle(Xls\Style\Border::lookup((0x00000007 & $borderLines) >> 0));
2422
2423
                // bit: 5-3; mask: 0x00000038; left line style
2424
                $objStyle->getBorders()->getLeft()->setBorderStyle(Xls\Style\Border::lookup((0x00000038 & $borderLines) >> 3));
2425
2426
                // bit: 8-6; mask: 0x000001C0; right line style
2427
                $objStyle->getBorders()->getRight()->setBorderStyle(Xls\Style\Border::lookup((0x000001C0 & $borderLines) >> 6));
2428
2429
                // bit: 15-9; mask: 0x0000FE00; top line color index
2430
                $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9;
2431
2432
                // bit: 22-16; mask: 0x007F0000; left line color index
2433
                $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16;
2434
2435
                // bit: 29-23; mask: 0x3F800000; right line color index
2436
                $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23;
2437
            }
2438
2439
            // add cellStyleXf or cellXf and update mapping
2440 18
            if ($isCellStyleXf) {
2441
                // we only read one style XF record which is always the first
2442 18
                if ($this->xfIndex == 0) {
2443 18
                    $this->spreadsheet->addCellStyleXf($objStyle);
2444 18
                    $this->mapCellStyleXfIndex[$this->xfIndex] = 0;
2445
                }
2446
            } else {
2447
                // we read all cell XF records
2448 18
                $this->spreadsheet->addCellXf($objStyle);
2449 18
                $this->mapCellXfIndex[$this->xfIndex] = count($this->spreadsheet->getCellXfCollection()) - 1;
2450
            }
2451
2452
            // update XF index for when we read next record
2453 18
            ++$this->xfIndex;
2454
        }
2455 19
    }
2456
2457 3
    private function readXfExt()
2458
    {
2459 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
2460 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2461
2462
        // move stream pointer to next record
2463 3
        $this->pos += 4 + $length;
2464
2465 3
        if (!$this->readDataOnly) {
2466
            // offset: 0; size: 2; 0x087D = repeated header
2467
2468
            // offset: 2; size: 2
2469
2470
            // offset: 4; size: 8; not used
2471
2472
            // offset: 12; size: 2; record version
2473
2474
            // offset: 14; size: 2; index to XF record which this record modifies
2475 3
            $ixfe = self::getUInt2d($recordData, 14);
2476
2477
            // offset: 16; size: 2; not used
2478
2479
            // offset: 18; size: 2; number of extension properties that follow
2480 3
            $cexts = self::getUInt2d($recordData, 18);
2481
2482
            // start reading the actual extension data
2483 3
            $offset = 20;
2484 3
            while ($offset < $length) {
2485
                // extension type
2486 3
                $extType = self::getUInt2d($recordData, $offset);
2487
2488
                // extension length
2489 3
                $cb = self::getUInt2d($recordData, $offset + 2);
2490
2491
                // extension data
2492 3
                $extData = substr($recordData, $offset + 4, $cb);
2493
2494
                switch ($extType) {
2495 3 View Code Duplication
                    case 4:        // fill start color
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2496 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2497 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2498
2499 3
                        if ($xclfType == 2) {
2500 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2501
2502
                            // modify the relevant style property
2503 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2504 1
                                $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
2505 1
                                $fill->getStartColor()->setRGB($rgb);
2506 1
                                unset($fill->startcolorIndex); // normal color index does not apply, discard
2507
                            }
2508
                        }
2509
2510 3
                        break;
2511 3 View Code Duplication
                    case 5:        // fill end color
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2512 1
                        $xclfType = self::getUInt2d($extData, 0); // color type
2513 1
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2514
2515 1
                        if ($xclfType == 2) {
2516 1
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2517
2518
                            // modify the relevant style property
2519 1
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2520 1
                                $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
2521 1
                                $fill->getEndColor()->setRGB($rgb);
2522 1
                                unset($fill->endcolorIndex); // normal color index does not apply, discard
2523
                            }
2524
                        }
2525
2526 1
                        break;
2527 3 View Code Duplication
                    case 7:        // border color top
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2528 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2529 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2530
2531 3
                        if ($xclfType == 2) {
2532 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2533
2534
                            // modify the relevant style property
2535 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2536 1
                                $top = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getTop();
2537 1
                                $top->getColor()->setRGB($rgb);
2538 1
                                unset($top->colorIndex); // normal color index does not apply, discard
2539
                            }
2540
                        }
2541
2542 3
                        break;
2543 3 View Code Duplication
                    case 8:        // border color bottom
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2544 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2545 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2546
2547 3
                        if ($xclfType == 2) {
2548 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2549
2550
                            // modify the relevant style property
2551 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2552 1
                                $bottom = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getBottom();
2553 1
                                $bottom->getColor()->setRGB($rgb);
2554 1
                                unset($bottom->colorIndex); // normal color index does not apply, discard
2555
                            }
2556
                        }
2557
2558 3
                        break;
2559 3 View Code Duplication
                    case 9:        // border color left
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2560 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2561 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2562
2563 3
                        if ($xclfType == 2) {
2564 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2565
2566
                            // modify the relevant style property
2567 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2568 1
                                $left = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getLeft();
2569 1
                                $left->getColor()->setRGB($rgb);
2570 1
                                unset($left->colorIndex); // normal color index does not apply, discard
2571
                            }
2572
                        }
2573
2574 3
                        break;
2575 3 View Code Duplication
                    case 10:        // border color right
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2576 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2577 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2578
2579 3
                        if ($xclfType == 2) {
2580 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2581
2582
                            // modify the relevant style property
2583 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2584 1
                                $right = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getRight();
2585 1
                                $right->getColor()->setRGB($rgb);
2586 1
                                unset($right->colorIndex); // normal color index does not apply, discard
2587
                            }
2588
                        }
2589
2590 3
                        break;
2591 3 View Code Duplication
                    case 11:        // border color diagonal
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2592
                        $xclfType = self::getUInt2d($extData, 0); // color type
2593
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2594
2595
                        if ($xclfType == 2) {
2596
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2597
2598
                            // modify the relevant style property
2599
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2600
                                $diagonal = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getDiagonal();
2601
                                $diagonal->getColor()->setRGB($rgb);
2602
                                unset($diagonal->colorIndex); // normal color index does not apply, discard
2603
                            }
2604
                        }
2605
2606
                        break;
2607 3 View Code Duplication
                    case 13:    // font color
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2608 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2609 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2610
2611 3
                        if ($xclfType == 2) {
2612 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2613
2614
                            // modify the relevant style property
2615 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2616 1
                                $font = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont();
2617 1
                                $font->getColor()->setRGB($rgb);
2618 1
                                unset($font->colorIndex); // normal color index does not apply, discard
2619
                            }
2620
                        }
2621
2622 3
                        break;
2623
                }
2624
2625 3
                $offset += $cb;
2626
            }
2627
        }
2628 3
    }
2629
2630
    /**
2631
     * Read STYLE record.
2632
     */
2633 19
    private function readStyle()
2634
    {
2635 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
2636 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2637
2638
        // move stream pointer to next record
2639 19
        $this->pos += 4 + $length;
2640
2641 19
        if (!$this->readDataOnly) {
2642
            // offset: 0; size: 2; index to XF record and flag for built-in style
2643 18
            $ixfe = self::getUInt2d($recordData, 0);
2644
2645
            // bit: 11-0; mask 0x0FFF; index to XF record
2646 18
            $xfIndex = (0x0FFF & $ixfe) >> 0;
2647
2648
            // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style
2649 18
            $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15);
2650
2651 18
            if ($isBuiltIn) {
2652
                // offset: 2; size: 1; identifier for built-in style
2653 18
                $builtInId = ord($recordData[2]);
2654
2655
                switch ($builtInId) {
2656 18
                    case 0x00:
2657
                        // currently, we are not using this for anything
2658 18
                        break;
2659
                    default:
2660 14
                        break;
2661
                }
2662
            }
2663
            // user-defined; not supported by PhpSpreadsheet
2664
        }
2665 19
    }
2666
2667
    /**
2668
     * Read PALETTE record.
2669
     */
2670 5
    private function readPalette()
2671
    {
2672 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
2673 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2674
2675
        // move stream pointer to next record
2676 5
        $this->pos += 4 + $length;
2677
2678 5
        if (!$this->readDataOnly) {
2679
            // offset: 0; size: 2; number of following colors
2680 5
            $nm = self::getUInt2d($recordData, 0);
2681
2682
            // list of RGB colors
2683 5
            for ($i = 0; $i < $nm; ++$i) {
2684 5
                $rgb = substr($recordData, 2 + 4 * $i, 4);
2685 5
                $this->palette[] = self::readRGB($rgb);
2686
            }
2687
        }
2688 5
    }
2689
2690
    /**
2691
     * SHEET.
2692
     *
2693
     * This record is  located in the  Workbook Globals
2694
     * Substream  and represents a sheet inside the workbook.
2695
     * One SHEET record is written for each sheet. It stores the
2696
     * sheet name and a stream offset to the BOF record of the
2697
     * respective Sheet Substream within the Workbook Stream.
2698
     *
2699
     * --    "OpenOffice.org's Documentation of the Microsoft
2700
     *         Excel File Format"
2701
     */
2702 22
    private function readSheet()
2703
    {
2704 22
        $length = self::getUInt2d($this->data, $this->pos + 2);
2705 22
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2706
2707
        // offset: 0; size: 4; absolute stream position of the BOF record of the sheet
2708
        // NOTE: not encrypted
2709 22
        $rec_offset = self::getInt4d($this->data, $this->pos + 4);
2710
2711
        // move stream pointer to next record
2712 22
        $this->pos += 4 + $length;
2713
2714
        // offset: 4; size: 1; sheet state
2715 22
        switch (ord($recordData[4])) {
2716 22
            case 0x00:
2717 22
                $sheetState = Worksheet::SHEETSTATE_VISIBLE;
2718
2719 22
                break;
2720
            case 0x01:
2721
                $sheetState = Worksheet::SHEETSTATE_HIDDEN;
2722
2723
                break;
2724
            case 0x02:
2725
                $sheetState = Worksheet::SHEETSTATE_VERYHIDDEN;
2726
2727
                break;
2728
            default:
2729
                $sheetState = Worksheet::SHEETSTATE_VISIBLE;
2730
2731
                break;
2732
        }
2733
2734
        // offset: 5; size: 1; sheet type
2735 22
        $sheetType = ord($recordData[5]);
2736
2737
        // offset: 6; size: var; sheet name
2738 22 View Code Duplication
        if ($this->version == self::XLS_BIFF8) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2739 22
            $string = self::readUnicodeStringShort(substr($recordData, 6));
2740 22
            $rec_name = $string['value'];
2741
        } elseif ($this->version == self::XLS_BIFF7) {
2742
            $string = $this->readByteStringShort(substr($recordData, 6));
2743
            $rec_name = $string['value'];
2744
        }
2745
2746 22
        $this->sheets[] = [
2747 22
            'name' => $rec_name,
2748 22
            'offset' => $rec_offset,
2749 22
            'sheetState' => $sheetState,
2750 22
            'sheetType' => $sheetType,
2751
        ];
2752 22
    }
2753
2754
    /**
2755
     * Read EXTERNALBOOK record.
2756
     */
2757 5
    private function readExternalBook()
2758
    {
2759 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
2760 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2761
2762
        // move stream pointer to next record
2763 5
        $this->pos += 4 + $length;
2764
2765
        // offset within record data
2766 5
        $offset = 0;
2767
2768
        // there are 4 types of records
2769 5
        if (strlen($recordData) > 4) {
2770
            // external reference
2771
            // offset: 0; size: 2; number of sheet names ($nm)
2772
            $nm = self::getUInt2d($recordData, 0);
2773
            $offset += 2;
2774
2775
            // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length)
2776
            $encodedUrlString = self::readUnicodeStringLong(substr($recordData, 2));
2777
            $offset += $encodedUrlString['size'];
2778
2779
            // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length)
2780
            $externalSheetNames = [];
2781
            for ($i = 0; $i < $nm; ++$i) {
2782
                $externalSheetNameString = self::readUnicodeStringLong(substr($recordData, $offset));
2783
                $externalSheetNames[] = $externalSheetNameString['value'];
2784
                $offset += $externalSheetNameString['size'];
2785
            }
2786
2787
            // store the record data
2788
            $this->externalBooks[] = [
2789
                'type' => 'external',
2790
                'encodedUrl' => $encodedUrlString['value'],
2791
                'externalSheetNames' => $externalSheetNames,
2792
            ];
2793 5
        } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) {
2794
            // internal reference
2795
            // offset: 0; size: 2; number of sheet in this document
2796
            // 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...
2797 5
            $this->externalBooks[] = [
2798 5
                'type' => 'internal',
2799
            ];
2800
        } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) {
2801
            // add-in function
2802
            // 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...
2803
            $this->externalBooks[] = [
2804
                'type' => 'addInFunction',
2805
            ];
2806
        } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) {
2807
            // DDE links, OLE links
2808
            // 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...
2809
            // offset: 2; size: var; encoded source document name
2810
            $this->externalBooks[] = [
2811
                'type' => 'DDEorOLE',
2812
            ];
2813
        }
2814 5
    }
2815
2816
    /**
2817
     * Read EXTERNNAME record.
2818
     */
2819
    private function readExternName()
2820
    {
2821
        $length = self::getUInt2d($this->data, $this->pos + 2);
2822
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2823
2824
        // move stream pointer to next record
2825
        $this->pos += 4 + $length;
2826
2827
        // external sheet references provided for named cells
2828
        if ($this->version == self::XLS_BIFF8) {
2829
            // offset: 0; size: 2; options
2830
            $options = self::getUInt2d($recordData, 0);
2831
2832
            // 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...
2833
2834
            // offset: 4; size: 2; not used
2835
2836
            // offset: 6; size: var
2837
            $nameString = self::readUnicodeStringShort(substr($recordData, 6));
2838
2839
            // offset: var; size: var; formula data
2840
            $offset = 6 + $nameString['size'];
2841
            $formula = $this->getFormulaFromStructure(substr($recordData, $offset));
2842
2843
            $this->externalNames[] = [
2844
                'name' => $nameString['value'],
2845
                'formula' => $formula,
2846
            ];
2847
        }
2848
    }
2849
2850
    /**
2851
     * Read EXTERNSHEET record.
2852
     */
2853 5
    private function readExternSheet()
2854
    {
2855 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
2856 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2857
2858
        // move stream pointer to next record
2859 5
        $this->pos += 4 + $length;
2860
2861
        // external sheet references provided for named cells
2862 5
        if ($this->version == self::XLS_BIFF8) {
2863
            // offset: 0; size: 2; number of following ref structures
2864 5
            $nm = self::getUInt2d($recordData, 0);
2865 5
            for ($i = 0; $i < $nm; ++$i) {
2866 5
                $this->ref[] = [
2867
                    // offset: 2 + 6 * $i; index to EXTERNALBOOK record
2868 5
                    'externalBookIndex' => self::getUInt2d($recordData, 2 + 6 * $i),
2869
                    // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record
2870 5
                    'firstSheetIndex' => self::getUInt2d($recordData, 4 + 6 * $i),
2871
                    // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record
2872 5
                    'lastSheetIndex' => self::getUInt2d($recordData, 6 + 6 * $i),
2873
                ];
2874
            }
2875
        }
2876 5
    }
2877
2878
    /**
2879
     * DEFINEDNAME.
2880
     *
2881
     * This record is part of a Link Table. It contains the name
2882
     * and the token array of an internal defined name. Token
2883
     * arrays of defined names contain tokens with aberrant
2884
     * token classes.
2885
     *
2886
     * --    "OpenOffice.org's Documentation of the Microsoft
2887
     *         Excel File Format"
2888
     */
2889 1
    private function readDefinedName()
2890
    {
2891 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
2892 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2893
2894
        // move stream pointer to next record
2895 1
        $this->pos += 4 + $length;
2896
2897 1
        if ($this->version == self::XLS_BIFF8) {
2898
            // retrieves named cells
2899
2900
            // offset: 0; size: 2; option flags
2901 1
            $opts = self::getUInt2d($recordData, 0);
2902
2903
            // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name
2904 1
            $isBuiltInName = (0x0020 & $opts) >> 5;
2905
2906
            // offset: 2; size: 1; keyboard shortcut
2907
2908
            // offset: 3; size: 1; length of the name (character count)
2909 1
            $nlen = ord($recordData[3]);
2910
2911
            // offset: 4; size: 2; size of the formula data (it can happen that this is zero)
2912
            // note: there can also be additional data, this is not included in $flen
2913 1
            $flen = self::getUInt2d($recordData, 4);
2914
2915
            // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based)
2916 1
            $scope = self::getUInt2d($recordData, 8);
2917
2918
            // offset: 14; size: var; Name (Unicode string without length field)
2919 1
            $string = self::readUnicodeString(substr($recordData, 14), $nlen);
2920
2921
            // offset: var; size: $flen; formula data
2922 1
            $offset = 14 + $string['size'];
2923 1
            $formulaStructure = pack('v', $flen) . substr($recordData, $offset);
2924
2925
            try {
2926 1
                $formula = $this->getFormulaFromStructure($formulaStructure);
2927
            } catch (PhpSpreadsheetException $e) {
2928
                $formula = '';
2929
            }
2930
2931 1
            $this->definedname[] = [
2932 1
                'isBuiltInName' => $isBuiltInName,
2933 1
                'name' => $string['value'],
2934 1
                'formula' => $formula,
2935 1
                'scope' => $scope,
2936
            ];
2937
        }
2938 1
    }
2939
2940
    /**
2941
     * Read MSODRAWINGGROUP record.
2942
     */
2943 3 View Code Duplication
    private function readMsoDrawingGroup()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2944
    {
2945 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
2946
2947
        // get spliced record data
2948 3
        $splicedRecordData = $this->getSplicedRecordData();
2949 3
        $recordData = $splicedRecordData['recordData'];
2950
2951 3
        $this->drawingGroupData .= $recordData;
2952 3
    }
2953
2954
    /**
2955
     * SST - Shared String Table.
2956
     *
2957
     * This record contains a list of all strings used anywhere
2958
     * in the workbook. Each string occurs only once. The
2959
     * workbook uses indexes into the list to reference the
2960
     * strings.
2961
     *
2962
     * --    "OpenOffice.org's Documentation of the Microsoft
2963
     *         Excel File Format"
2964
     **/
2965 19
    private function readSst()
2966
    {
2967
        // offset within (spliced) record data
2968 19
        $pos = 0;
2969
2970
        // get spliced record data
2971 19
        $splicedRecordData = $this->getSplicedRecordData();
2972
2973 19
        $recordData = $splicedRecordData['recordData'];
2974 19
        $spliceOffsets = $splicedRecordData['spliceOffsets'];
2975
2976
        // offset: 0; size: 4; total number of strings in the workbook
2977 19
        $pos += 4;
2978
2979
        // offset: 4; size: 4; number of following strings ($nm)
2980 19
        $nm = self::getInt4d($recordData, 4);
2981 19
        $pos += 4;
2982
2983
        // loop through the Unicode strings (16-bit length)
2984 19
        for ($i = 0; $i < $nm; ++$i) {
2985
            // number of characters in the Unicode string
2986 18
            $numChars = self::getUInt2d($recordData, $pos);
2987 18
            $pos += 2;
2988
2989
            // option flags
2990 18
            $optionFlags = ord($recordData[$pos]);
2991 18
            ++$pos;
2992
2993
            // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed
2994 18
            $isCompressed = (($optionFlags & 0x01) == 0);
2995
2996
            // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic
2997 18
            $hasAsian = (($optionFlags & 0x04) != 0);
2998
2999
            // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text
3000 18
            $hasRichText = (($optionFlags & 0x08) != 0);
3001
3002 18
            if ($hasRichText) {
3003
                // number of Rich-Text formatting runs
3004 2
                $formattingRuns = self::getUInt2d($recordData, $pos);
3005 2
                $pos += 2;
3006
            }
3007
3008 18
            if ($hasAsian) {
3009
                // size of Asian phonetic setting
3010
                $extendedRunLength = self::getInt4d($recordData, $pos);
3011
                $pos += 4;
3012
            }
3013
3014
            // expected byte length of character array if not split
3015 18
            $len = ($isCompressed) ? $numChars : $numChars * 2;
3016
3017
            // look up limit position
3018 18
            foreach ($spliceOffsets as $spliceOffset) {
3019
                // it can happen that the string is empty, therefore we need
3020
                // <= and not just <
3021 18
                if ($pos <= $spliceOffset) {
3022 18
                    $limitpos = $spliceOffset;
3023
3024 18
                    break;
3025
                }
3026
            }
3027
3028 18
            if ($pos + $len <= $limitpos) {
3029
                // character array is not split between records
3030
3031 18
                $retstr = substr($recordData, $pos, $len);
3032 18
                $pos += $len;
3033
            } else {
3034
                // character array is split between records
3035
3036
                // first part of character array
3037
                $retstr = substr($recordData, $pos, $limitpos - $pos);
3038
3039
                $bytesRead = $limitpos - $pos;
3040
3041
                // remaining characters in Unicode string
3042
                $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));
3043
3044
                $pos = $limitpos;
3045
3046
                // keep reading the characters
3047
                while ($charsLeft > 0) {
3048
                    // look up next limit position, in case the string span more than one continue record
3049
                    foreach ($spliceOffsets as $spliceOffset) {
3050
                        if ($pos < $spliceOffset) {
3051
                            $limitpos = $spliceOffset;
3052
3053
                            break;
3054
                        }
3055
                    }
3056
3057
                    // repeated option flags
3058
                    // OpenOffice.org documentation 5.21
3059
                    $option = ord($recordData[$pos]);
3060
                    ++$pos;
3061
3062
                    if ($isCompressed && ($option == 0)) {
3063
                        // 1st fragment compressed
3064
                        // this fragment compressed
3065
                        $len = min($charsLeft, $limitpos - $pos);
3066
                        $retstr .= substr($recordData, $pos, $len);
3067
                        $charsLeft -= $len;
3068
                        $isCompressed = true;
3069
                    } elseif (!$isCompressed && ($option != 0)) {
3070
                        // 1st fragment uncompressed
3071
                        // this fragment uncompressed
3072
                        $len = min($charsLeft * 2, $limitpos - $pos);
3073
                        $retstr .= substr($recordData, $pos, $len);
3074
                        $charsLeft -= $len / 2;
3075
                        $isCompressed = false;
3076
                    } elseif (!$isCompressed && ($option == 0)) {
3077
                        // 1st fragment uncompressed
3078
                        // this fragment compressed
3079
                        $len = min($charsLeft, $limitpos - $pos);
3080
                        for ($j = 0; $j < $len; ++$j) {
3081
                            $retstr .= $recordData[$pos + $j]
3082
                            . chr(0);
3083
                        }
3084
                        $charsLeft -= $len;
3085
                        $isCompressed = false;
3086
                    } else {
3087
                        // 1st fragment compressed
3088
                        // this fragment uncompressed
3089
                        $newstr = '';
3090
                        for ($j = 0; $j < strlen($retstr); ++$j) {
3091
                            $newstr .= $retstr[$j] . chr(0);
3092
                        }
3093
                        $retstr = $newstr;
3094
                        $len = min($charsLeft * 2, $limitpos - $pos);
3095
                        $retstr .= substr($recordData, $pos, $len);
3096
                        $charsLeft -= $len / 2;
3097
                        $isCompressed = false;
3098
                    }
3099
3100
                    $pos += $len;
3101
                }
3102
            }
3103
3104
            // convert to UTF-8
3105 18
            $retstr = self::encodeUTF16($retstr, $isCompressed);
3106
3107
            // read additional Rich-Text information, if any
3108 18
            $fmtRuns = [];
3109 18
            if ($hasRichText) {
3110
                // list of formatting runs
3111 2
                for ($j = 0; $j < $formattingRuns; ++$j) {
3112
                    // first formatted character; zero-based
3113 2
                    $charPos = self::getUInt2d($recordData, $pos + $j * 4);
3114
3115
                    // index to font record
3116 2
                    $fontIndex = self::getUInt2d($recordData, $pos + 2 + $j * 4);
3117
3118 2
                    $fmtRuns[] = [
3119 2
                        'charPos' => $charPos,
3120 2
                        'fontIndex' => $fontIndex,
3121
                    ];
3122
                }
3123 2
                $pos += 4 * $formattingRuns;
3124
            }
3125
3126
            // read additional Asian phonetics information, if any
3127 18
            if ($hasAsian) {
3128
                // For Asian phonetic settings, we skip the extended string data
3129
                $pos += $extendedRunLength;
3130
            }
3131
3132
            // store the shared sting
3133 18
            $this->sst[] = [
3134 18
                'value' => $retstr,
3135 18
                'fmtRuns' => $fmtRuns,
3136
            ];
3137
        }
3138
3139
        // getSplicedRecordData() takes care of moving current position in data stream
3140 19
    }
3141
3142
    /**
3143
     * Read PRINTGRIDLINES record.
3144
     */
3145 19
    private function readPrintGridlines()
3146
    {
3147 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
3148 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3149
3150
        // move stream pointer to next record
3151 19
        $this->pos += 4 + $length;
3152
3153 19
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3154
            // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines
3155 18
            $printGridlines = (bool) self::getUInt2d($recordData, 0);
3156 18
            $this->phpSheet->setPrintGridlines($printGridlines);
3157
        }
3158 19
    }
3159
3160
    /**
3161
     * Read DEFAULTROWHEIGHT record.
3162
     */
3163 17 View Code Duplication
    private function readDefaultRowHeight()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3164
    {
3165 17
        $length = self::getUInt2d($this->data, $this->pos + 2);
3166 17
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3167
3168
        // move stream pointer to next record
3169 17
        $this->pos += 4 + $length;
3170
3171
        // offset: 0; size: 2; option flags
3172
        // 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...
3173 17
        $height = self::getUInt2d($recordData, 2);
3174 17
        $this->phpSheet->getDefaultRowDimension()->setRowHeight($height / 20);
3175 17
    }
3176
3177
    /**
3178
     * Read SHEETPR record.
3179
     */
3180 19
    private function readSheetPr()
3181
    {
3182 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
3183 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3184
3185
        // move stream pointer to next record
3186 19
        $this->pos += 4 + $length;
3187
3188
        // offset: 0; size: 2
3189
3190
        // bit: 6; mask: 0x0040; 0 = outline buttons above outline group
3191 19
        $isSummaryBelow = (0x0040 & self::getUInt2d($recordData, 0)) >> 6;
3192 19
        $this->phpSheet->setShowSummaryBelow($isSummaryBelow);
3193
3194
        // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group
3195 19
        $isSummaryRight = (0x0080 & self::getUInt2d($recordData, 0)) >> 7;
3196 19
        $this->phpSheet->setShowSummaryRight($isSummaryRight);
3197
3198
        // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages
3199
        // this corresponds to radio button setting in page setup dialog in Excel
3200 19
        $this->isFitToPages = (bool) ((0x0100 & self::getUInt2d($recordData, 0)) >> 8);
3201 19
    }
3202
3203
    /**
3204
     * Read HORIZONTALPAGEBREAKS record.
3205
     */
3206 View Code Duplication
    private function readHorizontalPageBreaks()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3207
    {
3208
        $length = self::getUInt2d($this->data, $this->pos + 2);
3209
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3210
3211
        // move stream pointer to next record
3212
        $this->pos += 4 + $length;
3213
3214
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3215
            // offset: 0; size: 2; number of the following row index structures
3216
            $nm = self::getUInt2d($recordData, 0);
3217
3218
            // offset: 2; size: 6 * $nm; list of $nm row index structures
3219
            for ($i = 0; $i < $nm; ++$i) {
3220
                $r = self::getUInt2d($recordData, 2 + 6 * $i);
3221
                $cf = self::getUInt2d($recordData, 2 + 6 * $i + 2);
3222
                $cl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
3223
3224
                // not sure why two column indexes are necessary?
3225
                $this->phpSheet->setBreakByColumnAndRow($cf + 1, $r, Worksheet::BREAK_ROW);
3226
            }
3227
        }
3228
    }
3229
3230
    /**
3231
     * Read VERTICALPAGEBREAKS record.
3232
     */
3233 View Code Duplication
    private function readVerticalPageBreaks()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3234
    {
3235
        $length = self::getUInt2d($this->data, $this->pos + 2);
3236
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3237
3238
        // move stream pointer to next record
3239
        $this->pos += 4 + $length;
3240
3241
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3242
            // offset: 0; size: 2; number of the following column index structures
3243
            $nm = self::getUInt2d($recordData, 0);
3244
3245
            // offset: 2; size: 6 * $nm; list of $nm row index structures
3246
            for ($i = 0; $i < $nm; ++$i) {
3247
                $c = self::getUInt2d($recordData, 2 + 6 * $i);
3248
                $rf = self::getUInt2d($recordData, 2 + 6 * $i + 2);
3249
                $rl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
3250
3251
                // not sure why two row indexes are necessary?
3252
                $this->phpSheet->setBreakByColumnAndRow($c + 1, $rf, Worksheet::BREAK_COLUMN);
3253
            }
3254
        }
3255
    }
3256
3257
    /**
3258
     * Read HEADER record.
3259
     */
3260 19 View Code Duplication
    private function readHeader()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3261
    {
3262 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
3263 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3264
3265
        // move stream pointer to next record
3266 19
        $this->pos += 4 + $length;
3267
3268 19
        if (!$this->readDataOnly) {
3269
            // offset: 0; size: var
3270
            // realized that $recordData can be empty even when record exists
3271 18
            if ($recordData) {
3272 4
                if ($this->version == self::XLS_BIFF8) {
3273 4
                    $string = self::readUnicodeStringLong($recordData);
3274
                } else {
3275
                    $string = $this->readByteStringShort($recordData);
3276
                }
3277
3278 4
                $this->phpSheet->getHeaderFooter()->setOddHeader($string['value']);
3279 4
                $this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']);
3280
            }
3281
        }
3282 19
    }
3283
3284
    /**
3285
     * Read FOOTER record.
3286
     */
3287 19 View Code Duplication
    private function readFooter()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3288
    {
3289 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
3290 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3291
3292
        // move stream pointer to next record
3293 19
        $this->pos += 4 + $length;
3294
3295 19
        if (!$this->readDataOnly) {
3296
            // offset: 0; size: var
3297
            // realized that $recordData can be empty even when record exists
3298 18
            if ($recordData) {
3299 4
                if ($this->version == self::XLS_BIFF8) {
3300 4
                    $string = self::readUnicodeStringLong($recordData);
3301
                } else {
3302
                    $string = $this->readByteStringShort($recordData);
3303
                }
3304 4
                $this->phpSheet->getHeaderFooter()->setOddFooter($string['value']);
3305 4
                $this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']);
3306
            }
3307
        }
3308 19
    }
3309
3310
    /**
3311
     * Read HCENTER record.
3312
     */
3313 19 View Code Duplication
    private function readHcenter()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3314
    {
3315 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
3316 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3317
3318
        // move stream pointer to next record
3319 19
        $this->pos += 4 + $length;
3320
3321 19
        if (!$this->readDataOnly) {
3322
            // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally
3323 18
            $isHorizontalCentered = (bool) self::getUInt2d($recordData, 0);
3324
3325 18
            $this->phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered);
3326
        }
3327 19
    }
3328
3329
    /**
3330
     * Read VCENTER record.
3331
     */
3332 19 View Code Duplication
    private function readVcenter()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3333
    {
3334 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
3335 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3336
3337
        // move stream pointer to next record
3338 19
        $this->pos += 4 + $length;
3339
3340 19
        if (!$this->readDataOnly) {
3341
            // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered
3342 18
            $isVerticalCentered = (bool) self::getUInt2d($recordData, 0);
3343
3344 18
            $this->phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered);
3345
        }
3346 19
    }
3347
3348
    /**
3349
     * Read LEFTMARGIN record.
3350
     */
3351 6
    private function readLeftMargin()
3352
    {
3353 6
        $length = self::getUInt2d($this->data, $this->pos + 2);
3354 6
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3355
3356
        // move stream pointer to next record
3357 6
        $this->pos += 4 + $length;
3358
3359 6
        if (!$this->readDataOnly) {
3360
            // offset: 0; size: 8
3361 6
            $this->phpSheet->getPageMargins()->setLeft(self::extractNumber($recordData));
3362
        }
3363 6
    }
3364
3365
    /**
3366
     * Read RIGHTMARGIN record.
3367
     */
3368 6
    private function readRightMargin()
3369
    {
3370 6
        $length = self::getUInt2d($this->data, $this->pos + 2);
3371 6
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3372
3373
        // move stream pointer to next record
3374 6
        $this->pos += 4 + $length;
3375
3376 6
        if (!$this->readDataOnly) {
3377
            // offset: 0; size: 8
3378 6
            $this->phpSheet->getPageMargins()->setRight(self::extractNumber($recordData));
3379
        }
3380 6
    }
3381
3382
    /**
3383
     * Read TOPMARGIN record.
3384
     */
3385 6
    private function readTopMargin()
3386
    {
3387 6
        $length = self::getUInt2d($this->data, $this->pos + 2);
3388 6
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3389
3390
        // move stream pointer to next record
3391 6
        $this->pos += 4 + $length;
3392
3393 6
        if (!$this->readDataOnly) {
3394
            // offset: 0; size: 8
3395 6
            $this->phpSheet->getPageMargins()->setTop(self::extractNumber($recordData));
3396
        }
3397 6
    }
3398
3399
    /**
3400
     * Read BOTTOMMARGIN record.
3401
     */
3402 6
    private function readBottomMargin()
3403
    {
3404 6
        $length = self::getUInt2d($this->data, $this->pos + 2);
3405 6
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3406
3407
        // move stream pointer to next record
3408 6
        $this->pos += 4 + $length;
3409
3410 6
        if (!$this->readDataOnly) {
3411
            // offset: 0; size: 8
3412 6
            $this->phpSheet->getPageMargins()->setBottom(self::extractNumber($recordData));
3413
        }
3414 6
    }
3415
3416
    /**
3417
     * Read PAGESETUP record.
3418
     */
3419 19
    private function readPageSetup()
3420
    {
3421 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
3422 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3423
3424
        // move stream pointer to next record
3425 19
        $this->pos += 4 + $length;
3426
3427 19
        if (!$this->readDataOnly) {
3428
            // offset: 0; size: 2; paper size
3429 18
            $paperSize = self::getUInt2d($recordData, 0);
3430
3431
            // offset: 2; size: 2; scaling factor
3432 18
            $scale = self::getUInt2d($recordData, 2);
3433
3434
            // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed
3435 18
            $fitToWidth = self::getUInt2d($recordData, 6);
3436
3437
            // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed
3438 18
            $fitToHeight = self::getUInt2d($recordData, 8);
3439
3440
            // offset: 10; size: 2; option flags
3441
3442
            // 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...
3443 18
            $isPortrait = (0x0002 & self::getUInt2d($recordData, 10)) >> 1;
3444
3445
            // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init
3446
            // when this bit is set, do not use flags for those properties
3447 18
            $isNotInit = (0x0004 & self::getUInt2d($recordData, 10)) >> 2;
3448
3449 18
            if (!$isNotInit) {
3450 17
                $this->phpSheet->getPageSetup()->setPaperSize($paperSize);
3451
                switch ($isPortrait) {
3452 17
                    case 0:
3453 2
                        $this->phpSheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE);
3454
3455 2
                        break;
3456 17
                    case 1:
3457 17
                        $this->phpSheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_PORTRAIT);
3458
3459 17
                        break;
3460
                }
3461
3462 17
                $this->phpSheet->getPageSetup()->setScale($scale, false);
3463 17
                $this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages);
3464 17
                $this->phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false);
3465 17
                $this->phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false);
3466
            }
3467
3468
            // offset: 16; size: 8; header margin (IEEE 754 floating-point value)
3469 18
            $marginHeader = self::extractNumber(substr($recordData, 16, 8));
3470 18
            $this->phpSheet->getPageMargins()->setHeader($marginHeader);
3471
3472
            // offset: 24; size: 8; footer margin (IEEE 754 floating-point value)
3473 18
            $marginFooter = self::extractNumber(substr($recordData, 24, 8));
3474 18
            $this->phpSheet->getPageMargins()->setFooter($marginFooter);
3475
        }
3476 19
    }
3477
3478
    /**
3479
     * PROTECT - Sheet protection (BIFF2 through BIFF8)
3480
     *   if this record is omitted, then it also means no sheet protection.
3481
     */
3482 1 View Code Duplication
    private function readProtect()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3483
    {
3484 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
3485 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3486
3487
        // move stream pointer to next record
3488 1
        $this->pos += 4 + $length;
3489
3490 1
        if ($this->readDataOnly) {
3491
            return;
3492
        }
3493
3494
        // 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...
3495
3496
        // bit 0, mask 0x01; 1 = sheet is protected
3497 1
        $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
3498 1
        $this->phpSheet->getProtection()->setSheet((bool) $bool);
3499 1
    }
3500
3501
    /**
3502
     * SCENPROTECT.
3503
     */
3504 View Code Duplication
    private function readScenProtect()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3505
    {
3506
        $length = self::getUInt2d($this->data, $this->pos + 2);
3507
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3508
3509
        // move stream pointer to next record
3510
        $this->pos += 4 + $length;
3511
3512
        if ($this->readDataOnly) {
3513
            return;
3514
        }
3515
3516
        // 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...
3517
3518
        // bit: 0, mask 0x01; 1 = scenarios are protected
3519
        $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
3520
3521
        $this->phpSheet->getProtection()->setScenarios((bool) $bool);
3522
    }
3523
3524
    /**
3525
     * OBJECTPROTECT.
3526
     */
3527 View Code Duplication
    private function readObjectProtect()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3528
    {
3529
        $length = self::getUInt2d($this->data, $this->pos + 2);
3530
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3531
3532
        // move stream pointer to next record
3533
        $this->pos += 4 + $length;
3534
3535
        if ($this->readDataOnly) {
3536
            return;
3537
        }
3538
3539
        // 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...
3540
3541
        // bit: 0, mask 0x01; 1 = objects are protected
3542
        $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
3543
3544
        $this->phpSheet->getProtection()->setObjects((bool) $bool);
3545
    }
3546
3547
    /**
3548
     * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8).
3549
     */
3550
    private function readPassword()
3551
    {
3552
        $length = self::getUInt2d($this->data, $this->pos + 2);
3553
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3554
3555
        // move stream pointer to next record
3556
        $this->pos += 4 + $length;
3557
3558
        if (!$this->readDataOnly) {
3559
            // offset: 0; size: 2; 16-bit hash value of password
3560
            $password = strtoupper(dechex(self::getUInt2d($recordData, 0))); // the hashed password
3561
            $this->phpSheet->getProtection()->setPassword($password, true);
3562
        }
3563
    }
3564
3565
    /**
3566
     * Read DEFCOLWIDTH record.
3567
     */
3568 19 View Code Duplication
    private function readDefColWidth()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3569
    {
3570 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
3571 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3572
3573
        // move stream pointer to next record
3574 19
        $this->pos += 4 + $length;
3575
3576
        // offset: 0; size: 2; default column width
3577 19
        $width = self::getUInt2d($recordData, 0);
3578 19
        if ($width != 8) {
3579 1
            $this->phpSheet->getDefaultColumnDimension()->setWidth($width);
3580
        }
3581 19
    }
3582
3583
    /**
3584
     * Read COLINFO record.
3585
     */
3586 15
    private function readColInfo()
3587
    {
3588 15
        $length = self::getUInt2d($this->data, $this->pos + 2);
3589 15
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3590
3591
        // move stream pointer to next record
3592 15
        $this->pos += 4 + $length;
3593
3594 15
        if (!$this->readDataOnly) {
3595
            // offset: 0; size: 2; index to first column in range
3596 14
            $firstColumnIndex = self::getUInt2d($recordData, 0);
3597
3598
            // offset: 2; size: 2; index to last column in range
3599 14
            $lastColumnIndex = self::getUInt2d($recordData, 2);
3600
3601
            // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character
3602 14
            $width = self::getUInt2d($recordData, 4);
3603
3604
            // offset: 6; size: 2; index to XF record for default column formatting
3605 14
            $xfIndex = self::getUInt2d($recordData, 6);
3606
3607
            // offset: 8; size: 2; option flags
3608
            // bit: 0; mask: 0x0001; 1= columns are hidden
3609 14
            $isHidden = (0x0001 & self::getUInt2d($recordData, 8)) >> 0;
3610
3611
            // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline)
3612 14
            $level = (0x0700 & self::getUInt2d($recordData, 8)) >> 8;
3613
3614
            // bit: 12; mask: 0x1000; 1 = collapsed
3615 14
            $isCollapsed = (0x1000 & self::getUInt2d($recordData, 8)) >> 12;
3616
3617
            // offset: 10; size: 2; not used
3618
3619 14
            for ($i = $firstColumnIndex + 1; $i <= $lastColumnIndex + 1; ++$i) {
3620 14
                if ($lastColumnIndex == 255 || $lastColumnIndex == 256) {
3621 1
                    $this->phpSheet->getDefaultColumnDimension()->setWidth($width / 256);
3622
3623 1
                    break;
3624
                }
3625 13
                $this->phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256);
3626 13
                $this->phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden);
3627 13
                $this->phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level);
3628 13
                $this->phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed);
3629 13
                $this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3630
            }
3631
        }
3632 15
    }
3633
3634
    /**
3635
     * ROW.
3636
     *
3637
     * This record contains the properties of a single row in a
3638
     * sheet. Rows and cells in a sheet are divided into blocks
3639
     * of 32 rows.
3640
     *
3641
     * --    "OpenOffice.org's Documentation of the Microsoft
3642
     *         Excel File Format"
3643
     */
3644 17
    private function readRow()
3645
    {
3646 17
        $length = self::getUInt2d($this->data, $this->pos + 2);
3647 17
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3648
3649
        // move stream pointer to next record
3650 17
        $this->pos += 4 + $length;
3651
3652 17
        if (!$this->readDataOnly) {
3653
            // offset: 0; size: 2; index of this row
3654 16
            $r = self::getUInt2d($recordData, 0);
3655
3656
            // offset: 2; size: 2; index to column of the first cell which is described by a cell record
3657
3658
            // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1
3659
3660
            // 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...
3661
3662
            // bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point
3663 16
            $height = (0x7FFF & self::getUInt2d($recordData, 6)) >> 0;
3664
3665
            // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height
3666 16
            $useDefaultHeight = (0x8000 & self::getUInt2d($recordData, 6)) >> 15;
3667
3668 16
            if (!$useDefaultHeight) {
3669 16
                $this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
3670
            }
3671
3672
            // offset: 8; size: 2; not used
3673
3674
            // offset: 10; size: 2; not used in BIFF5-BIFF8
3675
3676
            // offset: 12; size: 4; option flags and default row formatting
3677
3678
            // bit: 2-0: mask: 0x00000007; outline level of the row
3679 16
            $level = (0x00000007 & self::getInt4d($recordData, 12)) >> 0;
3680 16
            $this->phpSheet->getRowDimension($r + 1)->setOutlineLevel($level);
3681
3682
            // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed
3683 16
            $isCollapsed = (0x00000010 & self::getInt4d($recordData, 12)) >> 4;
3684 16
            $this->phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed);
3685
3686
            // bit: 5; mask: 0x00000020; 1 = row is hidden
3687 16
            $isHidden = (0x00000020 & self::getInt4d($recordData, 12)) >> 5;
3688 16
            $this->phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden);
3689
3690
            // bit: 7; mask: 0x00000080; 1 = row has explicit format
3691 16
            $hasExplicitFormat = (0x00000080 & self::getInt4d($recordData, 12)) >> 7;
3692
3693
            // bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record
3694 16
            $xfIndex = (0x0FFF0000 & self::getInt4d($recordData, 12)) >> 16;
3695
3696 16
            if ($hasExplicitFormat && isset($this->mapCellXfIndex[$xfIndex])) {
3697 2
                $this->phpSheet->getRowDimension($r + 1)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3698
            }
3699
        }
3700 17
    }
3701
3702
    /**
3703
     * Read RK record
3704
     * This record represents a cell that contains an RK value
3705
     * (encoded integer or floating-point value). If a
3706
     * floating-point value cannot be encoded to an RK value,
3707
     * a NUMBER record will be written. This record replaces the
3708
     * record INTEGER written in BIFF2.
3709
     *
3710
     * --    "OpenOffice.org's Documentation of the Microsoft
3711
     *         Excel File Format"
3712
     */
3713 12 View Code Duplication
    private function readRk()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3714
    {
3715 12
        $length = self::getUInt2d($this->data, $this->pos + 2);
3716 12
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3717
3718
        // move stream pointer to next record
3719 12
        $this->pos += 4 + $length;
3720
3721
        // offset: 0; size: 2; index to row
3722 12
        $row = self::getUInt2d($recordData, 0);
3723
3724
        // offset: 2; size: 2; index to column
3725 12
        $column = self::getUInt2d($recordData, 2);
3726 12
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
3727
3728
        // Read cell?
3729 12
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3730
            // offset: 4; size: 2; index to XF record
3731 12
            $xfIndex = self::getUInt2d($recordData, 4);
3732
3733
            // offset: 6; size: 4; RK value
3734 12
            $rknum = self::getInt4d($recordData, 6);
3735 12
            $numValue = self::getIEEE754($rknum);
3736
3737 12
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3738 12
            if (!$this->readDataOnly) {
3739
                // add style information
3740 11
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3741
            }
3742
3743
            // add cell
3744 12
            $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
3745
        }
3746 12
    }
3747
3748
    /**
3749
     * Read LABELSST record
3750
     * This record represents a cell that contains a string. It
3751
     * replaces the LABEL record and RSTRING record used in
3752
     * BIFF2-BIFF5.
3753
     *
3754
     * --    "OpenOffice.org's Documentation of the Microsoft
3755
     *         Excel File Format"
3756
     */
3757 17
    private function readLabelSst()
3758
    {
3759 17
        $length = self::getUInt2d($this->data, $this->pos + 2);
3760 17
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3761
3762
        // move stream pointer to next record
3763 17
        $this->pos += 4 + $length;
3764
3765
        // offset: 0; size: 2; index to row
3766 17
        $row = self::getUInt2d($recordData, 0);
3767
3768
        // offset: 2; size: 2; index to column
3769 17
        $column = self::getUInt2d($recordData, 2);
3770 17
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
3771
3772 17
        $emptyCell = true;
3773
        // Read cell?
3774 17
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3775
            // offset: 4; size: 2; index to XF record
3776 17
            $xfIndex = self::getUInt2d($recordData, 4);
3777
3778
            // offset: 6; size: 4; index to SST record
3779 17
            $index = self::getInt4d($recordData, 6);
3780
3781
            // add cell
3782 17
            if (($fmtRuns = $this->sst[$index]['fmtRuns']) && !$this->readDataOnly) {
3783
                // then we should treat as rich text
3784 2
                $richText = new RichText();
3785 2
                $charPos = 0;
3786 2
                $sstCount = count($this->sst[$index]['fmtRuns']);
3787 2
                for ($i = 0; $i <= $sstCount; ++$i) {
3788 2
                    if (isset($fmtRuns[$i])) {
3789 2
                        $text = StringHelper::substring($this->sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos);
3790 2
                        $charPos = $fmtRuns[$i]['charPos'];
3791
                    } else {
3792 2
                        $text = StringHelper::substring($this->sst[$index]['value'], $charPos, StringHelper::countCharacters($this->sst[$index]['value']));
3793
                    }
3794
3795 2
                    if (StringHelper::countCharacters($text) > 0) {
3796 2
                        if ($i == 0) { // first text run, no style
3797 1
                            $richText->createText($text);
3798
                        } else {
3799 2
                            $textRun = $richText->createTextRun($text);
3800 2
                            if (isset($fmtRuns[$i - 1])) {
3801 2
                                if ($fmtRuns[$i - 1]['fontIndex'] < 4) {
3802 2
                                    $fontIndex = $fmtRuns[$i - 1]['fontIndex'];
3803
                                } else {
3804
                                    // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
3805
                                    // check the OpenOffice documentation of the FONT record
3806 2
                                    $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1;
3807
                                }
3808 2
                                $textRun->setFont(clone $this->objFonts[$fontIndex]);
3809
                            }
3810
                        }
3811
                    }
3812
                }
3813 2
                if ($this->readEmptyCells || trim($richText->getPlainText()) !== '') {
3814 2
                    $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3815 2
                    $cell->setValueExplicit($richText, DataType::TYPE_STRING);
3816 2
                    $emptyCell = false;
3817
                }
3818
            } else {
3819 17
                if ($this->readEmptyCells || trim($this->sst[$index]['value']) !== '') {
3820 17
                    $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3821 17
                    $cell->setValueExplicit($this->sst[$index]['value'], DataType::TYPE_STRING);
3822 17
                    $emptyCell = false;
3823
                }
3824
            }
3825
3826 17
            if (!$this->readDataOnly && !$emptyCell) {
3827
                // add style information
3828 16
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3829
            }
3830
        }
3831 17
    }
3832
3833
    /**
3834
     * Read MULRK record
3835
     * This record represents a cell range containing RK value
3836
     * cells. All cells are located in the same row.
3837
     *
3838
     * --    "OpenOffice.org's Documentation of the Microsoft
3839
     *         Excel File Format"
3840
     */
3841 11
    private function readMulRk()
3842
    {
3843 11
        $length = self::getUInt2d($this->data, $this->pos + 2);
3844 11
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3845
3846
        // move stream pointer to next record
3847 11
        $this->pos += 4 + $length;
3848
3849
        // offset: 0; size: 2; index to row
3850 11
        $row = self::getUInt2d($recordData, 0);
3851
3852
        // offset: 2; size: 2; index to first column
3853 11
        $colFirst = self::getUInt2d($recordData, 2);
3854
3855
        // offset: var; size: 2; index to last column
3856 11
        $colLast = self::getUInt2d($recordData, $length - 2);
3857 11
        $columns = $colLast - $colFirst + 1;
3858
3859
        // offset within record data
3860 11
        $offset = 4;
3861
3862 11
        for ($i = 1; $i <= $columns; ++$i) {
3863 11
            $columnString = Coordinate::stringFromColumnIndex($colFirst + $i);
3864
3865
            // Read cell?
3866 11
            if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3867
                // offset: var; size: 2; index to XF record
3868 11
                $xfIndex = self::getUInt2d($recordData, $offset);
3869
3870
                // offset: var; size: 4; RK value
3871 11
                $numValue = self::getIEEE754(self::getInt4d($recordData, $offset + 2));
3872 11
                $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3873 11
                if (!$this->readDataOnly) {
3874
                    // add style
3875 10
                    $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3876
                }
3877
3878
                // add cell value
3879 11
                $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
3880
            }
3881
3882 11
            $offset += 6;
3883
        }
3884 11
    }
3885
3886
    /**
3887
     * Read NUMBER record
3888
     * This record represents a cell that contains a
3889
     * floating-point value.
3890
     *
3891
     * --    "OpenOffice.org's Documentation of the Microsoft
3892
     *         Excel File Format"
3893
     */
3894 11 View Code Duplication
    private function readNumber()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3895
    {
3896 11
        $length = self::getUInt2d($this->data, $this->pos + 2);
3897 11
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3898
3899
        // move stream pointer to next record
3900 11
        $this->pos += 4 + $length;
3901
3902
        // offset: 0; size: 2; index to row
3903 11
        $row = self::getUInt2d($recordData, 0);
3904
3905
        // offset: 2; size 2; index to column
3906 11
        $column = self::getUInt2d($recordData, 2);
3907 11
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
3908
3909
        // Read cell?
3910 11
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3911
            // offset 4; size: 2; index to XF record
3912 11
            $xfIndex = self::getUInt2d($recordData, 4);
3913
3914 11
            $numValue = self::extractNumber(substr($recordData, 6, 8));
3915
3916 11
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3917 11
            if (!$this->readDataOnly) {
3918
                // add cell style
3919 10
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3920
            }
3921
3922
            // add cell value
3923 11
            $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
3924
        }
3925 11
    }
3926
3927
    /**
3928
     * Read FORMULA record + perhaps a following STRING record if formula result is a string
3929
     * This record contains the token array and the result of a
3930
     * formula cell.
3931
     *
3932
     * --    "OpenOffice.org's Documentation of the Microsoft
3933
     *         Excel File Format"
3934
     */
3935 10
    private function readFormula()
3936
    {
3937 10
        $length = self::getUInt2d($this->data, $this->pos + 2);
3938 10
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3939
3940
        // move stream pointer to next record
3941 10
        $this->pos += 4 + $length;
3942
3943
        // offset: 0; size: 2; row index
3944 10
        $row = self::getUInt2d($recordData, 0);
3945
3946
        // offset: 2; size: 2; col index
3947 10
        $column = self::getUInt2d($recordData, 2);
3948 10
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
3949
3950
        // offset: 20: size: variable; formula structure
3951 10
        $formulaStructure = substr($recordData, 20);
3952
3953
        // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc.
3954 10
        $options = self::getUInt2d($recordData, 14);
3955
3956
        // bit: 0; mask: 0x0001; 1 = recalculate always
3957
        // bit: 1; mask: 0x0002; 1 = calculate on open
3958
        // bit: 2; mask: 0x0008; 1 = part of a shared formula
3959 10
        $isPartOfSharedFormula = (bool) (0x0008 & $options);
3960
3961
        // WARNING:
3962
        // We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true
3963
        // the formula data may be ordinary formula data, therefore we need to check
3964
        // explicitly for the tExp token (0x01)
3965 10
        $isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure[2]) == 0x01;
3966
3967 10
        if ($isPartOfSharedFormula) {
3968
            // part of shared formula which means there will be a formula with a tExp token and nothing else
3969
            // get the base cell, grab tExp token
3970
            $baseRow = self::getUInt2d($formulaStructure, 3);
3971
            $baseCol = self::getUInt2d($formulaStructure, 5);
3972
            $this->baseCell = Coordinate::stringFromColumnIndex($baseCol + 1) . ($baseRow + 1);
3973
        }
3974
3975
        // Read cell?
3976 10
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3977 10
            if ($isPartOfSharedFormula) {
3978
                // formula is added to this cell after the sheet has been read
3979
                $this->sharedFormulaParts[$columnString . ($row + 1)] = $this->baseCell;
3980
            }
3981
3982
            // offset: 16: size: 4; not used
3983
3984
            // offset: 4; size: 2; XF index
3985 10
            $xfIndex = self::getUInt2d($recordData, 4);
3986
3987
            // offset: 6; size: 8; result of the formula
3988 10
            if ((ord($recordData[6]) == 0) && (ord($recordData[12]) == 255) && (ord($recordData[13]) == 255)) {
3989
                // String formula. Result follows in appended STRING record
3990
                $dataType = DataType::TYPE_STRING;
3991
3992
                // read possible SHAREDFMLA record
3993
                $code = self::getUInt2d($this->data, $this->pos);
3994
                if ($code == self::XLS_TYPE_SHAREDFMLA) {
3995
                    $this->readSharedFmla();
3996
                }
3997
3998
                // read STRING record
3999
                $value = $this->readString();
4000 10 View Code Duplication
            } elseif ((ord($recordData[6]) == 1)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4001 10
                && (ord($recordData[12]) == 255)
4002 10
                && (ord($recordData[13]) == 255)) {
4003
                // Boolean formula. Result is in +2; 0=false, 1=true
4004
                $dataType = DataType::TYPE_BOOL;
4005
                $value = (bool) ord($recordData[8]);
4006 10
            } elseif ((ord($recordData[6]) == 2)
4007 10
                && (ord($recordData[12]) == 255)
4008 10
                && (ord($recordData[13]) == 255)) {
4009
                // Error formula. Error code is in +2
4010 8
                $dataType = DataType::TYPE_ERROR;
4011 8
                $value = Xls\ErrorCode::lookup(ord($recordData[8]));
4012 10 View Code Duplication
            } elseif ((ord($recordData[6]) == 3)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4013 10
                && (ord($recordData[12]) == 255)
4014 10
                && (ord($recordData[13]) == 255)) {
4015
                // Formula result is a null string
4016 1
                $dataType = DataType::TYPE_NULL;
4017 1
                $value = '';
4018
            } else {
4019
                // forumla result is a number, first 14 bytes like _NUMBER record
4020 10
                $dataType = DataType::TYPE_NUMERIC;
4021 10
                $value = self::extractNumber(substr($recordData, 6, 8));
4022
            }
4023
4024 10
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4025 10
            if (!$this->readDataOnly) {
4026
                // add cell style
4027 9
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4028
            }
4029
4030
            // store the formula
4031 10
            if (!$isPartOfSharedFormula) {
4032
                // not part of shared formula
4033
                // add cell value. If we can read formula, populate with formula, otherwise just used cached value
4034
                try {
4035 10
                    if ($this->version != self::XLS_BIFF8) {
4036
                        throw new Exception('Not BIFF8. Can only read BIFF8 formulas');
4037
                    }
4038 10
                    $formula = $this->getFormulaFromStructure($formulaStructure); // get formula in human language
4039 10
                    $cell->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA);
4040
                } catch (PhpSpreadsheetException $e) {
4041 10
                    $cell->setValueExplicit($value, $dataType);
4042
                }
4043
            } else {
4044
                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...
4045
                    // do nothing at this point, formula id added later in the code
4046
                } else {
4047
                    $cell->setValueExplicit($value, $dataType);
4048
                }
4049
            }
4050
4051
            // store the cached calculated value
4052 10
            $cell->setCalculatedValue($value);
4053
        }
4054 10
    }
4055
4056
    /**
4057
     * Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader,
4058
     * which usually contains relative references.
4059
     * These will be used to construct the formula in each shared formula part after the sheet is read.
4060
     */
4061
    private function readSharedFmla()
4062
    {
4063
        $length = self::getUInt2d($this->data, $this->pos + 2);
4064
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4065
4066
        // move stream pointer to next record
4067
        $this->pos += 4 + $length;
4068
4069
        // offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything
4070
        $cellRange = substr($recordData, 0, 6);
4071
        $cellRange = $this->readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax
4072
4073
        // offset: 6, size: 1; not used
4074
4075
        // offset: 7, size: 1; number of existing FORMULA records for this shared formula
4076
        $no = ord($recordData[7]);
4077
4078
        // offset: 8, size: var; Binary token array of the shared formula
4079
        $formula = substr($recordData, 8);
4080
4081
        // at this point we only store the shared formula for later use
4082
        $this->sharedFormulas[$this->baseCell] = $formula;
4083
    }
4084
4085
    /**
4086
     * Read a STRING record from current stream position and advance the stream pointer to next record
4087
     * This record is used for storing result from FORMULA record when it is a string, and
4088
     * it occurs directly after the FORMULA record.
4089
     *
4090
     * @return string The string contents as UTF-8
4091
     */
4092
    private function readString()
4093
    {
4094
        $length = self::getUInt2d($this->data, $this->pos + 2);
4095
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4096
4097
        // move stream pointer to next record
4098
        $this->pos += 4 + $length;
4099
4100
        if ($this->version == self::XLS_BIFF8) {
4101
            $string = self::readUnicodeStringLong($recordData);
4102
            $value = $string['value'];
4103
        } else {
4104
            $string = $this->readByteStringLong($recordData);
4105
            $value = $string['value'];
4106
        }
4107
4108
        return $value;
4109
    }
4110
4111
    /**
4112
     * Read BOOLERR record
4113
     * This record represents a Boolean value or error value
4114
     * cell.
4115
     *
4116
     * --    "OpenOffice.org's Documentation of the Microsoft
4117
     *         Excel File Format"
4118
     */
4119 8
    private function readBoolErr()
4120
    {
4121 8
        $length = self::getUInt2d($this->data, $this->pos + 2);
4122 8
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4123
4124
        // move stream pointer to next record
4125 8
        $this->pos += 4 + $length;
4126
4127
        // offset: 0; size: 2; row index
4128 8
        $row = self::getUInt2d($recordData, 0);
4129
4130
        // offset: 2; size: 2; column index
4131 8
        $column = self::getUInt2d($recordData, 2);
4132 8
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
4133
4134
        // Read cell?
4135 8
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4136
            // offset: 4; size: 2; index to XF record
4137 8
            $xfIndex = self::getUInt2d($recordData, 4);
4138
4139
            // offset: 6; size: 1; the boolean value or error value
4140 8
            $boolErr = ord($recordData[6]);
4141
4142
            // 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...
4143 8
            $isError = ord($recordData[7]);
4144
4145 8
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4146
            switch ($isError) {
4147 8
                case 0: // boolean
4148 8
                    $value = (bool) $boolErr;
4149
4150
                    // add cell value
4151 8
                    $cell->setValueExplicit($value, DataType::TYPE_BOOL);
4152
4153 8
                    break;
4154
                case 1: // error type
4155
                    $value = Xls\ErrorCode::lookup($boolErr);
4156
4157
                    // add cell value
4158
                    $cell->setValueExplicit($value, DataType::TYPE_ERROR);
4159
4160
                    break;
4161
            }
4162
4163 8
            if (!$this->readDataOnly) {
4164
                // add cell style
4165 7
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4166
            }
4167
        }
4168 8
    }
4169
4170
    /**
4171
     * Read MULBLANK record
4172
     * This record represents a cell range of empty cells. All
4173
     * cells are located in the same row.
4174
     *
4175
     * --    "OpenOffice.org's Documentation of the Microsoft
4176
     *         Excel File Format"
4177
     */
4178 11
    private function readMulBlank()
4179
    {
4180 11
        $length = self::getUInt2d($this->data, $this->pos + 2);
4181 11
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4182
4183
        // move stream pointer to next record
4184 11
        $this->pos += 4 + $length;
4185
4186
        // offset: 0; size: 2; index to row
4187 11
        $row = self::getUInt2d($recordData, 0);
4188
4189
        // offset: 2; size: 2; index to first column
4190 11
        $fc = self::getUInt2d($recordData, 2);
4191
4192
        // offset: 4; size: 2 x nc; list of indexes to XF records
4193
        // add style information
4194 11
        if (!$this->readDataOnly && $this->readEmptyCells) {
4195 10
            for ($i = 0; $i < $length / 2 - 3; ++$i) {
4196 10
                $columnString = Coordinate::stringFromColumnIndex($fc + $i + 1);
4197
4198
                // Read cell?
4199 10
                if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4200 10
                    $xfIndex = self::getUInt2d($recordData, 4 + 2 * $i);
4201 10
                    $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4202
                }
4203
            }
4204
        }
4205
4206
        // offset: 6; size 2; index to last column (not needed)
4207 11
    }
4208
4209
    /**
4210
     * Read LABEL record
4211
     * This record represents a cell that contains a string. In
4212
     * BIFF8 it is usually replaced by the LABELSST record.
4213
     * Excel still uses this record, if it copies unformatted
4214
     * text cells to the clipboard.
4215
     *
4216
     * --    "OpenOffice.org's Documentation of the Microsoft
4217
     *         Excel File Format"
4218
     */
4219
    private function readLabel()
4220
    {
4221
        $length = self::getUInt2d($this->data, $this->pos + 2);
4222
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4223
4224
        // move stream pointer to next record
4225
        $this->pos += 4 + $length;
4226
4227
        // offset: 0; size: 2; index to row
4228
        $row = self::getUInt2d($recordData, 0);
4229
4230
        // offset: 2; size: 2; index to column
4231
        $column = self::getUInt2d($recordData, 2);
4232
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
4233
4234
        // Read cell?
4235
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4236
            // offset: 4; size: 2; XF index
4237
            $xfIndex = self::getUInt2d($recordData, 4);
4238
4239
            // add cell value
4240
            // todo: what if string is very long? continue record
4241 View Code Duplication
            if ($this->version == self::XLS_BIFF8) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4242
                $string = self::readUnicodeStringLong(substr($recordData, 6));
4243
                $value = $string['value'];
4244
            } else {
4245
                $string = $this->readByteStringLong(substr($recordData, 6));
4246
                $value = $string['value'];
4247
            }
4248
            if ($this->readEmptyCells || trim($value) !== '') {
4249
                $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4250
                $cell->setValueExplicit($value, DataType::TYPE_STRING);
4251
4252
                if (!$this->readDataOnly) {
4253
                    // add cell style
4254
                    $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4255
                }
4256
            }
4257
        }
4258
    }
4259
4260
    /**
4261
     * Read BLANK record.
4262
     */
4263 2
    private function readBlank()
4264
    {
4265 2
        $length = self::getUInt2d($this->data, $this->pos + 2);
4266 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4267
4268
        // move stream pointer to next record
4269 2
        $this->pos += 4 + $length;
4270
4271
        // offset: 0; size: 2; row index
4272 2
        $row = self::getUInt2d($recordData, 0);
4273
4274
        // offset: 2; size: 2; col index
4275 2
        $col = self::getUInt2d($recordData, 2);
4276 2
        $columnString = Coordinate::stringFromColumnIndex($col + 1);
4277
4278
        // Read cell?
4279 2
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4280
            // offset: 4; size: 2; XF index
4281 2
            $xfIndex = self::getUInt2d($recordData, 4);
4282
4283
            // add style information
4284 2
            if (!$this->readDataOnly && $this->readEmptyCells) {
4285 2
                $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4286
            }
4287
        }
4288 2
    }
4289
4290
    /**
4291
     * Read MSODRAWING record.
4292
     */
4293 3 View Code Duplication
    private function readMsoDrawing()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4294
    {
4295 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
4296
4297
        // get spliced record data
4298 3
        $splicedRecordData = $this->getSplicedRecordData();
4299 3
        $recordData = $splicedRecordData['recordData'];
4300
4301 3
        $this->drawingData .= $recordData;
4302 3
    }
4303
4304
    /**
4305
     * Read OBJ record.
4306
     */
4307 3
    private function readObj()
4308
    {
4309 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
4310 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4311
4312
        // move stream pointer to next record
4313 3
        $this->pos += 4 + $length;
4314
4315 3
        if ($this->readDataOnly || $this->version != self::XLS_BIFF8) {
4316
            return;
4317
        }
4318
4319
        // recordData consists of an array of subrecords looking like this:
4320
        //    ft: 2 bytes; ftCmo type (0x15)
4321
        //    cb: 2 bytes; size in bytes of ftCmo data
4322
        //    ot: 2 bytes; Object Type
4323
        //    id: 2 bytes; Object id number
4324
        //    grbit: 2 bytes; Option Flags
4325
        //    data: var; subrecord data
4326
4327
        // for now, we are just interested in the second subrecord containing the object type
4328 3
        $ftCmoType = self::getUInt2d($recordData, 0);
4329 3
        $cbCmoSize = self::getUInt2d($recordData, 2);
4330 3
        $otObjType = self::getUInt2d($recordData, 4);
4331 3
        $idObjID = self::getUInt2d($recordData, 6);
4332 3
        $grbitOpts = self::getUInt2d($recordData, 6);
4333
4334 3
        $this->objs[] = [
4335 3
            'ftCmoType' => $ftCmoType,
4336 3
            'cbCmoSize' => $cbCmoSize,
4337 3
            'otObjType' => $otObjType,
4338 3
            'idObjID' => $idObjID,
4339 3
            'grbitOpts' => $grbitOpts,
4340
        ];
4341 3
        $this->textObjRef = $idObjID;
4342 3
    }
4343
4344
    /**
4345
     * Read WINDOW2 record.
4346
     */
4347 19
    private function readWindow2()
4348
    {
4349 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
4350 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4351
4352
        // move stream pointer to next record
4353 19
        $this->pos += 4 + $length;
4354
4355
        // offset: 0; size: 2; option flags
4356 19
        $options = self::getUInt2d($recordData, 0);
4357
4358
        // offset: 2; size: 2; index to first visible row
4359 19
        $firstVisibleRow = self::getUInt2d($recordData, 2);
4360
4361
        // offset: 4; size: 2; index to first visible colum
4362 19
        $firstVisibleColumn = self::getUInt2d($recordData, 4);
4363 19
        if ($this->version === self::XLS_BIFF8) {
4364
            // offset:  8; size: 2; not used
4365
            // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%)
4366
            // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%)
4367
            // offset: 14; size: 4; not used
4368 19
            $zoomscaleInPageBreakPreview = self::getUInt2d($recordData, 10);
4369 19
            if ($zoomscaleInPageBreakPreview === 0) {
4370 19
                $zoomscaleInPageBreakPreview = 60;
4371
            }
4372 19
            $zoomscaleInNormalView = self::getUInt2d($recordData, 12);
4373 19
            if ($zoomscaleInNormalView === 0) {
4374 17
                $zoomscaleInNormalView = 100;
4375
            }
4376
        }
4377
4378
        // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines
4379 19
        $showGridlines = (bool) ((0x0002 & $options) >> 1);
4380 19
        $this->phpSheet->setShowGridlines($showGridlines);
4381
4382
        // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers
4383 19
        $showRowColHeaders = (bool) ((0x0004 & $options) >> 2);
4384 19
        $this->phpSheet->setShowRowColHeaders($showRowColHeaders);
4385
4386
        // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen
4387 19
        $this->frozen = (bool) ((0x0008 & $options) >> 3);
4388
4389
        // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left
4390 19
        $this->phpSheet->setRightToLeft((bool) ((0x0040 & $options) >> 6));
4391
4392
        // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active
4393 19
        $isActive = (bool) ((0x0400 & $options) >> 10);
4394 19
        if ($isActive) {
4395 16
            $this->spreadsheet->setActiveSheetIndex($this->spreadsheet->getIndex($this->phpSheet));
4396
        }
4397
4398
        // bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view
4399 19
        $isPageBreakPreview = (bool) ((0x0800 & $options) >> 11);
4400
4401
        //FIXME: set $firstVisibleRow and $firstVisibleColumn
4402
4403 19
        if ($this->phpSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_PAGE_LAYOUT) {
4404
            //NOTE: this setting is inferior to page layout view(Excel2007-)
4405 19
            $view = $isPageBreakPreview ? SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW : SheetView::SHEETVIEW_NORMAL;
4406 19
            $this->phpSheet->getSheetView()->setView($view);
4407 19
            if ($this->version === self::XLS_BIFF8) {
4408 19
                $zoomScale = $isPageBreakPreview ? $zoomscaleInPageBreakPreview : $zoomscaleInNormalView;
4409 19
                $this->phpSheet->getSheetView()->setZoomScale($zoomScale);
4410 19
                $this->phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView);
4411
            }
4412
        }
4413 19
    }
4414
4415
    /**
4416
     * Read PLV Record(Created by Excel2007 or upper).
4417
     */
4418 5
    private function readPageLayoutView()
4419
    {
4420 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
4421 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4422
4423
        // move stream pointer to next record
4424 5
        $this->pos += 4 + $length;
4425
4426
        // offset: 0; size: 2; rt
4427
        //->ignore
4428 5
        $rt = self::getUInt2d($recordData, 0);
4429
        // offset: 2; size: 2; grbitfr
4430
        //->ignore
4431 5
        $grbitFrt = self::getUInt2d($recordData, 2);
4432
        // offset: 4; size: 8; reserved
4433
        //->ignore
4434
4435
        // offset: 12; size 2; zoom scale
4436 5
        $wScalePLV = self::getUInt2d($recordData, 12);
4437
        // offset: 14; size 2; grbit
4438 5
        $grbit = self::getUInt2d($recordData, 14);
4439
4440
        // decomprise grbit
4441 5
        $fPageLayoutView = $grbit & 0x01;
4442 5
        $fRulerVisible = ($grbit >> 1) & 0x01; //no support
4443 5
        $fWhitespaceHidden = ($grbit >> 3) & 0x01; //no support
4444
4445 5
        if ($fPageLayoutView === 1) {
4446
            $this->phpSheet->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_LAYOUT);
4447
            $this->phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT
4448
        }
4449
        //otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW.
4450 5
    }
4451
4452
    /**
4453
     * Read SCL record.
4454
     */
4455
    private function readScl()
4456
    {
4457
        $length = self::getUInt2d($this->data, $this->pos + 2);
4458
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4459
4460
        // move stream pointer to next record
4461
        $this->pos += 4 + $length;
4462
4463
        // offset: 0; size: 2; numerator of the view magnification
4464
        $numerator = self::getUInt2d($recordData, 0);
4465
4466
        // offset: 2; size: 2; numerator of the view magnification
4467
        $denumerator = self::getUInt2d($recordData, 2);
4468
4469
        // set the zoom scale (in percent)
4470
        $this->phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator);
4471
    }
4472
4473
    /**
4474
     * Read PANE record.
4475
     */
4476 1
    private function readPane()
4477
    {
4478 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
4479 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4480
4481
        // move stream pointer to next record
4482 1
        $this->pos += 4 + $length;
4483
4484 1
        if (!$this->readDataOnly) {
4485
            // offset: 0; size: 2; position of vertical split
4486 1
            $px = self::getUInt2d($recordData, 0);
4487
4488
            // offset: 2; size: 2; position of horizontal split
4489 1
            $py = self::getUInt2d($recordData, 2);
4490
4491
            // offset: 4; size: 2; top most visible row in the bottom pane
4492 1
            $rwTop = self::getUInt2d($recordData, 4);
4493
4494
            // offset: 6; size: 2; first visible left column in the right pane
4495 1
            $colLeft = self::getUInt2d($recordData, 6);
4496
4497 1
            if ($this->frozen) {
4498
                // frozen panes
4499 1
                $cell = Coordinate::stringFromColumnIndex($px + 1) . ($py + 1);
4500 1
                $topLeftCell = Coordinate::stringFromColumnIndex($colLeft + 1) . ($rwTop + 1);
4501 1
                $this->phpSheet->freezePane($cell, $topLeftCell);
4502
            }
4503
            // unfrozen panes; split windows; not supported by PhpSpreadsheet core
4504
        }
4505 1
    }
4506
4507
    /**
4508
     * Read SELECTION record. There is one such record for each pane in the sheet.
4509
     */
4510 19
    private function readSelection()
4511
    {
4512 19
        $length = self::getUInt2d($this->data, $this->pos + 2);
4513 19
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4514
4515
        // move stream pointer to next record
4516 19
        $this->pos += 4 + $length;
4517
4518 19
        if (!$this->readDataOnly) {
4519
            // offset: 0; size: 1; pane identifier
4520 18
            $paneId = ord($recordData[0]);
4521
4522
            // offset: 1; size: 2; index to row of the active cell
4523 18
            $r = self::getUInt2d($recordData, 1);
4524
4525
            // offset: 3; size: 2; index to column of the active cell
4526 18
            $c = self::getUInt2d($recordData, 3);
4527
4528
            // offset: 5; size: 2; index into the following cell range list to the
4529
            //  entry that contains the active cell
4530 18
            $index = self::getUInt2d($recordData, 5);
4531
4532
            // offset: 7; size: var; cell range address list containing all selected cell ranges
4533 18
            $data = substr($recordData, 7);
4534 18
            $cellRangeAddressList = $this->readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax
4535
4536 18
            $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0];
4537
4538
            // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!)
4539 18
            if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) {
4540
                $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells);
4541
            }
4542
4543
            // first row '1' + last row '65536' indicates that full column is selected
4544 18
            if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) {
4545
                $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells);
4546
            }
4547
4548
            // first column 'A' + last column 'IV' indicates that full row is selected
4549 18
            if (preg_match('/^(A[0-9]+\:)IV([0-9]+)$/', $selectedCells)) {
4550 2
                $selectedCells = preg_replace('/^(A[0-9]+\:)IV([0-9]+)$/', '${1}XFD${2}', $selectedCells);
4551
            }
4552
4553 18
            $this->phpSheet->setSelectedCells($selectedCells);
4554
        }
4555 19
    }
4556
4557 11
    private function includeCellRangeFiltered($cellRangeAddress)
4558
    {
4559 11
        $includeCellRange = true;
4560 11
        if ($this->getReadFilter() !== null) {
4561 11
            $includeCellRange = false;
4562 11
            $rangeBoundaries = Coordinate::getRangeBoundaries($cellRangeAddress);
4563 11
            ++$rangeBoundaries[1][0];
4564 11
            for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; ++$row) {
4565 11
                for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; ++$column) {
4566 11
                    if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
4567 11
                        $includeCellRange = true;
4568
4569 11
                        break 2;
4570
                    }
4571
                }
4572
            }
4573
        }
4574
4575 11
        return $includeCellRange;
4576
    }
4577
4578
    /**
4579
     * MERGEDCELLS.
4580
     *
4581
     * This record contains the addresses of merged cell ranges
4582
     * in the current sheet.
4583
     *
4584
     * --    "OpenOffice.org's Documentation of the Microsoft
4585
     *         Excel File Format"
4586
     */
4587 12
    private function readMergedCells()
4588
    {
4589 12
        $length = self::getUInt2d($this->data, $this->pos + 2);
4590 12
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4591
4592
        // move stream pointer to next record
4593 12
        $this->pos += 4 + $length;
4594
4595 12
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
4596 11
            $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData);
4597 11
            foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) {
4598 11
                if ((strpos($cellRangeAddress, ':') !== false) &&
4599 11
                    ($this->includeCellRangeFiltered($cellRangeAddress))) {
4600 11
                    $this->phpSheet->mergeCells($cellRangeAddress);
4601
                }
4602
            }
4603
        }
4604 12
    }
4605
4606
    /**
4607
     * Read HYPERLINK record.
4608
     */
4609 2
    private function readHyperLink()
4610
    {
4611 2
        $length = self::getUInt2d($this->data, $this->pos + 2);
4612 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4613
4614
        // move stream pointer forward to next record
4615 2
        $this->pos += 4 + $length;
4616
4617 2
        if (!$this->readDataOnly) {
4618
            // offset: 0; size: 8; cell range address of all cells containing this hyperlink
4619
            try {
4620 2
                $cellRange = $this->readBIFF8CellRangeAddressFixed($recordData);
4621
            } catch (PhpSpreadsheetException $e) {
4622
                return;
4623
            }
4624
4625
            // offset: 8, size: 16; GUID of StdLink
4626
4627
            // offset: 24, size: 4; unknown value
4628
4629
            // offset: 28, size: 4; option flags
4630
            // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL
4631 2
            $isFileLinkOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 0;
4632
4633
            // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL
4634 2
            $isAbsPathOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 1;
4635
4636
            // 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...
4637 2
            $hasDesc = (0x00000014 & self::getUInt2d($recordData, 28)) >> 2;
4638
4639
            // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text
4640 2
            $hasText = (0x00000008 & self::getUInt2d($recordData, 28)) >> 3;
4641
4642
            // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame
4643 2
            $hasFrame = (0x00000080 & self::getUInt2d($recordData, 28)) >> 7;
4644
4645
            // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name)
4646 2
            $isUNC = (0x00000100 & self::getUInt2d($recordData, 28)) >> 8;
4647
4648
            // offset within record data
4649 2
            $offset = 32;
4650
4651 2
            if ($hasDesc) {
4652
                // offset: 32; size: var; character count of description text
4653 1
                $dl = self::getInt4d($recordData, 32);
4654
                // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated
4655 1
                $desc = self::encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false);
4656 1
                $offset += 4 + 2 * $dl;
4657
            }
4658 2
            if ($hasFrame) {
4659
                $fl = self::getInt4d($recordData, $offset);
4660
                $offset += 4 + 2 * $fl;
4661
            }
4662
4663
            // detect type of hyperlink (there are 4 types)
4664 2
            $hyperlinkType = null;
4665
4666 2
            if ($isUNC) {
4667
                $hyperlinkType = 'UNC';
4668 2
            } elseif (!$isFileLinkOrUrl) {
4669 2
                $hyperlinkType = 'workbook';
4670 2
            } elseif (ord($recordData[$offset]) == 0x03) {
4671
                $hyperlinkType = 'local';
4672 2
            } elseif (ord($recordData[$offset]) == 0xE0) {
4673 2
                $hyperlinkType = 'URL';
4674
            }
4675
4676
            switch ($hyperlinkType) {
4677 2
                case 'URL':
4678
                    // section 5.58.2: Hyperlink containing a URL
4679
                    // e.g. http://example.org/index.php
4680
4681
                    // offset: var; size: 16; GUID of URL Moniker
4682 2
                    $offset += 16;
4683
                    // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word
4684 2
                    $us = self::getInt4d($recordData, $offset);
4685 2
                    $offset += 4;
4686
                    // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated
4687 2
                    $url = self::encodeUTF16(substr($recordData, $offset, $us - 2), false);
4688 2
                    $nullOffset = strpos($url, 0x00);
4689 2
                    if ($nullOffset) {
4690 1
                        $url = substr($url, 0, $nullOffset);
4691
                    }
4692 2
                    $url .= $hasText ? '#' : '';
4693 2
                    $offset += $us;
4694
4695 2
                    break;
4696 2
                case 'local':
4697
                    // section 5.58.3: Hyperlink to local file
4698
                    // examples:
4699
                    //   mydoc.txt
4700
                    //   ../../somedoc.xls#Sheet!A1
4701
4702
                    // offset: var; size: 16; GUI of File Moniker
4703
                    $offset += 16;
4704
4705
                    // offset: var; size: 2; directory up-level count.
4706
                    $upLevelCount = self::getUInt2d($recordData, $offset);
4707
                    $offset += 2;
4708
4709
                    // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word
4710
                    $sl = self::getInt4d($recordData, $offset);
4711
                    $offset += 4;
4712
4713
                    // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string)
4714
                    $shortenedFilePath = substr($recordData, $offset, $sl);
4715
                    $shortenedFilePath = self::encodeUTF16($shortenedFilePath, true);
4716
                    $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero
4717
4718
                    $offset += $sl;
4719
4720
                    // offset: var; size: 24; unknown sequence
4721
                    $offset += 24;
4722
4723
                    // extended file path
4724
                    // offset: var; size: 4; size of the following file link field including string lenth mark
4725
                    $sz = self::getInt4d($recordData, $offset);
4726
                    $offset += 4;
4727
4728
                    // only present if $sz > 0
4729
                    if ($sz > 0) {
4730
                        // offset: var; size: 4; size of the character array of the extended file path and name
4731
                        $xl = self::getInt4d($recordData, $offset);
4732
                        $offset += 4;
4733
4734
                        // offset: var; size 2; unknown
4735
                        $offset += 2;
4736
4737
                        // offset: var; size $xl; character array of the extended file path and name.
4738
                        $extendedFilePath = substr($recordData, $offset, $xl);
4739
                        $extendedFilePath = self::encodeUTF16($extendedFilePath, false);
4740
                        $offset += $xl;
4741
                    }
4742
4743
                    // construct the path
4744
                    $url = str_repeat('..\\', $upLevelCount);
4745
                    $url .= ($sz > 0) ? $extendedFilePath : $shortenedFilePath; // use extended path if available
4746
                    $url .= $hasText ? '#' : '';
4747
4748
                    break;
4749 2
                case 'UNC':
4750
                    // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path
4751
                    // todo: implement
4752
                    return;
4753 2
                case 'workbook':
4754
                    // section 5.58.5: Hyperlink to the Current Workbook
4755
                    // e.g. Sheet2!B1:C2, stored in text mark field
4756 2
                    $url = 'sheet://';
4757
4758 2
                    break;
4759
                default:
4760
                    return;
4761
            }
4762
4763 2
            if ($hasText) {
4764
                // offset: var; size: 4; character count of text mark including trailing zero word
4765 2
                $tl = self::getInt4d($recordData, $offset);
4766 2
                $offset += 4;
4767
                // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated
4768 2
                $text = self::encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false);
4769 2
                $url .= $text;
4770
            }
4771
4772
            // apply the hyperlink to all the relevant cells
4773 2
            foreach (Coordinate::extractAllCellReferencesInRange($cellRange) as $coordinate) {
4774 2
                $this->phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url);
4775
            }
4776
        }
4777 2
    }
4778
4779
    /**
4780
     * Read DATAVALIDATIONS record.
4781
     */
4782
    private function readDataValidations()
4783
    {
4784
        $length = self::getUInt2d($this->data, $this->pos + 2);
4785
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4786
4787
        // move stream pointer forward to next record
4788
        $this->pos += 4 + $length;
4789
    }
4790
4791
    /**
4792
     * Read DATAVALIDATION record.
4793
     */
4794
    private function readDataValidation()
4795
    {
4796
        $length = self::getUInt2d($this->data, $this->pos + 2);
4797
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4798
4799
        // move stream pointer forward to next record
4800
        $this->pos += 4 + $length;
4801
4802
        if ($this->readDataOnly) {
4803
            return;
4804
        }
4805
4806
        // offset: 0; size: 4; Options
4807
        $options = self::getInt4d($recordData, 0);
4808
4809
        // 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...
4810
        $type = (0x0000000F & $options) >> 0;
4811 View Code Duplication
        switch ($type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4812
            case 0x00:
4813
                $type = DataValidation::TYPE_NONE;
4814
4815
                break;
4816
            case 0x01:
4817
                $type = DataValidation::TYPE_WHOLE;
4818
4819
                break;
4820
            case 0x02:
4821
                $type = DataValidation::TYPE_DECIMAL;
4822
4823
                break;
4824
            case 0x03:
4825
                $type = DataValidation::TYPE_LIST;
4826
4827
                break;
4828
            case 0x04:
4829
                $type = DataValidation::TYPE_DATE;
4830
4831
                break;
4832
            case 0x05:
4833
                $type = DataValidation::TYPE_TIME;
4834
4835
                break;
4836
            case 0x06:
4837
                $type = DataValidation::TYPE_TEXTLENGTH;
4838
4839
                break;
4840
            case 0x07:
4841
                $type = DataValidation::TYPE_CUSTOM;
4842
4843
                break;
4844
        }
4845
4846
        // bit: 4-6; mask: 0x00000070; error type
4847
        $errorStyle = (0x00000070 & $options) >> 4;
4848 View Code Duplication
        switch ($errorStyle) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4849
            case 0x00:
4850
                $errorStyle = DataValidation::STYLE_STOP;
4851
4852
                break;
4853
            case 0x01:
4854
                $errorStyle = DataValidation::STYLE_WARNING;
4855
4856
                break;
4857
            case 0x02:
4858
                $errorStyle = DataValidation::STYLE_INFORMATION;
4859
4860
                break;
4861
        }
4862
4863
        // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)
4864
        // I have only seen cases where this is 1
4865
        $explicitFormula = (0x00000080 & $options) >> 7;
4866
4867
        // bit: 8; mask: 0x00000100; 1= empty cells allowed
4868
        $allowBlank = (0x00000100 & $options) >> 8;
4869
4870
        // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity
4871
        $suppressDropDown = (0x00000200 & $options) >> 9;
4872
4873
        // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected
4874
        $showInputMessage = (0x00040000 & $options) >> 18;
4875
4876
        // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered
4877
        $showErrorMessage = (0x00080000 & $options) >> 19;
4878
4879
        // bit: 20-23; mask: 0x00F00000; condition operator
4880
        $operator = (0x00F00000 & $options) >> 20;
4881 View Code Duplication
        switch ($operator) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4882
            case 0x00:
4883
                $operator = DataValidation::OPERATOR_BETWEEN;
4884
4885
                break;
4886
            case 0x01:
4887
                $operator = DataValidation::OPERATOR_NOTBETWEEN;
4888
4889
                break;
4890
            case 0x02:
4891
                $operator = DataValidation::OPERATOR_EQUAL;
4892
4893
                break;
4894
            case 0x03:
4895
                $operator = DataValidation::OPERATOR_NOTEQUAL;
4896
4897
                break;
4898
            case 0x04:
4899
                $operator = DataValidation::OPERATOR_GREATERTHAN;
4900
4901
                break;
4902
            case 0x05:
4903
                $operator = DataValidation::OPERATOR_LESSTHAN;
4904
4905
                break;
4906
            case 0x06:
4907
                $operator = DataValidation::OPERATOR_GREATERTHANOREQUAL;
4908
4909
                break;
4910
            case 0x07:
4911
                $operator = DataValidation::OPERATOR_LESSTHANOREQUAL;
4912
4913
                break;
4914
        }
4915
4916
        // offset: 4; size: var; title of the prompt box
4917
        $offset = 4;
4918
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4919
        $promptTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4920
        $offset += $string['size'];
4921
4922
        // offset: var; size: var; title of the error box
4923
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4924
        $errorTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4925
        $offset += $string['size'];
4926
4927
        // offset: var; size: var; text of the prompt box
4928
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4929
        $prompt = $string['value'] !== chr(0) ? $string['value'] : '';
4930
        $offset += $string['size'];
4931
4932
        // offset: var; size: var; text of the error box
4933
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4934
        $error = $string['value'] !== chr(0) ? $string['value'] : '';
4935
        $offset += $string['size'];
4936
4937
        // offset: var; size: 2; size of the formula data for the first condition
4938
        $sz1 = self::getUInt2d($recordData, $offset);
4939
        $offset += 2;
4940
4941
        // offset: var; size: 2; not used
4942
        $offset += 2;
4943
4944
        // offset: var; size: $sz1; formula data for first condition (without size field)
4945
        $formula1 = substr($recordData, $offset, $sz1);
4946
        $formula1 = pack('v', $sz1) . $formula1; // prepend the length
4947
        try {
4948
            $formula1 = $this->getFormulaFromStructure($formula1);
4949
4950
            // in list type validity, null characters are used as item separators
4951
            if ($type == DataValidation::TYPE_LIST) {
4952
                $formula1 = str_replace(chr(0), ',', $formula1);
4953
            }
4954
        } catch (PhpSpreadsheetException $e) {
4955
            return;
4956
        }
4957
        $offset += $sz1;
4958
4959
        // offset: var; size: 2; size of the formula data for the first condition
4960
        $sz2 = self::getUInt2d($recordData, $offset);
4961
        $offset += 2;
4962
4963
        // offset: var; size: 2; not used
4964
        $offset += 2;
4965
4966
        // offset: var; size: $sz2; formula data for second condition (without size field)
4967
        $formula2 = substr($recordData, $offset, $sz2);
4968
        $formula2 = pack('v', $sz2) . $formula2; // prepend the length
4969
        try {
4970
            $formula2 = $this->getFormulaFromStructure($formula2);
4971
        } catch (PhpSpreadsheetException $e) {
4972
            return;
4973
        }
4974
        $offset += $sz2;
4975
4976
        // offset: var; size: var; cell range address list with
4977
        $cellRangeAddressList = $this->readBIFF8CellRangeAddressList(substr($recordData, $offset));
4978
        $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
4979
4980
        foreach ($cellRangeAddresses as $cellRange) {
4981
            $stRange = $this->phpSheet->shrinkRangeToFit($cellRange);
4982
            foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $coordinate) {
4983
                $objValidation = $this->phpSheet->getCell($coordinate)->getDataValidation();
4984
                $objValidation->setType($type);
4985
                $objValidation->setErrorStyle($errorStyle);
4986
                $objValidation->setAllowBlank((bool) $allowBlank);
4987
                $objValidation->setShowInputMessage((bool) $showInputMessage);
4988
                $objValidation->setShowErrorMessage((bool) $showErrorMessage);
4989
                $objValidation->setShowDropDown(!$suppressDropDown);
4990
                $objValidation->setOperator($operator);
4991
                $objValidation->setErrorTitle($errorTitle);
4992
                $objValidation->setError($error);
4993
                $objValidation->setPromptTitle($promptTitle);
4994
                $objValidation->setPrompt($prompt);
4995
                $objValidation->setFormula1($formula1);
4996
                $objValidation->setFormula2($formula2);
4997
            }
4998
        }
4999
    }
5000
5001
    /**
5002
     * Read SHEETLAYOUT record. Stores sheet tab color information.
5003
     */
5004 2
    private function readSheetLayout()
5005
    {
5006 2
        $length = self::getUInt2d($this->data, $this->pos + 2);
5007 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5008
5009
        // move stream pointer to next record
5010 2
        $this->pos += 4 + $length;
5011
5012
        // local pointer in record data
5013 2
        $offset = 0;
5014
5015 2
        if (!$this->readDataOnly) {
5016
            // offset: 0; size: 2; repeated record identifier 0x0862
5017
5018
            // offset: 2; size: 10; not used
5019
5020
            // offset: 12; size: 4; size of record data
5021
            // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?)
5022 2
            $sz = self::getInt4d($recordData, 12);
5023
5024
            switch ($sz) {
5025 2
                case 0x14:
5026
                    // offset: 16; size: 2; color index for sheet tab
5027 1
                    $colorIndex = self::getUInt2d($recordData, 16);
5028 1
                    $color = Xls\Color::map($colorIndex, $this->palette, $this->version);
5029 1
                    $this->phpSheet->getTabColor()->setRGB($color['rgb']);
5030
5031 1
                    break;
5032 1
                case 0x28:
5033
                    // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007
5034 1
                    return;
5035
                    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...
5036
            }
5037
        }
5038 1
    }
5039
5040
    /**
5041
     * Read SHEETPROTECTION record (FEATHEADR).
5042
     */
5043 6
    private function readSheetProtection()
5044
    {
5045 6
        $length = self::getUInt2d($this->data, $this->pos + 2);
5046 6
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5047
5048
        // move stream pointer to next record
5049 6
        $this->pos += 4 + $length;
5050
5051 6
        if ($this->readDataOnly) {
5052
            return;
5053
        }
5054
5055
        // offset: 0; size: 2; repeated record header
5056
5057
        // offset: 2; size: 2; FRT cell reference flag (=0 currently)
5058
5059
        // offset: 4; size: 8; Currently not used and set to 0
5060
5061
        // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag)
5062 6
        $isf = self::getUInt2d($recordData, 12);
5063 6
        if ($isf != 2) {
5064
            return;
5065
        }
5066
5067
        // offset: 14; size: 1; =1 since this is a feat header
5068
5069
        // offset: 15; size: 4; size of rgbHdrSData
5070
5071
        // rgbHdrSData, assume "Enhanced Protection"
5072
        // offset: 19; size: 2; option flags
5073 6
        $options = self::getUInt2d($recordData, 19);
5074
5075
        // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects
5076 6
        $bool = (0x0001 & $options) >> 0;
5077 6
        $this->phpSheet->getProtection()->setObjects(!$bool);
5078
5079
        // bit: 1; mask 0x0002; edit scenarios
5080 6
        $bool = (0x0002 & $options) >> 1;
5081 6
        $this->phpSheet->getProtection()->setScenarios(!$bool);
5082
5083
        // bit: 2; mask 0x0004; format cells
5084 6
        $bool = (0x0004 & $options) >> 2;
5085 6
        $this->phpSheet->getProtection()->setFormatCells(!$bool);
5086
5087
        // bit: 3; mask 0x0008; format columns
5088 6
        $bool = (0x0008 & $options) >> 3;
5089 6
        $this->phpSheet->getProtection()->setFormatColumns(!$bool);
5090
5091
        // bit: 4; mask 0x0010; format rows
5092 6
        $bool = (0x0010 & $options) >> 4;
5093 6
        $this->phpSheet->getProtection()->setFormatRows(!$bool);
5094
5095
        // bit: 5; mask 0x0020; insert columns
5096 6
        $bool = (0x0020 & $options) >> 5;
5097 6
        $this->phpSheet->getProtection()->setInsertColumns(!$bool);
5098
5099
        // bit: 6; mask 0x0040; insert rows
5100 6
        $bool = (0x0040 & $options) >> 6;
5101 6
        $this->phpSheet->getProtection()->setInsertRows(!$bool);
5102
5103
        // bit: 7; mask 0x0080; insert hyperlinks
5104 6
        $bool = (0x0080 & $options) >> 7;
5105 6
        $this->phpSheet->getProtection()->setInsertHyperlinks(!$bool);
5106
5107
        // bit: 8; mask 0x0100; delete columns
5108 6
        $bool = (0x0100 & $options) >> 8;
5109 6
        $this->phpSheet->getProtection()->setDeleteColumns(!$bool);
5110
5111
        // bit: 9; mask 0x0200; delete rows
5112 6
        $bool = (0x0200 & $options) >> 9;
5113 6
        $this->phpSheet->getProtection()->setDeleteRows(!$bool);
5114
5115
        // bit: 10; mask 0x0400; select locked cells
5116 6
        $bool = (0x0400 & $options) >> 10;
5117 6
        $this->phpSheet->getProtection()->setSelectLockedCells(!$bool);
5118
5119
        // bit: 11; mask 0x0800; sort cell range
5120 6
        $bool = (0x0800 & $options) >> 11;
5121 6
        $this->phpSheet->getProtection()->setSort(!$bool);
5122
5123
        // bit: 12; mask 0x1000; auto filter
5124 6
        $bool = (0x1000 & $options) >> 12;
5125 6
        $this->phpSheet->getProtection()->setAutoFilter(!$bool);
5126
5127
        // bit: 13; mask 0x2000; pivot tables
5128 6
        $bool = (0x2000 & $options) >> 13;
5129 6
        $this->phpSheet->getProtection()->setPivotTables(!$bool);
5130
5131
        // bit: 14; mask 0x4000; select unlocked cells
5132 6
        $bool = (0x4000 & $options) >> 14;
5133 6
        $this->phpSheet->getProtection()->setSelectUnlockedCells(!$bool);
5134
5135
        // offset: 21; size: 2; not used
5136 6
    }
5137
5138
    /**
5139
     * Read RANGEPROTECTION record
5140
     * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification,
5141
     * where it is referred to as FEAT record.
5142
     */
5143 1
    private function readRangeProtection()
5144
    {
5145 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
5146 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5147
5148
        // move stream pointer to next record
5149 1
        $this->pos += 4 + $length;
5150
5151
        // local pointer in record data
5152 1
        $offset = 0;
5153
5154 1
        if (!$this->readDataOnly) {
5155 1
            $offset += 12;
5156
5157
            // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag
5158 1
            $isf = self::getUInt2d($recordData, 12);
5159 1
            if ($isf != 2) {
5160
                // we only read FEAT records of type 2
5161
                return;
5162
            }
5163 1
            $offset += 2;
5164
5165 1
            $offset += 5;
5166
5167
            // offset: 19; size: 2; count of ref ranges this feature is on
5168 1
            $cref = self::getUInt2d($recordData, 19);
5169 1
            $offset += 2;
5170
5171 1
            $offset += 6;
5172
5173
            // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record)
5174 1
            $cellRanges = [];
5175 1
            for ($i = 0; $i < $cref; ++$i) {
5176
                try {
5177 1
                    $cellRange = $this->readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8));
5178
                } catch (PhpSpreadsheetException $e) {
5179
                    return;
5180
                }
5181 1
                $cellRanges[] = $cellRange;
5182 1
                $offset += 8;
5183
            }
5184
5185
            // offset: var; size: var; variable length of feature specific data
5186 1
            $rgbFeat = substr($recordData, $offset);
5187 1
            $offset += 4;
5188
5189
            // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit)
5190 1
            $wPassword = self::getInt4d($recordData, $offset);
5191 1
            $offset += 4;
5192
5193
            // Apply range protection to sheet
5194 1
            if ($cellRanges) {
5195 1
                $this->phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true);
5196
            }
5197
        }
5198 1
    }
5199
5200
    /**
5201
     * Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record
5202
     * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented.
5203
     * In this case, we must treat the CONTINUE record as a MSODRAWING record.
5204
     */
5205
    private function readContinue()
5206
    {
5207
        $length = self::getUInt2d($this->data, $this->pos + 2);
5208
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5209
5210
        // check if we are reading drawing data
5211
        // this is in case a free CONTINUE record occurs in other circumstances we are unaware of
5212
        if ($this->drawingData == '') {
5213
            // move stream pointer to next record
5214
            $this->pos += 4 + $length;
5215
5216
            return;
5217
        }
5218
5219
        // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data
5220
        if ($length < 4) {
5221
            // move stream pointer to next record
5222
            $this->pos += 4 + $length;
5223
5224
            return;
5225
        }
5226
5227
        // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record
5228
        // look inside CONTINUE record to see if it looks like a part of an Escher stream
5229
        // we know that Escher stream may be split at least at
5230
        //        0xF003 MsofbtSpgrContainer
5231
        //        0xF004 MsofbtSpContainer
5232
        //        0xF00D MsofbtClientTextbox
5233
        $validSplitPoints = [0xF003, 0xF004, 0xF00D]; // add identifiers if we find more
5234
5235
        $splitPoint = self::getUInt2d($recordData, 2);
5236
        if (in_array($splitPoint, $validSplitPoints)) {
5237
            // get spliced record data (and move pointer to next record)
5238
            $splicedRecordData = $this->getSplicedRecordData();
5239
            $this->drawingData .= $splicedRecordData['recordData'];
5240
5241
            return;
5242
        }
5243
5244
        // move stream pointer to next record
5245
        $this->pos += 4 + $length;
5246
    }
5247
5248
    /**
5249
     * Reads a record from current position in data stream and continues reading data as long as CONTINUE
5250
     * records are found. Splices the record data pieces and returns the combined string as if record data
5251
     * is in one piece.
5252
     * Moves to next current position in data stream to start of next record different from a CONtINUE record.
5253
     *
5254
     * @return array
5255
     */
5256 19
    private function getSplicedRecordData()
5257
    {
5258 19
        $data = '';
5259 19
        $spliceOffsets = [];
5260
5261 19
        $i = 0;
5262 19
        $spliceOffsets[0] = 0;
5263
5264
        do {
5265 19
            ++$i;
5266
5267
            // offset: 0; size: 2; identifier
5268 19
            $identifier = self::getUInt2d($this->data, $this->pos);
5269
            // offset: 2; size: 2; length
5270 19
            $length = self::getUInt2d($this->data, $this->pos + 2);
5271 19
            $data .= $this->readRecordData($this->data, $this->pos + 4, $length);
5272
5273 19
            $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length;
5274
5275 19
            $this->pos += 4 + $length;
5276 19
            $nextIdentifier = self::getUInt2d($this->data, $this->pos);
5277 19
        } while ($nextIdentifier == self::XLS_TYPE_CONTINUE);
5278
5279
        $splicedData = [
5280 19
            'recordData' => $data,
5281 19
            'spliceOffsets' => $spliceOffsets,
5282
        ];
5283
5284 19
        return $splicedData;
5285
    }
5286
5287
    /**
5288
     * Convert formula structure into human readable Excel formula like 'A3+A5*5'.
5289
     *
5290
     * @param string $formulaStructure The complete binary data for the formula
5291
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5292
     *
5293
     * @return string Human readable formula
5294
     */
5295 10
    private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1')
5296
    {
5297
        // offset: 0; size: 2; size of the following formula data
5298 10
        $sz = self::getUInt2d($formulaStructure, 0);
5299
5300
        // offset: 2; size: sz
5301 10
        $formulaData = substr($formulaStructure, 2, $sz);
5302
5303
        // offset: 2 + sz; size: variable (optional)
5304 10
        if (strlen($formulaStructure) > 2 + $sz) {
5305
            $additionalData = substr($formulaStructure, 2 + $sz);
5306
        } else {
5307 10
            $additionalData = '';
5308
        }
5309
5310 10
        return $this->getFormulaFromData($formulaData, $additionalData, $baseCell);
5311
    }
5312
5313
    /**
5314
     * Take formula data and additional data for formula and return human readable formula.
5315
     *
5316
     * @param string $formulaData The binary data for the formula itself
5317
     * @param string $additionalData Additional binary data going with the formula
5318
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5319
     *
5320
     * @return string Human readable formula
5321
     */
5322 10
    private function getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1')
5323
    {
5324
        // start parsing the formula data
5325 10
        $tokens = [];
5326
5327 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...
5328 10
            $tokens[] = $token;
5329 10
            $formulaData = substr($formulaData, $token['size']);
5330
        }
5331
5332 10
        $formulaString = $this->createFormulaFromTokens($tokens, $additionalData);
5333
5334 10
        return $formulaString;
5335
    }
5336
5337
    /**
5338
     * Take array of tokens together with additional data for formula and return human readable formula.
5339
     *
5340
     * @param array $tokens
5341
     * @param string $additionalData Additional binary data going with the formula
5342
     *
5343
     * @return string Human readable formula
5344
     */
5345 10
    private function createFormulaFromTokens($tokens, $additionalData)
5346
    {
5347
        // empty formula?
5348 10
        if (empty($tokens)) {
5349
            return '';
5350
        }
5351
5352 10
        $formulaStrings = [];
5353 10
        foreach ($tokens as $token) {
5354
            // initialize spaces
5355 10
            $space0 = isset($space0) ? $space0 : ''; // spaces before next token, not tParen
5356 10
            $space1 = isset($space1) ? $space1 : ''; // carriage returns before next token, not tParen
5357 10
            $space2 = isset($space2) ? $space2 : ''; // spaces before opening parenthesis
5358 10
            $space3 = isset($space3) ? $space3 : ''; // carriage returns before opening parenthesis
5359 10
            $space4 = isset($space4) ? $space4 : ''; // spaces before closing parenthesis
5360 10
            $space5 = isset($space5) ? $space5 : ''; // carriage returns before closing parenthesis
5361
5362 10
            switch ($token['name']) {
5363 10
                case 'tAdd': // addition
5364 10
                case 'tConcat': // addition
5365 10
                case 'tDiv': // division
5366 10
                case 'tEQ': // equality
5367 10
                case 'tGE': // greater than or equal
5368 10
                case 'tGT': // greater than
5369 10
                case 'tIsect': // intersection
5370 10
                case 'tLE': // less than or equal
5371 10
                case 'tList': // less than or equal
5372 10
                case 'tLT': // less than
5373 10
                case 'tMul': // multiplication
5374 10
                case 'tNE': // multiplication
5375 10
                case 'tPower': // power
5376 10
                case 'tRange': // range
5377 10
                case 'tSub': // subtraction
5378 10
                    $op2 = array_pop($formulaStrings);
5379 10
                    $op1 = array_pop($formulaStrings);
5380 10
                    $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2";
5381 10
                    unset($space0, $space1);
5382
5383 10
                    break;
5384 10
                case 'tUplus': // unary plus
5385 10 View Code Duplication
                case 'tUminus': // unary minus
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5386
                    $op = array_pop($formulaStrings);
5387
                    $formulaStrings[] = "$space1$space0{$token['data']}$op";
5388
                    unset($space0, $space1);
5389
5390
                    break;
5391 10 View Code Duplication
                case 'tPercent': // percent sign
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5392
                    $op = array_pop($formulaStrings);
5393
                    $formulaStrings[] = "$op$space1$space0{$token['data']}";
5394
                    unset($space0, $space1);
5395
5396
                    break;
5397 10
                case 'tAttrVolatile': // indicates volatile function
5398 10
                case 'tAttrIf':
5399 10
                case 'tAttrSkip':
5400 10
                case 'tAttrChoose':
5401
                    // token is only important for Excel formula evaluator
5402
                    // do nothing
5403
                    break;
5404 10
                case 'tAttrSpace': // space / carriage return
5405
                    // space will be used when next token arrives, do not alter formulaString stack
5406
                    switch ($token['data']['spacetype']) {
5407
                        case 'type0':
5408
                            $space0 = str_repeat(' ', $token['data']['spacecount']);
5409
5410
                            break;
5411
                        case 'type1':
5412
                            $space1 = str_repeat("\n", $token['data']['spacecount']);
5413
5414
                            break;
5415
                        case 'type2':
5416
                            $space2 = str_repeat(' ', $token['data']['spacecount']);
5417
5418
                            break;
5419
                        case 'type3':
5420
                            $space3 = str_repeat("\n", $token['data']['spacecount']);
5421
5422
                            break;
5423
                        case 'type4':
5424
                            $space4 = str_repeat(' ', $token['data']['spacecount']);
5425
5426
                            break;
5427
                        case 'type5':
5428
                            $space5 = str_repeat("\n", $token['data']['spacecount']);
5429
5430
                            break;
5431
                    }
5432
5433
                    break;
5434 10
                case 'tAttrSum': // SUM function with one parameter
5435 9
                    $op = array_pop($formulaStrings);
5436 9
                    $formulaStrings[] = "{$space1}{$space0}SUM($op)";
5437 9
                    unset($space0, $space1);
5438
5439 9
                    break;
5440 10
                case 'tFunc': // function with fixed number of arguments
5441 10
                case 'tFuncV': // function with variable number of arguments
5442 9
                    if ($token['data']['function'] != '') {
5443
                        // normal function
5444 9
                        $ops = []; // array of operators
5445 9 View Code Duplication
                        for ($i = 0; $i < $token['data']['args']; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5446 1
                            $ops[] = array_pop($formulaStrings);
5447
                        }
5448 9
                        $ops = array_reverse($ops);
5449 9
                        $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ')';
5450 9
                        unset($space0, $space1);
5451
                    } else {
5452
                        // add-in function
5453
                        $ops = []; // array of operators
5454 View Code Duplication
                        for ($i = 0; $i < $token['data']['args'] - 1; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5455
                            $ops[] = array_pop($formulaStrings);
5456
                        }
5457
                        $ops = array_reverse($ops);
5458
                        $function = array_pop($formulaStrings);
5459
                        $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ')';
5460
                        unset($space0, $space1);
5461
                    }
5462
5463 9
                    break;
5464 10
                case 'tParen': // parenthesis
5465
                    $expression = array_pop($formulaStrings);
5466
                    $formulaStrings[] = "$space3$space2($expression$space5$space4)";
5467
                    unset($space2, $space3, $space4, $space5);
5468
5469
                    break;
5470 10
                case 'tArray': // array constant
5471
                    $constantArray = self::readBIFF8ConstantArray($additionalData);
5472
                    $formulaStrings[] = $space1 . $space0 . $constantArray['value'];
5473
                    $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data
5474
                    unset($space0, $space1);
5475
5476
                    break;
5477 10
                case 'tMemArea':
5478
                    // bite off chunk of additional data
5479
                    $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($additionalData);
5480
                    $additionalData = substr($additionalData, $cellRangeAddressList['size']);
5481
                    $formulaStrings[] = "$space1$space0{$token['data']}";
5482
                    unset($space0, $space1);
5483
5484
                    break;
5485 10
                case 'tArea': // cell range address
5486 10
                case 'tBool': // boolean
5487 10
                case 'tErr': // error code
5488 10
                case 'tInt': // integer
5489 2
                case 'tMemErr':
5490 2
                case 'tMemFunc':
5491 2
                case 'tMissArg':
5492 2
                case 'tName':
5493 2
                case 'tNameX':
5494 2
                case 'tNum': // number
5495 2
                case 'tRef': // single cell reference
5496 2
                case 'tRef3d': // 3d cell reference
5497 2
                case 'tArea3d': // 3d cell range reference
5498 1
                case 'tRefN':
5499 1
                case 'tAreaN':
5500 1
                case 'tStr': // string
5501 10
                    $formulaStrings[] = "$space1$space0{$token['data']}";
5502 10
                    unset($space0, $space1);
5503
5504 10
                    break;
5505
            }
5506
        }
5507 10
        $formulaString = $formulaStrings[0];
5508
5509 10
        return $formulaString;
5510
    }
5511
5512
    /**
5513
     * Fetch next token from binary formula data.
5514
     *
5515
     * @param string $formulaData Formula data
5516
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5517
     *
5518
     * @throws Exception
5519
     *
5520
     * @return array
5521
     */
5522 10
    private function getNextToken($formulaData, $baseCell = 'A1')
5523
    {
5524
        // offset: 0; size: 1; token id
5525 10
        $id = ord($formulaData[0]); // token id
5526 10
        $name = false; // initialize token name
5527
5528
        switch ($id) {
5529 10
            case 0x03:
5530 1
                $name = 'tAdd';
5531 1
                $size = 1;
5532 1
                $data = '+';
5533
5534 1
                break;
5535 10
            case 0x04:
5536
                $name = 'tSub';
5537
                $size = 1;
5538
                $data = '-';
5539
5540
                break;
5541 10
            case 0x05:
5542 2
                $name = 'tMul';
5543 2
                $size = 1;
5544 2
                $data = '*';
5545
5546 2
                break;
5547 10
            case 0x06:
5548 8
                $name = 'tDiv';
5549 8
                $size = 1;
5550 8
                $data = '/';
5551
5552 8
                break;
5553 10
            case 0x07:
5554
                $name = 'tPower';
5555
                $size = 1;
5556
                $data = '^';
5557
5558
                break;
5559 10
            case 0x08:
5560
                $name = 'tConcat';
5561
                $size = 1;
5562
                $data = '&';
5563
5564
                break;
5565 10
            case 0x09:
5566
                $name = 'tLT';
5567
                $size = 1;
5568
                $data = '<';
5569
5570
                break;
5571 10
            case 0x0A:
5572
                $name = 'tLE';
5573
                $size = 1;
5574
                $data = '<=';
5575
5576
                break;
5577 10
            case 0x0B:
5578
                $name = 'tEQ';
5579
                $size = 1;
5580
                $data = '=';
5581
5582
                break;
5583 10
            case 0x0C:
5584
                $name = 'tGE';
5585
                $size = 1;
5586
                $data = '>=';
5587
5588
                break;
5589 10
            case 0x0D:
5590
                $name = 'tGT';
5591
                $size = 1;
5592
                $data = '>';
5593
5594
                break;
5595 10
            case 0x0E:
5596 1
                $name = 'tNE';
5597 1
                $size = 1;
5598 1
                $data = '<>';
5599
5600 1
                break;
5601 10
            case 0x0F:
5602
                $name = 'tIsect';
5603
                $size = 1;
5604
                $data = ' ';
5605
5606
                break;
5607 10
            case 0x10:
5608
                $name = 'tList';
5609
                $size = 1;
5610
                $data = ',';
5611
5612
                break;
5613 10
            case 0x11:
5614
                $name = 'tRange';
5615
                $size = 1;
5616
                $data = ':';
5617
5618
                break;
5619 10
            case 0x12:
5620
                $name = 'tUplus';
5621
                $size = 1;
5622
                $data = '+';
5623
5624
                break;
5625 10
            case 0x13:
5626
                $name = 'tUminus';
5627
                $size = 1;
5628
                $data = '-';
5629
5630
                break;
5631 10
            case 0x14:
5632
                $name = 'tPercent';
5633
                $size = 1;
5634
                $data = '%';
5635
5636
                break;
5637 10
            case 0x15:    //    parenthesis
5638
                $name = 'tParen';
5639
                $size = 1;
5640
                $data = null;
5641
5642
                break;
5643 10
            case 0x16:    //    missing argument
5644
                $name = 'tMissArg';
5645
                $size = 1;
5646
                $data = '';
5647
5648
                break;
5649 10
            case 0x17:    //    string
5650 1
                $name = 'tStr';
5651
                // offset: 1; size: var; Unicode string, 8-bit string length
5652 1
                $string = self::readUnicodeStringShort(substr($formulaData, 1));
5653 1
                $size = 1 + $string['size'];
5654 1
                $data = self::UTF8toExcelDoubleQuoted($string['value']);
5655
5656 1
                break;
5657 10
            case 0x19:    //    Special attribute
5658
                // offset: 1; size: 1; attribute type flags:
5659 9
                switch (ord($formulaData[1])) {
5660 9
                    case 0x01:
5661
                        $name = 'tAttrVolatile';
5662
                        $size = 4;
5663
                        $data = null;
5664
5665
                        break;
5666 9
                    case 0x02:
5667
                        $name = 'tAttrIf';
5668
                        $size = 4;
5669
                        $data = null;
5670
5671
                        break;
5672 9
                    case 0x04:
5673
                        $name = 'tAttrChoose';
5674
                        // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1)
5675
                        $nc = self::getUInt2d($formulaData, 2);
5676
                        // offset: 4; size: 2 * $nc
5677
                        // offset: 4 + 2 * $nc; size: 2
5678
                        $size = 2 * $nc + 6;
5679
                        $data = null;
5680
5681
                        break;
5682 9
                    case 0x08:
5683
                        $name = 'tAttrSkip';
5684
                        $size = 4;
5685
                        $data = null;
5686
5687
                        break;
5688 9
                    case 0x10:
5689 9
                        $name = 'tAttrSum';
5690 9
                        $size = 4;
5691 9
                        $data = null;
5692
5693 9
                        break;
5694
                    case 0x40:
5695
                    case 0x41:
5696
                        $name = 'tAttrSpace';
5697
                        $size = 4;
5698
                        // offset: 2; size: 2; space type and position
5699
                        switch (ord($formulaData[2])) {
5700
                            case 0x00:
5701
                                $spacetype = 'type0';
5702
5703
                                break;
5704
                            case 0x01:
5705
                                $spacetype = 'type1';
5706
5707
                                break;
5708
                            case 0x02:
5709
                                $spacetype = 'type2';
5710
5711
                                break;
5712
                            case 0x03:
5713
                                $spacetype = 'type3';
5714
5715
                                break;
5716
                            case 0x04:
5717
                                $spacetype = 'type4';
5718
5719
                                break;
5720
                            case 0x05:
5721
                                $spacetype = 'type5';
5722
5723
                                break;
5724
                            default:
5725
                                throw new Exception('Unrecognized space type in tAttrSpace token');
5726
                                break;
5727
                        }
5728
                        // offset: 3; size: 1; number of inserted spaces/carriage returns
5729
                        $spacecount = ord($formulaData[3]);
5730
5731
                        $data = ['spacetype' => $spacetype, 'spacecount' => $spacecount];
5732
5733
                        break;
5734
                    default:
5735
                        throw new Exception('Unrecognized attribute flag in tAttr token');
5736
                        break;
5737
                }
5738
5739 9
                break;
5740 10
            case 0x1C:    //    error code
5741
                // offset: 1; size: 1; error code
5742
                $name = 'tErr';
5743
                $size = 2;
5744
                $data = Xls\ErrorCode::lookup(ord($formulaData[1]));
5745
5746
                break;
5747 10
            case 0x1D:    //    boolean
5748
                // 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...
5749
                $name = 'tBool';
5750
                $size = 2;
5751
                $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE';
5752
5753
                break;
5754 10
            case 0x1E:    //    integer
5755
                // offset: 1; size: 2; unsigned 16-bit integer
5756 8
                $name = 'tInt';
5757 8
                $size = 3;
5758 8
                $data = self::getUInt2d($formulaData, 1);
5759
5760 8
                break;
5761 10
            case 0x1F:    //    number
5762
                // 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...
5763 2
                $name = 'tNum';
5764 2
                $size = 9;
5765 2
                $data = self::extractNumber(substr($formulaData, 1));
5766 2
                $data = str_replace(',', '.', (string) $data); // in case non-English locale
5767 2
                break;
5768 10
            case 0x20:    //    array constant
5769 10
            case 0x40:
5770 10
            case 0x60:
5771
                // offset: 1; size: 7; not used
5772
                $name = 'tArray';
5773
                $size = 8;
5774
                $data = null;
5775
5776
                break;
5777 10
            case 0x21:    //    function with fixed number of arguments
5778 10
            case 0x41:
5779 10
            case 0x61:
5780 8
                $name = 'tFunc';
5781 8
                $size = 3;
5782
                // offset: 1; size: 2; index to built-in sheet function
5783 8
                switch (self::getUInt2d($formulaData, 1)) {
5784 8
                    case 2:
5785
                        $function = 'ISNA';
5786
                        $args = 1;
5787
5788
                        break;
5789 8
                    case 3:
5790
                        $function = 'ISERROR';
5791
                        $args = 1;
5792
5793
                        break;
5794 8
                    case 10:
5795 8
                        $function = 'NA';
5796 8
                        $args = 0;
5797
5798 8
                        break;
5799
                    case 15:
5800
                        $function = 'SIN';
5801
                        $args = 1;
5802
5803
                        break;
5804
                    case 16:
5805
                        $function = 'COS';
5806
                        $args = 1;
5807
5808
                        break;
5809
                    case 17:
5810
                        $function = 'TAN';
5811
                        $args = 1;
5812
5813
                        break;
5814
                    case 18:
5815
                        $function = 'ATAN';
5816
                        $args = 1;
5817
5818
                        break;
5819
                    case 19:
5820
                        $function = 'PI';
5821
                        $args = 0;
5822
5823
                        break;
5824
                    case 20:
5825
                        $function = 'SQRT';
5826
                        $args = 1;
5827
5828
                        break;
5829
                    case 21:
5830
                        $function = 'EXP';
5831
                        $args = 1;
5832
5833
                        break;
5834
                    case 22:
5835
                        $function = 'LN';
5836
                        $args = 1;
5837
5838
                        break;
5839
                    case 23:
5840
                        $function = 'LOG10';
5841
                        $args = 1;
5842
5843
                        break;
5844
                    case 24:
5845
                        $function = 'ABS';
5846
                        $args = 1;
5847
5848
                        break;
5849
                    case 25:
5850
                        $function = 'INT';
5851
                        $args = 1;
5852
5853
                        break;
5854
                    case 26:
5855
                        $function = 'SIGN';
5856
                        $args = 1;
5857
5858
                        break;
5859
                    case 27:
5860
                        $function = 'ROUND';
5861
                        $args = 2;
5862
5863
                        break;
5864
                    case 30:
5865
                        $function = 'REPT';
5866
                        $args = 2;
5867
5868
                        break;
5869
                    case 31:
5870
                        $function = 'MID';
5871
                        $args = 3;
5872
5873
                        break;
5874
                    case 32:
5875
                        $function = 'LEN';
5876
                        $args = 1;
5877
5878
                        break;
5879
                    case 33:
5880
                        $function = 'VALUE';
5881
                        $args = 1;
5882
5883
                        break;
5884
                    case 34:
5885
                        $function = 'TRUE';
5886
                        $args = 0;
5887
5888
                        break;
5889
                    case 35:
5890
                        $function = 'FALSE';
5891
                        $args = 0;
5892
5893
                        break;
5894
                    case 38:
5895
                        $function = 'NOT';
5896
                        $args = 1;
5897
5898
                        break;
5899
                    case 39:
5900
                        $function = 'MOD';
5901
                        $args = 2;
5902
5903
                        break;
5904
                    case 40:
5905
                        $function = 'DCOUNT';
5906
                        $args = 3;
5907
5908
                        break;
5909
                    case 41:
5910
                        $function = 'DSUM';
5911
                        $args = 3;
5912
5913
                        break;
5914
                    case 42:
5915
                        $function = 'DAVERAGE';
5916
                        $args = 3;
5917
5918
                        break;
5919
                    case 43:
5920
                        $function = 'DMIN';
5921
                        $args = 3;
5922
5923
                        break;
5924
                    case 44:
5925
                        $function = 'DMAX';
5926
                        $args = 3;
5927
5928
                        break;
5929
                    case 45:
5930
                        $function = 'DSTDEV';
5931
                        $args = 3;
5932
5933
                        break;
5934
                    case 48:
5935
                        $function = 'TEXT';
5936
                        $args = 2;
5937
5938
                        break;
5939
                    case 61:
5940
                        $function = 'MIRR';
5941
                        $args = 3;
5942
5943
                        break;
5944
                    case 63:
5945
                        $function = 'RAND';
5946
                        $args = 0;
5947
5948
                        break;
5949
                    case 65:
5950
                        $function = 'DATE';
5951
                        $args = 3;
5952
5953
                        break;
5954
                    case 66:
5955
                        $function = 'TIME';
5956
                        $args = 3;
5957
5958
                        break;
5959
                    case 67:
5960
                        $function = 'DAY';
5961
                        $args = 1;
5962
5963
                        break;
5964
                    case 68:
5965
                        $function = 'MONTH';
5966
                        $args = 1;
5967
5968
                        break;
5969
                    case 69:
5970
                        $function = 'YEAR';
5971
                        $args = 1;
5972
5973
                        break;
5974
                    case 71:
5975
                        $function = 'HOUR';
5976
                        $args = 1;
5977
5978
                        break;
5979
                    case 72:
5980
                        $function = 'MINUTE';
5981
                        $args = 1;
5982
5983
                        break;
5984
                    case 73:
5985
                        $function = 'SECOND';
5986
                        $args = 1;
5987
5988
                        break;
5989
                    case 74:
5990
                        $function = 'NOW';
5991
                        $args = 0;
5992
5993
                        break;
5994
                    case 75:
5995
                        $function = 'AREAS';
5996
                        $args = 1;
5997
5998
                        break;
5999
                    case 76:
6000
                        $function = 'ROWS';
6001
                        $args = 1;
6002
6003
                        break;
6004
                    case 77:
6005
                        $function = 'COLUMNS';
6006
                        $args = 1;
6007
6008
                        break;
6009
                    case 83:
6010
                        $function = 'TRANSPOSE';
6011
                        $args = 1;
6012
6013
                        break;
6014
                    case 86:
6015
                        $function = 'TYPE';
6016
                        $args = 1;
6017
6018
                        break;
6019
                    case 97:
6020
                        $function = 'ATAN2';
6021
                        $args = 2;
6022
6023
                        break;
6024
                    case 98:
6025
                        $function = 'ASIN';
6026
                        $args = 1;
6027
6028
                        break;
6029
                    case 99:
6030
                        $function = 'ACOS';
6031
                        $args = 1;
6032
6033
                        break;
6034
                    case 105:
6035
                        $function = 'ISREF';
6036
                        $args = 1;
6037
6038
                        break;
6039
                    case 111:
6040
                        $function = 'CHAR';
6041
                        $args = 1;
6042
6043
                        break;
6044
                    case 112:
6045
                        $function = 'LOWER';
6046
                        $args = 1;
6047
6048
                        break;
6049
                    case 113:
6050
                        $function = 'UPPER';
6051
                        $args = 1;
6052
6053
                        break;
6054
                    case 114:
6055
                        $function = 'PROPER';
6056
                        $args = 1;
6057
6058
                        break;
6059
                    case 117:
6060
                        $function = 'EXACT';
6061
                        $args = 2;
6062
6063
                        break;
6064
                    case 118:
6065
                        $function = 'TRIM';
6066
                        $args = 1;
6067
6068
                        break;
6069
                    case 119:
6070
                        $function = 'REPLACE';
6071
                        $args = 4;
6072
6073
                        break;
6074
                    case 121:
6075
                        $function = 'CODE';
6076
                        $args = 1;
6077
6078
                        break;
6079
                    case 126:
6080
                        $function = 'ISERR';
6081
                        $args = 1;
6082
6083
                        break;
6084
                    case 127:
6085
                        $function = 'ISTEXT';
6086
                        $args = 1;
6087
6088
                        break;
6089
                    case 128:
6090
                        $function = 'ISNUMBER';
6091
                        $args = 1;
6092
6093
                        break;
6094
                    case 129:
6095
                        $function = 'ISBLANK';
6096
                        $args = 1;
6097
6098
                        break;
6099
                    case 130:
6100
                        $function = 'T';
6101
                        $args = 1;
6102
6103
                        break;
6104
                    case 131:
6105
                        $function = 'N';
6106
                        $args = 1;
6107
6108
                        break;
6109
                    case 140:
6110
                        $function = 'DATEVALUE';
6111
                        $args = 1;
6112
6113
                        break;
6114
                    case 141:
6115
                        $function = 'TIMEVALUE';
6116
                        $args = 1;
6117
6118
                        break;
6119
                    case 142:
6120
                        $function = 'SLN';
6121
                        $args = 3;
6122
6123
                        break;
6124
                    case 143:
6125
                        $function = 'SYD';
6126
                        $args = 4;
6127
6128
                        break;
6129
                    case 162:
6130
                        $function = 'CLEAN';
6131
                        $args = 1;
6132
6133
                        break;
6134
                    case 163:
6135
                        $function = 'MDETERM';
6136
                        $args = 1;
6137
6138
                        break;
6139
                    case 164:
6140
                        $function = 'MINVERSE';
6141
                        $args = 1;
6142
6143
                        break;
6144
                    case 165:
6145
                        $function = 'MMULT';
6146
                        $args = 2;
6147
6148
                        break;
6149
                    case 184:
6150
                        $function = 'FACT';
6151
                        $args = 1;
6152
6153
                        break;
6154
                    case 189:
6155
                        $function = 'DPRODUCT';
6156
                        $args = 3;
6157
6158
                        break;
6159
                    case 190:
6160
                        $function = 'ISNONTEXT';
6161
                        $args = 1;
6162
6163
                        break;
6164
                    case 195:
6165
                        $function = 'DSTDEVP';
6166
                        $args = 3;
6167
6168
                        break;
6169
                    case 196:
6170
                        $function = 'DVARP';
6171
                        $args = 3;
6172
6173
                        break;
6174
                    case 198:
6175
                        $function = 'ISLOGICAL';
6176
                        $args = 1;
6177
6178
                        break;
6179
                    case 199:
6180
                        $function = 'DCOUNTA';
6181
                        $args = 3;
6182
6183
                        break;
6184
                    case 207:
6185
                        $function = 'REPLACEB';
6186
                        $args = 4;
6187
6188
                        break;
6189
                    case 210:
6190
                        $function = 'MIDB';
6191
                        $args = 3;
6192
6193
                        break;
6194
                    case 211:
6195
                        $function = 'LENB';
6196
                        $args = 1;
6197
6198
                        break;
6199
                    case 212:
6200
                        $function = 'ROUNDUP';
6201
                        $args = 2;
6202
6203
                        break;
6204
                    case 213:
6205
                        $function = 'ROUNDDOWN';
6206
                        $args = 2;
6207
6208
                        break;
6209
                    case 214:
6210
                        $function = 'ASC';
6211
                        $args = 1;
6212
6213
                        break;
6214
                    case 215:
6215
                        $function = 'DBCS';
6216
                        $args = 1;
6217
6218
                        break;
6219
                    case 221:
6220
                        $function = 'TODAY';
6221
                        $args = 0;
6222
6223
                        break;
6224
                    case 229:
6225
                        $function = 'SINH';
6226
                        $args = 1;
6227
6228
                        break;
6229
                    case 230:
6230
                        $function = 'COSH';
6231
                        $args = 1;
6232
6233
                        break;
6234
                    case 231:
6235
                        $function = 'TANH';
6236
                        $args = 1;
6237
6238
                        break;
6239
                    case 232:
6240
                        $function = 'ASINH';
6241
                        $args = 1;
6242
6243
                        break;
6244
                    case 233:
6245
                        $function = 'ACOSH';
6246
                        $args = 1;
6247
6248
                        break;
6249
                    case 234:
6250
                        $function = 'ATANH';
6251
                        $args = 1;
6252
6253
                        break;
6254
                    case 235:
6255
                        $function = 'DGET';
6256
                        $args = 3;
6257
6258
                        break;
6259
                    case 244:
6260
                        $function = 'INFO';
6261
                        $args = 1;
6262
6263
                        break;
6264
                    case 252:
6265
                        $function = 'FREQUENCY';
6266
                        $args = 2;
6267
6268
                        break;
6269
                    case 261:
6270
                        $function = 'ERROR.TYPE';
6271
                        $args = 1;
6272
6273
                        break;
6274
                    case 271:
6275
                        $function = 'GAMMALN';
6276
                        $args = 1;
6277
6278
                        break;
6279
                    case 273:
6280
                        $function = 'BINOMDIST';
6281
                        $args = 4;
6282
6283
                        break;
6284
                    case 274:
6285
                        $function = 'CHIDIST';
6286
                        $args = 2;
6287
6288
                        break;
6289
                    case 275:
6290
                        $function = 'CHIINV';
6291
                        $args = 2;
6292
6293
                        break;
6294
                    case 276:
6295
                        $function = 'COMBIN';
6296
                        $args = 2;
6297
6298
                        break;
6299
                    case 277:
6300
                        $function = 'CONFIDENCE';
6301
                        $args = 3;
6302
6303
                        break;
6304
                    case 278:
6305
                        $function = 'CRITBINOM';
6306
                        $args = 3;
6307
6308
                        break;
6309
                    case 279:
6310
                        $function = 'EVEN';
6311
                        $args = 1;
6312
6313
                        break;
6314
                    case 280:
6315
                        $function = 'EXPONDIST';
6316
                        $args = 3;
6317
6318
                        break;
6319
                    case 281:
6320
                        $function = 'FDIST';
6321
                        $args = 3;
6322
6323
                        break;
6324
                    case 282:
6325
                        $function = 'FINV';
6326
                        $args = 3;
6327
6328
                        break;
6329
                    case 283:
6330
                        $function = 'FISHER';
6331
                        $args = 1;
6332
6333
                        break;
6334
                    case 284:
6335
                        $function = 'FISHERINV';
6336
                        $args = 1;
6337
6338
                        break;
6339
                    case 285:
6340
                        $function = 'FLOOR';
6341
                        $args = 2;
6342
6343
                        break;
6344
                    case 286:
6345
                        $function = 'GAMMADIST';
6346
                        $args = 4;
6347
6348
                        break;
6349
                    case 287:
6350
                        $function = 'GAMMAINV';
6351
                        $args = 3;
6352
6353
                        break;
6354
                    case 288:
6355
                        $function = 'CEILING';
6356
                        $args = 2;
6357
6358
                        break;
6359
                    case 289:
6360
                        $function = 'HYPGEOMDIST';
6361
                        $args = 4;
6362
6363
                        break;
6364
                    case 290:
6365
                        $function = 'LOGNORMDIST';
6366
                        $args = 3;
6367
6368
                        break;
6369
                    case 291:
6370
                        $function = 'LOGINV';
6371
                        $args = 3;
6372
6373
                        break;
6374
                    case 292:
6375
                        $function = 'NEGBINOMDIST';
6376
                        $args = 3;
6377
6378
                        break;
6379
                    case 293:
6380
                        $function = 'NORMDIST';
6381
                        $args = 4;
6382
6383
                        break;
6384
                    case 294:
6385
                        $function = 'NORMSDIST';
6386
                        $args = 1;
6387
6388
                        break;
6389
                    case 295:
6390
                        $function = 'NORMINV';
6391
                        $args = 3;
6392
6393
                        break;
6394
                    case 296:
6395
                        $function = 'NORMSINV';
6396
                        $args = 1;
6397
6398
                        break;
6399
                    case 297:
6400
                        $function = 'STANDARDIZE';
6401
                        $args = 3;
6402
6403
                        break;
6404
                    case 298:
6405
                        $function = 'ODD';
6406
                        $args = 1;
6407
6408
                        break;
6409
                    case 299:
6410
                        $function = 'PERMUT';
6411
                        $args = 2;
6412
6413
                        break;
6414
                    case 300:
6415
                        $function = 'POISSON';
6416
                        $args = 3;
6417
6418
                        break;
6419
                    case 301:
6420
                        $function = 'TDIST';
6421
                        $args = 3;
6422
6423
                        break;
6424
                    case 302:
6425
                        $function = 'WEIBULL';
6426
                        $args = 4;
6427
6428
                        break;
6429
                    case 303:
6430
                        $function = 'SUMXMY2';
6431
                        $args = 2;
6432
6433
                        break;
6434
                    case 304:
6435
                        $function = 'SUMX2MY2';
6436
                        $args = 2;
6437
6438
                        break;
6439
                    case 305:
6440
                        $function = 'SUMX2PY2';
6441
                        $args = 2;
6442
6443
                        break;
6444
                    case 306:
6445
                        $function = 'CHITEST';
6446
                        $args = 2;
6447
6448
                        break;
6449
                    case 307:
6450
                        $function = 'CORREL';
6451
                        $args = 2;
6452
6453
                        break;
6454
                    case 308:
6455
                        $function = 'COVAR';
6456
                        $args = 2;
6457
6458
                        break;
6459
                    case 309:
6460
                        $function = 'FORECAST';
6461
                        $args = 3;
6462
6463
                        break;
6464
                    case 310:
6465
                        $function = 'FTEST';
6466
                        $args = 2;
6467
6468
                        break;
6469
                    case 311:
6470
                        $function = 'INTERCEPT';
6471
                        $args = 2;
6472
6473
                        break;
6474
                    case 312:
6475
                        $function = 'PEARSON';
6476
                        $args = 2;
6477
6478
                        break;
6479
                    case 313:
6480
                        $function = 'RSQ';
6481
                        $args = 2;
6482
6483
                        break;
6484
                    case 314:
6485
                        $function = 'STEYX';
6486
                        $args = 2;
6487
6488
                        break;
6489
                    case 315:
6490
                        $function = 'SLOPE';
6491
                        $args = 2;
6492
6493
                        break;
6494
                    case 316:
6495
                        $function = 'TTEST';
6496
                        $args = 4;
6497
6498
                        break;
6499
                    case 325:
6500
                        $function = 'LARGE';
6501
                        $args = 2;
6502
6503
                        break;
6504
                    case 326:
6505
                        $function = 'SMALL';
6506
                        $args = 2;
6507
6508
                        break;
6509
                    case 327:
6510
                        $function = 'QUARTILE';
6511
                        $args = 2;
6512
6513
                        break;
6514
                    case 328:
6515
                        $function = 'PERCENTILE';
6516
                        $args = 2;
6517
6518
                        break;
6519
                    case 331:
6520
                        $function = 'TRIMMEAN';
6521
                        $args = 2;
6522
6523
                        break;
6524
                    case 332:
6525
                        $function = 'TINV';
6526
                        $args = 2;
6527
6528
                        break;
6529
                    case 337:
6530
                        $function = 'POWER';
6531
                        $args = 2;
6532
6533
                        break;
6534
                    case 342:
6535
                        $function = 'RADIANS';
6536
                        $args = 1;
6537
6538
                        break;
6539
                    case 343:
6540
                        $function = 'DEGREES';
6541
                        $args = 1;
6542
6543
                        break;
6544
                    case 346:
6545
                        $function = 'COUNTIF';
6546
                        $args = 2;
6547
6548
                        break;
6549
                    case 347:
6550
                        $function = 'COUNTBLANK';
6551
                        $args = 1;
6552
6553
                        break;
6554
                    case 350:
6555
                        $function = 'ISPMT';
6556
                        $args = 4;
6557
6558
                        break;
6559
                    case 351:
6560
                        $function = 'DATEDIF';
6561
                        $args = 3;
6562
6563
                        break;
6564
                    case 352:
6565
                        $function = 'DATESTRING';
6566
                        $args = 1;
6567
6568
                        break;
6569
                    case 353:
6570
                        $function = 'NUMBERSTRING';
6571
                        $args = 2;
6572
6573
                        break;
6574
                    case 360:
6575
                        $function = 'PHONETIC';
6576
                        $args = 1;
6577
6578
                        break;
6579
                    case 368:
6580
                        $function = 'BAHTTEXT';
6581
                        $args = 1;
6582
6583
                        break;
6584
                    default:
6585
                        throw new Exception('Unrecognized function in formula');
6586
                        break;
6587
                }
6588 8
                $data = ['function' => $function, 'args' => $args];
6589
6590 8
                break;
6591 10
            case 0x22:    //    function with variable number of arguments
6592 10
            case 0x42:
6593 10
            case 0x62:
6594 1
                $name = 'tFuncV';
6595 1
                $size = 4;
6596
                // offset: 1; size: 1; number of arguments
6597 1
                $args = ord($formulaData[1]);
6598
                // offset: 2: size: 2; index to built-in sheet function
6599 1
                $index = self::getUInt2d($formulaData, 2);
6600
                switch ($index) {
6601 1
                    case 0:
6602
                        $function = 'COUNT';
6603
6604
                        break;
6605 1
                    case 1:
6606 1
                        $function = 'IF';
6607
6608 1
                        break;
6609 1
                    case 4:
6610 1
                        $function = 'SUM';
6611
6612 1
                        break;
6613
                    case 5:
6614
                        $function = 'AVERAGE';
6615
6616
                        break;
6617
                    case 6:
6618
                        $function = 'MIN';
6619
6620
                        break;
6621
                    case 7:
6622
                        $function = 'MAX';
6623
6624
                        break;
6625
                    case 8:
6626
                        $function = 'ROW';
6627
6628
                        break;
6629
                    case 9:
6630
                        $function = 'COLUMN';
6631
6632
                        break;
6633
                    case 11:
6634
                        $function = 'NPV';
6635
6636
                        break;
6637
                    case 12:
6638
                        $function = 'STDEV';
6639
6640
                        break;
6641
                    case 13:
6642
                        $function = 'DOLLAR';
6643
6644
                        break;
6645
                    case 14:
6646
                        $function = 'FIXED';
6647
6648
                        break;
6649
                    case 28:
6650
                        $function = 'LOOKUP';
6651
6652
                        break;
6653
                    case 29:
6654
                        $function = 'INDEX';
6655
6656
                        break;
6657
                    case 36:
6658
                        $function = 'AND';
6659
6660
                        break;
6661
                    case 37:
6662
                        $function = 'OR';
6663
6664
                        break;
6665
                    case 46:
6666
                        $function = 'VAR';
6667
6668
                        break;
6669
                    case 49:
6670
                        $function = 'LINEST';
6671
6672
                        break;
6673
                    case 50:
6674
                        $function = 'TREND';
6675
6676
                        break;
6677
                    case 51:
6678
                        $function = 'LOGEST';
6679
6680
                        break;
6681
                    case 52:
6682
                        $function = 'GROWTH';
6683
6684
                        break;
6685
                    case 56:
6686
                        $function = 'PV';
6687
6688
                        break;
6689
                    case 57:
6690
                        $function = 'FV';
6691
6692
                        break;
6693
                    case 58:
6694
                        $function = 'NPER';
6695
6696
                        break;
6697
                    case 59:
6698
                        $function = 'PMT';
6699
6700
                        break;
6701
                    case 60:
6702
                        $function = 'RATE';
6703
6704
                        break;
6705
                    case 62:
6706
                        $function = 'IRR';
6707
6708
                        break;
6709
                    case 64:
6710
                        $function = 'MATCH';
6711
6712
                        break;
6713
                    case 70:
6714
                        $function = 'WEEKDAY';
6715
6716
                        break;
6717
                    case 78:
6718
                        $function = 'OFFSET';
6719
6720
                        break;
6721
                    case 82:
6722
                        $function = 'SEARCH';
6723
6724
                        break;
6725
                    case 100:
6726
                        $function = 'CHOOSE';
6727
6728
                        break;
6729
                    case 101:
6730
                        $function = 'HLOOKUP';
6731
6732
                        break;
6733
                    case 102:
6734
                        $function = 'VLOOKUP';
6735
6736
                        break;
6737
                    case 109:
6738
                        $function = 'LOG';
6739
6740
                        break;
6741
                    case 115:
6742
                        $function = 'LEFT';
6743
6744
                        break;
6745
                    case 116:
6746
                        $function = 'RIGHT';
6747
6748
                        break;
6749
                    case 120:
6750
                        $function = 'SUBSTITUTE';
6751
6752
                        break;
6753
                    case 124:
6754
                        $function = 'FIND';
6755
6756
                        break;
6757
                    case 125:
6758
                        $function = 'CELL';
6759
6760
                        break;
6761
                    case 144:
6762
                        $function = 'DDB';
6763
6764
                        break;
6765
                    case 148:
6766
                        $function = 'INDIRECT';
6767
6768
                        break;
6769
                    case 167:
6770
                        $function = 'IPMT';
6771
6772
                        break;
6773
                    case 168:
6774
                        $function = 'PPMT';
6775
6776
                        break;
6777
                    case 169:
6778
                        $function = 'COUNTA';
6779
6780
                        break;
6781
                    case 183:
6782
                        $function = 'PRODUCT';
6783
6784
                        break;
6785
                    case 193:
6786
                        $function = 'STDEVP';
6787
6788
                        break;
6789
                    case 194:
6790
                        $function = 'VARP';
6791
6792
                        break;
6793
                    case 197:
6794
                        $function = 'TRUNC';
6795
6796
                        break;
6797
                    case 204:
6798
                        $function = 'USDOLLAR';
6799
6800
                        break;
6801
                    case 205:
6802
                        $function = 'FINDB';
6803
6804
                        break;
6805
                    case 206:
6806
                        $function = 'SEARCHB';
6807
6808
                        break;
6809
                    case 208:
6810
                        $function = 'LEFTB';
6811
6812
                        break;
6813
                    case 209:
6814
                        $function = 'RIGHTB';
6815
6816
                        break;
6817
                    case 216:
6818
                        $function = 'RANK';
6819
6820
                        break;
6821
                    case 219:
6822
                        $function = 'ADDRESS';
6823
6824
                        break;
6825
                    case 220:
6826
                        $function = 'DAYS360';
6827
6828
                        break;
6829
                    case 222:
6830
                        $function = 'VDB';
6831
6832
                        break;
6833
                    case 227:
6834
                        $function = 'MEDIAN';
6835
6836
                        break;
6837
                    case 228:
6838
                        $function = 'SUMPRODUCT';
6839
6840
                        break;
6841
                    case 247:
6842
                        $function = 'DB';
6843
6844
                        break;
6845
                    case 255:
6846
                        $function = '';
6847
6848
                        break;
6849
                    case 269:
6850
                        $function = 'AVEDEV';
6851
6852
                        break;
6853
                    case 270:
6854
                        $function = 'BETADIST';
6855
6856
                        break;
6857
                    case 272:
6858
                        $function = 'BETAINV';
6859
6860
                        break;
6861
                    case 317:
6862
                        $function = 'PROB';
6863
6864
                        break;
6865
                    case 318:
6866
                        $function = 'DEVSQ';
6867
6868
                        break;
6869
                    case 319:
6870
                        $function = 'GEOMEAN';
6871
6872
                        break;
6873
                    case 320:
6874
                        $function = 'HARMEAN';
6875
6876
                        break;
6877
                    case 321:
6878
                        $function = 'SUMSQ';
6879
6880
                        break;
6881
                    case 322:
6882
                        $function = 'KURT';
6883
6884
                        break;
6885
                    case 323:
6886
                        $function = 'SKEW';
6887
6888
                        break;
6889
                    case 324:
6890
                        $function = 'ZTEST';
6891
6892
                        break;
6893
                    case 329:
6894
                        $function = 'PERCENTRANK';
6895
6896
                        break;
6897
                    case 330:
6898
                        $function = 'MODE';
6899
6900
                        break;
6901
                    case 336:
6902
                        $function = 'CONCATENATE';
6903
6904
                        break;
6905
                    case 344:
6906
                        $function = 'SUBTOTAL';
6907
6908
                        break;
6909
                    case 345:
6910
                        $function = 'SUMIF';
6911
6912
                        break;
6913
                    case 354:
6914
                        $function = 'ROMAN';
6915
6916
                        break;
6917
                    case 358:
6918
                        $function = 'GETPIVOTDATA';
6919
6920
                        break;
6921
                    case 359:
6922
                        $function = 'HYPERLINK';
6923
6924
                        break;
6925
                    case 361:
6926
                        $function = 'AVERAGEA';
6927
6928
                        break;
6929
                    case 362:
6930
                        $function = 'MAXA';
6931
6932
                        break;
6933
                    case 363:
6934
                        $function = 'MINA';
6935
6936
                        break;
6937
                    case 364:
6938
                        $function = 'STDEVPA';
6939
6940
                        break;
6941
                    case 365:
6942
                        $function = 'VARPA';
6943
6944
                        break;
6945
                    case 366:
6946
                        $function = 'STDEVA';
6947
6948
                        break;
6949
                    case 367:
6950
                        $function = 'VARA';
6951
6952
                        break;
6953
                    default:
6954
                        throw new Exception('Unrecognized function in formula');
6955
                        break;
6956
                }
6957 1
                $data = ['function' => $function, 'args' => $args];
6958
6959 1
                break;
6960 10
            case 0x23:    //    index to defined name
6961 10
            case 0x43:
6962 10
            case 0x63:
6963
                $name = 'tName';
6964
                $size = 5;
6965
                // offset: 1; size: 2; one-based index to definedname record
6966
                $definedNameIndex = self::getUInt2d($formulaData, 1) - 1;
6967
                // offset: 2; size: 2; not used
6968
                $data = $this->definedname[$definedNameIndex]['name'];
6969
6970
                break;
6971 10
            case 0x24:    //    single cell reference e.g. A5
6972 10
            case 0x44:
6973 10
            case 0x64:
6974 2
                $name = 'tRef';
6975 2
                $size = 5;
6976 2
                $data = $this->readBIFF8CellAddress(substr($formulaData, 1, 4));
6977
6978 2
                break;
6979 10
            case 0x25:    //    cell range reference to cells in the same sheet (2d)
6980 1
            case 0x45:
6981 1
            case 0x65:
6982 10
                $name = 'tArea';
6983 10
                $size = 9;
6984 10
                $data = $this->readBIFF8CellRangeAddress(substr($formulaData, 1, 8));
6985
6986 10
                break;
6987 1
            case 0x26:    //    Constant reference sub-expression
6988 1
            case 0x46:
6989 1 View Code Duplication
            case 0x66:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6990
                $name = 'tMemArea';
6991
                // offset: 1; size: 4; not used
6992
                // offset: 5; size: 2; size of the following subexpression
6993
                $subSize = self::getUInt2d($formulaData, 5);
6994
                $size = 7 + $subSize;
6995
                $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
6996
6997
                break;
6998 1
            case 0x27:    //    Deleted constant reference sub-expression
6999 1
            case 0x47:
7000 1 View Code Duplication
            case 0x67:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7001
                $name = 'tMemErr';
7002
                // offset: 1; size: 4; not used
7003
                // offset: 5; size: 2; size of the following subexpression
7004
                $subSize = self::getUInt2d($formulaData, 5);
7005
                $size = 7 + $subSize;
7006
                $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
7007
7008
                break;
7009 1
            case 0x29:    //    Variable reference sub-expression
7010 1
            case 0x49:
7011 1 View Code Duplication
            case 0x69:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7012
                $name = 'tMemFunc';
7013
                // offset: 1; size: 2; size of the following sub-expression
7014
                $subSize = self::getUInt2d($formulaData, 1);
7015
                $size = 3 + $subSize;
7016
                $data = $this->getFormulaFromData(substr($formulaData, 3, $subSize));
7017
7018
                break;
7019 1
            case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places
7020 1
            case 0x4C:
7021 1
            case 0x6C:
7022
                $name = 'tRefN';
7023
                $size = 5;
7024
                $data = $this->readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell);
7025
7026
                break;
7027 1
            case 0x2D:    //    Relative 2d range reference
7028 1
            case 0x4D:
7029 1
            case 0x6D:
7030
                $name = 'tAreaN';
7031
                $size = 9;
7032
                $data = $this->readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell);
7033
7034
                break;
7035 1
            case 0x39:    //    External name
7036 1
            case 0x59:
7037 1
            case 0x79:
7038
                $name = 'tNameX';
7039
                $size = 7;
7040
                // offset: 1; size: 2; index to REF entry in EXTERNSHEET record
7041
                // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record
7042
                $index = self::getUInt2d($formulaData, 3);
7043
                // assume index is to EXTERNNAME record
7044
                $data = $this->externalNames[$index - 1]['name'];
7045
                // offset: 5; size: 2; not used
7046
                break;
7047 1
            case 0x3A:    //    3d reference to cell
7048 1
            case 0x5A:
7049 1 View Code Duplication
            case 0x7A:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7050
                $name = 'tRef3d';
7051
                $size = 7;
7052
7053
                try {
7054
                    // offset: 1; size: 2; index to REF entry
7055
                    $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1));
7056
                    // offset: 3; size: 4; cell address
7057
                    $cellAddress = $this->readBIFF8CellAddress(substr($formulaData, 3, 4));
7058
7059
                    $data = "$sheetRange!$cellAddress";
7060
                } catch (PhpSpreadsheetException $e) {
7061
                    // deleted sheet reference
7062
                    $data = '#REF!';
7063
                }
7064
7065
                break;
7066 1
            case 0x3B:    //    3d reference to cell range
7067
            case 0x5B:
7068 View Code Duplication
            case 0x7B:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7069 1
                $name = 'tArea3d';
7070 1
                $size = 11;
7071
7072
                try {
7073
                    // offset: 1; size: 2; index to REF entry
7074 1
                    $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1));
7075
                    // offset: 3; size: 8; cell address
7076 1
                    $cellRangeAddress = $this->readBIFF8CellRangeAddress(substr($formulaData, 3, 8));
7077
7078 1
                    $data = "$sheetRange!$cellRangeAddress";
7079
                } catch (PhpSpreadsheetException $e) {
7080
                    // deleted sheet reference
7081
                    $data = '#REF!';
7082
                }
7083
7084 1
                break;
7085
            // Unknown cases    // don't know how to deal with
7086
            default:
7087
                throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula');
7088
                break;
7089
        }
7090
7091
        return [
7092 10
            'id' => $id,
7093 10
            'name' => $name,
7094 10
            'size' => $size,
7095 10
            'data' => $data,
7096
        ];
7097
    }
7098
7099
    /**
7100
     * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2'
7101
     * section 3.3.4.
7102
     *
7103
     * @param string $cellAddressStructure
7104
     *
7105
     * @return string
7106
     */
7107 2
    private function readBIFF8CellAddress($cellAddressStructure)
7108
    {
7109
        // 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...
7110 2
        $row = self::getUInt2d($cellAddressStructure, 0) + 1;
7111
7112
        // offset: 2; size: 2; index to column or column offset + relative flags
7113
        // bit: 7-0; mask 0x00FF; column index
7114 2
        $column = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($cellAddressStructure, 2)) + 1);
7115
7116
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7117 2
        if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) {
7118 1
            $column = '$' . $column;
7119
        }
7120
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7121 2
        if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
7122 1
            $row = '$' . $row;
7123
        }
7124
7125 2
        return $column . $row;
7126
    }
7127
7128
    /**
7129
     * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column
7130
     * to indicate offsets from a base cell
7131
     * section 3.3.4.
7132
     *
7133
     * @param string $cellAddressStructure
7134
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
7135
     *
7136
     * @return string
7137
     */
7138
    private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1')
7139
    {
7140
        list($baseCol, $baseRow) = Coordinate::coordinateFromString($baseCell);
7141
        $baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
7142
7143
        // 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...
7144
        $rowIndex = self::getUInt2d($cellAddressStructure, 0);
7145
        $row = self::getUInt2d($cellAddressStructure, 0) + 1;
7146
7147
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7148 View Code Duplication
        if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7149
            // offset: 2; size: 2; index to column or column offset + relative flags
7150
            // bit: 7-0; mask 0x00FF; column index
7151
            $colIndex = 0x00FF & self::getUInt2d($cellAddressStructure, 2);
7152
7153
            $column = Coordinate::stringFromColumnIndex($colIndex + 1);
7154
            $column = '$' . $column;
7155
        } else {
7156
            // offset: 2; size: 2; index to column or column offset + relative flags
7157
            // bit: 7-0; mask 0x00FF; column index
7158
            $relativeColIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2);
7159
            $colIndex = $baseCol + $relativeColIndex;
7160
            $colIndex = ($colIndex < 256) ? $colIndex : $colIndex - 256;
7161
            $colIndex = ($colIndex >= 0) ? $colIndex : $colIndex + 256;
7162
            $column = Coordinate::stringFromColumnIndex($colIndex + 1);
7163
        }
7164
7165
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7166
        if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
7167
            $row = '$' . $row;
7168
        } else {
7169
            $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536;
7170
            $row = $baseRow + $rowIndex;
7171
        }
7172
7173
        return $column . $row;
7174
    }
7175
7176
    /**
7177
     * Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1'
7178
     * always fixed range
7179
     * section 2.5.14.
7180
     *
7181
     * @param string $subData
7182
     *
7183
     * @throws Exception
7184
     *
7185
     * @return string
7186
     */
7187 18
    private function readBIFF5CellRangeAddressFixed($subData)
7188
    {
7189
        // offset: 0; size: 2; index to first row
7190 18
        $fr = self::getUInt2d($subData, 0) + 1;
7191
7192
        // offset: 2; size: 2; index to last row
7193 18
        $lr = self::getUInt2d($subData, 2) + 1;
7194
7195
        // offset: 4; size: 1; index to first column
7196 18
        $fc = ord($subData[4]);
7197
7198
        // offset: 5; size: 1; index to last column
7199 18
        $lc = ord($subData[5]);
7200
7201
        // check values
7202 18
        if ($fr > $lr || $fc > $lc) {
7203
            throw new Exception('Not a cell range address');
7204
        }
7205
7206
        // column index to letter
7207 18
        $fc = Coordinate::stringFromColumnIndex($fc + 1);
7208 18
        $lc = Coordinate::stringFromColumnIndex($lc + 1);
7209
7210 18
        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...
7211 14
            return "$fc$fr";
7212
        }
7213
7214 12
        return "$fc$fr:$lc$lr";
7215
    }
7216
7217
    /**
7218
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1'
7219
     * always fixed range
7220
     * section 2.5.14.
7221
     *
7222
     * @param string $subData
7223
     *
7224
     * @throws Exception
7225
     *
7226
     * @return string
7227
     */
7228 11
    private function readBIFF8CellRangeAddressFixed($subData)
7229
    {
7230
        // offset: 0; size: 2; index to first row
7231 11
        $fr = self::getUInt2d($subData, 0) + 1;
7232
7233
        // offset: 2; size: 2; index to last row
7234 11
        $lr = self::getUInt2d($subData, 2) + 1;
7235
7236
        // offset: 4; size: 2; index to first column
7237 11
        $fc = self::getUInt2d($subData, 4);
7238
7239
        // offset: 6; size: 2; index to last column
7240 11
        $lc = self::getUInt2d($subData, 6);
7241
7242
        // check values
7243 11
        if ($fr > $lr || $fc > $lc) {
7244
            throw new Exception('Not a cell range address');
7245
        }
7246
7247
        // column index to letter
7248 11
        $fc = Coordinate::stringFromColumnIndex($fc + 1);
7249 11
        $lc = Coordinate::stringFromColumnIndex($lc + 1);
7250
7251 11
        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...
7252 2
            return "$fc$fr";
7253
        }
7254
7255 11
        return "$fc$fr:$lc$lr";
7256
    }
7257
7258
    /**
7259
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6'
7260
     * there are flags indicating whether column/row index is relative
7261
     * section 3.3.4.
7262
     *
7263
     * @param string $subData
7264
     *
7265
     * @return string
7266
     */
7267 10
    private function readBIFF8CellRangeAddress($subData)
7268
    {
7269
        // todo: if cell range is just a single cell, should this funciton
7270
        // not just return e.g. 'A1' and not 'A1:A1' ?
7271
7272
        // 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...
7273 10
        $fr = self::getUInt2d($subData, 0) + 1;
7274
7275
        // 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...
7276 10
        $lr = self::getUInt2d($subData, 2) + 1;
7277
7278
        // offset: 4; size: 2; index to first column or column offset + relative flags
7279
7280
        // bit: 7-0; mask 0x00FF; column index
7281 10
        $fc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 4)) + 1);
7282
7283
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7284 10
        if (!(0x4000 & self::getUInt2d($subData, 4))) {
7285 1
            $fc = '$' . $fc;
7286
        }
7287
7288
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7289 10
        if (!(0x8000 & self::getUInt2d($subData, 4))) {
7290 1
            $fr = '$' . $fr;
7291
        }
7292
7293
        // offset: 6; size: 2; index to last column or column offset + relative flags
7294
7295
        // bit: 7-0; mask 0x00FF; column index
7296 10
        $lc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 6)) + 1);
7297
7298
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7299 10
        if (!(0x4000 & self::getUInt2d($subData, 6))) {
7300 1
            $lc = '$' . $lc;
7301
        }
7302
7303
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7304 10
        if (!(0x8000 & self::getUInt2d($subData, 6))) {
7305 1
            $lr = '$' . $lr;
7306
        }
7307
7308 10
        return "$fc$fr:$lc$lr";
7309
    }
7310
7311
    /**
7312
     * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column
7313
     * to indicate offsets from a base cell
7314
     * section 3.3.4.
7315
     *
7316
     * @param string $subData
7317
     * @param string $baseCell Base cell
7318
     *
7319
     * @return string Cell range address
7320
     */
7321
    private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1')
7322
    {
7323
        list($baseCol, $baseRow) = Coordinate::coordinateFromString($baseCell);
7324
        $baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
7325
7326
        // TODO: if cell range is just a single cell, should this funciton
7327
        // not just return e.g. 'A1' and not 'A1:A1' ?
7328
7329
        // offset: 0; size: 2; first row
7330
        $frIndex = self::getUInt2d($subData, 0); // adjust below
7331
7332
        // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767)
7333
        $lrIndex = self::getUInt2d($subData, 2); // adjust below
7334
7335
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7336 View Code Duplication
        if (!(0x4000 & self::getUInt2d($subData, 4))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7337
            // absolute column index
7338
            // offset: 4; size: 2; first column with relative/absolute flags
7339
            // bit: 7-0; mask 0x00FF; column index
7340
            $fcIndex = 0x00FF & self::getUInt2d($subData, 4);
7341
            $fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
7342
            $fc = '$' . $fc;
7343
        } else {
7344
            // column offset
7345
            // offset: 4; size: 2; first column with relative/absolute flags
7346
            // bit: 7-0; mask 0x00FF; column index
7347
            $relativeFcIndex = 0x00FF & self::getInt2d($subData, 4);
7348
            $fcIndex = $baseCol + $relativeFcIndex;
7349
            $fcIndex = ($fcIndex < 256) ? $fcIndex : $fcIndex - 256;
7350
            $fcIndex = ($fcIndex >= 0) ? $fcIndex : $fcIndex + 256;
7351
            $fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
7352
        }
7353
7354
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7355 View Code Duplication
        if (!(0x8000 & self::getUInt2d($subData, 4))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7356
            // absolute row index
7357
            $fr = $frIndex + 1;
7358
            $fr = '$' . $fr;
7359
        } else {
7360
            // row offset
7361
            $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536;
7362
            $fr = $baseRow + $frIndex;
7363
        }
7364
7365
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7366 View Code Duplication
        if (!(0x4000 & self::getUInt2d($subData, 6))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7367
            // absolute column index
7368
            // offset: 6; size: 2; last column with relative/absolute flags
7369
            // bit: 7-0; mask 0x00FF; column index
7370
            $lcIndex = 0x00FF & self::getUInt2d($subData, 6);
7371
            $lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
7372
            $lc = '$' . $lc;
7373
        } else {
7374
            // column offset
7375
            // offset: 4; size: 2; first column with relative/absolute flags
7376
            // bit: 7-0; mask 0x00FF; column index
7377
            $relativeLcIndex = 0x00FF & self::getInt2d($subData, 4);
7378
            $lcIndex = $baseCol + $relativeLcIndex;
7379
            $lcIndex = ($lcIndex < 256) ? $lcIndex : $lcIndex - 256;
7380
            $lcIndex = ($lcIndex >= 0) ? $lcIndex : $lcIndex + 256;
7381
            $lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
7382
        }
7383
7384
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7385 View Code Duplication
        if (!(0x8000 & self::getUInt2d($subData, 6))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7386
            // absolute row index
7387
            $lr = $lrIndex + 1;
7388
            $lr = '$' . $lr;
7389
        } else {
7390
            // row offset
7391
            $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536;
7392
            $lr = $baseRow + $lrIndex;
7393
        }
7394
7395
        return "$fc$fr:$lc$lr";
7396
    }
7397
7398
    /**
7399
     * Read BIFF8 cell range address list
7400
     * section 2.5.15.
7401
     *
7402
     * @param string $subData
7403
     *
7404
     * @return array
7405
     */
7406 11 View Code Duplication
    private function readBIFF8CellRangeAddressList($subData)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7407
    {
7408 11
        $cellRangeAddresses = [];
7409
7410
        // offset: 0; size: 2; number of the following cell range addresses
7411 11
        $nm = self::getUInt2d($subData, 0);
7412
7413 11
        $offset = 2;
7414
        // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses
7415 11
        for ($i = 0; $i < $nm; ++$i) {
7416 11
            $cellRangeAddresses[] = $this->readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
7417 11
            $offset += 8;
7418
        }
7419
7420
        return [
7421 11
            'size' => 2 + 8 * $nm,
7422 11
            'cellRangeAddresses' => $cellRangeAddresses,
7423
        ];
7424
    }
7425
7426
    /**
7427
     * Read BIFF5 cell range address list
7428
     * section 2.5.15.
7429
     *
7430
     * @param string $subData
7431
     *
7432
     * @return array
7433
     */
7434 18 View Code Duplication
    private function readBIFF5CellRangeAddressList($subData)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7435
    {
7436 18
        $cellRangeAddresses = [];
7437
7438
        // offset: 0; size: 2; number of the following cell range addresses
7439 18
        $nm = self::getUInt2d($subData, 0);
7440
7441 18
        $offset = 2;
7442
        // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses
7443 18
        for ($i = 0; $i < $nm; ++$i) {
7444 18
            $cellRangeAddresses[] = $this->readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6));
7445 18
            $offset += 6;
7446
        }
7447
7448
        return [
7449 18
            'size' => 2 + 6 * $nm,
7450 18
            'cellRangeAddresses' => $cellRangeAddresses,
7451
        ];
7452
    }
7453
7454
    /**
7455
     * Get a sheet range like Sheet1:Sheet3 from REF index
7456
     * Note: If there is only one sheet in the range, one gets e.g Sheet1
7457
     * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets,
7458
     * in which case an Exception is thrown.
7459
     *
7460
     * @param int $index
7461
     *
7462
     * @throws Exception
7463
     *
7464
     * @return false|string
7465
     */
7466 1
    private function readSheetRangeByRefIndex($index)
7467
    {
7468 1
        if (isset($this->ref[$index])) {
7469 1
            $type = $this->externalBooks[$this->ref[$index]['externalBookIndex']]['type'];
7470
7471
            switch ($type) {
7472 1
                case 'internal':
7473
                    // check if we have a deleted 3d reference
7474 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...
7475
                        throw new Exception('Deleted sheet reference');
7476
                    }
7477
7478
                    // we have normal sheet range (collapsed or uncollapsed)
7479 1
                    $firstSheetName = $this->sheets[$this->ref[$index]['firstSheetIndex']]['name'];
7480 1
                    $lastSheetName = $this->sheets[$this->ref[$index]['lastSheetIndex']]['name'];
7481
7482 1
                    if ($firstSheetName == $lastSheetName) {
7483
                        // collapsed sheet range
7484 1
                        $sheetRange = $firstSheetName;
7485
                    } else {
7486
                        $sheetRange = "$firstSheetName:$lastSheetName";
7487
                    }
7488
7489
                    // escape the single-quotes
7490 1
                    $sheetRange = str_replace("'", "''", $sheetRange);
7491
7492
                    // if there are special characters, we need to enclose the range in single-quotes
7493
                    // todo: check if we have identified the whole set of special characters
7494
                    // it seems that the following characters are not accepted for sheet names
7495
                    // and we may assume that they are not present: []*/:\?
7496 1
                    if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/u", $sheetRange)) {
7497
                        $sheetRange = "'$sheetRange'";
7498
                    }
7499
7500 1
                    return $sheetRange;
7501
                    break;
7502
                default:
7503
                    // TODO: external sheet support
7504
                    throw new Exception('Xls reader only supports internal sheets in fomulas');
7505
                    break;
7506
            }
7507
        }
7508
7509
        return false;
7510
    }
7511
7512
    /**
7513
     * read BIFF8 constant value array from array data
7514
     * returns e.g. array('value' => '{1,2;3,4}', 'size' => 40}
7515
     * section 2.5.8.
7516
     *
7517
     * @param string $arrayData
7518
     *
7519
     * @return array
7520
     */
7521
    private static function readBIFF8ConstantArray($arrayData)
7522
    {
7523
        // offset: 0; size: 1; number of columns decreased by 1
7524
        $nc = ord($arrayData[0]);
7525
7526
        // offset: 1; size: 2; number of rows decreased by 1
7527
        $nr = self::getUInt2d($arrayData, 1);
7528
        $size = 3; // initialize
7529
        $arrayData = substr($arrayData, 3);
7530
7531
        // 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...
7532
        $matrixChunks = [];
7533
        for ($r = 1; $r <= $nr + 1; ++$r) {
7534
            $items = [];
7535
            for ($c = 1; $c <= $nc + 1; ++$c) {
7536
                $constant = self::readBIFF8Constant($arrayData);
7537
                $items[] = $constant['value'];
7538
                $arrayData = substr($arrayData, $constant['size']);
7539
                $size += $constant['size'];
7540
            }
7541
            $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'
7542
        }
7543
        $matrix = '{' . implode(';', $matrixChunks) . '}';
7544
7545
        return [
7546
            'value' => $matrix,
7547
            'size' => $size,
7548
        ];
7549
    }
7550
7551
    /**
7552
     * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value'
7553
     * section 2.5.7
7554
     * returns e.g. array('value' => '5', 'size' => 9).
7555
     *
7556
     * @param string $valueData
7557
     *
7558
     * @return array
7559
     */
7560
    private static function readBIFF8Constant($valueData)
7561
    {
7562
        // offset: 0; size: 1; identifier for type of constant
7563
        $identifier = ord($valueData[0]);
7564
7565
        switch ($identifier) {
7566
            case 0x00: // empty constant (what is this?)
7567
                $value = '';
7568
                $size = 9;
7569
7570
                break;
7571
            case 0x01: // number
7572
                // offset: 1; size: 8; IEEE 754 floating-point value
7573
                $value = self::extractNumber(substr($valueData, 1, 8));
7574
                $size = 9;
7575
7576
                break;
7577
            case 0x02: // string value
7578
                // offset: 1; size: var; Unicode string, 16-bit string length
7579
                $string = self::readUnicodeStringLong(substr($valueData, 1));
7580
                $value = '"' . $string['value'] . '"';
7581
                $size = 1 + $string['size'];
7582
7583
                break;
7584
            case 0x04: // boolean
7585
                // 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...
7586
                if (ord($valueData[1])) {
7587
                    $value = 'TRUE';
7588
                } else {
7589
                    $value = 'FALSE';
7590
                }
7591
                $size = 9;
7592
7593
                break;
7594
            case 0x10: // error code
7595
                // offset: 1; size: 1; error code
7596
                $value = Xls\ErrorCode::lookup(ord($valueData[1]));
7597
                $size = 9;
7598
7599
                break;
7600
        }
7601
7602
        return [
7603
            'value' => $value,
7604
            'size' => $size,
7605
        ];
7606
    }
7607
7608
    /**
7609
     * Extract RGB color
7610
     * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4.
7611
     *
7612
     * @param string $rgb Encoded RGB value (4 bytes)
7613
     *
7614
     * @return array
7615
     */
7616 5
    private static function readRGB($rgb)
7617
    {
7618
        // offset: 0; size 1; Red component
7619 5
        $r = ord($rgb[0]);
7620
7621
        // offset: 1; size: 1; Green component
7622 5
        $g = ord($rgb[1]);
7623
7624
        // offset: 2; size: 1; Blue component
7625 5
        $b = ord($rgb[2]);
7626
7627
        // HEX notation, e.g. 'FF00FC'
7628 5
        $rgb = sprintf('%02X%02X%02X', $r, $g, $b);
7629
7630 5
        return ['rgb' => $rgb];
7631
    }
7632
7633
    /**
7634
     * Read byte string (8-bit string length)
7635
     * OpenOffice documentation: 2.5.2.
7636
     *
7637
     * @param string $subData
7638
     *
7639
     * @return array
7640
     */
7641 View Code Duplication
    private function readByteStringShort($subData)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7642
    {
7643
        // offset: 0; size: 1; length of the string (character count)
7644
        $ln = ord($subData[0]);
7645
7646
        // 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...
7647
        $value = $this->decodeCodepage(substr($subData, 1, $ln));
7648
7649
        return [
7650
            'value' => $value,
7651
            'size' => 1 + $ln, // size in bytes of data structure
7652
        ];
7653
    }
7654
7655
    /**
7656
     * Read byte string (16-bit string length)
7657
     * OpenOffice documentation: 2.5.2.
7658
     *
7659
     * @param string $subData
7660
     *
7661
     * @return array
7662
     */
7663 View Code Duplication
    private function readByteStringLong($subData)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7664
    {
7665
        // offset: 0; size: 2; length of the string (character count)
7666
        $ln = self::getUInt2d($subData, 0);
7667
7668
        // 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...
7669
        $value = $this->decodeCodepage(substr($subData, 2));
7670
7671
        //return $string;
7672
        return [
7673
            'value' => $value,
7674
            'size' => 2 + $ln, // size in bytes of data structure
7675
        ];
7676
    }
7677
7678
    /**
7679
     * Extracts an Excel Unicode short string (8-bit string length)
7680
     * OpenOffice documentation: 2.5.3
7681
     * function will automatically find out where the Unicode string ends.
7682
     *
7683
     * @param string $subData
7684
     *
7685
     * @return array
7686
     */
7687 22 View Code Duplication
    private static function readUnicodeStringShort($subData)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7688
    {
7689 22
        $value = '';
7690
7691
        // offset: 0: size: 1; length of the string (character count)
7692 22
        $characterCount = ord($subData[0]);
7693
7694 22
        $string = self::readUnicodeString(substr($subData, 1), $characterCount);
7695
7696
        // add 1 for the string length
7697 22
        $string['size'] += 1;
7698
7699 22
        return $string;
7700
    }
7701
7702
    /**
7703
     * Extracts an Excel Unicode long string (16-bit string length)
7704
     * OpenOffice documentation: 2.5.3
7705
     * this function is under construction, needs to support rich text, and Asian phonetic settings.
7706
     *
7707
     * @param string $subData
7708
     *
7709
     * @return array
7710
     */
7711 18 View Code Duplication
    private static function readUnicodeStringLong($subData)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7712
    {
7713 18
        $value = '';
7714
7715
        // offset: 0: size: 2; length of the string (character count)
7716 18
        $characterCount = self::getUInt2d($subData, 0);
7717
7718 18
        $string = self::readUnicodeString(substr($subData, 2), $characterCount);
7719
7720
        // add 2 for the string length
7721 18
        $string['size'] += 2;
7722
7723 18
        return $string;
7724
    }
7725
7726
    /**
7727
     * Read Unicode string with no string length field, but with known character count
7728
     * this function is under construction, needs to support rich text, and Asian phonetic settings
7729
     * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3.
7730
     *
7731
     * @param string $subData
7732
     * @param int $characterCount
7733
     *
7734
     * @return array
7735
     */
7736 22
    private static function readUnicodeString($subData, $characterCount)
7737
    {
7738 22
        $value = '';
7739
7740
        // offset: 0: size: 1; option flags
7741
        // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)
7742 22
        $isCompressed = !((0x01 & ord($subData[0])) >> 0);
7743
7744
        // bit: 2; mask: 0x04; Asian phonetic settings
7745 22
        $hasAsian = (0x04) & ord($subData[0]) >> 2;
7746
7747
        // bit: 3; mask: 0x08; Rich-Text settings
7748 22
        $hasRichText = (0x08) & ord($subData[0]) >> 3;
7749
7750
        // 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...
7751
        // this offset assumes richtext and Asian phonetic settings are off which is generally wrong
7752
        // needs to be fixed
7753 22
        $value = self::encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed);
7754
7755
        return [
7756 22
            'value' => $value,
7757 22
            'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags
7758
        ];
7759
    }
7760
7761
    /**
7762
     * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas.
7763
     * Example:  hello"world  -->  "hello""world".
7764
     *
7765
     * @param string $value UTF-8 encoded string
7766
     *
7767
     * @return string
7768
     */
7769 1
    private static function UTF8toExcelDoubleQuoted($value)
7770
    {
7771 1
        return '"' . str_replace('"', '""', $value) . '"';
7772
    }
7773
7774
    /**
7775
     * Reads first 8 bytes of a string and return IEEE 754 float.
7776
     *
7777
     * @param string $data Binary string that is at least 8 bytes long
7778
     *
7779
     * @return float
7780
     */
7781 19
    private static function extractNumber($data)
7782
    {
7783 19
        $rknumhigh = self::getInt4d($data, 4);
7784 19
        $rknumlow = self::getInt4d($data, 0);
7785 19
        $sign = ($rknumhigh & 0x80000000) >> 31;
7786 19
        $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023;
7787 19
        $mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
7788 19
        $mantissalow1 = ($rknumlow & 0x80000000) >> 31;
7789 19
        $mantissalow2 = ($rknumlow & 0x7fffffff);
7790 19
        $value = $mantissa / pow(2, (20 - $exp));
7791
7792 19
        if ($mantissalow1 != 0) {
7793 12
            $value += 1 / pow(2, (21 - $exp));
7794
        }
7795
7796 19
        $value += $mantissalow2 / pow(2, (52 - $exp));
7797 19
        if ($sign) {
7798 10
            $value *= -1;
7799
        }
7800
7801 19
        return $value;
7802
    }
7803
7804
    /**
7805
     * @param int $rknum
7806
     */
7807 12
    private static function getIEEE754($rknum)
7808
    {
7809 12
        if (($rknum & 0x02) != 0) {
7810
            $value = $rknum >> 2;
7811
        } else {
7812
            // changes by mmp, info on IEEE754 encoding from
7813
            // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
7814
            // The RK format calls for using only the most significant 30 bits
7815
            // of the 64 bit floating point value. The other 34 bits are assumed
7816
            // to be 0 so we use the upper 30 bits of $rknum as follows...
7817 12
            $sign = ($rknum & 0x80000000) >> 31;
7818 12
            $exp = ($rknum & 0x7ff00000) >> 20;
7819 12
            $mantissa = (0x100000 | ($rknum & 0x000ffffc));
7820 12
            $value = $mantissa / pow(2, (20 - ($exp - 1023)));
7821 12
            if ($sign) {
7822 10
                $value = -1 * $value;
7823
            }
7824
            //end of changes by mmp
7825
        }
7826 12
        if (($rknum & 0x01) != 0) {
7827 10
            $value /= 100;
7828
        }
7829
7830 12
        return $value;
7831
    }
7832
7833
    /**
7834
     * Get UTF-8 string from (compressed or uncompressed) UTF-16 string.
7835
     *
7836
     * @param string $string
7837
     * @param bool $compressed
7838
     *
7839
     * @return string
7840
     */
7841 22
    private static function encodeUTF16($string, $compressed = false)
7842
    {
7843 22
        if ($compressed) {
7844 20
            $string = self::uncompressByteString($string);
7845
        }
7846
7847 22
        return StringHelper::convertEncoding($string, 'UTF-8', 'UTF-16LE');
7848
    }
7849
7850
    /**
7851
     * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8.
7852
     *
7853
     * @param string $string
7854
     *
7855
     * @return string
7856
     */
7857 20
    private static function uncompressByteString($string)
7858
    {
7859 20
        $uncompressedString = '';
7860 20
        $strLen = strlen($string);
7861 20
        for ($i = 0; $i < $strLen; ++$i) {
7862 20
            $uncompressedString .= $string[$i] . "\0";
7863
        }
7864
7865 20
        return $uncompressedString;
7866
    }
7867
7868
    /**
7869
     * Convert string to UTF-8. Only used for BIFF5.
7870
     *
7871
     * @param string $string
7872
     *
7873
     * @return string
7874
     */
7875
    private function decodeCodepage($string)
7876
    {
7877
        return StringHelper::convertEncoding($string, 'UTF-8', $this->codepage);
7878
    }
7879
7880
    /**
7881
     * Read 16-bit unsigned integer.
7882
     *
7883
     * @param string $data
7884
     * @param int $pos
7885
     *
7886
     * @return int
7887
     */
7888 22
    public static function getUInt2d($data, $pos)
7889
    {
7890 22
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
7891
    }
7892
7893
    /**
7894
     * Read 16-bit signed integer.
7895
     *
7896
     * @param string $data
7897
     * @param int $pos
7898
     *
7899
     * @return int
7900
     */
7901
    public static function getInt2d($data, $pos)
7902
    {
7903
        return unpack('s', $data[$pos] . $data[$pos + 1])[1];
7904
    }
7905
7906
    /**
7907
     * Read 32-bit signed integer.
7908
     *
7909
     * @param string $data
7910
     * @param int $pos
7911
     *
7912
     * @return int
7913
     */
7914 22
    public static function getInt4d($data, $pos)
7915
    {
7916
        // FIX: represent numbers correctly on 64-bit system
7917
        // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
7918
        // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
7919 22
        $_or_24 = ord($data[$pos + 3]);
7920 22 View Code Duplication
        if ($_or_24 >= 128) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
7921
            // negative number
7922 12
            $_ord_24 = -abs((256 - $_or_24) << 24);
7923
        } else {
7924 22
            $_ord_24 = ($_or_24 & 127) << 24;
7925
        }
7926
7927 22
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
7928
    }
7929
7930 1
    private function parseRichText($is)
7931
    {
7932 1
        $value = new RichText();
7933 1
        $value->createText($is);
7934
7935 1
        return $value;
7936
    }
7937
}
7938