Failed Conditions
Pull Request — master (#4122)
by Owen
28:12 queued 17:19
created

Spreadsheet::sheetCodeNameExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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