Passed
Pull Request — master (#3487)
by Mark
14:08
created

Spreadsheet   F

Complexity

Total Complexity 198

Size/Duplication

Total Lines 1650
Duplicated Lines 0 %

Test Coverage

Coverage 97.59%

Importance

Changes 0
Metric Value
wmc 198
eloc 396
dl 0
loc 1650
ccs 445
cts 456
cp 0.9759
rs 2
c 0
b 0
f 0

97 Methods

Rating   Name   Duplication   Size   Complexity  
A addNamedRange() 0 3 1
A getMacrosCertificate() 0 3 1
A getDefinedNames() 0 3 1
A getGlobalDefinedNameByType() 0 7 3
A addNamedFormula() 0 3 1
A setShowVerticalScroll() 0 3 1
A getShowHorizontalScroll() 0 3 1
A removeDefinedName() 0 17 5
A getSheetCount() 0 3 1
A setShowHorizontalScroll() 0 3 1
A getAllSheets() 0 3 1
A getLocalDefinedNameByType() 0 10 4
A removeCellStyleXfByIndex() 0 6 2
A setAutoFilterDateGrouping() 0 3 1
A getVisibility() 0 3 1
A removeNamedRange() 0 7 2
A getCellStyleXfByIndex() 0 3 1
A addDefinedName() 0 9 2
A getNamedRange() 0 13 4
A addCellStyleXf() 0 4 1
A getSheetByName() 0 10 3
A setFirstSheetIndex() 0 6 2
A hasMacrosCertificate() 0 3 1
A getActiveSheetIndex() 0 3 1
A getShowSheetTabs() 0 3 1
A removeSheetByIndex() 0 16 5
A getTheme() 0 3 1
A getCellStyleXfCollection() 0 3 1
A disconnectWorksheets() 0 7 2
A getExtensionOnly() 0 5 1
A getTabRatio() 0 3 1
A getSheetByCodeName() 0 10 3
A jsonSerialize() 0 3 1
A __serialize() 0 3 1
A getProperties() 0 3 1
A getNamedFormulae() 0 6 1
A getCellStyleXfByHashCode() 0 9 3
A setShowSheetTabs() 0 3 1
F garbageCollect() 0 74 17
A __construct() 0 27 1
A getCellXfCollection() 0 3 1
A hasMacros() 0 3 1
A removeNamedFormula() 0 7 2
A getDefinedName() 0 18 5
A getSecurity() 0 3 1
A setMinimized() 0 3 1
A setIndexByName() 0 16 1
A getSharedComponent() 0 3 1
A getSheetByNameOrThrow() 0 8 2
A cellXfExists() 0 3 1
A getCellXfByHashCode() 0 9 3
A addCellXf() 0 4 1
A getCalculationEngine() 0 3 1
A getSheet() 0 11 2
A __clone() 0 4 1
A getID() 0 3 1
A getDefaultStyle() 0 7 2
A hasRibbon() 0 3 1
A getIndex() 0 9 3
A setUnparsedLoadedData() 0 3 1
A getNamedFormula() 0 13 4
B getRibbonBinObjects() 0 29 10
A setProperties() 0 3 1
A getSheetNames() 0 9 2
A getWorksheetIterator() 0 3 1
B addExternalSheet() 0 37 7
A setTabRatio() 0 6 3
A copy() 0 13 1
A getActiveSheet() 0 3 1
A getAutoFilterDateGrouping() 0 3 1
A createSheet() 0 6 1
A getShowVerticalScroll() 0 3 1
A getNamedRanges() 0 6 1
A discardMacros() 0 5 1
A getUnparsedLoadedData() 0 3 1
A getFirstSheetIndex() 0 3 1
A setRibbonBinObjects() 0 6 3
A getCellXfByIndex() 0 3 1
A setActiveSheetIndex() 0 12 2
A hasRibbonBinObjects() 0 3 1
A reevaluateAutoFilters() 0 9 4
A setVisibility() 0 10 3
A addSheet() 0 33 6
A setSecurity() 0 3 1
A getCellXfSupervisor() 0 3 1
A getMinimized() 0 3 1
A getMacrosCode() 0 3 1
A __destruct() 0 6 1
A removeCellXfByIndex() 0 20 6
A setMacrosCertificate() 0 3 1
A setRibbonXMLData() 0 6 3
A sheetCodeNameExists() 0 3 1
A setHasMacros() 0 3 1
A getRibbonXMLData() 0 19 5
A sheetNameExists() 0 3 1
A setMacrosCode() 0 4 1
A setActiveSheetIndexByName() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like Spreadsheet often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Spreadsheet, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet;
4
5
use JsonSerializable;
6
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
7
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
8
use PhpOffice\PhpSpreadsheet\Shared\File;
9
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
10
use PhpOffice\PhpSpreadsheet\Style\Style;
11
use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
12
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
13
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
14
15
class Spreadsheet implements JsonSerializable
16
{
17
    // Allowable values for workbook window visilbity
18
    const VISIBILITY_VISIBLE = 'visible';
19
    const VISIBILITY_HIDDEN = 'hidden';
20
    const VISIBILITY_VERY_HIDDEN = 'veryHidden';
21
22
    private const DEFINED_NAME_IS_RANGE = false;
23
    private const DEFINED_NAME_IS_FORMULA = true;
24
25
    private const WORKBOOK_VIEW_VISIBILITY_VALUES = [
26
        self::VISIBILITY_VISIBLE,
27
        self::VISIBILITY_HIDDEN,
28
        self::VISIBILITY_VERY_HIDDEN,
29
    ];
30
31
    /**
32
     * Unique ID.
33
     *
34
     * @var string
35
     */
36
    private $uniqueID;
37
38
    /**
39
     * Document properties.
40
     *
41
     * @var Document\Properties
42
     */
43
    private $properties;
44
45
    /**
46
     * Document security.
47
     *
48
     * @var Document\Security
49
     */
50
    private $security;
51
52
    /**
53
     * Collection of Worksheet objects.
54
     *
55
     * @var Worksheet[]
56
     */
57
    private $workSheetCollection = [];
58
59
    /**
60
     * Calculation Engine.
61
     *
62
     * @var null|Calculation
63
     */
64
    private $calculationEngine;
65
66
    /**
67
     * Active sheet index.
68
     *
69
     * @var int
70
     */
71
    private $activeSheetIndex = 0;
72
73
    /**
74
     * Named ranges.
75
     *
76
     * @var DefinedName[]
77
     */
78
    private $definedNames = [];
79
80
    /**
81
     * CellXf supervisor.
82
     *
83
     * @var Style
84
     */
85
    private $cellXfSupervisor;
86
87
    /**
88
     * CellXf collection.
89
     *
90
     * @var Style[]
91
     */
92
    private $cellXfCollection = [];
93
94
    /**
95
     * CellStyleXf collection.
96
     *
97
     * @var Style[]
98
     */
99
    private $cellStyleXfCollection = [];
100
101
    /**
102
     * hasMacros : this workbook have macros ?
103
     *
104
     * @var bool
105
     */
106
    private $hasMacros = false;
107
108
    /**
109
     * macrosCode : all macros code as binary data (the vbaProject.bin file, this include form, code,  etc.), null if no macro.
110
     *
111
     * @var null|string
112
     */
113
    private $macrosCode;
114
115
    /**
116
     * macrosCertificate : if macros are signed, contains binary data vbaProjectSignature.bin file, null if not signed.
117
     *
118
     * @var null|string
119
     */
120
    private $macrosCertificate;
121
122
    /**
123
     * ribbonXMLData : null if workbook is'nt Excel 2007 or not contain a customized UI.
124
     *
125
     * @var null|array{target: string, data: string}
126
     */
127
    private $ribbonXMLData;
128
129
    /**
130
     * ribbonBinObjects : null if workbook is'nt Excel 2007 or not contain embedded objects (picture(s)) for Ribbon Elements
131
     * ignored if $ribbonXMLData is null.
132
     *
133
     * @var null|array
134
     */
135
    private $ribbonBinObjects;
136
137
    /**
138
     * List of unparsed loaded data for export to same format with better compatibility.
139
     * It has to be minimized when the library start to support currently unparsed data.
140
     *
141
     * @var array
142
     */
143
    private $unparsedLoadedData = [];
144
145
    /**
146
     * Controls visibility of the horizonal scroll bar in the application.
147
     *
148
     * @var bool
149
     */
150
    private $showHorizontalScroll = true;
151
152
    /**
153
     * Controls visibility of the horizonal scroll bar in the application.
154
     *
155
     * @var bool
156
     */
157
    private $showVerticalScroll = true;
158
159
    /**
160
     * Controls visibility of the sheet tabs in the application.
161
     *
162
     * @var bool
163
     */
164
    private $showSheetTabs = true;
165
166
    /**
167
     * Specifies a boolean value that indicates whether the workbook window
168
     * is minimized.
169
     *
170
     * @var bool
171
     */
172
    private $minimized = false;
173
174
    /**
175
     * Specifies a boolean value that indicates whether to group dates
176
     * when presenting the user with filtering optiomd in the user
177
     * interface.
178
     *
179
     * @var bool
180
     */
181
    private $autoFilterDateGrouping = true;
182
183
    /**
184
     * Specifies the index to the first sheet in the book view.
185
     *
186
     * @var int
187
     */
188
    private $firstSheetIndex = 0;
189
190
    /**
191
     * Specifies the visible status of the workbook.
192
     *
193
     * @var string
194
     */
195
    private $visibility = self::VISIBILITY_VISIBLE;
196
197
    /**
198
     * Specifies the ratio between the workbook tabs bar and the horizontal
199
     * scroll bar.  TabRatio is assumed to be out of 1000 of the horizontal
200
     * window width.
201
     *
202
     * @var int
203
     */
204
    private $tabRatio = 600;
205
206
    /** @var Theme */
207
    private $theme;
208
209 600
    public function getTheme(): Theme
210
    {
211 600
        return $this->theme;
212
    }
213
214
    /**
215
     * The workbook has macros ?
216
     *
217
     * @return bool
218
     */
219 311
    public function hasMacros()
220
    {
221 311
        return $this->hasMacros;
222
    }
223
224
    /**
225
     * Define if a workbook has macros.
226
     *
227
     * @param bool $hasMacros true|false
228
     */
229 3
    public function setHasMacros($hasMacros): void
230
    {
231 3
        $this->hasMacros = (bool) $hasMacros;
232
    }
233
234
    /**
235
     * Set the macros code.
236
     *
237
     * @param string $macroCode string|null
238
     */
239 3
    public function setMacrosCode($macroCode): void
240
    {
241 3
        $this->macrosCode = $macroCode;
242 3
        $this->setHasMacros($macroCode !== null);
243
    }
244
245
    /**
246
     * Return the macros code.
247
     *
248
     * @return null|string
249
     */
250 3
    public function getMacrosCode()
251
    {
252 3
        return $this->macrosCode;
253
    }
254
255
    /**
256
     * Set the macros certificate.
257
     *
258
     * @param null|string $certificate
259
     */
260 3
    public function setMacrosCertificate($certificate): void
261
    {
262 3
        $this->macrosCertificate = $certificate;
263
    }
264
265
    /**
266
     * Is the project signed ?
267
     *
268
     * @return bool true|false
269
     */
270 2
    public function hasMacrosCertificate()
271
    {
272 2
        return $this->macrosCertificate !== null;
273
    }
274
275
    /**
276
     * Return the macros certificate.
277
     *
278
     * @return null|string
279
     */
280 2
    public function getMacrosCertificate()
281
    {
282 2
        return $this->macrosCertificate;
283
    }
284
285
    /**
286
     * Remove all macros, certificate from spreadsheet.
287
     */
288 1
    public function discardMacros(): void
289
    {
290 1
        $this->hasMacros = false;
291 1
        $this->macrosCode = null;
292 1
        $this->macrosCertificate = null;
293
    }
294
295
    /**
296
     * set ribbon XML data.
297
     *
298
     * @param null|mixed $target
299
     * @param null|mixed $xmlData
300
     */
301 2
    public function setRibbonXMLData($target, $xmlData): void
302
    {
303 2
        if ($target !== null && $xmlData !== null) {
304 2
            $this->ribbonXMLData = ['target' => $target, 'data' => $xmlData];
305
        } else {
306
            $this->ribbonXMLData = null;
307
        }
308
    }
309
310
    /**
311
     * retrieve ribbon XML Data.
312
     *
313
     * @param string $what
314
     *
315
     * @return null|array|string
316
     */
317 273
    public function getRibbonXMLData($what = 'all') //we need some constants here...
318
    {
319 273
        $returnData = null;
320 273
        $what = strtolower($what);
321
        switch ($what) {
322 273
            case 'all':
323 2
                $returnData = $this->ribbonXMLData;
324
325 2
                break;
326 273
            case 'target':
327 2
            case 'data':
328 273
                if (is_array($this->ribbonXMLData)) {
329 2
                    $returnData = $this->ribbonXMLData[$what];
330
                }
331
332 273
                break;
333
        }
334
335 273
        return $returnData;
336
    }
337
338
    /**
339
     * store binaries ribbon objects (pictures).
340
     *
341
     * @param null|mixed $BinObjectsNames
342
     * @param null|mixed $BinObjectsData
343
     */
344 2
    public function setRibbonBinObjects($BinObjectsNames, $BinObjectsData): void
345
    {
346 2
        if ($BinObjectsNames !== null && $BinObjectsData !== null) {
347
            $this->ribbonBinObjects = ['names' => $BinObjectsNames, 'data' => $BinObjectsData];
348
        } else {
349 2
            $this->ribbonBinObjects = null;
350
        }
351
    }
352
353
    /**
354
     * List of unparsed loaded data for export to same format with better compatibility.
355
     * It has to be minimized when the library start to support currently unparsed data.
356
     *
357
     * @internal
358
     *
359
     * @return array
360
     */
361 311
    public function getUnparsedLoadedData()
362
    {
363 311
        return $this->unparsedLoadedData;
364
    }
365
366
    /**
367
     * List of unparsed loaded data for export to same format with better compatibility.
368
     * It has to be minimized when the library start to support currently unparsed data.
369
     *
370
     * @internal
371
     */
372 505
    public function setUnparsedLoadedData(array $unparsedLoadedData): void
373
    {
374 505
        $this->unparsedLoadedData = $unparsedLoadedData;
375
    }
376
377
    /**
378
     * return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function).
379
     *
380
     * @param mixed $path
381
     *
382
     * @return string
383
     */
384
    private function getExtensionOnly($path)
385
    {
386
        $extension = pathinfo($path, PATHINFO_EXTENSION);
387
388
        return substr(/** @scrutinizer ignore-type */$extension, 0);
389
    }
390
391
    /**
392
     * retrieve Binaries Ribbon Objects.
393
     *
394
     * @param string $what
395
     *
396
     * @return null|array
397
     */
398 2
    public function getRibbonBinObjects($what = 'all')
399
    {
400 2
        $ReturnData = null;
401 2
        $what = strtolower($what);
402
        switch ($what) {
403 2
            case 'all':
404 2
                return $this->ribbonBinObjects;
405 1
            case 'names':
406 1
            case 'data':
407 1
                if (is_array($this->ribbonBinObjects) && isset($this->ribbonBinObjects[$what])) {
408
                    $ReturnData = $this->ribbonBinObjects[$what];
409
                }
410
411 1
                break;
412 1
            case 'types':
413
                if (
414 1
                    is_array($this->ribbonBinObjects) &&
415 1
                    isset($this->ribbonBinObjects['data']) && is_array($this->ribbonBinObjects['data'])
416
                ) {
417
                    $tmpTypes = array_keys($this->ribbonBinObjects['data']);
418
                    $ReturnData = array_unique(array_map([$this, 'getExtensionOnly'], $tmpTypes));
419
                } else {
420 1
                    $ReturnData = []; // the caller want an array... not null if empty
421
                }
422
423 1
                break;
424
        }
425
426 1
        return $ReturnData;
427
    }
428
429
    /**
430
     * This workbook have a custom UI ?
431
     *
432
     * @return bool
433
     */
434 273
    public function hasRibbon()
435
    {
436 273
        return $this->ribbonXMLData !== null;
437
    }
438
439
    /**
440
     * This workbook have additionnal object for the ribbon ?
441
     *
442
     * @return bool
443
     */
444 273
    public function hasRibbonBinObjects()
445
    {
446 273
        return $this->ribbonBinObjects !== null;
447
    }
448
449
    /**
450
     * Check if a sheet with a specified code name already exists.
451
     *
452
     * @param string $codeName Name of the worksheet to check
453
     *
454
     * @return bool
455
     */
456 9683
    public function sheetCodeNameExists($codeName)
457
    {
458 9683
        return $this->getSheetByCodeName($codeName) !== null;
459
    }
460
461
    /**
462
     * Get sheet by code name. Warning : sheet don't have always a code name !
463
     *
464
     * @param string $codeName Sheet name
465
     *
466
     * @return null|Worksheet
467
     */
468 9683
    public function getSheetByCodeName($codeName)
469
    {
470 9683
        $worksheetCount = count($this->workSheetCollection);
471 9683
        for ($i = 0; $i < $worksheetCount; ++$i) {
472 635
            if ($this->workSheetCollection[$i]->getCodeName() == $codeName) {
473 467
                return $this->workSheetCollection[$i];
474
            }
475
        }
476
477 9683
        return null;
478
    }
479
480
    /**
481
     * Create a new PhpSpreadsheet with one Worksheet.
482
     */
483 9683
    public function __construct()
484
    {
485 9683
        $this->uniqueID = uniqid('', true);
486 9683
        $this->calculationEngine = new Calculation($this);
487 9683
        $this->theme = new Theme();
488
489
        // Initialise worksheet collection and add one worksheet
490 9683
        $this->workSheetCollection = [];
491 9683
        $this->workSheetCollection[] = new Worksheet($this);
492 9683
        $this->activeSheetIndex = 0;
493
494
        // Create document properties
495 9683
        $this->properties = new Document\Properties();
496
497
        // Create document security
498 9683
        $this->security = new Document\Security();
499
500
        // Set defined names
501 9683
        $this->definedNames = [];
502
503
        // Create the cellXf supervisor
504 9683
        $this->cellXfSupervisor = new Style(true);
505 9683
        $this->cellXfSupervisor->bindParent($this);
506
507
        // Create the default style
508 9683
        $this->addCellXf(new Style());
509 9683
        $this->addCellStyleXf(new Style());
510
    }
511
512
    /**
513
     * Code to execute when this worksheet is unset().
514
     */
515 100
    public function __destruct()
516
    {
517 100
        $this->disconnectWorksheets();
518 100
        $this->calculationEngine = null;
519 100
        $this->cellXfCollection = [];
520 100
        $this->cellStyleXfCollection = [];
521
    }
522
523
    /**
524
     * Disconnect all worksheets from this PhpSpreadsheet workbook object,
525
     * typically so that the PhpSpreadsheet object can be unset.
526
     */
527 8217
    public function disconnectWorksheets(): void
528
    {
529 8217
        foreach ($this->workSheetCollection as $worksheet) {
530 8217
            $worksheet->disconnectCells();
531 8217
            unset($worksheet);
532
        }
533 8217
        $this->workSheetCollection = [];
534
    }
535
536
    /**
537
     * Return the calculation engine for this worksheet.
538
     *
539
     * @return null|Calculation
540
     */
541 9683
    public function getCalculationEngine()
542
    {
543 9683
        return $this->calculationEngine;
544
    }
545
546
    /**
547
     * Get properties.
548
     *
549
     * @return Document\Properties
550
     */
551 1190
    public function getProperties()
552
    {
553 1190
        return $this->properties;
554
    }
555
556
    /**
557
     * Set properties.
558
     */
559 1
    public function setProperties(Document\Properties $documentProperties): void
560
    {
561 1
        $this->properties = $documentProperties;
562
    }
563
564
    /**
565
     * Get security.
566
     *
567
     * @return Document\Security
568
     */
569 282
    public function getSecurity()
570
    {
571 282
        return $this->security;
572
    }
573
574
    /**
575
     * Set security.
576
     */
577 1
    public function setSecurity(Document\Security $documentSecurity): void
578
    {
579 1
        $this->security = $documentSecurity;
580
    }
581
582
    /**
583
     * Get active sheet.
584
     *
585
     * @return Worksheet
586
     */
587 9627
    public function getActiveSheet()
588
    {
589 9627
        return $this->getSheet($this->activeSheetIndex);
590
    }
591
592
    /**
593
     * Create sheet and add it to this workbook.
594
     *
595
     * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
596
     *
597
     * @return Worksheet
598
     */
599 904
    public function createSheet($sheetIndex = null)
600
    {
601 904
        $newSheet = new Worksheet($this);
602 904
        $this->addSheet($newSheet, $sheetIndex);
603
604 904
        return $newSheet;
605
    }
606
607
    /**
608
     * Check if a sheet with a specified name already exists.
609
     *
610
     * @param string $worksheetName Name of the worksheet to check
611
     *
612
     * @return bool
613
     */
614 9683
    public function sheetNameExists($worksheetName)
615
    {
616 9683
        return $this->getSheetByName($worksheetName) !== null;
617
    }
618
619
    /**
620
     * Add sheet.
621
     *
622
     * @param Worksheet $worksheet The worksheet to add
623
     * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
624
     *
625
     * @return Worksheet
626
     */
627 985
    public function addSheet(Worksheet $worksheet, $sheetIndex = null)
628
    {
629 985
        if ($this->sheetNameExists($worksheet->getTitle())) {
630 1
            throw new Exception(
631 1
                "Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename this worksheet first."
632 1
            );
633
        }
634
635 985
        if ($sheetIndex === null) {
636 956
            if ($this->activeSheetIndex < 0) {
637 612
                $this->activeSheetIndex = 0;
638
            }
639 956
            $this->workSheetCollection[] = $worksheet;
640
        } else {
641
            // Insert the sheet at the requested index
642 32
            array_splice(
643 32
                $this->workSheetCollection,
644 32
                $sheetIndex,
645 32
                0,
646 32
                [$worksheet]
647 32
            );
648
649
            // Adjust active sheet index if necessary
650 32
            if ($this->activeSheetIndex >= $sheetIndex) {
651 28
                ++$this->activeSheetIndex;
652
            }
653
        }
654
655 985
        if ($worksheet->getParent() === null) {
656 50
            $worksheet->rebindParent($this);
657
        }
658
659 985
        return $worksheet;
660
    }
661
662
    /**
663
     * Remove sheet by index.
664
     *
665
     * @param int $sheetIndex Index position of the worksheet to remove
666
     */
667 623
    public function removeSheetByIndex($sheetIndex): void
668
    {
669 623
        $numSheets = count($this->workSheetCollection);
670 623
        if ($sheetIndex > $numSheets - 1) {
671 1
            throw new Exception(
672 1
                "You tried to remove a sheet by the out of bounds index: {$sheetIndex}. The actual number of sheets is {$numSheets}."
673 1
            );
674
        }
675 622
        array_splice($this->workSheetCollection, $sheetIndex, 1);
676
677
        // Adjust active sheet index if necessary
678
        if (
679 622
            ($this->activeSheetIndex >= $sheetIndex) &&
680 622
            ($this->activeSheetIndex > 0 || $numSheets <= 1)
681
        ) {
682 620
            --$this->activeSheetIndex;
683
        }
684
    }
685
686
    /**
687
     * Get sheet by index.
688
     *
689
     * @param int $sheetIndex Sheet index
690
     *
691
     * @return Worksheet
692
     */
693 9644
    public function getSheet($sheetIndex)
694
    {
695 9644
        if (!isset($this->workSheetCollection[$sheetIndex])) {
696 1
            $numSheets = $this->getSheetCount();
697
698 1
            throw new Exception(
699 1
                "Your requested sheet index: {$sheetIndex} is out of bounds. The actual number of sheets is {$numSheets}."
700 1
            );
701
        }
702
703 9644
        return $this->workSheetCollection[$sheetIndex];
704
    }
705
706
    /**
707
     * Get all sheets.
708
     *
709
     * @return Worksheet[]
710
     */
711 128
    public function getAllSheets()
712
    {
713 128
        return $this->workSheetCollection;
714
    }
715
716
    /**
717
     * Get sheet by name.
718
     *
719
     * @param string $worksheetName Sheet name
720
     *
721
     * @return null|Worksheet
722
     */
723 9683
    public function getSheetByName($worksheetName)
724
    {
725 9683
        $worksheetCount = count($this->workSheetCollection);
726 9683
        for ($i = 0; $i < $worksheetCount; ++$i) {
727 8576
            if ($this->workSheetCollection[$i]->getTitle() === trim($worksheetName, "'")) {
728 8072
                return $this->workSheetCollection[$i];
729
            }
730
        }
731
732 9683
        return null;
733
    }
734
735
    /**
736
     * Get sheet by name, throwing exception if not found.
737
     */
738 206
    public function getSheetByNameOrThrow(string $worksheetName): Worksheet
739
    {
740 206
        $worksheet = $this->getSheetByName($worksheetName);
741 206
        if ($worksheet === null) {
742 1
            throw new Exception("Sheet $worksheetName does not exist.");
743
        }
744
745 205
        return $worksheet;
746
    }
747
748
    /**
749
     * Get index for sheet.
750
     *
751
     * @return int index
752
     */
753 8946
    public function getIndex(Worksheet $worksheet)
754
    {
755 8946
        foreach ($this->workSheetCollection as $key => $value) {
756 8946
            if ($value->getHashCode() === $worksheet->getHashCode()) {
757 8945
                return $key;
758
            }
759
        }
760
761 2
        throw new Exception('Sheet does not exist.');
762
    }
763
764
    /**
765
     * Set index for sheet by sheet name.
766
     *
767
     * @param string $worksheetName Sheet name to modify index for
768
     * @param int $newIndexPosition New index for the sheet
769
     *
770
     * @return int New sheet index
771
     */
772 1
    public function setIndexByName($worksheetName, $newIndexPosition)
773
    {
774 1
        $oldIndex = $this->getIndex($this->getSheetByNameOrThrow($worksheetName));
775 1
        $worksheet = array_splice(
776 1
            $this->workSheetCollection,
777 1
            $oldIndex,
778 1
            1
779 1
        );
780 1
        array_splice(
781 1
            $this->workSheetCollection,
782 1
            $newIndexPosition,
783 1
            0,
784 1
            $worksheet
785 1
        );
786
787 1
        return $newIndexPosition;
788
    }
789
790
    /**
791
     * Get sheet count.
792
     *
793
     * @return int
794
     */
795 1532
    public function getSheetCount()
796
    {
797 1532
        return count($this->workSheetCollection);
798
    }
799
800
    /**
801
     * Get active sheet index.
802
     *
803
     * @return int Active sheet index
804
     */
805 7998
    public function getActiveSheetIndex()
806
    {
807 7998
        return $this->activeSheetIndex;
808
    }
809
810
    /**
811
     * Set active sheet index.
812
     *
813
     * @param int $worksheetIndex Active sheet index
814
     *
815
     * @return Worksheet
816
     */
817 9009
    public function setActiveSheetIndex($worksheetIndex)
818
    {
819 9009
        $numSheets = count($this->workSheetCollection);
820
821 9009
        if ($worksheetIndex > $numSheets - 1) {
822 1
            throw new Exception(
823 1
                "You tried to set a sheet active by the out of bounds index: {$worksheetIndex}. The actual number of sheets is {$numSheets}."
824 1
            );
825
        }
826 9008
        $this->activeSheetIndex = $worksheetIndex;
827
828 9008
        return $this->getActiveSheet();
829
    }
830
831
    /**
832
     * Set active sheet index by name.
833
     *
834
     * @param string $worksheetName Sheet title
835
     *
836
     * @return Worksheet
837
     */
838 73
    public function setActiveSheetIndexByName($worksheetName)
839
    {
840 73
        if (($worksheet = $this->getSheetByName($worksheetName)) instanceof Worksheet) {
841 71
            $this->setActiveSheetIndex($this->getIndex($worksheet));
842
843 71
            return $worksheet;
844
        }
845
846 2
        throw new Exception('Workbook does not contain sheet:' . $worksheetName);
847
    }
848
849
    /**
850
     * Get sheet names.
851
     *
852
     * @return string[]
853
     */
854 9
    public function getSheetNames()
855
    {
856 9
        $returnValue = [];
857 9
        $worksheetCount = $this->getSheetCount();
858 9
        for ($i = 0; $i < $worksheetCount; ++$i) {
859 9
            $returnValue[] = $this->getSheet($i)->getTitle();
860
        }
861
862 9
        return $returnValue;
863
    }
864
865
    /**
866
     * Add external sheet.
867
     *
868
     * @param Worksheet $worksheet External sheet to add
869
     * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
870
     *
871
     * @return Worksheet
872
     */
873 5
    public function addExternalSheet(Worksheet $worksheet, $sheetIndex = null)
874
    {
875 5
        if ($this->sheetNameExists($worksheet->getTitle())) {
876 1
            throw new Exception("Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename the external sheet first.");
877
        }
878
879
        // count how many cellXfs there are in this workbook currently, we will need this below
880 4
        $countCellXfs = count($this->cellXfCollection);
881
882
        // copy all the shared cellXfs from the external workbook and append them to the current
883 4
        foreach ($worksheet->getParentOrThrow()->getCellXfCollection() as $cellXf) {
884 4
            $this->addCellXf(clone $cellXf);
885
        }
886
887
        // move sheet to this workbook
888 4
        $worksheet->rebindParent($this);
889
890
        // update the cellXfs
891 4
        foreach ($worksheet->getCoordinates(false) as $coordinate) {
892 4
            $cell = $worksheet->getCell($coordinate);
893 4
            $cell->setXfIndex($cell->getXfIndex() + $countCellXfs);
894
        }
895
896
        // update the column dimensions Xfs
897 4
        foreach ($worksheet->getColumnDimensions() as $columnDimension) {
898 1
            $columnDimension->setXfIndex($columnDimension->getXfIndex() + $countCellXfs);
899
        }
900
901
        // update the row dimensions Xfs
902 4
        foreach ($worksheet->getRowDimensions() as $rowDimension) {
903 1
            $xfIndex = $rowDimension->getXfIndex();
904 1
            if ($xfIndex !== null) {
905 1
                $rowDimension->setXfIndex($xfIndex + $countCellXfs);
906
            }
907
        }
908
909 4
        return $this->addSheet($worksheet, $sheetIndex);
910
    }
911
912
    /**
913
     * Get an array of all Named Ranges.
914
     *
915
     * @return DefinedName[]
916
     */
917 9
    public function getNamedRanges(): array
918
    {
919 9
        return array_filter(
920 9
            $this->definedNames,
921 9
            function (DefinedName $definedName) {
922 8
                return $definedName->isFormula() === self::DEFINED_NAME_IS_RANGE;
923 9
            }
924 9
        );
925
    }
926
927
    /**
928
     * Get an array of all Named Formulae.
929
     *
930
     * @return DefinedName[]
931
     */
932 14
    public function getNamedFormulae(): array
933
    {
934 14
        return array_filter(
935 14
            $this->definedNames,
936 14
            function (DefinedName $definedName) {
937 12
                return $definedName->isFormula() === self::DEFINED_NAME_IS_FORMULA;
938 14
            }
939 14
        );
940
    }
941
942
    /**
943
     * Get an array of all Defined Names (both named ranges and named formulae).
944
     *
945
     * @return DefinedName[]
946
     */
947 407
    public function getDefinedNames(): array
948
    {
949 407
        return $this->definedNames;
950
    }
951
952
    /**
953
     * Add a named range.
954
     * If a named range with this name already exists, then this will replace the existing value.
955
     */
956 216
    public function addNamedRange(NamedRange $namedRange): void
957
    {
958 216
        $this->addDefinedName($namedRange);
959
    }
960
961
    /**
962
     * Add a named formula.
963
     * If a named formula with this name already exists, then this will replace the existing value.
964
     */
965 12
    public function addNamedFormula(NamedFormula $namedFormula): void
966
    {
967 12
        $this->addDefinedName($namedFormula);
968
    }
969
970
    /**
971
     * Add a defined name (either a named range or a named formula).
972
     * If a defined named with this name already exists, then this will replace the existing value.
973
     */
974 330
    public function addDefinedName(DefinedName $definedName): void
975
    {
976 330
        $upperCaseName = StringHelper::strToUpper($definedName->getName());
977 330
        if ($definedName->getScope() == null) {
978
            // global scope
979 317
            $this->definedNames[$upperCaseName] = $definedName;
980
        } else {
981
            // local scope
982 119
            $this->definedNames[$definedName->getScope()->getTitle() . '!' . $upperCaseName] = $definedName;
983
        }
984
    }
985
986
    /**
987
     * Get named range.
988
     *
989
     * @param null|Worksheet $worksheet Scope. Use null for global scope
990
     */
991 26
    public function getNamedRange(string $namedRange, ?Worksheet $worksheet = null): ?NamedRange
992
    {
993 26
        $returnValue = null;
994
995 26
        if ($namedRange !== '') {
996 26
            $namedRange = StringHelper::strToUpper($namedRange);
997
            // first look for global named range
998 26
            $returnValue = $this->getGlobalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE);
999
            // then look for local named range (has priority over global named range if both names exist)
1000 26
            $returnValue = $this->getLocalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE, $worksheet) ?: $returnValue;
1001
        }
1002
1003 26
        return $returnValue instanceof NamedRange ? $returnValue : null;
1004
    }
