PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / vendor / PhpSpreadsheet / Reader / Xlsx / Chart.php

Chart.php in TablePress – Tables in WordPress made easy 3.4, at libraries/vendor/PhpSpreadsheet/Reader/Xlsx/Chart.php

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