Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
 
3
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
4
 
5
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
6
use PhpOffice\PhpSpreadsheet\Chart\Axis;
7
use PhpOffice\PhpSpreadsheet\Chart\AxisText;
8
use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
9
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
10
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
11
use PhpOffice\PhpSpreadsheet\Chart\GridLines;
12
use PhpOffice\PhpSpreadsheet\Chart\Layout;
13
use PhpOffice\PhpSpreadsheet\Chart\Legend;
14
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
15
use PhpOffice\PhpSpreadsheet\Chart\Properties as ChartProperties;
16
use PhpOffice\PhpSpreadsheet\Chart\Title;
17
use PhpOffice\PhpSpreadsheet\Chart\TrendLine;
18
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
19
use PhpOffice\PhpSpreadsheet\RichText\RichText;
20
use PhpOffice\PhpSpreadsheet\Style\Font;
21
use SimpleXMLElement;
22
 
23
class Chart
24
{
25
    private string $cNamespace;
26
 
27
    private string $aNamespace;
28
 
29
    public function __construct(string $cNamespace = Namespaces::CHART, string $aNamespace = Namespaces::DRAWINGML)
30
    {
31
        $this->cNamespace = $cNamespace;
32
        $this->aNamespace = $aNamespace;
33
    }
34
 
35
    private static function getAttributeString(SimpleXMLElement $component, string $name): string|null
36
    {
37
        $attributes = $component->attributes();
38
        if (@isset($attributes[$name])) {
39
            return (string) $attributes[$name];
40
        }
41
 
42
        return null;
43
    }
44
 
45
    private static function getAttributeInteger(SimpleXMLElement $component, string $name): int|null
46
    {
47
        $attributes = $component->attributes();
48
        if (@isset($attributes[$name])) {
49
            return (int) $attributes[$name];
50
        }
51
 
52
        return null;
53
    }
54
 
55
    private static function getAttributeBoolean(SimpleXMLElement $component, string $name): bool|null
56
    {
57
        $attributes = $component->attributes();
58
        if (@isset($attributes[$name])) {
59
            $value = (string) $attributes[$name];
60
 
61
            return $value === 'true' || $value === '1';
62
        }
63
 
64
        return null;
65
    }
66
 
67
    private static function getAttributeFloat(SimpleXMLElement $component, string $name): float|null
68
    {
69
        $attributes = $component->attributes();
70
        if (@isset($attributes[$name])) {
71
            return (float) $attributes[$name];
72
        }
73
 
74
        return null;
75
    }
76
 
77
    public function readChart(SimpleXMLElement $chartElements, string $chartName): \PhpOffice\PhpSpreadsheet\Chart\Chart
78
    {
79
        $chartElementsC = $chartElements->children($this->cNamespace);
80
 
81
        $XaxisLabel = $YaxisLabel = $legend = $title = null;
82
        $dispBlanksAs = null;
83
        $plotVisOnly = false;
84
        $plotArea = null;
85
        $rotX = $rotY = $rAngAx = $perspective = null;
86
        $xAxis = new Axis();
87
        $yAxis = new Axis();
88
        $autoTitleDeleted = null;
89
        $chartNoFill = false;
90
        $chartBorderLines = null;
91
        $chartFillColor = null;
92
        $gradientArray = [];
93
        $gradientLin = null;
94
        $roundedCorners = false;
95
        $gapWidth = null;
96
        $useUpBars = null;
97
        $useDownBars = null;
98
        $noBorder = false;
99
        foreach ($chartElementsC as $chartElementKey => $chartElement) {
100
            switch ($chartElementKey) {
101
                case 'spPr':
102
                    $children = $chartElementsC->spPr->children($this->aNamespace);
103
                    if (isset($children->noFill)) {
104
                        $chartNoFill = true;
105
                    }
106
                    if (isset($children->solidFill)) {
107
                        $chartFillColor = $this->readColor($children->solidFill);
108
                    }
109
                    if (isset($children->ln)) {
110
                        $chartBorderLines = new GridLines();
111
                        $this->readLineStyle($chartElementsC, $chartBorderLines);
112
                        if (isset($children->ln->noFill)) {
113
                            $noBorder = true;
114
                        }
115
                    }
116
 
117
                    break;
118
                case 'roundedCorners':
119
                    /** @var bool $roundedCorners */
120
                    $roundedCorners = self::getAttributeBoolean($chartElementsC->roundedCorners, 'val');
121
 
122
                    break;
123
                case 'chart':
124
                    foreach ($chartElement as $chartDetailsKey => $chartDetails) {
125
                        $chartDetails = Xlsx::testSimpleXml($chartDetails);
126
                        switch ($chartDetailsKey) {
127
                            case 'autoTitleDeleted':
128
                                /** @var bool $autoTitleDeleted */
129
                                $autoTitleDeleted = self::getAttributeBoolean($chartElementsC->chart->autoTitleDeleted, 'val');
130
 
131
                                break;
132
                            case 'view3D':
133
                                $rotX = self::getAttributeInteger($chartDetails->rotX, 'val');
134
                                $rotY = self::getAttributeInteger($chartDetails->rotY, 'val');
135
                                $rAngAx = self::getAttributeInteger($chartDetails->rAngAx, 'val');
136
                                $perspective = self::getAttributeInteger($chartDetails->perspective, 'val');
137
 
138
                                break;
139
                            case 'plotArea':
140
                                $plotAreaLayout = $XaxisLabel = $YaxisLabel = null;
141
                                $plotSeries = $plotAttributes = [];
142
                                $catAxRead = false;
143
                                $plotNoFill = false;
144
                                foreach ($chartDetails as $chartDetailKey => $chartDetail) {
145
                                    $chartDetail = Xlsx::testSimpleXml($chartDetail);
146
                                    switch ($chartDetailKey) {
147
                                        case 'spPr':
148
                                            $possibleNoFill = $chartDetails->spPr->children($this->aNamespace);
149
                                            if (isset($possibleNoFill->noFill)) {
150
                                                $plotNoFill = true;
151
                                            }
152
                                            if (isset($possibleNoFill->gradFill->gsLst)) {
153
                                                foreach ($possibleNoFill->gradFill->gsLst->gs as $gradient) {
154
                                                    $gradient = Xlsx::testSimpleXml($gradient);
155
                                                    /** @var float $pos */
156
                                                    $pos = self::getAttributeFloat($gradient, 'pos');
157
                                                    $gradientArray[] = [
158
                                                        $pos / ChartProperties::PERCENTAGE_MULTIPLIER,
159
                                                        new ChartColor($this->readColor($gradient)),
160
                                                    ];
161
                                                }
162
                                            }
163
                                            if (isset($possibleNoFill->gradFill->lin)) {
164
                                                $gradientLin = ChartProperties::XmlToAngle((string) self::getAttributeString($possibleNoFill->gradFill->lin, 'ang'));
165
                                            }
166
 
167
                                            break;
168
                                        case 'layout':
169
                                            $plotAreaLayout = $this->chartLayoutDetails($chartDetail);
170
 
171
                                            break;
172
                                        case Axis::AXIS_TYPE_CATEGORY:
173
                                        case Axis::AXIS_TYPE_DATE:
174
                                            $catAxRead = true;
175
                                            if (isset($chartDetail->title)) {
176
                                                $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
177
                                            }
178
                                            $xAxis->setAxisType($chartDetailKey);
179
                                            $this->readEffects($chartDetail, $xAxis);
180
                                            $this->readLineStyle($chartDetail, $xAxis);
181
                                            if (isset($chartDetail->spPr)) {
182
                                                $sppr = $chartDetail->spPr->children($this->aNamespace);
183
                                                if (isset($sppr->solidFill)) {
184
                                                    $axisColorArray = $this->readColor($sppr->solidFill);
185
                                                    $xAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
186
                                                }
187
                                                if (isset($chartDetail->spPr->ln->noFill)) {
188
                                                    $xAxis->setNoFill(true);
189
                                                }
190
                                            }
191
                                            if (isset($chartDetail->majorGridlines)) {
192
                                                $majorGridlines = new GridLines();
193
                                                if (isset($chartDetail->majorGridlines->spPr)) {
194
                                                    $this->readEffects($chartDetail->majorGridlines, $majorGridlines);
195
                                                    $this->readLineStyle($chartDetail->majorGridlines, $majorGridlines);
196
                                                }
197
                                                $xAxis->setMajorGridlines($majorGridlines);
198
                                            }
199
                                            if (isset($chartDetail->minorGridlines)) {
200
                                                $minorGridlines = new GridLines();
201
                                                $minorGridlines->activateObject();
202
                                                if (isset($chartDetail->minorGridlines->spPr)) {
203
                                                    $this->readEffects($chartDetail->minorGridlines, $minorGridlines);
204
                                                    $this->readLineStyle($chartDetail->minorGridlines, $minorGridlines);
205
                                                }
206
                                                $xAxis->setMinorGridlines($minorGridlines);
207
                                            }
208
                                            $this->setAxisProperties($chartDetail, $xAxis);
209
 
210
                                            break;
211
                                        case Axis::AXIS_TYPE_VALUE:
212
                                            $whichAxis = null;
213
                                            $axPos = null;
214
                                            if (isset($chartDetail->axPos)) {
215
                                                $axPos = self::getAttributeString($chartDetail->axPos, 'val');
216
                                            }
217
                                            if ($catAxRead) {
218
                                                $whichAxis = $yAxis;
219
                                                $yAxis->setAxisType($chartDetailKey);
220
                                            } elseif (!empty($axPos)) {
221
                                                switch ($axPos) {
222
                                                    case 't':
223
                                                    case 'b':
224
                                                        $whichAxis = $xAxis;
225
                                                        $xAxis->setAxisType($chartDetailKey);
226
 
227
                                                        break;
228
                                                    case 'r':
229
                                                    case 'l':
230
                                                        $whichAxis = $yAxis;
231
                                                        $yAxis->setAxisType($chartDetailKey);
232
 
233
                                                        break;
234
                                                }
235
                                            }
236
                                            if (isset($chartDetail->title)) {
237
                                                $axisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
238
 
239
                                                switch ($axPos) {
240
                                                    case 't':
241
                                                    case 'b':
242
                                                        $XaxisLabel = $axisLabel;
243
 
244
                                                        break;
245
                                                    case 'r':
246
                                                    case 'l':
247
                                                        $YaxisLabel = $axisLabel;
248
 
249
                                                        break;
250
                                                }
251
                                            }
252
                                            $this->readEffects($chartDetail, $whichAxis);
253
                                            $this->readLineStyle($chartDetail, $whichAxis);
254
                                            if ($whichAxis !== null && isset($chartDetail->spPr)) {
255
                                                $sppr = $chartDetail->spPr->children($this->aNamespace);
256
                                                if (isset($sppr->solidFill)) {
257
                                                    $axisColorArray = $this->readColor($sppr->solidFill);
258
                                                    $whichAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
259
                                                }
260
                                                if (isset($sppr->ln->noFill)) {
261
                                                    $whichAxis->setNoFill(true);
262
                                                }
263
                                            }
264
                                            if ($whichAxis !== null && isset($chartDetail->majorGridlines)) {
265
                                                $majorGridlines = new GridLines();
266
                                                if (isset($chartDetail->majorGridlines->spPr)) {
267
                                                    $this->readEffects($chartDetail->majorGridlines, $majorGridlines);
268
                                                    $this->readLineStyle($chartDetail->majorGridlines, $majorGridlines);
269
                                                }
270
                                                $whichAxis->setMajorGridlines($majorGridlines);
271
                                            }
272
                                            if ($whichAxis !== null && isset($chartDetail->minorGridlines)) {
273
                                                $minorGridlines = new GridLines();
274
                                                $minorGridlines->activateObject();
275
                                                if (isset($chartDetail->minorGridlines->spPr)) {
276
                                                    $this->readEffects($chartDetail->minorGridlines, $minorGridlines);
277
                                                    $this->readLineStyle($chartDetail->minorGridlines, $minorGridlines);
278
                                                }
279
                                                $whichAxis->setMinorGridlines($minorGridlines);
280
                                            }
281
                                            $this->setAxisProperties($chartDetail, $whichAxis);
282
 
283
                                            break;
284
                                        case 'barChart':
285
                                        case 'bar3DChart':
286
                                            $barDirection = self::getAttributeString($chartDetail->barDir, 'val');
287
                                            $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
288
                                            $plotSer->setPlotDirection("$barDirection");
289
                                            $plotSeries[] = $plotSer;
290
                                            $plotAttributes = $this->readChartAttributes($chartDetail);
291
 
292
                                            break;
293
                                        case 'lineChart':
294
                                        case 'line3DChart':
295
                                            $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
296
                                            $plotAttributes = $this->readChartAttributes($chartDetail);
297
 
298
                                            break;
299
                                        case 'areaChart':
300
                                        case 'area3DChart':
301
                                            $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
302
                                            $plotAttributes = $this->readChartAttributes($chartDetail);
303
 
304
                                            break;
305
                                        case 'doughnutChart':
306
                                        case 'pieChart':
307
                                        case 'pie3DChart':
308
                                            $explosion = self::getAttributeString($chartDetail->ser->explosion, 'val');
309
                                            $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
310
                                            $plotSer->setPlotStyle("$explosion");
311
                                            $plotSeries[] = $plotSer;
312
                                            $plotAttributes = $this->readChartAttributes($chartDetail);
313
 
314
                                            break;
315
                                        case 'scatterChart':
316
                                            /** @var string $scatterStyle */
317
                                            $scatterStyle = self::getAttributeString($chartDetail->scatterStyle, 'val');
318
                                            $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
319
                                            $plotSer->setPlotStyle($scatterStyle);
320
                                            $plotSeries[] = $plotSer;
321
                                            $plotAttributes = $this->readChartAttributes($chartDetail);
322
 
323
                                            break;
324
                                        case 'bubbleChart':
325
                                            $bubbleScale = self::getAttributeInteger($chartDetail->bubbleScale, 'val');
326
                                            $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
327
                                            $plotSer->setPlotStyle("$bubbleScale");
328
                                            $plotSeries[] = $plotSer;
329
                                            $plotAttributes = $this->readChartAttributes($chartDetail);
330
 
331
                                            break;
332
                                        case 'radarChart':
333
                                            /** @var string $radarStyle */
334
                                            $radarStyle = self::getAttributeString($chartDetail->radarStyle, 'val');
335
                                            $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
336
                                            $plotSer->setPlotStyle($radarStyle);
337
                                            $plotSeries[] = $plotSer;
338
                                            $plotAttributes = $this->readChartAttributes($chartDetail);
339
 
340
                                            break;
341
                                        case 'surfaceChart':
342
                                        case 'surface3DChart':
343
                                            $wireFrame = self::getAttributeBoolean($chartDetail->wireframe, 'val');
344
                                            $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
345
                                            $plotSer->setPlotStyle("$wireFrame");
346
                                            $plotSeries[] = $plotSer;
347
                                            $plotAttributes = $this->readChartAttributes($chartDetail);
348
 
349
                                            break;
350
                                        case 'stockChart':
351
                                            $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
352
                                            if (isset($chartDetail->upDownBars->gapWidth)) {
353
                                                $gapWidth = self::getAttributeInteger($chartDetail->upDownBars->gapWidth, 'val');
354
                                            }
355
                                            if (isset($chartDetail->upDownBars->upBars)) {
356
                                                $useUpBars = true;
357
                                            }
358
                                            if (isset($chartDetail->upDownBars->downBars)) {
359
                                                $useDownBars = true;
360
                                            }
361
                                            $plotAttributes = $this->readChartAttributes($chartDetail);
362
 
363
                                            break;
364
                                    }
365
                                }
366
                                if ($plotAreaLayout == null) {
367
                                    $plotAreaLayout = new Layout();
368
                                }
369
                                $plotArea = new PlotArea($plotAreaLayout, $plotSeries);
370
                                $this->setChartAttributes($plotAreaLayout, $plotAttributes);
371
                                if ($plotNoFill) {
372
                                    $plotArea->setNoFill(true);
373
                                }
374
                                if (!empty($gradientArray)) {
375
                                    $plotArea->setGradientFillProperties($gradientArray, $gradientLin);
376
                                }
377
                                if (is_int($gapWidth)) {
378
                                    $plotArea->setGapWidth($gapWidth);
379
                                }
380
                                if ($useUpBars === true) {
381
                                    $plotArea->setUseUpBars(true);
382
                                }
383
                                if ($useDownBars === true) {
384
                                    $plotArea->setUseDownBars(true);
385
                                }
386
 
387
                                break;
388
                            case 'plotVisOnly':
389
                                $plotVisOnly = (bool) self::getAttributeString($chartDetails, 'val');
390
 
391
                                break;
392
                            case 'dispBlanksAs':
393
                                $dispBlanksAs = self::getAttributeString($chartDetails, 'val');
394
 
395
                                break;
396
                            case 'title':
397
                                $title = $this->chartTitle($chartDetails);
398
 
399
                                break;
400
                            case 'legend':
401
                                $legendPos = 'r';
402
                                $legendLayout = null;
403
                                $legendOverlay = false;
404
                                $legendBorderLines = null;
405
                                $legendFillColor = null;
406
                                $legendText = null;
407
                                $addLegendText = false;
408
                                foreach ($chartDetails as $chartDetailKey => $chartDetail) {
409
                                    $chartDetail = Xlsx::testSimpleXml($chartDetail);
410
                                    switch ($chartDetailKey) {
411
                                        case 'legendPos':
412
                                            $legendPos = self::getAttributeString($chartDetail, 'val');
413
 
414
                                            break;
415
                                        case 'overlay':
416
                                            $legendOverlay = self::getAttributeBoolean($chartDetail, 'val');
417
 
418
                                            break;
419
                                        case 'layout':
420
                                            $legendLayout = $this->chartLayoutDetails($chartDetail);
421
 
422
                                            break;
423
                                        case 'spPr':
424
                                            $children = $chartDetails->spPr->children($this->aNamespace);
425
                                            if (isset($children->solidFill)) {
426
                                                $legendFillColor = $this->readColor($children->solidFill);
427
                                            }
428
                                            if (isset($children->ln)) {
429
                                                $legendBorderLines = new GridLines();
430
                                                $this->readLineStyle($chartDetails, $legendBorderLines);
431
                                            }
432
 
433
                                            break;
434
                                        case 'txPr':
435
                                            $children = $chartDetails->txPr->children($this->aNamespace);
436
                                            $addLegendText = false;
437
                                            $legendText = new AxisText();
438
                                            if (isset($children->p->pPr->defRPr->solidFill)) {
439
                                                $colorArray = $this->readColor($children->p->pPr->defRPr->solidFill);
440
                                                $legendText->getFillColorObject()->setColorPropertiesArray($colorArray);
441
                                                $addLegendText = true;
442
                                            }
443
                                            if (isset($children->p->pPr->defRPr->effectLst)) {
444
                                                $this->readEffects($children->p->pPr->defRPr, $legendText, false);
445
                                                $addLegendText = true;
446
                                            }
447
 
448
                                            break;
449
                                    }
450
                                }
451
                                $legend = new Legend("$legendPos", $legendLayout, (bool) $legendOverlay);
452
                                if ($legendFillColor !== null) {
453
                                    $legend->getFillColor()->setColorPropertiesArray($legendFillColor);
454
                                }
455
                                if ($legendBorderLines !== null) {
456
                                    $legend->setBorderLines($legendBorderLines);
457
                                }
458
                                if ($addLegendText) {
459
                                    $legend->setLegendText($legendText);
460
                                }
461
 
462
                                break;
463
                        }
464
                    }
465
            }
466
        }
467
        $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis);
468
        if ($chartNoFill) {
469
            $chart->setNoFill(true);
470
        }
471
        if ($chartFillColor !== null) {
472
            $chart->getFillColor()->setColorPropertiesArray($chartFillColor);
473
        }
474
        if ($chartBorderLines !== null) {
475
            $chart->setBorderLines($chartBorderLines);
476
        }
477
        $chart->setNoBorder($noBorder);
478
        $chart->setRoundedCorners($roundedCorners);
479
        if (is_bool($autoTitleDeleted)) {
480
            $chart->setAutoTitleDeleted($autoTitleDeleted);
481
        }
482
        if (is_int($rotX)) {
483
            $chart->setRotX($rotX);
484
        }
485
        if (is_int($rotY)) {
486
            $chart->setRotY($rotY);
487
        }
488
        if (is_int($rAngAx)) {
489
            $chart->setRAngAx($rAngAx);
490
        }
491
        if (is_int($perspective)) {
492
            $chart->setPerspective($perspective);
493
        }
494
 
495
        return $chart;
496
    }
