Failed Conditions
Pull Request — master (#3962)
by Owen
11:35
created

Worksheet::writeCell()   F

Complexity

Conditions 18
Paths 218

Size

Total Lines 63
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 18

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 41
dl 0
loc 63
ccs 26
cts 26
cp 1
rs 3.8083
c 1
b 0
f 0
cc 18
nc 218
nop 4
crap 18

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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