Completed
Push — develop ( d383bc...d9bd45 )
by Adrien
23:59
created

Xls::readBIFF8Constant()   C

Complexity

Conditions 7

Size

Total Lines 45
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 29
nop 1
dl 0
loc 45
ccs 0
cts 27
cp 0
crap 56
rs 5.5
c 0
b 0
f 0
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 24
    public function __construct()
414
    {
415 24
        $this->readFilter = new DefaultReadFilter();
416 24
    }
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 18
    public function load($pFilename)
624
    {
625
        // Read the OLE file
626 18
        $this->loadOLE($pFilename);
627
628
        // Initialisations
629 18
        $this->spreadsheet = new Spreadsheet();
630 18
        $this->spreadsheet->removeSheetByIndex(0); // remove 1st sheet
631 18
        if (!$this->readDataOnly) {
632 17
            $this->spreadsheet->removeCellStyleXfByIndex(0); // remove the default style
633 17
            $this->spreadsheet->removeCellXfByIndex(0); // remove the default style
634
        }
635
636
        // Read the summary information stream (containing meta data)
637 18
        $this->readSummaryInformation();
638
639
        // Read the Additional document summary information stream (containing application-specific meta data)
640 18
        $this->readDocumentSummaryInformation();
641
642
        // total byte size of Excel data (workbook global substream + sheet substreams)
643 18
        $this->dataSize = strlen($this->data);
644
645
        // initialize
646 18
        $this->pos = 0;
647 18
        $this->codepage = 'CP1252';
648 18
        $this->formats = [];
649 18
        $this->objFonts = [];
650 18
        $this->palette = [];
651 18
        $this->sheets = [];
652 18
        $this->externalBooks = [];
653 18
        $this->ref = [];
654 18
        $this->definedname = [];
655 18
        $this->sst = [];
656 18
        $this->drawingGroupData = '';
657 18
        $this->xfIndex = '';
658 18
        $this->mapCellXfIndex = [];
659 18
        $this->mapCellStyleXfIndex = [];
660
661
        // Parse Workbook Global Substream
662 18
        while ($this->pos < $this->dataSize) {
663 18
            $code = self::getUInt2d($this->data, $this->pos);
664
665
            switch ($code) {
666 18
                case self::XLS_TYPE_BOF:
667 18
                    $this->readBof();
668
669 18
                    break;
670 18
                case self::XLS_TYPE_FILEPASS:
671
                    $this->readFilepass();
672
673
                    break;
674 18
                case self::XLS_TYPE_CODEPAGE:
675 18
                    $this->readCodepage();
676
677 18
                    break;
678 18
                case self::XLS_TYPE_DATEMODE:
679 18
                    $this->readDateMode();
680
681 18
                    break;
682 18
                case self::XLS_TYPE_FONT:
683 18
                    $this->readFont();
684
685 18
                    break;
686 18
                case self::XLS_TYPE_FORMAT:
687 18
                    $this->readFormat();
688
689 18
                    break;
690 18
                case self::XLS_TYPE_XF:
691 18
                    $this->readXf();
692
693 18
                    break;
694 18
                case self::XLS_TYPE_XFEXT:
695 3
                    $this->readXfExt();
696
697 3
                    break;
698 18
                case self::XLS_TYPE_STYLE:
699 18
                    $this->readStyle();
700
701 18
                    break;
702 18
                case self::XLS_TYPE_PALETTE:
703 4
                    $this->readPalette();
704
705 4
                    break;
706 18
                case self::XLS_TYPE_SHEET:
707 18
                    $this->readSheet();
708
709 18
                    break;
710 18
                case self::XLS_TYPE_EXTERNALBOOK:
711 4
                    $this->readExternalBook();
712
713 4
                    break;
714 18
                case self::XLS_TYPE_EXTERNNAME:
715
                    $this->readExternName();
716
717
                    break;
718 18
                case self::XLS_TYPE_EXTERNSHEET:
719 4
                    $this->readExternSheet();
720
721 4
                    break;
722 18
                case self::XLS_TYPE_DEFINEDNAME:
723 1
                    $this->readDefinedName();
724
725 1
                    break;
726 18
                case self::XLS_TYPE_MSODRAWINGGROUP:
727 3
                    $this->readMsoDrawingGroup();
728
729 3
                    break;
730 18
                case self::XLS_TYPE_SST:
731 18
                    $this->readSst();
732
733 18
                    break;
734 18
                case self::XLS_TYPE_EOF:
735 18
                    $this->readDefault();
736
737 18
                    break 2;
738
                default:
739 18
                    $this->readDefault();
740
741 18
                    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 18
        if (!$this->readDataOnly) {
748 17
            foreach ($this->objFonts as $objFont) {
749 17 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 17
                    $color = Xls\Color::map($objFont->colorIndex, $this->palette, $this->version);
751 17
                    $objFont->getColor()->setRGB($color['rgb']);
752
                }
753
            }
754
755 17
            foreach ($this->spreadsheet->getCellXfCollection() as $objStyle) {
756
                // fill start and end color
757 17
                $fill = $objStyle->getFill();
758
759 17
                if (isset($fill->startcolorIndex)) {
760 17
                    $startColor = Xls\Color::map($fill->startcolorIndex, $this->palette, $this->version);
761 17
                    $fill->getStartColor()->setRGB($startColor['rgb']);
762
                }
763 17
                if (isset($fill->endcolorIndex)) {
764 17
                    $endColor = Xls\Color::map($fill->endcolorIndex, $this->palette, $this->version);
765 17
                    $fill->getEndColor()->setRGB($endColor['rgb']);
766
                }
767
768
                // border colors
769 17
                $top = $objStyle->getBorders()->getTop();
770 17
                $right = $objStyle->getBorders()->getRight();
771 17
                $bottom = $objStyle->getBorders()->getBottom();
772 17
                $left = $objStyle->getBorders()->getLeft();
773 17
                $diagonal = $objStyle->getBorders()->getDiagonal();
774
775 17 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 17
                    $borderTopColor = Xls\Color::map($top->colorIndex, $this->palette, $this->version);
777 17
                    $top->getColor()->setRGB($borderTopColor['rgb']);
778
                }
779 17 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 17
                    $borderRightColor = Xls\Color::map($right->colorIndex, $this->palette, $this->version);
781 17
                    $right->getColor()->setRGB($borderRightColor['rgb']);
782
                }
783 17 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 17
                    $borderBottomColor = Xls\Color::map($bottom->colorIndex, $this->palette, $this->version);
785 17
                    $bottom->getColor()->setRGB($borderBottomColor['rgb']);
786
                }
787 17 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 17
                    $borderLeftColor = Xls\Color::map($left->colorIndex, $this->palette, $this->version);
789 17
                    $left->getColor()->setRGB($borderLeftColor['rgb']);
790
                }
791 17 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 17
                    $borderDiagonalColor = Xls\Color::map($diagonal->colorIndex, $this->palette, $this->version);
793 17
                    $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']);
794
                }
795
            }
796
        }
797
798
        // treat MSODRAWINGGROUP records, workbook-level Escher
799 18
        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 18
        foreach ($this->sheets as $sheet) {
807 18
            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 18
            if (isset($this->loadSheetsOnly) && !in_array($sheet['name'], $this->loadSheetsOnly)) {
814 4
                continue;
815
            }
816
817
            // add sheet to PhpSpreadsheet object
818 18
            $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 18
            $this->phpSheet->setTitle($sheet['name'], false, false);
823 18
            $this->phpSheet->setSheetState($sheet['sheetState']);
824
825 18
            $this->pos = $sheet['offset'];
826
827
            // Initialize isFitToPages. May change after reading SHEETPR record.
828 18
            $this->isFitToPages = false;
829
830
            // Initialize drawingData
831 18
            $this->drawingData = '';
832
833
            // Initialize objs
834 18
            $this->objs = [];
835
836
            // Initialize shared formula parts
837 18
            $this->sharedFormulaParts = [];
838
839
            // Initialize shared formulas
840 18
            $this->sharedFormulas = [];
841
842
            // Initialize text objs
843 18
            $this->textObjects = [];
844
845
            // Initialize cell annotations
846 18
            $this->cellNotes = [];
847 18
            $this->textObjRef = -1;
848
849 18
            while ($this->pos <= $this->dataSize - 4) {
850 18
                $code = self::getUInt2d($this->data, $this->pos);
851
852
                switch ($code) {
853 18
                    case self::XLS_TYPE_BOF:
854 18
                        $this->readBof();
855
856 18
                        break;
857 18
                    case self::XLS_TYPE_PRINTGRIDLINES:
858 18
                        $this->readPrintGridlines();
859
860 18
                        break;
861 18
                    case self::XLS_TYPE_DEFAULTROWHEIGHT:
862 17
                        $this->readDefaultRowHeight();
863
864 17
                        break;
865 18
                    case self::XLS_TYPE_SHEETPR:
866 18
                        $this->readSheetPr();
867
868 18
                        break;
869 18
                    case self::XLS_TYPE_HORIZONTALPAGEBREAKS:
870
                        $this->readHorizontalPageBreaks();
871
872
                        break;
873 18
                    case self::XLS_TYPE_VERTICALPAGEBREAKS:
874
                        $this->readVerticalPageBreaks();
875
876
                        break;
877 18
                    case self::XLS_TYPE_HEADER:
878 18
                        $this->readHeader();
879
880 18
                        break;
881 18
                    case self::XLS_TYPE_FOOTER:
882 18
                        $this->readFooter();
883
884 18
                        break;
885 18
                    case self::XLS_TYPE_HCENTER:
886 18
                        $this->readHcenter();
887
888 18
                        break;
889 18
                    case self::XLS_TYPE_VCENTER:
890 18
                        $this->readVcenter();
891
892 18
                        break;
893 18
                    case self::XLS_TYPE_LEFTMARGIN:
894 5
                        $this->readLeftMargin();
895
896 5
                        break;
897 18
                    case self::XLS_TYPE_RIGHTMARGIN:
898 5
                        $this->readRightMargin();
899
900 5
                        break;
901 18
                    case self::XLS_TYPE_TOPMARGIN:
902 5
                        $this->readTopMargin();
903
904 5
                        break;
905 18
                    case self::XLS_TYPE_BOTTOMMARGIN:
906 5
                        $this->readBottomMargin();
907
908 5
                        break;
909 18
                    case self::XLS_TYPE_PAGESETUP:
910 18
                        $this->readPageSetup();
911
912 18
                        break;
913 18
                    case self::XLS_TYPE_PROTECT:
914 1
                        $this->readProtect();
915
916 1
                        break;
917 18
                    case self::XLS_TYPE_SCENPROTECT:
918
                        $this->readScenProtect();
919
920
                        break;
921 18
                    case self::XLS_TYPE_OBJECTPROTECT:
922
                        $this->readObjectProtect();
923
924
                        break;
925 18
                    case self::XLS_TYPE_PASSWORD:
926
                        $this->readPassword();
927
928
                        break;
929 18
                    case self::XLS_TYPE_DEFCOLWIDTH:
930 18
                        $this->readDefColWidth();
931
932 18
                        break;
933 18
                    case self::XLS_TYPE_COLINFO:
934 14
                        $this->readColInfo();
935
936 14
                        break;
937 18
                    case self::XLS_TYPE_DIMENSION:
938 18
                        $this->readDefault();
939
940 18
                        break;
941 18
                    case self::XLS_TYPE_ROW:
942 17
                        $this->readRow();
943
944 17
                        break;
945 18
                    case self::XLS_TYPE_DBCELL:
946 16
                        $this->readDefault();
947
948 16
                        break;
949 18
                    case self::XLS_TYPE_RK:
950 12
                        $this->readRk();
951
952 12
                        break;
953 18
                    case self::XLS_TYPE_LABELSST:
954 17
                        $this->readLabelSst();
955
956 17
                        break;
957 18
                    case self::XLS_TYPE_MULRK:
958 11
                        $this->readMulRk();
959
960 11
                        break;
961 18
                    case self::XLS_TYPE_NUMBER:
962 11
                        $this->readNumber();
963
964 11
                        break;
965 18
                    case self::XLS_TYPE_FORMULA:
966 10
                        $this->readFormula();
967
968 10
                        break;
969 18
                    case self::XLS_TYPE_SHAREDFMLA:
970
                        $this->readSharedFmla();
971
972
                        break;
973 18
                    case self::XLS_TYPE_BOOLERR:
974 8
                        $this->readBoolErr();
975
976 8
                        break;
977 18
                    case self::XLS_TYPE_MULBLANK:
978 11
                        $this->readMulBlank();
979
980 11
                        break;
981 18
                    case self::XLS_TYPE_LABEL:
982
                        $this->readLabel();
983
984
                        break;
985 18
                    case self::XLS_TYPE_BLANK:
986 2
                        $this->readBlank();
987
988 2
                        break;
989 18
                    case self::XLS_TYPE_MSODRAWING:
990 3
                        $this->readMsoDrawing();
991
992 3
                        break;
993 18
                    case self::XLS_TYPE_OBJ:
994 3
                        $this->readObj();
995
996 3
                        break;
997 18
                    case self::XLS_TYPE_WINDOW2:
998 18
                        $this->readWindow2();
999
1000 18
                        break;
1001 18
                    case self::XLS_TYPE_PAGELAYOUTVIEW:
1002 4
                        $this->readPageLayoutView();
1003
1004 4
                        break;
1005 18
                    case self::XLS_TYPE_SCL:
1006
                        $this->readScl();
1007
1008
                        break;
1009 18
                    case self::XLS_TYPE_PANE:
1010
                        $this->readPane();
1011
1012
                        break;
1013 18
                    case self::XLS_TYPE_SELECTION:
1014 18
                        $this->readSelection();
1015
1016 18
                        break;
1017 18
                    case self::XLS_TYPE_MERGEDCELLS:
1018 12
                        $this->readMergedCells();
1019
1020 12
                        break;
1021 18
                    case self::XLS_TYPE_HYPERLINK:
1022 2
                        $this->readHyperLink();
1023
1024 2
                        break;
1025 18
                    case self::XLS_TYPE_DATAVALIDATIONS:
1026
                        $this->readDataValidations();
1027
1028
                        break;
1029 18
                    case self::XLS_TYPE_DATAVALIDATION:
1030
                        $this->readDataValidation();
1031
1032
                        break;
1033 18
                    case self::XLS_TYPE_SHEETLAYOUT:
1034 2
                        $this->readSheetLayout();
1035
1036 2
                        break;
1037 18
                    case self::XLS_TYPE_SHEETPROTECTION:
1038 5
                        $this->readSheetProtection();
1039
1040 5
                        break;
1041 18
                    case self::XLS_TYPE_RANGEPROTECTION:
1042 1
                        $this->readRangeProtection();
1043
1044 1
                        break;
1045 18
                    case self::XLS_TYPE_NOTE:
1046 1
                        $this->readNote();
1047
1048 1
                        break;
1049 18
                    case self::XLS_TYPE_TXO:
1050 1
                        $this->readTextObject();
1051
1052 1
                        break;
1053 18
                    case self::XLS_TYPE_CONTINUE:
1054
                        $this->readContinue();
1055
1056
                        break;
1057 18
                    case self::XLS_TYPE_EOF:
1058 18
                        $this->readDefault();
1059
1060 18
                        break 2;
1061
                    default:
1062 18
                        $this->readDefault();
1063
1064 18
                        break;
1065
                }
1066
            }
1067
1068
            // treat MSODRAWING records, sheet-level Escher
1069 18
            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 18
            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 18
            if ($this->version == self::XLS_BIFF8) {
1175 18
                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 18
            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 18
                    $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 18
        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 18
        $this->data = null;
1291
1292 18
        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 21
    private function readRecordData($data, $pos, $len)
1305
    {
1306 21
        $data = substr($data, $pos, $len);
1307
1308
        // File not encrypted, or record before encryption start point
1309 21
        if ($this->encryption == self::MS_BIFF_CRYPTO_NONE || $pos < $this->encryptionStartPos) {
1310 21
            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 21
    private function loadOLE($pFilename)
1357
    {
1358
        // OLE reader
1359 21
        $ole = new OLERead();
1360
        // get excel data,
1361 21
        $ole->read($pFilename);
1362
        // Get workbook data: workbook stream + sheet streams
1363 21
        $this->data = $ole->getStream($ole->wrkbook);
1364
        // Get summary information data
1365 21
        $this->summaryInformation = $ole->getStream($ole->summaryInformation);
1366
        // Get additional document summary information data
1367 21
        $this->documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation);
1368 21
    }
1369
1370
    /**
1371
     * Read summary information.
1372
     */
1373 18
    private function readSummaryInformation()
1374
    {
1375 18
        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 18
        $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 18
        $secOffset = self::getInt4d($this->summaryInformation, 44);
1390
1391
        // section header
1392
        // offset: $secOffset; size: 4; section length
1393 18
        $secLength = self::getInt4d($this->summaryInformation, $secOffset);
1394
1395
        // offset: $secOffset+4; size: 4; property count
1396 18
        $countProperties = self::getInt4d($this->summaryInformation, $secOffset + 4);
1397
1398
        // initialize code page (used to resolve string values)
1399 18
        $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 18
        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 18
            $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 18
            $offset = self::getInt4d($this->summaryInformation, ($secOffset + 12) + (8 * $i));
1410
1411 18
            $type = self::getInt4d($this->summaryInformation, $secOffset + $offset);
1412
1413
            // initialize property value
1414 18
            $value = null;
1415
1416
            // extract property value based on property type
1417
            switch ($type) {
1418 18
                case 0x02: // 2 byte signed integer
1419 18
                    $value = self::getUInt2d($this->summaryInformation, $secOffset + 4 + $offset);
1420
1421 18
                    break;
1422 18
                case 0x03: // 4 byte signed integer
1423 17
                    $value = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
1424
1425 17
                    break;
1426 18
                case 0x13: // 4 byte unsigned integer
1427
                    // not needed yet, fix later if necessary
1428
                    break;
1429 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...
1430 18
                    $byteLength = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
1431 18
                    $value = substr($this->summaryInformation, $secOffset + 8 + $offset, $byteLength);
1432 18
                    $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage);
1433 18
                    $value = rtrim($value);
1434
1435 18
                    break;
1436 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...
1437
                    // PHP-time
1438 18
                    $value = OLE::OLE2LocalDate(substr($this->summaryInformation, $secOffset + 4 + $offset, 8));
1439
1440 18
                    break;
1441
                case 0x47: // Clipboard format
1442
                    // not needed yet, fix later if necessary
1443
                    break;
1444
            }
1445
1446
            switch ($id) {
1447 18
                case 0x01:    //    Code Page
1448 18
                    $codePage = CodePage::numberToName($value);
1449
1450 18
                    break;
1451 18
                case 0x02:    //    Title
1452 4
                    $this->spreadsheet->getProperties()->setTitle($value);
1453
1454 4
                    break;
1455 18
                case 0x03:    //    Subject
1456 4
                    $this->spreadsheet->getProperties()->setSubject($value);
1457
1458 4
                    break;
1459 18
                case 0x04:    //    Author (Creator)
1460 16
                    $this->spreadsheet->getProperties()->setCreator($value);
1461
1462 16
                    break;
1463 18
                case 0x05:    //    Keywords
1464 4
                    $this->spreadsheet->getProperties()->setKeywords($value);
1465
1466 4
                    break;
1467 18
                case 0x06:    //    Comments (Description)
1468 4
                    $this->spreadsheet->getProperties()->setDescription($value);
1469
1470 4
                    break;
1471 18
                case 0x07:    //    Template
1472
                    //    Not supported by PhpSpreadsheet
1473
                    break;
1474 18
                case 0x08:    //    Last Saved By (LastModifiedBy)
1475 17
                    $this->spreadsheet->getProperties()->setLastModifiedBy($value);
1476
1477 17
                    break;
1478 18
                case 0x09:    //    Revision
1479
                    //    Not supported by PhpSpreadsheet
1480 1
                    break;
1481 18
                case 0x0A:    //    Total Editing Time
1482
                    //    Not supported by PhpSpreadsheet
1483 1
                    break;
1484 18
                case 0x0B:    //    Last Printed
1485
                    //    Not supported by PhpSpreadsheet
1486 1
                    break;
1487 18
                case 0x0C:    //    Created Date/Time
1488 18
                    $this->spreadsheet->getProperties()->setCreated($value);
1489
1490 18
                    break;
1491 18
                case 0x0D:    //    Modified Date/Time
1492 18
                    $this->spreadsheet->getProperties()->setModified($value);
1493
1494 18
                    break;
1495 17
                case 0x0E:    //    Number of Pages
1496
                    //    Not supported by PhpSpreadsheet
1497
                    break;
1498 17
                case 0x0F:    //    Number of Words
1499
                    //    Not supported by PhpSpreadsheet
1500
                    break;
1501 17
                case 0x10:    //    Number of Characters
1502
                    //    Not supported by PhpSpreadsheet
1503
                    break;
1504 17
                case 0x11:    //    Thumbnail
1505
                    //    Not supported by PhpSpreadsheet
1506
                    break;
1507 17
                case 0x12:    //    Name of creating application
1508
                    //    Not supported by PhpSpreadsheet
1509 12
                    break;
1510 17
                case 0x13:    //    Security
1511
                    //    Not supported by PhpSpreadsheet
1512 17
                    break;
1513
            }
1514
        }
1515 18
    }
1516
1517
    /**
1518
     * Read additional document summary information.
1519
     */
1520 18
    private function readDocumentSummaryInformation()
1521
    {
1522 18
        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 18
        $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 18
        $secOffset = self::getInt4d($this->documentSummaryInformation, 44);
1537
1538
        //    section header
1539
        //    offset: $secOffset;    size: 4;    section length
1540 18
        $secLength = self::getInt4d($this->documentSummaryInformation, $secOffset);
1541
1542
        //    offset: $secOffset+4;    size: 4;    property count
1543 18
        $countProperties = self::getInt4d($this->documentSummaryInformation, $secOffset + 4);
1544
1545
        // initialize code page (used to resolve string values)
1546 18
        $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 18
        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 18
            $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 18
            $offset = self::getInt4d($this->documentSummaryInformation, ($secOffset + 12) + (8 * $i));
1557
1558 18
            $type = self::getInt4d($this->documentSummaryInformation, $secOffset + $offset);
1559
1560
            // initialize property value
1561 18
            $value = null;
1562
1563
            // extract property value based on property type
1564
            switch ($type) {
1565 18
                case 0x02:    //    2 byte signed integer
1566 18
                    $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1567
1568 18
                    break;
1569 17
                case 0x03:    //    4 byte signed integer
1570 17
                    $value = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1571
1572 17
                    break;
1573 17
                case 0x0B:  // Boolean
1574 17
                    $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
1575 17
                    $value = ($value == 0 ? false : true);
1576
1577 17
                    break;
1578 17
                case 0x13:    //    4 byte unsigned integer
1579
                    // not needed yet, fix later if necessary
1580
                    break;
1581 17 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 17 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 17
                case 0x47:    //    Clipboard format
1594
                    // not needed yet, fix later if necessary
1595
                    break;
1596
            }
1597
1598
            switch ($id) {
1599 18
                case 0x01:    //    Code Page
1600 18
                    $codePage = CodePage::numberToName($value);
1601
1602 18
                    break;
1603 17
                case 0x02:    //    Category
1604 4
                    $this->spreadsheet->getProperties()->setCategory($value);
1605
1606 4
                    break;
1607 17
                case 0x03:    //    Presentation Target
1608
                    //    Not supported by PhpSpreadsheet
1609
                    break;
1610 17
                case 0x04:    //    Bytes
1611
                    //    Not supported by PhpSpreadsheet
1612
                    break;
1613 17
                case 0x05:    //    Lines
1614
                    //    Not supported by PhpSpreadsheet
1615
                    break;
1616 17
                case 0x06:    //    Paragraphs
1617
                    //    Not supported by PhpSpreadsheet
1618
                    break;
1619 17
                case 0x07:    //    Slides
1620
                    //    Not supported by PhpSpreadsheet
1621
                    break;
1622 17
                case 0x08:    //    Notes
1623
                    //    Not supported by PhpSpreadsheet
1624
                    break;
1625 17
                case 0x09:    //    Hidden Slides
1626
                    //    Not supported by PhpSpreadsheet
1627
                    break;
1628 17
                case 0x0A:    //    MM Clips
1629
                    //    Not supported by PhpSpreadsheet
1630
                    break;
1631 17
                case 0x0B:    //    Scale Crop
1632
                    //    Not supported by PhpSpreadsheet
1633 17
                    break;
1634 17
                case 0x0C:    //    Heading Pairs
1635
                    //    Not supported by PhpSpreadsheet
1636 17
                    break;
1637 17
                case 0x0D:    //    Titles of Parts
1638
                    //    Not supported by PhpSpreadsheet
1639 17
                    break;
1640 17
                case 0x0E:    //    Manager
1641 2
                    $this->spreadsheet->getProperties()->setManager($value);
1642
1643 2
                    break;
1644 17
                case 0x0F:    //    Company
1645 14
                    $this->spreadsheet->getProperties()->setCompany($value);
1646
1647 14
                    break;
1648 17
                case 0x10:    //    Links up-to-date
1649
                    //    Not supported by PhpSpreadsheet
1650 17
                    break;
1651
            }
1652
        }
1653 18
    }
1654
1655
    /**
1656
     * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record.
1657
     */
1658 21
    private function readDefault()
1659
    {
1660 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
1661
1662
        // move stream pointer to next record
1663 21
        $this->pos += 4 + $length;
1664 21
    }
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 21
    private function readBof()
1771
    {
1772 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
1773 21
        $recordData = substr($this->data, $this->pos + 4, $length);
1774
1775
        // move stream pointer to next record
1776 21
        $this->pos += 4 + $length;
1777
1778
        // offset: 2; size: 2; type of the following data
1779 21
        $substreamType = self::getUInt2d($recordData, 2);
1780
1781
        switch ($substreamType) {
1782 21
            case self::XLS_WORKBOOKGLOBALS:
1783 21
                $version = self::getUInt2d($recordData, 0);
1784 21
                if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) {
1785
                    throw new Exception('Cannot read this Excel file. Version is too old.');
1786
                }
1787 21
                $this->version = $version;
1788
1789 21
                break;
1790 19
            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 19
                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 21
    }
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
        $iMax = strlen($password);
1893
        for ($i = 0; $i < $iMax; ++$i) {
1894
            $o = ord(substr($password, $i, 1));
1895
            $pwarray[2 * $i] = chr($o & 0xff);
1896
            $pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff);
1897
        }
1898
        $pwarray[2 * $i] = chr(0x80);
1899
        $pwarray[56] = chr(($i << 4) & 0xff);
1900
1901
        $md5 = new Xls\MD5();
1902
        $md5->add($pwarray);
1903
1904
        $mdContext1 = $md5->getContext();
1905
1906
        $offset = 0;
1907
        $keyoffset = 0;
1908
        $tocopy = 5;
1909
1910
        $md5->reset();
1911
1912
        while ($offset != 16) {
1913
            if ((64 - $offset) < 5) {
1914
                $tocopy = 64 - $offset;
1915
            }
1916
            for ($i = 0; $i <= $tocopy; ++$i) {
1917
                $pwarray[$offset + $i] = $mdContext1[$keyoffset + $i];
1918
            }
1919
            $offset += $tocopy;
1920
1921
            if ($offset == 64) {
1922
                $md5->add($pwarray);
1923
                $keyoffset = $tocopy;
1924
                $tocopy = 5 - $tocopy;
1925
                $offset = 0;
1926
1927
                continue;
1928
            }
1929
1930
            $keyoffset = 0;
1931
            $tocopy = 5;
1932
            for ($i = 0; $i < 16; ++$i) {
1933
                $pwarray[$offset + $i] = $docid[$i];
1934
            }
1935
            $offset += 16;
1936
        }
1937
1938
        $pwarray[16] = "\x80";
1939
        for ($i = 0; $i < 47; ++$i) {
1940
            $pwarray[17 + $i] = "\0";
1941
        }
1942
        $pwarray[56] = "\x80";
1943
        $pwarray[57] = "\x0a";