1005
1006
    /**
1007
     * Get named formula.
1008
     *
1009
     * @param null|Worksheet $worksheet Scope. Use null for global scope
1010
     */
1011 11
    public function getNamedFormula(string $namedFormula, ?Worksheet $worksheet = null): ?NamedFormula
1012
    {
1013 11
        $returnValue = null;
1014
1015 11
        if ($namedFormula !== '') {
1016 11
            $namedFormula = StringHelper::strToUpper($namedFormula);
1017
            // first look for global named formula
1018 11
            $returnValue = $this->getGlobalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA);
1019
            // then look for local named formula (has priority over global named formula if both names exist)
1020 11
            $returnValue = $this->getLocalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA, $worksheet) ?: $returnValue;
1021
        }
1022
1023 11
        return $returnValue instanceof NamedFormula ? $returnValue : null;
1024
    }
1025
1026 37
    private function getGlobalDefinedNameByType(string $name, bool $type): ?DefinedName
1027
    {
1028 37
        if (isset($this->definedNames[$name]) && $this->definedNames[$name]->isFormula() === $type) {
1029 27
            return $this->definedNames[$name];
1030
        }
1031
1032 12
        return null;
1033
    }
1034
1035 37
    private function getLocalDefinedNameByType(string $name, bool $type, ?Worksheet $worksheet = null): ?DefinedName
