Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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