1944
1945
        $md5->add($pwarray);
1946
        $valContext = $md5->getContext();
1947
1948
        $key = $this->makeKey(0, $valContext);
1949
1950
        $salt = $key->RC4($salt_data);
1951
        $hashedsalt = $key->RC4($hashedsalt_data);
1952
1953
        $salt .= "\x80" . str_repeat("\0", 47);
1954
        $salt[56] = "\x80";
1955
1956
        $md5->reset();
1957
        $md5->add($salt);
1958
        $mdContext2 = $md5->getContext();
1959
1960
        return $mdContext2 == $hashedsalt;
1961
    }
1962
1963
    /**
1964
     * CODEPAGE.
1965
     *
1966
     * This record stores the text encoding used to write byte
1967
     * strings, stored as MS Windows code page identifier.
1968
     *
1969
     * --    "OpenOffice.org's Documentation of the Microsoft
1970
     *         Excel File Format"
1971
     */
1972 18
    private function readCodepage()
1973
    {
1974 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
1975 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1976
1977
        // move stream pointer to next record
1978 18
        $this->pos += 4 + $length;
1979
1980
        // offset: 0; size: 2; code page identifier
1981 18
        $codepage = self::getUInt2d($recordData, 0);
1982
1983 18
        $this->codepage = CodePage::numberToName($codepage);
1984 18
    }
1985
1986
    /**
1987
     * DATEMODE.
1988
     *
1989
     * This record specifies the base date for displaying date
1990
     * values. All dates are stored as count of days past this
1991
     * base date. In BIFF2-BIFF4 this record is part of the
1992
     * Calculation Settings Block. In BIFF5-BIFF8 it is
1993
     * stored in the Workbook Globals Substream.
1994
     *
1995
     * --    "OpenOffice.org's Documentation of the Microsoft
1996
     *         Excel File Format"
1997
     */
1998 18
    private function readDateMode()
1999
    {
2000 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
2001 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2002
2003
        // move stream pointer to next record
2004 18
        $this->pos += 4 + $length;
2005
2006
        // offset: 0; size: 2; 0 = base 1900, 1 = base 1904
2007 18
        Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
2008 18
        if (ord($recordData[0]) == 1) {
2009
            Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
2010
        }
2011 18
    }
2012
2013
    /**
2014
     * Read a FONT record.
2015
     */
2016 18
    private function readFont()
2017
    {
2018 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
2019 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2020
2021
        // move stream pointer to next record
2022 18
        $this->pos += 4 + $length;
2023
2024 18
        if (!$this->readDataOnly) {
2025 17
            $objFont = new Font();
2026
2027
            // offset: 0; size: 2; height of the font (in twips = 1/20 of a point)
2028 17
            $size = self::getUInt2d($recordData, 0);
2029 17
            $objFont->setSize($size / 20);
2030
2031
            // offset: 2; size: 2; option flags
2032
            // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8)
2033
            // bit: 1; mask 0x0002; italic
2034 17
            $isItalic = (0x0002 & self::getUInt2d($recordData, 2)) >> 1;
2035 17
            if ($isItalic) {
2036 5
                $objFont->setItalic(true);
2037
            }
2038
2039
            // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8)
2040
            // bit: 3; mask 0x0008; strikethrough
2041 17
            $isStrike = (0x0008 & self::getUInt2d($recordData, 2)) >> 3;
2042 17
            if ($isStrike) {
2043
                $objFont->setStrikethrough(true);
2044
            }
2045
2046
            // offset: 4; size: 2; colour index
2047 17
            $colorIndex = self::getUInt2d($recordData, 4);
2048 17
            $objFont->colorIndex = $colorIndex;
2049
2050
            // offset: 6; size: 2; font weight
2051 17
            $weight = self::getUInt2d($recordData, 6);
2052
            switch ($weight) {
2053 17
                case 0x02BC:
2054 17
                    $objFont->setBold(true);
2055
2056 17
                    break;
2057
            }
2058
2059
            // offset: 8; size: 2; escapement type
2060 17
            $escapement = self::getUInt2d($recordData, 8);
2061
            switch ($escapement) {
2062 17
                case 0x0001:
2063
                    $objFont->setSuperscript(true);
2064
2065
                    break;
2066 17
                case 0x0002:
2067
                    $objFont->setSubscript(true);
2068
2069
                    break;
2070
            }
2071
2072
            // offset: 10; size: 1; underline type
2073 17
            $underlineType = ord($recordData[10]);
2074
            switch ($underlineType) {
2075 17
                case 0x00:
2076 17
                    break; // no underline
2077 2
                case 0x01:
2078 2
                    $objFont->setUnderline(Font::UNDERLINE_SINGLE);
2079
2080 2
                    break;
2081
                case 0x02:
2082
                    $objFont->setUnderline(Font::UNDERLINE_DOUBLE);
2083
2084
                    break;
2085
                case 0x21:
2086
                    $objFont->setUnderline(Font::UNDERLINE_SINGLEACCOUNTING);
2087
2088
                    break;
2089
                case 0x22:
2090
                    $objFont->setUnderline(Font::UNDERLINE_DOUBLEACCOUNTING);
2091
2092
                    break;
2093
            }
2094
2095
            // offset: 11; size: 1; font family
2096
            // offset: 12; size: 1; character set
2097
            // offset: 13; size: 1; not used
2098
            // offset: 14; size: var; font name
2099 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...
2100 17
                $string = self::readUnicodeStringShort(substr($recordData, 14));
2101
            } else {
2102
                $string = $this->readByteStringShort(substr($recordData, 14));
2103
            }
2104 17
            $objFont->setName($string['value']);
2105
2106 17
            $this->objFonts[] = $objFont;
2107
        }
2108 18
    }
2109
2110
    /**
2111
     * FORMAT.
2112
     *
2113
     * This record contains information about a number format.
2114
     * All FORMAT records occur together in a sequential list.
2115
     *
2116
     * In BIFF2-BIFF4 other records referencing a FORMAT record
2117
     * contain a zero-based index into this list. From BIFF5 on
2118
     * the FORMAT record contains the index itself that will be
2119
     * used by other records.
2120
     *
2121
     * --    "OpenOffice.org's Documentation of the Microsoft
2122
     *         Excel File Format"
2123
     */
2124 18
    private function readFormat()
2125
    {
2126 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
2127 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2128
2129
        // move stream pointer to next record
2130 18
        $this->pos += 4 + $length;
2131
2132 18
        if (!$this->readDataOnly) {
2133 17
            $indexCode = self::getUInt2d($recordData, 0);
2134
2135 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...
2136 17
                $string = self::readUnicodeStringLong(substr($recordData, 2));
2137
            } else {
2138
                // BIFF7
2139
                $string = $this->readByteStringShort(substr($recordData, 2));
2140
            }
2141
2142 17
            $formatString = $string['value'];
2143 17
            $this->formats[$indexCode] = $formatString;
2144
        }
2145 18
    }
2146
2147
    /**
2148
     * XF - Extended Format.
2149
     *
2150
     * This record contains formatting information for cells, rows, columns or styles.
2151
     * According to http://support.microsoft.com/kb/147732 there are always at least 15 cell style XF
2152
     * and 1 cell XF.
2153
     * Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF
2154
     * and XF record 15 is a cell XF
2155
     * We only read the first cell style XF and skip the remaining cell style XF records
2156
     * We read all cell XF records.
2157
     *
2158
     * --    "OpenOffice.org's Documentation of the Microsoft
2159
     *         Excel File Format"
2160
     */
2161 18
    private function readXf()
2162
    {
2163 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
2164 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2165
2166
        // move stream pointer to next record
2167 18
        $this->pos += 4 + $length;
2168
2169 18
        $objStyle = new Style();
2170
2171 18
        if (!$this->readDataOnly) {
2172
            // offset:  0; size: 2; Index to FONT record
2173 17
            if (self::getUInt2d($recordData, 0) < 4) {
2174 17
                $fontIndex = self::getUInt2d($recordData, 0);
2175
            } else {
2176
                // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
2177
                // check the OpenOffice documentation of the FONT record
2178 17
                $fontIndex = self::getUInt2d($recordData, 0) - 1;
2179
            }
2180 17
            $objStyle->setFont($this->objFonts[$fontIndex]);
2181
2182
            // offset:  2; size: 2; Index to FORMAT record
2183 17
            $numberFormatIndex = self::getUInt2d($recordData, 2);
2184 17
            if (isset($this->formats[$numberFormatIndex])) {
2185
                // then we have user-defined format code
2186 15
                $numberFormat = ['formatCode' => $this->formats[$numberFormatIndex]];
2187 17
            } elseif (($code = NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') {
2188
                // then we have built-in format code
2189 17
                $numberFormat = ['formatCode' => $code];
2190
            } else {
2191
                // we set the general format code
2192 1
                $numberFormat = ['formatCode' => 'General'];
2193
            }
2194 17
            $objStyle->getNumberFormat()->setFormatCode($numberFormat['formatCode']);
2195
2196
            // offset:  4; size: 2; XF type, cell protection, and parent style XF
2197
            // 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...
2198 17
            $xfTypeProt = self::getUInt2d($recordData, 4);
2199
            // bit 0; mask 0x01; 1 = cell is locked
2200 17
            $isLocked = (0x01 & $xfTypeProt) >> 0;
2201 17
            $objStyle->getProtection()->setLocked($isLocked ? Protection::PROTECTION_INHERIT : Protection::PROTECTION_UNPROTECTED);
2202
2203
            // bit 1; mask 0x02; 1 = Formula is hidden
2204 17
            $isHidden = (0x02 & $xfTypeProt) >> 1;
2205 17
            $objStyle->getProtection()->setHidden($isHidden ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED);
2206
2207
            // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF
2208 17
            $isCellStyleXf = (0x04 & $xfTypeProt) >> 2;
2209
2210
            // offset:  6; size: 1; Alignment and text break
2211
            // bit 2-0, mask 0x07; horizontal alignment
2212 17
            $horAlign = (0x07 & ord($recordData[6])) >> 0;
2213
            switch ($horAlign) {
2214 17
                case 0:
2215 17
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_GENERAL);
2216
2217 17
                    break;
2218 12
                case 1:
2219 2
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
2220
2221 2
                    break;
2222 12
                case 2:
2223 10
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
2224
2225 10
                    break;
2226 2
                case 3:
2227 2
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
2228
2229 2
                    break;
2230 2
                case 4:
2231
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_FILL);
2232
2233
                    break;
2234 2
                case 5:
2235 2
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_JUSTIFY);
2236
2237 2
                    break;
2238
                case 6:
2239
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER_CONTINUOUS);
2240
2241
                    break;
2242
            }
2243
            // bit 3, mask 0x08; wrap text
2244 17
            $wrapText = (0x08 & ord($recordData[6])) >> 3;
2245
            switch ($wrapText) {
2246 17
                case 0:
2247 17
                    $objStyle->getAlignment()->setWrapText(false);
2248
2249 17
                    break;
2250 2
                case 1:
2251 2
                    $objStyle->getAlignment()->setWrapText(true);
2252
2253 2
                    break;
2254
            }
2255
            // bit 6-4, mask 0x70; vertical alignment
2256 17
            $vertAlign = (0x70 & ord($recordData[6])) >> 4;
2257
            switch ($vertAlign) {
2258 17
                case 0:
2259
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_TOP);
2260
2261
                    break;
2262 17
                case 1:
2263 2
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
2264
2265 2
                    break;
2266 17
                case 2:
2267 17
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_BOTTOM);
2268
2269 17
                    break;
2270
                case 3:
2271
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_JUSTIFY);
2272
2273
                    break;
2274
            }
2275
2276 17
            if ($this->version == self::XLS_BIFF8) {
2277
                // offset:  7; size: 1; XF_ROTATION: Text rotation angle
2278 17
                $angle = ord($recordData[7]);
2279 17
                $rotation = 0;
2280 17
                if ($angle <= 90) {
2281 17
                    $rotation = $angle;
2282
                } elseif ($angle <= 180) {
2283
                    $rotation = 90 - $angle;
2284
                } elseif ($angle == 255) {
2285
                    $rotation = -165;
2286
                }
2287 17
                $objStyle->getAlignment()->setTextRotation($rotation);
2288
2289
                // offset:  8; size: 1; Indentation, shrink to cell size, and text direction
2290
                // bit: 3-0; mask: 0x0F; indent level
2291 17
                $indent = (0x0F & ord($recordData[8])) >> 0;
2292 17
                $objStyle->getAlignment()->setIndent($indent);
2293
2294
                // bit: 4; mask: 0x10; 1 = shrink content to fit into cell
2295 17
                $shrinkToFit = (0x10 & ord($recordData[8])) >> 4;
2296
                switch ($shrinkToFit) {
2297 17
                    case 0:
2298 17
                        $objStyle->getAlignment()->setShrinkToFit(false);
2299
2300 17
                        break;
2301 1
                    case 1:
2302 1
                        $objStyle->getAlignment()->setShrinkToFit(true);
2303
2304 1
                        break;
2305
                }
2306
2307
                // offset:  9; size: 1; Flags used for attribute groups
2308
2309
                // offset: 10; size: 4; Cell border lines and background area
2310
                // bit: 3-0; mask: 0x0000000F; left style
2311 17 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...
2312 17
                    $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle);
2313
                }
2314
                // bit: 7-4; mask: 0x000000F0; right style
2315 17 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...
2316 17
                    $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle);
2317
                }
2318
                // bit: 11-8; mask: 0x00000F00; top style
2319 17 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...
2320 17
                    $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle);
2321
                }
2322
                // bit: 15-12; mask: 0x0000F000; bottom style
2323 17 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...
2324 17
                    $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle);
2325
                }
2326
                // bit: 22-16; mask: 0x007F0000; left color
2327 17
                $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::getInt4d($recordData, 10)) >> 16;
2328
2329
                // bit: 29-23; mask: 0x3F800000; right color
2330 17
                $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::getInt4d($recordData, 10)) >> 23;
2331
2332
                // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom
2333 17
                $diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false;
2334
2335
                // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right
2336 17
                $diagonalUp = (0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false;
2337
2338 17
                if ($diagonalUp == false && $diagonalDown == false) {
2339 17
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE);
2340
                } elseif ($diagonalUp == true && $diagonalDown == false) {
2341
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP);
2342
                } elseif ($diagonalUp == false && $diagonalDown == true) {
2343
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN);
2344
                } elseif ($diagonalUp == true && $diagonalDown == true) {
2345
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH);
2346
                }
2347
2348
                // 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...
2349
                // bit: 6-0; mask: 0x0000007F; top color
2350 17
                $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::getInt4d($recordData, 14)) >> 0;
2351
2352
                // bit: 13-7; mask: 0x00003F80; bottom color
2353 17
                $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::getInt4d($recordData, 14)) >> 7;
2354
2355
                // bit: 20-14; mask: 0x001FC000; diagonal color
2356 17
                $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::getInt4d($recordData, 14)) >> 14;
2357
2358
                // bit: 24-21; mask: 0x01E00000; diagonal style
2359 17 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...
2360 17
                    $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle);
2361
                }
2362
2363
                // bit: 31-26; mask: 0xFC000000 fill pattern
2364 17
                if ($fillType = Xls\Style\FillPattern::lookup((0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) {
2365 17
                    $objStyle->getFill()->setFillType($fillType);
2366
                }
2367
                // offset: 18; size: 2; pattern and background colour
2368
                // bit: 6-0; mask: 0x007F; color index for pattern color
2369 17
                $objStyle->getFill()->startcolorIndex = (0x007F & self::getUInt2d($recordData, 18)) >> 0;
2370
2371
                // bit: 13-7; mask: 0x3F80; color index for pattern background
2372 17
                $objStyle->getFill()->endcolorIndex = (0x3F80 & self::getUInt2d($recordData, 18)) >> 7;
2373
            } else {
2374
                // BIFF5
2375
2376
                // offset: 7; size: 1; Text orientation and flags
2377
                $orientationAndFlags = ord($recordData[7]);
2378
2379
                // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation
2380
                $xfOrientation = (0x03 & $orientationAndFlags) >> 0;
2381
                switch ($xfOrientation) {
2382
                    case 0:
2383
                        $objStyle->getAlignment()->setTextRotation(0);
2384
2385
                        break;
2386
                    case 1:
2387
                        $objStyle->getAlignment()->setTextRotation(-165);
2388
2389
                        break;
2390
                    case 2:
2391
                        $objStyle->getAlignment()->setTextRotation(90);
2392
2393
                        break;
2394
                    case 3:
2395
                        $objStyle->getAlignment()->setTextRotation(-90);
2396
2397
                        break;
2398
                }
2399
2400
                // offset: 8; size: 4; cell border lines and background area
2401
                $borderAndBackground = self::getInt4d($recordData, 8);
2402
2403
                // bit: 6-0; mask: 0x0000007F; color index for pattern color
2404
                $objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0;
2405
2406
                // bit: 13-7; mask: 0x00003F80; color index for pattern background
2407
                $objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7;
2408
2409
                // bit: 21-16; mask: 0x003F0000; fill pattern
2410
                $objStyle->getFill()->setFillType(Xls\Style\FillPattern::lookup((0x003F0000 & $borderAndBackground) >> 16));
2411
2412
                // bit: 24-22; mask: 0x01C00000; bottom line style
2413
                $objStyle->getBorders()->getBottom()->setBorderStyle(Xls\Style\Border::lookup((0x01C00000 & $borderAndBackground) >> 22));
2414
2415
                // bit: 31-25; mask: 0xFE000000; bottom line color
2416
                $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25;
2417
2418
                // offset: 12; size: 4; cell border lines
2419
                $borderLines = self::getInt4d($recordData, 12);
2420
2421
                // bit: 2-0; mask: 0x00000007; top line style
2422
                $objStyle->getBorders()->getTop()->setBorderStyle(Xls\Style\Border::lookup((0x00000007 & $borderLines) >> 0));
2423
2424
                // bit: 5-3; mask: 0x00000038; left line style
2425
                $objStyle->getBorders()->getLeft()->setBorderStyle(Xls\Style\Border::lookup((0x00000038 & $borderLines) >> 3));
2426
2427
                // bit: 8-6; mask: 0x000001C0; right line style
2428
                $objStyle->getBorders()->getRight()->setBorderStyle(Xls\Style\Border::lookup((0x000001C0 & $borderLines) >> 6));
2429
2430
                // bit: 15-9; mask: 0x0000FE00; top line color index
2431
                $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9;
2432
2433
                // bit: 22-16; mask: 0x007F0000; left line color index
2434
                $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16;
2435
2436
                // bit: 29-23; mask: 0x3F800000; right line color index
2437
                $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23;
2438
            }
2439
2440
            // add cellStyleXf or cellXf and update mapping
2441 17
            if ($isCellStyleXf) {
2442
                // we only read one style XF record which is always the first
2443 17
                if ($this->xfIndex == 0) {
2444 17
                    $this->spreadsheet->addCellStyleXf($objStyle);
2445 17
                    $this->mapCellStyleXfIndex[$this->xfIndex] = 0;
2446
                }
2447
            } else {
2448
                // we read all cell XF records
2449 17
                $this->spreadsheet->addCellXf($objStyle);
2450 17
                $this->mapCellXfIndex[$this->xfIndex] = count($this->spreadsheet->getCellXfCollection()) - 1;
2451
            }
2452
2453
            // update XF index for when we read next record
2454 17
            ++$this->xfIndex;
2455
        }
2456 18
    }
2457
2458 3
    private function readXfExt()
2459
    {
2460 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
2461 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2462
2463
        // move stream pointer to next record
2464 3
        $this->pos += 4 + $length;
2465
2466 3
        if (!$this->readDataOnly) {
2467
            // offset: 0; size: 2; 0x087D = repeated header
2468
2469
            // offset: 2; size: 2
2470
2471
            // offset: 4; size: 8; not used
2472
2473
            // offset: 12; size: 2; record version
2474
2475
            // offset: 14; size: 2; index to XF record which this record modifies
2476 3
            $ixfe = self::getUInt2d($recordData, 14);
2477
2478
            // offset: 16; size: 2; not used
2479
2480
            // offset: 18; size: 2; number of extension properties that follow
2481 3
            $cexts = self::getUInt2d($recordData, 18);
2482
2483
            // start reading the actual extension data
2484 3
            $offset = 20;
2485 3
            while ($offset < $length) {
2486
                // extension type
2487 3
                $extType = self::getUInt2d($recordData, $offset);
2488
2489
                // extension length
2490 3
                $cb = self::getUInt2d($recordData, $offset + 2);
2491
2492
                // extension data
2493 3
                $extData = substr($recordData, $offset + 4, $cb);
2494
2495
                switch ($extType) {
2496 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...
2497 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2498 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2499
2500 3
                        if ($xclfType == 2) {
2501 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2502
2503
                            // modify the relevant style property
2504 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2505 1
                                $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
2506 1
                                $fill->getStartColor()->setRGB($rgb);
2507 1
                                unset($fill->startcolorIndex); // normal color index does not apply, discard
2508
                            }
2509
                        }
2510
2511 3
                        break;
2512 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...
2513 1
                        $xclfType = self::getUInt2d($extData, 0); // color type
2514 1
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2515
2516 1
                        if ($xclfType == 2) {
2517 1
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2518
2519
                            // modify the relevant style property
2520 1
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2521 1
                                $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
2522 1
                                $fill->getEndColor()->setRGB($rgb);
2523 1
                                unset($fill->endcolorIndex); // normal color index does not apply, discard
2524
                            }
2525
                        }
2526
2527 1
                        break;
2528 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...
2529 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2530 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2531
2532 3
                        if ($xclfType == 2) {
2533 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2534
2535
                            // modify the relevant style property
2536 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2537 1
                                $top = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getTop();
2538 1
                                $top->getColor()->setRGB($rgb);
2539 1
                                unset($top->colorIndex); // normal color index does not apply, discard
2540
                            }
2541
                        }
2542
2543 3
                        break;
2544 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...
2545 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2546 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2547
2548 3
                        if ($xclfType == 2) {
2549 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2550
2551
                            // modify the relevant style property
2552 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2553 1
                                $bottom = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getBottom();
2554 1
                                $bottom->getColor()->setRGB($rgb);
2555 1
                                unset($bottom->colorIndex); // normal color index does not apply, discard
2556
                            }
2557
                        }
2558
2559 3
                        break;
2560 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...
2561 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2562 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2563
2564 3
                        if ($xclfType == 2) {
2565 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2566
2567
                            // modify the relevant style property
2568 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2569 1
                                $left = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getLeft();
2570 1
                                $left->getColor()->setRGB($rgb);
2571 1
                                unset($left->colorIndex); // normal color index does not apply, discard
2572
                            }
2573
                        }
2574
2575 3
                        break;
2576 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...
2577 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2578 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2579
2580 3
                        if ($xclfType == 2) {
2581 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2582
2583
                            // modify the relevant style property
2584 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2585 1
                                $right = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getRight();
2586 1
                                $right->getColor()->setRGB($rgb);
2587 1
                                unset($right->colorIndex); // normal color index does not apply, discard
2588
                            }
2589
                        }
2590
2591 3
                        break;
2592 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...
2593
                        $xclfType = self::getUInt2d($extData, 0); // color type
2594
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2595
2596
                        if ($xclfType == 2) {
2597
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2598
2599
                            // modify the relevant style property
2600
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2601
                                $diagonal = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getDiagonal();
2602
                                $diagonal->getColor()->setRGB($rgb);
2603
                                unset($diagonal->colorIndex); // normal color index does not apply, discard
2604
                            }
2605
                        }
2606
2607
                        break;
2608 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...
2609 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2610 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2611
2612 3
                        if ($xclfType == 2) {
2613 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2614
2615
                            // modify the relevant style property
2616 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2617 1
                                $font = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont();
2618 1
                                $font->getColor()->setRGB($rgb);
2619 1
                                unset($font->colorIndex); // normal color index does not apply, discard
2620
                            }
2621
                        }
2622
2623 3
                        break;
2624
                }
2625
2626 3
                $offset += $cb;
2627
            }
2628
        }
2629 3
    }
2630
2631
    /**
2632
     * Read STYLE record.
2633
     */
2634 18
    private function readStyle()
2635
    {
2636 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
2637 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2638
2639
        // move stream pointer to next record
2640 18
        $this->pos += 4 + $length;
2641
2642 18
        if (!$this->readDataOnly) {
2643
            // offset: 0; size: 2; index to XF record and flag for built-in style
2644 17
            $ixfe = self::getUInt2d($recordData, 0);
2645
2646
            // bit: 11-0; mask 0x0FFF; index to XF record
2647 17
            $xfIndex = (0x0FFF & $ixfe) >> 0;
2648
2649
            // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style
2650 17
            $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15);
2651
2652 17
            if ($isBuiltIn) {
2653
                // offset: 2; size: 1; identifier for built-in style
2654 17
                $builtInId = ord($recordData[2]);
2655
2656
                switch ($builtInId) {
2657 17
                    case 0x00:
2658
                        // currently, we are not using this for anything
2659 17
                        break;
2660
                    default:
2661 14
                        break;
2662
                }
2663
            }
2664
            // user-defined; not supported by PhpSpreadsheet
2665
        }
2666 18
    }
2667
2668
    /**
2669
     * Read PALETTE record.
2670
     */
2671 4
    private function readPalette()
2672
    {
2673 4
        $length = self::getUInt2d($this->data, $this->pos + 2);
2674 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2675
2676
        // move stream pointer to next record
2677 4
        $this->pos += 4 + $length;
2678
2679 4
        if (!$this->readDataOnly) {
2680
            // offset: 0; size: 2; number of following colors
2681 4
            $nm = self::getUInt2d($recordData, 0);
2682
2683
            // list of RGB colors
2684 4
            for ($i = 0; $i < $nm; ++$i) {
2685 4
                $rgb = substr($recordData, 2 + 4 * $i, 4);
2686 4
                $this->palette[] = self::readRGB($rgb);
2687
            }
2688
        }
2689 4
    }
2690
2691
    /**
2692
     * SHEET.
2693
     *
2694
     * This record is  located in the  Workbook Globals
2695
     * Substream  and represents a sheet inside the workbook.
2696
     * One SHEET record is written for each sheet. It stores the
2697
     * sheet name and a stream offset to the BOF record of the
2698
     * respective Sheet Substream within the Workbook Stream.
2699
     *
2700
     * --    "OpenOffice.org's Documentation of the Microsoft
2701
     *         Excel File Format"
2702
     */
2703 21
    private function readSheet()
2704
    {
2705 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
2706 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2707
2708
        // offset: 0; size: 4; absolute stream position of the BOF record of the sheet
2709
        // NOTE: not encrypted
2710 21
        $rec_offset = self::getInt4d($this->data, $this->pos + 4);
2711
2712
        // move stream pointer to next record
2713 21
        $this->pos += 4 + $length;
2714
2715
        // offset: 4; size: 1; sheet state
2716 21
        switch (ord($recordData[4])) {
2717 21
            case 0x00:
2718 21
                $sheetState = Worksheet::SHEETSTATE_VISIBLE;
2719
2720 21
                break;
2721
            case 0x01:
2722
                $sheetState = Worksheet::SHEETSTATE_HIDDEN;
2723
2724
                break;
2725
            case 0x02:
2726
                $sheetState = Worksheet::SHEETSTATE_VERYHIDDEN;
2727
2728
                break;
2729
            default:
2730
                $sheetState = Worksheet::SHEETSTATE_VISIBLE;
2731
2732
                break;
2733
        }
2734
2735
        // offset: 5; size: 1; sheet type
2736 21
        $sheetType = ord($recordData[5]);
2737
2738
        // offset: 6; size: var; sheet name
2739 21 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...
2740 21
            $string = self::readUnicodeStringShort(substr($recordData, 6));
2741 21
            $rec_name = $string['value'];
2742
        } elseif ($this->version == self::XLS_BIFF7) {
2743
            $string = $this->readByteStringShort(substr($recordData, 6));
2744
            $rec_name = $string['value'];
2745
        }