1036
    {
1037
        if (
1038 37
            ($worksheet !== null) && isset($this->definedNames[$worksheet->getTitle() . '!' . $name])
1039 37
            && $this->definedNames[$worksheet->getTitle() . '!' . $name]->isFormula() === $type
1040
        ) {
1041 8
            return $this->definedNames[$worksheet->getTitle() . '!' . $name];
1042
        }
1043
1044 35
        return null;
1045
    }
1046
1047
    /**
1048
     * Get named range.
1049
     *
1050
     * @param null|Worksheet $worksheet Scope. Use null for global scope
1051
     */
1052 8926
    public function getDefinedName(string $definedName, ?Worksheet $worksheet = null): ?DefinedName
1053
    {
1054 8926
        $returnValue = null;
1055
1056 8926
        if ($definedName !== '') {
1057 8926
            $definedName = StringHelper::strToUpper($definedName);
1058
            // first look for global defined name
1059 8926
            if (isset($this->definedNames[$definedName])) {
1060 115
                $returnValue = $this->definedNames[$definedName];
1061
            }
1062
1063
            // then look for local defined name (has priority over global defined name if both names exist)
1064 8926
            if (($worksheet !== null) && isset($this->definedNames[$worksheet->getTitle() . '!' . $definedName])) {
1065 20
                $returnValue = $this->definedNames[$worksheet->getTitle() . '!' . $definedName];
1066
            }
1067
        }
1068
1069 8926
        return $returnValue;
1070
    }