497
 
498
    private function chartTitle(SimpleXMLElement $titleDetails): Title
499
    {
500
        $caption = '';
501
        $titleLayout = null;
502
        $titleOverlay = false;
503
        $titleFormula = null;
504
        $titleFont = null;
505
        foreach ($titleDetails as $titleDetailKey => $chartDetail) {
506
            $chartDetail = Xlsx::testSimpleXml($chartDetail);
507
            switch ($titleDetailKey) {
508
                case 'tx':
509
                    $caption = [];
510
                    if (isset($chartDetail->rich)) {
511
                        $titleDetails = $chartDetail->rich->children($this->aNamespace);
512
                        foreach ($titleDetails as $titleKey => $titleDetail) {
513
                            $titleDetail = Xlsx::testSimpleXml($titleDetail);
514
                            switch ($titleKey) {
515
                                case 'p':
516
                                    $titleDetailPart = $titleDetail->children($this->aNamespace);
517
                                    $caption[] = $this->parseRichText($titleDetailPart);
518
                            }
519
                        }
520
                    } elseif (isset($chartDetail->strRef->strCache)) {
521
                        foreach ($chartDetail->strRef->strCache->pt as $pt) {
522
                            if (isset($pt->v)) {
523
                                $caption[] = (string) $pt->v;
524
                            }
525
                        }
526
                        if (isset($chartDetail->strRef->f)) {
527
                            $titleFormula = (string) $chartDetail->strRef->f;
528
                        }
529
                    }
530
 
531
                    break;
532
                case 'overlay':
533
                    $titleOverlay = self::getAttributeBoolean($chartDetail, 'val');
534
 
535
                    break;
536
                case 'layout':
537
                    $titleLayout = $this->chartLayoutDetails($chartDetail);
538
 
539
                    break;
540
                case 'txPr':
541
                    if (isset($chartDetail->children($this->aNamespace)->p)) {
542
                        $titleFont = $this->parseFont($chartDetail->children($this->aNamespace)->p);
543
                    }
544
 
545
                    break;
546
            }
547
        }
548
        $title = new Title($caption, $titleLayout, (bool) $titleOverlay);
549
        if (!empty($titleFormula)) {
550
            $title->setCellReference($titleFormula);
551
        }
552
        if ($titleFont !== null) {
553
            $title->setFont($titleFont);
554
        }
555
 
556
        return $title;
557
    }