2746
2747 21
        $this->sheets[] = [
2748 21
            'name' => $rec_name,
2749 21
            'offset' => $rec_offset,
2750 21
            'sheetState' => $sheetState,
2751 21
            'sheetType' => $sheetType,
2752
        ];
2753 21
    }
2754
2755
    /**
2756
     * Read EXTERNALBOOK record.
2757
     */
2758 4
    private function readExternalBook()
2759
    {
2760 4
        $length = self::getUInt2d($this->data, $this->pos + 2);
2761 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2762
2763
        // move stream pointer to next record
2764 4
        $this->pos += 4 + $length;
2765
2766
        // offset within record data
2767 4
        $offset = 0;
2768
2769
        // there are 4 types of records
2770 4
        if (strlen($recordData) > 4) {
2771
            // external reference
2772
            // offset: 0; size: 2; number of sheet names ($nm)
2773
            $nm = self::getUInt2d($recordData, 0);
2774
            $offset += 2;
2775
2776
            // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length)
2777
            $encodedUrlString = self::readUnicodeStringLong(substr($recordData, 2));
2778
            $offset += $encodedUrlString['size'];
2779
2780
            // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length)
2781
            $externalSheetNames = [];
2782
            for ($i = 0; $i < $nm; ++$i) {
2783
                $externalSheetNameString = self::readUnicodeStringLong(substr($recordData, $offset));
2784
                $externalSheetNames[] = $externalSheetNameString['value'];
2785
                $offset += $externalSheetNameString['size'];
2786
            }
2787
2788
            // store the record data
2789
            $this->externalBooks[] = [
2790
                'type' => 'external',
2791
                'encodedUrl' => $encodedUrlString['value'],
2792
                'externalSheetNames' => $externalSheetNames,
2793
            ];
2794 4
        } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) {
2795
            // internal reference
2796
            // offset: 0; size: 2; number of sheet in this document
2797
            // 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...
2798 4
            $this->externalBooks[] = [
2799 4
                'type' => 'internal',
2800
            ];
2801
        } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) {
2802
            // add-in function
2803
            // 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...
2804
            $this->externalBooks[] = [
2805
                'type' => 'addInFunction',
2806
            ];
2807
        } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) {
2808
            // DDE links, OLE links
2809
            // 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...
2810
            // offset: 2; size: var; encoded source document name
2811
            $this->externalBooks[] = [
2812
                'type' => 'DDEorOLE',
2813
            ];
2814
        }
2815 4
    }
2816
2817
    /**
2818
     * Read EXTERNNAME record.
2819
     */
2820
    private function readExternName()
2821
    {
2822
        $length = self::getUInt2d($this->data, $this->pos + 2);
2823
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2824
2825
        // move stream pointer to next record
2826
        $this->pos += 4 + $length;
2827
2828
        // external sheet references provided for named cells
2829
        if ($this->version == self::XLS_BIFF8) {
2830
            // offset: 0; size: 2; options
2831
            $options = self::getUInt2d($recordData, 0);
2832
2833
            // 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...
2834
2835
            // offset: 4; size: 2; not used
2836
2837
            // offset: 6; size: var
2838
            $nameString = self::readUnicodeStringShort(substr($recordData, 6));
2839
2840
            // offset: var; size: var; formula data
2841
            $offset = 6 + $nameString['size'];
2842
            $formula = $this->getFormulaFromStructure(substr($recordData, $offset));
2843
2844
            $this->externalNames[] = [
2845
                'name' => $nameString['value'],
2846
                'formula' => $formula,
2847
            ];
2848
        }
2849
    }
2850
2851
    /**
2852
     * Read EXTERNSHEET record.
2853
     */
2854 4
    private function readExternSheet()
2855
    {
2856 4
        $length = self::getUInt2d($this->data, $this->pos + 2);
2857 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2858
2859
        // move stream pointer to next record
2860 4
        $this->pos += 4 + $length;
2861
2862
        // external sheet references provided for named cells
2863 4
        if ($this->version == self::XLS_BIFF8) {
2864
            // offset: 0; size: 2; number of following ref structures
2865 4
            $nm = self::getUInt2d($recordData, 0);
2866 4
            for ($i = 0; $i < $nm; ++$i) {
2867 4
                $this->ref[] = [
2868
                    // offset: 2 + 6 * $i; index to EXTERNALBOOK record
2869 4
                    'externalBookIndex' => self::getUInt2d($recordData, 2 + 6 * $i),
2870
                    // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record
2871 4
                    'firstSheetIndex' => self::getUInt2d($recordData, 4 + 6 * $i),
2872
                    // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record
2873 4
                    'lastSheetIndex' => self::getUInt2d($recordData, 6 + 6 * $i),
2874
                ];
2875
            }
2876
        }
2877 4
    }
2878
2879
    /**
2880
     * DEFINEDNAME.
2881
     *
2882
     * This record is part of a Link Table. It contains the name
2883
     * and the token array of an internal defined name. Token
2884
     * arrays of defined names contain tokens with aberrant
2885
     * token classes.
2886
     *
2887
     * --    "OpenOffice.org's Documentation of the Microsoft
2888
     *         Excel File Format"
2889
     */
2890 1
    private function readDefinedName()
2891
    {
2892 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
2893 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2894
2895
        // move stream pointer to next record
2896 1
        $this->pos += 4 + $length;
2897
2898 1
        if ($this->version == self::XLS_BIFF8) {
2899
            // retrieves named cells
2900
2901
            // offset: 0; size: 2; option flags
2902 1
            $opts = self::getUInt2d($recordData, 0);
2903
2904
            // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name
2905 1
            $isBuiltInName = (0x0020 & $opts) >> 5;
2906
2907
            // offset: 2; size: 1; keyboard shortcut
2908
2909
            // offset: 3; size: 1; length of the name (character count)
2910 1
            $nlen = ord($recordData[3]);
2911
2912
            // offset: 4; size: 2; size of the formula data (it can happen that this is zero)
2913
            // note: there can also be additional data, this is not included in $flen
2914 1
            $flen = self::getUInt2d($recordData, 4);
2915
2916
            // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based)
2917 1
            $scope = self::getUInt2d($recordData, 8);
2918
2919
            // offset: 14; size: var; Name (Unicode string without length field)
2920 1
            $string = self::readUnicodeString(substr($recordData, 14), $nlen);
2921
2922
            // offset: var; size: $flen; formula data
2923 1
            $offset = 14 + $string['size'];
2924 1
            $formulaStructure = pack('v', $flen) . substr($recordData, $offset);
2925
2926
            try {
2927 1
                $formula = $this->getFormulaFromStructure($formulaStructure);
2928
            } catch (PhpSpreadsheetException $e) {
2929
                $formula = '';
2930
            }
2931
2932 1
            $this->definedname[] = [
2933 1
                'isBuiltInName' => $isBuiltInName,
2934 1
                'name' => $string['value'],
2935 1
                'formula' => $formula,
2936 1
                'scope' => $scope,
2937
            ];
2938
        }
2939 1
    }
2940
2941
    /**
2942
     * Read MSODRAWINGGROUP record.
2943
     */
2944 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...
2945
    {
2946 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
2947
2948
        // get spliced record data
2949 3
        $splicedRecordData = $this->getSplicedRecordData();
2950 3
        $recordData = $splicedRecordData['recordData'];
2951
2952 3
        $this->drawingGroupData .= $recordData;
2953 3
    }
2954
2955
    /**
2956
     * SST - Shared String Table.
2957
     *
2958
     * This record contains a list of all strings used anywhere
2959
     * in the workbook. Each string occurs only once. The
2960
     * workbook uses indexes into the list to reference the
2961
     * strings.
2962
     *
2963
     * --    "OpenOffice.org's Documentation of the Microsoft
2964
     *         Excel File Format"
2965
     **/
2966 18
    private function readSst()
2967
    {
2968
        // offset within (spliced) record data
2969 18
        $pos = 0;
2970
2971
        // get spliced record data
2972 18
        $splicedRecordData = $this->getSplicedRecordData();
2973
2974 18
        $recordData = $splicedRecordData['recordData'];
2975 18
        $spliceOffsets = $splicedRecordData['spliceOffsets'];
2976
2977
        // offset: 0; size: 4; total number of strings in the workbook
2978 18
        $pos += 4;
2979
2980
        // offset: 4; size: 4; number of following strings ($nm)
2981 18
        $nm = self::getInt4d($recordData, 4);
2982 18
        $pos += 4;
2983
2984
        // loop through the Unicode strings (16-bit length)
2985 18
        for ($i = 0; $i < $nm; ++$i) {
2986
            // number of characters in the Unicode string
2987 18
            $numChars = self::getUInt2d($recordData, $pos);
2988 18
            $pos += 2;
2989
2990
            // option flags
2991 18
            $optionFlags = ord($recordData[$pos]);
2992 18
            ++$pos;
2993
2994
            // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed
2995 18
            $isCompressed = (($optionFlags & 0x01) == 0);
2996
2997
            // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic
2998 18
            $hasAsian = (($optionFlags & 0x04) != 0);
2999
3000
            // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text
3001 18
            $hasRichText = (($optionFlags & 0x08) != 0);
3002
3003 18
            if ($hasRichText) {
3004
                // number of Rich-Text formatting runs
3005 2
                $formattingRuns = self::getUInt2d($recordData, $pos);
3006 2
                $pos += 2;
3007
            }
3008
3009 18
            if ($hasAsian) {
3010
                // size of Asian phonetic setting
3011
                $extendedRunLength = self::getInt4d($recordData, $pos);
3012
                $pos += 4;
3013
            }
3014
3015
            // expected byte length of character array if not split
3016 18
            $len = ($isCompressed) ? $numChars : $numChars * 2;
3017
3018
            // look up limit position
3019 18
            foreach ($spliceOffsets as $spliceOffset) {
3020
                // it can happen that the string is empty, therefore we need
3021
                // <= and not just <
3022 18
                if ($pos <= $spliceOffset) {
3023 18
                    $limitpos = $spliceOffset;
3024
3025 18
                    break;
3026
                }
3027
            }
3028
3029 18
            if ($pos + $len <= $limitpos) {
3030
                // character array is not split between records
3031
3032 18
                $retstr = substr($recordData, $pos, $len);
3033 18
                $pos += $len;
3034
            } else {
3035
                // character array is split between records
3036
3037
                // first part of character array
3038
                $retstr = substr($recordData, $pos, $limitpos - $pos);
3039
3040
                $bytesRead = $limitpos - $pos;
3041
3042
                // remaining characters in Unicode string
3043
                $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));
3044
3045
                $pos = $limitpos;
3046
3047
                // keep reading the characters
3048
                while ($charsLeft > 0) {
3049
                    // look up next limit position, in case the string span more than one continue record
3050
                    foreach ($spliceOffsets as $spliceOffset) {
3051
                        if ($pos < $spliceOffset) {
3052
                            $limitpos = $spliceOffset;
3053
3054
                            break;
3055
                        }
3056
                    }
3057
3058
                    // repeated option flags
3059
                    // OpenOffice.org documentation 5.21
3060
                    $option = ord($recordData[$pos]);
3061
                    ++$pos;
3062
3063
                    if ($isCompressed && ($option == 0)) {
3064
                        // 1st fragment compressed
3065
                        // this fragment compressed
3066
                        $len = min($charsLeft, $limitpos - $pos);
3067
                        $retstr .= substr($recordData, $pos, $len);
3068
                        $charsLeft -= $len;
3069
                        $isCompressed = true;
3070
                    } elseif (!$isCompressed && ($option != 0)) {
3071
                        // 1st fragment uncompressed
3072
                        // this fragment uncompressed
3073
                        $len = min($charsLeft * 2, $limitpos - $pos);
3074
                        $retstr .= substr($recordData, $pos, $len);
3075
                        $charsLeft -= $len / 2;
3076
                        $isCompressed = false;
3077
                    } elseif (!$isCompressed && ($option == 0)) {
3078
                        // 1st fragment uncompressed
3079
                        // this fragment compressed
3080
                        $len = min($charsLeft, $limitpos - $pos);
3081
                        for ($j = 0; $j < $len; ++$j) {
3082
                            $retstr .= $recordData[$pos + $j]
3083
                            . chr(0);
3084
                        }
3085
                        $charsLeft -= $len;
3086
                        $isCompressed = false;
3087
                    } else {
3088
                        // 1st fragment compressed
3089
                        // this fragment uncompressed
3090
                        $newstr = '';
3091
                        $jMax = strlen($retstr);
3092
                        for ($j = 0; $j < $jMax; ++$j) {
3093
                            $newstr .= $retstr[$j] . chr(0);
3094
                        }
3095
                        $retstr = $newstr;
3096
                        $len = min($charsLeft * 2, $limitpos - $pos);
3097
                        $retstr .= substr($recordData, $pos, $len);
3098
                        $charsLeft -= $len / 2;
3099
                        $isCompressed = false;
3100
                    }
3101
3102
                    $pos += $len;
3103
                }
3104
            }
3105
3106
            // convert to UTF-8
3107 18
            $retstr = self::encodeUTF16($retstr, $isCompressed);
3108
3109
            // read additional Rich-Text information, if any
3110 18
            $fmtRuns = [];
3111 18
            if ($hasRichText) {
3112
                // list of formatting runs
3113 2
                for ($j = 0; $j < $formattingRuns; ++$j) {
3114
                    // first formatted character; zero-based
3115 2
                    $charPos = self::getUInt2d($recordData, $pos + $j * 4);
3116
3117
                    // index to font record
3118 2
                    $fontIndex = self::getUInt2d($recordData, $pos + 2 + $j * 4);
3119
3120 2
                    $fmtRuns[] = [
3121 2
                        'charPos' => $charPos,
3122 2
                        'fontIndex' => $fontIndex,
3123
                    ];
3124
                }
3125 2
                $pos += 4 * $formattingRuns;
3126
            }
3127
3128
            // read additional Asian phonetics information, if any
3129 18
            if ($hasAsian) {
3130
                // For Asian phonetic settings, we skip the extended string data
3131
                $pos += $extendedRunLength;
3132
            }
3133
3134
            // store the shared sting
3135 18
            $this->sst[] = [
3136 18
                'value' => $retstr,
3137 18
                'fmtRuns' => $fmtRuns,
3138
            ];
3139
        }
3140
3141
        // getSplicedRecordData() takes care of moving current position in data stream
3142 18
    }
3143
3144
    /**
3145
     * Read PRINTGRIDLINES record.
3146
     */
3147 18
    private function readPrintGridlines()
3148
    {
3149 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3150 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3151
3152
        // move stream pointer to next record
3153 18
        $this->pos += 4 + $length;
3154
3155 18
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3156
            // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines
3157 17
            $printGridlines = (bool) self::getUInt2d($recordData, 0);
3158 17
            $this->phpSheet->setPrintGridlines($printGridlines);
3159
        }
3160 18
    }
3161
3162
    /**
3163
     * Read DEFAULTROWHEIGHT record.
3164
     */
3165 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...
3166
    {
3167 17
        $length = self::getUInt2d($this->data, $this->pos + 2);
3168 17
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3169
3170
        // move stream pointer to next record
3171 17
        $this->pos += 4 + $length;
3172
3173
        // offset: 0; size: 2; option flags
3174
        // 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...
3175 17
        $height = self::getUInt2d($recordData, 2);
3176 17
        $this->phpSheet->getDefaultRowDimension()->setRowHeight($height / 20);
3177 17
    }
3178
3179
    /**
3180
     * Read SHEETPR record.
3181
     */
3182 18
    private function readSheetPr()
3183
    {
3184 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3185 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3186
3187
        // move stream pointer to next record
3188 18
        $this->pos += 4 + $length;
3189
3190
        // offset: 0; size: 2
3191
3192
        // bit: 6; mask: 0x0040; 0 = outline buttons above outline group
3193 18
        $isSummaryBelow = (0x0040 & self::getUInt2d($recordData, 0)) >> 6;
3194 18
        $this->phpSheet->setShowSummaryBelow($isSummaryBelow);
3195
3196
        // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group
3197 18
        $isSummaryRight = (0x0080 & self::getUInt2d($recordData, 0)) >> 7;
3198 18
        $this->phpSheet->setShowSummaryRight($isSummaryRight);
3199
3200
        // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages
3201
        // this corresponds to radio button setting in page setup dialog in Excel
3202 18
        $this->isFitToPages = (bool) ((0x0100 & self::getUInt2d($recordData, 0)) >> 8);
3203 18
    }
3204
3205
    /**
3206
     * Read HORIZONTALPAGEBREAKS record.
3207
     */
3208 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...
3209
    {
3210
        $length = self::getUInt2d($this->data, $this->pos + 2);
3211
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3212
3213
        // move stream pointer to next record
3214
        $this->pos += 4 + $length;
3215
3216
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3217
            // offset: 0; size: 2; number of the following row index structures
3218
            $nm = self::getUInt2d($recordData, 0);
3219
3220
            // offset: 2; size: 6 * $nm; list of $nm row index structures
3221
            for ($i = 0; $i < $nm; ++$i) {
3222
                $r = self::getUInt2d($recordData, 2 + 6 * $i);
3223
                $cf = self::getUInt2d($recordData, 2 + 6 * $i + 2);
3224
                $cl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
3225
3226
                // not sure why two column indexes are necessary?
3227
                $this->phpSheet->setBreakByColumnAndRow($cf + 1, $r, Worksheet::BREAK_ROW);
3228
            }
3229
        }
3230
    }
3231
3232
    /**
3233
     * Read VERTICALPAGEBREAKS record.
3234
     */
3235 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...
3236
    {
3237
        $length = self::getUInt2d($this->data, $this->pos + 2);
3238
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3239
3240
        // move stream pointer to next record
3241
        $this->pos += 4 + $length;
3242
3243
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3244
            // offset: 0; size: 2; number of the following column index structures
3245
            $nm = self::getUInt2d($recordData, 0);
3246
3247
            // offset: 2; size: 6 * $nm; list of $nm row index structures
3248
            for ($i = 0; $i < $nm; ++$i) {
3249
                $c = self::getUInt2d($recordData, 2 + 6 * $i);
3250
                $rf = self::getUInt2d($recordData, 2 + 6 * $i + 2);
3251
                $rl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
3252
3253
                // not sure why two row indexes are necessary?
3254
                $this->phpSheet->setBreakByColumnAndRow($c + 1, $rf, Worksheet::BREAK_COLUMN);
3255
            }
3256
        }
3257
    }
3258
3259
    /**
3260
     * Read HEADER record.
3261
     */
3262 18 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...
3263
    {
3264 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3265 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3266
3267
        // move stream pointer to next record
3268 18
        $this->pos += 4 + $length;
3269
3270 18
        if (!$this->readDataOnly) {
3271
            // offset: 0; size: var
3272
            // realized that $recordData can be empty even when record exists
3273 17
            if ($recordData) {
3274 3
                if ($this->version == self::XLS_BIFF8) {
3275 3
                    $string = self::readUnicodeStringLong($recordData);
3276
                } else {
3277
                    $string = $this->readByteStringShort($recordData);
3278
                }
3279
3280 3
                $this->phpSheet->getHeaderFooter()->setOddHeader($string['value']);
3281 3
                $this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']);
3282
            }
3283
        }
3284 18
    }
3285
3286
    /**
3287
     * Read FOOTER record.
3288
     */
3289 18 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...
3290
    {
3291 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3292 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3293
3294
        // move stream pointer to next record
3295 18
        $this->pos += 4 + $length;
3296
3297 18
        if (!$this->readDataOnly) {
3298
            // offset: 0; size: var
3299
            // realized that $recordData can be empty even when record exists
3300 17
            if ($recordData) {
3301 3
                if ($this->version == self::XLS_BIFF8) {
3302 3
                    $string = self::readUnicodeStringLong($recordData);
3303
                } else {
3304
                    $string = $this->readByteStringShort($recordData);
3305
                }
3306 3
                $this->phpSheet->getHeaderFooter()->setOddFooter($string['value']);
3307 3
                $this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']);
3308
            }
3309
        }
3310 18
    }
3311
3312
    /**
3313
     * Read HCENTER record.
3314
     */
3315 18 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...
3316
    {
3317 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3318 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3319
3320
        // move stream pointer to next record
3321 18
        $this->pos += 4 + $length;
3322
3323 18
        if (!$this->readDataOnly) {
3324
            // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally
3325 17
            $isHorizontalCentered = (bool) self::getUInt2d($recordData, 0);
3326
3327 17
            $this->phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered);
3328
        }
3329 18
    }
3330
3331
    /**
3332
     * Read VCENTER record.
3333
     */
3334 18 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...
3335
    {
3336 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3337 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3338
3339
        // move stream pointer to next record
3340 18
        $this->pos += 4 + $length;
3341
3342 18
        if (!$this->readDataOnly) {
3343
            // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered
3344 17
            $isVerticalCentered = (bool) self::getUInt2d($recordData, 0);
3345
3346 17
            $this->phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered);
3347
        }
3348 18
    }
3349
3350
    /**
3351
     * Read LEFTMARGIN record.
3352
     */
3353 5
    private function readLeftMargin()
3354
    {
3355 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
3356 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3357
3358
        // move stream pointer to next record
3359 5
        $this->pos += 4 + $length;
3360
3361 5
        if (!$this->readDataOnly) {
3362
            // offset: 0; size: 8
3363 5
            $this->phpSheet->getPageMargins()->setLeft(self::extractNumber($recordData));
3364
        }
3365 5
    }
3366
3367
    /**
3368
     * Read RIGHTMARGIN record.
3369
     */
3370 5
    private function readRightMargin()
3371
    {
3372 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
3373 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3374
3375
        // move stream pointer to next record
3376 5
        $this->pos += 4 + $length;
3377
3378 5
        if (!$this->readDataOnly) {
3379
            // offset: 0; size: 8
3380 5
            $this->phpSheet->getPageMargins()->setRight(self::extractNumber($recordData));
3381
        }
3382 5
    }
3383
3384
    /**
3385
     * Read TOPMARGIN record.
3386
     */
3387 5
    private function readTopMargin()
3388
    {
3389 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
3390 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3391
3392
        // move stream pointer to next record
3393 5
        $this->pos += 4 + $length;
3394
3395 5
        if (!$this->readDataOnly) {
3396
            // offset: 0; size: 8
3397 5
            $this->phpSheet->getPageMargins()->setTop(self::extractNumber($recordData));
3398
        }
3399 5
    }
3400
3401
    /**
3402
     * Read BOTTOMMARGIN record.
3403
     */
3404 5
    private function readBottomMargin()
3405
    {
3406 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
3407 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3408
3409
        // move stream pointer to next record
3410 5
        $this->pos += 4 + $length;
3411
3412 5
        if (!$this->readDataOnly) {
3413
            // offset: 0; size: 8
3414 5
            $this->phpSheet->getPageMargins()->setBottom(self::extractNumber($recordData));
3415
        }
3416 5
    }
3417
3418
    /**
3419
     * Read PAGESETUP record.
3420
     */
3421 18
    private function readPageSetup()
3422
    {
3423 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3424 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3425
3426
        // move stream pointer to next record
3427 18
        $this->pos += 4 + $length;
3428
3429 18
        if (!$this->readDataOnly) {
3430
            // offset: 0; size: 2; paper size
3431 17
            $paperSize = self::getUInt2d($recordData, 0);
3432
3433
            // offset: 2; size: 2; scaling factor
3434 17
            $scale = self::getUInt2d($recordData, 2);
3435
3436
            // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed
3437 17
            $fitToWidth = self::getUInt2d($recordData, 6);
3438
3439
            // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed
3440 17
            $fitToHeight = self::getUInt2d($recordData, 8);
3441
3442
            // offset: 10; size: 2; option flags
3443
3444
            // 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...
3445 17
            $isPortrait = (0x0002 & self::getUInt2d($recordData, 10)) >> 1;
3446
3447
            // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init
3448
            // when this bit is set, do not use flags for those properties
3449 17
            $isNotInit = (0x0004 & self::getUInt2d($recordData, 10)) >> 2;
3450
3451 17
            if (!$isNotInit) {
3452 16
                $this->phpSheet->getPageSetup()->setPaperSize($paperSize);
3453
                switch ($isPortrait) {
3454 16
                    case 0:
3455 2
                        $this->phpSheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE);
3456
3457 2
                        break;
3458 16
                    case 1:
3459 16
                        $this->phpSheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_PORTRAIT);
3460
3461 16
                        break;
3462
                }
3463
3464 16
                $this->phpSheet->getPageSetup()->setScale($scale, false);
3465 16
                $this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages);
3466 16
                $this->phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false);
3467 16
                $this->phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false);
3468
            }
3469
3470
            // offset: 16; size: 8; header margin (IEEE 754 floating-point value)
3471 17
            $marginHeader = self::extractNumber(substr($recordData, 16, 8));
3472 17
            $this->phpSheet->getPageMargins()->setHeader($marginHeader);
3473
3474
            // offset: 24; size: 8; footer margin (IEEE 754 floating-point value)
3475 17
            $marginFooter = self::extractNumber(substr($recordData, 24, 8));
3476 17
            $this->phpSheet->getPageMargins()->setFooter($marginFooter);
3477
        }
3478 18
    }
3479
3480
    /**
3481
     * PROTECT - Sheet protection (BIFF2 through BIFF8)
3482
     *   if this record is omitted, then it also means no sheet protection.
3483
     */
3484 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...
3485
    {
3486 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
3487 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3488
3489
        // move stream pointer to next record
3490 1
        $this->pos += 4 + $length;
3491
3492 1
        if ($this->readDataOnly) {
3493
            return;
3494
        }
3495
3496
        // 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...
3497
3498
        // bit 0, mask 0x01; 1 = sheet is protected
3499 1
        $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
3500 1
        $this->phpSheet->getProtection()->setSheet((bool) $bool);
3501 1
    }
3502
3503
    /**
3504
     * SCENPROTECT.
3505
     */
3506 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...
3507
    {
3508
        $length = self::getUInt2d($this->data, $this->pos + 2);
3509
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3510
3511
        // move stream pointer to next record
3512
        $this->pos += 4 + $length;
3513
3514
        if ($this->readDataOnly) {
3515
            return;
3516
        }
3517
3518
        // 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...
3519
3520
        // bit: 0, mask 0x01; 1 = scenarios are protected
3521
        $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
3522
3523
        $this->phpSheet->getProtection()->setScenarios((bool) $bool);
3524
    }
3525
3526
    /**
3527
     * OBJECTPROTECT.
3528
     */
3529 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...
3530
    {
3531
        $length = self::getUInt2d($this->data, $this->pos + 2);
3532
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3533
3534
        // move stream pointer to next record
3535
        $this->pos += 4 + $length;
3536
3537
        if ($this->readDataOnly) {
3538
            return;
3539
        }
3540
3541
        // 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...
3542
3543
        // bit: 0, mask 0x01; 1 = objects are protected
3544
        $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
3545
3546
        $this->phpSheet->getProtection()->setObjects((bool) $bool);
3547
    }
3548
3549
    /**
3550
     * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8).
3551
     */
3552
    private function readPassword()
3553
    {
3554
        $length = self::getUInt2d($this->data, $this->pos + 2);
3555
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3556
3557
        // move stream pointer to next record
3558
        $this->pos += 4 + $length;
3559
3560
        if (!$this->readDataOnly) {
3561
            // offset: 0; size: 2; 16-bit hash value of password
3562
            $password = strtoupper(dechex(self::getUInt2d($recordData, 0))); // the hashed password
3563
            $this->phpSheet->getProtection()->setPassword($password, true);
3564
        }
3565
    }
3566
3567
    /**
3568
     * Read DEFCOLWIDTH record.
3569
     */
3570 18 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...
3571
    {
3572 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3573 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3574
3575
        // move stream pointer to next record
3576 18
        $this->pos += 4 + $length;
3577
3578
        // offset: 0; size: 2; default column width
3579 18
        $width = self::getUInt2d($recordData, 0);
3580 18
        if ($width != 8) {
3581 1
            $this->phpSheet->getDefaultColumnDimension()->setWidth($width);
3582
        }
3583 18
    }
3584
3585
    /**
3586
     * Read COLINFO record.
3587
     */
3588 14
    private function readColInfo()