1071
1072
    /**
1073
     * Remove named range.
1074
     *
1075
     * @param null|Worksheet $worksheet scope: use null for global scope
1076
     *
1077
     * @return $this
1078
     */
1079 5
    public function removeNamedRange(string $namedRange, ?Worksheet $worksheet = null): self
1080
    {
1081 5
        if ($this->getNamedRange($namedRange, $worksheet) === null) {
1082 1
            return $this;
1083
        }
1084
1085 4
        return $this->removeDefinedName($namedRange, $worksheet);
1086
    }
1087
1088
    /**
1089
     * Remove named formula.
1090
     *
1091
     * @param null|Worksheet $worksheet scope: use null for global scope
1092
     *
1093
     * @return $this
1094
     */
1095 4
    public function removeNamedFormula(string $namedFormula, ?Worksheet $worksheet = null): self
1096
    {
1097 4
        if ($this->getNamedFormula($namedFormula, $worksheet) === null) {
1098 1
            return $this;
1099
        }
1100
1101 3
        return $this->removeDefinedName($namedFormula, $worksheet);
1102
    }
1103
1104
    /**
1105
     * Remove defined name.
1106
     *
1107
     * @param null|Worksheet $worksheet scope: use null for global scope
1108
     *
1109
     * @return $this
1110
     */
