Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [Versions 402 and 403]

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