3589
    {
3590 14
        $length = self::getUInt2d($this->data, $this->pos + 2);
3591 14
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3592
3593
        // move stream pointer to next record
3594 14
        $this->pos += 4 + $length;
3595
3596 14
        if (!$this->readDataOnly) {
3597
            // offset: 0; size: 2; index to first column in range
3598 13
            $firstColumnIndex = self::getUInt2d($recordData, 0);
3599
3600
            // offset: 2; size: 2; index to last column in range
3601 13
            $lastColumnIndex = self::getUInt2d($recordData, 2);
3602
3603
            // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character
3604 13
            $width = self::getUInt2d($recordData, 4);
3605
3606
            // offset: 6; size: 2; index to XF record for default column formatting
3607 13
            $xfIndex = self::getUInt2d($recordData, 6);
3608
3609
            // offset: 8; size: 2; option flags
3610
            // bit: 0; mask: 0x0001; 1= columns are hidden
3611 13
            $isHidden = (0x0001 & self::getUInt2d($recordData, 8)) >> 0;
3612
3613
            // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline)
3614 13
            $level = (0x0700 & self::getUInt2d($recordData, 8)) >> 8;
3615
3616
            // bit: 12; mask: 0x1000; 1 = collapsed
3617 13
            $isCollapsed = (0x1000 & self::getUInt2d($recordData, 8)) >> 12;
3618
3619
            // offset: 10; size: 2; not used
3620
3621 13
            for ($i = $firstColumnIndex + 1; $i <= $lastColumnIndex + 1; ++$i) {
3622 13
                if ($lastColumnIndex == 255 || $lastColumnIndex == 256) {
3623 1
                    $this->phpSheet->getDefaultColumnDimension()->setWidth($width / 256);
3624
3625 1
                    break;
3626
                }
3627 12
                $this->phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256);
3628 12
                $this->phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden);
3629 12
                $this->phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level);
3630 12
                $this->phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed);
3631 12
                $this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3632
            }
3633
        }
3634 14
    }
3635
3636
    /**
3637
     * ROW.
3638
     *
3639
     * This record contains the properties of a single row in a
3640
     * sheet. Rows and cells in a sheet are divided into blocks
3641
     * of 32 rows.
3642
     *
3643
     * --    "OpenOffice.org's Documentation of the Microsoft
3644
     *         Excel File Format"
3645
     */
3646 17
    private function readRow()
3647
    {
3648 17
        $length = self::getUInt2d($this->data, $this->pos + 2);
3649 17
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3650
3651
        // move stream pointer to next record
3652 17
        $this->pos += 4 + $length;
3653
3654 17
        if (!$this->readDataOnly) {
3655
            // offset: 0; size: 2; index of this row
3656 16
            $r = self::getUInt2d($recordData, 0);
3657
3658
            // offset: 2; size: 2; index to column of the first cell which is described by a cell record
3659
3660
            // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1
3661
3662
            // 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...
3663
3664
            // bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point
3665 16
            $height = (0x7FFF & self::getUInt2d($recordData, 6)) >> 0;
3666
3667
            // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height
3668 16
            $useDefaultHeight = (0x8000 & self::getUInt2d($recordData, 6)) >> 15;
3669
3670 16
            if (!$useDefaultHeight) {
3671 16
                $this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
3672
            }
3673
3674
            // offset: 8; size: 2; not used
3675
3676
            // offset: 10; size: 2; not used in BIFF5-BIFF8
3677
3678
            // offset: 12; size: 4; option flags and default row formatting
3679
3680
            // bit: 2-0: mask: 0x00000007; outline level of the row
3681 16
            $level = (0x00000007 & self::getInt4d($recordData, 12)) >> 0;
3682 16
            $this->phpSheet->getRowDimension($r + 1)->setOutlineLevel($level);
3683
3684
            // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed
3685 16
            $isCollapsed = (0x00000010 & self::getInt4d($recordData, 12)) >> 4;
3686 16
            $this->phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed);
3687
3688
            // bit: 5; mask: 0x00000020; 1 = row is hidden
3689 16
            $isHidden = (0x00000020 & self::getInt4d($recordData, 12)) >> 5;
3690 16
            $this->phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden);
3691
3692
            // bit: 7; mask: 0x00000080; 1 = row has explicit format
3693 16
            $hasExplicitFormat = (0x00000080 & self::getInt4d($recordData, 12)) >> 7;
3694
3695
            // bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record
3696 16
            $xfIndex = (0x0FFF0000 & self::getInt4d($recordData, 12)) >> 16;
3697
3698 16
            if ($hasExplicitFormat && isset($this->mapCellXfIndex[$xfIndex])) {
3699 2
                $this->phpSheet->getRowDimension($r + 1)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3700
            }
3701
        }
3702 17
    }
3703
3704
    /**
3705
     * Read RK record
3706
     * This record represents a cell that contains an RK value
3707
     * (encoded integer or floating-point value). If a
3708
     * floating-point value cannot be encoded to an RK value,
3709
     * a NUMBER record will be written. This record replaces the
3710
     * record INTEGER written in BIFF2.
3711
     *
3712
     * --    "OpenOffice.org's Documentation of the Microsoft
3713
     *         Excel File Format"
3714
     */
3715 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...
3716
    {
3717 12
        $length = self::getUInt2d($this->data, $this->pos + 2);
3718 12
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3719
3720
        // move stream pointer to next record
3721 12
        $this->pos += 4 + $length;
3722
3723
        // offset: 0; size: 2; index to row
3724 12
        $row = self::getUInt2d($recordData, 0);
3725
3726
        // offset: 2; size: 2; index to column
3727 12
        $column = self::getUInt2d($recordData, 2);
3728 12
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
3729
3730
        // Read cell?
3731 12
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3732
            // offset: 4; size: 2; index to XF record
3733 12
            $xfIndex = self::getUInt2d($recordData, 4);
3734
3735
            // offset: 6; size: 4; RK value
3736 12
            $rknum = self::getInt4d($recordData, 6);
3737 12
            $numValue = self::getIEEE754($rknum);
3738
3739 12
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3740 12
            if (!$this->readDataOnly) {
3741
                // add style information
3742 11
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3743
            }
3744
3745
            // add cell
3746 12
            $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
3747
        }
3748 12
    }
3749
3750
    /**
3751
     * Read LABELSST record
3752
     * This record represents a cell that contains a string. It
3753
     * replaces the LABEL record and RSTRING record used in
3754
     * BIFF2-BIFF5.
3755
     *
3756
     * --    "OpenOffice.org's Documentation of the Microsoft
3757
     *         Excel File Format"
3758
     */
3759 17
    private function readLabelSst()
3760
    {
3761 17
        $length = self::getUInt2d($this->data, $this->pos + 2);
3762 17
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3763
3764
        // move stream pointer to next record
3765 17
        $this->pos += 4 + $length;
3766
3767
        // offset: 0; size: 2; index to row
3768 17
        $row = self::getUInt2d($recordData, 0);
3769
3770
        // offset: 2; size: 2; index to column
3771 17
        $column = self::getUInt2d($recordData, 2);
3772 17
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
3773
3774 17
        $emptyCell = true;
3775
        // Read cell?
3776 17
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3777
            // offset: 4; size: 2; index to XF record
3778 17
            $xfIndex = self::getUInt2d($recordData, 4);
3779
3780
            // offset: 6; size: 4; index to SST record
3781 17
            $index = self::getInt4d($recordData, 6);
3782
3783
            // add cell
3784 17
            if (($fmtRuns = $this->sst[$index]['fmtRuns']) && !$this->readDataOnly) {
3785
                // then we should treat as rich text
3786 2
                $richText = new RichText();
3787 2
                $charPos = 0;
3788 2
                $sstCount = count($this->sst[$index]['fmtRuns']);
3789 2
                for ($i = 0; $i <= $sstCount; ++$i) {
3790 2
                    if (isset($fmtRuns[$i])) {
3791 2
                        $text = StringHelper::substring($this->sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos);
3792 2
                        $charPos = $fmtRuns[$i]['charPos'];
3793
                    } else {
3794 2
                        $text = StringHelper::substring($this->sst[$index]['value'], $charPos, StringHelper::countCharacters($this->sst[$index]['value']));
3795
                    }
3796
3797 2
                    if (StringHelper::countCharacters($text) > 0) {
3798 2
                        if ($i == 0) { // first text run, no style
3799 1
                            $richText->createText($text);
3800
                        } else {
3801 2
                            $textRun = $richText->createTextRun($text);
3802 2
                            if (isset($fmtRuns[$i - 1])) {
3803 2
                                if ($fmtRuns[$i - 1]['fontIndex'] < 4) {
3804 2
                                    $fontIndex = $fmtRuns[$i - 1]['fontIndex'];
3805
                                } else {
3806
                                    // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
3807
                                    // check the OpenOffice documentation of the FONT record
3808 2
                                    $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1;
3809
                                }
3810 2
                                $textRun->setFont(clone $this->objFonts[$fontIndex]);
3811
                            }
3812
                        }
3813
                    }
3814
                }
3815 2
                if ($this->readEmptyCells || trim($richText->getPlainText()) !== '') {
3816 2
                    $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3817 2
                    $cell->setValueExplicit($richText, DataType::TYPE_STRING);
3818 2
                    $emptyCell = false;
3819
                }
3820
            } else {
3821 17
                if ($this->readEmptyCells || trim($this->sst[$index]['value']) !== '') {
3822 17
                    $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3823 17
                    $cell->setValueExplicit($this->sst[$index]['value'], DataType::TYPE_STRING);
3824 17
                    $emptyCell = false;
3825
                }
3826
            }
3827
3828 17
            if (!$this->readDataOnly && !$emptyCell) {
3829
                // add style information
3830 16
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3831
            }
3832
        }
3833 17
    }
3834
3835
    /**
3836
     * Read MULRK record
3837
     * This record represents a cell range containing RK value
3838
     * cells. All cells are located in the same row.
3839
     *
3840
     * --    "OpenOffice.org's Documentation of the Microsoft
3841
     *         Excel File Format"
3842
     */
3843 11
    private function readMulRk()
3844
    {
3845 11
        $length = self::getUInt2d($this->data, $this->pos + 2);
3846 11
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3847
3848
        // move stream pointer to next record
3849 11
        $this->pos += 4 + $length;
3850
3851
        // offset: 0; size: 2; index to row
3852 11
        $row = self::getUInt2d($recordData, 0);
3853
3854
        // offset: 2; size: 2; index to first column
3855 11
        $colFirst = self::getUInt2d($recordData, 2);
3856
3857
        // offset: var; size: 2; index to last column
3858 11
        $colLast = self::getUInt2d($recordData, $length - 2);
3859 11
        $columns = $colLast - $colFirst + 1;
3860
3861
        // offset within record data
3862 11
        $offset = 4;
3863
3864 11
        for ($i = 1; $i <= $columns; ++$i) {
3865 11
            $columnString = Coordinate::stringFromColumnIndex($colFirst + $i);
3866
3867
            // Read cell?
3868 11
            if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3869
                // offset: var; size: 2; index to XF record
3870 11
                $xfIndex = self::getUInt2d($recordData, $offset);
3871
3872
                // offset: var; size: 4; RK value
3873 11
                $numValue = self::getIEEE754(self::getInt4d($recordData, $offset + 2));
3874 11
                $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3875 11
                if (!$this->readDataOnly) {
3876
                    // add style
3877 10
                    $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3878
                }
3879
3880
                // add cell value
3881 11
                $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
3882
            }
3883
3884 11
            $offset += 6;
3885
        }
3886 11
    }
3887
3888
    /**
3889
     * Read NUMBER record
3890
     * This record represents a cell that contains a
3891
     * floating-point value.
3892
     *
3893
     * --    "OpenOffice.org's Documentation of the Microsoft
3894
     *         Excel File Format"
3895
     */
3896 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...
3897
    {
3898 11
        $length = self::getUInt2d($this->data, $this->pos + 2);
3899 11
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3900
3901
        // move stream pointer to next record
3902 11
        $this->pos += 4 + $length;
3903
3904
        // offset: 0; size: 2; index to row
3905 11
        $row = self::getUInt2d($recordData, 0);
3906
3907
        // offset: 2; size 2; index to column
3908 11
        $column = self::getUInt2d($recordData, 2);
3909 11
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
3910
3911
        // Read cell?
3912 11
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3913
            // offset 4; size: 2; index to XF record
3914 11
            $xfIndex = self::getUInt2d($recordData, 4);
3915
3916 11
            $numValue = self::extractNumber(substr($recordData, 6, 8));
3917
3918 11
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
3919 11
            if (!$this->readDataOnly) {
3920
                // add cell style
3921 10
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
3922
            }
3923
3924
            // add cell value
3925 11
            $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
3926
        }
3927 11
    }
3928
3929
    /**
3930
     * Read FORMULA record + perhaps a following STRING record if formula result is a string
3931
     * This record contains the token array and the result of a
3932
     * formula cell.
3933
     *
3934
     * --    "OpenOffice.org's Documentation of the Microsoft
3935
     *         Excel File Format"
3936
     */
3937 10
    private function readFormula()
3938
    {
3939 10
        $length = self::getUInt2d($this->data, $this->pos + 2);
3940 10
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3941
3942
        // move stream pointer to next record
3943 10
        $this->pos += 4 + $length;
3944
3945
        // offset: 0; size: 2; row index
3946 10
        $row = self::getUInt2d($recordData, 0);
3947
3948
        // offset: 2; size: 2; col index
3949 10
        $column = self::getUInt2d($recordData, 2);
3950 10
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
3951
3952
        // offset: 20: size: variable; formula structure
3953 10
        $formulaStructure = substr($recordData, 20);
3954
3955
        // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc.
3956 10
        $options = self::getUInt2d($recordData, 14);
3957
3958
        // bit: 0; mask: 0x0001; 1 = recalculate always
3959
        // bit: 1; mask: 0x0002; 1 = calculate on open
3960
        // bit: 2; mask: 0x0008; 1 = part of a shared formula
3961 10
        $isPartOfSharedFormula = (bool) (0x0008 & $options);
3962
3963
        // WARNING:
3964
        // We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true
3965
        // the formula data may be ordinary formula data, therefore we need to check
3966
        // explicitly for the tExp token (0x01)
3967 10
        $isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure[2]) == 0x01;
3968
3969 10
        if ($isPartOfSharedFormula) {
3970
            // part of shared formula which means there will be a formula with a tExp token and nothing else
3971
            // get the base cell, grab tExp token
3972
            $baseRow = self::getUInt2d($formulaStructure, 3);
3973
            $baseCol = self::getUInt2d($formulaStructure, 5);
3974
            $this->baseCell = Coordinate::stringFromColumnIndex($baseCol + 1) . ($baseRow + 1);
3975
        }
3976
3977
        // Read cell?
3978 10
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
3979 10
            if ($isPartOfSharedFormula) {
3980
                // formula is added to this cell after the sheet has been read
3981
                $this->sharedFormulaParts[$columnString . ($row + 1)] = $this->baseCell;
3982
            }
3983
3984
            // offset: 16: size: 4; not used
3985
3986
            // offset: 4; size: 2; XF index
3987 10
            $xfIndex = self::getUInt2d($recordData, 4);
3988
3989
            // offset: 6; size: 8; result of the formula
3990 10
            if ((ord($recordData[6]) == 0) && (ord($recordData[12]) == 255) && (ord($recordData[13]) == 255)) {
3991
                // String formula. Result follows in appended STRING record
3992
                $dataType = DataType::TYPE_STRING;
3993
3994
                // read possible SHAREDFMLA record
3995
                $code = self::getUInt2d($this->data, $this->pos);
3996
                if ($code == self::XLS_TYPE_SHAREDFMLA) {
3997
                    $this->readSharedFmla();
3998
                }
3999
4000
                // read STRING record
4001
                $value = $this->readString();
4002 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...
4003 10
                && (ord($recordData[12]) == 255)
4004 10
                && (ord($recordData[13]) == 255)) {
4005
                // Boolean formula. Result is in +2; 0=false, 1=true
4006
                $dataType = DataType::TYPE_BOOL;
4007
                $value = (bool) ord($recordData[8]);
4008 10
            } elseif ((ord($recordData[6]) == 2)
4009 10
                && (ord($recordData[12]) == 255)
4010 10
                && (ord($recordData[13]) == 255)) {
4011
                // Error formula. Error code is in +2
4012 8
                $dataType = DataType::TYPE_ERROR;
4013 8
                $value = Xls\ErrorCode::lookup(ord($recordData[8]));
4014 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...
4015 10
                && (ord($recordData[12]) == 255)
4016 10
                && (ord($recordData[13]) == 255)) {
4017
                // Formula result is a null string
4018 1
                $dataType = DataType::TYPE_NULL;
4019 1
                $value = '';
4020
            } else {
4021
                // forumla result is a number, first 14 bytes like _NUMBER record
4022 10
                $dataType = DataType::TYPE_NUMERIC;
4023 10
                $value = self::extractNumber(substr($recordData, 6, 8));
4024
            }
4025
4026 10
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4027 10
            if (!$this->readDataOnly) {
4028
                // add cell style
4029 9
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4030
            }
4031
4032
            // store the formula
4033 10
            if (!$isPartOfSharedFormula) {
4034
                // not part of shared formula
4035
                // add cell value. If we can read formula, populate with formula, otherwise just used cached value
4036
                try {
4037 10
                    if ($this->version != self::XLS_BIFF8) {
4038
                        throw new Exception('Not BIFF8. Can only read BIFF8 formulas');
4039
                    }
4040 10
                    $formula = $this->getFormulaFromStructure($formulaStructure); // get formula in human language
4041 10
                    $cell->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA);
4042
                } catch (PhpSpreadsheetException $e) {
4043 10
                    $cell->setValueExplicit($value, $dataType);
4044
                }
4045
            } else {
4046
                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...
4047
                    // do nothing at this point, formula id added later in the code
4048
                } else {
4049
                    $cell->setValueExplicit($value, $dataType);
4050
                }
4051
            }
4052
4053
            // store the cached calculated value
4054 10
            $cell->setCalculatedValue($value);
4055
        }
4056 10
    }
4057
4058
    /**
4059
     * Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader,
4060
     * which usually contains relative references.
4061
     * These will be used to construct the formula in each shared formula part after the sheet is read.
4062
     */
4063
    private function readSharedFmla()
4064
    {
4065
        $length = self::getUInt2d($this->data, $this->pos + 2);
4066
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4067
4068
        // move stream pointer to next record
4069
        $this->pos += 4 + $length;
4070
4071
        // offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything
4072
        $cellRange = substr($recordData, 0, 6);
4073
        $cellRange = $this->readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax
4074
4075
        // offset: 6, size: 1; not used
4076
4077
        // offset: 7, size: 1; number of existing FORMULA records for this shared formula
4078
        $no = ord($recordData[7]);
4079
4080
        // offset: 8, size: var; Binary token array of the shared formula
4081
        $formula = substr($recordData, 8);
4082
4083
        // at this point we only store the shared formula for later use
4084
        $this->sharedFormulas[$this->baseCell] = $formula;
4085
    }
4086
4087
    /**
4088
     * Read a STRING record from current stream position and advance the stream pointer to next record
4089
     * This record is used for storing result from FORMULA record when it is a string, and
4090
     * it occurs directly after the FORMULA record.
4091
     *
4092
     * @return string The string contents as UTF-8
4093
     */
4094
    private function readString()
4095
    {
4096
        $length = self::getUInt2d($this->data, $this->pos + 2);
4097
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4098
4099
        // move stream pointer to next record
4100
        $this->pos += 4 + $length;
4101
4102
        if ($this->version == self::XLS_BIFF8) {
4103
            $string = self::readUnicodeStringLong($recordData);
4104
            $value = $string['value'];
4105
        } else {
4106
            $string = $this->readByteStringLong($recordData);
4107
            $value = $string['value'];
4108
        }
4109
4110
        return $value;
4111
    }
4112
4113
    /**
4114
     * Read BOOLERR record
4115
     * This record represents a Boolean value or error value
4116
     * cell.
4117
     *
4118
     * --    "OpenOffice.org's Documentation of the Microsoft
4119
     *         Excel File Format"
4120
     */
4121 8
    private function readBoolErr()
4122
    {
4123 8
        $length = self::getUInt2d($this->data, $this->pos + 2);
4124 8
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4125
4126
        // move stream pointer to next record
4127 8
        $this->pos += 4 + $length;
4128
4129
        // offset: 0; size: 2; row index
4130 8
        $row = self::getUInt2d($recordData, 0);
4131
4132
        // offset: 2; size: 2; column index
4133 8
        $column = self::getUInt2d($recordData, 2);
4134 8
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
4135
4136
        // Read cell?
4137 8
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4138
            // offset: 4; size: 2; index to XF record
4139 8
            $xfIndex = self::getUInt2d($recordData, 4);
4140
4141
            // offset: 6; size: 1; the boolean value or error value
4142 8
            $boolErr = ord($recordData[6]);
4143
4144
            // 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...
4145 8
            $isError = ord($recordData[7]);
4146
4147 8
            $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4148
            switch ($isError) {
4149 8
                case 0: // boolean
4150 8
                    $value = (bool) $boolErr;
4151
4152
                    // add cell value
4153 8
                    $cell->setValueExplicit($value, DataType::TYPE_BOOL);
4154
4155 8
                    break;
4156
                case 1: // error type
4157
                    $value = Xls\ErrorCode::lookup($boolErr);
4158
4159
                    // add cell value
4160
                    $cell->setValueExplicit($value, DataType::TYPE_ERROR);
4161
4162
                    break;
4163
            }
4164
4165 8
            if (!$this->readDataOnly) {
4166
                // add cell style
4167 7
                $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4168
            }
4169
        }
4170 8
    }
4171
4172
    /**
4173
     * Read MULBLANK record
4174
     * This record represents a cell range of empty cells. All
4175
     * cells are located in the same row.
4176
     *
4177
     * --    "OpenOffice.org's Documentation of the Microsoft
4178
     *         Excel File Format"
4179
     */
4180 11
    private function readMulBlank()
4181
    {
4182 11
        $length = self::getUInt2d($this->data, $this->pos + 2);
4183 11
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4184
4185
        // move stream pointer to next record
4186 11
        $this->pos += 4 + $length;
4187
4188
        // offset: 0; size: 2; index to row
4189 11
        $row = self::getUInt2d($recordData, 0);
4190
4191
        // offset: 2; size: 2; index to first column
4192 11
        $fc = self::getUInt2d($recordData, 2);
4193
4194
        // offset: 4; size: 2 x nc; list of indexes to XF records
4195
        // add style information
4196 11
        if (!$this->readDataOnly && $this->readEmptyCells) {
4197 10
            for ($i = 0; $i < $length / 2 - 3; ++$i) {
4198 10
                $columnString = Coordinate::stringFromColumnIndex($fc + $i + 1);
4199
4200
                // Read cell?
4201 10
                if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4202 10
                    $xfIndex = self::getUInt2d($recordData, 4 + 2 * $i);
4203 10
                    $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4204
                }
4205
            }
4206
        }
4207
4208
        // offset: 6; size 2; index to last column (not needed)
4209 11
    }
4210
4211
    /**
4212
     * Read LABEL record
4213
     * This record represents a cell that contains a string. In
4214
     * BIFF8 it is usually replaced by the LABELSST record.
4215
     * Excel still uses this record, if it copies unformatted
4216
     * text cells to the clipboard.
4217
     *
4218
     * --    "OpenOffice.org's Documentation of the Microsoft
4219
     *         Excel File Format"
4220
     */
4221
    private function readLabel()
4222
    {
4223
        $length = self::getUInt2d($this->data, $this->pos + 2);
4224
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4225
4226
        // move stream pointer to next record
4227
        $this->pos += 4 + $length;
4228
4229
        // offset: 0; size: 2; index to row
4230
        $row = self::getUInt2d($recordData, 0);
4231
4232
        // offset: 2; size: 2; index to column
4233
        $column = self::getUInt2d($recordData, 2);
4234
        $columnString = Coordinate::stringFromColumnIndex($column + 1);
4235
4236
        // Read cell?
4237
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4238
            // offset: 4; size: 2; XF index
4239
            $xfIndex = self::getUInt2d($recordData, 4);
4240
4241
            // add cell value
4242
            // todo: what if string is very long? continue record
4243 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...
4244
                $string = self::readUnicodeStringLong(substr($recordData, 6));
4245
                $value = $string['value'];
4246
            } else {
4247
                $string = $this->readByteStringLong(substr($recordData, 6));
4248
                $value = $string['value'];
4249
            }
4250
            if ($this->readEmptyCells || trim($value) !== '') {
4251
                $cell = $this->phpSheet->getCell($columnString . ($row + 1));
4252
                $cell->setValueExplicit($value, DataType::TYPE_STRING);
4253
4254
                if (!$this->readDataOnly) {
4255
                    // add cell style
4256
                    $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4257
                }
4258
            }
4259
        }
4260
    }
4261
4262
    /**
4263
     * Read BLANK record.
4264
     */
4265 2
    private function readBlank()
4266
    {
4267 2
        $length = self::getUInt2d($this->data, $this->pos + 2);
4268 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4269
4270
        // move stream pointer to next record
4271 2
        $this->pos += 4 + $length;
4272
4273
        // offset: 0; size: 2; row index
4274 2
        $row = self::getUInt2d($recordData, 0);
4275
4276
        // offset: 2; size: 2; col index
4277 2
        $col = self::getUInt2d($recordData, 2);
4278 2
        $columnString = Coordinate::stringFromColumnIndex($col + 1);
4279
4280
        // Read cell?
4281 2
        if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
4282
            // offset: 4; size: 2; XF index
4283 2
            $xfIndex = self::getUInt2d($recordData, 4);
4284
4285
            // add style information
4286 2
            if (!$this->readDataOnly && $this->readEmptyCells) {
4287 2
                $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
4288
            }
4289
        }
4290 2
    }
4291
4292
    /**
4293
     * Read MSODRAWING record.
4294
     */
4295 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...
4296
    {
4297 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
4298
4299
        // get spliced record data
4300 3
        $splicedRecordData = $this->getSplicedRecordData();
4301 3
        $recordData = $splicedRecordData['recordData'];
4302
4303 3
        $this->drawingData .= $recordData;
4304 3
    }
4305
4306
    /**
4307
     * Read OBJ record.
4308
     */
4309 3
    private function readObj()
4310
    {
4311 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
4312 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4313
4314
        // move stream pointer to next record
4315 3
        $this->pos += 4 + $length;
4316
4317 3
        if ($this->readDataOnly || $this->version != self::XLS_BIFF8) {
4318
            return;
4319
        }
4320
4321
        // recordData consists of an array of subrecords looking like this:
4322
        //    ft: 2 bytes; ftCmo type (0x15)
4323
        //    cb: 2 bytes; size in bytes of ftCmo data
4324
        //    ot: 2 bytes; Object Type
4325
        //    id: 2 bytes; Object id number
4326
        //    grbit: 2 bytes; Option Flags
4327
        //    data: var; subrecord data
4328
4329
        // for now, we are just interested in the second subrecord containing the object type
4330 3
        $ftCmoType = self::getUInt2d($recordData, 0);
4331 3
        $cbCmoSize = self::getUInt2d($recordData, 2);
4332 3
        $otObjType = self::getUInt2d($recordData, 4);
4333 3
        $idObjID = self::getUInt2d($recordData, 6);
4334 3
        $grbitOpts = self::getUInt2d($recordData, 6);
4335
4336 3
        $this->objs[] = [
4337 3
            'ftCmoType' => $ftCmoType,
4338 3
            'cbCmoSize' => $cbCmoSize,
4339 3
            'otObjType' => $otObjType,
4340 3
            'idObjID' => $idObjID,
4341 3
            'grbitOpts' => $grbitOpts,
4342
        ];
4343 3
        $this->textObjRef = $idObjID;
4344 3
    }
4345
4346
    /**
4347
     * Read WINDOW2 record.
4348
     */
4349 18
    private function readWindow2()
