Failed Conditions
Pull Request — master (#4240)
by Owen
31:19 queued 20:40
created

Worksheet   F

Complexity

Total Complexity 328

Size/Duplication

Total Lines 1713
Duplicated Lines 0 %

Test Coverage

Coverage 98.11%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 328
eloc 934
dl 0
loc 1713
ccs 935
cts 953
cp 0.9811
rs 1.666
c 5
b 0
f 0

46 Methods

Rating   Name   Duplication   Size   Complexity  
B writeTextCondElements() 0 17 7
A writeIgnoredErrors() 0 9 2
A writeWorksheet() 0 116 2
A writeDimension() 0 6 1
A writeAttributeIf() 0 4 2
B writeExtConditionalFormattingElements() 0 44 8
A writeIgnoredError() 0 11 3
B writeCols() 0 55 9
F writeSheetViews() 0 153 38
C writeTimePeriodCondElements() 0 29 13
C writeSheetPr() 0 48 12
F writeColorScaleElements() 0 93 20
C writeConditionalFormatting() 0 72 16
B writeSheetFormatPr() 0 42 8
A writeElementIf() 0 4 2
A writeAttributeNotNull() 0 4 2
A writeProtectionAttribute() 0 6 3
B writeDataBarElements() 0 37 7
B writeOtherCondElements() 0 28 11
A writeSheetProtection() 0 35 4
A writeCellError() 0 6 2
F writeDataValidations() 0 56 16
A parseRef() 0 21 3
A writeCellNumeric() 0 11 3
A writeTable() 0 17 3
B writePageSetup() 0 32 6
A writeMergeCells() 0 15 3
A writeProtectedRanges() 0 20 3
F writeSheetData() 0 109 27
F writeCell() 0 63 18
A writeHyperlinks() 0 32 5
A writeLegacyDrawingHF() 0 7 2
A writePrintOptions() 0 17 4
A writeAutoFilter() 0 3 1
F writeCellFormula() 0 78 20
B writeBreaks() 0 50 8
A writeCellString() 0 7 2
A writeLegacyDrawing() 0 8 3
A writeCellBoolean() 0 4 2
A writeBackgroundImage() 0 6 2
A writeAlternateContent() 0 8 3
A writePageMargins() 0 11 1
B writeHeaderFooter() 0 28 6
B writeExtLst() 0 25 7
A writeCellInlineStr() 0 14 2
A writeDrawings() 0 22 6

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
6
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
7
use PhpOffice\PhpSpreadsheet\Cell\Cell;
8
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
9
use PhpOffice\PhpSpreadsheet\Cell\DataType;
10
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
11
use PhpOffice\PhpSpreadsheet\RichText\RichText;
12
use PhpOffice\PhpSpreadsheet\Settings;
13
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
14
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
15
use PhpOffice\PhpSpreadsheet\Style\Conditional;
16
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalColorScale;
17
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
18
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension;
19
use PhpOffice\PhpSpreadsheet\Worksheet\RowDimension;
20
use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
21
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as PhpspreadsheetWorksheet;
22
23
class Worksheet extends WriterPart
24
{
25
    private string $numberStoredAsText = '';
26
27
    private string $formula = '';
28
29
    private string $twoDigitTextYear = '';
30
31
    private string $evalError = '';
32
33
    private bool $explicitStyle0;
34
35
    private bool $useDynamicArrays = false;
36
37
    /**
38
     * Write worksheet to XML format.
39
     *
40
     * @param string[] $stringTable
41
     * @param bool $includeCharts Flag indicating if we should write charts
42
     *
43
     * @return string XML Output
44
     */
45 406
    public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, array $stringTable = [], bool $includeCharts = false): string
46
    {
47 406
        $this->useDynamicArrays = $this->getParentWriter()->useDynamicArrays();
48 406
        $this->explicitStyle0 = $this->getParentWriter()->getExplicitStyle0();
49 406
        $worksheet->calculateArrays($this->getParentWriter()->getPreCalculateFormulas());
50 406
        $this->numberStoredAsText = '';
51 406
        $this->formula = '';
52 406
        $this->twoDigitTextYear = '';
53 406
        $this->evalError = '';
54
        // Create XML writer
55 406
        $objWriter = null;
56 406
        if ($this->getParentWriter()->getUseDiskCaching()) {
57
            $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
58
        } else {
59 406
            $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
60
        }
61
62
        // XML header
63 406
        $objWriter->startDocument('1.0', 'UTF-8', 'yes');
64
65
        // Worksheet
66 406
        $objWriter->startElement('worksheet');
67 406
        $objWriter->writeAttribute('xml:space', 'preserve');
68 406
        $objWriter->writeAttribute('xmlns', Namespaces::MAIN);
69 406
        $objWriter->writeAttribute('xmlns:r', Namespaces::SCHEMA_OFFICE_DOCUMENT);
70
71 406
        $objWriter->writeAttribute('xmlns:xdr', Namespaces::SPREADSHEET_DRAWING);
72 406
        $objWriter->writeAttribute('xmlns:x14', Namespaces::DATA_VALIDATIONS1);
73 406
        $objWriter->writeAttribute('xmlns:xm', Namespaces::DATA_VALIDATIONS2);
74 406
        $objWriter->writeAttribute('xmlns:mc', Namespaces::COMPATIBILITY);
75 406
        $objWriter->writeAttribute('mc:Ignorable', 'x14ac');
76 406
        $objWriter->writeAttribute('xmlns:x14ac', Namespaces::SPREADSHEETML_AC);
77
78
        // sheetPr
79 406
        $this->writeSheetPr($objWriter, $worksheet);
80
81
        // Dimension
82 406
        $this->writeDimension($objWriter, $worksheet);
83
84
        // sheetViews
85 406
        $this->writeSheetViews($objWriter, $worksheet);
86
87
        // sheetFormatPr
88 406
        $this->writeSheetFormatPr($objWriter, $worksheet);
89
90
        // cols
91 406
        $this->writeCols($objWriter, $worksheet);
92
93
        // sheetData
94 406
        $this->writeSheetData($objWriter, $worksheet, $stringTable);
95
96
        // sheetProtection
97 405
        $this->writeSheetProtection($objWriter, $worksheet);
98
99
        // protectedRanges
100 405
        $this->writeProtectedRanges($objWriter, $worksheet);
101
102
        // autoFilter
103 405
        $this->writeAutoFilter($objWriter, $worksheet);
104
105
        // mergeCells
106 405
        $this->writeMergeCells($objWriter, $worksheet);
107
108
        // conditionalFormatting
109 405
        $this->writeConditionalFormatting($objWriter, $worksheet);
110
111
        // dataValidations
112 405
        $this->writeDataValidations($objWriter, $worksheet);
113
114
        // hyperlinks
115 405
        $this->writeHyperlinks($objWriter, $worksheet);
116
117
        // Print options
118 405
        $this->writePrintOptions($objWriter, $worksheet);
119
120
        // Page margins
121 405
        $this->writePageMargins($objWriter, $worksheet);
122
123
        // Page setup
124 405
        $this->writePageSetup($objWriter, $worksheet);
125
126
        // Header / footer
127 405
        $this->writeHeaderFooter($objWriter, $worksheet);
128
129
        // Breaks
130 405
        $this->writeBreaks($objWriter, $worksheet);
131
132
        // IgnoredErrors
133 405
        $this->writeIgnoredErrors($objWriter);
134
135
        // Drawings and/or Charts
136 405
        $this->writeDrawings($objWriter, $worksheet, $includeCharts);
137
138
        // LegacyDrawing
139 405
        $this->writeLegacyDrawing($objWriter, $worksheet);
140
141
        // LegacyDrawingHF
142 405
        $this->writeLegacyDrawingHF($objWriter, $worksheet);
143
144
        // AlternateContent
145 405
        $this->writeAlternateContent($objWriter, $worksheet);
146
147
        // BackgroundImage must come after ignored, before table
148 405
        $this->writeBackgroundImage($objWriter, $worksheet);
149
150
        // Table
151 405
        $this->writeTable($objWriter, $worksheet);
152
153
        // ConditionalFormattingRuleExtensionList
154
        // (Must be inserted last. Not insert last, an Excel parse error will occur)
155 405
        $this->writeExtLst($objWriter, $worksheet);
156
157 405
        $objWriter->endElement();
158
159
        // Return
160 405
        return $objWriter->getData();
161
    }
162
163 405
    private function writeIgnoredError(XMLWriter $objWriter, bool &$started, string $attr, string $cells): void
164
    {
165 405
        if ($cells !== '') {
166 4
            if (!$started) {
167 4
                $objWriter->startElement('ignoredErrors');
168 4
                $started = true;
169
            }
170 4
            $objWriter->startElement('ignoredError');
171 4
            $objWriter->writeAttribute('sqref', substr($cells, 1));
172 4
            $objWriter->writeAttribute($attr, '1');
173 4
            $objWriter->endElement();
174
        }
175
    }
176
177 405
    private function writeIgnoredErrors(XMLWriter $objWriter): void
178
    {
179 405
        $started = false;
180 405
        $this->writeIgnoredError($objWriter, $started, 'numberStoredAsText', $this->numberStoredAsText);
181 405
        $this->writeIgnoredError($objWriter, $started, 'formula', $this->formula);
182 405
        $this->writeIgnoredError($objWriter, $started, 'twoDigitTextYear', $this->twoDigitTextYear);
183 405
        $this->writeIgnoredError($objWriter, $started, 'evalError', $this->evalError);
184 405
        if ($started) {
185 4
            $objWriter->endElement();
186
        }
187
    }
188
189
    /**
190
     * Write SheetPr.
191
     */
192 406
    private function writeSheetPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
193
    {
194
        // sheetPr
195 406
        $objWriter->startElement('sheetPr');
196 406
        if ($worksheet->getParentOrThrow()->hasMacros()) {
197
            //if the workbook have macros, we need to have codeName for the sheet
198 2
            if (!$worksheet->hasCodeName()) {
199
                $worksheet->setCodeName($worksheet->getTitle());
200
            }
201 2
            self::writeAttributeNotNull($objWriter, 'codeName', $worksheet->getCodeName());
202
        }
203 406
        $autoFilterRange = $worksheet->getAutoFilter()->getRange();
204 406
        if (!empty($autoFilterRange)) {
205 10
            $objWriter->writeAttribute('filterMode', '1');
206 10
            if (!$worksheet->getAutoFilter()->getEvaluated()) {
207 6
                $worksheet->getAutoFilter()->showHideRows();
208
            }
209
        }
210 406
        $tables = $worksheet->getTableCollection();
211 406
        if (count($tables)) {
212 8
            foreach ($tables as $table) {
213 8
                if (!$table->getAutoFilter()->getEvaluated()) {
214 8
                    $table->getAutoFilter()->showHideRows();
215
                }
216
            }
217
        }
218
219
        // tabColor
220 406
        if ($worksheet->isTabColorSet()) {
221 8
            $objWriter->startElement('tabColor');
222 8
            $objWriter->writeAttribute('rgb', $worksheet->getTabColor()->getARGB() ?? '');
223 8
            $objWriter->endElement();
224
        }
225
226
        // outlinePr
227 406
        $objWriter->startElement('outlinePr');
228 406
        $objWriter->writeAttribute('summaryBelow', ($worksheet->getShowSummaryBelow() ? '1' : '0'));
229 406
        $objWriter->writeAttribute('summaryRight', ($worksheet->getShowSummaryRight() ? '1' : '0'));
230 406
        $objWriter->endElement();
231
232
        // pageSetUpPr
233 406
        if ($worksheet->getPageSetup()->getFitToPage()) {
234 5
            $objWriter->startElement('pageSetUpPr');
235 5
            $objWriter->writeAttribute('fitToPage', '1');
236 5
            $objWriter->endElement();
237
        }
238
239 406
        $objWriter->endElement();
240
    }
241
242
    /**
243
     * Write Dimension.
244
     */
245 406
    private function writeDimension(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
246
    {
247
        // dimension
248 406
        $objWriter->startElement('dimension');
249 406
        $objWriter->writeAttribute('ref', $worksheet->calculateWorksheetDimension());
250 406
        $objWriter->endElement();
251
    }
252
253
    /**
254
     * Write SheetViews.
255
     */
256 406
    private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
257
    {
258
        // sheetViews
259 406
        $objWriter->startElement('sheetViews');
260
261
        // Sheet selected?
262 406
        $sheetSelected = false;
263 406
        if ($this->getParentWriter()->getSpreadsheet()->getIndex($worksheet) == $this->getParentWriter()->getSpreadsheet()->getActiveSheetIndex()) {
264 401
            $sheetSelected = true;
265
        }
266
267
        // sheetView
268 406
        $objWriter->startElement('sheetView');
269 406
        $objWriter->writeAttribute('tabSelected', $sheetSelected ? '1' : '0');
270 406
        $objWriter->writeAttribute('workbookViewId', '0');
271
272
        // Zoom scales
273 406
        $zoomScale = $worksheet->getSheetView()->getZoomScale();
274 406
        if ($zoomScale !== 100 && $zoomScale !== null) {
275 8
            $objWriter->writeAttribute('zoomScale', (string) $zoomScale);
276
        }
277 406
        $zoomScale = $worksheet->getSheetView()->getZoomScaleNormal();
278 406
        if ($zoomScale !== 100 && $zoomScale !== null) {
279 5
            $objWriter->writeAttribute('zoomScaleNormal', (string) $zoomScale);
280
        }
281 406
        $zoomScale = $worksheet->getSheetView()->getZoomScalePageLayoutView();
282 406
        if ($zoomScale !== 100) {
283 4
            $objWriter->writeAttribute('zoomScalePageLayoutView', (string) $zoomScale);
284
        }
285 406
        $zoomScale = $worksheet->getSheetView()->getZoomScaleSheetLayoutView();
286 406
        if ($zoomScale !== 100) {
287 3
            $objWriter->writeAttribute('zoomScaleSheetLayoutView', (string) $zoomScale);
288
        }
289
290
        // Show zeros (Excel also writes this attribute only if set to false)
291 406
        if ($worksheet->getSheetView()->getShowZeros() === false) {
292
            $objWriter->writeAttribute('showZeros', '0');
293
        }
294
295
        // View Layout Type
296 406
        if ($worksheet->getSheetView()->getView() !== SheetView::SHEETVIEW_NORMAL) {
297 5
            $objWriter->writeAttribute('view', $worksheet->getSheetView()->getView());
298
        }
299
300
        // Gridlines
301 406
        if ($worksheet->getShowGridlines()) {
302 403
            $objWriter->writeAttribute('showGridLines', 'true');
303
        } else {
304 7
            $objWriter->writeAttribute('showGridLines', 'false');
305
        }
306
307
        // Row and column headers
308 406
        if ($worksheet->getShowRowColHeaders()) {
309 406
            $objWriter->writeAttribute('showRowColHeaders', '1');
310
        } else {
311
            $objWriter->writeAttribute('showRowColHeaders', '0');
312
        }
313
314
        // Right-to-left
315 406
        if ($worksheet->getRightToLeft()) {
316 1
            $objWriter->writeAttribute('rightToLeft', 'true');
317
        }
318
319 406
        $topLeftCell = $worksheet->getTopLeftCell();
320 406
        if (!empty($topLeftCell) && $worksheet->getPaneState() !== PhpspreadsheetWorksheet::PANE_FROZEN && $worksheet->getPaneState() !== PhpspreadsheetWorksheet::PANE_FROZENSPLIT) {
321 12
            $objWriter->writeAttribute('topLeftCell', $topLeftCell);
322
        }
323 406
        $activeCell = $worksheet->getActiveCell();
324 406
        $sqref = $worksheet->getSelectedCells();
325
326
        // Pane
327 406
        if ($worksheet->usesPanes()) {
328 10
            $objWriter->startElement('pane');
329 10
            $xSplit = $worksheet->getXSplit();
330 10
            $ySplit = $worksheet->getYSplit();
331 10
            $pane = $worksheet->getActivePane();
332 10
            $paneTopLeftCell = $worksheet->getPaneTopLeftCell();
333 10
            $paneState = $worksheet->getPaneState();
334 10
            $normalFreeze = '';
335 10
            if ($paneState === PhpspreadsheetWorksheet::PANE_FROZEN) {
336 10
                if ($ySplit > 0) {
337 10
                    $normalFreeze = ($xSplit <= 0) ? 'bottomLeft' : 'bottomRight';
338
                } else {
339 1
                    $normalFreeze = 'topRight';
340
                }
341
            }
342 10
            if ($xSplit > 0) {
343 4
                $objWriter->writeAttribute('xSplit', "$xSplit");
344
            }
345 10
            if ($ySplit > 0) {
346 10
                $objWriter->writeAttribute('ySplit', "$ySplit");
347
            }
348 10
            if ($normalFreeze !== '') {
349 10
                $objWriter->writeAttribute('activePane', $normalFreeze);
350 1
            } elseif ($pane !== '') {
351 1
                $objWriter->writeAttribute('activePane', $pane);
352
            }
353 10
            if ($paneState !== '') {
354 10
                $objWriter->writeAttribute('state', $paneState);
355
            }
356 10
            if ($paneTopLeftCell !== '') {
357 10
                $objWriter->writeAttribute('topLeftCell', $paneTopLeftCell);
358
            }
359 10
            $objWriter->endElement(); // pane
360
361 10
            if ($normalFreeze !== '') {
362 10
                $objWriter->startElement('selection');
363 10
                $objWriter->writeAttribute('pane', $normalFreeze);
364 10
                if ($activeCell !== '') {
365 10
                    $objWriter->writeAttribute('activeCell', $activeCell);
366
                }
367 10
                if ($sqref !== '') {
368 10
                    $objWriter->writeAttribute('sqref', $sqref);
369
                }
370 10
                $objWriter->endElement(); // selection
371 10
                $sqref = $activeCell = '';
372
            } else {
373 1
                foreach ($worksheet->getPanes() as $panex) {
374 1
                    if ($panex !== null) {
375 1
                        $sqref = $activeCell = '';
376 1
                        $objWriter->startElement('selection');
377 1
                        $objWriter->writeAttribute('pane', $panex->getPosition());
378 1
                        $activeCellPane = $panex->getActiveCell();
379 1
                        if ($activeCellPane !== '') {
380 1
                            $objWriter->writeAttribute('activeCell', $activeCellPane);
381
                        }
382 1
                        $sqrefPane = $panex->getSqref();
383 1
                        if ($sqrefPane !== '') {
384 1
                            $objWriter->writeAttribute('sqref', $sqrefPane);
385
                        }
386 1
                        $objWriter->endElement(); // selection
387
                    }
388
                }
389
            }
390
        }
391
392
        // Selection
393
        // Only need to write selection element if we have a split pane
394
        // We cheat a little by over-riding the active cell selection, setting it to the split cell
395 406
        if (!empty($sqref) || !empty($activeCell)) {
396 399
            $objWriter->startElement('selection');
397 399
            if (!empty($activeCell)) {
398 399
                $objWriter->writeAttribute('activeCell', $activeCell);
399
            }
400 399
            if (!empty($sqref)) {
401 399
                $objWriter->writeAttribute('sqref', $sqref);
402
            }
403 399
            $objWriter->endElement(); // selection
404
        }
405
406 406
        $objWriter->endElement();
407
408 406
        $objWriter->endElement();
409
    }
410
411
    /**
412
     * Write SheetFormatPr.
413
     */
414 406
    private function writeSheetFormatPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
415
    {
416
        // sheetFormatPr
417 406
        $objWriter->startElement('sheetFormatPr');
418
419
        // Default row height
420 406
        if ($worksheet->getDefaultRowDimension()->getRowHeight() >= 0) {
421 16
            $objWriter->writeAttribute('customHeight', 'true');
422 16
            $objWriter->writeAttribute('defaultRowHeight', StringHelper::formatNumber($worksheet->getDefaultRowDimension()->getRowHeight()));
423
        } else {
424 391
            $objWriter->writeAttribute('defaultRowHeight', '14.4');
425
        }
426
427
        // Set Zero Height row
428 406
        if ($worksheet->getDefaultRowDimension()->getZeroHeight()) {
429
            $objWriter->writeAttribute('zeroHeight', '1');
430
        }
431
432
        // Default column width
433 406
        if ($worksheet->getDefaultColumnDimension()->getWidth() >= 0) {
434 26
            $objWriter->writeAttribute('defaultColWidth', StringHelper::formatNumber($worksheet->getDefaultColumnDimension()->getWidth()));
435
        }
436
437
        // Outline level - row
438 406
        $outlineLevelRow = 0;
439 406
        foreach ($worksheet->getRowDimensions() as $dimension) {
440 56
            if ($dimension->getOutlineLevel() > $outlineLevelRow) {
441
                $outlineLevelRow = $dimension->getOutlineLevel();
442
            }
443
        }
444 406
        $objWriter->writeAttribute('outlineLevelRow', (string) (int) $outlineLevelRow);
445
446
        // Outline level - column
447 406
        $outlineLevelCol = 0;
448 406
        foreach ($worksheet->getColumnDimensions() as $dimension) {
449 85
            if ($dimension->getOutlineLevel() > $outlineLevelCol) {
450 1
                $outlineLevelCol = $dimension->getOutlineLevel();
451
            }
452
        }
453 406
        $objWriter->writeAttribute('outlineLevelCol', (string) (int) $outlineLevelCol);
454
455 406
        $objWriter->endElement();
456
    }
457
458
    /**
459
     * Write Cols.
460
     */
461 406
    private function writeCols(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
462
    {
463
        // cols
464 406
        if (count($worksheet->getColumnDimensions()) > 0) {
465 85
            $objWriter->startElement('cols');
466
467 85
            $worksheet->calculateColumnWidths();
468
469
            // Loop through column dimensions
470 85
            foreach ($worksheet->getColumnDimensions() as $colDimension) {
471
                // col
472 85
                $objWriter->startElement('col');
473 85
                $objWriter->writeAttribute('min', (string) Coordinate::columnIndexFromString($colDimension->getColumnIndex()));
474 85
                $objWriter->writeAttribute('max', (string) Coordinate::columnIndexFromString($colDimension->getColumnIndex()));
475
476 85
                if ($colDimension->getWidth() < 0) {
477
                    // No width set, apply default of 10
478 3
                    $objWriter->writeAttribute('width', '9.10');
479
                } else {
480
                    // Width set
481 84
                    $objWriter->writeAttribute('width', StringHelper::formatNumber($colDimension->getWidth()));
482
                }
483
484
                // Column visibility
485 85
                if ($colDimension->getVisible() === false) {
486 7
                    $objWriter->writeAttribute('hidden', 'true');
487
                }
488
489
                // Auto size?
490 85
                if ($colDimension->getAutoSize()) {
491 30
                    $objWriter->writeAttribute('bestFit', 'true');
492
                }
493
494
                // Custom width?
495 85
                if ($colDimension->getWidth() != $worksheet->getDefaultColumnDimension()->getWidth()) {
496 82
                    $objWriter->writeAttribute('customWidth', 'true');
497
                }
498
499
                // Collapsed
500 85
                if ($colDimension->getCollapsed() === true) {
501 1
                    $objWriter->writeAttribute('collapsed', 'true');
502
                }
503
504
                // Outline level
505 85
                if ($colDimension->getOutlineLevel() > 0) {
506 1
                    $objWriter->writeAttribute('outlineLevel', (string) $colDimension->getOutlineLevel());
507
                }
508
509
                // Style
510 85
                $objWriter->writeAttribute('style', (string) $colDimension->getXfIndex());
511
512 85
                $objWriter->endElement();
513
            }
514
515 85
            $objWriter->endElement();
516
        }
517
    }
518
519
    /**
520
     * Write SheetProtection.
521
     */
522 405
    private function writeSheetProtection(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
523
    {
524 405
        $protection = $worksheet->getProtection();
525 405
        if (!$protection->isProtectionEnabled()) {
526 382
            return;
527
        }
528
        // sheetProtection
529 33
        $objWriter->startElement('sheetProtection');
530
531 33
        if ($protection->getAlgorithm()) {
532 2
            $objWriter->writeAttribute('algorithmName', $protection->getAlgorithm());
533 2
            $objWriter->writeAttribute('hashValue', $protection->getPassword());
534 2
            $objWriter->writeAttribute('saltValue', $protection->getSalt());
535 2
            $objWriter->writeAttribute('spinCount', (string) $protection->getSpinCount());
536 32
        } elseif ($protection->getPassword() !== '') {
537 5
            $objWriter->writeAttribute('password', $protection->getPassword());
538
        }
539
540 33
        self::writeProtectionAttribute($objWriter, 'sheet', $protection->getSheet());
541 33
        self::writeProtectionAttribute($objWriter, 'objects', $protection->getObjects());
542 33
        self::writeProtectionAttribute($objWriter, 'scenarios', $protection->getScenarios());
543 33
        self::writeProtectionAttribute($objWriter, 'formatCells', $protection->getFormatCells());
544 33
        self::writeProtectionAttribute($objWriter, 'formatColumns', $protection->getFormatColumns());
545 33
        self::writeProtectionAttribute($objWriter, 'formatRows', $protection->getFormatRows());
546 33
        self::writeProtectionAttribute($objWriter, 'insertColumns', $protection->getInsertColumns());
547 33
        self::writeProtectionAttribute($objWriter, 'insertRows', $protection->getInsertRows());
548 33
        self::writeProtectionAttribute($objWriter, 'insertHyperlinks', $protection->getInsertHyperlinks());
549 33
        self::writeProtectionAttribute($objWriter, 'deleteColumns', $protection->getDeleteColumns());
550 33
        self::writeProtectionAttribute($objWriter, 'deleteRows', $protection->getDeleteRows());
551 33
        self::writeProtectionAttribute($objWriter, 'sort', $protection->getSort());
552 33
        self::writeProtectionAttribute($objWriter, 'autoFilter', $protection->getAutoFilter());
553 33
        self::writeProtectionAttribute($objWriter, 'pivotTables', $protection->getPivotTables());
554 33
        self::writeProtectionAttribute($objWriter, 'selectLockedCells', $protection->getSelectLockedCells());
555 33
        self::writeProtectionAttribute($objWriter, 'selectUnlockedCells', $protection->getSelectUnlockedCells());
556 33
        $objWriter->endElement();
557
    }
558
559 33
    private static function writeProtectionAttribute(XMLWriter $objWriter, string $name, ?bool $value): void
560
    {
561 33
        if ($value === true) {
562 22
            $objWriter->writeAttribute($name, '1');
563 33
        } elseif ($value === false) {
564 20
            $objWriter->writeAttribute($name, '0');
565
        }
566
    }
567
568 72
    private static function writeAttributeIf(XMLWriter $objWriter, ?bool $condition, string $attr, string $val): void
569
    {
570 72
        if ($condition) {
571 71
            $objWriter->writeAttribute($attr, $val);
572
        }
573
    }
574
575 2
    private static function writeAttributeNotNull(XMLWriter $objWriter, string $attr, ?string $val): void
576
    {
577 2
        if ($val !== null) {
578 2
            $objWriter->writeAttribute($attr, $val);
579
        }
580
    }
581
582 254
    private static function writeElementIf(XMLWriter $objWriter, bool $condition, string $attr, string $val): void
583
    {
584 254
        if ($condition) {
585 241
            $objWriter->writeElement($attr, $val);
586
        }
587
    }
588
589 41
    private static function writeOtherCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void
590
    {
591 41
        $conditions = $conditional->getConditions();
592
        if (
593 41
            $conditional->getConditionType() == Conditional::CONDITION_CELLIS
594 41
            || $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION
595 41
            || !empty($conditions)
596
        ) {
597 30
            foreach ($conditions as $formula) {
598
                // Formula
599 30
                if (is_bool($formula)) {
600 1
                    $formula = $formula ? 'TRUE' : 'FALSE';
601
                }
602 30
                $objWriter->writeElement('formula', FunctionPrefix::addFunctionPrefix("$formula"));
603
            }
604
        } else {
605 11
            if ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) {
606
                // formula copied from ms xlsx xml source file
607 2
                $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))=0');
608 9
            } elseif ($conditional->getConditionType() == Conditional::CONDITION_NOTCONTAINSBLANKS) {
609
                // formula copied from ms xlsx xml source file
610 1
                $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))>0');
611 8
            } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSERRORS) {
612
                // formula copied from ms xlsx xml source file
613 1
                $objWriter->writeElement('formula', 'ISERROR(' . $cellCoordinate . ')');
614 7
            } elseif ($conditional->getConditionType() == Conditional::CONDITION_NOTCONTAINSERRORS) {
615
                // formula copied from ms xlsx xml source file
616 1
                $objWriter->writeElement('formula', 'NOT(ISERROR(' . $cellCoordinate . '))');
617
            }