558
 
559
    private function chartLayoutDetails(SimpleXMLElement $chartDetail): ?Layout
560
    {
561
        if (!isset($chartDetail->manualLayout)) {
562
            return null;
563
        }
564
        $details = $chartDetail->manualLayout->children($this->cNamespace);
565
        if ($details === null) {
566
            return null;
567
        }
568
        $layout = [];
569
        foreach ($details as $detailKey => $detail) {
570
            $detail = Xlsx::testSimpleXml($detail);
571
            $layout[$detailKey] = self::getAttributeString($detail, 'val');
572
        }
573
 
574
        return new Layout($layout);
575
    }
576
 
577
    private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType): DataSeries
578
    {
579
        $multiSeriesType = null;
580
        $smoothLine = false;
581
        $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = $seriesBubbles = [];
582
        $plotDirection = null;
583
 
584
        $seriesDetailSet = $chartDetail->children($this->cNamespace);
585
        foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) {
586
            switch ($seriesDetailKey) {
587
                case 'grouping':
588
                    $multiSeriesType = self::getAttributeString($chartDetail->grouping, 'val');
589
 
590
                    break;
591
                case 'ser':
592
                    $marker = null;
593
                    $seriesIndex = '';
594
                    $fillColor = null;
595
                    $pointSize = null;
596
                    $noFill = false;
597
                    $bubble3D = false;
598
                    $dptColors = [];
599
                    $markerFillColor = null;
600
                    $markerBorderColor = null;
601
                    $lineStyle = null;
602
                    $labelLayout = null;
603
                    $trendLines = [];
604
                    foreach ($seriesDetails as $seriesKey => $seriesDetail) {
605
                        $seriesDetail = Xlsx::testSimpleXml($seriesDetail);
606
                        switch ($seriesKey) {
607
                            case 'idx':
608
                                $seriesIndex = self::getAttributeInteger($seriesDetail, 'val');
609
 
610
                                break;
611
                            case 'order':
612
                                $seriesOrder = self::getAttributeInteger($seriesDetail, 'val');
613
                                if ($seriesOrder !== null) {
614
                                    $plotOrder[$seriesIndex] = $seriesOrder;
615
                                }
616
 
617
                                break;
618
                            case 'tx':
619
                                $temp = $this->chartDataSeriesValueSet($seriesDetail);
620
                                if ($temp !== null) {
621
                                    $seriesLabel[$seriesIndex] = $temp;
622
                                }
623
 
624
                                break;
625
                            case 'spPr':
626
                                $children = $seriesDetail->children($this->aNamespace);
627
                                if (isset($children->ln)) {
628
                                    $ln = $children->ln;
629
                                    if (is_countable($ln->noFill) && count($ln->noFill) === 1) {
630
                                        $noFill = true;
631
                                    }
632
                                    $lineStyle = new GridLines();
633
                                    $this->readLineStyle($seriesDetails, $lineStyle);
634
                                }
635
                                if (isset($children->effectLst)) {
636
                                    if ($lineStyle === null) {
637
                                        $lineStyle = new GridLines();
638
                                    }
639
                                    $this->readEffects($seriesDetails, $lineStyle);
640
                                }
641
                                if (isset($children->solidFill)) {
642
                                    $fillColor = new ChartColor($this->readColor($children->solidFill));
643
                                }
644
 
645
                                break;
646
                            case 'dPt':
647
                                $dptIdx = (int) self::getAttributeString($seriesDetail->idx, 'val');
648
                                if (isset($seriesDetail->spPr)) {
649
                                    $children = $seriesDetail->spPr->children($this->aNamespace);
650
                                    if (isset($children->solidFill)) {
651
                                        $arrayColors = $this->readColor($children->solidFill);
652
                                        $dptColors[$dptIdx] = new ChartColor($arrayColors);
653
                                    }
654
                                }
655
 
656
                                break;
657
                            case 'trendline':
658
                                $trendLine = new TrendLine();
659
                                $this->readLineStyle($seriesDetail, $trendLine);
660
                                $trendLineType = self::getAttributeString($seriesDetail->trendlineType, 'val');
661
                                $dispRSqr = self::getAttributeBoolean($seriesDetail->dispRSqr, 'val');
662
                                $dispEq = self::getAttributeBoolean($seriesDetail->dispEq, 'val');
663
                                $order = self::getAttributeInteger($seriesDetail->order, 'val');
664
                                $period = self::getAttributeInteger($seriesDetail->period, 'val');
665
                                $forward = self::getAttributeFloat($seriesDetail->forward, 'val');
666
                                $backward = self::getAttributeFloat($seriesDetail->backward, 'val');
667
                                $intercept = self::getAttributeFloat($seriesDetail->intercept, 'val');
668
                                $name = (string) $seriesDetail->name;
669
                                $trendLine->setTrendLineProperties(
670
                                    $trendLineType,
671
                                    $order,
672
                                    $period,
673
                                    $dispRSqr,
674
                                    $dispEq,
675
                                    $backward,
676
                                    $forward,
677
                                    $intercept,
678
                                    $name
679
                                );
680
                                $trendLines[] = $trendLine;
681
 
682
                                break;
683
                            case 'marker':
684
                                $marker = self::getAttributeString($seriesDetail->symbol, 'val');
685
                                $pointSize = self::getAttributeString($seriesDetail->size, 'val');
686
                                $pointSize = is_numeric($pointSize) ? ((int) $pointSize) : null;
687
                                if (isset($seriesDetail->spPr)) {
688
                                    $children = $seriesDetail->spPr->children($this->aNamespace);
689
                                    if (isset($children->solidFill)) {
690
                                        $markerFillColor = $this->readColor($children->solidFill);
691
                                    }
692
                                    if (isset($children->ln->solidFill)) {
693
                                        $markerBorderColor = $this->readColor($children->ln->solidFill);
694
                                    }
695
                                }
696
 
697
                                break;
698
                            case 'smooth':
699
                                $smoothLine = self::getAttributeBoolean($seriesDetail, 'val') ?? false;
700
 
701
                                break;
702
                            case 'cat':
703
                                $temp = $this->chartDataSeriesValueSet($seriesDetail);
704
                                if ($temp !== null) {
705
                                    $seriesCategory[$seriesIndex] = $temp;
706
                                }
707
 
708
                                break;
709
                            case 'val':
710
                                $temp = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
711
                                if ($temp !== null) {
712
                                    $seriesValues[$seriesIndex] = $temp;
713
                                }
714
 
715
                                break;
716
                            case 'xVal':
717
                                $temp = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
718
                                if ($temp !== null) {
719
                                    $seriesCategory[$seriesIndex] = $temp;
720
                                }
721
 
722
                                break;
723
                            case 'yVal':
724
                                $temp = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
725
                                if ($temp !== null) {
726
                                    $seriesValues[$seriesIndex] = $temp;
727
                                }
728
 
729
                                break;
730
                            case 'bubbleSize':
731
                                $seriesBubble = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
732
                                if ($seriesBubble !== null) {
733
                                    $seriesBubbles[$seriesIndex] = $seriesBubble;
734
                                }
735
 
736
                                break;
737
                            case 'bubble3D':
738
                                $bubble3D = self::getAttributeBoolean($seriesDetail, 'val');
739
 
740
                                break;
741
                            case 'dLbls':
742
                                $labelLayout = new Layout($this->readChartAttributes($seriesDetails));
743
 
744
                                break;
745
                        }
746
                    }
747
                    if ($labelLayout) {
748
                        if (isset($seriesLabel[$seriesIndex])) {
749
                            $seriesLabel[$seriesIndex]->setLabelLayout($labelLayout);
750
                        }
751
                        if (isset($seriesCategory[$seriesIndex])) {
752
                            $seriesCategory[$seriesIndex]->setLabelLayout($labelLayout);
753
                        }
754
                        if (isset($seriesValues[$seriesIndex])) {
755
                            $seriesValues[$seriesIndex]->setLabelLayout($labelLayout);
756
                        }
757
                    }
758
                    if ($noFill) {
759
                        if (isset($seriesLabel[$seriesIndex])) {
760
                            $seriesLabel[$seriesIndex]->setScatterLines(false);
761
                        }
762
                        if (isset($seriesCategory[$seriesIndex])) {
763
                            $seriesCategory[$seriesIndex]->setScatterLines(false);
764
                        }
765
                        if (isset($seriesValues[$seriesIndex])) {
766
                            $seriesValues[$seriesIndex]->setScatterLines(false);
767
                        }
768
                    }
769
                    if ($lineStyle !== null) {
770
                        if (isset($seriesLabel[$seriesIndex])) {
771
                            $seriesLabel[$seriesIndex]->copyLineStyles($lineStyle);
772
                        }
773
                        if (isset($seriesCategory[$seriesIndex])) {
774
                            $seriesCategory[$seriesIndex]->copyLineStyles($lineStyle);
775
                        }
776
                        if (isset($seriesValues[$seriesIndex])) {
777
                            $seriesValues[$seriesIndex]->copyLineStyles($lineStyle);
778
                        }
779
                    }
780
                    if ($bubble3D) {
781
                        if (isset($seriesLabel[$seriesIndex])) {
782
                            $seriesLabel[$seriesIndex]->setBubble3D($bubble3D);
783
                        }
784
                        if (isset($seriesCategory[$seriesIndex])) {
785
                            $seriesCategory[$seriesIndex]->setBubble3D($bubble3D);
786
                        }
787
                        if (isset($seriesValues[$seriesIndex])) {
788
                            $seriesValues[$seriesIndex]->setBubble3D($bubble3D);
789
                        }
790
                    }
791
                    if (!empty($dptColors)) {
792
                        if (isset($seriesLabel[$seriesIndex])) {
793
                            $seriesLabel[$seriesIndex]->setFillColor($dptColors);
794
                        }
795
                        if (isset($seriesCategory[$seriesIndex])) {
796
                            $seriesCategory[$seriesIndex]->setFillColor($dptColors);
797
                        }
798
                        if (isset($seriesValues[$seriesIndex])) {
799
                            $seriesValues[$seriesIndex]->setFillColor($dptColors);
800
                        }
801
                    }
802
                    if ($markerFillColor !== null) {
803
                        if (isset($seriesLabel[$seriesIndex])) {
804
                            $seriesLabel[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
805
                        }
806
                        if (isset($seriesCategory[$seriesIndex])) {
807
                            $seriesCategory[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
808
                        }
809
                        if (isset($seriesValues[$seriesIndex])) {
810
                            $seriesValues[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
811
                        }
812
                    }
813
                    if ($markerBorderColor !== null) {
814
                        if (isset($seriesLabel[$seriesIndex])) {
815
                            $seriesLabel[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
816
                        }
817
                        if (isset($seriesCategory[$seriesIndex])) {
818
                            $seriesCategory[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
819
                        }
820
                        if (isset($seriesValues[$seriesIndex])) {
821
                            $seriesValues[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
822
                        }
823
                    }
824
                    if ($smoothLine) {
825
                        if (isset($seriesLabel[$seriesIndex])) {
826
                            $seriesLabel[$seriesIndex]->setSmoothLine(true);
827
                        }
828
                        if (isset($seriesCategory[$seriesIndex])) {
829
                            $seriesCategory[$seriesIndex]->setSmoothLine(true);
830
                        }
831
                        if (isset($seriesValues[$seriesIndex])) {
832
                            $seriesValues[$seriesIndex]->setSmoothLine(true);
833
                        }
834
                    }
835
                    if (!empty($trendLines)) {
836
                        if (isset($seriesLabel[$seriesIndex])) {
837
                            $seriesLabel[$seriesIndex]->setTrendLines($trendLines);
838
                        }
839
                        if (isset($seriesCategory[$seriesIndex])) {
840
                            $seriesCategory[$seriesIndex]->setTrendLines($trendLines);
841
                        }
842
                        if (isset($seriesValues[$seriesIndex])) {
843
                            $seriesValues[$seriesIndex]->setTrendLines($trendLines);
844
                        }
845
                    }
846
            }
847
        }
848
        $series = new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $plotDirection, $smoothLine);
849
        $series->setPlotBubbleSizes($seriesBubbles);
850
 
851
        return $series;
852
    }
853
 
854
    private function chartDataSeriesValueSet(SimpleXMLElement $seriesDetail, ?string $marker = null, ?ChartColor $fillColor = null, ?string $pointSize = null): ?DataSeriesValues
855
    {
856
        if (isset($seriesDetail->strRef)) {
857
            $seriesSource = (string) $seriesDetail->strRef->f;
858
            $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
859
 
860
            if (isset($seriesDetail->strRef->strCache)) {
861
                $seriesData = $this->chartDataSeriesValues($seriesDetail->strRef->strCache->children($this->cNamespace), 's');
862
                $seriesValues
863
                    ->setFormatCode($seriesData['formatCode'])
864
                    ->setDataValues($seriesData['dataValues']);
865
            }
866
 
867
            return $seriesValues;
868
        } elseif (isset($seriesDetail->numRef)) {
869
            $seriesSource = (string) $seriesDetail->numRef->f;
870
            $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
871
            if (isset($seriesDetail->numRef->numCache)) {
872
                $seriesData = $this->chartDataSeriesValues($seriesDetail->numRef->numCache->children($this->cNamespace));
873
                $seriesValues
874
                    ->setFormatCode($seriesData['formatCode'])
875
                    ->setDataValues($seriesData['dataValues']);
876
            }
877
 
878
            return $seriesValues;
879
        } elseif (isset($seriesDetail->multiLvlStrRef)) {
880
            $seriesSource = (string) $seriesDetail->multiLvlStrRef->f;
881
            $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
882
 
883
            if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) {
884
                $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($this->cNamespace), 's');
885
                $seriesValues
886
                    ->setFormatCode($seriesData['formatCode'])
887
                    ->setDataValues($seriesData['dataValues']);
888
            }
889
 
890
            return $seriesValues;
891
        } elseif (isset($seriesDetail->multiLvlNumRef)) {
892
            $seriesSource = (string) $seriesDetail->multiLvlNumRef->f;
893
            $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
894
 
895
            if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) {
896
                $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($this->cNamespace), 's');
897
                $seriesValues
898
                    ->setFormatCode($seriesData['formatCode'])
899
                    ->setDataValues($seriesData['dataValues']);
900
            }
901
 
902
            return $seriesValues;
903
        }
904
 
905
        if (isset($seriesDetail->v)) {
906
            return new DataSeriesValues(
907
                DataSeriesValues::DATASERIES_TYPE_STRING,
908
                null,
909
                null,
910
                1,
911
                [(string) $seriesDetail->v]
912
            );
913
        }
914
 
915
        return null;
916
    }
