Test Failed
Push — develop ( 90366f...812a46 )
by Adrien
28:16
created

Xls::readPageSetup()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 56
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 27
nc 5
nop 0
dl 0
loc 56
ccs 27
cts 27
cp 1
crap 5
rs 8.7592
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Reader;
4
5
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6
use PhpOffice\PhpSpreadsheet\Cell\DataType;
7
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
8
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
9
use PhpOffice\PhpSpreadsheet\NamedRange;
10
use PhpOffice\PhpSpreadsheet\RichText\RichText;
11
use PhpOffice\PhpSpreadsheet\Shared\CodePage;
12
use PhpOffice\PhpSpreadsheet\Shared\Date;
13
use PhpOffice\PhpSpreadsheet\Shared\Escher;
14
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
15
use PhpOffice\PhpSpreadsheet\Shared\File;
16
use PhpOffice\PhpSpreadsheet\Shared\OLE;
17
use PhpOffice\PhpSpreadsheet\Shared\OLERead;
18
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
19
use PhpOffice\PhpSpreadsheet\Spreadsheet;
20
use PhpOffice\PhpSpreadsheet\Style\Alignment;
21
use PhpOffice\PhpSpreadsheet\Style\Borders;
22
use PhpOffice\PhpSpreadsheet\Style\Font;
23
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
24
use PhpOffice\PhpSpreadsheet\Style\Protection;
25
use PhpOffice\PhpSpreadsheet\Style\Style;
26
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
27
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
28
use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
29
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
30
31
// Original file header of ParseXL (used as the base for this class):
32
// --------------------------------------------------------------------------------
33
// Adapted from Excel_Spreadsheet_Reader developed by users bizon153,
34
// trex005, and mmp11 (SourceForge.net)
35
// http://sourceforge.net/projects/phpexcelreader/
36
// Primary changes made by canyoncasa (dvc) for ParseXL 1.00 ...
37
//     Modelled moreso after Perl Excel Parse/Write modules
38
//     Added Parse_Excel_Spreadsheet object
39
//         Reads a whole worksheet or tab as row,column array or as
40
//         associated hash of indexed rows and named column fields
41
//     Added variables for worksheet (tab) indexes and names
42
//     Added an object call for loading individual woorksheets
43
//     Changed default indexing defaults to 0 based arrays
44
//     Fixed date/time and percent formats
45
//     Includes patches found at SourceForge...
46
//         unicode patch by nobody
47
//         unpack("d") machine depedency patch by matchy
48
//         boundsheet utf16 patch by bjaenichen
49
//     Renamed functions for shorter names
50
//     General code cleanup and rigor, including <80 column width
51
//     Included a testcase Excel file and PHP example calls
52
//     Code works for PHP 5.x
53
54
// Primary changes made by canyoncasa (dvc) for ParseXL 1.10 ...
55
// http://sourceforge.net/tracker/index.php?func=detail&aid=1466964&group_id=99160&atid=623334
56
//     Decoding of formula conditions, results, and tokens.
57
//     Support for user-defined named cells added as an array "namedcells"
58
//         Patch code for user-defined named cells supports single cells only.
59
//         NOTE: this patch only works for BIFF8 as BIFF5-7 use a different
60
//         external sheet reference structure
61
class Xls extends BaseReader
62
{
63
    // ParseXL definitions
64
    const XLS_BIFF8 = 0x0600;
65
    const XLS_BIFF7 = 0x0500;
66
    const XLS_WORKBOOKGLOBALS = 0x0005;
67
    const XLS_WORKSHEET = 0x0010;
68
69
    // record identifiers
70
    const XLS_TYPE_FORMULA = 0x0006;
71
    const XLS_TYPE_EOF = 0x000a;
72
    const XLS_TYPE_PROTECT = 0x0012;
73
    const XLS_TYPE_OBJECTPROTECT = 0x0063;
74
    const XLS_TYPE_SCENPROTECT = 0x00dd;
75
    const XLS_TYPE_PASSWORD = 0x0013;
76
    const XLS_TYPE_HEADER = 0x0014;
77
    const XLS_TYPE_FOOTER = 0x0015;
78
    const XLS_TYPE_EXTERNSHEET = 0x0017;
79
    const XLS_TYPE_DEFINEDNAME = 0x0018;
80
    const XLS_TYPE_VERTICALPAGEBREAKS = 0x001a;
81
    const XLS_TYPE_HORIZONTALPAGEBREAKS = 0x001b;
82
    const XLS_TYPE_NOTE = 0x001c;
83
    const XLS_TYPE_SELECTION = 0x001d;
84
    const XLS_TYPE_DATEMODE = 0x0022;
85
    const XLS_TYPE_EXTERNNAME = 0x0023;
86
    const XLS_TYPE_LEFTMARGIN = 0x0026;
87
    const XLS_TYPE_RIGHTMARGIN = 0x0027;
88
    const XLS_TYPE_TOPMARGIN = 0x0028;
89
    const XLS_TYPE_BOTTOMMARGIN = 0x0029;
90
    const XLS_TYPE_PRINTGRIDLINES = 0x002b;
91
    const XLS_TYPE_FILEPASS = 0x002f;
92
    const XLS_TYPE_FONT = 0x0031;
93
    const XLS_TYPE_CONTINUE = 0x003c;
94
    const XLS_TYPE_PANE = 0x0041;
95
    const XLS_TYPE_CODEPAGE = 0x0042;
96
    const XLS_TYPE_DEFCOLWIDTH = 0x0055;
97
    const XLS_TYPE_OBJ = 0x005d;
98
    const XLS_TYPE_COLINFO = 0x007d;
99
    const XLS_TYPE_IMDATA = 0x007f;
100
    const XLS_TYPE_SHEETPR = 0x0081;
101
    const XLS_TYPE_HCENTER = 0x0083;
102
    const XLS_TYPE_VCENTER = 0x0084;
103
    const XLS_TYPE_SHEET = 0x0085;
104
    const XLS_TYPE_PALETTE = 0x0092;
105
    const XLS_TYPE_SCL = 0x00a0;
106
    const XLS_TYPE_PAGESETUP = 0x00a1;
107
    const XLS_TYPE_MULRK = 0x00bd;
108
    const XLS_TYPE_MULBLANK = 0x00be;
109
    const XLS_TYPE_DBCELL = 0x00d7;
110
    const XLS_TYPE_XF = 0x00e0;
111
    const XLS_TYPE_MERGEDCELLS = 0x00e5;
112
    const XLS_TYPE_MSODRAWINGGROUP = 0x00eb;
113
    const XLS_TYPE_MSODRAWING = 0x00ec;
114
    const XLS_TYPE_SST = 0x00fc;
115
    const XLS_TYPE_LABELSST = 0x00fd;
116
    const XLS_TYPE_EXTSST = 0x00ff;
117
    const XLS_TYPE_EXTERNALBOOK = 0x01ae;
118
    const XLS_TYPE_DATAVALIDATIONS = 0x01b2;
119
    const XLS_TYPE_TXO = 0x01b6;
120
    const XLS_TYPE_HYPERLINK = 0x01b8;
121
    const XLS_TYPE_DATAVALIDATION = 0x01be;
122
    const XLS_TYPE_DIMENSION = 0x0200;
123
    const XLS_TYPE_BLANK = 0x0201;
124
    const XLS_TYPE_NUMBER = 0x0203;
125
    const XLS_TYPE_LABEL = 0x0204;
126
    const XLS_TYPE_BOOLERR = 0x0205;
127
    const XLS_TYPE_STRING = 0x0207;
128
    const XLS_TYPE_ROW = 0x0208;
129
    const XLS_TYPE_INDEX = 0x020b;
130
    const XLS_TYPE_ARRAY = 0x0221;
131
    const XLS_TYPE_DEFAULTROWHEIGHT = 0x0225;
132
    const XLS_TYPE_WINDOW2 = 0x023e;
133
    const XLS_TYPE_RK = 0x027e;
134
    const XLS_TYPE_STYLE = 0x0293;
135
    const XLS_TYPE_FORMAT = 0x041e;
136
    const XLS_TYPE_SHAREDFMLA = 0x04bc;
137
    const XLS_TYPE_BOF = 0x0809;
138
    const XLS_TYPE_SHEETPROTECTION = 0x0867;
139
    const XLS_TYPE_RANGEPROTECTION = 0x0868;
140
    const XLS_TYPE_SHEETLAYOUT = 0x0862;
141
    const XLS_TYPE_XFEXT = 0x087d;
142
    const XLS_TYPE_PAGELAYOUTVIEW = 0x088b;
143
    const XLS_TYPE_UNKNOWN = 0xffff;
144
145
    // Encryption type
146
    const MS_BIFF_CRYPTO_NONE = 0;
147
    const MS_BIFF_CRYPTO_XOR = 1;
148
    const MS_BIFF_CRYPTO_RC4 = 2;
149
150
    // Size of stream blocks when using RC4 encryption
151
    const REKEY_BLOCK = 0x400;
152
153
    /**
154
     * Summary Information stream data.
155
     *
156
     * @var string
157
     */
158
    private $summaryInformation;
159
160
    /**
161
     * Extended Summary Information stream data.
162
     *
163
     * @var string
164
     */
165
    private $documentSummaryInformation;
166
167
    /**
168
     * Workbook stream data. (Includes workbook globals substream as well as sheet substreams).
169
     *
170
     * @var string
171
     */
172
    private $data;
173
174
    /**
175
     * Size in bytes of $this->data.
176
     *
177
     * @var int
178
     */
179
    private $dataSize;
180
181
    /**
182
     * Current position in stream.
183
     *
184
     * @var int
185
     */
186
    private $pos;
187
188
    /**
189
     * Workbook to be returned by the reader.
190
     *
191
     * @var Spreadsheet
192
     */
193
    private $spreadsheet;
194
195
    /**
196
     * Worksheet that is currently being built by the reader.
197
     *
198
     * @var Worksheet
199
     */
200
    private $phpSheet;
201
202
    /**
203
     * BIFF version.
204
     *
205
     * @var int
206
     */
207
    private $version;
208
209
    /**
210
     * Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95)
211
     * For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'.
212
     *
213
     * @var string
214
     */
215
    private $codepage;
216
217
    /**
218
     * Shared formats.
219
     *
220
     * @var array
221
     */
222
    private $formats;
223
224
    /**
225
     * Shared fonts.
226
     *
227
     * @var array
228
     */
229
    private $objFonts;
230
231
    /**
232
     * Color palette.
233
     *
234
     * @var array
235
     */
236
    private $palette;
237
238
    /**
239
     * Worksheets.
240
     *
241
     * @var array
242
     */
243
    private $sheets;
244
245
    /**
246
     * External books.
247
     *
248
     * @var array
249
     */
250
    private $externalBooks;
251
252
    /**
253
     * REF structures. Only applies to BIFF8.
254
     *
255
     * @var array
256
     */
257
    private $ref;
258
259
    /**
260
     * External names.
261
     *
262
     * @var array
263
     */
264
    private $externalNames;
265
266
    /**
267
     * Defined names.
268
     *
269
     * @var array
270
     */
271
    private $definedname;
272
273
    /**
274
     * Shared strings. Only applies to BIFF8.
275
     *
276
     * @var array
277
     */
278
    private $sst;
279
280
    /**
281
     * Panes are frozen? (in sheet currently being read). See WINDOW2 record.
282
     *
283
     * @var bool
284
     */
285
    private $frozen;
286
287
    /**
288
     * Fit printout to number of pages? (in sheet currently being read). See SHEETPR record.
289
     *
290
     * @var bool
291
     */
292
    private $isFitToPages;
293
294
    /**
295
     * Objects. One OBJ record contributes with one entry.
296
     *
297
     * @var array
298
     */
299
    private $objs;
300
301
    /**
302
     * Text Objects. One TXO record corresponds with one entry.
303
     *
304
     * @var array
305
     */
306
    private $textObjects;
307
308
    /**
309
     * Cell Annotations (BIFF8).
310
     *
311
     * @var array
312
     */
313
    private $cellNotes;
314
315
    /**
316
     * The combined MSODRAWINGGROUP data.
317
     *
318
     * @var string
319
     */
320
    private $drawingGroupData;
321
322
    /**
323
     * The combined MSODRAWING data (per sheet).
324
     *
325
     * @var string
326
     */
327
    private $drawingData;
328
329
    /**
330
     * Keep track of XF index.
331
     *
332
     * @var int
333
     */
334
    private $xfIndex;
335
336
    /**
337
     * Mapping of XF index (that is a cell XF) to final index in cellXf collection.
338
     *
339
     * @var array
340
     */
341
    private $mapCellXfIndex;
342
343
    /**
344
     * Mapping of XF index (that is a style XF) to final index in cellStyleXf collection.
345
     *
346
     * @var array
347
     */
348
    private $mapCellStyleXfIndex;
349
350
    /**
351
     * The shared formulas in a sheet. One SHAREDFMLA record contributes with one value.
352
     *
353
     * @var array
354
     */
355
    private $sharedFormulas;
356
357
    /**
358
     * The shared formula parts in a sheet. One FORMULA record contributes with one value if it
359
     * refers to a shared formula.
360
     *
361
     * @var array
362
     */
363
    private $sharedFormulaParts;
364
365
    /**
366
     * The type of encryption in use.
367
     *
368
     * @var int
369
     */
370
    private $encryption = 0;
371
372
    /**
373
     * The position in the stream after which contents are encrypted.
374
     *
375
     * @var int
376
     */
377
    private $encryptionStartPos = false;
378
379
    /**
380
     * The current RC4 decryption object.
381
     *
382
     * @var Xls\RC4
383
     */
384
    private $rc4Key;
385
386
    /**
387
     * The position in the stream that the RC4 decryption object was left at.
388
     *
389
     * @var int
390
     */
391
    private $rc4Pos = 0;
392
393
    /**
394
     * The current MD5 context state.
395
     *
396
     * @var string
397
     */
398
    private $md5Ctxt;
399
400
    /**
401
     * @var int
402
     */
403
    private $textObjRef;
404
405
    /**
406
     * @var string
407
     */
408
    private $baseCell;
409
410
    /**
411
     * Create a new Xls Reader instance.
412
     */
413 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
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
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
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
1213
                            //        Bar!$A$1:$IV$2
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
1233
                        //        rows 1-2 repeat
1234
                        // 2. repeating columns
1235
                        //        formula looks like this: Sheet!$A$1:$B$65536
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
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
1243
                            //        Sheet!$A$1:$IV$2
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') {
1256
                                            // then we have repeating rows
1257
                                            $docSheet->getPageSetup()->setRowsToRepeatAtTop([$firstRow, $lastRow]);
1258
                                        } elseif ($firstRow == 1 and $lastRow == 65536) {
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;
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
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
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)
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;
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
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
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)
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
        for ($i = 0; $i < strlen($password); ++$i) {
1893
            $o = ord(substr($password, $i, 1));
1894
            $pwarray[2 * $i] = chr($o & 0xff);
1895
            $pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff);
1896
        }
1897
        $pwarray[2 * $i] = chr(0x80);
1898
        $pwarray[56] = chr(($i << 4) & 0xff);
1899
1900
        $md5 = new Xls\MD5();
1901
        $md5->add($pwarray);
1902
1903
        $mdContext1 = $md5->getContext();
1904
1905
        $offset = 0;
1906
        $keyoffset = 0;
1907
        $tocopy = 5;
1908
1909
        $md5->reset();
1910
1911
        while ($offset != 16) {
1912
            if ((64 - $offset) < 5) {
1913
                $tocopy = 64 - $offset;
1914
            }
1915
            for ($i = 0; $i <= $tocopy; ++$i) {
1916
                $pwarray[$offset + $i] = $mdContext1[$keyoffset + $i];
1917
            }
1918
            $offset += $tocopy;
1919
1920
            if ($offset == 64) {
1921
                $md5->add($pwarray);
1922
                $keyoffset = $tocopy;
1923
                $tocopy = 5 - $tocopy;
1924
                $offset = 0;
1925
1926
                continue;
1927
            }
1928
1929
            $keyoffset = 0;
1930
            $tocopy = 5;
1931
            for ($i = 0; $i < 16; ++$i) {
1932
                $pwarray[$offset + $i] = $docid[$i];
1933
            }
1934
            $offset += 16;
1935
        }
1936
1937
        $pwarray[16] = "\x80";
1938
        for ($i = 0; $i < 47; ++$i) {
1939
            $pwarray[17 + $i] = "\0";
1940
        }
1941
        $pwarray[56] = "\x80";
1942
        $pwarray[57] = "\x0a";
1943
1944
        $md5->add($pwarray);
1945
        $valContext = $md5->getContext();
1946
1947
        $key = $this->makeKey(0, $valContext);
1948
1949
        $salt = $key->RC4($salt_data);
1950
        $hashedsalt = $key->RC4($hashedsalt_data);
1951
1952
        $salt .= "\x80" . str_repeat("\0", 47);
1953
        $salt[56] = "\x80";
1954
1955
        $md5->reset();
1956
        $md5->add($salt);
1957
        $mdContext2 = $md5->getContext();
1958
1959
        return $mdContext2 == $hashedsalt;
1960
    }
1961
1962
    /**
1963
     * CODEPAGE.
1964
     *
1965
     * This record stores the text encoding used to write byte
1966
     * strings, stored as MS Windows code page identifier.
1967
     *
1968
     * --    "OpenOffice.org's Documentation of the Microsoft
1969
     *         Excel File Format"
1970
     */
1971 18
    private function readCodepage()
1972
    {
1973 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
1974 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
1975
1976
        // move stream pointer to next record
1977 18
        $this->pos += 4 + $length;
1978
1979
        // offset: 0; size: 2; code page identifier
1980 18
        $codepage = self::getUInt2d($recordData, 0);
1981
1982 18
        $this->codepage = CodePage::numberToName($codepage);
1983 18
    }
1984
1985
    /**
1986
     * DATEMODE.
1987
     *
1988
     * This record specifies the base date for displaying date
1989
     * values. All dates are stored as count of days past this
1990
     * base date. In BIFF2-BIFF4 this record is part of the
1991
     * Calculation Settings Block. In BIFF5-BIFF8 it is
1992
     * stored in the Workbook Globals Substream.
1993
     *
1994
     * --    "OpenOffice.org's Documentation of the Microsoft
1995
     *         Excel File Format"
1996
     */
1997 18
    private function readDateMode()
1998
    {
1999 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
2000 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2001
2002
        // move stream pointer to next record
2003 18
        $this->pos += 4 + $length;
2004
2005
        // offset: 0; size: 2; 0 = base 1900, 1 = base 1904
2006 18
        Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
2007 18
        if (ord($recordData[0]) == 1) {
2008
            Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
2009
        }
2010 18
    }
2011
2012
    /**
2013
     * Read a FONT record.
2014
     */
2015 18
    private function readFont()
2016
    {
2017 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
2018 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2019
2020
        // move stream pointer to next record
2021 18
        $this->pos += 4 + $length;
2022
2023 18
        if (!$this->readDataOnly) {
2024 17
            $objFont = new Font();
2025
2026
            // offset: 0; size: 2; height of the font (in twips = 1/20 of a point)
2027 17
            $size = self::getUInt2d($recordData, 0);
2028 17
            $objFont->setSize($size / 20);
2029
2030
            // offset: 2; size: 2; option flags
2031
            // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8)
2032
            // bit: 1; mask 0x0002; italic
2033 17
            $isItalic = (0x0002 & self::getUInt2d($recordData, 2)) >> 1;
2034 17
            if ($isItalic) {
2035 5
                $objFont->setItalic(true);
2036
            }
2037
2038
            // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8)
2039
            // bit: 3; mask 0x0008; strikethrough
2040 17
            $isStrike = (0x0008 & self::getUInt2d($recordData, 2)) >> 3;
2041 17
            if ($isStrike) {
2042
                $objFont->setStrikethrough(true);
2043
            }
2044
2045
            // offset: 4; size: 2; colour index
2046 17
            $colorIndex = self::getUInt2d($recordData, 4);
2047 17
            $objFont->colorIndex = $colorIndex;
2048
2049
            // offset: 6; size: 2; font weight
2050 17
            $weight = self::getUInt2d($recordData, 6);
2051
            switch ($weight) {
2052 17
                case 0x02BC:
2053 17
                    $objFont->setBold(true);
2054
2055 17
                    break;
2056
            }
2057
2058
            // offset: 8; size: 2; escapement type
2059 17
            $escapement = self::getUInt2d($recordData, 8);
2060
            switch ($escapement) {
2061 17
                case 0x0001:
2062
                    $objFont->setSuperscript(true);
2063
2064
                    break;
2065 17
                case 0x0002:
2066
                    $objFont->setSubscript(true);
2067
2068
                    break;
2069
            }
2070
2071
            // offset: 10; size: 1; underline type
2072 17
            $underlineType = ord($recordData[10]);
2073
            switch ($underlineType) {
2074 17
                case 0x00:
2075 17
                    break; // no underline
2076 2
                case 0x01:
2077 2
                    $objFont->setUnderline(Font::UNDERLINE_SINGLE);
2078
2079 2
                    break;
2080
                case 0x02:
2081
                    $objFont->setUnderline(Font::UNDERLINE_DOUBLE);
2082
2083
                    break;
2084
                case 0x21:
2085
                    $objFont->setUnderline(Font::UNDERLINE_SINGLEACCOUNTING);
2086
2087
                    break;
2088
                case 0x22:
2089
                    $objFont->setUnderline(Font::UNDERLINE_DOUBLEACCOUNTING);
2090
2091
                    break;
2092
            }
2093
2094
            // offset: 11; size: 1; font family
2095
            // offset: 12; size: 1; character set
2096
            // offset: 13; size: 1; not used
2097
            // offset: 14; size: var; font name
2098 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...
2099 17
                $string = self::readUnicodeStringShort(substr($recordData, 14));
2100
            } else {
2101
                $string = $this->readByteStringShort(substr($recordData, 14));
2102
            }
2103 17
            $objFont->setName($string['value']);
2104
2105 17
            $this->objFonts[] = $objFont;
2106
        }