4350
    {
4351 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
4352 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4353
4354
        // move stream pointer to next record
4355 18
        $this->pos += 4 + $length;
4356
4357
        // offset: 0; size: 2; option flags
4358 18
        $options = self::getUInt2d($recordData, 0);
4359
4360
        // offset: 2; size: 2; index to first visible row
4361 18
        $firstVisibleRow = self::getUInt2d($recordData, 2);
4362
4363
        // offset: 4; size: 2; index to first visible colum
4364 18
        $firstVisibleColumn = self::getUInt2d($recordData, 4);
4365 18
        if ($this->version === self::XLS_BIFF8) {
4366
            // offset:  8; size: 2; not used
4367
            // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%)
4368
            // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%)
4369
            // offset: 14; size: 4; not used
4370 18
            $zoomscaleInPageBreakPreview = self::getUInt2d($recordData, 10);
4371 18
            if ($zoomscaleInPageBreakPreview === 0) {
4372 18
                $zoomscaleInPageBreakPreview = 60;
4373
            }
4374 18
            $zoomscaleInNormalView = self::getUInt2d($recordData, 12);
4375 18
            if ($zoomscaleInNormalView === 0) {
4376 17
                $zoomscaleInNormalView = 100;
4377
            }
4378
        }
4379
4380
        // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines
4381 18
        $showGridlines = (bool) ((0x0002 & $options) >> 1);
4382 18
        $this->phpSheet->setShowGridlines($showGridlines);
4383
4384
        // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers
4385 18
        $showRowColHeaders = (bool) ((0x0004 & $options) >> 2);
4386 18
        $this->phpSheet->setShowRowColHeaders($showRowColHeaders);
4387
4388
        // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen
4389 18
        $this->frozen = (bool) ((0x0008 & $options) >> 3);
4390
4391
        // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left
4392 18
        $this->phpSheet->setRightToLeft((bool) ((0x0040 & $options) >> 6));
4393
4394
        // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active
4395 18
        $isActive = (bool) ((0x0400 & $options) >> 10);
4396 18
        if ($isActive) {
4397 15
            $this->spreadsheet->setActiveSheetIndex($this->spreadsheet->getIndex($this->phpSheet));
4398
        }
4399
4400
        // bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view
4401 18
        $isPageBreakPreview = (bool) ((0x0800 & $options) >> 11);
4402
4403
        //FIXME: set $firstVisibleRow and $firstVisibleColumn
4404
4405 18
        if ($this->phpSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_PAGE_LAYOUT) {
4406
            //NOTE: this setting is inferior to page layout view(Excel2007-)
4407 18
            $view = $isPageBreakPreview ? SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW : SheetView::SHEETVIEW_NORMAL;
4408 18
            $this->phpSheet->getSheetView()->setView($view);
4409 18
            if ($this->version === self::XLS_BIFF8) {
4410 18
                $zoomScale = $isPageBreakPreview ? $zoomscaleInPageBreakPreview : $zoomscaleInNormalView;
4411 18
                $this->phpSheet->getSheetView()->setZoomScale($zoomScale);
4412 18
                $this->phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView);
4413
            }
4414
        }
4415 18
    }
4416
4417
    /**
4418
     * Read PLV Record(Created by Excel2007 or upper).
4419
     */
4420 4
    private function readPageLayoutView()
4421
    {
4422 4
        $length = self::getUInt2d($this->data, $this->pos + 2);
4423 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4424
4425
        // move stream pointer to next record
4426 4
        $this->pos += 4 + $length;
4427
4428
        // offset: 0; size: 2; rt
4429
        //->ignore
4430 4
        $rt = self::getUInt2d($recordData, 0);
4431
        // offset: 2; size: 2; grbitfr
4432
        //->ignore
4433 4
        $grbitFrt = self::getUInt2d($recordData, 2);
4434
        // offset: 4; size: 8; reserved
4435
        //->ignore
4436
4437
        // offset: 12; size 2; zoom scale
4438 4
        $wScalePLV = self::getUInt2d($recordData, 12);
4439
        // offset: 14; size 2; grbit
4440 4
        $grbit = self::getUInt2d($recordData, 14);
4441
4442
        // decomprise grbit
4443 4
        $fPageLayoutView = $grbit & 0x01;
4444 4
        $fRulerVisible = ($grbit >> 1) & 0x01; //no support
4445 4
        $fWhitespaceHidden = ($grbit >> 3) & 0x01; //no support
4446
4447 4
        if ($fPageLayoutView === 1) {
4448
            $this->phpSheet->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_LAYOUT);
4449
            $this->phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT
4450
        }
4451
        //otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW.
4452 4
    }
4453
4454
    /**
4455
     * Read SCL record.
4456
     */
4457
    private function readScl()
4458
    {
4459
        $length = self::getUInt2d($this->data, $this->pos + 2);
4460
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4461
4462
        // move stream pointer to next record
4463
        $this->pos += 4 + $length;
4464
4465
        // offset: 0; size: 2; numerator of the view magnification
4466
        $numerator = self::getUInt2d($recordData, 0);
4467
4468
        // offset: 2; size: 2; numerator of the view magnification
4469
        $denumerator = self::getUInt2d($recordData, 2);
4470
4471
        // set the zoom scale (in percent)
4472
        $this->phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator);
4473
    }
4474
4475
    /**
4476
     * Read PANE record.
4477
     */
4478
    private function readPane()
4479
    {
4480
        $length = self::getUInt2d($this->data, $this->pos + 2);
4481
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4482
4483
        // move stream pointer to next record
4484
        $this->pos += 4 + $length;
4485
4486
        if (!$this->readDataOnly) {
4487
            // offset: 0; size: 2; position of vertical split
4488
            $px = self::getUInt2d($recordData, 0);
4489
4490
            // offset: 2; size: 2; position of horizontal split
4491
            $py = self::getUInt2d($recordData, 2);
4492
4493
            // offset: 4; size: 2; top most visible row in the bottom pane
4494
            $rwTop = self::getUInt2d($recordData, 4);
4495
4496
            // offset: 6; size: 2; first visible left column in the right pane
4497
            $colLeft = self::getUInt2d($recordData, 6);
4498
4499
            if ($this->frozen) {
4500
                // frozen panes
4501
                $cell = Coordinate::stringFromColumnIndex($px + 1) . ($py + 1);
4502
                $topLeftCell = Coordinate::stringFromColumnIndex($colLeft + 1) . ($rwTop + 1);
4503
                $this->phpSheet->freezePane($cell, $topLeftCell);
4504
            }
4505
            // unfrozen panes; split windows; not supported by PhpSpreadsheet core
4506
        }
4507
    }
4508
4509
    /**
4510
     * Read SELECTION record. There is one such record for each pane in the sheet.
4511
     */
4512 18
    private function readSelection()
4513
    {
4514 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
4515 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4516
4517
        // move stream pointer to next record
4518 18
        $this->pos += 4 + $length;
4519
4520 18
        if (!$this->readDataOnly) {
4521
            // offset: 0; size: 1; pane identifier
4522 17
            $paneId = ord($recordData[0]);
4523
4524
            // offset: 1; size: 2; index to row of the active cell
4525 17
            $r = self::getUInt2d($recordData, 1);
4526
4527
            // offset: 3; size: 2; index to column of the active cell
4528 17
            $c = self::getUInt2d($recordData, 3);
4529
4530
            // offset: 5; size: 2; index into the following cell range list to the
4531
            //  entry that contains the active cell
4532 17
            $index = self::getUInt2d($recordData, 5);
4533
4534
            // offset: 7; size: var; cell range address list containing all selected cell ranges
4535 17
            $data = substr($recordData, 7);
4536 17
            $cellRangeAddressList = $this->readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax
4537
4538 17
            $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0];
4539
4540
            // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!)
4541 17
            if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) {
4542
                $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells);
4543
            }
4544
4545
            // first row '1' + last row '65536' indicates that full column is selected
4546 17
            if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) {
4547
                $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells);
4548
            }
4549
4550
            // first column 'A' + last column 'IV' indicates that full row is selected
4551 17
            if (preg_match('/^(A\d+\:)IV(\d+)$/', $selectedCells)) {
4552 2
                $selectedCells = preg_replace('/^(A\d+\:)IV(\d+)$/', '${1}XFD${2}', $selectedCells);
4553
            }
4554
4555 17
            $this->phpSheet->setSelectedCells($selectedCells);
4556
        }
4557 18
    }
4558
4559 11
    private function includeCellRangeFiltered($cellRangeAddress)
4560
    {
4561 11
        $includeCellRange = true;
4562 11
        if ($this->getReadFilter() !== null) {
4563 11
            $includeCellRange = false;
4564 11
            $rangeBoundaries = Coordinate::getRangeBoundaries($cellRangeAddress);
4565 11
            ++$rangeBoundaries[1][0];
4566 11
            for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; ++$row) {
4567 11
                for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; ++$column) {
4568 11
                    if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
4569 11
                        $includeCellRange = true;
4570
4571 11
                        break 2;
4572
                    }
4573
                }
4574
            }
4575
        }
4576
4577 11
        return $includeCellRange;
4578
    }
4579
4580
    /**
4581
     * MERGEDCELLS.
4582
     *
4583
     * This record contains the addresses of merged cell ranges
4584
     * in the current sheet.
4585
     *
4586
     * --    "OpenOffice.org's Documentation of the Microsoft
4587
     *         Excel File Format"
4588
     */
4589 12
    private function readMergedCells()
4590
    {
4591 12
        $length = self::getUInt2d($this->data, $this->pos + 2);
4592 12
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4593
4594
        // move stream pointer to next record
4595 12
        $this->pos += 4 + $length;
4596
4597 12
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
4598 11
            $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData);
4599 11
            foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) {
4600 11
                if ((strpos($cellRangeAddress, ':') !== false) &&
4601 11
                    ($this->includeCellRangeFiltered($cellRangeAddress))) {
4602 11
                    $this->phpSheet->mergeCells($cellRangeAddress);
4603
                }
4604
            }
4605
        }
4606 12
    }
4607
4608
    /**
4609
     * Read HYPERLINK record.
4610
     */
4611 2
    private function readHyperLink()
4612
    {
4613 2
        $length = self::getUInt2d($this->data, $this->pos + 2);
4614 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4615
4616
        // move stream pointer forward to next record
4617 2
        $this->pos += 4 + $length;
4618
4619 2
        if (!$this->readDataOnly) {
4620
            // offset: 0; size: 8; cell range address of all cells containing this hyperlink
4621
            try {
4622 2
                $cellRange = $this->readBIFF8CellRangeAddressFixed($recordData);
4623
            } catch (PhpSpreadsheetException $e) {
4624
                return;
4625
            }
4626
4627
            // offset: 8, size: 16; GUID of StdLink
4628
4629
            // offset: 24, size: 4; unknown value
4630
4631
            // offset: 28, size: 4; option flags
4632
            // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL
4633 2
            $isFileLinkOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 0;
4634
4635
            // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL
4636 2
            $isAbsPathOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 1;
4637
4638
            // 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...
4639 2
            $hasDesc = (0x00000014 & self::getUInt2d($recordData, 28)) >> 2;
4640
4641
            // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text
4642 2
            $hasText = (0x00000008 & self::getUInt2d($recordData, 28)) >> 3;
4643
4644
            // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame
4645 2
            $hasFrame = (0x00000080 & self::getUInt2d($recordData, 28)) >> 7;
4646
4647
            // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name)
4648 2
            $isUNC = (0x00000100 & self::getUInt2d($recordData, 28)) >> 8;
4649
4650
            // offset within record data
4651 2
            $offset = 32;
4652
4653 2
            if ($hasDesc) {
4654
                // offset: 32; size: var; character count of description text
4655 1
                $dl = self::getInt4d($recordData, 32);
4656
                // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated
4657 1
                $desc = self::encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false);
4658 1
                $offset += 4 + 2 * $dl;
4659
            }
4660 2
            if ($hasFrame) {
4661
                $fl = self::getInt4d($recordData, $offset);
4662
                $offset += 4 + 2 * $fl;
4663
            }
4664
4665
            // detect type of hyperlink (there are 4 types)
4666 2
            $hyperlinkType = null;
4667
4668 2
            if ($isUNC) {
4669
                $hyperlinkType = 'UNC';
4670 2
            } elseif (!$isFileLinkOrUrl) {
4671 2
                $hyperlinkType = 'workbook';
4672 2
            } elseif (ord($recordData[$offset]) == 0x03) {
4673
                $hyperlinkType = 'local';
4674 2
            } elseif (ord($recordData[$offset]) == 0xE0) {
4675 2
                $hyperlinkType = 'URL';
4676
            }
4677
4678
            switch ($hyperlinkType) {
4679 2
                case 'URL':
4680
                    // section 5.58.2: Hyperlink containing a URL
4681
                    // e.g. http://example.org/index.php
4682
4683
                    // offset: var; size: 16; GUID of URL Moniker
4684 2
                    $offset += 16;
4685
                    // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word
4686 2
                    $us = self::getInt4d($recordData, $offset);
4687 2
                    $offset += 4;
4688
                    // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated
4689 2
                    $url = self::encodeUTF16(substr($recordData, $offset, $us - 2), false);
4690 2
                    $nullOffset = strpos($url, 0x00);
4691 2
                    if ($nullOffset) {
4692 1
                        $url = substr($url, 0, $nullOffset);
4693
                    }
4694 2
                    $url .= $hasText ? '#' : '';
4695 2
                    $offset += $us;
4696
4697 2
                    break;
4698 2
                case 'local':
4699
                    // section 5.58.3: Hyperlink to local file
4700
                    // examples:
4701
                    //   mydoc.txt
4702
                    //   ../../somedoc.xls#Sheet!A1
4703
4704
                    // offset: var; size: 16; GUI of File Moniker
4705
                    $offset += 16;
4706
4707
                    // offset: var; size: 2; directory up-level count.
4708
                    $upLevelCount = self::getUInt2d($recordData, $offset);
4709
                    $offset += 2;
4710
4711
                    // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word
4712
                    $sl = self::getInt4d($recordData, $offset);
4713
                    $offset += 4;
4714
4715
                    // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string)
4716
                    $shortenedFilePath = substr($recordData, $offset, $sl);
4717
                    $shortenedFilePath = self::encodeUTF16($shortenedFilePath, true);
4718
                    $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero
4719
4720
                    $offset += $sl;
4721
4722
                    // offset: var; size: 24; unknown sequence
4723
                    $offset += 24;
4724
4725
                    // extended file path
4726
                    // offset: var; size: 4; size of the following file link field including string lenth mark
4727
                    $sz = self::getInt4d($recordData, $offset);
4728
                    $offset += 4;
4729
4730
                    // only present if $sz > 0
4731
                    if ($sz > 0) {
4732
                        // offset: var; size: 4; size of the character array of the extended file path and name
4733
                        $xl = self::getInt4d($recordData, $offset);
4734
                        $offset += 4;
4735
4736
                        // offset: var; size 2; unknown
4737
                        $offset += 2;
4738
4739
                        // offset: var; size $xl; character array of the extended file path and name.
4740
                        $extendedFilePath = substr($recordData, $offset, $xl);
4741
                        $extendedFilePath = self::encodeUTF16($extendedFilePath, false);
4742
                        $offset += $xl;
4743
                    }
4744
4745
                    // construct the path
4746
                    $url = str_repeat('..\\', $upLevelCount);
4747
                    $url .= ($sz > 0) ? $extendedFilePath : $shortenedFilePath; // use extended path if available
4748
                    $url .= $hasText ? '#' : '';
4749
4750
                    break;
4751 2
                case 'UNC':
4752
                    // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path
4753
                    // todo: implement
4754
                    return;
4755 2
                case 'workbook':
4756
                    // section 5.58.5: Hyperlink to the Current Workbook
4757
                    // e.g. Sheet2!B1:C2, stored in text mark field
4758 2
                    $url = 'sheet://';
4759
4760 2
                    break;
4761
                default:
4762
                    return;
4763
            }
4764
4765 2
            if ($hasText) {
4766
                // offset: var; size: 4; character count of text mark including trailing zero word
4767 2
                $tl = self::getInt4d($recordData, $offset);
4768 2
                $offset += 4;
4769
                // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated
4770 2
                $text = self::encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false);
4771 2
                $url .= $text;
4772
            }
4773
4774
            // apply the hyperlink to all the relevant cells
4775 2
            foreach (Coordinate::extractAllCellReferencesInRange($cellRange) as $coordinate) {
4776 2
                $this->phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url);
4777
            }
4778
        }
4779 2
    }
4780
4781
    /**
4782
     * Read DATAVALIDATIONS record.
4783
     */
4784
    private function readDataValidations()
4785
    {
4786
        $length = self::getUInt2d($this->data, $this->pos + 2);
4787
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4788
4789
        // move stream pointer forward to next record
4790
        $this->pos += 4 + $length;
4791
    }
4792
4793
    /**
4794
     * Read DATAVALIDATION record.
4795
     */
4796
    private function readDataValidation()
4797
    {
4798
        $length = self::getUInt2d($this->data, $this->pos + 2);
4799
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4800
4801
        // move stream pointer forward to next record
4802
        $this->pos += 4 + $length;
4803
4804
        if ($this->readDataOnly) {
4805
            return;
4806
        }
4807
4808
        // offset: 0; size: 4; Options
4809
        $options = self::getInt4d($recordData, 0);
4810
4811
        // 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...
4812
        $type = (0x0000000F & $options) >> 0;
4813 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...
4814
            case 0x00:
4815
                $type = DataValidation::TYPE_NONE;
4816
4817
                break;
4818
            case 0x01:
4819
                $type = DataValidation::TYPE_WHOLE;
4820
4821
                break;
4822
            case 0x02:
4823
                $type = DataValidation::TYPE_DECIMAL;
4824
4825
                break;
4826
            case 0x03:
4827
                $type = DataValidation::TYPE_LIST;
4828
4829
                break;
4830
            case 0x04:
4831
                $type = DataValidation::TYPE_DATE;
4832
4833
                break;
4834
            case 0x05:
4835
                $type = DataValidation::TYPE_TIME;
4836
4837
                break;
4838
            case 0x06:
4839
                $type = DataValidation::TYPE_TEXTLENGTH;
4840
4841
                break;
4842
            case 0x07:
4843
                $type = DataValidation::TYPE_CUSTOM;
4844
4845
                break;
4846
        }
4847
4848
        // bit: 4-6; mask: 0x00000070; error type
4849
        $errorStyle = (0x00000070 & $options) >> 4;
4850 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...
4851
            case 0x00:
4852
                $errorStyle = DataValidation::STYLE_STOP;
4853
4854
                break;
4855
            case 0x01:
4856
                $errorStyle = DataValidation::STYLE_WARNING;
4857
4858
                break;
4859
            case 0x02:
4860
                $errorStyle = DataValidation::STYLE_INFORMATION;
4861
4862
                break;
4863
        }
4864
4865
        // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)
4866
        // I have only seen cases where this is 1
4867
        $explicitFormula = (0x00000080 & $options) >> 7;
4868
4869
        // bit: 8; mask: 0x00000100; 1= empty cells allowed
4870
        $allowBlank = (0x00000100 & $options) >> 8;
4871
4872
        // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity
4873
        $suppressDropDown = (0x00000200 & $options) >> 9;
4874
4875
        // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected
4876
        $showInputMessage = (0x00040000 & $options) >> 18;
4877
4878
        // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered
4879
        $showErrorMessage = (0x00080000 & $options) >> 19;
4880
4881
        // bit: 20-23; mask: 0x00F00000; condition operator
4882
        $operator = (0x00F00000 & $options) >> 20;
4883 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...
4884
            case 0x00:
4885
                $operator = DataValidation::OPERATOR_BETWEEN;
4886
4887
                break;
4888
            case 0x01:
4889
                $operator = DataValidation::OPERATOR_NOTBETWEEN;
4890
4891
                break;
4892
            case 0x02:
4893
                $operator = DataValidation::OPERATOR_EQUAL;
4894
4895
                break;
4896
            case 0x03:
4897
                $operator = DataValidation::OPERATOR_NOTEQUAL;
4898
4899
                break;
4900
            case 0x04:
4901
                $operator = DataValidation::OPERATOR_GREATERTHAN;
4902
4903
                break;
4904
            case 0x05:
4905
                $operator = DataValidation::OPERATOR_LESSTHAN;
4906
4907
                break;
4908
            case 0x06:
4909
                $operator = DataValidation::OPERATOR_GREATERTHANOREQUAL;
4910
4911
                break;
4912
            case 0x07:
4913
                $operator = DataValidation::OPERATOR_LESSTHANOREQUAL;
4914
4915
                break;
4916
        }
4917
4918
        // offset: 4; size: var; title of the prompt box
4919
        $offset = 4;
4920
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4921
        $promptTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4922
        $offset += $string['size'];
4923
4924
        // offset: var; size: var; title of the error box
4925
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4926
        $errorTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4927
        $offset += $string['size'];
4928
4929
        // offset: var; size: var; text of the prompt box
4930
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4931
        $prompt = $string['value'] !== chr(0) ? $string['value'] : '';
4932
        $offset += $string['size'];
4933
4934
        // offset: var; size: var; text of the error box
4935
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4936
        $error = $string['value'] !== chr(0) ? $string['value'] : '';
4937
        $offset += $string['size'];
4938
4939
        // offset: var; size: 2; size of the formula data for the first condition
4940
        $sz1 = self::getUInt2d($recordData, $offset);
4941
        $offset += 2;
4942
4943
        // offset: var; size: 2; not used
4944
        $offset += 2;
4945
4946
        // offset: var; size: $sz1; formula data for first condition (without size field)
4947
        $formula1 = substr($recordData, $offset, $sz1);
4948
        $formula1 = pack('v', $sz1) . $formula1; // prepend the length
4949
        try {
4950
            $formula1 = $this->getFormulaFromStructure($formula1);
4951
4952
            // in list type validity, null characters are used as item separators
4953
            if ($type == DataValidation::TYPE_LIST) {
4954
                $formula1 = str_replace(chr(0), ',', $formula1);
4955
            }
4956
        } catch (PhpSpreadsheetException $e) {
4957
            return;
4958
        }
4959
        $offset += $sz1;
4960
4961
        // offset: var; size: 2; size of the formula data for the first condition
4962
        $sz2 = self::getUInt2d($recordData, $offset);
4963
        $offset += 2;
4964
4965
        // offset: var; size: 2; not used
4966
        $offset += 2;
4967
4968
        // offset: var; size: $sz2; formula data for second condition (without size field)
4969
        $formula2 = substr($recordData, $offset, $sz2);
4970
        $formula2 = pack('v', $sz2) . $formula2; // prepend the length
4971
        try {
4972
            $formula2 = $this->getFormulaFromStructure($formula2);
4973
        } catch (PhpSpreadsheetException $e) {
4974
            return;
4975
        }
4976
        $offset += $sz2;
4977
4978
        // offset: var; size: var; cell range address list with
4979
        $cellRangeAddressList = $this->readBIFF8CellRangeAddressList(substr($recordData, $offset));
4980
        $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
4981
4982
        foreach ($cellRangeAddresses as $cellRange) {
4983
            $stRange = $this->phpSheet->shrinkRangeToFit($cellRange);
4984
            foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $coordinate) {
4985
                $objValidation = $this->phpSheet->getCell($coordinate)->getDataValidation();
4986
                $objValidation->setType($type);
4987
                $objValidation->setErrorStyle($errorStyle);
4988
                $objValidation->setAllowBlank((bool) $allowBlank);
4989
                $objValidation->setShowInputMessage((bool) $showInputMessage);
4990
                $objValidation->setShowErrorMessage((bool) $showErrorMessage);
4991
                $objValidation->setShowDropDown(!$suppressDropDown);
4992
                $objValidation->setOperator($operator);
4993
                $objValidation->setErrorTitle($errorTitle);
4994
                $objValidation->setError($error);
4995
                $objValidation->setPromptTitle($promptTitle);
4996
                $objValidation->setPrompt($prompt);
4997
                $objValidation->setFormula1($formula1);
4998
                $objValidation->setFormula2($formula2);
4999
            }
5000
        }
5001
    }
5002
5003
    /**
5004
     * Read SHEETLAYOUT record. Stores sheet tab color information.
5005
     */
5006 2
    private function readSheetLayout()
5007
    {
5008 2
        $length = self::getUInt2d($this->data, $this->pos + 2);
5009 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5010
5011
        // move stream pointer to next record
5012 2
        $this->pos += 4 + $length;
5013
5014
        // local pointer in record data
5015 2
        $offset = 0;
5016
5017 2
        if (!$this->readDataOnly) {
5018
            // offset: 0; size: 2; repeated record identifier 0x0862
5019
5020
            // offset: 2; size: 10; not used
5021
5022
            // offset: 12; size: 4; size of record data
5023
            // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?)
5024 2
            $sz = self::getInt4d($recordData, 12);
5025
5026
            switch ($sz) {
5027 2
                case 0x14:
5028
                    // offset: 16; size: 2; color index for sheet tab
5029 1
                    $colorIndex = self::getUInt2d($recordData, 16);
5030 1
                    $color = Xls\Color::map($colorIndex, $this->palette, $this->version);
5031 1
                    $this->phpSheet->getTabColor()->setRGB($color['rgb']);
5032
5033 1
                    break;
5034 1
                case 0x28:
5035
                    // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007
5036 1
                    return;
5037
                    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...
5038
            }
5039
        }
5040 1
    }
5041
5042
    /**
5043
     * Read SHEETPROTECTION record (FEATHEADR).
5044
     */
5045 5
    private function readSheetProtection()
5046
    {
5047 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
5048 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5049
5050
        // move stream pointer to next record
5051 5
        $this->pos += 4 + $length;
5052
5053 5
        if ($this->readDataOnly) {
5054
            return;
5055
        }
5056
5057
        // offset: 0; size: 2; repeated record header
5058
5059
        // offset: 2; size: 2; FRT cell reference flag (=0 currently)
5060
5061
        // offset: 4; size: 8; Currently not used and set to 0
5062
5063
        // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag)
5064 5
        $isf = self::getUInt2d($recordData, 12);
5065 5
        if ($isf != 2) {
5066
            return;
5067
        }
5068
5069
        // offset: 14; size: 1; =1 since this is a feat header
5070
5071
        // offset: 15; size: 4; size of rgbHdrSData
5072
5073
        // rgbHdrSData, assume "Enhanced Protection"
5074
        // offset: 19; size: 2; option flags
5075 5
        $options = self::getUInt2d($recordData, 19);
5076
5077
        // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects
5078 5
        $bool = (0x0001 & $options) >> 0;
5079 5
        $this->phpSheet->getProtection()->setObjects(!$bool);
5080
5081
        // bit: 1; mask 0x0002; edit scenarios
5082 5
        $bool = (0x0002 & $options) >> 1;
5083 5
        $this->phpSheet->getProtection()->setScenarios(!$bool);
5084
5085
        // bit: 2; mask 0x0004; format cells
5086 5
        $bool = (0x0004 & $options) >> 2;
5087 5
        $this->phpSheet->getProtection()->setFormatCells(!$bool);
5088
5089
        // bit: 3; mask 0x0008; format columns
5090 5
        $bool = (0x0008 & $options) >> 3;
5091 5
        $this->phpSheet->getProtection()->setFormatColumns(!$bool);
5092
5093
        // bit: 4; mask 0x0010; format rows
5094 5
        $bool = (0x0010 & $options) >> 4;
5095 5
        $this->phpSheet->getProtection()->setFormatRows(!$bool);
5096
5097
        // bit: 5; mask 0x0020; insert columns
5098 5
        $bool = (0x0020 & $options) >> 5;
5099 5
        $this->phpSheet->getProtection()->setInsertColumns(!$bool);
5100
5101
        // bit: 6; mask 0x0040; insert rows
5102 5
        $bool = (0x0040 & $options) >> 6;
5103 5
        $this->phpSheet->getProtection()->setInsertRows(!$bool);
5104
5105
        // bit: 7; mask 0x0080; insert hyperlinks
5106 5
        $bool = (0x0080 & $options) >> 7;
5107 5
        $this->phpSheet->getProtection()->setInsertHyperlinks(!$bool);
5108
5109
        // bit: 8; mask 0x0100; delete columns
5110 5
        $bool = (0x0100 & $options) >> 8;
5111 5
        $this->phpSheet->getProtection()->setDeleteColumns(!$bool);
5112
5113
        // bit: 9; mask 0x0200; delete rows
5114 5
        $bool = (0x0200 & $options) >> 9;
5115 5
        $this->phpSheet->getProtection()->setDeleteRows(!$bool);
5116
5117
        // bit: 10; mask 0x0400; select locked cells
5118 5
        $bool = (0x0400 & $options) >> 10;
5119 5
        $this->phpSheet->getProtection()->setSelectLockedCells(!$bool);
5120
5121
        // bit: 11; mask 0x0800; sort cell range
5122 5
        $bool = (0x0800 & $options) >> 11;
5123 5
        $this->phpSheet->getProtection()->setSort(!$bool);
5124
5125
        // bit: 12; mask 0x1000; auto filter
5126 5
        $bool = (0x1000 & $options) >> 12;
5127 5
        $this->phpSheet->getProtection()->setAutoFilter(!$bool);
5128
5129
        // bit: 13; mask 0x2000; pivot tables
5130 5
        $bool = (0x2000 & $options) >> 13;
5131 5
        $this->phpSheet->getProtection()->setPivotTables(!$bool);
5132
5133
        // bit: 14; mask 0x4000; select unlocked cells
5134 5
        $bool = (0x4000 & $options) >> 14;
5135 5
        $this->phpSheet->getProtection()->setSelectUnlockedCells(!$bool);
5136
5137
        // offset: 21; size: 2; not used
5138 5
    }
