Passed
Push — master ( 46fdc8...4b16f3 )
by Mark
15:13 queued 06:31
created

Worksheet::writeAttributeNotNull()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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