2107 18
    }
2108
2109
    /**
2110
     * FORMAT.
2111
     *
2112
     * This record contains information about a number format.
2113
     * All FORMAT records occur together in a sequential list.
2114
     *
2115
     * In BIFF2-BIFF4 other records referencing a FORMAT record
2116
     * contain a zero-based index into this list. From BIFF5 on
2117
     * the FORMAT record contains the index itself that will be
2118
     * used by other records.
2119
     *
2120
     * --    "OpenOffice.org's Documentation of the Microsoft
2121
     *         Excel File Format"
2122
     */
2123 18
    private function readFormat()
2124
    {
2125 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
2126 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2127
2128
        // move stream pointer to next record
2129 18
        $this->pos += 4 + $length;
2130
2131 18
        if (!$this->readDataOnly) {
2132 17
            $indexCode = self::getUInt2d($recordData, 0);
2133
2134 17 View Code Duplication
            if ($this->version == self::XLS_BIFF8) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2135 17
                $string = self::readUnicodeStringLong(substr($recordData, 2));
2136
            } else {
2137
                // BIFF7
2138
                $string = $this->readByteStringShort(substr($recordData, 2));
2139
            }
2140
2141 17
            $formatString = $string['value'];
2142 17
            $this->formats[$indexCode] = $formatString;
2143
        }
2144 18
    }
2145
2146
    /**
2147
     * XF - Extended Format.
2148
     *
2149
     * This record contains formatting information for cells, rows, columns or styles.
2150
     * According to http://support.microsoft.com/kb/147732 there are always at least 15 cell style XF
2151
     * and 1 cell XF.
2152
     * Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF
2153
     * and XF record 15 is a cell XF
2154
     * We only read the first cell style XF and skip the remaining cell style XF records
2155
     * We read all cell XF records.
2156
     *
2157
     * --    "OpenOffice.org's Documentation of the Microsoft
2158
     *         Excel File Format"
2159
     */
2160 18
    private function readXf()
2161
    {
2162 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
2163 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2164
2165
        // move stream pointer to next record
2166 18
        $this->pos += 4 + $length;
2167
2168 18
        $objStyle = new Style();
2169
2170 18
        if (!$this->readDataOnly) {
2171
            // offset:  0; size: 2; Index to FONT record
2172 17
            if (self::getUInt2d($recordData, 0) < 4) {
2173 17
                $fontIndex = self::getUInt2d($recordData, 0);
2174
            } else {
2175
                // this has to do with that index 4 is omitted in all BIFF versions for some strange reason
2176
                // check the OpenOffice documentation of the FONT record
2177 17
                $fontIndex = self::getUInt2d($recordData, 0) - 1;
2178
            }
2179 17
            $objStyle->setFont($this->objFonts[$fontIndex]);
2180
2181
            // offset:  2; size: 2; Index to FORMAT record
2182 17
            $numberFormatIndex = self::getUInt2d($recordData, 2);
2183 17
            if (isset($this->formats[$numberFormatIndex])) {
2184
                // then we have user-defined format code
2185 15
                $numberFormat = ['formatCode' => $this->formats[$numberFormatIndex]];
2186 17
            } elseif (($code = NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') {
2187
                // then we have built-in format code
2188 17
                $numberFormat = ['formatCode' => $code];
2189
            } else {
2190
                // we set the general format code
2191 1
                $numberFormat = ['formatCode' => 'General'];
2192
            }
2193 17
            $objStyle->getNumberFormat()->setFormatCode($numberFormat['formatCode']);
2194
2195
            // offset:  4; size: 2; XF type, cell protection, and parent style XF
2196
            // bit 2-0; mask 0x0007; XF_TYPE_PROT
2197 17
            $xfTypeProt = self::getUInt2d($recordData, 4);
2198
            // bit 0; mask 0x01; 1 = cell is locked
2199 17
            $isLocked = (0x01 & $xfTypeProt) >> 0;
2200 17
            $objStyle->getProtection()->setLocked($isLocked ? Protection::PROTECTION_INHERIT : Protection::PROTECTION_UNPROTECTED);
2201
2202
            // bit 1; mask 0x02; 1 = Formula is hidden
2203 17
            $isHidden = (0x02 & $xfTypeProt) >> 1;
2204 17
            $objStyle->getProtection()->setHidden($isHidden ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED);
2205
2206
            // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF
2207 17
            $isCellStyleXf = (0x04 & $xfTypeProt) >> 2;
2208
2209
            // offset:  6; size: 1; Alignment and text break
2210
            // bit 2-0, mask 0x07; horizontal alignment
2211 17
            $horAlign = (0x07 & ord($recordData[6])) >> 0;
2212
            switch ($horAlign) {
2213 17
                case 0:
2214 17
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_GENERAL);
2215
2216 17
                    break;
2217 12
                case 1:
2218 2
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
2219
2220 2
                    break;
2221 12
                case 2:
2222 10
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
2223
2224 10
                    break;
2225 2
                case 3:
2226 2
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
2227
2228 2
                    break;
2229 2
                case 4:
2230
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_FILL);
2231
2232
                    break;
2233 2
                case 5:
2234 2
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_JUSTIFY);
2235
2236 2
                    break;
2237
                case 6:
2238
                    $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER_CONTINUOUS);
2239
2240
                    break;
2241
            }
2242
            // bit 3, mask 0x08; wrap text
2243 17
            $wrapText = (0x08 & ord($recordData[6])) >> 3;
2244
            switch ($wrapText) {
2245 17
                case 0:
2246 17
                    $objStyle->getAlignment()->setWrapText(false);
2247
2248 17
                    break;
2249 2
                case 1:
2250 2
                    $objStyle->getAlignment()->setWrapText(true);
2251
2252 2
                    break;
2253
            }
2254
            // bit 6-4, mask 0x70; vertical alignment
2255 17
            $vertAlign = (0x70 & ord($recordData[6])) >> 4;
2256
            switch ($vertAlign) {
2257 17
                case 0:
2258
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_TOP);
2259
2260
                    break;
2261 17
                case 1:
2262 2
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
2263
2264 2
                    break;
2265 17
                case 2:
2266 17
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_BOTTOM);
2267
2268 17
                    break;
2269
                case 3:
2270
                    $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_JUSTIFY);
2271
2272
                    break;
2273
            }
2274
2275 17
            if ($this->version == self::XLS_BIFF8) {
2276
                // offset:  7; size: 1; XF_ROTATION: Text rotation angle
2277 17
                $angle = ord($recordData[7]);
2278 17
                $rotation = 0;
2279 17
                if ($angle <= 90) {
2280 17
                    $rotation = $angle;
2281
                } elseif ($angle <= 180) {
2282
                    $rotation = 90 - $angle;
2283
                } elseif ($angle == 255) {
2284
                    $rotation = -165;
2285
                }
2286 17
                $objStyle->getAlignment()->setTextRotation($rotation);
2287
2288
                // offset:  8; size: 1; Indentation, shrink to cell size, and text direction
2289
                // bit: 3-0; mask: 0x0F; indent level
2290 17
                $indent = (0x0F & ord($recordData[8])) >> 0;
2291 17
                $objStyle->getAlignment()->setIndent($indent);
2292
2293
                // bit: 4; mask: 0x10; 1 = shrink content to fit into cell
2294 17
                $shrinkToFit = (0x10 & ord($recordData[8])) >> 4;
2295
                switch ($shrinkToFit) {
2296 17
                    case 0:
2297 17
                        $objStyle->getAlignment()->setShrinkToFit(false);
2298
2299 17
                        break;
2300 1
                    case 1:
2301 1
                        $objStyle->getAlignment()->setShrinkToFit(true);
2302
2303 1
                        break;
2304
                }
2305
2306
                // offset:  9; size: 1; Flags used for attribute groups
2307
2308
                // offset: 10; size: 4; Cell border lines and background area
2309
                // bit: 3-0; mask: 0x0000000F; left style
2310 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...
2311 17
                    $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle);
2312
                }
2313
                // bit: 7-4; mask: 0x000000F0; right style
2314 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...
2315 17
                    $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle);
2316
                }
2317
                // bit: 11-8; mask: 0x00000F00; top style
2318 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...
2319 17
                    $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle);
2320
                }
2321
                // bit: 15-12; mask: 0x0000F000; bottom style
2322 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...
2323 17
                    $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle);
2324
                }
2325
                // bit: 22-16; mask: 0x007F0000; left color
2326 17
                $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::getInt4d($recordData, 10)) >> 16;
2327
2328
                // bit: 29-23; mask: 0x3F800000; right color
2329 17
                $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::getInt4d($recordData, 10)) >> 23;
2330
2331
                // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom
2332 17
                $diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false;
2333
2334
                // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right
2335 17
                $diagonalUp = (0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false;
2336
2337 17
                if ($diagonalUp == false && $diagonalDown == false) {
2338 17
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE);
2339
                } elseif ($diagonalUp == true && $diagonalDown == false) {
2340
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP);
2341
                } elseif ($diagonalUp == false && $diagonalDown == true) {
2342
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN);
2343
                } elseif ($diagonalUp == true && $diagonalDown == true) {
2344
                    $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH);
2345
                }
2346
2347
                // offset: 14; size: 4;
2348
                // bit: 6-0; mask: 0x0000007F; top color
2349 17
                $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::getInt4d($recordData, 14)) >> 0;
2350
2351
                // bit: 13-7; mask: 0x00003F80; bottom color
2352 17
                $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::getInt4d($recordData, 14)) >> 7;
2353
2354
                // bit: 20-14; mask: 0x001FC000; diagonal color
2355 17
                $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::getInt4d($recordData, 14)) >> 14;
2356
2357
                // bit: 24-21; mask: 0x01E00000; diagonal style
2358 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...
2359 17
                    $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle);
2360
                }
2361
2362
                // bit: 31-26; mask: 0xFC000000 fill pattern
2363 17
                if ($fillType = Xls\Style\FillPattern::lookup((0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) {
2364 17
                    $objStyle->getFill()->setFillType($fillType);
2365
                }
2366
                // offset: 18; size: 2; pattern and background colour
2367
                // bit: 6-0; mask: 0x007F; color index for pattern color
2368 17
                $objStyle->getFill()->startcolorIndex = (0x007F & self::getUInt2d($recordData, 18)) >> 0;
2369
2370
                // bit: 13-7; mask: 0x3F80; color index for pattern background
2371 17
                $objStyle->getFill()->endcolorIndex = (0x3F80 & self::getUInt2d($recordData, 18)) >> 7;
2372
            } else {
2373
                // BIFF5
2374
2375
                // offset: 7; size: 1; Text orientation and flags
2376
                $orientationAndFlags = ord($recordData[7]);
2377
2378
                // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation
2379
                $xfOrientation = (0x03 & $orientationAndFlags) >> 0;
2380
                switch ($xfOrientation) {
2381
                    case 0:
2382
                        $objStyle->getAlignment()->setTextRotation(0);
2383
2384
                        break;
2385
                    case 1:
2386
                        $objStyle->getAlignment()->setTextRotation(-165);
2387
2388
                        break;
2389
                    case 2:
2390
                        $objStyle->getAlignment()->setTextRotation(90);
2391
2392
                        break;
2393
                    case 3:
2394
                        $objStyle->getAlignment()->setTextRotation(-90);
2395
2396
                        break;
2397
                }
2398
2399
                // offset: 8; size: 4; cell border lines and background area
2400
                $borderAndBackground = self::getInt4d($recordData, 8);
2401
2402
                // bit: 6-0; mask: 0x0000007F; color index for pattern color
2403
                $objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0;
2404
2405
                // bit: 13-7; mask: 0x00003F80; color index for pattern background
2406
                $objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7;
2407
2408
                // bit: 21-16; mask: 0x003F0000; fill pattern
2409
                $objStyle->getFill()->setFillType(Xls\Style\FillPattern::lookup((0x003F0000 & $borderAndBackground) >> 16));
2410
2411
                // bit: 24-22; mask: 0x01C00000; bottom line style
2412
                $objStyle->getBorders()->getBottom()->setBorderStyle(Xls\Style\Border::lookup((0x01C00000 & $borderAndBackground) >> 22));
2413
2414
                // bit: 31-25; mask: 0xFE000000; bottom line color
2415
                $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25;
2416
2417
                // offset: 12; size: 4; cell border lines
2418
                $borderLines = self::getInt4d($recordData, 12);
2419
2420
                // bit: 2-0; mask: 0x00000007; top line style
2421
                $objStyle->getBorders()->getTop()->setBorderStyle(Xls\Style\Border::lookup((0x00000007 & $borderLines) >> 0));
2422
2423
                // bit: 5-3; mask: 0x00000038; left line style
2424
                $objStyle->getBorders()->getLeft()->setBorderStyle(Xls\Style\Border::lookup((0x00000038 & $borderLines) >> 3));
2425
2426
                // bit: 8-6; mask: 0x000001C0; right line style
2427
                $objStyle->getBorders()->getRight()->setBorderStyle(Xls\Style\Border::lookup((0x000001C0 & $borderLines) >> 6));
2428
2429
                // bit: 15-9; mask: 0x0000FE00; top line color index
2430
                $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9;
2431
2432
                // bit: 22-16; mask: 0x007F0000; left line color index
2433
                $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16;
2434
2435
                // bit: 29-23; mask: 0x3F800000; right line color index
2436
                $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23;
2437
            }
2438
2439
            // add cellStyleXf or cellXf and update mapping
2440 17
            if ($isCellStyleXf) {
2441
                // we only read one style XF record which is always the first
2442 17
                if ($this->xfIndex == 0) {
2443 17
                    $this->spreadsheet->addCellStyleXf($objStyle);
2444 17
                    $this->mapCellStyleXfIndex[$this->xfIndex] = 0;
2445
                }
2446
            } else {
2447
                // we read all cell XF records
2448 17
                $this->spreadsheet->addCellXf($objStyle);
2449 17
                $this->mapCellXfIndex[$this->xfIndex] = count($this->spreadsheet->getCellXfCollection()) - 1;
2450
            }
2451
2452
            // update XF index for when we read next record
2453 17
            ++$this->xfIndex;
2454
        }
2455 18
    }
2456
2457 3
    private function readXfExt()
2458
    {
2459 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
2460 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2461
2462
        // move stream pointer to next record
2463 3
        $this->pos += 4 + $length;
2464
2465 3
        if (!$this->readDataOnly) {
2466
            // offset: 0; size: 2; 0x087D = repeated header
2467
2468
            // offset: 2; size: 2
2469
2470
            // offset: 4; size: 8; not used
2471
2472
            // offset: 12; size: 2; record version
2473
2474
            // offset: 14; size: 2; index to XF record which this record modifies
2475 3
            $ixfe = self::getUInt2d($recordData, 14);
2476
2477
            // offset: 16; size: 2; not used
2478
2479
            // offset: 18; size: 2; number of extension properties that follow
2480 3
            $cexts = self::getUInt2d($recordData, 18);
2481
2482
            // start reading the actual extension data
2483 3
            $offset = 20;
2484 3
            while ($offset < $length) {
2485
                // extension type
2486 3
                $extType = self::getUInt2d($recordData, $offset);
2487
2488
                // extension length
2489 3
                $cb = self::getUInt2d($recordData, $offset + 2);
2490
2491
                // extension data
2492 3
                $extData = substr($recordData, $offset + 4, $cb);
2493
2494
                switch ($extType) {
2495 3 View Code Duplication
                    case 4:        // fill start color
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
2608 3
                        $xclfType = self::getUInt2d($extData, 0); // color type
2609 3
                        $xclrValue = substr($extData, 4, 4); // color value (value based on color type)
2610
2611 3
                        if ($xclfType == 2) {
2612 3
                            $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
2613
2614
                            // modify the relevant style property
2615 3
                            if (isset($this->mapCellXfIndex[$ixfe])) {
2616 1
                                $font = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont();
2617 1
                                $font->getColor()->setRGB($rgb);
2618 1
                                unset($font->colorIndex); // normal color index does not apply, discard
2619
                            }
2620
                        }
2621
2622 3
                        break;
2623
                }
2624
2625 3
                $offset += $cb;
2626
            }
2627
        }
2628 3
    }
2629
2630
    /**
2631
     * Read STYLE record.
2632
     */
2633 18
    private function readStyle()
2634
    {
2635 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
2636 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2637
2638
        // move stream pointer to next record
2639 18
        $this->pos += 4 + $length;
2640
2641 18
        if (!$this->readDataOnly) {
2642
            // offset: 0; size: 2; index to XF record and flag for built-in style
2643 17
            $ixfe = self::getUInt2d($recordData, 0);
2644
2645
            // bit: 11-0; mask 0x0FFF; index to XF record
2646 17
            $xfIndex = (0x0FFF & $ixfe) >> 0;
2647
2648
            // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style
2649 17
            $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15);
2650
2651 17
            if ($isBuiltIn) {
2652
                // offset: 2; size: 1; identifier for built-in style
2653 17
                $builtInId = ord($recordData[2]);
2654
2655
                switch ($builtInId) {
2656 17
                    case 0x00:
2657
                        // currently, we are not using this for anything
2658 17
                        break;
2659
                    default:
2660 14
                        break;
2661
                }
2662
            }
2663
            // user-defined; not supported by PhpSpreadsheet
2664
        }
2665 18
    }
2666
2667
    /**
2668
     * Read PALETTE record.
2669
     */
2670 4
    private function readPalette()
2671
    {
2672 4
        $length = self::getUInt2d($this->data, $this->pos + 2);
2673 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2674
2675
        // move stream pointer to next record
2676 4
        $this->pos += 4 + $length;
2677
2678 4
        if (!$this->readDataOnly) {
2679
            // offset: 0; size: 2; number of following colors
2680 4
            $nm = self::getUInt2d($recordData, 0);
2681
2682
            // list of RGB colors
2683 4
            for ($i = 0; $i < $nm; ++$i) {
2684 4
                $rgb = substr($recordData, 2 + 4 * $i, 4);
2685 4
                $this->palette[] = self::readRGB($rgb);
2686
            }
2687
        }
2688 4
    }
2689
2690
    /**
2691
     * SHEET.
2692
     *
2693
     * This record is  located in the  Workbook Globals
2694
     * Substream  and represents a sheet inside the workbook.
2695
     * One SHEET record is written for each sheet. It stores the
2696
     * sheet name and a stream offset to the BOF record of the
2697
     * respective Sheet Substream within the Workbook Stream.
2698
     *
2699
     * --    "OpenOffice.org's Documentation of the Microsoft
2700
     *         Excel File Format"
2701
     */
2702 21
    private function readSheet()
2703
    {
2704 21
        $length = self::getUInt2d($this->data, $this->pos + 2);
2705 21
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2706
2707
        // offset: 0; size: 4; absolute stream position of the BOF record of the sheet
2708
        // NOTE: not encrypted
2709 21
        $rec_offset = self::getInt4d($this->data, $this->pos + 4);
2710
2711
        // move stream pointer to next record
2712 21
        $this->pos += 4 + $length;
2713
2714
        // offset: 4; size: 1; sheet state
2715 21
        switch (ord($recordData[4])) {
2716 21
            case 0x00:
2717 21
                $sheetState = Worksheet::SHEETSTATE_VISIBLE;
2718
2719 21
                break;
2720
            case 0x01:
2721
                $sheetState = Worksheet::SHEETSTATE_HIDDEN;
2722
2723
                break;
2724
            case 0x02:
2725
                $sheetState = Worksheet::SHEETSTATE_VERYHIDDEN;
2726
2727
                break;
2728
            default:
2729
                $sheetState = Worksheet::SHEETSTATE_VISIBLE;
2730
2731
                break;
2732
        }
2733
2734
        // offset: 5; size: 1; sheet type
2735 21
        $sheetType = ord($recordData[5]);
2736
2737
        // offset: 6; size: var; sheet name
2738 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...
2739 21
            $string = self::readUnicodeStringShort(substr($recordData, 6));
2740 21
            $rec_name = $string['value'];
2741
        } elseif ($this->version == self::XLS_BIFF7) {
2742
            $string = $this->readByteStringShort(substr($recordData, 6));
2743
            $rec_name = $string['value'];
2744
        }
2745
2746 21
        $this->sheets[] = [
2747 21
            'name' => $rec_name,
2748 21
            'offset' => $rec_offset,
2749 21
            'sheetState' => $sheetState,
2750 21
            'sheetType' => $sheetType,
2751
        ];
2752 21
    }
2753
2754
    /**
2755
     * Read EXTERNALBOOK record.
2756
     */
2757 4
    private function readExternalBook()