5139
5140
    /**
5141
     * Read RANGEPROTECTION record
5142
     * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification,
5143
     * where it is referred to as FEAT record.
5144
     */
5145 1
    private function readRangeProtection()
5146
    {
5147 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
5148 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5149
5150
        // move stream pointer to next record
5151 1
        $this->pos += 4 + $length;
5152
5153
        // local pointer in record data
5154 1
        $offset = 0;
5155
5156 1
        if (!$this->readDataOnly) {
5157 1
            $offset += 12;
5158
5159
            // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag
5160 1
            $isf = self::getUInt2d($recordData, 12);
5161 1
            if ($isf != 2) {
5162
                // we only read FEAT records of type 2
5163
                return;
5164
            }
5165 1
            $offset += 2;
5166
5167 1
            $offset += 5;
5168
5169
            // offset: 19; size: 2; count of ref ranges this feature is on
5170 1
            $cref = self::getUInt2d($recordData, 19);
5171 1
            $offset += 2;
5172
5173 1
            $offset += 6;
5174
5175
            // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record)
5176 1
            $cellRanges = [];
5177 1
            for ($i = 0; $i < $cref; ++$i) {
5178
                try {
5179 1
                    $cellRange = $this->readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8));
5180
                } catch (PhpSpreadsheetException $e) {
5181
                    return;
5182
                }
5183 1
                $cellRanges[] = $cellRange;
5184 1
                $offset += 8;
5185
            }
5186
5187
            // offset: var; size: var; variable length of feature specific data
5188 1
            $rgbFeat = substr($recordData, $offset);
5189 1
            $offset += 4;
5190
5191
            // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit)
5192 1
            $wPassword = self::getInt4d($recordData, $offset);
5193 1
            $offset += 4;
5194
5195
            // Apply range protection to sheet
5196 1
            if ($cellRanges) {
5197 1
                $this->phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true);
5198
            }
5199
        }
5200 1
    }
5201
5202
    /**
5203
     * Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record
5204
     * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented.
5205
     * In this case, we must treat the CONTINUE record as a MSODRAWING record.
5206
     */
5207
    private function readContinue()
5208
    {
5209
        $length = self::getUInt2d($this->data, $this->pos + 2);
5210
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5211
5212
        // check if we are reading drawing data
5213
        // this is in case a free CONTINUE record occurs in other circumstances we are unaware of
5214
        if ($this->drawingData == '') {
5215
            // move stream pointer to next record
5216
            $this->pos += 4 + $length;
5217
5218
            return;
5219
        }
5220
5221
        // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data
5222
        if ($length < 4) {
5223
            // move stream pointer to next record
5224
            $this->pos += 4 + $length;
5225
5226
            return;
5227
        }
5228
5229
        // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record
5230
        // look inside CONTINUE record to see if it looks like a part of an Escher stream
5231
        // we know that Escher stream may be split at least at
5232
        //        0xF003 MsofbtSpgrContainer
5233
        //        0xF004 MsofbtSpContainer
5234
        //        0xF00D MsofbtClientTextbox
5235
        $validSplitPoints = [0xF003, 0xF004, 0xF00D]; // add identifiers if we find more
5236
5237
        $splitPoint = self::getUInt2d($recordData, 2);
5238
        if (in_array($splitPoint, $validSplitPoints)) {
5239
            // get spliced record data (and move pointer to next record)
5240
            $splicedRecordData = $this->getSplicedRecordData();
5241
            $this->drawingData .= $splicedRecordData['recordData'];
5242
5243
            return;
5244
        }
5245
5246
        // move stream pointer to next record
5247
        $this->pos += 4 + $length;
5248
    }
5249
5250
    /**
5251
     * Reads a record from current position in data stream and continues reading data as long as CONTINUE
5252
     * records are found. Splices the record data pieces and returns the combined string as if record data
5253
     * is in one piece.
5254
     * Moves to next current position in data stream to start of next record different from a CONtINUE record.
5255
     *
5256
     * @return array
5257
     */
5258 18
    private function getSplicedRecordData()
5259
    {
5260 18
        $data = '';
5261 18
        $spliceOffsets = [];
5262
5263 18
        $i = 0;
5264 18
        $spliceOffsets[0] = 0;
5265
5266
        do {
5267 18
            ++$i;
5268
5269
            // offset: 0; size: 2; identifier
5270 18
            $identifier = self::getUInt2d($this->data, $this->pos);
5271
            // offset: 2; size: 2; length
5272 18
            $length = self::getUInt2d($this->data, $this->pos + 2);
5273 18
            $data .= $this->readRecordData($this->data, $this->pos + 4, $length);
5274
5275 18
            $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length;
5276
5277 18
            $this->pos += 4 + $length;
5278 18
            $nextIdentifier = self::getUInt2d($this->data, $this->pos);
5279 18
        } while ($nextIdentifier == self::XLS_TYPE_CONTINUE);
5280
5281
        $splicedData = [
5282 18
            'recordData' => $data,
5283 18
            'spliceOffsets' => $spliceOffsets,
5284
        ];
5285
5286 18
        return $splicedData;
5287
    }
5288
5289
    /**
5290
     * Convert formula structure into human readable Excel formula like 'A3+A5*5'.
5291
     *
5292
     * @param string $formulaStructure The complete binary data for the formula
5293
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5294
     *
5295
     * @return string Human readable formula
5296
     */
5297 10
    private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1')
5298
    {
5299
        // offset: 0; size: 2; size of the following formula data
5300 10
        $sz = self::getUInt2d($formulaStructure, 0);
5301
5302
        // offset: 2; size: sz
5303 10
        $formulaData = substr($formulaStructure, 2, $sz);
5304
5305
        // offset: 2 + sz; size: variable (optional)
5306 10
        if (strlen($formulaStructure) > 2 + $sz) {
5307
            $additionalData = substr($formulaStructure, 2 + $sz);
5308
        } else {
5309 10
            $additionalData = '';
5310
        }
5311
5312 10
        return $this->getFormulaFromData($formulaData, $additionalData, $baseCell);
5313
    }
5314
5315
    /**
5316
     * Take formula data and additional data for formula and return human readable formula.
5317
     *
5318
     * @param string $formulaData The binary data for the formula itself
5319
     * @param string $additionalData Additional binary data going with the formula
5320
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5321
     *
5322
     * @return string Human readable formula
5323
     */
5324 10
    private function getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1')
5325
    {
5326
        // start parsing the formula data
5327 10
        $tokens = [];
5328
5329 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...
5330 10
            $tokens[] = $token;
5331 10
            $formulaData = substr($formulaData, $token['size']);
5332
        }
5333
5334 10
        $formulaString = $this->createFormulaFromTokens($tokens, $additionalData);
5335
5336 10
        return $formulaString;
5337
    }
5338
5339
    /**
5340
     * Take array of tokens together with additional data for formula and return human readable formula.
5341
     *
5342
     * @param array $tokens
5343
     * @param string $additionalData Additional binary data going with the formula
5344
     *
5345
     * @return string Human readable formula
5346
     */
5347 10
    private function createFormulaFromTokens($tokens, $additionalData)
5348
    {
5349
        // empty formula?
5350 10
        if (empty($tokens)) {
5351
            return '';
5352
        }
5353
5354 10
        $formulaStrings = [];
5355 10
        foreach ($tokens as $token) {
5356
            // initialize spaces
5357 10
            $space0 = isset($space0) ? $space0 : ''; // spaces before next token, not tParen
5358 10
            $space1 = isset($space1) ? $space1 : ''; // carriage returns before next token, not tParen
5359 10
            $space2 = isset($space2) ? $space2 : ''; // spaces before opening parenthesis
5360 10
            $space3 = isset($space3) ? $space3 : ''; // carriage returns before opening parenthesis
5361 10
            $space4 = isset($space4) ? $space4 : ''; // spaces before closing parenthesis
5362 10
            $space5 = isset($space5) ? $space5 : ''; // carriage returns before closing parenthesis
5363
5364 10
            switch ($token['name']) {
5365 10
                case 'tAdd': // addition
5366 10
                case 'tConcat': // addition
5367 10
                case 'tDiv': // division
5368 10
                case 'tEQ': // equality
5369 10
                case 'tGE': // greater than or equal
5370 10
                case 'tGT': // greater than
5371 10
                case 'tIsect': // intersection
5372 10
                case 'tLE': // less than or equal
5373 10
                case 'tList': // less than or equal
5374 10
                case 'tLT': // less than
5375 10
                case 'tMul': // multiplication
5376 10
                case 'tNE': // multiplication
5377 10
                case 'tPower': // power
5378 10
                case 'tRange': // range
5379 10
                case 'tSub': // subtraction
5380 10
                    $op2 = array_pop($formulaStrings);
5381 10
                    $op1 = array_pop($formulaStrings);
5382 10
                    $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2";
5383 10
                    unset($space0, $space1);
5384
5385 10
                    break;
5386 10
                case 'tUplus': // unary plus
5387 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...
5388
                    $op = array_pop($formulaStrings);
5389
                    $formulaStrings[] = "$space1$space0{$token['data']}$op";
5390
                    unset($space0, $space1);
5391
5392
                    break;
5393 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...
5394
                    $op = array_pop($formulaStrings);
5395
                    $formulaStrings[] = "$op$space1$space0{$token['data']}";
5396
                    unset($space0, $space1);
5397
5398
                    break;
5399 10
                case 'tAttrVolatile': // indicates volatile function
5400 10
                case 'tAttrIf':
5401 10
                case 'tAttrSkip':
5402 10
                case 'tAttrChoose':
5403
                    // token is only important for Excel formula evaluator
5404
                    // do nothing
5405
                    break;
5406 10
                case 'tAttrSpace': // space / carriage return
5407
                    // space will be used when next token arrives, do not alter formulaString stack
5408
                    switch ($token['data']['spacetype']) {
5409
                        case 'type0':
5410
                            $space0 = str_repeat(' ', $token['data']['spacecount']);
5411
5412
                            break;
5413
                        case 'type1':
5414
                            $space1 = str_repeat("\n", $token['data']['spacecount']);
5415
5416
                            break;
5417
                        case 'type2':
5418
                            $space2 = str_repeat(' ', $token['data']['spacecount']);
5419
5420
                            break;
5421
                        case 'type3':
5422
                            $space3 = str_repeat("\n", $token['data']['spacecount']);
5423
5424
                            break;
5425
                        case 'type4':
5426
                            $space4 = str_repeat(' ', $token['data']['spacecount']);
5427
5428
                            break;
5429
                        case 'type5':
5430
                            $space5 = str_repeat("\n", $token['data']['spacecount']);
5431
5432
                            break;
5433
                    }
5434
5435
                    break;
5436 10
                case 'tAttrSum': // SUM function with one parameter
5437 9
                    $op = array_pop($formulaStrings);
5438 9
                    $formulaStrings[] = "{$space1}{$space0}SUM($op)";
5439 9
                    unset($space0, $space1);
5440
5441 9
                    break;
5442 10
                case 'tFunc': // function with fixed number of arguments
5443 10
                case 'tFuncV': // function with variable number of arguments
5444 9
                    if ($token['data']['function'] != '') {
5445
                        // normal function
5446 9
                        $ops = []; // array of operators
5447 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...
5448 1
                            $ops[] = array_pop($formulaStrings);
5449
                        }
5450 9
                        $ops = array_reverse($ops);
5451 9
                        $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ')';
5452 9
                        unset($space0, $space1);
5453
                    } else {
5454
                        // add-in function
5455
                        $ops = []; // array of operators
5456 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...
5457
                            $ops[] = array_pop($formulaStrings);
5458
                        }
5459
                        $ops = array_reverse($ops);
5460
                        $function = array_pop($formulaStrings);
5461
                        $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ')';
5462
                        unset($space0, $space1);
5463
                    }
5464
5465 9
                    break;
5466 10
                case 'tParen': // parenthesis
5467
                    $expression = array_pop($formulaStrings);
5468
                    $formulaStrings[] = "$space3$space2($expression$space5$space4)";
5469
                    unset($space2, $space3, $space4, $space5);
5470
5471
                    break;
5472 10
                case 'tArray': // array constant
5473
                    $constantArray = self::readBIFF8ConstantArray($additionalData);
5474
                    $formulaStrings[] = $space1 . $space0 . $constantArray['value'];
5475
                    $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data
5476
                    unset($space0, $space1);
5477
5478
                    break;
5479 10
                case 'tMemArea':
5480
                    // bite off chunk of additional data
5481
                    $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($additionalData);
5482
                    $additionalData = substr($additionalData, $cellRangeAddressList['size']);
5483
                    $formulaStrings[] = "$space1$space0{$token['data']}";
5484
                    unset($space0, $space1);
5485
5486
                    break;
5487 10
                case 'tArea': // cell range address
5488 10
                case 'tBool': // boolean
5489 10
                case 'tErr': // error code
5490 10
                case 'tInt': // integer
5491 2
                case 'tMemErr':
5492 2
                case 'tMemFunc':
5493 2
                case 'tMissArg':
5494 2
                case 'tName':
5495 2
                case 'tNameX':
5496 2
                case 'tNum': // number
5497 2
                case 'tRef': // single cell reference
5498 2
                case 'tRef3d': // 3d cell reference
5499 2
                case 'tArea3d': // 3d cell range reference
5500 1
                case 'tRefN':
5501 1
                case 'tAreaN':
5502 1
                case 'tStr': // string
5503 10
                    $formulaStrings[] = "$space1$space0{$token['data']}";
5504 10
                    unset($space0, $space1);
5505
5506 10
                    break;
5507
            }
5508
        }
5509 10
        $formulaString = $formulaStrings[0];
5510
5511 10
        return $formulaString;
5512
    }
5513
5514
    /**
5515
     * Fetch next token from binary formula data.
5516
     *
5517
     * @param string $formulaData Formula data
5518
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5519
     *
5520
     * @throws Exception
5521
     *
5522
     * @return array
5523
     */
5524 10
    private function getNextToken($formulaData, $baseCell = 'A1')