917
 
918
    private function chartDataSeriesValues(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array
919
    {
920
        $seriesVal = [];
921
        $formatCode = '';
922
        $pointCount = 0;
923
 
924
        foreach ($seriesValueSet as $seriesValueIdx => $seriesValue) {
925
            $seriesValue = Xlsx::testSimpleXml($seriesValue);
926
            switch ($seriesValueIdx) {
927
                case 'ptCount':
928
                    $pointCount = self::getAttributeInteger($seriesValue, 'val');
929
 
930
                    break;
931
                case 'formatCode':
932
                    $formatCode = (string) $seriesValue;
933
 
934
                    break;
935
                case 'pt':
936
                    $pointVal = self::getAttributeInteger($seriesValue, 'idx');
937
                    if ($dataType == 's') {
938
                        $seriesVal[$pointVal] = (string) $seriesValue->v;
939
                    } elseif ((string) $seriesValue->v === ExcelError::NA()) {
940
                        $seriesVal[$pointVal] = null;
941
                    } else {
942
                        $seriesVal[$pointVal] = (float) $seriesValue->v;
943
                    }
944
 
945
                    break;
946
            }
947
        }
948
 
949
        return [
950
            'formatCode' => $formatCode,
951
            'pointCount' => $pointCount,
952
            'dataValues' => $seriesVal,
953
        ];
954
    }
955
 
956
    private function chartDataSeriesValuesMultiLevel(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array
957
    {
958
        $seriesVal = [];
959
        $formatCode = '';
960
        $pointCount = 0;
961
 
962
        foreach ($seriesValueSet->lvl as $seriesLevelIdx => $seriesLevel) {
963
            foreach ($seriesLevel as $seriesValueIdx => $seriesValue) {
964
                $seriesValue = Xlsx::testSimpleXml($seriesValue);
965
                switch ($seriesValueIdx) {
966
                    case 'ptCount':
967
                        $pointCount = self::getAttributeInteger($seriesValue, 'val');
968
 
969
                        break;
970
                    case 'formatCode':
971
                        $formatCode = (string) $seriesValue;
972
 
973
                        break;
974
                    case 'pt':
975
                        $pointVal = self::getAttributeInteger($seriesValue, 'idx');
976
                        if ($dataType == 's') {
977
                            $seriesVal[$pointVal][] = (string) $seriesValue->v;
978
                        } elseif ((string) $seriesValue->v === ExcelError::NA()) {
979
                            $seriesVal[$pointVal] = null;
980
                        } else {
981
                            $seriesVal[$pointVal][] = (float) $seriesValue->v;
982
                        }
983
 
984
                        break;
985
                }
986
            }
987
        }
988
 
989
        return [
990
            'formatCode' => $formatCode,
991
            'pointCount' => $pointCount,
992
            'dataValues' => $seriesVal,
993
        ];
994
    }
995
 
996
    private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
997
    {
998
        $value = new RichText();
999
        $defaultFontSize = null;
1000
        $defaultBold = null;
1001
        $defaultItalic = null;
1002
        $defaultUnderscore = null;
1003
        $defaultStrikethrough = null;
1004
        $defaultBaseline = null;
1005
        $defaultFontName = null;
1006
        $defaultLatin = null;
1007
        $defaultEastAsian = null;
1008
        $defaultComplexScript = null;
1009
        $defaultFontColor = null;
1010
        if (isset($titleDetailPart->pPr->defRPr)) {
1011
            $defaultFontSize = self::getAttributeInteger($titleDetailPart->pPr->defRPr, 'sz');
1012
            $defaultBold = self::getAttributeBoolean($titleDetailPart->pPr->defRPr, 'b');
1013
            $defaultItalic = self::getAttributeBoolean($titleDetailPart->pPr->defRPr, 'i');
1014
            $defaultUnderscore = self::getAttributeString($titleDetailPart->pPr->defRPr, 'u');
1015
            $defaultStrikethrough = self::getAttributeString($titleDetailPart->pPr->defRPr, 'strike');
1016
            $defaultBaseline = self::getAttributeInteger($titleDetailPart->pPr->defRPr, 'baseline');
1017
            if (isset($titleDetailPart->defRPr->rFont['val'])) {
1018
                $defaultFontName = (string) $titleDetailPart->defRPr->rFont['val'];
1019
            }
1020
            if (isset($titleDetailPart->pPr->defRPr->latin)) {
1021
                $defaultLatin = self::getAttributeString($titleDetailPart->pPr->defRPr->latin, 'typeface');
1022
            }
1023
            if (isset($titleDetailPart->pPr->defRPr->ea)) {
1024
                $defaultEastAsian = self::getAttributeString($titleDetailPart->pPr->defRPr->ea, 'typeface');
1025
            }
1026
            if (isset($titleDetailPart->pPr->defRPr->cs)) {
1027
                $defaultComplexScript = self::getAttributeString($titleDetailPart->pPr->defRPr->cs, 'typeface');
1028
            }
1029
            if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
1030
                $defaultFontColor = $this->readColor($titleDetailPart->pPr->defRPr->solidFill);
1031
            }
1032
        }
1033
        foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) {
1034
            if (
1035
                (string) $titleDetailElementKey !== 'r'
1036
                || !isset($titleDetailElement->t)
1037
            ) {
1038
                continue;
1039
            }
1040
            $objText = $value->createTextRun((string) $titleDetailElement->t);
1041
            if ($objText->getFont() === null) {
1042
                // @codeCoverageIgnoreStart
1043
                continue;
1044
                // @codeCoverageIgnoreEnd
1045
            }
1046
            $fontSize = null;
1047
            $bold = null;
1048
            $italic = null;
1049
            $underscore = null;
1050
            $strikethrough = null;
1051
            $baseline = null;
1052
            $fontName = null;
1053
            $latinName = null;
1054
            $eastAsian = null;
1055
            $complexScript = null;
1056
            $fontColor = null;
1057
            $underlineColor = null;
1058
            if (isset($titleDetailElement->rPr)) {
1059
                // not used now, not sure it ever was, grandfathering
1060
                if (isset($titleDetailElement->rPr->rFont['val'])) {
1061
                    // @codeCoverageIgnoreStart
1062
                    $fontName = (string) $titleDetailElement->rPr->rFont['val'];
1063
                    // @codeCoverageIgnoreEnd
1064
                }
1065
                if (isset($titleDetailElement->rPr->latin)) {
1066
                    $latinName = self::getAttributeString($titleDetailElement->rPr->latin, 'typeface');
1067
                }
1068
                if (isset($titleDetailElement->rPr->ea)) {
1069
                    $eastAsian = self::getAttributeString($titleDetailElement->rPr->ea, 'typeface');
1070
                }
1071
                if (isset($titleDetailElement->rPr->cs)) {
1072
                    $complexScript = self::getAttributeString($titleDetailElement->rPr->cs, 'typeface');
1073
                }
1074
                $fontSize = self::getAttributeInteger($titleDetailElement->rPr, 'sz');
1075
 
1076
                // not used now, not sure it ever was, grandfathering
1077
                if (isset($titleDetailElement->rPr->solidFill)) {
1078
                    $fontColor = $this->readColor($titleDetailElement->rPr->solidFill);
1079
                }
1080
 
1081
                $bold = self::getAttributeBoolean($titleDetailElement->rPr, 'b');
1082
                $italic = self::getAttributeBoolean($titleDetailElement->rPr, 'i');
1083
                $baseline = self::getAttributeInteger($titleDetailElement->rPr, 'baseline');
1084
                $underscore = self::getAttributeString($titleDetailElement->rPr, 'u');
1085
                if (isset($titleDetailElement->rPr->uFill->solidFill)) {
1086
                    $underlineColor = $this->readColor($titleDetailElement->rPr->uFill->solidFill);
1087
                }
1088
 
1089
                $strikethrough = self::getAttributeString($titleDetailElement->rPr, 'strike');
1090
            }
1091
 
1092
            $fontFound = false;
1093
            $latinName = $latinName ?? $defaultLatin;
1094
            if ($latinName !== null) {
1095
                $objText->getFont()->setLatin($latinName);
1096
                $fontFound = true;
1097
            }
1098
            $eastAsian = $eastAsian ?? $defaultEastAsian;
1099
            if ($eastAsian !== null) {
1100
                $objText->getFont()->setEastAsian($eastAsian);
1101
                $fontFound = true;
1102
            }
1103
            $complexScript = $complexScript ?? $defaultComplexScript;
1104
            if ($complexScript !== null) {
1105
                $objText->getFont()->setComplexScript($complexScript);
1106
                $fontFound = true;
1107
            }
1108
            $fontName = $fontName ?? $defaultFontName;
1109
            if ($fontName !== null) {
1110
                // @codeCoverageIgnoreStart
1111
                $objText->getFont()->setName($fontName);
1112
                $fontFound = true;
1113
                // @codeCoverageIgnoreEnd
1114
            }
1115
 
1116
            $fontSize = $fontSize ?? $defaultFontSize;
1117
            if (is_int($fontSize)) {
1118
                $objText->getFont()->setSize(floor($fontSize / 100));
1119
                $fontFound = true;
1120
            } else {
1121
                $objText->getFont()->setSize(null, true);
1122
            }
1123
 
1124
            $fontColor = $fontColor ?? $defaultFontColor;
1125
            if (!empty($fontColor)) {
1126
                $objText->getFont()->setChartColor($fontColor);
1127
                $fontFound = true;
1128
            }
1129
 
1130
            $bold = $bold ?? $defaultBold;
1131
            if ($bold !== null) {
1132
                $objText->getFont()->setBold($bold);
1133
                $fontFound = true;
1134
            }
1135
 
1136
            $italic = $italic ?? $defaultItalic;
1137
            if ($italic !== null) {
1138
                $objText->getFont()->setItalic($italic);
1139
                $fontFound = true;
1140
            }
1141
 
1142
            $baseline = $baseline ?? $defaultBaseline;
1143
            if ($baseline !== null) {
1144
                $objText->getFont()->setBaseLine($baseline);
1145
                if ($baseline > 0) {
1146
                    $objText->getFont()->setSuperscript(true);
1147
                } elseif ($baseline < 0) {
1148
                    $objText->getFont()->setSubscript(true);
1149
                }
1150
                $fontFound = true;
1151
            }
1152
 
1153
            $underscore = $underscore ?? $defaultUnderscore;
1154
            if ($underscore !== null) {
1155
                if ($underscore == 'sng') {
1156
                    $objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
1157
                } elseif ($underscore == 'dbl') {
1158
                    $objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE);
1159
                } elseif ($underscore !== '') {
1160
                    $objText->getFont()->setUnderline($underscore);
1161
                } else {
1162
                    $objText->getFont()->setUnderline(Font::UNDERLINE_NONE);
1163
                }
1164
                $fontFound = true;
1165
                if ($underlineColor) {
1166
                    $objText->getFont()->setUnderlineColor($underlineColor);
1167
                }
1168
            }
1169
 
1170
            $strikethrough = $strikethrough ?? $defaultStrikethrough;
1171
            if ($strikethrough !== null) {
1172
                $objText->getFont()->setStrikeType($strikethrough);
1173
                if ($strikethrough == 'noStrike') {
1174
                    $objText->getFont()->setStrikethrough(false);
1175
                } else {
1176
                    $objText->getFont()->setStrikethrough(true);
1177
                }
1178
                $fontFound = true;
1179
            }
1180
            if ($fontFound === false) {
1181
                $objText->setFont(null);
1182
            }
1183
        }