2758
    {
2759 4
        $length = self::getUInt2d($this->data, $this->pos + 2);
2760 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2761
2762
        // move stream pointer to next record
2763 4
        $this->pos += 4 + $length;
2764
2765
        // offset within record data
2766 4
        $offset = 0;
2767
2768
        // there are 4 types of records
2769 4
        if (strlen($recordData) > 4) {
2770
            // external reference
2771
            // offset: 0; size: 2; number of sheet names ($nm)
2772
            $nm = self::getUInt2d($recordData, 0);
2773
            $offset += 2;
2774
2775
            // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length)
2776
            $encodedUrlString = self::readUnicodeStringLong(substr($recordData, 2));
2777
            $offset += $encodedUrlString['size'];
2778
2779
            // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length)
2780
            $externalSheetNames = [];
2781
            for ($i = 0; $i < $nm; ++$i) {
2782
                $externalSheetNameString = self::readUnicodeStringLong(substr($recordData, $offset));
2783
                $externalSheetNames[] = $externalSheetNameString['value'];
2784
                $offset += $externalSheetNameString['size'];
2785
            }
2786
2787
            // store the record data
2788
            $this->externalBooks[] = [
2789
                'type' => 'external',
2790
                'encodedUrl' => $encodedUrlString['value'],
2791
                'externalSheetNames' => $externalSheetNames,
2792
            ];
2793 4
        } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) {
2794
            // internal reference
2795
            // offset: 0; size: 2; number of sheet in this document
2796
            // offset: 2; size: 2; 0x01 0x04
2797 4
            $this->externalBooks[] = [
2798 4
                'type' => 'internal',
2799
            ];
2800
        } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) {
2801
            // add-in function
2802
            // offset: 0; size: 2; 0x0001
2803
            $this->externalBooks[] = [
2804
                'type' => 'addInFunction',
2805
            ];
2806
        } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) {
2807
            // DDE links, OLE links
2808
            // offset: 0; size: 2; 0x0000
2809
            // offset: 2; size: var; encoded source document name
2810
            $this->externalBooks[] = [
2811
                'type' => 'DDEorOLE',
2812
            ];
2813
        }
2814 4
    }
2815
2816
    /**
2817
     * Read EXTERNNAME record.
2818
     */
2819
    private function readExternName()
2820
    {
2821
        $length = self::getUInt2d($this->data, $this->pos + 2);
2822
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2823
2824
        // move stream pointer to next record
2825
        $this->pos += 4 + $length;
2826
2827
        // external sheet references provided for named cells
2828
        if ($this->version == self::XLS_BIFF8) {
2829
            // offset: 0; size: 2; options
2830
            $options = self::getUInt2d($recordData, 0);
2831
2832
            // offset: 2; size: 2;
2833
2834
            // offset: 4; size: 2; not used
2835
2836
            // offset: 6; size: var
2837
            $nameString = self::readUnicodeStringShort(substr($recordData, 6));
2838
2839
            // offset: var; size: var; formula data
2840
            $offset = 6 + $nameString['size'];
2841
            $formula = $this->getFormulaFromStructure(substr($recordData, $offset));
2842
2843
            $this->externalNames[] = [
2844
                'name' => $nameString['value'],
2845
                'formula' => $formula,
2846
            ];
2847
        }
2848
    }
2849
2850
    /**
2851
     * Read EXTERNSHEET record.
2852
     */
2853 4
    private function readExternSheet()
2854
    {
2855 4
        $length = self::getUInt2d($this->data, $this->pos + 2);
2856 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2857
2858
        // move stream pointer to next record
2859 4
        $this->pos += 4 + $length;
2860
2861
        // external sheet references provided for named cells
2862 4
        if ($this->version == self::XLS_BIFF8) {
2863
            // offset: 0; size: 2; number of following ref structures
2864 4
            $nm = self::getUInt2d($recordData, 0);
2865 4
            for ($i = 0; $i < $nm; ++$i) {
2866 4
                $this->ref[] = [
2867
                    // offset: 2 + 6 * $i; index to EXTERNALBOOK record
2868 4
                    'externalBookIndex' => self::getUInt2d($recordData, 2 + 6 * $i),
2869
                    // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record
2870 4
                    'firstSheetIndex' => self::getUInt2d($recordData, 4 + 6 * $i),
2871
                    // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record
2872 4
                    'lastSheetIndex' => self::getUInt2d($recordData, 6 + 6 * $i),
2873
                ];
2874
            }
2875
        }
2876 4
    }
2877
2878
    /**
2879
     * DEFINEDNAME.
2880
     *
2881
     * This record is part of a Link Table. It contains the name
2882
     * and the token array of an internal defined name. Token
2883
     * arrays of defined names contain tokens with aberrant
2884
     * token classes.
2885
     *
2886
     * --    "OpenOffice.org's Documentation of the Microsoft
2887
     *         Excel File Format"
2888
     */
2889 1
    private function readDefinedName()
2890
    {
2891 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
2892 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
2893
2894
        // move stream pointer to next record
2895 1
        $this->pos += 4 + $length;
2896
2897 1
        if ($this->version == self::XLS_BIFF8) {
2898
            // retrieves named cells
2899
2900
            // offset: 0; size: 2; option flags
2901 1
            $opts = self::getUInt2d($recordData, 0);
2902
2903
            // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name
2904 1
            $isBuiltInName = (0x0020 & $opts) >> 5;
2905
2906
            // offset: 2; size: 1; keyboard shortcut
2907
2908
            // offset: 3; size: 1; length of the name (character count)
2909 1
            $nlen = ord($recordData[3]);
2910
2911
            // offset: 4; size: 2; size of the formula data (it can happen that this is zero)
2912
            // note: there can also be additional data, this is not included in $flen
2913 1
            $flen = self::getUInt2d($recordData, 4);
2914
2915
            // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based)
2916 1
            $scope = self::getUInt2d($recordData, 8);
2917
2918
            // offset: 14; size: var; Name (Unicode string without length field)
2919 1
            $string = self::readUnicodeString(substr($recordData, 14), $nlen);
2920
2921
            // offset: var; size: $flen; formula data
2922 1
            $offset = 14 + $string['size'];
2923 1
            $formulaStructure = pack('v', $flen) . substr($recordData, $offset);
2924
2925
            try {
2926 1
                $formula = $this->getFormulaFromStructure($formulaStructure);
2927
            } catch (PhpSpreadsheetException $e) {
2928
                $formula = '';
2929
            }
2930
2931 1
            $this->definedname[] = [
2932 1
                'isBuiltInName' => $isBuiltInName,
2933 1
                'name' => $string['value'],
2934 1
                'formula' => $formula,
2935 1
                'scope' => $scope,
2936
            ];
2937
        }
2938 1
    }
2939
2940
    /**
2941
     * Read MSODRAWINGGROUP record.
2942
     */
2943 3 View Code Duplication
    private function readMsoDrawingGroup()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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

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

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

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

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

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

Loading history...
3234
    {
3235
        $length = self::getUInt2d($this->data, $this->pos + 2);
3236
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3237
3238
        // move stream pointer to next record
3239
        $this->pos += 4 + $length;
3240
3241
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
3242
            // offset: 0; size: 2; number of the following column index structures
3243
            $nm = self::getUInt2d($recordData, 0);
3244
3245
            // offset: 2; size: 6 * $nm; list of $nm row index structures
3246
            for ($i = 0; $i < $nm; ++$i) {
3247
                $c = self::getUInt2d($recordData, 2 + 6 * $i);
3248
                $rf = self::getUInt2d($recordData, 2 + 6 * $i + 2);
3249
                $rl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
3250
3251
                // not sure why two row indexes are necessary?
3252
                $this->phpSheet->setBreakByColumnAndRow($c + 1, $rf, Worksheet::BREAK_COLUMN);
3253
            }
3254
        }
3255
    }
3256
3257
    /**
3258
     * Read HEADER record.
3259
     */
3260 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...
3261
    {
3262 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3263 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3264
3265
        // move stream pointer to next record
3266 18
        $this->pos += 4 + $length;
3267
3268 18
        if (!$this->readDataOnly) {
3269
            // offset: 0; size: var
3270
            // realized that $recordData can be empty even when record exists
3271 17
            if ($recordData) {
3272 3
                if ($this->version == self::XLS_BIFF8) {
3273 3
                    $string = self::readUnicodeStringLong($recordData);
3274
                } else {
3275
                    $string = $this->readByteStringShort($recordData);
3276
                }
3277
3278 3
                $this->phpSheet->getHeaderFooter()->setOddHeader($string['value']);
3279 3
                $this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']);
3280
            }
3281
        }
3282 18
    }
3283
3284
    /**
3285
     * Read FOOTER record.
3286
     */
3287 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...
3288
    {
3289 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3290 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3291
3292
        // move stream pointer to next record
3293 18
        $this->pos += 4 + $length;
3294
3295 18
        if (!$this->readDataOnly) {
3296
            // offset: 0; size: var
3297
            // realized that $recordData can be empty even when record exists
3298 17
            if ($recordData) {
3299 3
                if ($this->version == self::XLS_BIFF8) {
3300 3
                    $string = self::readUnicodeStringLong($recordData);
3301
                } else {
3302
                    $string = $this->readByteStringShort($recordData);
3303
                }
3304 3
                $this->phpSheet->getHeaderFooter()->setOddFooter($string['value']);
3305 3
                $this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']);
3306
            }
3307
        }
3308 18
    }
3309
3310
    /**
3311
     * Read HCENTER record.
3312
     */
3313 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...
3314
    {
3315 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3316 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3317
3318
        // move stream pointer to next record
3319 18
        $this->pos += 4 + $length;
3320
3321 18
        if (!$this->readDataOnly) {
3322
            // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally
3323 17
            $isHorizontalCentered = (bool) self::getUInt2d($recordData, 0);
3324
3325 17
            $this->phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered);
3326
        }
3327 18
    }
3328
3329
    /**
3330
     * Read VCENTER record.
3331
     */
3332 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...
3333
    {
3334 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3335 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3336
3337
        // move stream pointer to next record
3338 18
        $this->pos += 4 + $length;
3339
3340 18
        if (!$this->readDataOnly) {
3341
            // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered
3342 17
            $isVerticalCentered = (bool) self::getUInt2d($recordData, 0);
3343
3344 17
            $this->phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered);
3345
        }
3346 18
    }
3347
3348
    /**
3349
     * Read LEFTMARGIN record.
3350
     */
3351 5
    private function readLeftMargin()
3352
    {
3353 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
3354 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3355
3356
        // move stream pointer to next record
3357 5
        $this->pos += 4 + $length;
3358
3359 5
        if (!$this->readDataOnly) {
3360
            // offset: 0; size: 8
3361 5
            $this->phpSheet->getPageMargins()->setLeft(self::extractNumber($recordData));
3362
        }
3363 5
    }
3364
3365
    /**
3366
     * Read RIGHTMARGIN record.
3367
     */
3368 5
    private function readRightMargin()
3369
    {
3370 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
3371 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3372
3373
        // move stream pointer to next record
3374 5
        $this->pos += 4 + $length;
3375
3376 5
        if (!$this->readDataOnly) {
3377
            // offset: 0; size: 8
3378 5
            $this->phpSheet->getPageMargins()->setRight(self::extractNumber($recordData));
3379
        }
3380 5
    }
3381
3382
    /**
3383
     * Read TOPMARGIN record.
3384
     */
3385 5
    private function readTopMargin()
3386
    {
3387 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
3388 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3389
3390
        // move stream pointer to next record
3391 5
        $this->pos += 4 + $length;
3392
3393 5
        if (!$this->readDataOnly) {
3394
            // offset: 0; size: 8
3395 5
            $this->phpSheet->getPageMargins()->setTop(self::extractNumber($recordData));
3396
        }
3397 5
    }
3398
3399
    /**
3400
     * Read BOTTOMMARGIN record.
3401
     */
3402 5
    private function readBottomMargin()
3403
    {
3404 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
3405 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3406
3407
        // move stream pointer to next record
3408 5
        $this->pos += 4 + $length;
3409
3410 5
        if (!$this->readDataOnly) {
3411
            // offset: 0; size: 8
3412 5
            $this->phpSheet->getPageMargins()->setBottom(self::extractNumber($recordData));
3413
        }
3414 5
    }
3415
3416
    /**
3417
     * Read PAGESETUP record.
3418
     */
3419 18
    private function readPageSetup()
3420
    {
3421 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
3422 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
3423
3424
        // move stream pointer to next record
3425 18
        $this->pos += 4 + $length;
3426
3427 18
        if (!$this->readDataOnly) {
3428
            // offset: 0; size: 2; paper size
3429 17
            $paperSize = self::getUInt2d($recordData, 0);
3430
3431
            // offset: 2; size: 2; scaling factor
3432 17
            $scale = self::getUInt2d($recordData, 2);
3433
3434
            // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed
3435 17
            $fitToWidth = self::getUInt2d($recordData, 6);
3436
3437
            // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed
3438 17
            $fitToHeight = self::getUInt2d($recordData, 8);
3439
3440
            // offset: 10; size: 2; option flags
3441
3442
            // bit: 1; mask: 0x0002; 0=landscape, 1=portrait
3443 17
            $isPortrait = (0x0002 & self::getUInt2d($recordData, 10)) >> 1;
3444
3445
            // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init
3446
            // when this bit is set, do not use flags for those properties
3447 17
            $isNotInit = (0x0004 & self::getUInt2d($recordData, 10)) >> 2;
3448
3449 17
            if (!$isNotInit) {
3450 16
                $this->phpSheet->getPageSetup()->setPaperSize($paperSize);
3451
                switch ($isPortrait) {
3452 16
                    case 0:
3453 2
                        $this->phpSheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE);
3454
3455 2
                        break;
3456 16
                    case 1:
3457 16
                        $this->phpSheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_PORTRAIT);
3458
3459 16
                        break;
3460
                }
3461
3462 16
                $this->phpSheet->getPageSetup()->setScale($scale, false);
3463 16
                $this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages);
3464 16
                $this->phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false);
3465 16
                $this->phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false);
3466
            }
3467
3468
            // offset: 16; size: 8; header margin (IEEE 754 floating-point value)
3469 17
            $marginHeader = self::extractNumber(substr($recordData, 16, 8));
3470 17
            $this->phpSheet->getPageMargins()->setHeader($marginHeader);
3471
3472
            // offset: 24; size: 8; footer margin (IEEE 754 floating-point value)
3473 17
            $marginFooter = self::extractNumber(substr($recordData, 24, 8));
3474 17
            $this->phpSheet->getPageMargins()->setFooter($marginFooter);
3475
        }
3476 18
    }
3477
3478
    /**
3479
     * PROTECT - Sheet protection (BIFF2 through BIFF8)
3480
     *   if this record is omitted, then it also means no sheet protection.
3481
     */
3482 1 View Code Duplication
    private function readProtect()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
4294
    {
4295 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
4296
4297
        // get spliced record data
4298 3
        $splicedRecordData = $this->getSplicedRecordData();
4299 3
        $recordData = $splicedRecordData['recordData'];
4300
4301 3
        $this->drawingData .= $recordData;
4302 3
    }
4303
4304
    /**
4305
     * Read OBJ record.
4306
     */
4307 3
    private function readObj()
4308
    {
4309 3
        $length = self::getUInt2d($this->data, $this->pos + 2);
4310 3
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4311
4312
        // move stream pointer to next record
4313 3
        $this->pos += 4 + $length;
4314
4315 3
        if ($this->readDataOnly || $this->version != self::XLS_BIFF8) {
4316
            return;
4317
        }
4318
4319
        // recordData consists of an array of subrecords looking like this:
4320
        //    ft: 2 bytes; ftCmo type (0x15)
4321
        //    cb: 2 bytes; size in bytes of ftCmo data
4322
        //    ot: 2 bytes; Object Type
4323
        //    id: 2 bytes; Object id number
4324
        //    grbit: 2 bytes; Option Flags
4325
        //    data: var; subrecord data
4326
4327
        // for now, we are just interested in the second subrecord containing the object type
4328 3
        $ftCmoType = self::getUInt2d($recordData, 0);
4329 3
        $cbCmoSize = self::getUInt2d($recordData, 2);
4330 3
        $otObjType = self::getUInt2d($recordData, 4);
4331 3
        $idObjID = self::getUInt2d($recordData, 6);
4332 3
        $grbitOpts = self::getUInt2d($recordData, 6);
4333
4334 3
        $this->objs[] = [
4335 3
            'ftCmoType' => $ftCmoType,
4336 3
            'cbCmoSize' => $cbCmoSize,
4337 3
            'otObjType' => $otObjType,
4338 3
            'idObjID' => $idObjID,
4339 3
            'grbitOpts' => $grbitOpts,
4340
        ];
4341 3
        $this->textObjRef = $idObjID;
4342 3
    }
4343
4344
    /**
4345
     * Read WINDOW2 record.
4346
     */
4347 18
    private function readWindow2()
4348
    {
4349 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
4350 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4351
4352
        // move stream pointer to next record
4353 18
        $this->pos += 4 + $length;
4354
4355
        // offset: 0; size: 2; option flags
4356 18
        $options = self::getUInt2d($recordData, 0);
4357
4358
        // offset: 2; size: 2; index to first visible row
4359 18
        $firstVisibleRow = self::getUInt2d($recordData, 2);
4360
4361
        // offset: 4; size: 2; index to first visible colum
4362 18
        $firstVisibleColumn = self::getUInt2d($recordData, 4);
4363 18
        if ($this->version === self::XLS_BIFF8) {
4364
            // offset:  8; size: 2; not used
4365
            // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%)
4366
            // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%)
4367
            // offset: 14; size: 4; not used
4368 18
            $zoomscaleInPageBreakPreview = self::getUInt2d($recordData, 10);
4369 18
            if ($zoomscaleInPageBreakPreview === 0) {
4370 18
                $zoomscaleInPageBreakPreview = 60;
4371
            }
4372 18
            $zoomscaleInNormalView = self::getUInt2d($recordData, 12);
4373 18
            if ($zoomscaleInNormalView === 0) {
4374 17
                $zoomscaleInNormalView = 100;
4375
            }
4376
        }
4377
4378
        // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines
4379 18
        $showGridlines = (bool) ((0x0002 & $options) >> 1);
4380 18
        $this->phpSheet->setShowGridlines($showGridlines);
4381
4382
        // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers
4383 18
        $showRowColHeaders = (bool) ((0x0004 & $options) >> 2);
4384 18
        $this->phpSheet->setShowRowColHeaders($showRowColHeaders);
4385
4386
        // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen
4387 18
        $this->frozen = (bool) ((0x0008 & $options) >> 3);
4388
4389
        // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left
4390 18
        $this->phpSheet->setRightToLeft((bool) ((0x0040 & $options) >> 6));
4391
4392
        // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active
4393 18
        $isActive = (bool) ((0x0400 & $options) >> 10);
4394 18
        if ($isActive) {
4395 15
            $this->spreadsheet->setActiveSheetIndex($this->spreadsheet->getIndex($this->phpSheet));
4396
        }
4397
4398
        // bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view
4399 18
        $isPageBreakPreview = (bool) ((0x0800 & $options) >> 11);
4400
4401
        //FIXME: set $firstVisibleRow and $firstVisibleColumn
4402
4403 18
        if ($this->phpSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_PAGE_LAYOUT) {
4404
            //NOTE: this setting is inferior to page layout view(Excel2007-)
4405 18
            $view = $isPageBreakPreview ? SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW : SheetView::SHEETVIEW_NORMAL;
4406 18
            $this->phpSheet->getSheetView()->setView($view);
4407 18
            if ($this->version === self::XLS_BIFF8) {
4408 18
                $zoomScale = $isPageBreakPreview ? $zoomscaleInPageBreakPreview : $zoomscaleInNormalView;
4409 18
                $this->phpSheet->getSheetView()->setZoomScale($zoomScale);
4410 18
                $this->phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView);
4411
            }
4412
        }
4413 18
    }
4414
4415
    /**
4416
     * Read PLV Record(Created by Excel2007 or upper).
4417
     */
4418 4
    private function readPageLayoutView()
4419
    {
4420 4
        $length = self::getUInt2d($this->data, $this->pos + 2);
4421 4
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4422
4423
        // move stream pointer to next record
4424 4
        $this->pos += 4 + $length;
4425
4426
        // offset: 0; size: 2; rt
4427
        //->ignore
4428 4
        $rt = self::getUInt2d($recordData, 0);
4429
        // offset: 2; size: 2; grbitfr
4430
        //->ignore
4431 4
        $grbitFrt = self::getUInt2d($recordData, 2);
4432
        // offset: 4; size: 8; reserved
4433
        //->ignore
4434
4435
        // offset: 12; size 2; zoom scale
4436 4
        $wScalePLV = self::getUInt2d($recordData, 12);
4437
        // offset: 14; size 2; grbit
4438 4
        $grbit = self::getUInt2d($recordData, 14);
4439
4440
        // decomprise grbit
4441 4
        $fPageLayoutView = $grbit & 0x01;
4442 4
        $fRulerVisible = ($grbit >> 1) & 0x01; //no support
4443 4
        $fWhitespaceHidden = ($grbit >> 3) & 0x01; //no support
4444
4445 4
        if ($fPageLayoutView === 1) {
4446
            $this->phpSheet->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_LAYOUT);
4447
            $this->phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT
4448
        }
4449
        //otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW.
4450 4
    }
4451
4452
    /**
4453
     * Read SCL record.
4454
     */
4455
    private function readScl()
4456
    {
4457
        $length = self::getUInt2d($this->data, $this->pos + 2);
4458
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4459
4460
        // move stream pointer to next record
4461
        $this->pos += 4 + $length;
4462
4463
        // offset: 0; size: 2; numerator of the view magnification
4464
        $numerator = self::getUInt2d($recordData, 0);
4465
4466
        // offset: 2; size: 2; numerator of the view magnification
4467
        $denumerator = self::getUInt2d($recordData, 2);
4468
4469
        // set the zoom scale (in percent)
4470
        $this->phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator);
4471
    }
4472
4473
    /**
4474
     * Read PANE record.
4475
     */
4476
    private function readPane()