5525
    {
5526
        // offset: 0; size: 1; token id
5527 10
        $id = ord($formulaData[0]); // token id
5528 10
        $name = false; // initialize token name
5529
5530
        switch ($id) {
5531 10
            case 0x03:
5532 1
                $name = 'tAdd';
5533 1
                $size = 1;
5534 1
                $data = '+';
5535
5536 1
                break;
5537 10
            case 0x04:
5538
                $name = 'tSub';
5539
                $size = 1;
5540
                $data = '-';
5541
5542
                break;
5543 10
            case 0x05:
5544 2
                $name = 'tMul';
5545 2
                $size = 1;
5546 2
                $data = '*';
5547
5548 2
                break;
5549 10
            case 0x06:
5550 8
                $name = 'tDiv';
5551 8
                $size = 1;
5552 8
                $data = '/';
5553
5554 8
                break;
5555 10
            case 0x07:
5556
                $name = 'tPower';
5557
                $size = 1;
5558
                $data = '^';
5559
5560
                break;
5561 10
            case 0x08:
5562
                $name = 'tConcat';
5563
                $size = 1;
5564
                $data = '&';
5565
5566
                break;
5567 10
            case 0x09:
5568
                $name = 'tLT';
5569
                $size = 1;
5570
                $data = '<';
5571
5572
                break;
5573 10
            case 0x0A:
5574
                $name = 'tLE';
5575
                $size = 1;
5576
                $data = '<=';
5577
5578
                break;
5579 10
            case 0x0B:
5580
                $name = 'tEQ';
5581
                $size = 1;
5582
                $data = '=';
5583
5584
                break;
5585 10
            case 0x0C:
5586
                $name = 'tGE';
5587
                $size = 1;
5588
                $data = '>=';
5589
5590
                break;
5591 10
            case 0x0D:
5592
                $name = 'tGT';
5593
                $size = 1;
5594
                $data = '>';
5595
5596
                break;
5597 10
            case 0x0E:
5598 1
                $name = 'tNE';
5599 1
                $size = 1;
5600 1
                $data = '<>';
5601
5602 1
                break;
5603 10
            case 0x0F:
5604
                $name = 'tIsect';
5605
                $size = 1;
5606
                $data = ' ';
5607
5608
                break;
5609 10
            case 0x10:
5610
                $name = 'tList';
5611
                $size = 1;
5612
                $data = ',';
5613
5614
                break;
5615 10
            case 0x11:
5616
                $name = 'tRange';
5617
                $size = 1;
5618
                $data = ':';
5619
5620
                break;
5621 10
            case 0x12:
5622
                $name = 'tUplus';
5623
                $size = 1;
5624
                $data = '+';
5625
5626
                break;
5627 10
            case 0x13:
5628
                $name = 'tUminus';
5629
                $size = 1;
5630
                $data = '-';
5631
5632
                break;
5633 10
            case 0x14:
5634
                $name = 'tPercent';
5635
                $size = 1;
5636
                $data = '%';
5637
5638
                break;
5639 10
            case 0x15:    //    parenthesis
5640
                $name = 'tParen';
5641
                $size = 1;
5642
                $data = null;
5643
5644
                break;
5645 10
            case 0x16:    //    missing argument
5646
                $name = 'tMissArg';
5647
                $size = 1;
5648
                $data = '';
5649
5650
                break;
5651 10
            case 0x17:    //    string
5652 1
                $name = 'tStr';
5653
                // offset: 1; size: var; Unicode string, 8-bit string length
5654 1
                $string = self::readUnicodeStringShort(substr($formulaData, 1));
5655 1
                $size = 1 + $string['size'];
5656 1
                $data = self::UTF8toExcelDoubleQuoted($string['value']);
5657
5658 1
                break;
5659 10
            case 0x19:    //    Special attribute
5660
                // offset: 1; size: 1; attribute type flags:
5661 9
                switch (ord($formulaData[1])) {
5662 9
                    case 0x01:
5663
                        $name = 'tAttrVolatile';
5664
                        $size = 4;
5665
                        $data = null;
5666
5667
                        break;
5668 9
                    case 0x02:
5669
                        $name = 'tAttrIf';
5670
                        $size = 4;
5671
                        $data = null;
5672
5673
                        break;
5674 9
                    case 0x04:
5675
                        $name = 'tAttrChoose';
5676
                        // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1)
5677
                        $nc = self::getUInt2d($formulaData, 2);
5678
                        // offset: 4; size: 2 * $nc
5679
                        // offset: 4 + 2 * $nc; size: 2
5680
                        $size = 2 * $nc + 6;
5681
                        $data = null;
5682
5683
                        break;
5684 9
                    case 0x08:
5685
                        $name = 'tAttrSkip';
5686
                        $size = 4;
5687
                        $data = null;
5688
5689
                        break;
5690 9
                    case 0x10:
5691 9
                        $name = 'tAttrSum';
5692 9
                        $size = 4;
5693 9
                        $data = null;
5694
5695 9
                        break;
5696
                    case 0x40:
5697
                    case 0x41:
5698
                        $name = 'tAttrSpace';
5699
                        $size = 4;
5700
                        // offset: 2; size: 2; space type and position
5701
                        switch (ord($formulaData[2])) {
5702
                            case 0x00:
5703
                                $spacetype = 'type0';
5704
5705
                                break;
5706
                            case 0x01:
5707
                                $spacetype = 'type1';
5708
5709
                                break;
5710
                            case 0x02:
5711
                                $spacetype = 'type2';
5712
5713
                                break;
5714
                            case 0x03:
5715
                                $spacetype = 'type3';
5716
5717
                                break;
5718
                            case 0x04:
5719
                                $spacetype = 'type4';
5720
5721
                                break;
5722
                            case 0x05:
5723
                                $spacetype = 'type5';
5724
5725
                                break;
5726
                            default:
5727
                                throw new Exception('Unrecognized space type in tAttrSpace token');
5728
                                break;
5729
                        }
5730
                        // offset: 3; size: 1; number of inserted spaces/carriage returns
5731
                        $spacecount = ord($formulaData[3]);
5732
5733
                        $data = ['spacetype' => $spacetype, 'spacecount' => $spacecount];
5734
5735
                        break;
5736
                    default:
5737
                        throw new Exception('Unrecognized attribute flag in tAttr token');
5738
                        break;
5739
                }
5740
5741 9
                break;
5742 10
            case 0x1C:    //    error code
5743
                // offset: 1; size: 1; error code
5744
                $name = 'tErr';
5745
                $size = 2;
5746
                $data = Xls\ErrorCode::lookup(ord($formulaData[1]));
5747
5748
                break;
5749 10
            case 0x1D:    //    boolean
5750
                // 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...
5751
                $name = 'tBool';
5752
                $size = 2;
5753
                $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE';
5754
5755
                break;
5756 10
            case 0x1E:    //    integer
5757
                // offset: 1; size: 2; unsigned 16-bit integer
5758 8
                $name = 'tInt';
5759 8
                $size = 3;
5760 8
                $data = self::getUInt2d($formulaData, 1);
5761
5762 8
                break;
5763 10
            case 0x1F:    //    number
5764
                // 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...
5765 2
                $name = 'tNum';
5766 2
                $size = 9;
5767 2
                $data = self::extractNumber(substr($formulaData, 1));
5768 2
                $data = str_replace(',', '.', (string) $data); // in case non-English locale
5769 2
                break;
5770 10
            case 0x20:    //    array constant
5771 10
            case 0x40:
5772 10
            case 0x60:
5773
                // offset: 1; size: 7; not used
5774
                $name = 'tArray';
5775
                $size = 8;
5776
                $data = null;
5777
5778
                break;
5779 10
            case 0x21:    //    function with fixed number of arguments
5780 10
            case 0x41:
5781 10
            case 0x61:
5782 8
                $name = 'tFunc';
5783 8
                $size = 3;
5784
                // offset: 1; size: 2; index to built-in sheet function
5785 8
                switch (self::getUInt2d($formulaData, 1)) {
5786 8
                    case 2:
5787
                        $function = 'ISNA';
5788
                        $args = 1;
5789
5790
                        break;
5791 8
                    case 3:
5792
                        $function = 'ISERROR';
5793
                        $args = 1;
5794
5795
                        break;
5796 8
                    case 10:
5797 8
                        $function = 'NA';
5798 8
                        $args = 0;
5799
5800 8
                        break;
5801
                    case 15:
5802
                        $function = 'SIN';
5803
                        $args = 1;
5804
5805
                        break;
5806
                    case 16:
5807
                        $function = 'COS';
5808
                        $args = 1;
5809
5810
                        break;
5811
                    case 17:
5812
                        $function = 'TAN';
5813
                        $args = 1;
5814
5815
                        break;
5816
                    case 18:
5817
                        $function = 'ATAN';
5818
                        $args = 1;
5819
5820
                        break;
5821
                    case 19:
5822
                        $function = 'PI';
5823
                        $args = 0;
5824
5825
                        break;
5826
                    case 20:
5827
                        $function = 'SQRT';
5828
                        $args = 1;
5829
5830
                        break;
5831
                    case 21:
5832
                        $function = 'EXP';
5833
                        $args = 1;
5834
5835
                        break;
5836
                    case 22:
5837
                        $function = 'LN';
5838
                        $args = 1;
5839
5840
                        break;
5841
                    case 23:
5842
                        $function = 'LOG10';
5843
                        $args = 1;
5844
5845
                        break;
5846
                    case 24:
5847
                        $function = 'ABS';
5848
                        $args = 1;
5849
5850
                        break;
5851
                    case 25:
5852
                        $function = 'INT';
5853
                        $args = 1;
5854
5855
                        break;
5856
                    case 26:
5857
                        $function = 'SIGN';
5858
                        $args = 1;
5859
5860
                        break;
5861
                    case 27:
5862
                        $function = 'ROUND';
5863
                        $args = 2;
5864
5865
                        break;
5866
                    case 30:
5867
                        $function = 'REPT';
5868
                        $args = 2;
5869
5870
                        break;
5871
                    case 31:
5872
                        $function = 'MID';
5873
                        $args = 3;
5874
5875
                        break;
5876
                    case 32:
5877
                        $function = 'LEN';
5878
                        $args = 1;
5879
5880
                        break;
5881
                    case 33:
5882
                        $function = 'VALUE';
5883
                        $args = 1;
5884
5885
                        break;
5886
                    case 34:
5887
                        $function = 'TRUE';
5888
                        $args = 0;
5889
5890
                        break;
5891
                    case 35:
5892
                        $function = 'FALSE';
5893
                        $args = 0;
5894
5895
                        break;
5896
                    case 38:
5897
                        $function = 'NOT';
5898
                        $args = 1;
5899
5900
                        break;
5901
                    case 39:
5902
                        $function = 'MOD';
5903
                        $args = 2;
5904
5905
                        break;
5906
                    case 40:
5907
                        $function = 'DCOUNT';
5908
                        $args = 3;
5909
5910
                        break;
5911
                    case 41:
5912
                        $function = 'DSUM';
5913
                        $args = 3;
5914
5915
                        break;
5916
                    case 42:
5917
                        $function = 'DAVERAGE';
5918
                        $args = 3;
5919
5920
                        break;
5921
                    case 43:
5922
                        $function = 'DMIN';
5923
                        $args = 3;
5924
5925
                        break;
5926
                    case 44:
5927
                        $function = 'DMAX';
5928
                        $args = 3;
5929
5930
                        break;
5931
                    case 45:
5932
                        $function = 'DSTDEV';
5933
                        $args = 3;
5934
5935
                        break;
5936
                    case 48:
5937
                        $function = 'TEXT';
5938
                        $args = 2;
5939
5940
                        break;
5941
                    case 61:
5942
                        $function = 'MIRR';
5943
                        $args = 3;
5944
5945
                        break;
5946
                    case 63:
5947
                        $function = 'RAND';
5948
                        $args = 0;
5949
5950
                        break;
5951
                    case 65:
5952
                        $function = 'DATE';
5953
                        $args = 3;
5954
5955
                        break;
5956
                    case 66:
5957
                        $function = 'TIME';
5958
                        $args = 3;
5959
5960
                        break;
5961
                    case 67:
5962
                        $function = 'DAY';
5963
                        $args = 1;
5964
5965
                        break;
5966
                    case 68:
5967
                        $function = 'MONTH';
5968
                        $args = 1;
5969
5970
                        break;
5971
                    case 69:
5972
                        $function = 'YEAR';
5973
                        $args = 1;
5974
5975
                        break;
5976
                    case 71:
5977
                        $function = 'HOUR';
5978
                        $args = 1;
5979
5980
                        break;
5981
                    case 72:
5982
                        $function = 'MINUTE';
5983
                        $args = 1;
5984
5985
                        break;
5986
                    case 73:
5987
                        $function = 'SECOND';
5988
                        $args = 1;
5989
5990
                        break;
5991
                    case 74:
5992
                        $function = 'NOW';
5993
                        $args = 0;
5994
5995
                        break;
5996
                    case 75:
5997
                        $function = 'AREAS';
5998
                        $args = 1;
5999
6000
                        break;
6001
                    case 76:
6002
                        $function = 'ROWS';
6003
                        $args = 1;
6004
6005
                        break;
6006
                    case 77:
6007
                        $function = 'COLUMNS';
6008
                        $args = 1;
6009
6010
                        break;
6011
                    case 83:
6012
                        $function = 'TRANSPOSE';
6013
                        $args = 1;
6014
6015
                        break;
6016
                    case 86:
6017
                        $function = 'TYPE';
6018
                        $args = 1;
6019
6020
                        break;
6021
                    case 97:
6022
                        $function = 'ATAN2';
6023
                        $args = 2;
6024
6025
                        break;
6026
                    case 98:
6027
                        $function = 'ASIN';
6028
                        $args = 1;
6029
6030
                        break;
6031
                    case 99:
6032
                        $function = 'ACOS';
6033
                        $args = 1;
6034
6035
                        break;
6036
                    case 105:
6037
                        $function = 'ISREF';
6038
                        $args = 1;
6039
6040
                        break;
6041
                    case 111:
6042
                        $function = 'CHAR';
6043
                        $args = 1;
6044
6045
                        break;
6046
                    case 112:
6047
                        $function = 'LOWER';
6048
                        $args = 1;
6049
6050
                        break;
6051
                    case 113:
6052
                        $function = 'UPPER';
6053
                        $args = 1;
6054
6055
                        break;
6056
                    case 114:
6057
                        $function = 'PROPER';
6058
                        $args = 1;
6059
6060
                        break;
6061
                    case 117:
6062
                        $function = 'EXACT';
6063
                        $args = 2;
6064
6065
                        break;
6066
                    case 118:
6067
                        $function = 'TRIM';
6068
                        $args = 1;
6069
6070
                        break;
6071
                    case 119:
6072
                        $function = 'REPLACE';
6073
                        $args = 4;
6074
6075
                        break;
6076
                    case 121:
6077
                        $function = 'CODE';
6078
                        $args = 1;
6079
6080
                        break;
6081
                    case 126:
6082
                        $function = 'ISERR';
6083
                        $args = 1;
6084
6085
                        break;
6086
                    case 127:
6087
                        $function = 'ISTEXT';
6088
                        $args = 1;
6089
6090
                        break;
6091
                    case 128:
6092
                        $function = 'ISNUMBER';
6093
                        $args = 1;
6094
6095
                        break;
6096
                    case 129:
6097
                        $function = 'ISBLANK';
6098
                        $args = 1;
6099
6100
                        break;
6101
                    case 130:
6102
                        $function = 'T';
6103
                        $args = 1;
6104
6105
                        break;
6106
                    case 131:
6107
                        $function = 'N';
6108
                        $args = 1;
6109
6110
                        break;
6111
                    case 140:
6112
                        $function = 'DATEVALUE';
6113
                        $args = 1;
6114
6115
                        break;
6116
                    case 141:
6117
                        $function = 'TIMEVALUE';
6118
                        $args = 1;
6119
6120
                        break;
6121
                    case 142:
6122
                        $function = 'SLN';
6123
                        $args = 3;
6124
6125
                        break;
6126
                    case 143:
6127
                        $function = 'SYD';
6128
                        $args = 4;
6129
6130
                        break;
6131
                    case 162:
6132
                        $function = 'CLEAN';
6133
                        $args = 1;
6134
6135
                        break;
6136
                    case 163:
6137
                        $function = 'MDETERM';
6138
                        $args = 1;
6139
6140
                        break;
6141
                    case 164:
6142
                        $function = 'MINVERSE';
6143
                        $args = 1;
6144
6145
                        break;
6146
                    case 165:
6147
                        $function = 'MMULT';
6148
                        $args = 2;
6149
6150
                        break;
6151
                    case 184:
6152
                        $function = 'FACT';
6153
                        $args = 1;
6154
6155
                        break;
6156
                    case 189:
6157
                        $function = 'DPRODUCT';
6158
                        $args = 3;
6159
6160
                        break;
6161
                    case 190:
6162
                        $function = 'ISNONTEXT';
6163
                        $args = 1;
6164
6165
                        break;
6166
                    case 195:
6167
                        $function = 'DSTDEVP';
6168
                        $args = 3;
6169
6170
                        break;
6171
                    case 196:
6172
                        $function = 'DVARP';
6173
                        $args = 3;
6174
6175
                        break;
6176
                    case 198:
6177
                        $function = 'ISLOGICAL';
6178
                        $args = 1;
6179
6180
                        break;
6181
                    case 199:
6182
                        $function = 'DCOUNTA';
6183
                        $args = 3;
6184
6185
                        break;
6186
                    case 207:
6187
                        $function = 'REPLACEB';
6188
                        $args = 4;
6189
6190
                        break;
6191
                    case 210:
6192
                        $function = 'MIDB';
6193
                        $args = 3;
6194
6195
                        break;
6196
                    case 211:
6197
                        $function = 'LENB';
6198
                        $args = 1;
6199
6200
                        break;
6201
                    case 212:
6202
                        $function = 'ROUNDUP';
6203
                        $args = 2;
6204
6205
                        break;
6206
                    case 213:
6207
                        $function = 'ROUNDDOWN';
6208
                        $args = 2;
6209
6210
                        break;
6211
                    case 214:
6212
                        $function = 'ASC';
6213
                        $args = 1;
6214
6215
                        break;
6216
                    case 215:
6217
                        $function = 'DBCS';
6218
                        $args = 1;
6219
6220
                        break;
6221
                    case 221:
6222
                        $function = 'TODAY';
6223
                        $args = 0;
6224
6225
                        break;
6226
                    case 229:
6227
                        $function = 'SINH';
6228
                        $args = 1;
6229
6230
                        break;
6231
                    case 230:
6232
                        $function = 'COSH';
6233
                        $args = 1;
6234
6235
                        break;
6236
                    case 231:
6237
                        $function = 'TANH';
6238
                        $args = 1;
6239
6240
                        break;
6241
                    case 232:
6242
                        $function = 'ASINH';
6243
                        $args = 1;
6244
6245
                        break;
6246
                    case 233:
6247
                        $function = 'ACOSH';
6248
                        $args = 1;
6249
6250
                        break;
6251
                    case 234:
6252
                        $function = 'ATANH';
6253
                        $args = 1;
6254
6255
                        break;
6256
                    case 235:
6257
                        $function = 'DGET';
6258
                        $args = 3;
6259
6260
                        break;
6261
                    case 244:
6262
                        $function = 'INFO';
6263
                        $args = 1;
6264
6265
                        break;
6266
                    case 252:
6267
                        $function = 'FREQUENCY';
6268
                        $args = 2;
6269
6270
                        break;
6271
                    case 261:
6272
                        $function = 'ERROR.TYPE';
6273
                        $args = 1;
6274
6275
                        break;
6276
                    case 271:
6277
                        $function = 'GAMMALN';
6278
                        $args = 1;
6279
6280
                        break;
6281
                    case 273:
6282
                        $function = 'BINOMDIST';
6283
                        $args = 4;
6284
6285
                        break;
6286
                    case 274:
6287
                        $function = 'CHIDIST';
6288
                        $args = 2;
6289
6290
                        break;
6291
                    case 275:
6292
                        $function = 'CHIINV';
6293
                        $args = 2;
6294
6295
                        break;
6296
                    case 276:
6297
                        $function = 'COMBIN';
6298
                        $args = 2;
6299
6300
                        break;
6301
                    case 277:
6302
                        $function = 'CONFIDENCE';
6303
                        $args = 3;
6304
6305
                        break;
6306
                    case 278:
6307
                        $function = 'CRITBINOM';
6308
                        $args = 3;
6309
6310
                        break;
6311
                    case 279:
6312
                        $function = 'EVEN';
6313
                        $args = 1;
6314
6315
                        break;
6316
                    case 280:
6317
                        $function = 'EXPONDIST';
6318
                        $args = 3;
6319
6320
                        break;
6321
                    case 281:
6322
                        $function = 'FDIST';
6323
                        $args = 3;
6324
6325
                        break;
6326
                    case 282:
6327
                        $function = 'FINV';
6328
                        $args = 3;
6329
6330
                        break;
6331
                    case 283:
6332
                        $function = 'FISHER';
6333
                        $args = 1;
6334
6335
                        break;
6336
                    case 284:
6337
                        $function = 'FISHERINV';
6338
                        $args = 1;
6339
6340
                        break;
6341
                    case 285:
6342
                        $function = 'FLOOR';
6343
                        $args = 2;
6344
6345
                        break;
6346
                    case 286:
6347
                        $function = 'GAMMADIST';
6348
                        $args = 4;
6349
6350
                        break;
6351
                    case 287:
6352
                        $function = 'GAMMAINV';
6353
                        $args = 3;
6354
6355
                        break;
6356
                    case 288:
6357
                        $function = 'CEILING';
6358
                        $args = 2;
6359
6360
                        break;
6361
                    case 289:
6362
                        $function = 'HYPGEOMDIST';
6363
                        $args = 4;
6364
6365
                        break;
6366
                    case 290:
6367
                        $function = 'LOGNORMDIST';
6368
                        $args = 3;
6369
6370
                        break;
6371
                    case 291:
6372
                        $function = 'LOGINV';
6373
                        $args = 3;
6374
6375
                        break;
6376
                    case 292:
6377
                        $function = 'NEGBINOMDIST';
6378
                        $args = 3;
6379
6380
                        break;
6381
                    case 293:
6382
                        $function = 'NORMDIST';
6383
                        $args = 4;
6384
6385
                        break;
6386
                    case 294:
6387
                        $function = 'NORMSDIST';
6388
                        $args = 1;
6389
6390
                        break;
6391
                    case 295:
6392
                        $function = 'NORMINV';
6393
                        $args = 3;
6394
6395
                        break;
6396
                    case 296:
6397
                        $function = 'NORMSINV';
6398
                        $args = 1;
6399
6400
                        break;
6401
                    case 297:
6402
                        $function = 'STANDARDIZE';
6403
                        $args = 3;
6404
6405
                        break;
6406
                    case 298:
6407
                        $function = 'ODD';
6408
                        $args = 1;
6409
6410
                        break;
6411
                    case 299:
6412
                        $function = 'PERMUT';
6413
                        $args = 2;
6414
6415
                        break;
6416
                    case 300:
6417
                        $function = 'POISSON';
6418
                        $args = 3;
6419
6420
                        break;
6421
                    case 301:
6422
                        $function = 'TDIST';
6423
                        $args = 3;
6424
6425
                        break;
6426
                    case 302:
6427
                        $function = 'WEIBULL';
6428
                        $args = 4;
6429
6430
                        break;
6431
                    case 303:
6432
                        $function = 'SUMXMY2';
6433
                        $args = 2;
6434
6435
                        break;
6436
                    case 304:
6437
                        $function = 'SUMX2MY2';
6438
                        $args = 2;
6439
6440
                        break;
6441
                    case 305:
6442
                        $function = 'SUMX2PY2';
6443
                        $args = 2;
6444
6445
                        break;
6446
                    case 306:
6447
                        $function = 'CHITEST';
6448
                        $args = 2;
6449
6450
                        break;
6451
                    case 307:
6452
                        $function = 'CORREL';
6453
                        $args = 2;
6454
6455
                        break;
6456
                    case 308:
6457
                        $function = 'COVAR';
6458
                        $args = 2;
6459
6460
                        break;
6461
                    case 309:
6462
                        $function = 'FORECAST';
6463
                        $args = 3;
6464
6465
                        break;
6466
                    case 310:
6467
                        $function = 'FTEST';
6468
                        $args = 2;
6469
6470
                        break;
6471
                    case 311:
6472
                        $function = 'INTERCEPT';
6473
                        $args = 2;
6474
6475
                        break;
6476
                    case 312:
6477
                        $function = 'PEARSON';
6478
                        $args = 2;
6479
6480
                        break;
6481
                    case 313:
6482
                        $function = 'RSQ';
6483
                        $args = 2;
6484
6485
                        break;
6486
                    case 314:
6487
                        $function = 'STEYX';
6488
                        $args = 2;
6489
6490
                        break;
6491
                    case 315:
6492
                        $function = 'SLOPE';
6493
                        $args = 2;
6494
6495
                        break;
6496
                    case 316:
6497
                        $function = 'TTEST';
6498
                        $args = 4;
6499
6500
                        break;
6501
                    case 325:
6502
                        $function = 'LARGE';
6503
                        $args = 2;
6504
6505
                        break;
6506
                    case 326:
6507
                        $function = 'SMALL';
6508
                        $args = 2;
6509
6510
                        break;
6511
                    case 327:
6512
                        $function = 'QUARTILE';
6513
                        $args = 2;
6514
6515
                        break;
6516
                    case 328:
6517
                        $function = 'PERCENTILE';
6518
                        $args = 2;
6519
6520
                        break;
6521
                    case 331:
6522
                        $function = 'TRIMMEAN';
6523
                        $args = 2;
6524
6525
                        break;
6526
                    case 332:
6527
                        $function = 'TINV';
6528
                        $args = 2;
6529
6530
                        break;
6531
                    case 337:
6532
                        $function = 'POWER';
6533
                        $args = 2;
6534
6535
                        break;
6536
                    case 342:
6537
                        $function = 'RADIANS';
6538
                        $args = 1;
6539
6540
                        break;
6541
                    case 343:
6542
                        $function = 'DEGREES';
6543
                        $args = 1;
6544
6545
                        break;
6546
                    case 346:
6547
                        $function = 'COUNTIF';
6548
                        $args = 2;
6549
6550
                        break;
6551
                    case 347:
6552
                        $function = 'COUNTBLANK';
6553
                        $args = 1;
6554
6555
                        break;
6556
                    case 350:
6557
                        $function = 'ISPMT';
6558
                        $args = 4;
6559
6560
                        break;
6561
                    case 351:
6562
                        $function = 'DATEDIF';
6563
                        $args = 3;
6564
6565
                        break;
6566
                    case 352:
6567
                        $function = 'DATESTRING';
6568
                        $args = 1;
6569
6570
                        break;
6571
                    case 353:
6572
                        $function = 'NUMBERSTRING';
6573
                        $args = 2;
6574
6575
                        break;
6576
                    case 360:
6577
                        $function = 'PHONETIC';
6578
                        $args = 1;
6579
6580
                        break;
6581
                    case 368:
6582
                        $function = 'BAHTTEXT';
6583
                        $args = 1;
6584
6585
                        break;
6586
                    default:
6587
                        throw new Exception('Unrecognized function in formula');
6588
                        break;
6589
                }
6590 8
                $data = ['function' => $function, 'args' => $args];
6591
6592 8
                break;
6593 10
            case 0x22:    //    function with variable number of arguments
6594 10
            case 0x42:
6595 10
            case 0x62:
6596 1
                $name = 'tFuncV';
6597 1
                $size = 4;
6598
                // offset: 1; size: 1; number of arguments
6599 1
                $args = ord($formulaData[1]);
6600
                // offset: 2: size: 2; index to built-in sheet function
6601 1
                $index = self::getUInt2d($formulaData, 2);
6602
                switch ($index) {
6603 1
                    case 0:
6604
                        $function = 'COUNT';
6605
6606
                        break;
6607 1
                    case 1:
6608 1
                        $function = 'IF';
6609
6610 1
                        break;
6611 1
                    case 4:
6612 1
                        $function = 'SUM';
6613
6614 1
                        break;
6615
                    case 5:
6616
                        $function = 'AVERAGE';
6617
6618
                        break;
6619
                    case 6:
6620
                        $function = 'MIN';
6621
6622
                        break;
6623
                    case 7:
6624
                        $function = 'MAX';
6625
6626
                        break;
6627
                    case 8:
6628
                        $function = 'ROW';
6629
6630
                        break;
6631
                    case 9:
6632
                        $function = 'COLUMN';
6633
6634
                        break;
6635
                    case 11:
6636
                        $function = 'NPV';
6637
6638
                        break;
6639
                    case 12:
6640
                        $function = 'STDEV';
6641
6642
                        break;
6643
                    case 13:
6644
                        $function = 'DOLLAR';
6645
6646
                        break;
6647
                    case 14:
6648
                        $function = 'FIXED';
6649
6650
                        break;
6651
                    case 28:
6652
                        $function = 'LOOKUP';
6653
6654
                        break;
6655
                    case 29:
6656
                        $function = 'INDEX';
6657
6658
                        break;
6659
                    case 36:
6660
                        $function = 'AND';
6661
6662
                        break;
6663
                    case 37:
6664
                        $function = 'OR';
6665
6666
                        break;
6667
                    case 46:
6668
                        $function = 'VAR';
6669
6670
                        break;
6671
                    case 49:
6672
                        $function = 'LINEST';
6673
6674
                        break;
6675
                    case 50:
6676
                        $function = 'TREND';
6677
6678
                        break;
6679
                    case 51:
6680
                        $function = 'LOGEST';
6681
6682
                        break;
6683
                    case 52:
6684
                        $function = 'GROWTH';
6685
6686
                        break;
6687
                    case 56:
6688
                        $function = 'PV';
6689
6690
                        break;
6691
                    case 57:
6692
                        $function = 'FV';
6693
6694
                        break;
6695
                    case 58:
6696
                        $function = 'NPER';
6697
6698
                        break;
6699
                    case 59:
6700
                        $function = 'PMT';
6701
6702
                        break;
6703
                    case 60:
6704
                        $function = 'RATE';
6705
6706
                        break;
6707
                    case 62:
6708
                        $function = 'IRR';
6709
6710
                        break;
6711
                    case 64:
6712
                        $function = 'MATCH';
6713
6714
                        break;
6715
                    case 70:
6716
                        $function = 'WEEKDAY';
6717
6718
                        break;
6719
                    case 78:
6720
                        $function = 'OFFSET';
6721
6722
                        break;
6723
                    case 82:
6724
                        $function = 'SEARCH';
6725
6726
                        break;
6727
                    case 100:
6728
                        $function = 'CHOOSE';
6729
6730
                        break;
6731
                    case 101:
6732
                        $function = 'HLOOKUP';
6733
6734
                        break;
6735
                    case 102:
6736
                        $function = 'VLOOKUP';
6737
6738
                        break;
6739
                    case 109:
6740
                        $function = 'LOG';
6741
6742
                        break;
6743
                    case 115:
6744
                        $function = 'LEFT';
6745
6746
                        break;
6747
                    case 116:
6748
                        $function = 'RIGHT';
6749
6750
                        break;
6751
                    case 120:
6752
                        $function = 'SUBSTITUTE';
6753
6754
                        break;
6755
                    case 124:
6756
                        $function = 'FIND';
6757
6758
                        break;
6759
                    case 125:
6760
                        $function = 'CELL';
6761
6762
                        break;
6763
                    case 144:
6764
                        $function = 'DDB';
6765
6766
                        break;
6767
                    case 148:
6768
                        $function = 'INDIRECT';
6769
6770
                        break;
6771
                    case 167:
6772
                        $function = 'IPMT';
6773
6774
                        break;
6775
                    case 168:
6776
                        $function = 'PPMT';
6777
6778
                        break;
6779
                    case 169:
6780
                        $function = 'COUNTA';
6781
6782
                        break;
6783
                    case 183:
6784
                        $function = 'PRODUCT';
6785
6786
                        break;
6787
                    case 193:
6788
                        $function = 'STDEVP';
6789
6790
                        break;
6791
                    case 194:
6792
                        $function = 'VARP';
6793
6794
                        break;
6795
                    case 197:
6796
                        $function = 'TRUNC';
6797
6798
                        break;
6799
                    case 204:
6800
                        $function = 'USDOLLAR';
6801
6802
                        break;
6803
                    case 205:
6804
                        $function = 'FINDB';
6805
6806
                        break;
6807
                    case 206:
6808
                        $function = 'SEARCHB';
6809
6810
                        break;
6811
                    case 208:
6812
                        $function = 'LEFTB';
6813
6814
                        break;
6815
                    case 209:
6816
                        $function = 'RIGHTB';
6817
6818
                        break;
6819
                    case 216:
6820
                        $function = 'RANK';
6821
6822
                        break;
6823
                    case 219:
6824
                        $function = 'ADDRESS';
6825
6826
                        break;
6827
                    case 220:
6828
                        $function = 'DAYS360';
6829
6830
                        break;
6831
                    case 222:
6832
                        $function = 'VDB';
6833
6834
                        break;
6835
                    case 227:
6836
                        $function = 'MEDIAN';
6837
6838
                        break;
6839
                    case 228:
6840
                        $function = 'SUMPRODUCT';
6841
6842
                        break;
6843
                    case 247:
6844
                        $function = 'DB';
6845
6846
                        break;
6847
                    case 255:
6848
                        $function = '';
6849
6850
                        break;
6851
                    case 269:
6852
                        $function = 'AVEDEV';
6853
6854
                        break;
6855
                    case 270:
6856
                        $function = 'BETADIST';
6857
6858
                        break;
6859
                    case 272:
6860
                        $function = 'BETAINV';
6861
6862
                        break;
6863
                    case 317:
6864
                        $function = 'PROB';
6865
6866
                        break;
6867
                    case 318:
6868
                        $function = 'DEVSQ';
6869
6870
                        break;
6871
                    case 319:
6872
                        $function = 'GEOMEAN';
6873
6874
                        break;
6875
                    case 320:
6876
                        $function = 'HARMEAN';
6877
6878
                        break;
6879
                    case 321:
6880
                        $function = 'SUMSQ';
6881
6882
                        break;
6883
                    case 322:
6884
                        $function = 'KURT';
6885
6886
                        break;
6887
                    case 323:
6888
                        $function = 'SKEW';
6889
6890
                        break;
6891
                    case 324:
6892
                        $function = 'ZTEST';
6893
6894
                        break;
6895
                    case 329:
6896
                        $function = 'PERCENTRANK';
6897
6898
                        break;
6899
                    case 330:
6900
                        $function = 'MODE';
6901
6902
                        break;
6903
                    case 336:
6904
                        $function = 'CONCATENATE';
6905
6906
                        break;
6907
                    case 344:
6908
                        $function = 'SUBTOTAL';
6909
6910
                        break;
6911
                    case 345:
6912
                        $function = 'SUMIF';
6913
6914
                        break;
6915
                    case 354:
6916
                        $function = 'ROMAN';
6917
6918
                        break;
6919
                    case 358:
6920
                        $function = 'GETPIVOTDATA';
6921
6922
                        break;
6923
                    case 359:
6924
                        $function = 'HYPERLINK';
6925
6926
                        break;
6927
                    case 361:
6928
                        $function = 'AVERAGEA';
6929
6930
                        break;
6931
                    case 362:
6932
                        $function = 'MAXA';
6933
6934
                        break;
6935
                    case 363:
6936
                        $function = 'MINA';
6937
6938
                        break;
6939
                    case 364:
6940
                        $function = 'STDEVPA';
6941
6942
                        break;
6943
                    case 365:
6944
                        $function = 'VARPA';
6945
6946
                        break;
6947
                    case 366:
6948
                        $function = 'STDEVA';
6949
6950
                        break;
6951
                    case 367:
6952
                        $function = 'VARA';
6953
6954
                        break;
6955
                    default:
6956
                        throw new Exception('Unrecognized function in formula');
6957
                        break;
6958
                }
6959 1
                $data = ['function' => $function, 'args' => $args];
6960
6961 1
                break;
6962 10
            case 0x23:    //    index to defined name
6963 10
            case 0x43:
6964 10
            case 0x63:
6965
                $name = 'tName';
6966
                $size = 5;
6967
                // offset: 1; size: 2; one-based index to definedname record
6968
                $definedNameIndex = self::getUInt2d($formulaData, 1) - 1;
6969
                // offset: 2; size: 2; not used
6970
                $data = $this->definedname[$definedNameIndex]['name'];
6971
6972
                break;
6973 10
            case 0x24:    //    single cell reference e.g. A5
6974 10
            case 0x44:
6975 10
            case 0x64:
6976 2
                $name = 'tRef';
6977 2
                $size = 5;
6978 2
                $data = $this->readBIFF8CellAddress(substr($formulaData, 1, 4));
6979
6980 2
                break;
6981 10
            case 0x25:    //    cell range reference to cells in the same sheet (2d)
6982 1
            case 0x45:
6983 1
            case 0x65:
6984 10
                $name = 'tArea';
6985 10
                $size = 9;
6986 10
                $data = $this->readBIFF8CellRangeAddress(substr($formulaData, 1, 8));
6987
6988 10
                break;
6989 1
            case 0x26:    //    Constant reference sub-expression
6990 1
            case 0x46:
6991 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...
6992
                $name = 'tMemArea';
6993
                // offset: 1; size: 4; not used
6994
                // offset: 5; size: 2; size of the following subexpression
6995
                $subSize = self::getUInt2d($formulaData, 5);
6996
                $size = 7 + $subSize;
6997
                $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
6998
6999
                break;
7000 1
            case 0x27:    //    Deleted constant reference sub-expression
7001 1
            case 0x47:
7002 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...
7003
                $name = 'tMemErr';
7004
                // offset: 1; size: 4; not used
7005
                // offset: 5; size: 2; size of the following subexpression
7006
                $subSize = self::getUInt2d($formulaData, 5);
7007
                $size = 7 + $subSize;
7008
                $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
7009
7010
                break;
7011 1
            case 0x29:    //    Variable reference sub-expression
7012 1
            case 0x49:
7013 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...
7014
                $name = 'tMemFunc';
7015
                // offset: 1; size: 2; size of the following sub-expression
7016
                $subSize = self::getUInt2d($formulaData, 1);
7017
                $size = 3 + $subSize;
7018
                $data = $this->getFormulaFromData(substr($formulaData, 3, $subSize));
7019
7020
                break;
7021 1
            case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places
7022 1
            case 0x4C:
7023 1
            case 0x6C:
7024
                $name = 'tRefN';
7025
                $size = 5;
7026
                $data = $this->readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell);
7027
7028
                break;
7029 1
            case 0x2D:    //    Relative 2d range reference
7030 1
            case 0x4D:
7031 1
            case 0x6D:
7032
                $name = 'tAreaN';
7033
                $size = 9;
7034
                $data = $this->readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell);
7035
7036
                break;
7037 1
            case 0x39:    //    External name
7038 1
            case 0x59:
7039 1
            case 0x79:
7040
                $name = 'tNameX';
7041
                $size = 7;
7042
                // offset: 1; size: 2; index to REF entry in EXTERNSHEET record
7043
                // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record
7044
                $index = self::getUInt2d($formulaData, 3);
7045
                // assume index is to EXTERNNAME record
7046
                $data = $this->externalNames[$index - 1]['name'];
7047
                // offset: 5; size: 2; not used
7048
                break;
7049 1
            case 0x3A:    //    3d reference to cell
7050 1
            case 0x5A:
7051 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...
7052
                $name = 'tRef3d';
7053
                $size = 7;
7054
7055
                try {
7056
                    // offset: 1; size: 2; index to REF entry
7057
                    $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1));
7058
                    // offset: 3; size: 4; cell address
7059
                    $cellAddress = $this->readBIFF8CellAddress(substr($formulaData, 3, 4));
7060
7061
                    $data = "$sheetRange!$cellAddress";
7062
                } catch (PhpSpreadsheetException $e) {
7063
                    // deleted sheet reference
7064
                    $data = '#REF!';
7065
                }
7066
7067
                break;
7068 1
            case 0x3B:    //    3d reference to cell range
7069
            case 0x5B:
7070 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...
7071 1
                $name = 'tArea3d';
7072 1
                $size = 11;
7073
7074
                try {
7075
                    // offset: 1; size: 2; index to REF entry
7076 1
                    $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1));
7077
                    // offset: 3; size: 8; cell address
7078 1
                    $cellRangeAddress = $this->readBIFF8CellRangeAddress(substr($formulaData, 3, 8));