1184
 
1185
        return $value;
1186
    }
1187
 
1188
    private function parseFont(SimpleXMLElement $titleDetailPart): ?Font
1189
    {
1190
        if (!isset($titleDetailPart->pPr->defRPr)) {
1191
            return null;
1192
        }
1193
        $fontArray = [];
1194
        $fontArray['size'] = self::getAttributeInteger($titleDetailPart->pPr->defRPr, 'sz');
1195
        if ($fontArray['size'] !== null && $fontArray['size'] >= 100) {
1196
            $fontArray['size'] /= 100.0;
1197
        }
1198
        $fontArray['bold'] = self::getAttributeBoolean($titleDetailPart->pPr->defRPr, 'b');
1199
        $fontArray['italic'] = self::getAttributeBoolean($titleDetailPart->pPr->defRPr, 'i');
1200
        $fontArray['underscore'] = self::getAttributeString($titleDetailPart->pPr->defRPr, 'u');
1201
        $fontArray['strikethrough'] = self::getAttributeString($titleDetailPart->pPr->defRPr, 'strike');
1202
        $fontArray['cap'] = self::getAttributeString($titleDetailPart->pPr->defRPr, 'cap');
1203
 
1204
        if (isset($titleDetailPart->pPr->defRPr->latin)) {
1205
            $fontArray['latin'] = self::getAttributeString($titleDetailPart->pPr->defRPr->latin, 'typeface');
1206
        }
1207
        if (isset($titleDetailPart->pPr->defRPr->ea)) {
1208
            $fontArray['eastAsian'] = self::getAttributeString($titleDetailPart->pPr->defRPr->ea, 'typeface');
1209
        }
1210
        if (isset($titleDetailPart->pPr->defRPr->cs)) {
1211
            $fontArray['complexScript'] = self::getAttributeString($titleDetailPart->pPr->defRPr->cs, 'typeface');
1212
        }
1213
        if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
1214
            $fontArray['chartColor'] = new ChartColor($this->readColor($titleDetailPart->pPr->defRPr->solidFill));
1215
        }