618
        }
619
    }
620
621 12
    private static function writeTimePeriodCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void
622
    {
623 12
        $txt = $conditional->getText();
624 12
        if (!empty($txt)) {
625 12
            $objWriter->writeAttribute('timePeriod', $txt);
626 12
            if (empty($conditional->getConditions())) {
627 10
                if ($conditional->getOperatorType() == Conditional::TIMEPERIOD_TODAY) {
628 1
                    $objWriter->writeElement('formula', 'FLOOR(' . $cellCoordinate . ')=TODAY()');
629 9
                } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_TOMORROW) {
630 1
                    $objWriter->writeElement('formula', 'FLOOR(' . $cellCoordinate . ')=TODAY()+1');
631 8
                } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_YESTERDAY) {
632 1
                    $objWriter->writeElement('formula', 'FLOOR(' . $cellCoordinate . ')=TODAY()-1');
633 7
                } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_LAST_7_DAYS) {
634 1
                    $objWriter->writeElement('formula', 'AND(TODAY()-FLOOR(' . $cellCoordinate . ',1)<=6,FLOOR(' . $cellCoordinate . ',1)<=TODAY())');
635 6
                } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_LAST_WEEK) {
636 1
                    $objWriter->writeElement('formula', 'AND(TODAY()-ROUNDDOWN(' . $cellCoordinate . ',0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(' . $cellCoordinate . ',0)<(WEEKDAY(TODAY())+7))');
637 5
                } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_THIS_WEEK) {
638 1
                    $objWriter->writeElement('formula', 'AND(TODAY()-ROUNDDOWN(' . $cellCoordinate . ',0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(' . $cellCoordinate . ',0)-TODAY()<=7-WEEKDAY(TODAY()))');
639 4
                } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_NEXT_WEEK) {
640 1
                    $objWriter->writeElement('formula', 'AND(ROUNDDOWN(' . $cellCoordinate . ',0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(' . $cellCoordinate . ',0)-TODAY()<(15-WEEKDAY(TODAY())))');
641 3
                } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_LAST_MONTH) {
642 1
                    $objWriter->writeElement('formula', 'AND(MONTH(' . $cellCoordinate . ')=MONTH(EDATE(TODAY(),0-1)),YEAR(' . $cellCoordinate . ')=YEAR(EDATE(TODAY(),0-1)))');
643 2
                } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_THIS_MONTH) {
644 1
                    $objWriter->writeElement('formula', 'AND(MONTH(' . $cellCoordinate . ')=MONTH(TODAY()),YEAR(' . $cellCoordinate . ')=YEAR(TODAY()))');
645 1
                } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_NEXT_MONTH) {
646 1
                    $objWriter->writeElement('formula', 'AND(MONTH(' . $cellCoordinate . ')=MONTH(EDATE(TODAY(),0+1)),YEAR(' . $cellCoordinate . ')=YEAR(EDATE(TODAY(),0+1)))');
647
                }