4477
    {
4478
        $length = self::getUInt2d($this->data, $this->pos + 2);
4479
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4480
4481
        // move stream pointer to next record
4482
        $this->pos += 4 + $length;
4483
4484
        if (!$this->readDataOnly) {
4485
            // offset: 0; size: 2; position of vertical split
4486
            $px = self::getUInt2d($recordData, 0);
4487
4488
            // offset: 2; size: 2; position of horizontal split
4489
            $py = self::getUInt2d($recordData, 2);
4490
4491
            if ($this->frozen) {
4492
                // frozen panes
4493
                $this->phpSheet->freezePane(Coordinate::stringFromColumnIndex($px + 1) . ($py + 1));
4494
            }
4495
            // unfrozen panes; split windows; not supported by PhpSpreadsheet core
4496
        }
4497
    }
4498
4499
    /**
4500
     * Read SELECTION record. There is one such record for each pane in the sheet.
4501
     */
4502 18
    private function readSelection()
4503
    {
4504 18
        $length = self::getUInt2d($this->data, $this->pos + 2);
4505 18
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4506
4507
        // move stream pointer to next record
4508 18
        $this->pos += 4 + $length;
4509
4510 18
        if (!$this->readDataOnly) {
4511
            // offset: 0; size: 1; pane identifier
4512 17
            $paneId = ord($recordData[0]);
4513
4514
            // offset: 1; size: 2; index to row of the active cell
4515 17
            $r = self::getUInt2d($recordData, 1);
4516
4517
            // offset: 3; size: 2; index to column of the active cell
4518 17
            $c = self::getUInt2d($recordData, 3);
4519
4520
            // offset: 5; size: 2; index into the following cell range list to the
4521
            //  entry that contains the active cell
4522 17
            $index = self::getUInt2d($recordData, 5);
4523
4524
            // offset: 7; size: var; cell range address list containing all selected cell ranges
4525 17
            $data = substr($recordData, 7);
4526 17
            $cellRangeAddressList = $this->readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax
4527
4528 17
            $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0];
4529
4530
            // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!)
4531 17
            if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) {
4532
                $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells);
4533
            }
4534
4535
            // first row '1' + last row '65536' indicates that full column is selected
4536 17
            if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) {
4537
                $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells);
4538
            }
4539
4540
            // first column 'A' + last column 'IV' indicates that full row is selected
4541 17
            if (preg_match('/^(A[0-9]+\:)IV([0-9]+)$/', $selectedCells)) {
4542 2
                $selectedCells = preg_replace('/^(A[0-9]+\:)IV([0-9]+)$/', '${1}XFD${2}', $selectedCells);
4543
            }
4544
4545 17
            $this->phpSheet->setSelectedCells($selectedCells);
4546
        }
4547 18
    }
4548
4549 11
    private function includeCellRangeFiltered($cellRangeAddress)
4550
    {
4551 11
        $includeCellRange = true;
4552 11
        if ($this->getReadFilter() !== null) {
4553 11
            $includeCellRange = false;
4554 11
            $rangeBoundaries = Coordinate::getRangeBoundaries($cellRangeAddress);
4555 11
            ++$rangeBoundaries[1][0];
4556 11
            for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; ++$row) {
4557 11
                for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; ++$column) {
4558 11
                    if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
4559 11
                        $includeCellRange = true;
4560
4561 11
                        break 2;
4562
                    }
4563
                }
4564
            }
4565
        }
4566
4567 11
        return $includeCellRange;
4568
    }
4569
4570
    /**
4571
     * MERGEDCELLS.
4572
     *
4573
     * This record contains the addresses of merged cell ranges
4574
     * in the current sheet.
4575
     *
4576
     * --    "OpenOffice.org's Documentation of the Microsoft
4577
     *         Excel File Format"
4578
     */
4579 12
    private function readMergedCells()
4580
    {
4581 12
        $length = self::getUInt2d($this->data, $this->pos + 2);
4582 12
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4583
4584
        // move stream pointer to next record
4585 12
        $this->pos += 4 + $length;
4586
4587 12
        if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
4588 11
            $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData);
4589 11
            foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) {
4590 11
                if ((strpos($cellRangeAddress, ':') !== false) &&
4591 11
                    ($this->includeCellRangeFiltered($cellRangeAddress))) {
4592 11
                    $this->phpSheet->mergeCells($cellRangeAddress);
4593
                }
4594
            }
4595
        }
4596 12
    }
4597
4598
    /**
4599
     * Read HYPERLINK record.
4600
     */
4601 2
    private function readHyperLink()
4602
    {
4603 2
        $length = self::getUInt2d($this->data, $this->pos + 2);
4604 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4605
4606
        // move stream pointer forward to next record
4607 2
        $this->pos += 4 + $length;
4608
4609 2
        if (!$this->readDataOnly) {
4610
            // offset: 0; size: 8; cell range address of all cells containing this hyperlink
4611
            try {
4612 2
                $cellRange = $this->readBIFF8CellRangeAddressFixed($recordData);
4613
            } catch (PhpSpreadsheetException $e) {
4614
                return;
4615
            }
4616
4617
            // offset: 8, size: 16; GUID of StdLink
4618
4619
            // offset: 24, size: 4; unknown value
4620
4621
            // offset: 28, size: 4; option flags
4622
            // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL
4623 2
            $isFileLinkOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 0;
4624
4625
            // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL
4626 2
            $isAbsPathOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 1;
4627
4628
            // bit: 2 (and 4); mask: 0x00000014; 0 = no description
4629 2
            $hasDesc = (0x00000014 & self::getUInt2d($recordData, 28)) >> 2;
4630
4631
            // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text
4632 2
            $hasText = (0x00000008 & self::getUInt2d($recordData, 28)) >> 3;
4633
4634
            // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame
4635 2
            $hasFrame = (0x00000080 & self::getUInt2d($recordData, 28)) >> 7;
4636
4637
            // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name)
4638 2
            $isUNC = (0x00000100 & self::getUInt2d($recordData, 28)) >> 8;
4639
4640
            // offset within record data
4641 2
            $offset = 32;
4642
4643 2
            if ($hasDesc) {
4644
                // offset: 32; size: var; character count of description text
4645 1
                $dl = self::getInt4d($recordData, 32);
4646
                // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated
4647 1
                $desc = self::encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false);
4648 1
                $offset += 4 + 2 * $dl;
4649
            }
4650 2
            if ($hasFrame) {
4651
                $fl = self::getInt4d($recordData, $offset);
4652
                $offset += 4 + 2 * $fl;
4653
            }
4654
4655
            // detect type of hyperlink (there are 4 types)
4656 2
            $hyperlinkType = null;
4657
4658 2
            if ($isUNC) {
4659
                $hyperlinkType = 'UNC';
4660 2
            } elseif (!$isFileLinkOrUrl) {
4661 2
                $hyperlinkType = 'workbook';
4662 2
            } elseif (ord($recordData[$offset]) == 0x03) {
4663
                $hyperlinkType = 'local';
4664 2
            } elseif (ord($recordData[$offset]) == 0xE0) {
4665 2
                $hyperlinkType = 'URL';
4666
            }
4667
4668
            switch ($hyperlinkType) {
4669 2
                case 'URL':
4670
                    // section 5.58.2: Hyperlink containing a URL
4671
                    // e.g. http://example.org/index.php
4672
4673
                    // offset: var; size: 16; GUID of URL Moniker
4674 2
                    $offset += 16;
4675
                    // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word
4676 2
                    $us = self::getInt4d($recordData, $offset);
4677 2
                    $offset += 4;
4678
                    // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated
4679 2
                    $url = self::encodeUTF16(substr($recordData, $offset, $us - 2), false);
4680 2
                    $nullOffset = strpos($url, 0x00);
4681 2
                    if ($nullOffset) {
4682 1
                        $url = substr($url, 0, $nullOffset);
4683
                    }
4684 2
                    $url .= $hasText ? '#' : '';
4685 2
                    $offset += $us;
4686
4687 2
                    break;
4688 2
                case 'local':
4689
                    // section 5.58.3: Hyperlink to local file
4690
                    // examples:
4691
                    //   mydoc.txt
4692
                    //   ../../somedoc.xls#Sheet!A1
4693
4694
                    // offset: var; size: 16; GUI of File Moniker
4695
                    $offset += 16;
4696
4697
                    // offset: var; size: 2; directory up-level count.
4698
                    $upLevelCount = self::getUInt2d($recordData, $offset);
4699
                    $offset += 2;
4700
4701
                    // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word
4702
                    $sl = self::getInt4d($recordData, $offset);
4703
                    $offset += 4;
4704
4705
                    // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string)
4706
                    $shortenedFilePath = substr($recordData, $offset, $sl);
4707
                    $shortenedFilePath = self::encodeUTF16($shortenedFilePath, true);
4708
                    $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero
4709
4710
                    $offset += $sl;
4711
4712
                    // offset: var; size: 24; unknown sequence
4713
                    $offset += 24;
4714
4715
                    // extended file path
4716
                    // offset: var; size: 4; size of the following file link field including string lenth mark
4717
                    $sz = self::getInt4d($recordData, $offset);
4718
                    $offset += 4;
4719
4720
                    // only present if $sz > 0
4721
                    if ($sz > 0) {
4722
                        // offset: var; size: 4; size of the character array of the extended file path and name
4723
                        $xl = self::getInt4d($recordData, $offset);
4724
                        $offset += 4;
4725
4726
                        // offset: var; size 2; unknown
4727
                        $offset += 2;
4728
4729
                        // offset: var; size $xl; character array of the extended file path and name.
4730
                        $extendedFilePath = substr($recordData, $offset, $xl);
4731
                        $extendedFilePath = self::encodeUTF16($extendedFilePath, false);
4732
                        $offset += $xl;
4733
                    }
4734
4735
                    // construct the path
4736
                    $url = str_repeat('..\\', $upLevelCount);
4737
                    $url .= ($sz > 0) ? $extendedFilePath : $shortenedFilePath; // use extended path if available
4738
                    $url .= $hasText ? '#' : '';
4739
4740
                    break;
4741 2
                case 'UNC':
4742
                    // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path
4743
                    // todo: implement
4744
                    return;
4745 2
                case 'workbook':
4746
                    // section 5.58.5: Hyperlink to the Current Workbook
4747
                    // e.g. Sheet2!B1:C2, stored in text mark field
4748 2
                    $url = 'sheet://';
4749
4750 2
                    break;
4751
                default:
4752
                    return;
4753
            }
4754
4755 2
            if ($hasText) {
4756
                // offset: var; size: 4; character count of text mark including trailing zero word
4757 2
                $tl = self::getInt4d($recordData, $offset);
4758 2
                $offset += 4;
4759
                // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated
4760 2
                $text = self::encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false);
4761 2
                $url .= $text;
4762
            }
4763
4764
            // apply the hyperlink to all the relevant cells
4765 2
            foreach (Coordinate::extractAllCellReferencesInRange($cellRange) as $coordinate) {
4766 2
                $this->phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url);
4767
            }
4768
        }
4769 2
    }
4770
4771
    /**
4772
     * Read DATAVALIDATIONS record.
4773
     */
4774
    private function readDataValidations()
4775
    {
4776
        $length = self::getUInt2d($this->data, $this->pos + 2);
4777
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4778
4779
        // move stream pointer forward to next record
4780
        $this->pos += 4 + $length;
4781
    }
4782
4783
    /**
4784
     * Read DATAVALIDATION record.
4785
     */
4786
    private function readDataValidation()
4787
    {
4788
        $length = self::getUInt2d($this->data, $this->pos + 2);
4789
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
4790
4791
        // move stream pointer forward to next record
4792
        $this->pos += 4 + $length;
4793
4794
        if ($this->readDataOnly) {
4795
            return;
4796
        }
4797
4798
        // offset: 0; size: 4; Options
4799
        $options = self::getInt4d($recordData, 0);
4800
4801
        // bit: 0-3; mask: 0x0000000F; type
4802
        $type = (0x0000000F & $options) >> 0;
4803 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...
4804
            case 0x00:
4805
                $type = DataValidation::TYPE_NONE;
4806
4807
                break;
4808
            case 0x01:
4809
                $type = DataValidation::TYPE_WHOLE;
4810
4811
                break;
4812
            case 0x02:
4813
                $type = DataValidation::TYPE_DECIMAL;
4814
4815
                break;
4816
            case 0x03:
4817
                $type = DataValidation::TYPE_LIST;
4818
4819
                break;
4820
            case 0x04:
4821
                $type = DataValidation::TYPE_DATE;
4822
4823
                break;
4824
            case 0x05:
4825
                $type = DataValidation::TYPE_TIME;
4826
4827
                break;
4828
            case 0x06:
4829
                $type = DataValidation::TYPE_TEXTLENGTH;
4830
4831
                break;
4832
            case 0x07:
4833
                $type = DataValidation::TYPE_CUSTOM;
4834
4835
                break;
4836
        }
4837
4838
        // bit: 4-6; mask: 0x00000070; error type
4839
        $errorStyle = (0x00000070 & $options) >> 4;
4840 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...
4841
            case 0x00:
4842
                $errorStyle = DataValidation::STYLE_STOP;
4843
4844
                break;
4845
            case 0x01:
4846
                $errorStyle = DataValidation::STYLE_WARNING;
4847
4848
                break;
4849
            case 0x02:
4850
                $errorStyle = DataValidation::STYLE_INFORMATION;
4851
4852
                break;
4853
        }
4854
4855
        // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)
4856
        // I have only seen cases where this is 1
4857
        $explicitFormula = (0x00000080 & $options) >> 7;
4858
4859
        // bit: 8; mask: 0x00000100; 1= empty cells allowed
4860
        $allowBlank = (0x00000100 & $options) >> 8;
4861
4862
        // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity
4863
        $suppressDropDown = (0x00000200 & $options) >> 9;
4864
4865
        // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected
4866
        $showInputMessage = (0x00040000 & $options) >> 18;
4867
4868
        // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered
4869
        $showErrorMessage = (0x00080000 & $options) >> 19;
4870
4871
        // bit: 20-23; mask: 0x00F00000; condition operator
4872
        $operator = (0x00F00000 & $options) >> 20;
4873 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...
4874
            case 0x00:
4875
                $operator = DataValidation::OPERATOR_BETWEEN;
4876
4877
                break;
4878
            case 0x01:
4879
                $operator = DataValidation::OPERATOR_NOTBETWEEN;
4880
4881
                break;
4882
            case 0x02:
4883
                $operator = DataValidation::OPERATOR_EQUAL;
4884
4885
                break;
4886
            case 0x03:
4887
                $operator = DataValidation::OPERATOR_NOTEQUAL;
4888
4889
                break;
4890
            case 0x04:
4891
                $operator = DataValidation::OPERATOR_GREATERTHAN;
4892
4893
                break;
4894
            case 0x05:
4895
                $operator = DataValidation::OPERATOR_LESSTHAN;
4896
4897
                break;
4898
            case 0x06:
4899
                $operator = DataValidation::OPERATOR_GREATERTHANOREQUAL;
4900
4901
                break;
4902
            case 0x07:
4903
                $operator = DataValidation::OPERATOR_LESSTHANOREQUAL;
4904
4905
                break;
4906
        }
4907
4908
        // offset: 4; size: var; title of the prompt box
4909
        $offset = 4;
4910
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4911
        $promptTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4912
        $offset += $string['size'];
4913
4914
        // offset: var; size: var; title of the error box
4915
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4916
        $errorTitle = $string['value'] !== chr(0) ? $string['value'] : '';
4917
        $offset += $string['size'];
4918
4919
        // offset: var; size: var; text of the prompt box
4920
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4921
        $prompt = $string['value'] !== chr(0) ? $string['value'] : '';
4922
        $offset += $string['size'];
4923
4924
        // offset: var; size: var; text of the error box
4925
        $string = self::readUnicodeStringLong(substr($recordData, $offset));
4926
        $error = $string['value'] !== chr(0) ? $string['value'] : '';
4927
        $offset += $string['size'];
4928
4929
        // offset: var; size: 2; size of the formula data for the first condition
4930
        $sz1 = self::getUInt2d($recordData, $offset);
4931
        $offset += 2;
4932
4933
        // offset: var; size: 2; not used
4934
        $offset += 2;
4935
4936
        // offset: var; size: $sz1; formula data for first condition (without size field)
4937
        $formula1 = substr($recordData, $offset, $sz1);
4938
        $formula1 = pack('v', $sz1) . $formula1; // prepend the length
4939
        try {
4940
            $formula1 = $this->getFormulaFromStructure($formula1);
4941
4942
            // in list type validity, null characters are used as item separators
4943
            if ($type == DataValidation::TYPE_LIST) {
4944
                $formula1 = str_replace(chr(0), ',', $formula1);
4945
            }
4946
        } catch (PhpSpreadsheetException $e) {
4947
            return;
4948
        }
4949
        $offset += $sz1;
4950
4951
        // offset: var; size: 2; size of the formula data for the first condition
4952
        $sz2 = self::getUInt2d($recordData, $offset);
4953
        $offset += 2;
4954
4955
        // offset: var; size: 2; not used
4956
        $offset += 2;
4957
4958
        // offset: var; size: $sz2; formula data for second condition (without size field)
4959
        $formula2 = substr($recordData, $offset, $sz2);
4960
        $formula2 = pack('v', $sz2) . $formula2; // prepend the length
4961
        try {
4962
            $formula2 = $this->getFormulaFromStructure($formula2);
4963
        } catch (PhpSpreadsheetException $e) {
4964
            return;
4965
        }
4966
        $offset += $sz2;
4967
4968
        // offset: var; size: var; cell range address list with
4969
        $cellRangeAddressList = $this->readBIFF8CellRangeAddressList(substr($recordData, $offset));
4970
        $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
4971
4972
        foreach ($cellRangeAddresses as $cellRange) {
4973
            $stRange = $this->phpSheet->shrinkRangeToFit($cellRange);
4974
            foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $coordinate) {
4975
                $objValidation = $this->phpSheet->getCell($coordinate)->getDataValidation();
4976
                $objValidation->setType($type);
4977
                $objValidation->setErrorStyle($errorStyle);
4978
                $objValidation->setAllowBlank((bool) $allowBlank);
4979
                $objValidation->setShowInputMessage((bool) $showInputMessage);
4980
                $objValidation->setShowErrorMessage((bool) $showErrorMessage);
4981
                $objValidation->setShowDropDown(!$suppressDropDown);
4982
                $objValidation->setOperator($operator);
4983
                $objValidation->setErrorTitle($errorTitle);
4984
                $objValidation->setError($error);
4985
                $objValidation->setPromptTitle($promptTitle);
4986
                $objValidation->setPrompt($prompt);
4987
                $objValidation->setFormula1($formula1);
4988
                $objValidation->setFormula2($formula2);
4989
            }
4990
        }
4991
    }
4992
4993
    /**
4994
     * Read SHEETLAYOUT record. Stores sheet tab color information.
4995
     */
4996 2
    private function readSheetLayout()
4997
    {
4998 2
        $length = self::getUInt2d($this->data, $this->pos + 2);
4999 2
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5000
5001
        // move stream pointer to next record
5002 2
        $this->pos += 4 + $length;
5003
5004
        // local pointer in record data
5005 2
        $offset = 0;
5006
5007 2
        if (!$this->readDataOnly) {
5008
            // offset: 0; size: 2; repeated record identifier 0x0862
5009
5010
            // offset: 2; size: 10; not used
5011
5012
            // offset: 12; size: 4; size of record data
5013
            // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?)
5014 2
            $sz = self::getInt4d($recordData, 12);
5015
5016
            switch ($sz) {
5017 2
                case 0x14:
5018
                    // offset: 16; size: 2; color index for sheet tab
5019 1
                    $colorIndex = self::getUInt2d($recordData, 16);
5020 1
                    $color = Xls\Color::map($colorIndex, $this->palette, $this->version);
5021 1
                    $this->phpSheet->getTabColor()->setRGB($color['rgb']);
5022
5023 1
                    break;
5024 1
                case 0x28:
5025
                    // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007
5026 1
                    return;
5027
                    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...
5028
            }
5029
        }
5030 1
    }
5031
5032
    /**
5033
     * Read SHEETPROTECTION record (FEATHEADR).
5034
     */
5035 5
    private function readSheetProtection()