1216
        $font = new Font();
1217
        $font->setSize(null, true);
1218
        $font->applyFromArray($fontArray);
1219
 
1220
        return $font;
1221
    }
1222
 
1223
    private function readChartAttributes(?SimpleXMLElement $chartDetail): array
1224
    {
1225
        $plotAttributes = [];
1226
        if (isset($chartDetail->dLbls)) {
1227
            if (isset($chartDetail->dLbls->dLblPos)) {
1228
                $plotAttributes['dLblPos'] = self::getAttributeString($chartDetail->dLbls->dLblPos, 'val');
1229
            }
1230
            if (isset($chartDetail->dLbls->numFmt)) {
1231
                $plotAttributes['numFmtCode'] = self::getAttributeString($chartDetail->dLbls->numFmt, 'formatCode');
1232
                $plotAttributes['numFmtLinked'] = self::getAttributeBoolean($chartDetail->dLbls->numFmt, 'sourceLinked');
1233
            }
1234
            if (isset($chartDetail->dLbls->showLegendKey)) {
1235
                $plotAttributes['showLegendKey'] = self::getAttributeString($chartDetail->dLbls->showLegendKey, 'val');
1236
            }
1237
            if (isset($chartDetail->dLbls->showVal)) {
1238
                $plotAttributes['showVal'] = self::getAttributeString($chartDetail->dLbls->showVal, 'val');
1239
            }
1240
            if (isset($chartDetail->dLbls->showCatName)) {
1241
                $plotAttributes['showCatName'] = self::getAttributeString($chartDetail->dLbls->showCatName, 'val');
1242
            }
1243
            if (isset($chartDetail->dLbls->showSerName)) {
1244
                $plotAttributes['showSerName'] = self::getAttributeString($chartDetail->dLbls->showSerName, 'val');
1245
            }
1246
            if (isset($chartDetail->dLbls->showPercent)) {
1247
                $plotAttributes['showPercent'] = self::getAttributeString($chartDetail->dLbls->showPercent, 'val');
1248
            }
1249
            if (isset($chartDetail->dLbls->showBubbleSize)) {
1250
                $plotAttributes['showBubbleSize'] = self::getAttributeString($chartDetail->dLbls->showBubbleSize, 'val');
1251
            }
1252
            if (isset($chartDetail->dLbls->showLeaderLines)) {
1253
                $plotAttributes['showLeaderLines'] = self::getAttributeString($chartDetail->dLbls->showLeaderLines, 'val');
1254
            }
1255
            if (isset($chartDetail->dLbls->spPr)) {
1256
                $sppr = $chartDetail->dLbls->spPr->children($this->aNamespace);
1257
                if (isset($sppr->solidFill)) {
1258
                    $plotAttributes['labelFillColor'] = new ChartColor($this->readColor($sppr->solidFill));
1259
                }
1260
                if (isset($sppr->ln->solidFill)) {
1261
                    $plotAttributes['labelBorderColor'] = new ChartColor($this->readColor($sppr->ln->solidFill));
1262
                }
1263
            }
1264
            if (isset($chartDetail->dLbls->txPr)) {
1265
                $txpr = $chartDetail->dLbls->txPr->children($this->aNamespace);
1266
                if (isset($txpr->p)) {
1267
                    $plotAttributes['labelFont'] = $this->parseFont($txpr->p);
1268
                    if (isset($txpr->p->pPr->defRPr->effectLst)) {
1269
                        $labelEffects = new GridLines();
1270
                        $this->readEffects($txpr->p->pPr->defRPr, $labelEffects, false);
1271
                        $plotAttributes['labelEffects'] = $labelEffects;
1272
                    }
1273
                }
1274
            }
1275
        }
1276
 
1277
        return $plotAttributes;
1278
    }
