Failed Conditions
Pull Request — master (#4314)
by Owen
11:26
created

Worksheet   F

Complexity

Total Complexity 328

Size/Duplication

Total Lines 1714
Duplicated Lines 0 %

Test Coverage

Coverage 98%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 328
eloc 935
c 4
b 0
f 0
dl 0
loc 1714
ccs 931
cts 950
cp 0.98
rs 1.665

46 Methods

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