5036
    {
5037 5
        $length = self::getUInt2d($this->data, $this->pos + 2);
5038 5
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5039
5040
        // move stream pointer to next record
5041 5
        $this->pos += 4 + $length;
5042
5043 5
        if ($this->readDataOnly) {
5044
            return;
5045
        }
5046
5047
        // offset: 0; size: 2; repeated record header
5048
5049
        // offset: 2; size: 2; FRT cell reference flag (=0 currently)
5050
5051
        // offset: 4; size: 8; Currently not used and set to 0
5052
5053
        // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag)
5054 5
        $isf = self::getUInt2d($recordData, 12);
5055 5
        if ($isf != 2) {
5056
            return;
5057
        }
5058
5059
        // offset: 14; size: 1; =1 since this is a feat header
5060
5061
        // offset: 15; size: 4; size of rgbHdrSData
5062
5063
        // rgbHdrSData, assume "Enhanced Protection"
5064
        // offset: 19; size: 2; option flags
5065 5
        $options = self::getUInt2d($recordData, 19);
5066
5067
        // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects
5068 5
        $bool = (0x0001 & $options) >> 0;
5069 5
        $this->phpSheet->getProtection()->setObjects(!$bool);
5070
5071
        // bit: 1; mask 0x0002; edit scenarios
5072 5
        $bool = (0x0002 & $options) >> 1;
5073 5
        $this->phpSheet->getProtection()->setScenarios(!$bool);
5074
5075
        // bit: 2; mask 0x0004; format cells
5076 5
        $bool = (0x0004 & $options) >> 2;
5077 5
        $this->phpSheet->getProtection()->setFormatCells(!$bool);
5078
5079
        // bit: 3; mask 0x0008; format columns
5080 5
        $bool = (0x0008 & $options) >> 3;
5081 5
        $this->phpSheet->getProtection()->setFormatColumns(!$bool);
5082
5083
        // bit: 4; mask 0x0010; format rows
5084 5
        $bool = (0x0010 & $options) >> 4;
5085 5
        $this->phpSheet->getProtection()->setFormatRows(!$bool);
5086
5087
        // bit: 5; mask 0x0020; insert columns
5088 5
        $bool = (0x0020 & $options) >> 5;
5089 5
        $this->phpSheet->getProtection()->setInsertColumns(!$bool);
5090
5091
        // bit: 6; mask 0x0040; insert rows
5092 5
        $bool = (0x0040 & $options) >> 6;
5093 5
        $this->phpSheet->getProtection()->setInsertRows(!$bool);
5094
5095
        // bit: 7; mask 0x0080; insert hyperlinks
5096 5
        $bool = (0x0080 & $options) >> 7;
5097 5
        $this->phpSheet->getProtection()->setInsertHyperlinks(!$bool);
5098
5099
        // bit: 8; mask 0x0100; delete columns
5100 5
        $bool = (0x0100 & $options) >> 8;
5101 5
        $this->phpSheet->getProtection()->setDeleteColumns(!$bool);
5102
5103
        // bit: 9; mask 0x0200; delete rows
5104 5
        $bool = (0x0200 & $options) >> 9;
5105 5
        $this->phpSheet->getProtection()->setDeleteRows(!$bool);
5106
5107
        // bit: 10; mask 0x0400; select locked cells
5108 5
        $bool = (0x0400 & $options) >> 10;
5109 5
        $this->phpSheet->getProtection()->setSelectLockedCells(!$bool);
5110
5111
        // bit: 11; mask 0x0800; sort cell range
5112 5
        $bool = (0x0800 & $options) >> 11;
5113 5
        $this->phpSheet->getProtection()->setSort(!$bool);
5114
5115
        // bit: 12; mask 0x1000; auto filter
5116 5
        $bool = (0x1000 & $options) >> 12;
5117 5
        $this->phpSheet->getProtection()->setAutoFilter(!$bool);
5118
5119
        // bit: 13; mask 0x2000; pivot tables
5120 5
        $bool = (0x2000 & $options) >> 13;
5121 5
        $this->phpSheet->getProtection()->setPivotTables(!$bool);
5122
5123
        // bit: 14; mask 0x4000; select unlocked cells
5124 5
        $bool = (0x4000 & $options) >> 14;
5125 5
        $this->phpSheet->getProtection()->setSelectUnlockedCells(!$bool);
5126
5127
        // offset: 21; size: 2; not used
5128 5
    }
5129
5130
    /**
5131
     * Read RANGEPROTECTION record
5132
     * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification,
5133
     * where it is referred to as FEAT record.
5134
     */
5135 1
    private function readRangeProtection()
5136
    {
5137 1
        $length = self::getUInt2d($this->data, $this->pos + 2);
5138 1
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5139
5140
        // move stream pointer to next record
5141 1
        $this->pos += 4 + $length;
5142
5143
        // local pointer in record data
5144 1
        $offset = 0;
5145
5146 1
        if (!$this->readDataOnly) {
5147 1
            $offset += 12;
5148
5149
            // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag
5150 1
            $isf = self::getUInt2d($recordData, 12);
5151 1
            if ($isf != 2) {
5152
                // we only read FEAT records of type 2
5153
                return;
5154
            }
5155 1
            $offset += 2;
5156
5157 1
            $offset += 5;
5158
5159
            // offset: 19; size: 2; count of ref ranges this feature is on
5160 1
            $cref = self::getUInt2d($recordData, 19);
5161 1
            $offset += 2;
5162
5163 1
            $offset += 6;
5164
5165
            // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record)
5166 1
            $cellRanges = [];
5167 1
            for ($i = 0; $i < $cref; ++$i) {
5168
                try {
5169 1
                    $cellRange = $this->readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8));
5170
                } catch (PhpSpreadsheetException $e) {
5171
                    return;
5172
                }
5173 1
                $cellRanges[] = $cellRange;
5174 1
                $offset += 8;
5175
            }
5176
5177
            // offset: var; size: var; variable length of feature specific data
5178 1
            $rgbFeat = substr($recordData, $offset);
5179 1
            $offset += 4;
5180
5181
            // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit)
5182 1
            $wPassword = self::getInt4d($recordData, $offset);
5183 1
            $offset += 4;
5184
5185
            // Apply range protection to sheet
5186 1
            if ($cellRanges) {
5187 1
                $this->phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true);
5188
            }
5189
        }
5190 1
    }
5191
5192
    /**
5193
     * Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record
5194
     * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented.
5195
     * In this case, we must treat the CONTINUE record as a MSODRAWING record.
5196
     */
5197
    private function readContinue()
5198
    {
5199
        $length = self::getUInt2d($this->data, $this->pos + 2);
5200
        $recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
5201
5202
        // check if we are reading drawing data
5203
        // this is in case a free CONTINUE record occurs in other circumstances we are unaware of
5204
        if ($this->drawingData == '') {
5205
            // move stream pointer to next record
5206
            $this->pos += 4 + $length;
5207
5208
            return;
5209
        }
5210
5211
        // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data
5212
        if ($length < 4) {
5213
            // move stream pointer to next record
5214
            $this->pos += 4 + $length;
5215
5216
            return;
5217
        }
5218
5219
        // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record
5220
        // look inside CONTINUE record to see if it looks like a part of an Escher stream
5221
        // we know that Escher stream may be split at least at
5222
        //        0xF003 MsofbtSpgrContainer
5223
        //        0xF004 MsofbtSpContainer
5224
        //        0xF00D MsofbtClientTextbox
5225
        $validSplitPoints = [0xF003, 0xF004, 0xF00D]; // add identifiers if we find more
5226
5227
        $splitPoint = self::getUInt2d($recordData, 2);
5228
        if (in_array($splitPoint, $validSplitPoints)) {
5229
            // get spliced record data (and move pointer to next record)
5230
            $splicedRecordData = $this->getSplicedRecordData();
5231
            $this->drawingData .= $splicedRecordData['recordData'];
5232
5233
            return;
5234
        }
5235
5236
        // move stream pointer to next record
5237
        $this->pos += 4 + $length;
5238
    }
5239
5240
    /**
5241
     * Reads a record from current position in data stream and continues reading data as long as CONTINUE
5242
     * records are found. Splices the record data pieces and returns the combined string as if record data
5243
     * is in one piece.
5244
     * Moves to next current position in data stream to start of next record different from a CONtINUE record.
5245
     *
5246
     * @return array
5247
     */
5248 18
    private function getSplicedRecordData()
5249
    {
5250 18
        $data = '';
5251 18
        $spliceOffsets = [];
5252
5253 18
        $i = 0;
5254 18
        $spliceOffsets[0] = 0;
5255
5256
        do {
5257 18
            ++$i;
5258
5259
            // offset: 0; size: 2; identifier
5260 18
            $identifier = self::getUInt2d($this->data, $this->pos);
5261
            // offset: 2; size: 2; length
5262 18
            $length = self::getUInt2d($this->data, $this->pos + 2);
5263 18
            $data .= $this->readRecordData($this->data, $this->pos + 4, $length);
5264
5265 18
            $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length;
5266
5267 18
            $this->pos += 4 + $length;
5268 18
            $nextIdentifier = self::getUInt2d($this->data, $this->pos);
5269 18
        } while ($nextIdentifier == self::XLS_TYPE_CONTINUE);
5270
5271
        $splicedData = [
5272 18
            'recordData' => $data,
5273 18
            'spliceOffsets' => $spliceOffsets,
5274
        ];
5275
5276 18
        return $splicedData;
5277
    }
5278
5279
    /**
5280
     * Convert formula structure into human readable Excel formula like 'A3+A5*5'.
5281
     *
5282
     * @param string $formulaStructure The complete binary data for the formula
5283
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5284
     *
5285
     * @return string Human readable formula
5286
     */
5287 10
    private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1')
5288
    {
5289
        // offset: 0; size: 2; size of the following formula data
5290 10
        $sz = self::getUInt2d($formulaStructure, 0);
5291
5292
        // offset: 2; size: sz
5293 10
        $formulaData = substr($formulaStructure, 2, $sz);
5294
5295
        // offset: 2 + sz; size: variable (optional)
5296 10
        if (strlen($formulaStructure) > 2 + $sz) {
5297
            $additionalData = substr($formulaStructure, 2 + $sz);
5298
        } else {
5299 10
            $additionalData = '';
5300
        }
5301
5302 10
        return $this->getFormulaFromData($formulaData, $additionalData, $baseCell);
5303
    }
5304
5305
    /**
5306
     * Take formula data and additional data for formula and return human readable formula.
5307
     *
5308
     * @param string $formulaData The binary data for the formula itself
5309
     * @param string $additionalData Additional binary data going with the formula
5310
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5311
     *
5312
     * @return string Human readable formula
5313
     */
5314 10
    private function getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1')
5315
    {
5316
        // start parsing the formula data
5317 10
        $tokens = [];
5318
5319 10
        while (strlen($formulaData) > 0 and $token = $this->getNextToken($formulaData, $baseCell)) {
5320 10
            $tokens[] = $token;
5321 10
            $formulaData = substr($formulaData, $token['size']);
5322
        }
5323
5324 10
        $formulaString = $this->createFormulaFromTokens($tokens, $additionalData);
5325
5326 10
        return $formulaString;
5327
    }
5328
5329
    /**
5330
     * Take array of tokens together with additional data for formula and return human readable formula.
5331
     *
5332
     * @param array $tokens
5333
     * @param string $additionalData Additional binary data going with the formula
5334
     *
5335
     * @return string Human readable formula
5336
     */
5337 10
    private function createFormulaFromTokens($tokens, $additionalData)
5338
    {
5339
        // empty formula?
5340 10
        if (empty($tokens)) {
5341
            return '';
5342
        }
5343
5344 10
        $formulaStrings = [];
5345 10
        foreach ($tokens as $token) {
5346
            // initialize spaces
5347 10
            $space0 = isset($space0) ? $space0 : ''; // spaces before next token, not tParen
5348 10
            $space1 = isset($space1) ? $space1 : ''; // carriage returns before next token, not tParen
5349 10
            $space2 = isset($space2) ? $space2 : ''; // spaces before opening parenthesis
5350 10
            $space3 = isset($space3) ? $space3 : ''; // carriage returns before opening parenthesis
5351 10
            $space4 = isset($space4) ? $space4 : ''; // spaces before closing parenthesis
5352 10
            $space5 = isset($space5) ? $space5 : ''; // carriage returns before closing parenthesis
5353
5354 10
            switch ($token['name']) {
5355 10
                case 'tAdd': // addition
5356 10
                case 'tConcat': // addition
5357 10
                case 'tDiv': // division
5358 10
                case 'tEQ': // equality
5359 10
                case 'tGE': // greater than or equal
5360 10
                case 'tGT': // greater than
5361 10
                case 'tIsect': // intersection
5362 10
                case 'tLE': // less than or equal
5363 10
                case 'tList': // less than or equal
5364 10
                case 'tLT': // less than
5365 10
                case 'tMul': // multiplication
5366 10
                case 'tNE': // multiplication
5367 10
                case 'tPower': // power
5368 10
                case 'tRange': // range
5369 10
                case 'tSub': // subtraction
5370 10
                    $op2 = array_pop($formulaStrings);
5371 10
                    $op1 = array_pop($formulaStrings);
5372 10
                    $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2";
5373 10
                    unset($space0, $space1);
5374
5375 10
                    break;
5376 10
                case 'tUplus': // unary plus
5377 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...
5378
                    $op = array_pop($formulaStrings);
5379
                    $formulaStrings[] = "$space1$space0{$token['data']}$op";
5380
                    unset($space0, $space1);
5381
5382
                    break;
5383 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...
5384
                    $op = array_pop($formulaStrings);
5385
                    $formulaStrings[] = "$op$space1$space0{$token['data']}";
5386
                    unset($space0, $space1);
5387
5388
                    break;
5389 10
                case 'tAttrVolatile': // indicates volatile function
5390 10
                case 'tAttrIf':
5391 10
                case 'tAttrSkip':
5392 10
                case 'tAttrChoose':
5393
                    // token is only important for Excel formula evaluator
5394
                    // do nothing
5395
                    break;
5396 10
                case 'tAttrSpace': // space / carriage return
5397
                    // space will be used when next token arrives, do not alter formulaString stack
5398
                    switch ($token['data']['spacetype']) {
5399
                        case 'type0':
5400
                            $space0 = str_repeat(' ', $token['data']['spacecount']);
5401
5402
                            break;
5403
                        case 'type1':
5404
                            $space1 = str_repeat("\n", $token['data']['spacecount']);
5405
5406
                            break;
5407
                        case 'type2':
5408
                            $space2 = str_repeat(' ', $token['data']['spacecount']);
5409
5410
                            break;
5411
                        case 'type3':
5412
                            $space3 = str_repeat("\n", $token['data']['spacecount']);
5413
5414
                            break;
5415
                        case 'type4':
5416
                            $space4 = str_repeat(' ', $token['data']['spacecount']);
5417
5418
                            break;
5419
                        case 'type5':
5420
                            $space5 = str_repeat("\n", $token['data']['spacecount']);
5421
5422
                            break;
5423
                    }
5424
5425
                    break;
5426 10
                case 'tAttrSum': // SUM function with one parameter
5427 9
                    $op = array_pop($formulaStrings);
5428 9
                    $formulaStrings[] = "{$space1}{$space0}SUM($op)";
5429 9
                    unset($space0, $space1);
5430
5431 9
                    break;
5432 10
                case 'tFunc': // function with fixed number of arguments
5433 10
                case 'tFuncV': // function with variable number of arguments
5434 9
                    if ($token['data']['function'] != '') {
5435
                        // normal function
5436 9
                        $ops = []; // array of operators
5437 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...
5438 1
                            $ops[] = array_pop($formulaStrings);
5439
                        }
5440 9
                        $ops = array_reverse($ops);
5441 9
                        $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ')';
5442 9
                        unset($space0, $space1);
5443
                    } else {
5444
                        // add-in function
5445
                        $ops = []; // array of operators
5446 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...
5447
                            $ops[] = array_pop($formulaStrings);
5448
                        }
5449
                        $ops = array_reverse($ops);
5450
                        $function = array_pop($formulaStrings);
5451
                        $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ')';
5452
                        unset($space0, $space1);
5453
                    }
5454
5455 9
                    break;
5456 10
                case 'tParen': // parenthesis
5457
                    $expression = array_pop($formulaStrings);
5458
                    $formulaStrings[] = "$space3$space2($expression$space5$space4)";
5459
                    unset($space2, $space3, $space4, $space5);
5460
5461
                    break;
5462 10
                case 'tArray': // array constant
5463
                    $constantArray = self::readBIFF8ConstantArray($additionalData);
5464
                    $formulaStrings[] = $space1 . $space0 . $constantArray['value'];
5465
                    $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data
5466
                    unset($space0, $space1);
5467
5468
                    break;
5469 10
                case 'tMemArea':
5470
                    // bite off chunk of additional data
5471
                    $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($additionalData);
5472
                    $additionalData = substr($additionalData, $cellRangeAddressList['size']);
5473
                    $formulaStrings[] = "$space1$space0{$token['data']}";
5474
                    unset($space0, $space1);
5475
5476
                    break;
5477 10
                case 'tArea': // cell range address
5478 10
                case 'tBool': // boolean
5479 10
                case 'tErr': // error code
5480 10
                case 'tInt': // integer
5481 2
                case 'tMemErr':
5482 2
                case 'tMemFunc':
5483 2
                case 'tMissArg':
5484 2
                case 'tName':
5485 2
                case 'tNameX':
5486 2
                case 'tNum': // number
5487 2
                case 'tRef': // single cell reference
5488 2
                case 'tRef3d': // 3d cell reference
5489 2
                case 'tArea3d': // 3d cell range reference
5490 1
                case 'tRefN':
5491 1
                case 'tAreaN':
5492 1
                case 'tStr': // string
5493 10
                    $formulaStrings[] = "$space1$space0{$token['data']}";
5494 10
                    unset($space0, $space1);
5495
5496 10
                    break;
5497
            }
5498
        }
5499 10
        $formulaString = $formulaStrings[0];
5500
5501 10
        return $formulaString;
5502
    }
5503
5504
    /**
5505
     * Fetch next token from binary formula data.
5506
     *
5507
     * @param string $formulaData Formula data
5508
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
5509
     *
5510
     * @throws Exception
5511
     *
5512
     * @return array
5513
     */
5514 10
    private function getNextToken($formulaData, $baseCell = 'A1')