648
            } else {
649 2
                $objWriter->writeElement('formula', (string) ($conditional->getConditions()[0]));
650
            }
651
        }
652
    }
653
654 9
    private static function writeTextCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void
655
    {
656 9
        $txt = $conditional->getText();
657 9
        if (!empty($txt)) {
658 8
            $objWriter->writeAttribute('text', $txt);
659 8
            if (empty($conditional->getConditions())) {
660 5
                if ($conditional->getOperatorType() == Conditional::OPERATOR_CONTAINSTEXT) {
661 2
                    $objWriter->writeElement('formula', 'NOT(ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . ')))');
662 4
                } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_BEGINSWITH) {
663 2
                    $objWriter->writeElement('formula', 'LEFT(' . $cellCoordinate . ',LEN("' . $txt . '"))="' . $txt . '"');
664 3
                } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_ENDSWITH) {
665 2
                    $objWriter->writeElement('formula', 'RIGHT(' . $cellCoordinate . ',LEN("' . $txt . '"))="' . $txt . '"');
666 2
                } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_NOTCONTAINS) {
667 2
                    $objWriter->writeElement('formula', 'ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . '))');
668
                }
669
            } else {
670 3
                $objWriter->writeElement('formula', (string) ($conditional->getConditions()[0]));
671
            }
672
        }