1111 11
    public function removeDefinedName(string $definedName, ?Worksheet $worksheet = null): self
1112
    {
1113 11
        $definedName = StringHelper::strToUpper($definedName);
1114
1115 11
        if ($worksheet === null) {
1116 1
            if (isset($this->definedNames[$definedName])) {
1117 1
                unset($this->definedNames[$definedName]);
1118
            }
1119
        } else {
1120 10
            if (isset($this->definedNames[$worksheet->getTitle() . '!' . $definedName])) {
1121 3
                unset($this->definedNames[$worksheet->getTitle() . '!' . $definedName]);
1122 7
            } elseif (isset($this->definedNames[$definedName])) {
1123 7
                unset($this->definedNames[$definedName]);
1124
            }
1125
        }
1126
1127 11
        return $this;
1128
    }
1129
1130
    /**
1131
     * Get worksheet iterator.
1132
     *
1133
     * @return Iterator
1134
     */
1135 1070
    public function getWorksheetIterator()
1136
    {
1137 1070
        return new Iterator($this);
1138
    }
1139
1140
    /**
1141
     * Copy workbook (!= clone!).
1142
     *
1143
     * @return Spreadsheet
1144
     */
1145 1
    public function copy()
1146
    {
1147 1
        $filename = File::temporaryFilename();
1148 1
        $writer = new XlsxWriter($this);
1149 1
        $writer->setIncludeCharts(true);
1150 1
        $writer->save($filename);
1151
1152 1
        $reader = new XlsxReader();
1153 1
        $reader->setIncludeCharts(true);
1154 1
        $reloadedSpreadsheet = $reader->load($filename);
1155 1
        unlink($filename);
1156
1157 1
        return $reloadedSpreadsheet;
1158
    }
