Passed
Push — master ( 440039...6cc2bb )
by Mark
23:06 queued 12:16
created

Worksheet::writeLegacyDrawingHF()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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