1279
 
1280
    private function setChartAttributes(Layout $plotArea, array $plotAttributes): void
1281
    {
1282
        foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) {
1283
            switch ($plotAttributeKey) {
1284
                case 'showLegendKey':
1285
                    $plotArea->setShowLegendKey($plotAttributeValue);
1286
 
1287
                    break;
1288
                case 'showVal':
1289
                    $plotArea->setShowVal($plotAttributeValue);
1290
 
1291
                    break;
1292
                case 'showCatName':
1293
                    $plotArea->setShowCatName($plotAttributeValue);
1294
 
1295
                    break;
1296
                case 'showSerName':
1297
                    $plotArea->setShowSerName($plotAttributeValue);
1298
 
1299
                    break;
1300
                case 'showPercent':
1301
                    $plotArea->setShowPercent($plotAttributeValue);
1302
 
1303
                    break;
1304
                case 'showBubbleSize':
1305
                    $plotArea->setShowBubbleSize($plotAttributeValue);
1306
 
1307
                    break;
1308
                case 'showLeaderLines':
1309
                    $plotArea->setShowLeaderLines($plotAttributeValue);
1310
 
1311
                    break;
1312
                case 'labelFont':
1313
                    $plotArea->setLabelFont($plotAttributeValue);
1314
 
1315
                    break;
1316
            }
1317
        }
1318
    }
1319
 
1320
    private function readEffects(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject, bool $getSppr = true): void
1321
    {
1322
        if (!isset($chartObject)) {
1323
            return;
1324
        }
1325
        if ($getSppr) {
1326
            if (!isset($chartDetail->spPr)) {
1327
                return;
1328
            }
1329
            $sppr = $chartDetail->spPr->children($this->aNamespace);
1330
        } else {
1331
            $sppr = $chartDetail;
1332
        }
1333
        if (isset($sppr->effectLst->glow)) {
1334
            $axisGlowSize = (float) self::getAttributeInteger($sppr->effectLst->glow, 'rad') / ChartProperties::POINTS_WIDTH_MULTIPLIER;
1335
            if ($axisGlowSize != 0.0) {
1336
                $colorArray = $this->readColor($sppr->effectLst->glow);
1337
                $chartObject->setGlowProperties($axisGlowSize, $colorArray['value'], $colorArray['alpha'], $colorArray['type']);
1338
            }
1339
        }
1340
 
1341
        if (isset($sppr->effectLst->softEdge)) {
1342
            $softEdgeSize = self::getAttributeString($sppr->effectLst->softEdge, 'rad');
1343
            if (is_numeric($softEdgeSize)) {
1344
                $chartObject->setSoftEdges((float) ChartProperties::xmlToPoints($softEdgeSize));
1345
            }
1346
        }
1347
 
1348
        $type = '';
1349
        foreach (self::SHADOW_TYPES as $shadowType) {
1350
            if (isset($sppr->effectLst->$shadowType)) {
1351
                $type = $shadowType;
1352
 
1353
                break;
1354
            }
1355
        }
1356
        if ($type !== '') {
1357
            $blur = self::getAttributeString($sppr->effectLst->$type, 'blurRad');
1358
            $blur = is_numeric($blur) ? ChartProperties::xmlToPoints($blur) : null;
1359
            $dist = self::getAttributeString($sppr->effectLst->$type, 'dist');
1360
            $dist = is_numeric($dist) ? ChartProperties::xmlToPoints($dist) : null;
1361
            $direction = self::getAttributeString($sppr->effectLst->$type, 'dir');
1362
            $direction = is_numeric($direction) ? ChartProperties::xmlToAngle($direction) : null;
1363
            $algn = self::getAttributeString($sppr->effectLst->$type, 'algn');
1364
            $rot = self::getAttributeString($sppr->effectLst->$type, 'rotWithShape');
1365
            $size = [];
1366
            foreach (['sx', 'sy'] as $sizeType) {
1367
                $sizeValue = self::getAttributeString($sppr->effectLst->$type, $sizeType);
1368
                if (is_numeric($sizeValue)) {
1369
                    $size[$sizeType] = ChartProperties::xmlToTenthOfPercent((string) $sizeValue);
1370
                } else {
1371
                    $size[$sizeType] = null;
1372
                }
1373
            }
1374
            foreach (['kx', 'ky'] as $sizeType) {
1375
                $sizeValue = self::getAttributeString($sppr->effectLst->$type, $sizeType);
1376
                if (is_numeric($sizeValue)) {
1377
                    $size[$sizeType] = ChartProperties::xmlToAngle((string) $sizeValue);
1378
                } else {
1379
                    $size[$sizeType] = null;
1380
                }
1381
            }
1382
            $colorArray = $this->readColor($sppr->effectLst->$type);
1383
            $chartObject
1384
                ->setShadowProperty('effect', $type)
1385
                ->setShadowProperty('blur', $blur)
1386
                ->setShadowProperty('direction', $direction)
1387
                ->setShadowProperty('distance', $dist)
1388
                ->setShadowProperty('algn', $algn)
1389
                ->setShadowProperty('rotWithShape', $rot)
1390
                ->setShadowProperty('size', $size)
1391
                ->setShadowProperty('color', $colorArray);
1392
        }
1393
    }
1394
 
1395
    private const SHADOW_TYPES = [
1396
        'outerShdw',
1397
        'innerShdw',
1398
    ];
1399
 
1400
    private function readColor(SimpleXMLElement $colorXml): array
1401
    {
1402
        $result = [
1403
            'type' => null,
1404
            'value' => null,
1405
            'alpha' => null,
1406
            'brightness' => null,
1407
        ];
1408
        foreach (ChartColor::EXCEL_COLOR_TYPES as $type) {
1409
            if (isset($colorXml->$type)) {
1410
                $result['type'] = $type;
1411
                $result['value'] = self::getAttributeString($colorXml->$type, 'val');
1412
                if (isset($colorXml->$type->alpha)) {
1413
                    $alpha = self::getAttributeString($colorXml->$type->alpha, 'val');
1414
                    if (is_numeric($alpha)) {
1415
                        $result['alpha'] = ChartColor::alphaFromXml($alpha);
1416
                    }
1417
                }
1418
                if (isset($colorXml->$type->lumMod)) {
1419
                    $brightness = self::getAttributeString($colorXml->$type->lumMod, 'val');
1420
                    if (is_numeric($brightness)) {
1421
                        $result['brightness'] = ChartColor::alphaFromXml($brightness);
1422
                    }
1423
                }
1424
 
1425
                break;
1426
            }
1427
        }
1428
 
1429
        return $result;
1430
    }