673
    }
674
675 1
    private static function writeExtConditionalFormattingElements(XMLWriter $objWriter, ConditionalFormattingRuleExtension $ruleExtension): void
676
    {
677 1
        $prefix = 'x14';
678 1
        $objWriter->startElementNs($prefix, 'conditionalFormatting', null);
679
680 1
        $objWriter->startElementNs($prefix, 'cfRule', null);
681 1
        $objWriter->writeAttribute('type', $ruleExtension->getCfRule());
682 1
        $objWriter->writeAttribute('id', $ruleExtension->getId());
683 1
        $objWriter->startElementNs($prefix, 'dataBar', null);
684 1
        $dataBar = $ruleExtension->getDataBarExt();
685 1
        foreach ($dataBar->getXmlAttributes() as $attrKey => $val) {
686 1
            $objWriter->writeAttribute($attrKey, $val);
687
        }
688 1
        $minCfvo = $dataBar->getMinimumConditionalFormatValueObject();
689 1
        if ($minCfvo !== null) {
690 1
            $objWriter->startElementNs($prefix, 'cfvo', null);
691 1
            $objWriter->writeAttribute('type', $minCfvo->getType());
692 1
            if ($minCfvo->getCellFormula()) {
693 1
                $objWriter->writeElement('xm:f', $minCfvo->getCellFormula());
694
            }
695 1
            $objWriter->endElement(); //end cfvo
696
        }
697
698 1
        $maxCfvo = $dataBar->getMaximumConditionalFormatValueObject();
699 1
        if ($maxCfvo !== null) {
700 1
            $objWriter->startElementNs($prefix, 'cfvo', null);
701 1
            $objWriter->writeAttribute('type', $maxCfvo->getType());
702 1
            if ($maxCfvo->getCellFormula()) {
703 1
                $objWriter->writeElement('xm:f', $maxCfvo->getCellFormula());
704
            }
705 1
            $objWriter->endElement(); //end cfvo
706
        }
707
708 1
        foreach ($dataBar->getXmlElements() as $elmKey => $elmAttr) {
709 1
            $objWriter->startElementNs($prefix, $elmKey, null);
710 1
            foreach ($elmAttr as $attrKey => $attrVal) {
711 1
                $objWriter->writeAttribute($attrKey, $attrVal);
712
            }
713 1
            $objWriter->endElement(); //end elmKey
714
        }
715 1
        $objWriter->endElement(); //end dataBar
716 1
        $objWriter->endElement(); //end cfRule
717 1
        $objWriter->writeElement('xm:sqref', $ruleExtension->getSqref());
718 1
        $objWriter->endElement(); //end conditionalFormatting
719
    }
720
721 63
    private static function writeDataBarElements(XMLWriter $objWriter, ?ConditionalDataBar $dataBar): void
722
    {
723 63
        if ($dataBar) {
724 2
            $objWriter->startElement('dataBar');
725 2
            self::writeAttributeIf($objWriter, null !== $dataBar->getShowValue(), 'showValue', $dataBar->getShowValue() ? '1' : '0');
726
727 2
            $minCfvo = $dataBar->getMinimumConditionalFormatValueObject();
728 2
            if ($minCfvo) {
729 2
                $objWriter->startElement('cfvo');
730 2
                $objWriter->writeAttribute('type', $minCfvo->getType());
731 2
                self::writeAttributeIf($objWriter, $minCfvo->getValue() !== null, 'val', (string) $minCfvo->getValue());
732 2
                $objWriter->endElement();
733
            }
734 2
            $maxCfvo = $dataBar->getMaximumConditionalFormatValueObject();
735 2
            if ($maxCfvo) {
736 2
                $objWriter->startElement('cfvo');
737 2
                $objWriter->writeAttribute('type', $maxCfvo->getType());
738 2
                self::writeAttributeIf($objWriter, $maxCfvo->getValue() !== null, 'val', (string) $maxCfvo->getValue());
739 2
                $objWriter->endElement();
740
            }
741 2
            if ($dataBar->getColor()) {
742 2
                $objWriter->startElement('color');
743 2
                $objWriter->writeAttribute('rgb', $dataBar->getColor());
744 2
                $objWriter->endElement();
745
            }
746 2
            $objWriter->endElement(); // end dataBar
747
748 2
            if ($dataBar->getConditionalFormattingRuleExt()) {
749 1
                $objWriter->startElement('extLst');
750 1
                $extension = $dataBar->getConditionalFormattingRuleExt();
751 1
                $objWriter->startElement('ext');
752 1
                $objWriter->writeAttribute('uri', '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}');
753 1
                $objWriter->startElementNs('x14', 'id', null);
754 1
                $objWriter->text($extension->getId());
755 1
                $objWriter->endElement();
756 1
                $objWriter->endElement();
757 1
                $objWriter->endElement(); //end extLst
758
            }
759
        }
760
    }
761
762 3
    private static function writeColorScaleElements(XMLWriter $objWriter, ?ConditionalColorScale $colorScale): void
763
    {
764 3
        if ($colorScale) {
765 3
            $objWriter->startElement('colorScale');
766
767 3
            $minCfvo = $colorScale->getMinimumConditionalFormatValueObject();
768 3
            $minArgb = $colorScale->getMinimumColor()?->getARGB();
769 3
            $useMin = $minCfvo !== null || $minArgb !== null;
770 3
            if ($useMin) {
771 3
                $objWriter->startElement('cfvo');
772 3
                $type = 'min';
773 3
                $value = null;
774 3
                if ($minCfvo !== null) {
775 3
                    $typex = $minCfvo->getType();
776 3
                    if ($typex === 'formula') {
777 1
                        $value = $minCfvo->getCellFormula();
778 1
                        if ($value !== null) {
779 1
                            $type = $typex;
780
                        }
781
                    } else {
782 2
                        $type = $typex;
783 2
                        $defaults = ['number' => '0', 'percent' => '0', 'percentile' => '10'];
784 2
                        $value = $minCfvo->getValue() ?? $defaults[$type] ?? null;
785
                    }
786
                }
787 3
                $objWriter->writeAttribute('type', $type);
788 3
                self::writeAttributeIf($objWriter, $value !== null, 'val', (string) $value);
789 3
                $objWriter->endElement();
790
            }
791 3
            $midCfvo = $colorScale->getMidpointConditionalFormatValueObject();
792 3
            $midArgb = $colorScale->getMidpointColor()?->getARGB();
793 3
            $useMid = $midCfvo !== null || $midArgb !== null;
794 3
            if ($useMid) {
795 2
                $objWriter->startElement('cfvo');
796 2
                $type = 'percentile';
797 2
                $value = '50';
798 2
                if ($midCfvo !== null) {
799 2
                    $type = $midCfvo->getType();
800 2
                    if ($type === 'formula') {
801
                        $value = $midCfvo->getCellFormula();
802
                        if ($value === null) {
803
                            $type = 'percentile';
804
                            $value = '50';
805
                        }
806
                    } else {
807 2
                        $defaults = ['number' => '0', 'percent' => '50', 'percentile' => '50'];
808 2
                        $value = $midCfvo->getValue() ?? $defaults[$type] ?? null;
809
                    }
810
                }
811 2
                $objWriter->writeAttribute('type', $type);
812 2
                self::writeAttributeIf($objWriter, $value !== null, 'val', (string) $value);
813 2
                $objWriter->endElement();
814
            }
815 3
            $maxCfvo = $colorScale->getMaximumConditionalFormatValueObject();
816 3
            $maxArgb = $colorScale->getMaximumColor()?->getARGB();
817 3
            $useMax = $maxCfvo !== null || $maxArgb !== null;
818 3
            if ($useMax) {
819 3
                $objWriter->startElement('cfvo');
820 3
                $type = 'max';
821 3
                $value = null;
822 3
                if ($maxCfvo !== null) {
823 3
                    $typex = $maxCfvo->getType();
824 3
                    if ($typex === 'formula') {
825
                        $value = $maxCfvo->getCellFormula();
826
                        if ($value !== null) {
827
                            $type = $typex;
828
                        }
829
                    } else {
830 3
                        $type = $typex;
831 3
                        $defaults = ['number' => '0', 'percent' => '100', 'percentile' => '90'];
832 3
                        $value = $maxCfvo->getValue() ?? $defaults[$type] ?? null;
833
                    }
834
                }
835 3
                $objWriter->writeAttribute('type', $type);
836 3
                self::writeAttributeIf($objWriter, $value !== null, 'val', (string) $value);
837 3
                $objWriter->endElement();
838
            }
839 3
            if ($useMin) {
840 3
                $objWriter->startElement('color');
841 3
                self::writeAttributeIf($objWriter, $minArgb !== null, 'rgb', "$minArgb");
842 3
                $objWriter->endElement();
843
            }
844 3
            if ($useMid) {
845 2
                $objWriter->startElement('color');
846 2
                self::writeAttributeIf($objWriter, $midArgb !== null, 'rgb', "$midArgb");
847 2
                $objWriter->endElement();
848
            }
849 3
            if ($useMax) {
850 3
                $objWriter->startElement('color');
851 3
                self::writeAttributeIf($objWriter, $maxArgb !== null, 'rgb', "$maxArgb");
852 3
                $objWriter->endElement();
853
            }
854 3
            $objWriter->endElement(); // end colorScale
855
        }
856
    }