5515
    {
5516
        // offset: 0; size: 1; token id
5517 10
        $id = ord($formulaData[0]); // token id
5518 10
        $name = false; // initialize token name
5519
5520
        switch ($id) {
5521 10
            case 0x03:
5522 1
                $name = 'tAdd';
5523 1
                $size = 1;
5524 1
                $data = '+';
5525
5526 1
                break;
5527 10
            case 0x04:
5528
                $name = 'tSub';
5529
                $size = 1;
5530
                $data = '-';
5531
5532
                break;
5533 10
            case 0x05:
5534 2
                $name = 'tMul';
5535 2
                $size = 1;
5536 2
                $data = '*';
5537
5538 2
                break;
5539 10
            case 0x06:
5540 8
                $name = 'tDiv';
5541 8
                $size = 1;
5542 8
                $data = '/';
5543
5544 8
                break;
5545 10
            case 0x07:
5546
                $name = 'tPower';
5547
                $size = 1;
5548
                $data = '^';
5549
5550
                break;
5551 10
            case 0x08:
5552
                $name = 'tConcat';
5553
                $size = 1;
5554
                $data = '&';
5555
5556
                break;
5557 10
            case 0x09:
5558
                $name = 'tLT';
5559
                $size = 1;
5560
                $data = '<';
5561
5562
                break;
5563 10
            case 0x0A:
5564
                $name = 'tLE';
5565
                $size = 1;
5566
                $data = '<=';
5567
5568
                break;
5569 10
            case 0x0B:
5570
                $name = 'tEQ';
5571
                $size = 1;
5572
                $data = '=';
5573
5574
                break;
5575 10
            case 0x0C:
5576
                $name = 'tGE';
5577
                $size = 1;
5578
                $data = '>=';
5579
5580
                break;
5581 10
            case 0x0D:
5582
                $name = 'tGT';
5583
                $size = 1;
5584
                $data = '>';
5585
5586
                break;
5587 10
            case 0x0E:
5588 1
                $name = 'tNE';
5589 1
                $size = 1;
5590 1
                $data = '<>';
5591
5592 1
                break;
5593 10
            case 0x0F:
5594
                $name = 'tIsect';
5595
                $size = 1;
5596
                $data = ' ';
5597
5598
                break;
5599 10
            case 0x10:
5600
                $name = 'tList';
5601
                $size = 1;
5602
                $data = ',';
5603
5604
                break;
5605 10
            case 0x11:
5606
                $name = 'tRange';
5607
                $size = 1;
5608
                $data = ':';
5609
5610
                break;
5611 10
            case 0x12:
5612
                $name = 'tUplus';
5613
                $size = 1;
5614
                $data = '+';
5615
5616
                break;
5617 10
            case 0x13:
5618
                $name = 'tUminus';
5619
                $size = 1;
5620
                $data = '-';
5621
5622
                break;
5623 10
            case 0x14:
5624
                $name = 'tPercent';
5625
                $size = 1;
5626
                $data = '%';
5627
5628
                break;
5629 10
            case 0x15:    //    parenthesis
5630
                $name = 'tParen';
5631
                $size = 1;
5632
                $data = null;
5633
5634
                break;
5635 10
            case 0x16:    //    missing argument
5636
                $name = 'tMissArg';
5637
                $size = 1;
5638
                $data = '';
5639
5640
                break;
5641 10
            case 0x17:    //    string
5642 1
                $name = 'tStr';
5643
                // offset: 1; size: var; Unicode string, 8-bit string length
5644 1
                $string = self::readUnicodeStringShort(substr($formulaData, 1));
5645 1
                $size = 1 + $string['size'];
5646 1
                $data = self::UTF8toExcelDoubleQuoted($string['value']);
5647
5648 1
                break;
5649 10
            case 0x19:    //    Special attribute
5650
                // offset: 1; size: 1; attribute type flags:
5651 9
                switch (ord($formulaData[1])) {
5652 9
                    case 0x01:
5653
                        $name = 'tAttrVolatile';
5654
                        $size = 4;
5655
                        $data = null;
5656
5657
                        break;
5658 9
                    case 0x02:
5659
                        $name = 'tAttrIf';
5660
                        $size = 4;
5661
                        $data = null;
5662
5663
                        break;
5664 9
                    case 0x04:
5665
                        $name = 'tAttrChoose';
5666
                        // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1)
5667
                        $nc = self::getUInt2d($formulaData, 2);
5668
                        // offset: 4; size: 2 * $nc
5669
                        // offset: 4 + 2 * $nc; size: 2
5670
                        $size = 2 * $nc + 6;
5671
                        $data = null;
5672
5673
                        break;
5674 9
                    case 0x08:
5675
                        $name = 'tAttrSkip';
5676
                        $size = 4;
5677
                        $data = null;
5678
5679
                        break;
5680 9
                    case 0x10:
5681 9
                        $name = 'tAttrSum';
5682 9
                        $size = 4;
5683 9
                        $data = null;
5684
5685 9
                        break;
5686
                    case 0x40:
5687
                    case 0x41:
5688
                        $name = 'tAttrSpace';
5689
                        $size = 4;
5690
                        // offset: 2; size: 2; space type and position
5691
                        switch (ord($formulaData[2])) {
5692
                            case 0x00:
5693
                                $spacetype = 'type0';
5694
5695
                                break;
5696
                            case 0x01:
5697
                                $spacetype = 'type1';
5698
5699
                                break;
5700
                            case 0x02:
5701
                                $spacetype = 'type2';
5702
5703
                                break;
5704
                            case 0x03:
5705
                                $spacetype = 'type3';
5706
5707
                                break;
5708
                            case 0x04:
5709
                                $spacetype = 'type4';
5710
5711
                                break;
5712
                            case 0x05:
5713
                                $spacetype = 'type5';
5714
5715
                                break;
5716
                            default:
5717
                                throw new Exception('Unrecognized space type in tAttrSpace token');
5718
                                break;
5719
                        }
5720
                        // offset: 3; size: 1; number of inserted spaces/carriage returns
5721
                        $spacecount = ord($formulaData[3]);
5722
5723
                        $data = ['spacetype' => $spacetype, 'spacecount' => $spacecount];
5724
5725
                        break;
5726
                    default:
5727
                        throw new Exception('Unrecognized attribute flag in tAttr token');
5728
                        break;
5729
                }
5730
5731 9
                break;
5732 10
            case 0x1C:    //    error code
5733
                // offset: 1; size: 1; error code
5734
                $name = 'tErr';
5735
                $size = 2;
5736
                $data = Xls\ErrorCode::lookup(ord($formulaData[1]));
5737
5738
                break;
5739 10
            case 0x1D:    //    boolean
5740
                // offset: 1; size: 1; 0 = false, 1 = true;
5741
                $name = 'tBool';
5742
                $size = 2;
5743
                $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE';
5744
5745
                break;
5746 10
            case 0x1E:    //    integer
5747
                // offset: 1; size: 2; unsigned 16-bit integer
5748 8
                $name = 'tInt';
5749 8
                $size = 3;
5750 8
                $data = self::getUInt2d($formulaData, 1);
5751
5752 8
                break;
5753 10
            case 0x1F:    //    number
5754
                // offset: 1; size: 8;
5755 2
                $name = 'tNum';
5756 2
                $size = 9;
5757 2
                $data = self::extractNumber(substr($formulaData, 1));
5758 2
                $data = str_replace(',', '.', (string) $data); // in case non-English locale
5759 2
                break;
5760 10
            case 0x20:    //    array constant
5761 10
            case 0x40:
5762 10
            case 0x60:
5763
                // offset: 1; size: 7; not used
5764
                $name = 'tArray';
5765
                $size = 8;
5766
                $data = null;
5767
5768
                break;
5769 10
            case 0x21:    //    function with fixed number of arguments
5770 10
            case 0x41:
5771 10
            case 0x61:
5772 8
                $name = 'tFunc';
5773 8
                $size = 3;
5774
                // offset: 1; size: 2; index to built-in sheet function
5775 8
                switch (self::getUInt2d($formulaData, 1)) {
5776 8
                    case 2:
5777
                        $function = 'ISNA';
5778
                        $args = 1;
5779
5780
                        break;
5781 8
                    case 3:
5782
                        $function = 'ISERROR';
5783
                        $args = 1;
5784
5785
                        break;
5786 8
                    case 10:
5787 8
                        $function = 'NA';
5788 8
                        $args = 0;
5789
5790 8
                        break;
5791
                    case 15:
5792
                        $function = 'SIN';
5793
                        $args = 1;
5794
5795
                        break;
5796
                    case 16:
5797
                        $function = 'COS';
5798
                        $args = 1;
5799
5800
                        break;
5801
                    case 17:
5802
                        $function = 'TAN';
5803
                        $args = 1;
5804
5805
                        break;
5806
                    case 18:
5807
                        $function = 'ATAN';
5808
                        $args = 1;
5809
5810
                        break;
5811
                    case 19:
5812
                        $function = 'PI';
5813
                        $args = 0;
5814
5815
                        break;
5816
                    case 20:
5817
                        $function = 'SQRT';
5818
                        $args = 1;
5819
5820
                        break;
5821
                    case 21:
5822
                        $function = 'EXP';
5823
                        $args = 1;
5824
5825
                        break;
5826
                    case 22:
5827
                        $function = 'LN';
5828
                        $args = 1;
5829
5830
                        break;
5831
                    case 23:
5832
                        $function = 'LOG10';
5833
                        $args = 1;
5834
5835
                        break;
5836
                    case 24:
5837
                        $function = 'ABS';
5838
                        $args = 1;
5839
5840
                        break;
5841
                    case 25:
5842
                        $function = 'INT';
5843
                        $args = 1;
5844
5845
                        break;
5846
                    case 26:
5847
                        $function = 'SIGN';
5848
                        $args = 1;
5849
5850
                        break;
5851
                    case 27:
5852
                        $function = 'ROUND';
5853
                        $args = 2;
5854
5855
                        break;
5856
                    case 30:
5857
                        $function = 'REPT';
5858
                        $args = 2;
5859
5860
                        break;
5861
                    case 31:
5862
                        $function = 'MID';
5863
                        $args = 3;
5864
5865
                        break;
5866
                    case 32:
5867
                        $function = 'LEN';
5868
                        $args = 1;
5869
5870
                        break;
5871
                    case 33:
5872
                        $function = 'VALUE';
5873
                        $args = 1;
5874
5875
                        break;
5876
                    case 34:
5877
                        $function = 'TRUE';
5878
                        $args = 0;
5879
5880
                        break;
5881
                    case 35:
5882
                        $function = 'FALSE';
5883
                        $args = 0;
5884
5885
                        break;
5886
                    case 38:
5887
                        $function = 'NOT';
5888
                        $args = 1;
5889
5890
                        break;
5891
                    case 39:
5892
                        $function = 'MOD';
5893
                        $args = 2;
5894
5895
                        break;
5896
                    case 40:
5897
                        $function = 'DCOUNT';
5898
                        $args = 3;
5899
5900
                        break;
5901
                    case 41:
5902
                        $function = 'DSUM';
5903
                        $args = 3;
5904
5905
                        break;
5906
                    case 42:
5907
                        $function = 'DAVERAGE';
5908
                        $args = 3;
5909
5910
                        break;
5911
                    case 43:
5912
                        $function = 'DMIN';
5913
                        $args = 3;
5914
5915
                        break;
5916
                    case 44:
5917
                        $function = 'DMAX';
5918
                        $args = 3;
5919
5920
                        break;
5921
                    case 45:
5922
                        $function = 'DSTDEV';
5923
                        $args = 3;
5924
5925
                        break;
5926
                    case 48:
5927
                        $function = 'TEXT';
5928
                        $args = 2;
5929
5930
                        break;
5931
                    case 61:
5932
                        $function = 'MIRR';
5933
                        $args = 3;
5934
5935
                        break;
5936
                    case 63:
5937
                        $function = 'RAND';
5938
                        $args = 0;
5939
5940
                        break;
5941
                    case 65:
5942
                        $function = 'DATE';
5943
                        $args = 3;
5944
5945
                        break;
5946
                    case 66:
5947
                        $function = 'TIME';
5948
                        $args = 3;
5949
5950
                        break;
5951
                    case 67:
5952
                        $function = 'DAY';
5953
                        $args = 1;
5954
5955
                        break;
5956
                    case 68:
5957
                        $function = 'MONTH';
5958
                        $args = 1;
5959
5960
                        break;
5961
                    case 69:
5962
                        $function = 'YEAR';
5963
                        $args = 1;
5964
5965
                        break;
5966
                    case 71:
5967
                        $function = 'HOUR';
5968
                        $args = 1;
5969
5970
                        break;
5971
                    case 72:
5972
                        $function = 'MINUTE';
5973
                        $args = 1;
5974
5975
                        break;
5976
                    case 73:
5977
                        $function = 'SECOND';
5978
                        $args = 1;
5979
5980
                        break;
5981
                    case 74:
5982
                        $function = 'NOW';
5983
                        $args = 0;
5984
5985
                        break;
5986
                    case 75:
5987
                        $function = 'AREAS';
5988
                        $args = 1;
5989
5990
                        break;
5991
                    case 76:
5992
                        $function = 'ROWS';
5993
                        $args = 1;
5994
5995
                        break;
5996
                    case 77:
5997
                        $function = 'COLUMNS';
5998
                        $args = 1;
5999
6000
                        break;
6001
                    case 83:
6002
                        $function = 'TRANSPOSE';
6003
                        $args = 1;
6004
6005
                        break;
6006
                    case 86:
6007
                        $function = 'TYPE';
6008
                        $args = 1;
6009
6010
                        break;
6011
                    case 97:
6012
                        $function = 'ATAN2';
6013
                        $args = 2;
6014
6015
                        break;
6016
                    case 98:
6017
                        $function = 'ASIN';
6018
                        $args = 1;
6019
6020
                        break;
6021
                    case 99:
6022
                        $function = 'ACOS';
6023
                        $args = 1;
6024
6025
                        break;
6026
                    case 105:
6027
                        $function = 'ISREF';
6028
                        $args = 1;
6029
6030
                        break;
6031
                    case 111:
6032
                        $function = 'CHAR';
6033
                        $args = 1;
6034
6035
                        break;
6036
                    case 112:
6037
                        $function = 'LOWER';
6038
                        $args = 1;
6039
6040
                        break;
6041
                    case 113:
6042
                        $function = 'UPPER';
6043
                        $args = 1;
6044
6045
                        break;
6046
                    case 114:
6047
                        $function = 'PROPER';
6048
                        $args = 1;
6049
6050
                        break;
6051
                    case 117:
6052
                        $function = 'EXACT';
6053
                        $args = 2;
6054
6055
                        break;
6056
                    case 118:
6057
                        $function = 'TRIM';
6058
                        $args = 1;
6059
6060
                        break;
6061
                    case 119:
6062
                        $function = 'REPLACE';
6063
                        $args = 4;
6064
6065
                        break;
6066
                    case 121:
6067
                        $function = 'CODE';
6068
                        $args = 1;
6069
6070
                        break;
6071
                    case 126:
6072
                        $function = 'ISERR';
6073
                        $args = 1;
6074
6075
                        break;
6076
                    case 127:
6077
                        $function = 'ISTEXT';
6078
                        $args = 1;
6079
6080
                        break;
6081
                    case 128:
6082
                        $function = 'ISNUMBER';
6083
                        $args = 1;
6084
6085
                        break;
6086
                    case 129:
6087
                        $function = 'ISBLANK';
6088
                        $args = 1;
6089
6090
                        break;
6091
                    case 130:
6092
                        $function = 'T';
6093
                        $args = 1;
6094
6095
                        break;
6096
                    case 131:
6097
                        $function = 'N';
6098
                        $args = 1;
6099
6100
                        break;
6101
                    case 140:
6102
                        $function = 'DATEVALUE';
6103
                        $args = 1;
6104
6105
                        break;
6106
                    case 141:
6107
                        $function = 'TIMEVALUE';
6108
                        $args = 1;
6109
6110
                        break;
6111
                    case 142:
6112
                        $function = 'SLN';
6113
                        $args = 3;
6114
6115
                        break;
6116
                    case 143:
6117
                        $function = 'SYD';
6118
                        $args = 4;
6119
6120
                        break;
6121
                    case 162:
6122
                        $function = 'CLEAN';
6123
                        $args = 1;
6124
6125
                        break;
6126
                    case 163:
6127
                        $function = 'MDETERM';
6128
                        $args = 1;
6129
6130
                        break;
6131
                    case 164:
6132
                        $function = 'MINVERSE';
6133
                        $args = 1;
6134
6135
                        break;
6136
                    case 165:
6137
                        $function = 'MMULT';
6138
                        $args = 2;
6139
6140
                        break;
6141
                    case 184:
6142
                        $function = 'FACT';
6143
                        $args = 1;
6144
6145
                        break;
6146
                    case 189:
6147
                        $function = 'DPRODUCT';
6148
                        $args = 3;
6149
6150
                        break;
6151
                    case 190:
6152
                        $function = 'ISNONTEXT';
6153
                        $args = 1;
6154
6155
                        break;
6156
                    case 195:
6157
                        $function = 'DSTDEVP';
6158
                        $args = 3;
6159
6160
                        break;
6161
                    case 196:
6162
                        $function = 'DVARP';
6163
                        $args = 3;
6164
6165
                        break;
6166
                    case 198:
6167
                        $function = 'ISLOGICAL';
6168
                        $args = 1;
6169
6170
                        break;
6171
                    case 199:
6172
                        $function = 'DCOUNTA';
6173
                        $args = 3;
6174
6175
                        break;
6176
                    case 207:
6177
                        $function = 'REPLACEB';
6178
                        $args = 4;
6179
6180
                        break;
6181
                    case 210:
6182
                        $function = 'MIDB';
6183
                        $args = 3;
6184
6185
                        break;
6186
                    case 211:
6187
                        $function = 'LENB';
6188
                        $args = 1;
6189
6190
                        break;
6191
                    case 212:
6192
                        $function = 'ROUNDUP';
6193
                        $args = 2;
6194
6195
                        break;
6196
                    case 213:
6197
                        $function = 'ROUNDDOWN';
6198
                        $args = 2;
6199
6200
                        break;
6201
                    case 214:
6202
                        $function = 'ASC';
6203
                        $args = 1;
6204
6205
                        break;
6206
                    case 215:
6207
                        $function = 'DBCS';
6208
                        $args = 1;
6209
6210
                        break;
6211
                    case 221:
6212
                        $function = 'TODAY';
6213
                        $args = 0;
6214
6215
                        break;
6216
                    case 229:
6217
                        $function = 'SINH';
6218
                        $args = 1;
6219
6220
                        break;
6221
                    case 230:
6222
                        $function = 'COSH';
6223
                        $args = 1;
6224
6225
                        break;
6226
                    case 231:
6227
                        $function = 'TANH';
6228
                        $args = 1;
6229
6230
                        break;
6231
                    case 232:
6232
                        $function = 'ASINH';
6233
                        $args = 1;
6234
6235
                        break;
6236
                    case 233:
6237
                        $function = 'ACOSH';
6238
                        $args = 1;
6239
6240
                        break;
6241
                    case 234:
6242
                        $function = 'ATANH';
6243
                        $args = 1;
6244
6245
                        break;
6246
                    case 235:
6247
                        $function = 'DGET';
6248
                        $args = 3;
6249
6250
                        break;
6251
                    case 244:
6252
                        $function = 'INFO';
6253
                        $args = 1;
6254
6255
                        break;
6256
                    case 252:
6257
                        $function = 'FREQUENCY';
6258
                        $args = 2;
6259
6260
                        break;
6261
                    case 261:
6262
                        $function = 'ERROR.TYPE';
6263
                        $args = 1;
6264
6265
                        break;
6266
                    case 271:
6267
                        $function = 'GAMMALN';
6268
                        $args = 1;
6269
6270
                        break;
6271
                    case 273:
6272
                        $function = 'BINOMDIST';
6273
                        $args = 4;
6274
6275
                        break;
6276
                    case 274:
6277
                        $function = 'CHIDIST';
6278
                        $args = 2;
6279
6280
                        break;
6281
                    case 275:
6282
                        $function = 'CHIINV';
6283
                        $args = 2;
6284
6285
                        break;
6286
                    case 276:
6287
                        $function = 'COMBIN';
6288
                        $args = 2;
6289
6290
                        break;
6291
                    case 277:
6292
                        $function = 'CONFIDENCE';
6293
                        $args = 3;
6294
6295
                        break;
6296
                    case 278:
6297
                        $function = 'CRITBINOM';
6298
                        $args = 3;
6299
6300
                        break;
6301
                    case 279:
6302
                        $function = 'EVEN';
6303
                        $args = 1;
6304
6305
                        break;
6306
                    case 280:
6307
                        $function = 'EXPONDIST';
6308
                        $args = 3;
6309
6310
                        break;
6311
                    case 281:
6312
                        $function = 'FDIST';
6313
                        $args = 3;
6314
6315
                        break;
6316
                    case 282:
6317
                        $function = 'FINV';
6318
                        $args = 3;
6319
6320
                        break;
6321
                    case 283:
6322
                        $function = 'FISHER';
6323
                        $args = 1;
6324
6325
                        break;
6326
                    case 284:
6327
                        $function = 'FISHERINV';
6328
                        $args = 1;
6329
6330
                        break;
6331
                    case 285:
6332
                        $function = 'FLOOR';
6333
                        $args = 2;
6334
6335
                        break;
6336
                    case 286:
6337
                        $function = 'GAMMADIST';
6338
                        $args = 4;
6339
6340
                        break;
6341
                    case 287:
6342
                        $function = 'GAMMAINV';
6343
                        $args = 3;
6344
6345
                        break;
6346
                    case 288:
6347
                        $function = 'CEILING';
6348
                        $args = 2;
6349
6350
                        break;
6351
                    case 289:
6352
                        $function = 'HYPGEOMDIST';
6353
                        $args = 4;
6354
6355
                        break;
6356
                    case 290:
6357
                        $function = 'LOGNORMDIST';
6358
                        $args = 3;
6359
6360
                        break;
6361
                    case 291:
6362
                        $function = 'LOGINV';
6363
                        $args = 3;
6364
6365
                        break;
6366
                    case 292:
6367
                        $function = 'NEGBINOMDIST';
6368
                        $args = 3;
6369
6370
                        break;
6371
                    case 293:
6372
                        $function = 'NORMDIST';
6373
                        $args = 4;
6374
6375
                        break;
6376
                    case 294:
6377
                        $function = 'NORMSDIST';
6378
                        $args = 1;
6379
6380
                        break;
6381
                    case 295:
6382
                        $function = 'NORMINV';
6383
                        $args = 3;
6384
6385
                        break;
6386
                    case 296:
6387
                        $function = 'NORMSINV';
6388
                        $args = 1;
6389
6390
                        break;
6391
                    case 297:
6392
                        $function = 'STANDARDIZE';
6393
                        $args = 3;
6394
6395
                        break;
6396
                    case 298:
6397
                        $function = 'ODD';
6398
                        $args = 1;
6399
6400
                        break;
6401
                    case 299:
6402
                        $function = 'PERMUT';
6403
                        $args = 2;
6404
6405
                        break;
6406
                    case 300:
6407
                        $function = 'POISSON';
6408
                        $args = 3;
6409
6410
                        break;
6411
                    case 301:
6412
                        $function = 'TDIST';
6413
                        $args = 3;
6414
6415
                        break;
6416
                    case 302:
6417
                        $function = 'WEIBULL';
6418
                        $args = 4;
6419
6420
                        break;
6421
                    case 303:
6422
                        $function = 'SUMXMY2';
6423
                        $args = 2;
6424
6425
                        break;
6426
                    case 304:
6427
                        $function = 'SUMX2MY2';
6428
                        $args = 2;
6429
6430
                        break;
6431
                    case 305:
6432
                        $function = 'SUMX2PY2';
6433
                        $args = 2;
6434
6435
                        break;
6436
                    case 306:
6437
                        $function = 'CHITEST';
6438
                        $args = 2;
6439
6440
                        break;
6441
                    case 307:
6442
                        $function = 'CORREL';
6443
                        $args = 2;
6444
6445
                        break;
6446
                    case 308:
6447
                        $function = 'COVAR';
6448
                        $args = 2;
6449
6450
                        break;
6451
                    case 309:
6452
                        $function = 'FORECAST';
6453
                        $args = 3;
6454
6455
                        break;
6456
                    case 310:
6457
                        $function = 'FTEST';
6458
                        $args = 2;
6459
6460
                        break;
6461
                    case 311:
6462
                        $function = 'INTERCEPT';
6463
                        $args = 2;
6464
6465
                        break;
6466
                    case 312:
6467
                        $function = 'PEARSON';
6468
                        $args = 2;
6469
6470
                        break;
6471
                    case 313:
6472
                        $function = 'RSQ';
6473
                        $args = 2;
6474
6475
                        break;
6476
                    case 314:
6477
                        $function = 'STEYX';
6478
                        $args = 2;
6479
6480
                        break;
6481
                    case 315:
6482
                        $function = 'SLOPE';
6483
                        $args = 2;
6484
6485
                        break;
6486
                    case 316:
6487
                        $function = 'TTEST';
6488
                        $args = 4;
6489
6490
                        break;
6491
                    case 325:
6492
                        $function = 'LARGE';
6493
                        $args = 2;
6494
6495
                        break;
6496
                    case 326:
6497
                        $function = 'SMALL';
6498
                        $args = 2;
6499
6500
                        break;
6501
                    case 327:
6502
                        $function = 'QUARTILE';
6503
                        $args = 2;
6504
6505
                        break;
6506
                    case 328:
6507
                        $function = 'PERCENTILE';
6508
                        $args = 2;
6509
6510
                        break;
6511
                    case 331:
6512
                        $function = 'TRIMMEAN';
6513
                        $args = 2;
6514
6515
                        break;
6516
                    case 332:
6517
                        $function = 'TINV';
6518
                        $args = 2;
6519
6520
                        break;
6521
                    case 337:
6522
                        $function = 'POWER';
6523
                        $args = 2;
6524
6525
                        break;
6526
                    case 342:
6527
                        $function = 'RADIANS';
6528
                        $args = 1;
6529
6530
                        break;
6531
                    case 343:
6532
                        $function = 'DEGREES';
6533
                        $args = 1;
6534
6535
                        break;
6536
                    case 346:
6537
                        $function = 'COUNTIF';
6538
                        $args = 2;
6539
6540
                        break;
6541
                    case 347:
6542
                        $function = 'COUNTBLANK';
6543
                        $args = 1;
6544
6545
                        break;
6546
                    case 350:
6547
                        $function = 'ISPMT';
6548
                        $args = 4;
6549
6550
                        break;
6551
                    case 351:
6552
                        $function = 'DATEDIF';
6553
                        $args = 3;
6554
6555
                        break;
6556
                    case 352:
6557
                        $function = 'DATESTRING';
6558
                        $args = 1;
6559
6560
                        break;
6561
                    case 353:
6562
                        $function = 'NUMBERSTRING';
6563
                        $args = 2;
6564
6565
                        break;
6566
                    case 360:
6567
                        $function = 'PHONETIC';
6568
                        $args = 1;
6569
6570
                        break;
6571
                    case 368:
6572
                        $function = 'BAHTTEXT';
6573
                        $args = 1;
6574
6575
                        break;
6576
                    default:
6577
                        throw new Exception('Unrecognized function in formula');
6578
                        break;
6579
                }
6580 8
                $data = ['function' => $function, 'args' => $args];
6581
6582 8
                break;
6583 10
            case 0x22:    //    function with variable number of arguments
6584 10
            case 0x42:
6585 10
            case 0x62:
6586 1
                $name = 'tFuncV';
6587 1
                $size = 4;
6588
                // offset: 1; size: 1; number of arguments
6589 1
                $args = ord($formulaData[1]);
6590
                // offset: 2: size: 2; index to built-in sheet function
6591 1
                $index = self::getUInt2d($formulaData, 2);
6592
                switch ($index) {
6593 1
                    case 0:
6594
                        $function = 'COUNT';
6595
6596
                        break;
6597 1
                    case 1:
6598 1
                        $function = 'IF';
6599
6600 1
                        break;
6601 1
                    case 4:
6602 1
                        $function = 'SUM';
6603
6604 1
                        break;
6605
                    case 5:
6606
                        $function = 'AVERAGE';
6607
6608
                        break;
6609
                    case 6:
6610
                        $function = 'MIN';
6611
6612
                        break;
6613
                    case 7:
6614
                        $function = 'MAX';
6615
6616
                        break;
6617
                    case 8:
6618
                        $function = 'ROW';
6619
6620
                        break;
6621
                    case 9:
6622
                        $function = 'COLUMN';
6623
6624
                        break;
6625
                    case 11:
6626
                        $function = 'NPV';
6627
6628
                        break;
6629
                    case 12:
6630
                        $function = 'STDEV';
6631
6632
                        break;
6633
                    case 13:
6634
                        $function = 'DOLLAR';
6635
6636
                        break;
6637
                    case 14:
6638
                        $function = 'FIXED';
6639
6640
                        break;
6641
                    case 28:
6642
                        $function = 'LOOKUP';
6643
6644
                        break;
6645
                    case 29:
6646
                        $function = 'INDEX';
6647
6648
                        break;
6649
                    case 36:
6650
                        $function = 'AND';
6651
6652
                        break;
6653
                    case 37:
6654
                        $function = 'OR';
6655
6656
                        break;
6657
                    case 46:
6658
                        $function = 'VAR';
6659
6660
                        break;
6661
                    case 49:
6662
                        $function = 'LINEST';
6663
6664
                        break;
6665
                    case 50:
6666
                        $function = 'TREND';
6667
6668
                        break;
6669
                    case 51:
6670
                        $function = 'LOGEST';
6671
6672
                        break;
6673
                    case 52:
6674
                        $function = 'GROWTH';
6675
6676
                        break;
6677
                    case 56:
6678
                        $function = 'PV';
6679
6680
                        break;
6681
                    case 57:
6682
                        $function = 'FV';
6683
6684
                        break;
6685
                    case 58:
6686
                        $function = 'NPER';
6687
6688
                        break;
6689
                    case 59:
6690
                        $function = 'PMT';
6691
6692
                        break;
6693
                    case 60:
6694
                        $function = 'RATE';
6695
6696
                        break;
6697
                    case 62:
6698
                        $function = 'IRR';
6699
6700
                        break;
6701
                    case 64:
6702
                        $function = 'MATCH';
6703
6704
                        break;
6705
                    case 70:
6706
                        $function = 'WEEKDAY';
6707
6708
                        break;
6709
                    case 78:
6710
                        $function = 'OFFSET';
6711
6712
                        break;
6713
                    case 82:
6714
                        $function = 'SEARCH';
6715
6716
                        break;
6717
                    case 100:
6718
                        $function = 'CHOOSE';
6719
6720
                        break;
6721
                    case 101:
6722
                        $function = 'HLOOKUP';
6723
6724
                        break;
6725
                    case 102:
6726
                        $function = 'VLOOKUP';
6727
6728
                        break;
6729
                    case 109:
6730
                        $function = 'LOG';
6731
6732
                        break;
6733
                    case 115:
6734
                        $function = 'LEFT';
6735
6736
                        break;
6737
                    case 116:
6738
                        $function = 'RIGHT';
6739
6740
                        break;
6741
                    case 120:
6742
                        $function = 'SUBSTITUTE';
6743
6744
                        break;
6745
                    case 124:
6746
                        $function = 'FIND';
6747
6748
                        break;
6749
                    case 125:
6750
                        $function = 'CELL';
6751
6752
                        break;
6753
                    case 144:
6754
                        $function = 'DDB';
6755
6756
                        break;
6757
                    case 148:
6758
                        $function = 'INDIRECT';
6759
6760
                        break;
6761
                    case 167:
6762
                        $function = 'IPMT';
6763
6764
                        break;
6765
                    case 168:
6766
                        $function = 'PPMT';
6767
6768
                        break;
6769
                    case 169:
6770
                        $function = 'COUNTA';
6771
6772
                        break;
6773
                    case 183:
6774
                        $function = 'PRODUCT';
6775
6776
                        break;
6777
                    case 193:
6778
                        $function = 'STDEVP';
6779
6780
                        break;
6781
                    case 194:
6782
                        $function = 'VARP';
6783
6784
                        break;
6785
                    case 197:
6786
                        $function = 'TRUNC';
6787
6788
                        break;
6789
                    case 204:
6790
                        $function = 'USDOLLAR';
6791
6792
                        break;
6793
                    case 205:
6794
                        $function = 'FINDB';
6795
6796
                        break;
6797
                    case 206:
6798
                        $function = 'SEARCHB';
6799
6800
                        break;
6801
                    case 208:
6802
                        $function = 'LEFTB';
6803
6804
                        break;
6805
                    case 209:
6806
                        $function = 'RIGHTB';
6807
6808
                        break;
6809
                    case 216:
6810
                        $function = 'RANK';
6811
6812
                        break;
6813
                    case 219:
6814
                        $function = 'ADDRESS';
6815
6816
                        break;
6817
                    case 220:
6818
                        $function = 'DAYS360';
6819
6820
                        break;
6821
                    case 222:
6822
                        $function = 'VDB';
6823
6824
                        break;
6825
                    case 227:
6826
                        $function = 'MEDIAN';
6827
6828
                        break;
6829
                    case 228:
6830
                        $function = 'SUMPRODUCT';
6831
6832
                        break;
6833
                    case 247:
6834
                        $function = 'DB';
6835
6836
                        break;
6837
                    case 255:
6838
                        $function = '';
6839
6840
                        break;
6841
                    case 269:
6842
                        $function = 'AVEDEV';
6843
6844
                        break;
6845
                    case 270:
6846
                        $function = 'BETADIST';
6847
6848
                        break;
6849
                    case 272:
6850
                        $function = 'BETAINV';
6851
6852
                        break;
6853
                    case 317:
6854
                        $function = 'PROB';
6855
6856
                        break;
6857
                    case 318:
6858
                        $function = 'DEVSQ';
6859
6860
                        break;
6861
                    case 319:
6862
                        $function = 'GEOMEAN';
6863
6864
                        break;
6865
                    case 320:
6866
                        $function = 'HARMEAN';
6867
6868
                        break;
6869
                    case 321:
6870
                        $function = 'SUMSQ';
6871
6872
                        break;
6873
                    case 322:
6874
                        $function = 'KURT';
6875
6876
                        break;
6877
                    case 323:
6878
                        $function = 'SKEW';
6879
6880
                        break;
6881
                    case 324:
6882
                        $function = 'ZTEST';
6883
6884
                        break;
6885
                    case 329:
6886
                        $function = 'PERCENTRANK';
6887
6888
                        break;
6889
                    case 330:
6890
                        $function = 'MODE';
6891
6892
                        break;
6893
                    case 336:
6894
                        $function = 'CONCATENATE';
6895
6896
                        break;
6897
                    case 344:
6898
                        $function = 'SUBTOTAL';
6899
6900
                        break;
6901
                    case 345:
6902
                        $function = 'SUMIF';
6903
6904
                        break;
6905
                    case 354:
6906
                        $function = 'ROMAN';
6907
6908
                        break;
6909
                    case 358:
6910
                        $function = 'GETPIVOTDATA';
6911
6912
                        break;
6913
                    case 359:
6914
                        $function = 'HYPERLINK';
6915
6916
                        break;
6917
                    case 361:
6918
                        $function = 'AVERAGEA';
6919
6920
                        break;
6921
                    case 362:
6922
                        $function = 'MAXA';
6923
6924
                        break;
6925
                    case 363:
6926
                        $function = 'MINA';
6927
6928
                        break;
6929
                    case 364:
6930
                        $function = 'STDEVPA';
6931
6932
                        break;
6933
                    case 365:
6934
                        $function = 'VARPA';
6935
6936
                        break;
6937
                    case 366:
6938
                        $function = 'STDEVA';
6939
6940
                        break;
6941
                    case 367:
6942
                        $function = 'VARA';
6943
6944
                        break;
6945
                    default:
6946
                        throw new Exception('Unrecognized function in formula');
6947
                        break;
6948
                }
6949 1
                $data = ['function' => $function, 'args' => $args];
6950
6951 1
                break;
6952 10
            case 0x23:    //    index to defined name
6953 10
            case 0x43:
6954 10
            case 0x63:
6955
                $name = 'tName';
6956
                $size = 5;
6957
                // offset: 1; size: 2; one-based index to definedname record
6958
                $definedNameIndex = self::getUInt2d($formulaData, 1) - 1;
6959
                // offset: 2; size: 2; not used
6960
                $data = $this->definedname[$definedNameIndex]['name'];
6961
6962
                break;
6963 10
            case 0x24:    //    single cell reference e.g. A5
6964 10
            case 0x44:
6965 10
            case 0x64:
6966 2
                $name = 'tRef';
6967 2
                $size = 5;
6968 2
                $data = $this->readBIFF8CellAddress(substr($formulaData, 1, 4));
6969
6970 2
                break;
6971 10
            case 0x25:    //    cell range reference to cells in the same sheet (2d)
6972 1
            case 0x45:
6973 1
            case 0x65:
6974 10
                $name = 'tArea';
6975 10
                $size = 9;
6976 10
                $data = $this->readBIFF8CellRangeAddress(substr($formulaData, 1, 8));
6977
6978 10
                break;
6979 1
            case 0x26:    //    Constant reference sub-expression
6980 1
            case 0x46:
6981 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...
6982
                $name = 'tMemArea';
6983
                // offset: 1; size: 4; not used
6984
                // offset: 5; size: 2; size of the following subexpression
6985
                $subSize = self::getUInt2d($formulaData, 5);
6986
                $size = 7 + $subSize;
6987
                $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
6988
6989
                break;
6990 1
            case 0x27:    //    Deleted constant reference sub-expression
6991 1
            case 0x47:
6992 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...
6993
                $name = 'tMemErr';
6994
                // offset: 1; size: 4; not used
6995
                // offset: 5; size: 2; size of the following subexpression
6996
                $subSize = self::getUInt2d($formulaData, 5);
6997
                $size = 7 + $subSize;
6998
                $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
6999
7000
                break;
7001 1
            case 0x29:    //    Variable reference sub-expression
7002 1
            case 0x49:
7003 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...
7004
                $name = 'tMemFunc';
7005
                // offset: 1; size: 2; size of the following sub-expression
7006
                $subSize = self::getUInt2d($formulaData, 1);
7007
                $size = 3 + $subSize;
7008
                $data = $this->getFormulaFromData(substr($formulaData, 3, $subSize));
7009
7010
                break;
7011 1
            case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places
7012 1
            case 0x4C:
7013 1
            case 0x6C:
7014
                $name = 'tRefN';
7015
                $size = 5;
7016
                $data = $this->readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell);
7017
7018
                break;
7019 1
            case 0x2D:    //    Relative 2d range reference
7020 1
            case 0x4D:
7021 1
            case 0x6D:
7022
                $name = 'tAreaN';
7023
                $size = 9;
7024
                $data = $this->readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell);
