Failed Conditions
Pull Request — master (#3873)
by Adrien
13:56
created

Spreadsheet::getCellXfByHashCode()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 3

Importance

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