1431
 
1432
    private function readLineStyle(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void
1433
    {
1434
        if (!isset($chartObject, $chartDetail->spPr)) {
1435
            return;
1436
        }
1437
        $sppr = $chartDetail->spPr->children($this->aNamespace);
1438
 
1439
        if (!isset($sppr->ln)) {
1440
            return;
1441
        }
1442
        $lineWidth = null;
1443
        $lineWidthTemp = self::getAttributeString($sppr->ln, 'w');
1444
        if (is_numeric($lineWidthTemp)) {
1445
            $lineWidth = ChartProperties::xmlToPoints($lineWidthTemp);
1446
        }
1447
        /** @var string $compoundType */
1448
        $compoundType = self::getAttributeString($sppr->ln, 'cmpd');
1449
        /** @var string $dashType */
1450
        $dashType = self::getAttributeString($sppr->ln->prstDash, 'val');
1451
        /** @var string $capType */
1452
        $capType = self::getAttributeString($sppr->ln, 'cap');
1453
        if (isset($sppr->ln->miter)) {
1454
            $joinType = ChartProperties::LINE_STYLE_JOIN_MITER;
1455
        } elseif (isset($sppr->ln->bevel)) {
1456
            $joinType = ChartProperties::LINE_STYLE_JOIN_BEVEL;
1457
        } else {
1458
            $joinType = '';
1459
        }
1460
        $headArrowSize = 0;
1461
        $endArrowSize = 0;
1462
        $headArrowType = self::getAttributeString($sppr->ln->headEnd, 'type');
1463
        $headArrowWidth = self::getAttributeString($sppr->ln->headEnd, 'w');
1464
        $headArrowLength = self::getAttributeString($sppr->ln->headEnd, 'len');
1465
        $endArrowType = self::getAttributeString($sppr->ln->tailEnd, 'type');
1466
        $endArrowWidth = self::getAttributeString($sppr->ln->tailEnd, 'w');
1467
        $endArrowLength = self::getAttributeString($sppr->ln->tailEnd, 'len');
1468
        $chartObject->setLineStyleProperties(
1469
            $lineWidth,
1470
            $compoundType,
1471
            $dashType,
1472
            $capType,
1473
            $joinType,
1474
            $headArrowType,
1475
            $headArrowSize,
1476
            $endArrowType,
1477
            $endArrowSize,
1478
            $headArrowWidth,
1479
            $headArrowLength,
1480
            $endArrowWidth,
1481
            $endArrowLength
1482
        );
1483
        $colorArray = $this->readColor($sppr->ln->solidFill);
1484
        $chartObject->getLineColor()->setColorPropertiesArray($colorArray);
1485
    }
1486
 
1487
    private function setAxisProperties(SimpleXMLElement $chartDetail, ?Axis $whichAxis): void
1488
    {
1489
        if (!isset($whichAxis)) {
1490
            return;
1491
        }
1492
        if (isset($chartDetail->delete)) {
1493
            $whichAxis->setAxisOption('hidden', (string) self::getAttributeString($chartDetail->delete, 'val'));
1494
        }
1495
        if (isset($chartDetail->numFmt)) {
1496
            $whichAxis->setAxisNumberProperties(
1497
                (string) self::getAttributeString($chartDetail->numFmt, 'formatCode'),
1498
                null,
1499
                (int) self::getAttributeInteger($chartDetail->numFmt, 'sourceLinked')
1500
            );
1501
        }
1502
        if (isset($chartDetail->crossBetween)) {
1503
            $whichAxis->setCrossBetween((string) self::getAttributeString($chartDetail->crossBetween, 'val'));
1504
        }
1505
        if (isset($chartDetail->dispUnits, $chartDetail->dispUnits->builtInUnit)) {
1506
            $whichAxis->setAxisOption('dispUnitsBuiltIn', (string) self::getAttributeString($chartDetail->dispUnits->builtInUnit, 'val'));
1507
            if (isset($chartDetail->dispUnits->dispUnitsLbl)) {
1508
                $whichAxis->setDispUnitsTitle(new Title());
1509
                // TODO parse title elements
1510
            }
1511
        }
1512
        if (isset($chartDetail->majorTickMark)) {
1513
            $whichAxis->setAxisOption('major_tick_mark', (string) self::getAttributeString($chartDetail->majorTickMark, 'val'));
1514
        }
1515
        if (isset($chartDetail->minorTickMark)) {
1516
            $whichAxis->setAxisOption('minor_tick_mark', (string) self::getAttributeString($chartDetail->minorTickMark, 'val'));
1517
        }
1518
        if (isset($chartDetail->tickLblPos)) {
1519
            $whichAxis->setAxisOption('axis_labels', (string) self::getAttributeString($chartDetail->tickLblPos, 'val'));
1520
        }
1521
        if (isset($chartDetail->crosses)) {
1522
            $whichAxis->setAxisOption('horizontal_crosses', (string) self::getAttributeString($chartDetail->crosses, 'val'));
1523
        }
1524
        if (isset($chartDetail->crossesAt)) {
1525
            $whichAxis->setAxisOption('horizontal_crosses_value', (string) self::getAttributeString($chartDetail->crossesAt, 'val'));
1526
        }
1527
        if (isset($chartDetail->scaling->logBase)) {
1528
            $whichAxis->setAxisOption('logBase', (string) self::getAttributeString($chartDetail->scaling->logBase, 'val'));
1529
        }
1530
        if (isset($chartDetail->scaling->orientation)) {
1531
            $whichAxis->setAxisOption('orientation', (string) self::getAttributeString($chartDetail->scaling->orientation, 'val'));
1532
        }
1533
        if (isset($chartDetail->scaling->max)) {
1534
            $whichAxis->setAxisOption('maximum', (string) self::getAttributeString($chartDetail->scaling->max, 'val'));
1535
        }
1536
        if (isset($chartDetail->scaling->min)) {
1537
            $whichAxis->setAxisOption('minimum', (string) self::getAttributeString($chartDetail->scaling->min, 'val'));
1538
        }
1539
        if (isset($chartDetail->scaling->min)) {
1540
            $whichAxis->setAxisOption('minimum', (string) self::getAttributeString($chartDetail->scaling->min, 'val'));
1541
        }
1542
        if (isset($chartDetail->majorUnit)) {
1543
            $whichAxis->setAxisOption('major_unit', (string) self::getAttributeString($chartDetail->majorUnit, 'val'));
1544
        }
1545
        if (isset($chartDetail->minorUnit)) {
1546
            $whichAxis->setAxisOption('minor_unit', (string) self::getAttributeString($chartDetail->minorUnit, 'val'));
1547
        }
1548
        if (isset($chartDetail->baseTimeUnit)) {
1549
            $whichAxis->setAxisOption('baseTimeUnit', (string) self::getAttributeString($chartDetail->baseTimeUnit, 'val'));
1550
        }
1551
        if (isset($chartDetail->majorTimeUnit)) {
1552
            $whichAxis->setAxisOption('majorTimeUnit', (string) self::getAttributeString($chartDetail->majorTimeUnit, 'val'));
1553
        }
1554
        if (isset($chartDetail->minorTimeUnit)) {
1555
            $whichAxis->setAxisOption('minorTimeUnit', (string) self::getAttributeString($chartDetail->minorTimeUnit, 'val'));
1556
        }
1557
        if (isset($chartDetail->txPr)) {
1558
            $children = $chartDetail->txPr->children($this->aNamespace);
1559
            $addAxisText = false;
1560
            $axisText = new AxisText();
1561
            if (isset($children->bodyPr)) {
1562
                $textRotation = self::getAttributeString($children->bodyPr, 'rot');
1563
                if (is_numeric($textRotation)) {
1564
                    $axisText->setRotation((int) ChartProperties::xmlToAngle($textRotation));
1565
                    $addAxisText = true;
1566
                }
1567
            }
1568
            if (isset($children->p->pPr->defRPr)) {
1569
                $font = $this->parseFont($children->p);
1570
                if ($font !== null) {
1571
                    $axisText->setFont($font);
1572
                    $addAxisText = true;
1573
                }
1574
            }
1575
            if (isset($children->p->pPr->defRPr->effectLst)) {
1576
                $this->readEffects($children->p->pPr->defRPr, $axisText, false);
1577
                $addAxisText = true;
1578
            }
1579
            if ($addAxisText) {
1580
                $whichAxis->setAxisText($axisText);
1581
            }
1582
        }
1583
    }
1584
}