7025
7026
                break;
7027 1
            case 0x39:    //    External name
7028 1
            case 0x59:
7029 1
            case 0x79:
7030
                $name = 'tNameX';
7031
                $size = 7;
7032
                // offset: 1; size: 2; index to REF entry in EXTERNSHEET record
7033
                // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record
7034
                $index = self::getUInt2d($formulaData, 3);
7035
                // assume index is to EXTERNNAME record
7036
                $data = $this->externalNames[$index - 1]['name'];
7037
                // offset: 5; size: 2; not used
7038
                break;
7039 1
            case 0x3A:    //    3d reference to cell
7040 1
            case 0x5A:
7041 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...
7042
                $name = 'tRef3d';
7043
                $size = 7;
7044
7045
                try {
7046
                    // offset: 1; size: 2; index to REF entry
7047
                    $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1));
7048
                    // offset: 3; size: 4; cell address
7049
                    $cellAddress = $this->readBIFF8CellAddress(substr($formulaData, 3, 4));
7050
7051
                    $data = "$sheetRange!$cellAddress";
7052
                } catch (PhpSpreadsheetException $e) {
7053
                    // deleted sheet reference
7054
                    $data = '#REF!';
7055
                }
7056
7057
                break;
7058 1
            case 0x3B:    //    3d reference to cell range
7059
            case 0x5B:
7060 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...
7061 1
                $name = 'tArea3d';
7062 1
                $size = 11;
7063
7064
                try {
7065
                    // offset: 1; size: 2; index to REF entry
7066 1
                    $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1));
7067
                    // offset: 3; size: 8; cell address
7068 1
                    $cellRangeAddress = $this->readBIFF8CellRangeAddress(substr($formulaData, 3, 8));
7069
7070 1
                    $data = "$sheetRange!$cellRangeAddress";
7071
                } catch (PhpSpreadsheetException $e) {
7072
                    // deleted sheet reference
7073
                    $data = '#REF!';
7074
                }
7075
7076 1
                break;
7077
            // Unknown cases    // don't know how to deal with
7078
            default:
7079
                throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula');
7080
                break;
7081
        }
7082
7083
        return [
7084 10
            'id' => $id,
7085 10
            'name' => $name,
7086 10
            'size' => $size,
7087 10
            'data' => $data,
7088
        ];
7089
    }
7090
7091
    /**
7092
     * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2'
7093
     * section 3.3.4.
7094
     *
7095
     * @param string $cellAddressStructure
7096
     *
7097
     * @return string
7098
     */
7099 2
    private function readBIFF8CellAddress($cellAddressStructure)
7100
    {
7101
        // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
7102 2
        $row = self::getUInt2d($cellAddressStructure, 0) + 1;
7103
7104
        // offset: 2; size: 2; index to column or column offset + relative flags
7105
        // bit: 7-0; mask 0x00FF; column index
7106 2
        $column = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($cellAddressStructure, 2)) + 1);
7107
7108
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7109 2
        if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) {
7110 1
            $column = '$' . $column;
7111
        }
7112
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7113 2
        if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
7114 1
            $row = '$' . $row;
7115
        }
7116
7117 2
        return $column . $row;
7118
    }
7119
7120
    /**
7121
     * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column
7122
     * to indicate offsets from a base cell
7123
     * section 3.3.4.
7124
     *
7125
     * @param string $cellAddressStructure
7126
     * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
7127
     *
7128
     * @return string
7129
     */
7130
    private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1')
7131
    {
7132
        list($baseCol, $baseRow) = Coordinate::coordinateFromString($baseCell);
7133
        $baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
7134
7135
        // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
7136
        $rowIndex = self::getUInt2d($cellAddressStructure, 0);
7137
        $row = self::getUInt2d($cellAddressStructure, 0) + 1;
7138
7139
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7140 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...
7141
            // offset: 2; size: 2; index to column or column offset + relative flags
7142
            // bit: 7-0; mask 0x00FF; column index
7143
            $colIndex = 0x00FF & self::getUInt2d($cellAddressStructure, 2);
7144
7145
            $column = Coordinate::stringFromColumnIndex($colIndex + 1);
7146
            $column = '$' . $column;
7147
        } else {
7148
            // offset: 2; size: 2; index to column or column offset + relative flags
7149
            // bit: 7-0; mask 0x00FF; column index
7150
            $relativeColIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2);
7151
            $colIndex = $baseCol + $relativeColIndex;
7152
            $colIndex = ($colIndex < 256) ? $colIndex : $colIndex - 256;
7153
            $colIndex = ($colIndex >= 0) ? $colIndex : $colIndex + 256;
7154
            $column = Coordinate::stringFromColumnIndex($colIndex + 1);
7155
        }
7156
7157
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7158
        if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
7159
            $row = '$' . $row;
7160
        } else {
7161
            $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536;
7162
            $row = $baseRow + $rowIndex;
7163
        }
7164
7165
        return $column . $row;
7166
    }
7167
7168
    /**
7169
     * Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1'
7170
     * always fixed range
7171
     * section 2.5.14.
7172
     *
7173
     * @param string $subData
7174
     *
7175
     * @throws Exception
7176
     *
7177
     * @return string
7178
     */
7179 17
    private function readBIFF5CellRangeAddressFixed($subData)
7180
    {
7181
        // offset: 0; size: 2; index to first row
7182 17
        $fr = self::getUInt2d($subData, 0) + 1;
7183
7184
        // offset: 2; size: 2; index to last row
7185 17
        $lr = self::getUInt2d($subData, 2) + 1;
7186
7187
        // offset: 4; size: 1; index to first column
7188 17
        $fc = ord($subData[4]);
7189
7190
        // offset: 5; size: 1; index to last column
7191 17
        $lc = ord($subData[5]);
7192
7193
        // check values
7194 17
        if ($fr > $lr || $fc > $lc) {
7195
            throw new Exception('Not a cell range address');
7196
        }
7197
7198
        // column index to letter
7199 17
        $fc = Coordinate::stringFromColumnIndex($fc + 1);
7200 17
        $lc = Coordinate::stringFromColumnIndex($lc + 1);
7201
7202 17
        if ($fr == $lr and $fc == $lc) {
7203 13
            return "$fc$fr";
7204
        }
7205
7206 12
        return "$fc$fr:$lc$lr";
7207
    }
7208
7209
    /**
7210
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1'
7211
     * always fixed range
7212
     * section 2.5.14.
7213
     *
7214
     * @param string $subData
7215
     *
7216
     * @throws Exception
7217
     *
7218
     * @return string
7219
     */
7220 11
    private function readBIFF8CellRangeAddressFixed($subData)
7221
    {
7222
        // offset: 0; size: 2; index to first row
7223 11
        $fr = self::getUInt2d($subData, 0) + 1;
7224
7225
        // offset: 2; size: 2; index to last row
7226 11
        $lr = self::getUInt2d($subData, 2) + 1;
7227
7228
        // offset: 4; size: 2; index to first column
7229 11
        $fc = self::getUInt2d($subData, 4);
7230
7231
        // offset: 6; size: 2; index to last column
7232 11
        $lc = self::getUInt2d($subData, 6);
7233
7234
        // check values
7235 11
        if ($fr > $lr || $fc > $lc) {
7236
            throw new Exception('Not a cell range address');
7237
        }
7238
7239
        // column index to letter
7240 11
        $fc = Coordinate::stringFromColumnIndex($fc + 1);
7241 11
        $lc = Coordinate::stringFromColumnIndex($lc + 1);
7242
7243 11
        if ($fr == $lr and $fc == $lc) {
7244 2
            return "$fc$fr";
7245
        }
7246
7247 11
        return "$fc$fr:$lc$lr";
7248
    }
7249
7250
    /**
7251
     * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6'
7252
     * there are flags indicating whether column/row index is relative
7253
     * section 3.3.4.
7254
     *
7255
     * @param string $subData
7256
     *
7257
     * @return string
7258
     */
7259 10
    private function readBIFF8CellRangeAddress($subData)
7260
    {
7261
        // todo: if cell range is just a single cell, should this funciton
7262
        // not just return e.g. 'A1' and not 'A1:A1' ?
7263
7264
        // offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767))
7265 10
        $fr = self::getUInt2d($subData, 0) + 1;
7266
7267
        // offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767))
7268 10
        $lr = self::getUInt2d($subData, 2) + 1;
7269
7270
        // offset: 4; size: 2; index to first column or column offset + relative flags
7271
7272
        // bit: 7-0; mask 0x00FF; column index
7273 10
        $fc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 4)) + 1);
7274
7275
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7276 10
        if (!(0x4000 & self::getUInt2d($subData, 4))) {
7277 1
            $fc = '$' . $fc;
7278
        }