857
858
    /**
859
     * Write ConditionalFormatting.
860
     */
861 405
    private function writeConditionalFormatting(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
862
    {
863
        // Conditional id
864 405
        $id = 1;
865
866
        // Loop through styles in the current worksheet
867 405
        foreach ($worksheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) {
868 63
            $objWriter->startElement('conditionalFormatting');
869
            // N.B. In Excel UI, intersection is space and union is comma.
870
            // But in Xml, intersection is comma and union is space.
871
            // Anyhow, I don't think Excel handles intersection correctly when reading.
872 63
            $outCoordinate = Coordinate::resolveUnionAndIntersection(str_replace('$', '', $cellCoordinate), ' ');
873 63
            $objWriter->writeAttribute('sqref', $outCoordinate);
874
875 63
            foreach ($conditionalStyles as $conditional) {
876
                // WHY was this again?
877
                // if ($this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) == '') {
878
                //    continue;
879
                // }
880
                // cfRule
881 63
                $objWriter->startElement('cfRule');
882 63
                $objWriter->writeAttribute('type', $conditional->getConditionType());
883 63
                self::writeAttributeIf(
884 63
                    $objWriter,
885 63
                    ($conditional->getConditionType() !== Conditional::CONDITION_COLORSCALE
886 63
                        && $conditional->getConditionType() !== Conditional::CONDITION_DATABAR
887 63
                        && $conditional->getNoFormatSet() === false),
888 63
                    'dxfId',
889 63
                    (string) $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode())
890 63
                );
891 63
                $objWriter->writeAttribute('priority', (string) $id++);
892
893 63
                self::writeAttributeif(
894 63
                    $objWriter,
895 63
                    (
896 63
                        $conditional->getConditionType() === Conditional::CONDITION_CELLIS
897 63
                        || $conditional->getConditionType() === Conditional::CONDITION_CONTAINSTEXT
898 63
                        || $conditional->getConditionType() === Conditional::CONDITION_NOTCONTAINSTEXT
899 63
                        || $conditional->getConditionType() === Conditional::CONDITION_BEGINSWITH
900 63
                        || $conditional->getConditionType() === Conditional::CONDITION_ENDSWITH
901 63
                    ) && $conditional->getOperatorType() !== Conditional::OPERATOR_NONE,
902 63
                    'operator',
903 63
                    $conditional->getOperatorType()
904 63
                );
905
906 63
                self::writeAttributeIf($objWriter, $conditional->getStopIfTrue(), 'stopIfTrue', '1');
907
908 63
                $cellRange = Coordinate::splitRange(str_replace('$', '', strtoupper($cellCoordinate)));
909 63
                [$topLeftCell] = $cellRange[0];
910
911
                if (
912 63
                    $conditional->getConditionType() === Conditional::CONDITION_CONTAINSTEXT
913 63
                    || $conditional->getConditionType() === Conditional::CONDITION_NOTCONTAINSTEXT
914 63
                    || $conditional->getConditionType() === Conditional::CONDITION_BEGINSWITH
915 63
                    || $conditional->getConditionType() === Conditional::CONDITION_ENDSWITH
916
                ) {
917 9
                    self::writeTextCondElements($objWriter, $conditional, $topLeftCell);
918 56
                } elseif ($conditional->getConditionType() === Conditional::CONDITION_TIMEPERIOD) {
919 12
                    self::writeTimePeriodCondElements($objWriter, $conditional, $topLeftCell);
920 44
                } elseif ($conditional->getConditionType() === Conditional::CONDITION_COLORSCALE) {
921 3
                    self::writeColorScaleElements($objWriter, $conditional->getColorScale());
922
                } else {
923 41
                    self::writeOtherCondElements($objWriter, $conditional, $topLeftCell);
924
                }
925
926
                //<dataBar>
927 63
                self::writeDataBarElements($objWriter, $conditional->getDataBar());
928
929 63
                $objWriter->endElement(); //end cfRule
930
            }
931
932 63
            $objWriter->endElement(); //end conditionalFormatting
933
        }
934
    }
935
936
    /**
937
     * Write DataValidations.
938
     */
939 405
    private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
940
    {
941
        // Datavalidation collection
942 405
        $dataValidationCollection = $worksheet->getDataValidationCollection();
943
944
        // Write data validations?
945 405
        if (!empty($dataValidationCollection)) {
946 14
            $objWriter->startElement('dataValidations');
947 14
            $objWriter->writeAttribute('count', (string) count($dataValidationCollection));
948
949 14
            foreach ($dataValidationCollection as $coordinate => $dv) {
950 14
                $objWriter->startElement('dataValidation');
951
952 14
                if ($dv->getType() != '') {
953 14
                    $objWriter->writeAttribute('type', $dv->getType());
954
                }
955
956 14
                if ($dv->getErrorStyle() != '') {
957 9
                    $objWriter->writeAttribute('errorStyle', $dv->getErrorStyle());
958
                }
959
960 14
                if ($dv->getOperator() != '') {
961 14
                    $objWriter->writeAttribute('operator', $dv->getOperator());
962
                }
963
964 14
                $objWriter->writeAttribute('allowBlank', ($dv->getAllowBlank() ? '1' : '0'));
965 14
                $objWriter->writeAttribute('showDropDown', (!$dv->getShowDropDown() ? '1' : '0'));
966 14
                $objWriter->writeAttribute('showInputMessage', ($dv->getShowInputMessage() ? '1' : '0'));
967 14
                $objWriter->writeAttribute('showErrorMessage', ($dv->getShowErrorMessage() ? '1' : '0'));
968
969 14
                if ($dv->getErrorTitle() !== '') {
970 7
                    $objWriter->writeAttribute('errorTitle', $dv->getErrorTitle());
971
                }
972 14
                if ($dv->getError() !== '') {
973 9
                    $objWriter->writeAttribute('error', $dv->getError());
974
                }
975 14
                if ($dv->getPromptTitle() !== '') {
976 6
                    $objWriter->writeAttribute('promptTitle', $dv->getPromptTitle());
977
                }
978 14
                if ($dv->getPrompt() !== '') {
979 5
                    $objWriter->writeAttribute('prompt', $dv->getPrompt());
980
                }
981
982 14
                $objWriter->writeAttribute('sqref', $dv->getSqref() ?? $coordinate);
983
984 14
                if ($dv->getFormula1() !== '') {
985 14
                    $objWriter->writeElement('formula1', FunctionPrefix::addFunctionPrefix($dv->getFormula1()));
986
                }
987 14
                if ($dv->getFormula2() !== '') {
988 3
                    $objWriter->writeElement('formula2', FunctionPrefix::addFunctionPrefix($dv->getFormula2()));
989
                }
990
991 14
                $objWriter->endElement();
992
            }
993
994 14
            $objWriter->endElement();
995
        }
996
    }
997
998
    /**
999
     * Write Hyperlinks.
1000
     */