1159
1160 1
    public function __clone()
1161
    {
1162 1
        throw new Exception(
1163 1
            'Do not use clone on spreadsheet. Use spreadsheet->copy() instead.'
1164 1
        );
1165
    }
1166
1167
    /**
1168
     * Get the workbook collection of cellXfs.
1169
     *
1170
     * @return Style[]
1171
     */
1172 821
    public function getCellXfCollection()
1173
    {
1174 821
        return $this->cellXfCollection;
1175
    }
1176
1177
    /**
1178
     * Get cellXf by index.
1179
     *
1180
     * @param int $cellStyleIndex
1181
     *
1182
     * @return Style
1183
     */
1184 8697
    public function getCellXfByIndex($cellStyleIndex)
1185
    {
1186 8697
        return $this->cellXfCollection[$cellStyleIndex];
1187
    }
1188
1189
    /**
1190
     * Get cellXf by hash code.
1191
     *
1192
     * @param string $hashcode
1193
     *
1194
     * @return false|Style
1195
     */
1196 816
    public function getCellXfByHashCode($hashcode)
1197
    {
1198 816
        foreach ($this->cellXfCollection as $cellXf) {
1199 816
            if ($cellXf->getHashCode() === $hashcode) {
1200 255
                return $cellXf;
1201
            }
1202
        }
1203
1204 691
        return false;
1205
    }
1206
1207
    /**
1208
     * Check if style exists in style collection.
1209
     *
1210
     * @return bool
1211
     */
1212 1
    public function cellXfExists(Style $cellStyleIndex)
1213
    {
1214 1
        return in_array($cellStyleIndex, $this->cellXfCollection, true);
1215
    }
1216
1217
    /**
1218
     * Get default style.
1219
     *
1220
     * @return Style
1221
     */
1222 778
    public function getDefaultStyle()
1223
    {
1224 778
        if (isset($this->cellXfCollection[0])) {
1225 777
            return $this->cellXfCollection[0];
1226
        }
1227
1228 1
        throw new Exception('No default style found for this workbook');
1229
    }
1230
1231
    /**
1232
     * Add a cellXf to the workbook.
1233
     */
1234 9683
    public function addCellXf(Style $style): void
1235
    {
1236 9683
        $this->cellXfCollection[] = $style;
1237 9683
        $style->setIndex(count($this->cellXfCollection) - 1);
1238
    }
1239
1240
    /**
1241
     * Remove cellXf by index. It is ensured that all cells get their xf index updated.
1242
     *
1243
     * @param int $cellStyleIndex Index to cellXf
1244
     */
1245 587
    public function removeCellXfByIndex($cellStyleIndex): void
1246
    {
1247 587
        if ($cellStyleIndex > count($this->cellXfCollection) - 1) {
1248 1
            throw new Exception('CellXf index is out of bounds.');
1249
        }
1250
1251
        // first remove the cellXf
1252 586
        array_splice($this->cellXfCollection, $cellStyleIndex, 1);
1253
1254
        // then update cellXf indexes for cells
1255 586
        foreach ($this->workSheetCollection as $worksheet) {
1256 2
            foreach ($worksheet->getCoordinates(false) as $coordinate) {
1257 1
                $cell = $worksheet->getCell($coordinate);
1258 1
                $xfIndex = $cell->getXfIndex();
1259 1
                if ($xfIndex > $cellStyleIndex) {
1260
                    // decrease xf index by 1
1261 1
                    $cell->setXfIndex($xfIndex - 1);
1262 1
                } elseif ($xfIndex == $cellStyleIndex) {
1263
                    // set to default xf index 0
1264 1
                    $cell->setXfIndex(0);
1265
                }
1266
            }
1267
        }
1268
    }