7079
7080 1
                    $data = "$sheetRange!$cellRangeAddress";
7081
                } catch (PhpSpreadsheetException $e) {
7082
                    // deleted sheet reference
7083
                    $data = '#REF!';
7084
                }
7085
7086 1
                break;
7087
            // Unknown cases    // don't know how to deal with
7088
            default:
7089
                throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula');
7090
                break;
7091
        }
7092
7093
        return [
7094 10
            'id' => $id,
7095 10
            'name' => $name,
7096 10
            'size' => $size,
7097 10
            'data' => $data,
7098
        ];
7099
    }
7100
7101
    /**
7102
     * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2'
7103
     * section 3.3.4.
7104
     *
7105
     * @param string $cellAddressStructure
7106
     *
7107
     * @return string
7108
     */
7109 2
    private function readBIFF8CellAddress($cellAddressStructure)
7110
    {
7111
        // 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...
7112 2
        $row = self::getUInt2d($cellAddressStructure, 0) + 1;
7113
7114
        // offset: 2; size: 2; index to column or column offset + relative flags
7115
        // bit: 7-0; mask 0x00FF; column index
7116 2
        $column = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($cellAddressStructure, 2)) + 1);
7117
7118
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7119 2
        if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) {
7120 1
            $column = '$' . $column;
7121
        }
7122
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7123 2
        if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
7124 1
            $row = '$' . $row;
7125
        }
7126
7127 2
        return $column . $row;
7128
    }
7129
7130
    /**
7131
     * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column
7132
     * to indicate offsets from a base cell
7133
     * section 3.3.4.
7134
     *
7135
     * @param string $cellAddressStructure
7136
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
7137
     *
7138
     * @return string
7139
     */
7140
    private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1')
7141
    {
7142
        list($baseCol, $baseRow) = Coordinate::coordinateFromString($baseCell);
7143
        $baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
7144
7145
        // 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...
7146
        $rowIndex = self::getUInt2d($cellAddressStructure, 0);
7147
        $row = self::getUInt2d($cellAddressStructure, 0) + 1;
7148
7149
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7150 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...
7151
            // offset: 2; size: 2; index to column or column offset + relative flags
7152
            // bit: 7-0; mask 0x00FF; column index
7153
            $colIndex = 0x00FF & self::getUInt2d($cellAddressStructure, 2);
7154
7155
            $column = Coordinate::stringFromColumnIndex($colIndex + 1);
7156
            $column = '$' . $column;
7157
        } else {
7158
            // offset: 2; size: 2; index to column or column offset + relative flags
7159
            // bit: 7-0; mask 0x00FF; column index
7160
            $relativeColIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2);
7161
            $colIndex = $baseCol + $relativeColIndex;
7162
            $colIndex = ($colIndex < 256) ? $colIndex : $colIndex - 256;
7163
            $colIndex = ($colIndex >= 0) ? $colIndex : $colIndex + 256;
7164
            $column = Coordinate::stringFromColumnIndex($colIndex + 1);
7165
        }
7166
7167
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7168
        if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
7169
            $row = '$' . $row;
7170
        } else {
7171
            $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536;
7172
            $row = $baseRow + $rowIndex;
7173
        }
7174
7175
        return $column . $row;
7176
    }
7177
7178
    /**
7179
     * Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1'
7180
     * always fixed range
7181
     * section 2.5.14.
7182
     *
7183
     * @param string $subData
7184
     *
7185
     * @throws Exception
7186
     *
7187
     * @return string
7188
     */
7189 17
    private function readBIFF5CellRangeAddressFixed($subData)
7190
    {
7191
        // offset: 0; size: 2; index to first row
7192 17
        $fr = self::getUInt2d($subData, 0) + 1;
7193
7194
        // offset: 2; size: 2; index to last row
7195 17
        $lr = self::getUInt2d($subData, 2) + 1;
7196
7197
        // offset: 4; size: 1; index to first column
7198 17
        $fc = ord($subData[4]);
7199
7200
        // offset: 5; size: 1; index to last column
7201 17
        $lc = ord($subData[5]);
7202
7203
        // check values
7204 17
        if ($fr > $lr || $fc > $lc) {
7205
            throw new Exception('Not a cell range address');
7206
        }
7207
7208
        // column index to letter
7209 17
        $fc = Coordinate::stringFromColumnIndex($fc + 1);
7210 17
        $lc = Coordinate::stringFromColumnIndex($lc + 1);
7211
7212 17
        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...
7213 13
            return "$fc$fr";
7214
        }
7215
7216 12
        return "$fc$fr:$lc$lr";
7217
    }
7218
7219
    /**
7220
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1'
7221
     * always fixed range
7222
     * section 2.5.14.
7223
     *
7224
     * @param string $subData
7225
     *
7226
     * @throws Exception
7227
     *
7228
     * @return string
7229
     */
7230 11
    private function readBIFF8CellRangeAddressFixed($subData)
7231
    {
7232
        // offset: 0; size: 2; index to first row
7233 11
        $fr = self::getUInt2d($subData, 0) + 1;
7234
7235
        // offset: 2; size: 2; index to last row
7236 11
        $lr = self::getUInt2d($subData, 2) + 1;
7237
7238
        // offset: 4; size: 2; index to first column
7239 11
        $fc = self::getUInt2d($subData, 4);
7240
7241
        // offset: 6; size: 2; index to last column
7242 11
        $lc = self::getUInt2d($subData, 6);
7243
7244
        // check values
7245 11
        if ($fr > $lr || $fc > $lc) {
7246
            throw new Exception('Not a cell range address');
7247
        }
7248
7249
        // column index to letter
7250 11
        $fc = Coordinate::stringFromColumnIndex($fc + 1);
7251 11
        $lc = Coordinate::stringFromColumnIndex($lc + 1);
7252
7253 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...
7254 2
            return "$fc$fr";
7255
        }
7256
7257 11
        return "$fc$fr:$lc$lr";
7258
    }
7259
7260
    /**
7261
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6'
7262
     * there are flags indicating whether column/row index is relative
7263
     * section 3.3.4.
7264
     *
7265
     * @param string $subData
7266
     *
7267
     * @return string
7268
     */
7269 10
    private function readBIFF8CellRangeAddress($subData)
7270
    {
7271
        // todo: if cell range is just a single cell, should this funciton
7272
        // not just return e.g. 'A1' and not 'A1:A1' ?
7273
7274
        // 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...
7275 10
        $fr = self::getUInt2d($subData, 0) + 1;
7276
7277
        // 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...
7278 10
        $lr = self::getUInt2d($subData, 2) + 1;
7279
7280
        // offset: 4; size: 2; index to first column or column offset + relative flags
7281
7282
        // bit: 7-0; mask 0x00FF; column index
7283 10
        $fc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 4)) + 1);
7284
7285
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7286 10
        if (!(0x4000 & self::getUInt2d($subData, 4))) {
7287 1
            $fc = '$' . $fc;
7288
        }
7289
7290
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7291 10
        if (!(0x8000 & self::getUInt2d($subData, 4))) {
7292 1
            $fr = '$' . $fr;
7293
        }
7294
7295
        // offset: 6; size: 2; index to last column or column offset + relative flags
7296
7297
        // bit: 7-0; mask 0x00FF; column index
7298 10
        $lc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 6)) + 1);
7299
7300
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7301 10
        if (!(0x4000 & self::getUInt2d($subData, 6))) {
7302 1
            $lc = '$' . $lc;
7303
        }
7304
7305
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7306 10
        if (!(0x8000 & self::getUInt2d($subData, 6))) {
7307 1
            $lr = '$' . $lr;
7308
        }
7309
7310 10
        return "$fc$fr:$lc$lr";
7311
    }
7312
7313
    /**
7314
     * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column
7315
     * to indicate offsets from a base cell
7316
     * section 3.3.4.
7317
     *
7318
     * @param string $subData
7319
     * @param string $baseCell Base cell
7320
     *
7321
     * @return string Cell range address
7322
     */
7323
    private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1')
7324
    {
7325
        list($baseCol, $baseRow) = Coordinate::coordinateFromString($baseCell);
7326
        $baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
7327
7328
        // TODO: if cell range is just a single cell, should this funciton
7329
        // not just return e.g. 'A1' and not 'A1:A1' ?
7330
7331
        // offset: 0; size: 2; first row
7332
        $frIndex = self::getUInt2d($subData, 0); // adjust below
7333
7334
        // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767)
7335
        $lrIndex = self::getUInt2d($subData, 2); // adjust below
7336
7337
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7338 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...
7339
            // absolute column index
7340
            // offset: 4; size: 2; first column with relative/absolute flags
7341
            // bit: 7-0; mask 0x00FF; column index
7342
            $fcIndex = 0x00FF & self::getUInt2d($subData, 4);
7343
            $fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
7344
            $fc = '$' . $fc;
7345
        } else {
7346
            // column offset
7347
            // offset: 4; size: 2; first column with relative/absolute flags
7348
            // bit: 7-0; mask 0x00FF; column index
7349
            $relativeFcIndex = 0x00FF & self::getInt2d($subData, 4);
7350
            $fcIndex = $baseCol + $relativeFcIndex;
7351
            $fcIndex = ($fcIndex < 256) ? $fcIndex : $fcIndex - 256;
7352
            $fcIndex = ($fcIndex >= 0) ? $fcIndex : $fcIndex + 256;
7353
            $fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
7354
        }
7355
7356
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7357 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...
7358
            // absolute row index
7359
            $fr = $frIndex + 1;
7360
            $fr = '$' . $fr;
7361
        } else {
7362
            // row offset
7363
            $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536;
7364
            $fr = $baseRow + $frIndex;
7365
        }
7366
7367
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7368 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...
7369
            // absolute column index
7370
            // offset: 6; size: 2; last column with relative/absolute flags
7371
            // bit: 7-0; mask 0x00FF; column index
7372
            $lcIndex = 0x00FF & self::getUInt2d($subData, 6);
7373
            $lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
7374
            $lc = '$' . $lc;
7375
        } else {
7376
            // column offset
7377
            // offset: 4; size: 2; first column with relative/absolute flags
7378
            // bit: 7-0; mask 0x00FF; column index
7379
            $relativeLcIndex = 0x00FF & self::getInt2d($subData, 4);
7380
            $lcIndex = $baseCol + $relativeLcIndex;
7381
            $lcIndex = ($lcIndex < 256) ? $lcIndex : $lcIndex - 256;
7382
            $lcIndex = ($lcIndex >= 0) ? $lcIndex : $lcIndex + 256;
7383
            $lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
7384
        }
7385
7386
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7387 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...
7388
            // absolute row index
7389
            $lr = $lrIndex + 1;
7390
            $lr = '$' . $lr;
7391
        } else {
7392
            // row offset
7393
            $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536;
7394
            $lr = $baseRow + $lrIndex;
7395
        }
7396
7397
        return "$fc$fr:$lc$lr";
7398
    }
7399
7400
    /**
7401
     * Read BIFF8 cell range address list
7402
     * section 2.5.15.
7403
     *
7404
     * @param string $subData
7405
     *
7406
     * @return array
7407
     */
7408 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...
7409
    {
7410 11
        $cellRangeAddresses = [];
7411
7412
        // offset: 0; size: 2; number of the following cell range addresses
7413 11
        $nm = self::getUInt2d($subData, 0);
7414
7415 11
        $offset = 2;
7416
        // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses
7417 11
        for ($i = 0; $i < $nm; ++$i) {
7418 11
            $cellRangeAddresses[] = $this->readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
7419 11
            $offset += 8;
7420
        }
7421
7422
        return [
7423 11
            'size' => 2 + 8 * $nm,
7424 11
            'cellRangeAddresses' => $cellRangeAddresses,
7425
        ];
7426
    }
7427
7428
    /**
7429
     * Read BIFF5 cell range address list
7430
     * section 2.5.15.
7431
     *
7432
     * @param string $subData
7433
     *
7434
     * @return array
7435
     */
7436 17 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...
7437
    {
7438 17
        $cellRangeAddresses = [];
7439
7440
        // offset: 0; size: 2; number of the following cell range addresses
7441 17
        $nm = self::getUInt2d($subData, 0);
7442
7443 17
        $offset = 2;
7444
        // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses
7445 17
        for ($i = 0; $i < $nm; ++$i) {
7446 17
            $cellRangeAddresses[] = $this->readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6));
7447 17
            $offset += 6;
7448
        }
7449
7450
        return [
7451 17
            'size' => 2 + 6 * $nm,
7452 17
            'cellRangeAddresses' => $cellRangeAddresses,
7453
        ];
7454
    }
7455
7456
    /**
7457
     * Get a sheet range like Sheet1:Sheet3 from REF index
7458
     * Note: If there is only one sheet in the range, one gets e.g Sheet1
7459
     * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets,
7460
     * in which case an Exception is thrown.
7461
     *
7462
     * @param int $index
7463
     *
7464
     * @throws Exception
7465
     *
7466
     * @return false|string
7467
     */
7468 1
    private function readSheetRangeByRefIndex($index)
7469
    {
7470 1
        if (isset($this->ref[$index])) {
7471 1
            $type = $this->externalBooks[$this->ref[$index]['externalBookIndex']]['type'];
7472
7473
            switch ($type) {
7474 1
                case 'internal':
7475
                    // check if we have a deleted 3d reference
7476 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...
7477
                        throw new Exception('Deleted sheet reference');
7478
                    }
7479
7480
                    // we have normal sheet range (collapsed or uncollapsed)
7481 1
                    $firstSheetName = $this->sheets[$this->ref[$index]['firstSheetIndex']]['name'];
7482 1
                    $lastSheetName = $this->sheets[$this->ref[$index]['lastSheetIndex']]['name'];
7483
7484 1
                    if ($firstSheetName == $lastSheetName) {
7485
                        // collapsed sheet range
7486 1
                        $sheetRange = $firstSheetName;
7487
                    } else {
7488
                        $sheetRange = "$firstSheetName:$lastSheetName";
7489
                    }
7490
7491
                    // escape the single-quotes
7492 1
                    $sheetRange = str_replace("'", "''", $sheetRange);
7493
7494
                    // if there are special characters, we need to enclose the range in single-quotes
7495
                    // todo: check if we have identified the whole set of special characters
7496
                    // it seems that the following characters are not accepted for sheet names
7497
                    // and we may assume that they are not present: []*/:\?
7498 1
                    if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/u", $sheetRange)) {
7499
                        $sheetRange = "'$sheetRange'";
7500
                    }
7501
7502 1
                    return $sheetRange;
7503
                    break;
7504
                default:
7505
                    // TODO: external sheet support
7506
                    throw new Exception('Xls reader only supports internal sheets in fomulas');
7507
                    break;
7508
            }
7509
        }
7510
7511
        return false;
7512
    }
7513
7514
    /**
7515
     * read BIFF8 constant value array from array data
7516
     * returns e.g. array('value' => '{1,2;3,4}', 'size' => 40}
7517
     * section 2.5.8.
7518
     *
7519
     * @param string $arrayData
7520
     *
7521
     * @return array
7522
     */
7523
    private static function readBIFF8ConstantArray($arrayData)
7524
    {
7525
        // offset: 0; size: 1; number of columns decreased by 1
7526
        $nc = ord($arrayData[0]);
7527
7528
        // offset: 1; size: 2; number of rows decreased by 1
7529
        $nr = self::getUInt2d($arrayData, 1);
7530
        $size = 3; // initialize
7531
        $arrayData = substr($arrayData, 3);
7532
7533
        // 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...
7534
        $matrixChunks = [];
7535
        for ($r = 1; $r <= $nr + 1; ++$r) {
7536
            $items = [];
7537
            for ($c = 1; $c <= $nc + 1; ++$c) {
7538
                $constant = self::readBIFF8Constant($arrayData);
7539
                $items[] = $constant['value'];
7540
                $arrayData = substr($arrayData, $constant['size']);
7541
                $size += $constant['size'];
7542
            }
7543
            $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'
7544
        }
7545
        $matrix = '{' . implode(';', $matrixChunks) . '}';
7546
7547
        return [
7548
            'value' => $matrix,
7549
            'size' => $size,
7550
        ];
7551
    }
7552
7553
    /**
7554
     * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value'
7555
     * section 2.5.7
7556
     * returns e.g. array('value' => '5', 'size' => 9).
7557
     *
7558
     * @param string $valueData
7559
     *
7560
     * @return array
7561
     */
7562
    private static function readBIFF8Constant($valueData)
7563
    {
7564
        // offset: 0; size: 1; identifier for type of constant
7565
        $identifier = ord($valueData[0]);
7566
7567
        switch ($identifier) {
7568
            case 0x00: // empty constant (what is this?)
7569
                $value = '';
7570
                $size = 9;
7571
7572
                break;
7573
            case 0x01: // number
7574
                // offset: 1; size: 8; IEEE 754 floating-point value
7575
                $value = self::extractNumber(substr($valueData, 1, 8));
7576
                $size = 9;
7577
7578
                break;
7579
            case 0x02: // string value
7580
                // offset: 1; size: var; Unicode string, 16-bit string length
7581
                $string = self::readUnicodeStringLong(substr($valueData, 1));
7582
                $value = '"' . $string['value'] . '"';
7583
                $size = 1 + $string['size'];
7584
7585
                break;
7586
            case 0x04: // boolean
7587
                // 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...
7588
                if (ord($valueData[1])) {
7589
                    $value = 'TRUE';
7590
                } else {
7591
                    $value = 'FALSE';
7592
                }
7593
                $size = 9;
7594
7595
                break;
7596
            case 0x10: // error code
7597
                // offset: 1; size: 1; error code
7598
                $value = Xls\ErrorCode::lookup(ord($valueData[1]));
7599
                $size = 9;
7600
7601
                break;
7602
        }
7603
7604
        return [
7605
            'value' => $value,
7606
            'size' => $size,
7607
        ];
7608
    }
7609
7610
    /**
7611
     * Extract RGB color
7612
     * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4.
7613
     *
7614
     * @param string $rgb Encoded RGB value (4 bytes)
7615
     *
7616
     * @return array
7617
     */
7618 4
    private static function readRGB($rgb)
7619
    {
7620
        // offset: 0; size 1; Red component
7621 4
        $r = ord($rgb[0]);
7622
7623
        // offset: 1; size: 1; Green component
7624 4
        $g = ord($rgb[1]);
7625
7626
        // offset: 2; size: 1; Blue component
7627 4
        $b = ord($rgb[2]);
7628
7629
        // HEX notation, e.g. 'FF00FC'
7630 4
        $rgb = sprintf('%02X%02X%02X', $r, $g, $b);
7631
7632 4
        return ['rgb' => $rgb];
7633
    }
7634
7635
    /**
7636
     * Read byte string (8-bit string length)
7637
     * OpenOffice documentation: 2.5.2.
7638
     *
7639
     * @param string $subData
7640
     *
7641
     * @return array
7642
     */
7643 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...
7644
    {
7645
        // offset: 0; size: 1; length of the string (character count)
7646
        $ln = ord($subData[0]);
7647
7648
        // 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...
7649
        $value = $this->decodeCodepage(substr($subData, 1, $ln));
7650
7651
        return [
7652
            'value' => $value,
7653
            'size' => 1 + $ln, // size in bytes of data structure
7654
        ];
7655
    }
7656
7657
    /**
7658
     * Read byte string (16-bit string length)
7659
     * OpenOffice documentation: 2.5.2.
7660
     *
7661
     * @param string $subData
7662
     *
7663
     * @return array
7664
     */
7665 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...
7666
    {
7667
        // offset: 0; size: 2; length of the string (character count)
7668
        $ln = self::getUInt2d($subData, 0);
7669
7670
        // 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...
7671
        $value = $this->decodeCodepage(substr($subData, 2));
7672
7673
        //return $string;
7674
        return [
7675
            'value' => $value,
7676
            'size' => 2 + $ln, // size in bytes of data structure
7677
        ];
7678
    }
7679
7680
    /**
7681
     * Extracts an Excel Unicode short string (8-bit string length)
7682
     * OpenOffice documentation: 2.5.3
7683
     * function will automatically find out where the Unicode string ends.
7684
     *
7685
     * @param string $subData
7686
     *
7687
     * @return array
7688
     */
7689 21 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...
7690
    {
7691 21
        $value = '';
7692
7693
        // offset: 0: size: 1; length of the string (character count)
7694 21
        $characterCount = ord($subData[0]);
7695
7696 21
        $string = self::readUnicodeString(substr($subData, 1), $characterCount);
7697
7698
        // add 1 for the string length
7699 21
        $string['size'] += 1;
7700
7701 21
        return $string;
7702
    }
7703
7704
    /**
7705
     * Extracts an Excel Unicode long string (16-bit string length)
7706
     * OpenOffice documentation: 2.5.3
7707
     * this function is under construction, needs to support rich text, and Asian phonetic settings.
7708
     *
7709
     * @param string $subData
7710
     *
7711
     * @return array
7712
     */
7713 17 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...
7714
    {
7715 17
        $value = '';
7716
7717
        // offset: 0: size: 2; length of the string (character count)
7718 17
        $characterCount = self::getUInt2d($subData, 0);
7719
7720 17
        $string = self::readUnicodeString(substr($subData, 2), $characterCount);
7721
7722
        // add 2 for the string length
7723 17
        $string['size'] += 2;
7724
7725 17
        return $string;
7726
    }
7727
7728
    /**
7729
     * Read Unicode string with no string length field, but with known character count
7730
     * this function is under construction, needs to support rich text, and Asian phonetic settings
7731
     * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3.
7732
     *
7733
     * @param string $subData
7734
     * @param int $characterCount
7735
     *
7736
     * @return array
7737
     */
7738 21
    private static function readUnicodeString($subData, $characterCount)
7739
    {
7740 21
        $value = '';
7741
7742
        // offset: 0: size: 1; option flags
7743
        // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)
7744 21
        $isCompressed = !((0x01 & ord($subData[0])) >> 0);
7745
7746
        // bit: 2; mask: 0x04; Asian phonetic settings
7747 21
        $hasAsian = (0x04) & ord($subData[0]) >> 2;
7748
7749
        // bit: 3; mask: 0x08; Rich-Text settings
7750 21
        $hasRichText = (0x08) & ord($subData[0]) >> 3;
7751
7752
        // 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...
7753
        // this offset assumes richtext and Asian phonetic settings are off which is generally wrong
7754
        // needs to be fixed
7755 21
        $value = self::encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed);
7756
7757
        return [
7758 21
            'value' => $value,
7759 21
            'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags
7760
        ];
7761
    }
7762
7763
    /**
7764
     * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas.
7765
     * Example:  hello"world  -->  "hello""world".
7766
     *
7767
     * @param string $value UTF-8 encoded string
7768
     *
7769
     * @return string
7770
     */
7771 1
    private static function UTF8toExcelDoubleQuoted($value)
7772
    {
7773 1
        return '"' . str_replace('"', '""', $value) . '"';
7774
    }
7775
7776
    /**
7777
     * Reads first 8 bytes of a string and return IEEE 754 float.
7778
     *
7779
     * @param string $data Binary string that is at least 8 bytes long
7780
     *
7781
     * @return float
7782
     */
7783 18
    private static function extractNumber($data)
7784
    {
7785 18
        $rknumhigh = self::getInt4d($data, 4);
7786 18
        $rknumlow = self::getInt4d($data, 0);
7787 18
        $sign = ($rknumhigh & 0x80000000) >> 31;
7788 18
        $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023;
7789 18
        $mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
7790 18
        $mantissalow1 = ($rknumlow & 0x80000000) >> 31;
7791 18
        $mantissalow2 = ($rknumlow & 0x7fffffff);
7792 18
        $value = $mantissa / pow(2, (20 - $exp));
7793
7794 18
        if ($mantissalow1 != 0) {
7795 12
            $value += 1 / pow(2, (21 - $exp));
7796
        }
7797
7798 18
        $value += $mantissalow2 / pow(2, (52 - $exp));
7799 18
        if ($sign) {
7800 10
            $value *= -1;
7801
        }
7802
7803 18
        return $value;
7804
    }
7805
7806
    /**
7807
     * @param int $rknum
7808
     */
7809 12
    private static function getIEEE754($rknum)
7810
    {
7811 12
        if (($rknum & 0x02) != 0) {
7812
            $value = $rknum >> 2;
7813
        } else {
7814
            // changes by mmp, info on IEEE754 encoding from
7815
            // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
7816
            // The RK format calls for using only the most significant 30 bits
7817
            // of the 64 bit floating point value. The other 34 bits are assumed
7818
            // to be 0 so we use the upper 30 bits of $rknum as follows...
7819 12
            $sign = ($rknum & 0x80000000) >> 31;
7820 12
            $exp = ($rknum & 0x7ff00000) >> 20;
7821 12
            $mantissa = (0x100000 | ($rknum & 0x000ffffc));
7822 12
            $value = $mantissa / pow(2, (20 - ($exp - 1023)));
7823 12
            if ($sign) {
7824 10
                $value = -1 * $value;
7825
            }
7826
            //end of changes by mmp
7827
        }
7828 12
        if (($rknum & 0x01) != 0) {
7829 10
            $value /= 100;
7830
        }
7831
7832 12
        return $value;
7833
    }
7834
7835
    /**
7836
     * Get UTF-8 string from (compressed or uncompressed) UTF-16 string.
7837
     *
7838
     * @param string $string
7839
     * @param bool $compressed
7840
     *
7841
     * @return string
7842
     */
7843 21
    private static function encodeUTF16($string, $compressed = false)
7844
    {
7845 21
        if ($compressed) {
7846 20
            $string = self::uncompressByteString($string);
7847
        }
7848
7849 21
        return StringHelper::convertEncoding($string, 'UTF-8', 'UTF-16LE');
7850
    }
7851
7852
    /**
7853
     * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8.
7854
     *
7855
     * @param string $string
7856
     *
7857
     * @return string
7858
     */
7859 20
    private static function uncompressByteString($string)
7860
    {
7861 20
        $uncompressedString = '';
7862 20
        $strLen = strlen($string);
7863 20
        for ($i = 0; $i < $strLen; ++$i) {
7864 20
            $uncompressedString .= $string[$i] . "\0";
7865
        }
7866
7867 20
        return $uncompressedString;
7868
    }
7869
7870
    /**
7871
     * Convert string to UTF-8. Only used for BIFF5.
7872
     *
7873
     * @param string $string
7874
     *
7875
     * @return string
7876
     */
7877
    private function decodeCodepage($string)
7878
    {
7879
        return StringHelper::convertEncoding($string, 'UTF-8', $this->codepage);
7880
    }
7881
7882
    /**
7883
     * Read 16-bit unsigned integer.
7884
     *
7885
     * @param string $data
7886
     * @param int $pos
7887
     *
7888
     * @return int
7889
     */
7890 21
    public static function getUInt2d($data, $pos)
7891
    {
7892 21
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
7893
    }
7894
7895
    /**
7896
     * Read 16-bit signed integer.
7897
     *
7898
     * @param string $data
7899
     * @param int $pos
7900
     *
7901
     * @return int
7902
     */
7903
    public static function getInt2d($data, $pos)
7904
    {
7905
        return unpack('s', $data[$pos] . $data[$pos + 1])[1];
7906
    }
7907
7908
    /**
7909
     * Read 32-bit signed integer.
7910
     *
7911
     * @param string $data
7912
     * @param int $pos
7913
     *
7914
     * @return int
7915
     */
7916 21
    public static function getInt4d($data, $pos)
7917
    {
7918
        // FIX: represent numbers correctly on 64-bit system
7919
        // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
7920
        // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
7921 21
        $_or_24 = ord($data[$pos + 3]);
7922 21 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...
7923
            // negative number
7924 12
            $_ord_24 = -abs((256 - $_or_24) << 24);
7925
        } else {
7926 21
            $_ord_24 = ($_or_24 & 127) << 24;
7927
        }
7928
7929 21
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
7930
    }
7931
7932 1
    private function parseRichText($is)
7933
    {
7934 1
        $value = new RichText();
7935 1
        $value->createText($is);
7936
7937 1
        return $value;
7938
    }
7939
}
7940