1001 405
    private function writeHyperlinks(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1002
    {
1003
        // Hyperlink collection
1004 405
        $hyperlinkCollection = $worksheet->getHyperlinkCollection();
1005
1006
        // Relation ID
1007 405
        $relationId = 1;
1008
1009
        // Write hyperlinks?
1010 405
        if (!empty($hyperlinkCollection)) {
1011 15
            $objWriter->startElement('hyperlinks');
1012
1013 15
            foreach ($hyperlinkCollection as $coordinate => $hyperlink) {
1014 15
                $objWriter->startElement('hyperlink');
1015
1016 15
                $objWriter->writeAttribute('ref', $coordinate);
1017 15
                if (!$hyperlink->isInternal()) {
1018 15
                    $objWriter->writeAttribute('r:id', 'rId_hyperlink_' . $relationId);
1019 15
                    ++$relationId;
1020
                } else {
1021 8
                    $objWriter->writeAttribute('location', str_replace('sheet://', '', $hyperlink->getUrl()));
1022
                }
1023
1024 15
                if ($hyperlink->getTooltip() !== '') {
1025 9
                    $objWriter->writeAttribute('tooltip', $hyperlink->getTooltip());
1026 9
                    $objWriter->writeAttribute('display', $hyperlink->getTooltip());
1027
                }
1028
1029 15
                $objWriter->endElement();
1030
            }
1031
1032 15
            $objWriter->endElement();
1033
        }
1034
    }
1035
1036
    /**
1037
     * Write ProtectedRanges.
1038
     */
1039 405
    private function writeProtectedRanges(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1040
    {
1041 405
        if (count($worksheet->getProtectedCellRanges()) > 0) {
1042
            // protectedRanges
1043 9
            $objWriter->startElement('protectedRanges');
1044
1045
            // Loop protectedRanges
1046 9
            foreach ($worksheet->getProtectedCellRanges() as $protectedCell => $protectedRange) {
1047
                // protectedRange
1048 9
                $objWriter->startElement('protectedRange');
1049 9
                $objWriter->writeAttribute('name', $protectedRange->getName());
1050 9
                $objWriter->writeAttribute('sqref', $protectedCell);
1051 9
                $passwordHash = $protectedRange->getPassword();
1052 9
                $this->writeAttributeIf($objWriter, $passwordHash !== '', 'password', $passwordHash);
1053 9
                $securityDescriptor = $protectedRange->getSecurityDescriptor();
1054 9
                $this->writeAttributeIf($objWriter, $securityDescriptor !== '', 'securityDescriptor', $securityDescriptor);
1055 9
                $objWriter->endElement();
1056
            }
1057
1058 9
            $objWriter->endElement();
1059
        }
1060
    }
1061
1062
    /**
1063
     * Write MergeCells.
1064
     */
1065 405
    private function writeMergeCells(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1066
    {
1067 405
        if (count($worksheet->getMergeCells()) > 0) {
1068
            // mergeCells
1069 36
            $objWriter->startElement('mergeCells');
1070
1071
            // Loop mergeCells
1072 36
            foreach ($worksheet->getMergeCells() as $mergeCell) {
1073
                // mergeCell
1074 36
                $objWriter->startElement('mergeCell');
1075 36
                $objWriter->writeAttribute('ref', $mergeCell);
1076 36
                $objWriter->endElement();
1077
            }
1078
1079 36
            $objWriter->endElement();
1080
        }
1081
    }
1082
1083
    /**
1084
     * Write PrintOptions.
1085
     */
1086 405
    private function writePrintOptions(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1087
    {
1088
        // printOptions
1089 405
        $objWriter->startElement('printOptions');
1090
1091 405
        $objWriter->writeAttribute('gridLines', ($worksheet->getPrintGridlines() ? 'true' : 'false'));
1092 405
        $objWriter->writeAttribute('gridLinesSet', 'true');
1093
1094 405
        if ($worksheet->getPageSetup()->getHorizontalCentered()) {
1095 3
            $objWriter->writeAttribute('horizontalCentered', 'true');
1096
        }
1097
1098 405
        if ($worksheet->getPageSetup()->getVerticalCentered()) {
1099 2
            $objWriter->writeAttribute('verticalCentered', 'true');
1100
        }
1101
1102 405
        $objWriter->endElement();
1103
    }
1104
1105
    /**
1106
     * Write PageMargins.
1107
     */
1108 405
    private function writePageMargins(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1109
    {
1110
        // pageMargins
1111 405
        $objWriter->startElement('pageMargins');
1112 405
        $objWriter->writeAttribute('left', StringHelper::formatNumber($worksheet->getPageMargins()->getLeft()));
1113 405
        $objWriter->writeAttribute('right', StringHelper::formatNumber($worksheet->getPageMargins()->getRight()));
1114 405
        $objWriter->writeAttribute('top', StringHelper::formatNumber($worksheet->getPageMargins()->getTop()));
1115 405
        $objWriter->writeAttribute('bottom', StringHelper::formatNumber($worksheet->getPageMargins()->getBottom()));
1116 405
        $objWriter->writeAttribute('header', StringHelper::formatNumber($worksheet->getPageMargins()->getHeader()));
1117 405
        $objWriter->writeAttribute('footer', StringHelper::formatNumber($worksheet->getPageMargins()->getFooter()));
1118 405
        $objWriter->endElement();
1119
    }
1120
1121
    /**
1122
     * Write AutoFilter.
1123
     */
1124 405
    private function writeAutoFilter(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1125
    {
1126 405
        AutoFilter::writeAutoFilter($objWriter, $worksheet);
1127
    }
1128
1129
    /**
1130
     * Write Table.
1131
     */
1132 405
    private function writeTable(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1133
    {
1134 405
        $tableCount = $worksheet->getTableCollection()->count();
1135 405
        if ($tableCount === 0) {
1136 398
            return;
1137
        }
1138
1139 8
        $objWriter->startElement('tableParts');
1140 8
        $objWriter->writeAttribute('count', (string) $tableCount);
1141
1142 8
        for ($t = 1; $t <= $tableCount; ++$t) {
1143 8
            $objWriter->startElement('tablePart');
1144 8
            $objWriter->writeAttribute('r:id', 'rId_table_' . $t);
1145 8
            $objWriter->endElement();
1146
        }
1147
1148 8
        $objWriter->endElement();
1149
    }
1150
1151
    /**
1152
     * Write Background Image.
1153
     */
1154 405
    private function writeBackgroundImage(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1155
    {
1156 405
        if ($worksheet->getBackgroundImage() !== '') {
1157 2
            $objWriter->startElement('picture');
1158 2
            $objWriter->writeAttribute('r:id', 'rIdBg');
1159 2
            $objWriter->endElement();
1160
        }
1161
    }
1162
1163
    /**
1164
     * Write PageSetup.
1165
     */
1166 405
    private function writePageSetup(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1167
    {
1168
        // pageSetup
1169 405
        $objWriter->startElement('pageSetup');
1170 405
        $objWriter->writeAttribute('paperSize', (string) $worksheet->getPageSetup()->getPaperSize());
1171 405
        $objWriter->writeAttribute('orientation', $worksheet->getPageSetup()->getOrientation());
1172
1173 405
        if ($worksheet->getPageSetup()->getScale() !== null) {
1174 405
            $objWriter->writeAttribute('scale', (string) $worksheet->getPageSetup()->getScale());
1175
        }
1176 405
        if ($worksheet->getPageSetup()->getFitToHeight() !== null) {
1177 405
            $objWriter->writeAttribute('fitToHeight', (string) $worksheet->getPageSetup()->getFitToHeight());
1178
        } else {
1179
            $objWriter->writeAttribute('fitToHeight', '0');
1180
        }
1181 405
        if ($worksheet->getPageSetup()->getFitToWidth() !== null) {
1182 405
            $objWriter->writeAttribute('fitToWidth', (string) $worksheet->getPageSetup()->getFitToWidth());
1183
        } else {
1184
            $objWriter->writeAttribute('fitToWidth', '0');
1185
        }
1186 405
        if (!empty($worksheet->getPageSetup()->getFirstPageNumber())) {
1187 1
            $objWriter->writeAttribute('firstPageNumber', (string) $worksheet->getPageSetup()->getFirstPageNumber());
1188 1
            $objWriter->writeAttribute('useFirstPageNumber', '1');
1189
        }
1190 405
        $objWriter->writeAttribute('pageOrder', $worksheet->getPageSetup()->getPageOrder());
1191
1192 405
        $getUnparsedLoadedData = $worksheet->getParentOrThrow()->getUnparsedLoadedData();
1193 405
        if (isset($getUnparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'])) {
1194 35
            $objWriter->writeAttribute('r:id', $getUnparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId']);
1195
        }
1196
1197 405
        $objWriter->endElement();
1198
    }
1199
1200
    /**
1201
     * Write Header / Footer.
1202
     */
1203 405
    private function writeHeaderFooter(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1204
    {
1205
        // headerFooter
1206 405
        $headerFooter = $worksheet->getHeaderFooter();
1207 405
        $oddHeader = $headerFooter->getOddHeader();
1208 405
        $oddFooter = $headerFooter->getOddFooter();
1209 405
        $evenHeader = $headerFooter->getEvenHeader();
1210 405
        $evenFooter = $headerFooter->getEvenFooter();
1211 405
        $firstHeader = $headerFooter->getFirstHeader();
1212 405
        $firstFooter = $headerFooter->getFirstFooter();
1213 405
        if ("$oddHeader$oddFooter$evenHeader$evenFooter$firstHeader$firstFooter" === '') {
1214 395
            return;
1215
        }
1216
1217 20
        $objWriter->startElement('headerFooter');
1218 20
        $objWriter->writeAttribute('differentOddEven', ($worksheet->getHeaderFooter()->getDifferentOddEven() ? 'true' : 'false'));
1219 20
        $objWriter->writeAttribute('differentFirst', ($worksheet->getHeaderFooter()->getDifferentFirst() ? 'true' : 'false'));
1220 20
        $objWriter->writeAttribute('scaleWithDoc', ($worksheet->getHeaderFooter()->getScaleWithDocument() ? 'true' : 'false'));
1221 20
        $objWriter->writeAttribute('alignWithMargins', ($worksheet->getHeaderFooter()->getAlignWithMargins() ? 'true' : 'false'));
1222
1223 20
        self::writeElementIf($objWriter, $oddHeader !== '', 'oddHeader', $oddHeader);
1224 20
        self::writeElementIf($objWriter, $oddFooter !== '', 'oddFooter', $oddFooter);
1225 20
        self::writeElementIf($objWriter, $evenHeader !== '', 'evenHeader', $evenHeader);
1226 20
        self::writeElementIf($objWriter, $evenFooter !== '', 'evenFooter', $evenFooter);
1227 20
        self::writeElementIf($objWriter, $firstHeader !== '', 'firstHeader', $firstHeader);
1228 20
        self::writeElementIf($objWriter, $firstFooter !== '', 'firstFooter', $firstFooter);
1229
1230 20
        $objWriter->endElement(); // headerFooter
1231
    }
1232
1233
    /**
1234
     * Write Breaks.
1235
     */
1236 405
    private function writeBreaks(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1237
    {
1238
        // Get row and column breaks
1239 405
        $aRowBreaks = [];
1240 405
        $aColumnBreaks = [];
1241 405
        foreach ($worksheet->getRowBreaks() as $cell => $break) {
1242 7
            $aRowBreaks[$cell] = $break;
1243
        }
1244 405
        foreach ($worksheet->getColumnBreaks() as $cell => $break) {
1245 1
            $aColumnBreaks[$cell] = $break;
1246
        }
1247
1248
        // rowBreaks
1249 405
        if (!empty($aRowBreaks)) {
1250 7
            $objWriter->startElement('rowBreaks');
1251 7
            $objWriter->writeAttribute('count', (string) count($aRowBreaks));
1252 7
            $objWriter->writeAttribute('manualBreakCount', (string) count($aRowBreaks));
1253
1254 7
            foreach ($aRowBreaks as $cell => $break) {
1255 7
                $coords = Coordinate::coordinateFromString($cell);
1256
1257 7
                $objWriter->startElement('brk');
1258 7
                $objWriter->writeAttribute('id', $coords[1]);
1259 7
                $objWriter->writeAttribute('man', '1');
1260 7
                $rowBreakMax = $break->getMaxColOrRow();
1261 7
                if ($rowBreakMax >= 0) {
1262 4
                    $objWriter->writeAttribute('max', "$rowBreakMax");
1263
                }
1264 7
                $objWriter->endElement();
1265
            }
1266
1267 7
            $objWriter->endElement();
1268
        }
1269
1270
        // Second, write column breaks
1271 405
        if (!empty($aColumnBreaks)) {
1272 1
            $objWriter->startElement('colBreaks');
1273 1
            $objWriter->writeAttribute('count', (string) count($aColumnBreaks));
1274 1
            $objWriter->writeAttribute('manualBreakCount', (string) count($aColumnBreaks));
1275
1276 1
            foreach ($aColumnBreaks as $cell => $break) {
1277 1
                $coords = Coordinate::indexesFromString($cell);
1278
1279 1
                $objWriter->startElement('brk');
1280 1
                $objWriter->writeAttribute('id', (string) ((int) $coords[0] - 1));
1281 1
                $objWriter->writeAttribute('man', '1');
1282 1
                $objWriter->endElement();
1283
            }
1284
1285 1
            $objWriter->endElement();
1286
        }
1287
    }
1288
1289
    /**
1290
     * Write SheetData.
1291
     *
1292
     * @param string[] $stringTable String table
1293
     */
1294 406
    private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet, array $stringTable): void
1295
    {
1296
        // Flipped stringtable, for faster index searching
1297 406
        $aFlippedStringTable = $this->getParentWriter()->getWriterPartstringtable()->flipStringTable($stringTable);
1298
1299
        // sheetData
1300 406
        $objWriter->startElement('sheetData');
1301
1302
        // Get column count
1303 406
        $colCount = Coordinate::columnIndexFromString($worksheet->getHighestColumn());
1304
1305
        // Highest row number
1306 406
        $highestRow = $worksheet->getHighestRow();
1307
1308
        // Loop through cells building a comma-separated list of the columns in each row
1309
        // This is a trade-off between the memory usage that is required for a full array of columns,
1310
        //      and execution speed
1311
        /** @var array<int, string> $cellsByRow */
1312 406
        $cellsByRow = [];
1313 406
        foreach ($worksheet->getCoordinates() as $coordinate) {
1314 352
            [$column, $row] = Coordinate::coordinateFromString($coordinate);
1315 352
            if (!isset($cellsByRow[$row])) {
1316 352
                $pCell = $worksheet->getCell("$column$row");
1317 352
                $xfi = $pCell->getXfIndex();
1318 352
                $cellValue = $pCell->getValue();
1319 352
                $writeValue = $cellValue !== '' && $cellValue !== null;
1320 352
                if (!empty($xfi) || $writeValue) {
1321 335
                    $cellsByRow[$row] = "{$column},";
1322
                }
1323
            } else {
1324 216
                $cellsByRow[$row] .= "{$column},";
1325
            }
1326
        }
1327
1328 406
        $currentRow = 0;
1329 406
        $emptyDimension = new RowDimension();
1330 406
        while ($currentRow++ < $highestRow) {
1331 406
            $isRowSet = isset($cellsByRow[$currentRow]);
1332 406
            if ($isRowSet || $worksheet->rowDimensionExists($currentRow)) {
1333
                // Get row dimension
1334 335
                $rowDimension = $worksheet->rowDimensionExists($currentRow) ? $worksheet->getRowDimension($currentRow) : $emptyDimension;
1335
1336
                // Write current row?
1337 335
                $writeCurrentRow = $isRowSet || $rowDimension->getRowHeight() >= 0 || $rowDimension->getVisible() === false || $rowDimension->getCollapsed() === true || $rowDimension->getOutlineLevel() > 0 || $rowDimension->getXfIndex() !== null;
1338
1339 335
                if ($writeCurrentRow) {
1340
                    // Start a new row
1341 335
                    $objWriter->startElement('row');
1342 335
                    $objWriter->writeAttribute('r', "$currentRow");
1343 335
                    $objWriter->writeAttribute('spans', '1:' . $colCount);
1344
1345
                    // Row dimensions
1346 335
                    if ($rowDimension->getRowHeight() >= 0) {
1347 32
                        $objWriter->writeAttribute('customHeight', '1');
1348 32
                        $objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight()));
1349
                    }
1350
1351
                    // Row visibility
1352 335
                    if (!$rowDimension->getVisible() === true) {
1353 16
                        $objWriter->writeAttribute('hidden', 'true');
1354
                    }
1355
1356
                    // Collapsed
1357 335
                    if ($rowDimension->getCollapsed() === true) {
1358
                        $objWriter->writeAttribute('collapsed', 'true');
1359
                    }
1360
1361
                    // Outline level
1362 335
                    if ($rowDimension->getOutlineLevel() > 0) {
1363
                        $objWriter->writeAttribute('outlineLevel', (string) $rowDimension->getOutlineLevel());
1364
                    }
1365
1366
                    // Style
1367 335
                    if ($rowDimension->getXfIndex() !== null) {
1368 8
                        $objWriter->writeAttribute('s', (string) $rowDimension->getXfIndex());
1369 8
                        $objWriter->writeAttribute('customFormat', '1');
1370
                    }
1371
1372
                    // Write cells
1373 335
                    if (isset($cellsByRow[$currentRow])) {
1374
                        // We have a comma-separated list of column names (with a trailing entry); split to an array
1375 335
                        $columnsInRow = explode(',', $cellsByRow[$currentRow]);
1376 335
                        array_pop($columnsInRow);
1377 335
                        foreach ($columnsInRow as $column) {
1378
                            // Write cell
1379 335
                            $coord = "$column$currentRow";
1380 335
                            if ($worksheet->getCell($coord)->getIgnoredErrors()->getNumberStoredAsText()) {
1381 4
                                $this->numberStoredAsText .= " $coord";
1382
                            }
1383 335
                            if ($worksheet->getCell($coord)->getIgnoredErrors()->getFormula()) {
1384 1
                                $this->formula .= " $coord";
1385
                            }
1386 335
                            if ($worksheet->getCell($coord)->getIgnoredErrors()->getTwoDigitTextYear()) {
1387 1
                                $this->twoDigitTextYear .= " $coord";
1388
                            }
1389 335
                            if ($worksheet->getCell($coord)->getIgnoredErrors()->getEvalError()) {
1390 1
                                $this->evalError .= " $coord";
1391
                            }
1392 335
                            $this->writeCell($objWriter, $worksheet, $coord, $aFlippedStringTable);
1393
                        }
1394
                    }
1395
1396
                    // End row
1397 334
                    $objWriter->endElement();
1398
                }
1399
            }
1400
        }
1401
1402 405
        $objWriter->endElement();
1403
    }
1404
1405 10
    private function writeCellInlineStr(XMLWriter $objWriter, string $mappedType, RichText|string $cellValue): void
1406
    {
1407 10
        $objWriter->writeAttribute('t', $mappedType);
1408 10
        if (!$cellValue instanceof RichText) {
1409 1
            $objWriter->startElement('is');
1410 1
            $objWriter->writeElement(
1411 1
                't',
1412 1
                StringHelper::controlCharacterPHP2OOXML(htmlspecialchars($cellValue, Settings::htmlEntityFlags()))
1413 1
            );
1414 1
            $objWriter->endElement();
1415
        } else {
1416 10
            $objWriter->startElement('is');
1417 10
            $this->getParentWriter()->getWriterPartstringtable()->writeRichText($objWriter, $cellValue);
1418 10
            $objWriter->endElement();
1419
        }
1420
    }
1421
1422
    /**
1423
     * @param string[] $flippedStringTable
1424
     */
1425 235
    private function writeCellString(XMLWriter $objWriter, string $mappedType, RichText|string $cellValue, array $flippedStringTable): void
1426
    {
1427 235
        $objWriter->writeAttribute('t', $mappedType);
1428 235
        if (!$cellValue instanceof RichText) {
1429 234
            self::writeElementIf($objWriter, isset($flippedStringTable[$cellValue]), 'v', $flippedStringTable[$cellValue] ?? '');
1430
        } else {
1431 9
            $objWriter->writeElement('v', $flippedStringTable[$cellValue->getHashCode()]);
1432
        }
1433
    }
1434
1435 221
    private function writeCellNumeric(XMLWriter $objWriter, float|int $cellValue): void
1436
    {
1437
        //force a decimal to be written if the type is float
1438 221
        if (is_float($cellValue)) {
1439
            // force point as decimal separator in case current locale uses comma
1440 68
            $cellValue = str_replace(',', '.', (string) $cellValue);
1441 68
            if (!str_contains($cellValue, '.')) {
1442 29
                $cellValue = $cellValue . '.0';
1443
            }
1444
        }
1445 221
        $objWriter->writeElement('v', "$cellValue");
1446
    }
1447
1448 14
    private function writeCellBoolean(XMLWriter $objWriter, string $mappedType, bool $cellValue): void
1449
    {
1450 14
        $objWriter->writeAttribute('t', $mappedType);
1451 14
        $objWriter->writeElement('v', $cellValue ? '1' : '0');
1452
    }
1453
1454 11
    private function writeCellError(XMLWriter $objWriter, string $mappedType, string $cellValue, string $formulaerr = '#NULL!'): void
1455
    {
1456 11
        $objWriter->writeAttribute('t', $mappedType);
1457 11
        $cellIsFormula = str_starts_with($cellValue, '=');
1458 11
        self::writeElementIf($objWriter, $cellIsFormula, 'f', FunctionPrefix::addFunctionPrefixStripEquals($cellValue));
1459 11
        $objWriter->writeElement('v', $cellIsFormula ? $formulaerr : $cellValue);
1460
    }
1461
1462 105
    private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell $cell): void
1463
    {
1464 105
        $attributes = $cell->getFormulaAttributes() ?? [];
1465 105
        $coordinate = $cell->getCoordinate();
1466 105
        $calculatedValue = $this->getParentWriter()->getPreCalculateFormulas() ? $cell->getCalculatedValue() : $cellValue;
1467 104
        if ($calculatedValue === ExcelError::SPILL()) {
1468 1
            $objWriter->writeAttribute('t', 'e');
1469
            //$objWriter->writeAttribute('cm', '1'); // already added
1470 1
            $objWriter->writeAttribute('vm', '1');
1471 1
            $objWriter->startElement('f');
1472 1
            $objWriter->writeAttribute('t', 'array');
1473 1
            $objWriter->writeAttribute('aca', '1');
1474 1
            $objWriter->writeAttribute('ref', $coordinate);
1475 1
            $objWriter->writeAttribute('ca', '1');
1476 1
            $objWriter->text(FunctionPrefix::addFunctionPrefixStripEquals($cellValue));
1477 1
            $objWriter->endElement(); // f
1478 1
            $objWriter->writeElement('v', ExcelError::VALUE()); // note #VALUE! in xml even though error is #SPILL!
1479
1480 1
            return;
1481
        }
1482 103
        $calculatedValueString = $this->getParentWriter()->getPreCalculateFormulas() ? $cell->getCalculatedValueString() : $cellValue;
1483 103
        $result = $calculatedValue;
1484 103
        while (is_array($result)) {
1485 7
            $result = array_shift($result);
1486
        }
1487 103
        if (is_string($result)) {
1488 41
            if (ErrorValue::isError($result)) {
1489 10
                $this->writeCellError($objWriter, 'e', $cellValue, $result);
1490
1491 10
                return;
1492
            }
1493 40
            $objWriter->writeAttribute('t', 'str');
1494 40
            $result = $calculatedValueString = StringHelper::controlCharacterPHP2OOXML($result);
1495 40
            if (is_string($calculatedValue)) {
1496 39
                $calculatedValue = $calculatedValueString;
1497
            }
1498 83
        } elseif (is_bool($result)) {
1499 8
            $objWriter->writeAttribute('t', 'b');
1500 8
            if (is_bool($calculatedValue)) {
1501 8
                $calculatedValue = $result;
1502
            }
1503 8
            $result = (int) $result;
1504 8
            $calculatedValueString = (string) $result;
1505
        }
1506
1507 103
        if (isset($attributes['ref'])) {
1508 23
            $ref = $this->parseRef($coordinate, $attributes['ref']);
1509
        } else {
1510 95
            $ref = $coordinate;
1511
        }
1512 103
        if (is_array($calculatedValue)) {
1513 7
            $attributes['t'] = 'array';
1514
        }
1515 103
        if (($attributes['t'] ?? null) === 'array') {
1516 10
            $objWriter->startElement('f');
1517 10
            $objWriter->writeAttribute('t', 'array');
1518 10
            $objWriter->writeAttribute('ref', $ref);
1519 10
            $objWriter->writeAttribute('aca', '1');
1520 10
            $objWriter->writeAttribute('ca', '1');
1521 10
            $objWriter->text(FunctionPrefix::addFunctionPrefixStripEquals($cellValue));
1522 10
            $objWriter->endElement();
1523
            if (
1524 10
                is_scalar($result)
1525 10
                && $this->getParentWriter()->getOffice2003Compatibility() === false
1526 10
                && $this->getParentWriter()->getPreCalculateFormulas()
1527
            ) {
1528 10
                $objWriter->writeElement('v', (string) $result);
1529
            }
1530
        } else {
1531 93
            $objWriter->writeElement('f', FunctionPrefix::addFunctionPrefixStripEquals($cellValue));
1532 93
            self::writeElementIf(
1533 93
                $objWriter,
1534 93
                $this->getParentWriter()->getOffice2003Compatibility() === false
1535 93
                && $this->getParentWriter()->getPreCalculateFormulas()
1536 93
                && $calculatedValue !== null,
1537 93
                'v',
1538 93
                (!is_array($calculatedValue) && !str_starts_with($calculatedValueString, '#'))
1539 93
                    ? StringHelper::formatNumber($calculatedValueString) : '0'
1540 93
            );
1541
        }
1542
    }
1543
1544 23
    private function parseRef(string $coordinate, string $ref): string
1545
    {
1546 23
        if (preg_match('/^([A-Z]{1,3})([0-9]{1,7})(:([A-Z]{1,3})([0-9]{1,7}))?$/', $ref, $matches) !== 1) {
1547
            return $ref;
1548
        }
1549 23
        if (!isset($matches[3])) { // single cell, not range
1550 1
            return $coordinate;
1551
        }
1552 23
        $minRow = (int) $matches[2];
1553
        // https://github.com/phpstan/phpstan/issues/11602
1554 23
        $maxRow = (int) $matches[5]; // @phpstan-ignore-line
1555 23
        $rows = $maxRow - $minRow + 1;
1556 23
        $minCol = Coordinate::columnIndexFromString($matches[1]);
1557 23
        $maxCol = Coordinate::columnIndexFromString($matches[4]); // @phpstan-ignore-line
1558 23
        $cols = $maxCol - $minCol + 1;
1559 23
        $firstCellArray = Coordinate::indexesFromString($coordinate);
1560 23
        $lastRow = $firstCellArray[1] + $rows - 1;
1561 23
        $lastColumn = $firstCellArray[0] + $cols - 1;
1562 23
        $lastColumnString = Coordinate::stringFromColumnIndex($lastColumn);
1563
1564 23
        return "$coordinate:$lastColumnString$lastRow";
1565
    }
1566
1567
    /**
1568
     * Write Cell.
1569
     *
1570
     * @param string $cellAddress Cell Address
1571
     * @param string[] $flippedStringTable String table (flipped), for faster index searching
1572
     */
1573 335
    private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet, string $cellAddress, array $flippedStringTable): void
1574
    {
1575
        // Cell
1576 335
        $pCell = $worksheet->getCell($cellAddress);
1577 335
        $xfi = $pCell->getXfIndex();
1578 335
        $cellValue = $pCell->getValue();
1579 335
        $cellValueString = $pCell->getValueString();
1580 335
        $writeValue = $cellValue !== '' && $cellValue !== null;
1581 335
        if (empty($xfi) && !$writeValue) {
1582 26
            return;
1583
        }
1584 335
        $objWriter->startElement('c');
1585 335
        $objWriter->writeAttribute('r', $cellAddress);
1586 335
        $mappedType = $pCell->getDataType();
1587 335
        if ($mappedType === DataType::TYPE_FORMULA) {
1588 105
            if ($this->useDynamicArrays) {
1589 7
                $tempCalc = $pCell->getCalculatedValue();
1590 7
                if (is_array($tempCalc)) {
1591 6
                    $objWriter->writeAttribute('cm', '1');
1592
                }
1593
            }
1594
        }
1595
1596
        // Sheet styles
1597 335
        if ($xfi) {
1598 116
            $objWriter->writeAttribute('s', "$xfi");
1599 308
        } elseif ($this->explicitStyle0) {
1600 1
            $objWriter->writeAttribute('s', '0');
1601
        }
1602
1603
        // If cell value is supplied, write cell value
1604 335
        if ($writeValue) {
1605
            // Write data depending on its type
1606 329
            switch (strtolower($mappedType)) {
1607 329
                case 'inlinestr':    // Inline string
1608
                    /** @var RichText|string */
1609 10
                    $richText = $cellValue;
1610 10
                    $this->writeCellInlineStr($objWriter, $mappedType, $richText);
1611
1612 10
                    break;
1613 327
                case 's':            // String
1614 235
                    $this->writeCellString($objWriter, $mappedType, ($cellValue instanceof RichText) ? $cellValue : $cellValueString, $flippedStringTable);
1615
1616 235
                    break;
1617 252
                case 'f':            // Formula
1618 105
                    $this->writeCellFormula($objWriter, $cellValueString, $pCell);
1619
1620 104
                    break;
1621 225
                case 'n':            // Numeric
1622 221
                    $cellValueNumeric = is_numeric($cellValue) ? ($cellValue + 0) : 0;
1623 221
                    $this->writeCellNumeric($objWriter, $cellValueNumeric);
1624
1625 221
                    break;
1626 14
                case 'b':            // Boolean
1627 14
                    $this->writeCellBoolean($objWriter, $mappedType, (bool) $cellValue);
1628
1629 14
                    break;
1630 1
                case 'e':            // Error
1631 1
                    $this->writeCellError($objWriter, $mappedType, $cellValueString);
1632
            }
1633
        }
1634
1635 334
        $objWriter->endElement(); // c
1636
    }
1637
1638
    /**
1639
     * Write Drawings.
1640
     *
1641
     * @param bool $includeCharts Flag indicating if we should include drawing details for charts
1642
     */
1643 405
    private function writeDrawings(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet, bool $includeCharts = false): void
1644
    {
1645 405
        $unparsedLoadedData = $worksheet->getParentOrThrow()->getUnparsedLoadedData();
1646 405
        $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds']);
1647 405
        $chartCount = ($includeCharts) ? $worksheet->getChartCollection()->count() : 0;
1648 405
        if ($chartCount == 0 && $worksheet->getDrawingCollection()->count() == 0 && !$hasUnparsedDrawing) {
1649 298
            return;
1650
        }
1651
1652
        // If sheet contains drawings, add the relationships
1653 126
        $objWriter->startElement('drawing');
1654
1655 126
        $rId = 'rId1';
1656 126
        if (isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds'])) {
1657 49
            $drawingOriginalIds = $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds'];
1658
            // take first. In future can be overriten
1659
            // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels::writeWorksheetRelationships)
1660 49
            $rId = reset($drawingOriginalIds);
1661
        }
1662
1663 126
        $objWriter->writeAttribute('r:id', $rId);
1664 126
        $objWriter->endElement();
1665
    }
1666
1667
    /**
1668
     * Write LegacyDrawing.
1669
     */
1670 405
    private function writeLegacyDrawing(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1671
    {
1672
        // If sheet contains comments, add the relationships
1673 405
        $unparsedLoadedData = $worksheet->getParentOrThrow()->getUnparsedLoadedData();
1674 405
        if (count($worksheet->getComments()) > 0 || isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['legacyDrawing'])) {
1675 28
            $objWriter->startElement('legacyDrawing');
1676 28
            $objWriter->writeAttribute('r:id', 'rId_comments_vml1');
1677 28
            $objWriter->endElement();
1678
        }
1679
    }
1680
1681
    /**
1682
     * Write LegacyDrawingHF.
1683
     */
1684 405
    private function writeLegacyDrawingHF(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1685
    {
1686
        // If sheet contains images, add the relationships
1687 405
        if (count($worksheet->getHeaderFooter()->getImages()) > 0) {
1688 3
            $objWriter->startElement('legacyDrawingHF');
1689 3
            $objWriter->writeAttribute('r:id', 'rId_headerfooter_vml1');
1690 3
            $objWriter->endElement();
1691
        }
1692
    }
1693
1694 405
    private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1695
    {
1696 405
        if (empty($worksheet->getParentOrThrow()->getUnparsedLoadedData()['sheets'][$worksheet->getCodeName()]['AlternateContents'])) {
1697 404
            return;
1698
        }
1699
1700 4
        foreach ($worksheet->getParentOrThrow()->getUnparsedLoadedData()['sheets'][$worksheet->getCodeName()]['AlternateContents'] as $alternateContent) {
1701 4
            $objWriter->writeRaw($alternateContent);
1702
        }
1703
    }
1704
1705
    /**
1706
     * write <ExtLst>
1707
     * only implementation conditionalFormattings.
1708
     *
1709
     * @url https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/07d607af-5618-4ca2-b683-6a78dc0d9627
1710
     */
1711 405
    private function writeExtLst(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void
1712
    {
1713 405
        $conditionalFormattingRuleExtList = [];
1714 405
        foreach ($worksheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) {
1715
            /** @var Conditional $conditional */
1716 63
            foreach ($conditionalStyles as $conditional) {
1717 63
                $dataBar = $conditional->getDataBar();
1718 63
                if ($dataBar && $dataBar->getConditionalFormattingRuleExt()) {
1719 1
                    $conditionalFormattingRuleExtList[] = $dataBar->getConditionalFormattingRuleExt();
1720
                }
1721
            }
1722
        }
1723
1724 405
        if (count($conditionalFormattingRuleExtList) > 0) {
1725 1
            $conditionalFormattingRuleExtNsPrefix = 'x14';
1726 1
            $objWriter->startElement('extLst');
1727 1
            $objWriter->startElement('ext');
1728 1
            $objWriter->writeAttribute('uri', '{78C0D931-6437-407d-A8EE-F0AAD7539E65}');
1729 1
            $objWriter->startElementNs($conditionalFormattingRuleExtNsPrefix, 'conditionalFormattings', null);
1730 1
            foreach ($conditionalFormattingRuleExtList as $extension) {
1731 1
                self::writeExtConditionalFormattingElements($objWriter, $extension);
1732
            }
1733 1
            $objWriter->endElement(); //end conditionalFormattings
1734 1
            $objWriter->endElement(); //end ext
1735 1
            $objWriter->endElement(); //end extLst
1736
        }
1737
    }
1738
}
1739