1269
1270
    /**
1271
     * Get the cellXf supervisor.
1272
     *
1273
     * @return Style
1274
     */
1275 8700
    public function getCellXfSupervisor()
1276
    {
1277 8700
        return $this->cellXfSupervisor;
1278
    }
1279
1280
    /**
1281
     * Get the workbook collection of cellStyleXfs.
1282
     *
1283
     * @return Style[]
1284
     */
1285 1
    public function getCellStyleXfCollection()
1286
    {
1287 1
        return $this->cellStyleXfCollection;
1288
    }
1289
1290
    /**
1291
     * Get cellStyleXf by index.
1292
     *
1293
     * @param int $cellStyleIndex Index to cellXf
1294
     *
1295
     * @return Style
1296
     */
1297 1
    public function getCellStyleXfByIndex($cellStyleIndex)
1298
    {
1299 1
        return $this->cellStyleXfCollection[$cellStyleIndex];
1300
    }
1301
1302
    /**
1303
     * Get cellStyleXf by hash code.
1304
     *
1305
     * @param string $hashcode
1306
     *
1307
     * @return false|Style
1308
     */
1309 1
    public function getCellStyleXfByHashCode($hashcode)
1310
    {
1311 1
        foreach ($this->cellStyleXfCollection as $cellStyleXf) {
1312 1
            if ($cellStyleXf->getHashCode() === $hashcode) {
1313 1
                return $cellStyleXf;
1314
            }
1315
        }
1316
1317 1
        return false;
1318
    }
1319
1320
    /**
1321
     * Add a cellStyleXf to the workbook.
1322
     */
1323 9683
    public function addCellStyleXf(Style $style): void
1324
    {
1325 9683
        $this->cellStyleXfCollection[] = $style;
1326 9683
        $style->setIndex(count($this->cellStyleXfCollection) - 1);
1327
    }
1328
1329
    /**
1330
     * Remove cellStyleXf by index.
1331
     *
1332
     * @param int $cellStyleIndex Index to cellXf
1333
     */
1334 585
    public function removeCellStyleXfByIndex($cellStyleIndex): void
1335
    {
1336 585
        if ($cellStyleIndex > count($this->cellStyleXfCollection) - 1) {
1337 1
            throw new Exception('CellStyleXf index is out of bounds.');
1338
        }
1339 584
        array_splice($this->cellStyleXfCollection, $cellStyleIndex, 1);
1340
    }
1341
1342
    /**
1343
     * Eliminate all unneeded cellXf and afterwards update the xfIndex for all cells
1344
     * and columns in the workbook.
1345
     */
1346 776
    public function garbageCollect(): void
1347
    {
1348
        // how many references are there to each cellXf ?
1349 776
        $countReferencesCellXf = [];
1350 776
        foreach ($this->cellXfCollection as $index => $cellXf) {
1351 776
            $countReferencesCellXf[$index] = 0;
1352
        }
1353
1354 776
        foreach ($this->getWorksheetIterator() as $sheet) {
1355
            // from cells
1356 776
            foreach ($sheet->getCoordinates(false) as $coordinate) {
1357 758
                $cell = $sheet->getCell($coordinate);
1358 758
                ++$countReferencesCellXf[$cell->getXfIndex()];
1359
            }
1360
1361
            // from row dimensions
1362 776
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1363 95
                if ($rowDimension->getXfIndex() !== null) {
1364 6
                    ++$countReferencesCellXf[$rowDimension->getXfIndex()];
1365
                }
1366
            }
1367
1368
            // from column dimensions
1369 776
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1370 99
                ++$countReferencesCellXf[$columnDimension->getXfIndex()];
1371
            }
1372
        }
1373
1374
        // remove cellXfs without references and create mapping so we can update xfIndex
1375
        // for all cells and columns
1376 776
        $countNeededCellXfs = 0;
1377 776
        $map = [];
1378 776
        foreach ($this->cellXfCollection as $index => $cellXf) {
1379 776
            if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf
1380 776
                ++$countNeededCellXfs;
1381
            } else {
1382 28
                unset($this->cellXfCollection[$index]);
1383
            }
1384 776
            $map[$index] = $countNeededCellXfs - 1;
1385
        }
1386 776
        $this->cellXfCollection = array_values($this->cellXfCollection);
1387
1388
        // update the index for all cellXfs
1389 776
        foreach ($this->cellXfCollection as $i => $cellXf) {
1390 776
            $cellXf->setIndex($i);
1391
        }
1392
1393
        // make sure there is always at least one cellXf (there should be)
1394 776
        if (empty($this->cellXfCollection)) {
1395
            $this->cellXfCollection[] = new Style();
1396
        }
1397
1398
        // update the xfIndex for all cells, row dimensions, column dimensions
1399 776
        foreach ($this->getWorksheetIterator() as $sheet) {
1400
            // for all cells
1401 776
            foreach ($sheet->getCoordinates(false) as $coordinate) {
1402 758
                $cell = $sheet->getCell($coordinate);
1403 758
                $cell->setXfIndex($map[$cell->getXfIndex()]);
1404
            }
1405
1406
            // for all row dimensions
1407 776
            foreach ($sheet->getRowDimensions() as $rowDimension) {
1408 95
                if ($rowDimension->getXfIndex() !== null) {
1409 6
                    $rowDimension->setXfIndex($map[$rowDimension->getXfIndex()]);
1410
                }
1411
            }
1412
1413
            // for all column dimensions
1414 776
            foreach ($sheet->getColumnDimensions() as $columnDimension) {
1415 99
                $columnDimension->setXfIndex($map[$columnDimension->getXfIndex()]);
1416
            }
1417
1418
            // also do garbage collection for all the sheets
1419 776
            $sheet->garbageCollect();
1420
        }
1421
    }
1422
1423
    /**
1424
     * Return the unique ID value assigned to this spreadsheet workbook.
1425
     *
1426
     * @return string
1427
     */
1428
    public function getID()
1429
    {
1430
        return $this->uniqueID;
1431
    }
1432
1433
    /**
1434
     * Get the visibility of the horizonal scroll bar in the application.
1435
     *
1436
     * @return bool True if horizonal scroll bar is visible
1437
     */
1438 273
    public function getShowHorizontalScroll()
1439
    {
1440 273
        return $this->showHorizontalScroll;
1441
    }
1442
1443
    /**
1444
     * Set the visibility of the horizonal scroll bar in the application.
1445
     *
1446
     * @param bool $showHorizontalScroll True if horizonal scroll bar is visible
1447
     */
1448 171
    public function setShowHorizontalScroll($showHorizontalScroll): void
1449
    {
1450 171
        $this->showHorizontalScroll = (bool) $showHorizontalScroll;
1451
    }