7279
7280
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7281 10
        if (!(0x8000 & self::getUInt2d($subData, 4))) {
7282 1
            $fr = '$' . $fr;
7283
        }
7284
7285
        // offset: 6; size: 2; index to last column or column offset + relative flags
7286
7287
        // bit: 7-0; mask 0x00FF; column index
7288 10
        $lc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 6)) + 1);
7289
7290
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7291 10
        if (!(0x4000 & self::getUInt2d($subData, 6))) {
7292 1
            $lc = '$' . $lc;
7293
        }
7294
7295
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7296 10
        if (!(0x8000 & self::getUInt2d($subData, 6))) {
7297 1
            $lr = '$' . $lr;
7298
        }
7299
7300 10
        return "$fc$fr:$lc$lr";
7301
    }
7302
7303
    /**
7304
     * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column
7305
     * to indicate offsets from a base cell
7306
     * section 3.3.4.
7307
     *
7308
     * @param string $subData
7309
     * @param string $baseCell Base cell
7310
     *
7311
     * @return string Cell range address
7312
     */
7313
    private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1')
7314
    {
7315
        list($baseCol, $baseRow) = Coordinate::coordinateFromString($baseCell);
7316
        $baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
7317
7318
        // TODO: if cell range is just a single cell, should this funciton
7319
        // not just return e.g. 'A1' and not 'A1:A1' ?
7320
7321
        // offset: 0; size: 2; first row
7322
        $frIndex = self::getUInt2d($subData, 0); // adjust below
7323
7324
        // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767)
7325
        $lrIndex = self::getUInt2d($subData, 2); // adjust below
7326
7327
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7328 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...
7329
            // absolute column index
7330
            // offset: 4; size: 2; first column with relative/absolute flags
7331
            // bit: 7-0; mask 0x00FF; column index
7332
            $fcIndex = 0x00FF & self::getUInt2d($subData, 4);
7333
            $fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
7334
            $fc = '$' . $fc;
7335
        } else {
7336
            // column offset
7337
            // offset: 4; size: 2; first column with relative/absolute flags
7338
            // bit: 7-0; mask 0x00FF; column index
7339
            $relativeFcIndex = 0x00FF & self::getInt2d($subData, 4);
7340
            $fcIndex = $baseCol + $relativeFcIndex;
7341
            $fcIndex = ($fcIndex < 256) ? $fcIndex : $fcIndex - 256;
7342
            $fcIndex = ($fcIndex >= 0) ? $fcIndex : $fcIndex + 256;
7343
            $fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
7344
        }
7345
7346
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7347 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...
7348
            // absolute row index
7349
            $fr = $frIndex + 1;
7350
            $fr = '$' . $fr;
7351
        } else {
7352
            // row offset
7353
            $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536;
7354
            $fr = $baseRow + $frIndex;
7355
        }
7356
7357
        // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
7358 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...
7359
            // absolute column index
7360
            // offset: 6; size: 2; last column with relative/absolute flags
7361
            // bit: 7-0; mask 0x00FF; column index
7362
            $lcIndex = 0x00FF & self::getUInt2d($subData, 6);
7363
            $lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
7364
            $lc = '$' . $lc;
7365
        } else {
7366
            // column offset
7367
            // offset: 4; size: 2; first column with relative/absolute flags
7368
            // bit: 7-0; mask 0x00FF; column index
7369
            $relativeLcIndex = 0x00FF & self::getInt2d($subData, 4);
7370
            $lcIndex = $baseCol + $relativeLcIndex;
7371
            $lcIndex = ($lcIndex < 256) ? $lcIndex : $lcIndex - 256;
7372
            $lcIndex = ($lcIndex >= 0) ? $lcIndex : $lcIndex + 256;
7373
            $lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
7374
        }
7375
7376
        // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
7377 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...
7378
            // absolute row index
7379
            $lr = $lrIndex + 1;
7380
            $lr = '$' . $lr;
7381
        } else {
7382
            // row offset
7383
            $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536;
7384
            $lr = $baseRow + $lrIndex;
7385
        }
7386
7387
        return "$fc$fr:$lc$lr";
7388
    }
7389
7390
    /**
7391
     * Read BIFF8 cell range address list
7392
     * section 2.5.15.
7393
     *
7394
     * @param string $subData
7395
     *
7396
     * @return array
7397
     */
7398 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...
7399
    {
7400 11
        $cellRangeAddresses = [];
7401
7402
        // offset: 0; size: 2; number of the following cell range addresses
7403 11
        $nm = self::getUInt2d($subData, 0);
7404
7405 11
        $offset = 2;
7406
        // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses
7407 11
        for ($i = 0; $i < $nm; ++$i) {
7408 11
            $cellRangeAddresses[] = $this->readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
7409 11
            $offset += 8;
7410
        }
7411
7412
        return [
7413 11
            'size' => 2 + 8 * $nm,
7414 11
            'cellRangeAddresses' => $cellRangeAddresses,
7415
        ];
7416
    }
7417
7418
    /**
7419
     * Read BIFF5 cell range address list
7420
     * section 2.5.15.
7421
     *
7422
     * @param string $subData
7423
     *
7424
     * @return array
7425
     */
7426 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...
7427
    {
7428 17
        $cellRangeAddresses = [];
7429
7430
        // offset: 0; size: 2; number of the following cell range addresses
7431 17
        $nm = self::getUInt2d($subData, 0);
7432
7433 17
        $offset = 2;
7434
        // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses
7435 17
        for ($i = 0; $i < $nm; ++$i) {
7436 17
            $cellRangeAddresses[] = $this->readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6));
7437 17
            $offset += 6;
7438
        }
7439
7440
        return [
7441 17
            'size' => 2 + 6 * $nm,
7442 17
            'cellRangeAddresses' => $cellRangeAddresses,
7443
        ];
7444
    }
7445
7446
    /**
7447
     * Get a sheet range like Sheet1:Sheet3 from REF index
7448
     * Note: If there is only one sheet in the range, one gets e.g Sheet1
7449
     * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets,
7450
     * in which case an Exception is thrown.
7451
     *
7452
     * @param int $index
7453
     *
7454
     * @throws Exception
7455
     *
7456
     * @return false|string
7457
     */
7458 1
    private function readSheetRangeByRefIndex($index)
7459
    {
7460 1
        if (isset($this->ref[$index])) {
7461 1
            $type = $this->externalBooks[$this->ref[$index]['externalBookIndex']]['type'];
7462
7463
            switch ($type) {
7464 1
                case 'internal':
7465
                    // check if we have a deleted 3d reference
7466 1
                    if ($this->ref[$index]['firstSheetIndex'] == 0xFFFF or $this->ref[$index]['lastSheetIndex'] == 0xFFFF) {
7467
                        throw new Exception('Deleted sheet reference');
7468
                    }
7469
7470
                    // we have normal sheet range (collapsed or uncollapsed)
7471 1
                    $firstSheetName = $this->sheets[$this->ref[$index]['firstSheetIndex']]['name'];
7472 1
                    $lastSheetName = $this->sheets[$this->ref[$index]['lastSheetIndex']]['name'];
7473
7474 1
                    if ($firstSheetName == $lastSheetName) {
7475
                        // collapsed sheet range
7476 1
                        $sheetRange = $firstSheetName;
7477
                    } else {
7478
                        $sheetRange = "$firstSheetName:$lastSheetName";
7479
                    }
7480
7481
                    // escape the single-quotes
7482 1
                    $sheetRange = str_replace("'", "''", $sheetRange);
7483
7484
                    // if there are special characters, we need to enclose the range in single-quotes
7485
                    // todo: check if we have identified the whole set of special characters
7486
                    // it seems that the following characters are not accepted for sheet names
7487
                    // and we may assume that they are not present: []*/:\?
7488 1
                    if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/u", $sheetRange)) {
7489
                        $sheetRange = "'$sheetRange'";
7490
                    }
7491
7492 1
                    return $sheetRange;
7493
                    break;
7494
                default:
7495
                    // TODO: external sheet support
7496
                    throw new Exception('Xls reader only supports internal sheets in fomulas');
7497
                    break;
7498
            }
7499
        }
7500
7501
        return false;
7502
    }
7503
7504
    /**
7505
     * read BIFF8 constant value array from array data
7506
     * returns e.g. array('value' => '{1,2;3,4}', 'size' => 40}
7507
     * section 2.5.8.
7508
     *
7509
     * @param string $arrayData
7510
     *
7511
     * @return array
7512
     */
7513
    private static function readBIFF8ConstantArray($arrayData)
7514
    {
7515
        // offset: 0; size: 1; number of columns decreased by 1
7516
        $nc = ord($arrayData[0]);
7517
7518
        // offset: 1; size: 2; number of rows decreased by 1
7519
        $nr = self::getUInt2d($arrayData, 1);
7520
        $size = 3; // initialize
7521
        $arrayData = substr($arrayData, 3);
7522
7523
        // offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values
7524
        $matrixChunks = [];
7525
        for ($r = 1; $r <= $nr + 1; ++$r) {
7526
            $items = [];
7527
            for ($c = 1; $c <= $nc + 1; ++$c) {
7528
                $constant = self::readBIFF8Constant($arrayData);
7529
                $items[] = $constant['value'];
7530
                $arrayData = substr($arrayData, $constant['size']);
7531
                $size += $constant['size'];
7532
            }
7533
            $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'
7534
        }
7535
        $matrix = '{' . implode(';', $matrixChunks) . '}';
7536
7537
        return [
7538
            'value' => $matrix,
7539
            'size' => $size,
7540
        ];
7541
    }
7542
7543
    /**
7544
     * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value'
7545
     * section 2.5.7
7546
     * returns e.g. array('value' => '5', 'size' => 9).
7547
     *
7548
     * @param string $valueData
7549
     *
7550
     * @return array
7551
     */
7552
    private static function readBIFF8Constant($valueData)
7553
    {
7554
        // offset: 0; size: 1; identifier for type of constant
7555
        $identifier = ord($valueData[0]);
7556
7557
        switch ($identifier) {
7558
            case 0x00: // empty constant (what is this?)
7559
                $value = '';
7560
                $size = 9;
7561
7562
                break;
7563
            case 0x01: // number
7564
                // offset: 1; size: 8; IEEE 754 floating-point value
7565
                $value = self::extractNumber(substr($valueData, 1, 8));
7566
                $size = 9;
7567
7568
                break;
7569
            case 0x02: // string value
7570
                // offset: 1; size: var; Unicode string, 16-bit string length
7571
                $string = self::readUnicodeStringLong(substr($valueData, 1));
7572
                $value = '"' . $string['value'] . '"';
7573
                $size = 1 + $string['size'];
7574
7575
                break;
7576
            case 0x04: // boolean
7577
                // offset: 1; size: 1; 0 = FALSE, 1 = TRUE
7578
                if (ord($valueData[1])) {
7579
                    $value = 'TRUE';
7580
                } else {
7581
                    $value = 'FALSE';
7582
                }
7583
                $size = 9;
7584
7585
                break;
7586
            case 0x10: // error code
7587
                // offset: 1; size: 1; error code
7588
                $value = Xls\ErrorCode::lookup(ord($valueData[1]));
7589
                $size = 9;
7590
7591
                break;
7592
        }
7593
7594
        return [
7595
            'value' => $value,
7596
            'size' => $size,
7597
        ];
7598
    }
7599
7600
    /**
7601
     * Extract RGB color
7602
     * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4.
7603
     *
7604
     * @param string $rgb Encoded RGB value (4 bytes)
7605
     *
7606
     * @return array
7607
     */
7608 4
    private static function readRGB($rgb)
7609
    {
7610
        // offset: 0; size 1; Red component
7611 4
        $r = ord($rgb[0]);
7612
7613
        // offset: 1; size: 1; Green component
7614 4
        $g = ord($rgb[1]);
7615
7616
        // offset: 2; size: 1; Blue component
7617 4
        $b = ord($rgb[2]);
7618
7619
        // HEX notation, e.g. 'FF00FC'
7620 4
        $rgb = sprintf('%02X%02X%02X', $r, $g, $b);
7621
7622 4
        return ['rgb' => $rgb];
7623
    }
7624
7625
    /**
7626
     * Read byte string (8-bit string length)
7627
     * OpenOffice documentation: 2.5.2.
7628
     *
7629
     * @param string $subData
7630
     *
7631
     * @return array
7632
     */
7633 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...
7634
    {
7635
        // offset: 0; size: 1; length of the string (character count)
7636
        $ln = ord($subData[0]);
7637
7638
        // offset: 1: size: var; character array (8-bit characters)
7639
        $value = $this->decodeCodepage(substr($subData, 1, $ln));
7640
7641
        return [
7642
            'value' => $value,
7643
            'size' => 1 + $ln, // size in bytes of data structure
7644
        ];
7645
    }
7646
7647
    /**
7648
     * Read byte string (16-bit string length)
7649
     * OpenOffice documentation: 2.5.2.
7650
     *
7651
     * @param string $subData
7652
     *
7653
     * @return array
7654
     */
7655 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...
7656
    {
7657
        // offset: 0; size: 2; length of the string (character count)
7658
        $ln = self::getUInt2d($subData, 0);
7659
7660
        // offset: 2: size: var; character array (8-bit characters)
7661
        $value = $this->decodeCodepage(substr($subData, 2));
7662
7663
        //return $string;
7664
        return [
7665
            'value' => $value,
7666
            'size' => 2 + $ln, // size in bytes of data structure
7667
        ];
7668
    }
7669
7670
    /**
7671
     * Extracts an Excel Unicode short string (8-bit string length)
7672
     * OpenOffice documentation: 2.5.3
7673
     * function will automatically find out where the Unicode string ends.
7674
     *
7675
     * @param string $subData
7676
     *
7677
     * @return array
7678
     */
7679 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...
7680
    {
7681 21
        $value = '';
7682
7683
        // offset: 0: size: 1; length of the string (character count)
7684 21
        $characterCount = ord($subData[0]);
7685
7686 21
        $string = self::readUnicodeString(substr($subData, 1), $characterCount);
7687
7688
        // add 1 for the string length
7689 21
        $string['size'] += 1;
7690
7691 21
        return $string;
7692
    }
7693
7694
    /**
7695
     * Extracts an Excel Unicode long string (16-bit string length)
7696
     * OpenOffice documentation: 2.5.3
7697
     * this function is under construction, needs to support rich text, and Asian phonetic settings.
7698
     *
7699
     * @param string $subData
7700
     *
7701
     * @return array
7702
     */
7703 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...
7704
    {
7705 17
        $value = '';
7706
7707
        // offset: 0: size: 2; length of the string (character count)
7708 17
        $characterCount = self::getUInt2d($subData, 0);
7709
7710 17
        $string = self::readUnicodeString(substr($subData, 2), $characterCount);
7711
7712
        // add 2 for the string length
7713 17
        $string['size'] += 2;
7714
7715 17
        return $string;
7716
    }
7717
7718
    /**
7719
     * Read Unicode string with no string length field, but with known character count
7720
     * this function is under construction, needs to support rich text, and Asian phonetic settings
7721
     * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3.
7722
     *
7723
     * @param string $subData
7724
     * @param int $characterCount
7725
     *
7726
     * @return array
7727
     */
7728 21
    private static function readUnicodeString($subData, $characterCount)
7729
    {
7730 21
        $value = '';
7731
7732
        // offset: 0: size: 1; option flags
7733
        // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)
7734 21
        $isCompressed = !((0x01 & ord($subData[0])) >> 0);
7735
7736
        // bit: 2; mask: 0x04; Asian phonetic settings
7737 21
        $hasAsian = (0x04) & ord($subData[0]) >> 2;
7738
7739
        // bit: 3; mask: 0x08; Rich-Text settings
7740 21
        $hasRichText = (0x08) & ord($subData[0]) >> 3;
7741
7742
        // offset: 1: size: var; character array
7743
        // this offset assumes richtext and Asian phonetic settings are off which is generally wrong
7744
        // needs to be fixed
7745 21
        $value = self::encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed);
7746
7747
        return [
7748 21
            'value' => $value,
7749 21
            'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags
7750
        ];
7751
    }
7752
7753
    /**
7754
     * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas.
7755
     * Example:  hello"world  -->  "hello""world".
7756
     *
7757
     * @param string $value UTF-8 encoded string
7758
     *
7759
     * @return string
7760
     */
7761 1
    private static function UTF8toExcelDoubleQuoted($value)
7762
    {
7763 1
        return '"' . str_replace('"', '""', $value) . '"';
7764
    }
7765
7766
    /**
7767
     * Reads first 8 bytes of a string and return IEEE 754 float.
7768
     *
7769
     * @param string $data Binary string that is at least 8 bytes long
7770
     *
7771
     * @return float
7772
     */
7773 18
    private static function extractNumber($data)
7774
    {
7775 18
        $rknumhigh = self::getInt4d($data, 4);
7776 18
        $rknumlow = self::getInt4d($data, 0);
7777 18
        $sign = ($rknumhigh & 0x80000000) >> 31;
7778 18
        $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023;
7779 18
        $mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
7780 18
        $mantissalow1 = ($rknumlow & 0x80000000) >> 31;
7781 18
        $mantissalow2 = ($rknumlow & 0x7fffffff);
7782 18
        $value = $mantissa / pow(2, (20 - $exp));
7783
7784 18
        if ($mantissalow1 != 0) {
7785 12
            $value += 1 / pow(2, (21 - $exp));
7786
        }
7787
7788 18
        $value += $mantissalow2 / pow(2, (52 - $exp));
7789 18
        if ($sign) {
7790 10
            $value *= -1;
7791
        }
7792
7793 18
        return $value;
7794
    }
7795
7796
    /**
7797
     * @param int $rknum
7798
     */
7799 12
    private static function getIEEE754($rknum)
7800
    {
7801 12
        if (($rknum & 0x02) != 0) {
7802
            $value = $rknum >> 2;
7803
        } else {
7804
            // changes by mmp, info on IEEE754 encoding from
7805
            // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
7806
            // The RK format calls for using only the most significant 30 bits
7807
            // of the 64 bit floating point value. The other 34 bits are assumed
7808
            // to be 0 so we use the upper 30 bits of $rknum as follows...
7809 12
            $sign = ($rknum & 0x80000000) >> 31;
7810 12
            $exp = ($rknum & 0x7ff00000) >> 20;
7811 12
            $mantissa = (0x100000 | ($rknum & 0x000ffffc));
7812 12
            $value = $mantissa / pow(2, (20 - ($exp - 1023)));
7813 12
            if ($sign) {
7814 10
                $value = -1 * $value;
7815
            }
7816
            //end of changes by mmp
7817
        }
7818 12
        if (($rknum & 0x01) != 0) {
7819 10
            $value /= 100;
7820
        }
7821
7822 12
        return $value;
7823
    }
7824
7825
    /**
7826
     * Get UTF-8 string from (compressed or uncompressed) UTF-16 string.
7827
     *
7828
     * @param string $string
7829
     * @param bool $compressed
7830
     *
7831
     * @return string
7832
     */
7833 21
    private static function encodeUTF16($string, $compressed = false)
7834
    {
7835 21
        if ($compressed) {
7836 20
            $string = self::uncompressByteString($string);
7837
        }
7838
7839 21
        return StringHelper::convertEncoding($string, 'UTF-8', 'UTF-16LE');
7840
    }
7841
7842
    /**
7843
     * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8.
7844
     *
7845
     * @param string $string
7846
     *
7847
     * @return string
7848
     */
7849 20
    private static function uncompressByteString($string)
7850
    {
7851 20
        $uncompressedString = '';
7852 20
        $strLen = strlen($string);
7853 20
        for ($i = 0; $i < $strLen; ++$i) {
7854 20
            $uncompressedString .= $string[$i] . "\0";
7855
        }
7856
7857 20
        return $uncompressedString;
7858
    }
7859
7860
    /**
7861
     * Convert string to UTF-8. Only used for BIFF5.
7862
     *
7863
     * @param string $string
7864
     *
7865
     * @return string
7866
     */
7867
    private function decodeCodepage($string)
7868
    {
7869
        return StringHelper::convertEncoding($string, 'UTF-8', $this->codepage);
7870
    }
7871
7872
    /**
7873
     * Read 16-bit unsigned integer.
7874
     *
7875
     * @param string $data
7876
     * @param int $pos
7877
     *
7878
     * @return int
7879
     */
7880 21
    public static function getUInt2d($data, $pos)
7881
    {
7882 21
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
7883
    }
7884
7885
    /**
7886
     * Read 16-bit signed integer.
7887
     *
7888
     * @param string $data
7889
     * @param int $pos
7890
     *
7891
     * @return int
7892
     */
7893
    public static function getInt2d($data, $pos)
7894
    {
7895
        return unpack('s', $data[$pos] . $data[$pos + 1])[1];
7896
    }
7897
7898
    /**
7899
     * Read 32-bit signed integer.
7900
     *
7901
     * @param string $data
7902
     * @param int $pos
7903
     *
7904
     * @return int
7905
     */
7906 21
    public static function getInt4d($data, $pos)
7907
    {
7908
        // FIX: represent numbers correctly on 64-bit system
7909
        // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
7910
        // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
7911 21
        $_or_24 = ord($data[$pos + 3]);
7912 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...
7913
            // negative number
7914 12
            $_ord_24 = -abs((256 - $_or_24) << 24);
7915
        } else {
7916 21
            $_ord_24 = ($_or_24 & 127) << 24;
7917
        }
7918
7919 21
        return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
7920
    }
7921
7922 1
    private function parseRichText($is)
7923
    {
7924 1
        $value = new RichText();
7925 1
        $value->createText($is);
7926
7927 1
        return $value;
7928
    }
7929
}
7930