1452
1453
    /**
1454
     * Get the visibility of the vertical scroll bar in the application.
1455
     *
1456
     * @return bool True if vertical scroll bar is visible
1457
     */
1458 273
    public function getShowVerticalScroll()
1459
    {
1460 273
        return $this->showVerticalScroll;
1461
    }
1462
1463
    /**
1464
     * Set the visibility of the vertical scroll bar in the application.
1465
     *
1466
     * @param bool $showVerticalScroll True if vertical scroll bar is visible
1467
     */
1468 171
    public function setShowVerticalScroll($showVerticalScroll): void
1469
    {
1470 171
        $this->showVerticalScroll = (bool) $showVerticalScroll;
1471
    }
1472
1473
    /**
1474
     * Get the visibility of the sheet tabs in the application.
1475
     *
1476
     * @return bool True if the sheet tabs are visible
1477
     */
1478 273
    public function getShowSheetTabs()
1479
    {
1480 273
        return $this->showSheetTabs;
1481
    }
1482
1483
    /**
1484
     * Set the visibility of the sheet tabs  in the application.
1485
     *
1486
     * @param bool $showSheetTabs True if sheet tabs are visible
1487
     */
1488 171
    public function setShowSheetTabs($showSheetTabs): void
1489
    {
1490 171
        $this->showSheetTabs = (bool) $showSheetTabs;
1491
    }
1492
1493
    /**
1494
     * Return whether the workbook window is minimized.
1495
     *
1496
     * @return bool true if workbook window is minimized
1497
     */
1498 273
    public function getMinimized()
1499
    {
1500 273
        return $this->minimized;
1501
    }
1502
1503
    /**
1504
     * Set whether the workbook window is minimized.
1505
     *
1506
     * @param bool $minimized true if workbook window is minimized
1507
     */
1508 167
    public function setMinimized($minimized): void
1509
    {
1510 167
        $this->minimized = (bool) $minimized;
1511
    }
1512
1513
    /**
1514
     * Return whether to group dates when presenting the user with
1515
     * filtering optiomd in the user interface.
1516
     *
1517
     * @return bool true if workbook window is minimized
1518
     */
1519 273
    public function getAutoFilterDateGrouping()
1520
    {
1521 273
        return $this->autoFilterDateGrouping;
1522
    }
1523
1524
    /**
1525
     * Set whether to group dates when presenting the user with
1526
     * filtering optiomd in the user interface.
1527
     *
1528
     * @param bool $autoFilterDateGrouping true if workbook window is minimized
1529
     */
1530 167
    public function setAutoFilterDateGrouping($autoFilterDateGrouping): void
1531
    {
1532 167
        $this->autoFilterDateGrouping = (bool) $autoFilterDateGrouping;
1533
    }
1534
1535
    /**
1536
     * Return the first sheet in the book view.
1537
     *
1538
     * @return int First sheet in book view
1539
     */
1540 273
    public function getFirstSheetIndex()
1541
    {
1542 273
        return $this->firstSheetIndex;
1543
    }
1544
1545
    /**
1546
     * Set the first sheet in the book view.
1547
     *
1548
     * @param int $firstSheetIndex First sheet in book view
1549
     */
1550 172
    public function setFirstSheetIndex($firstSheetIndex): void
1551
    {
1552 172
        if ($firstSheetIndex >= 0) {
1553 171
            $this->firstSheetIndex = (int) $firstSheetIndex;
1554
        } else {
1555 1
            throw new Exception('First sheet index must be a positive integer.');
1556
        }
1557
    }
1558
1559
    /**
1560
     * Return the visibility status of the workbook.
1561
     *
1562
     * This may be one of the following three values:
1563
     * - visibile
1564
     *
1565
     * @return string Visible status
1566
     */
1567 274
    public function getVisibility()
1568
    {
1569 274
        return $this->visibility;
1570
    }
1571
1572
    /**
1573
     * Set the visibility status of the workbook.
1574
     *
1575
     * Valid values are:
1576
     *  - 'visible' (self::VISIBILITY_VISIBLE):
1577
     *       Workbook window is visible
1578
     *  - 'hidden' (self::VISIBILITY_HIDDEN):
1579
     *       Workbook window is hidden, but can be shown by the user
1580
     *       via the user interface
1581
     *  - 'veryHidden' (self::VISIBILITY_VERY_HIDDEN):
1582
     *       Workbook window is hidden and cannot be shown in the
1583
     *       user interface.
1584
     *
1585
     * @param null|string $visibility visibility status of the workbook
1586
     */
1587 168
    public function setVisibility($visibility): void
1588
    {
1589 168
        if ($visibility === null) {
1590 1
            $visibility = self::VISIBILITY_VISIBLE;
1591
        }
1592
1593 168
        if (in_array($visibility, self::WORKBOOK_VIEW_VISIBILITY_VALUES)) {
1594 168
            $this->visibility = $visibility;
1595
        } else {
1596 1
            throw new Exception('Invalid visibility value.');
1597
        }
1598
    }
1599
1600
    /**
1601
     * Get the ratio between the workbook tabs bar and the horizontal scroll bar.
1602
     * TabRatio is assumed to be out of 1000 of the horizontal window width.
1603
     *
1604
     * @return int Ratio between the workbook tabs bar and the horizontal scroll bar
1605
     */
1606 273
    public function getTabRatio()
1607
    {
1608 273
        return $this->tabRatio;
1609
    }
1610
1611
    /**
1612
     * Set the ratio between the workbook tabs bar and the horizontal scroll bar
1613
     * TabRatio is assumed to be out of 1000 of the horizontal window width.
1614
     *
1615
     * @param int $tabRatio Ratio between the tabs bar and the horizontal scroll bar
1616
     */
1617 175
    public function setTabRatio($tabRatio): void
1618
    {
1619 175
        if ($tabRatio >= 0 && $tabRatio <= 1000) {
1620 174
            $this->tabRatio = (int) $tabRatio;
1621
        } else {
1622 1
            throw new Exception('Tab ratio must be between 0 and 1000.');
1623
        }
1624
    }
1625
1626 2
    public function reevaluateAutoFilters(bool $resetToMax): void
1627
    {
1628 2
        foreach ($this->workSheetCollection as $sheet) {
1629 2
            $filter = $sheet->getAutoFilter();
1630 2
            if (!empty($filter->getRange())) {
1631 2
                if ($resetToMax) {
1632 1
                    $filter->setRangeToMaxRow();
1633
                }
1634 2
                $filter->showHideRows();
1635
            }
1636
        }
1637
    }
1638
1639
    /**
1640
     * Silliness to mollify Scrutinizer.
1641
     *
1642
     * @codeCoverageIgnore
1643
     */
1644
    public function getSharedComponent(): Style
1645
    {
1646
        return new Style();
1647
    }
1648
1649
    /**
1650
     * @throws Exception
1651
     *
1652
     * @return mixed
1653
     */
1654 1
    public function __serialize()
1655
    {
1656 1
        throw new Exception('Spreadsheet objects cannot be serialized');
1657
    }
1658
1659
    /**
1660
     * @throws Exception
1661
     */
1662 1
    public function jsonSerialize(): mixed
1663
    {
1664 1
        throw new Exception('Spreadsheet objects cannot be json encoded');
1665
    }
1666
}
1667