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 400 and 402] [Versions 401 and 402]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
   4  
   5  use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
   6  use PhpOffice\PhpSpreadsheet\Shared\Date;
   7  use PhpOffice\PhpSpreadsheet\Spreadsheet;
   8  use PhpOffice\PhpSpreadsheet\Style\Alignment;
   9  use PhpOffice\PhpSpreadsheet\Style\Border;
  10  use PhpOffice\PhpSpreadsheet\Style\Borders;
  11  use PhpOffice\PhpSpreadsheet\Style\Fill;
  12  use PhpOffice\PhpSpreadsheet\Style\Font;
  13  use SimpleXMLElement;
  14  
  15  class Styles
  16  {
  17      /**
  18       * @var Spreadsheet
  19       */
  20      private $spreadsheet;
  21  
  22      /**
  23       * @var bool
  24       */
  25      protected $readDataOnly = false;
  26  
  27      /** @var array */
  28      public static $mappings = [
  29          'borderStyle' => [
  30              '0' => Border::BORDER_NONE,
  31              '1' => Border::BORDER_THIN,
  32              '2' => Border::BORDER_MEDIUM,
  33              '3' => Border::BORDER_SLANTDASHDOT,
  34              '4' => Border::BORDER_DASHED,
  35              '5' => Border::BORDER_THICK,
  36              '6' => Border::BORDER_DOUBLE,
  37              '7' => Border::BORDER_DOTTED,
  38              '8' => Border::BORDER_MEDIUMDASHED,
  39              '9' => Border::BORDER_DASHDOT,
  40              '10' => Border::BORDER_MEDIUMDASHDOT,
  41              '11' => Border::BORDER_DASHDOTDOT,
  42              '12' => Border::BORDER_MEDIUMDASHDOTDOT,
  43              '13' => Border::BORDER_MEDIUMDASHDOTDOT,
  44          ],
  45          'fillType' => [
  46              '1' => Fill::FILL_SOLID,
  47              '2' => Fill::FILL_PATTERN_DARKGRAY,
  48              '3' => Fill::FILL_PATTERN_MEDIUMGRAY,
  49              '4' => Fill::FILL_PATTERN_LIGHTGRAY,
  50              '5' => Fill::FILL_PATTERN_GRAY125,
  51              '6' => Fill::FILL_PATTERN_GRAY0625,
  52              '7' => Fill::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe
  53              '8' => Fill::FILL_PATTERN_DARKVERTICAL, // vertical stripe
  54              '9' => Fill::FILL_PATTERN_DARKDOWN, // diagonal stripe
  55              '10' => Fill::FILL_PATTERN_DARKUP, // reverse diagonal stripe
  56              '11' => Fill::FILL_PATTERN_DARKGRID, // diagoanl crosshatch
  57              '12' => Fill::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch
  58              '13' => Fill::FILL_PATTERN_LIGHTHORIZONTAL,
  59              '14' => Fill::FILL_PATTERN_LIGHTVERTICAL,
  60              '15' => Fill::FILL_PATTERN_LIGHTUP,
  61              '16' => Fill::FILL_PATTERN_LIGHTDOWN,
  62              '17' => Fill::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch
  63              '18' => Fill::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch
  64          ],
  65          'horizontal' => [
  66              '1' => Alignment::HORIZONTAL_GENERAL,
  67              '2' => Alignment::HORIZONTAL_LEFT,
  68              '4' => Alignment::HORIZONTAL_RIGHT,
  69              '8' => Alignment::HORIZONTAL_CENTER,
  70              '16' => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
  71              '32' => Alignment::HORIZONTAL_JUSTIFY,
  72              '64' => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
  73          ],
  74          'underline' => [
  75              '1' => Font::UNDERLINE_SINGLE,
  76              '2' => Font::UNDERLINE_DOUBLE,
  77              '3' => Font::UNDERLINE_SINGLEACCOUNTING,
  78              '4' => Font::UNDERLINE_DOUBLEACCOUNTING,
  79          ],
  80          'vertical' => [
  81              '1' => Alignment::VERTICAL_TOP,
  82              '2' => Alignment::VERTICAL_BOTTOM,
  83              '4' => Alignment::VERTICAL_CENTER,
  84              '8' => Alignment::VERTICAL_JUSTIFY,
  85          ],
  86      ];
  87  
  88      public function __construct(Spreadsheet $spreadsheet, bool $readDataOnly)
  89      {
  90          $this->spreadsheet = $spreadsheet;
  91          $this->readDataOnly = $readDataOnly;
  92      }
  93  
  94      public function read(SimpleXMLElement $sheet, int $maxRow, int $maxCol): void
  95      {
  96          if ($sheet->Styles->StyleRegion !== null) {
  97              $this->readStyles($sheet->Styles->StyleRegion, $maxRow, $maxCol);
  98          }
  99      }
 100  
 101      private function readStyles(SimpleXMLElement $styleRegion, int $maxRow, int $maxCol): void
 102      {
 103          foreach ($styleRegion as $style) {
 104              /** @scrutinizer ignore-call */
 105              $styleAttributes = $style->attributes();
 106              if ($styleAttributes !== null && ($styleAttributes['startRow'] <= $maxRow) && ($styleAttributes['startCol'] <= $maxCol)) {
 107                  $cellRange = $this->readStyleRange($styleAttributes, $maxCol, $maxRow);
 108  
 109                  $styleAttributes = $style->Style->attributes();
 110  
 111                  $styleArray = [];
 112                  // We still set the number format mask for date/time values, even if readDataOnly is true
 113                  //    so that we can identify whether a float is a float or a date value
 114                  $formatCode = $styleAttributes ? (string) $styleAttributes['Format'] : null;
 115                  if ($formatCode && Date::isDateTimeFormatCode($formatCode)) {
 116                      $styleArray['numberFormat']['formatCode'] = $formatCode;
 117                  }
 118                  if ($this->readDataOnly === false && $styleAttributes !== null) {
 119                      //    If readDataOnly is false, we set all formatting information
 120                      $styleArray['numberFormat']['formatCode'] = $formatCode;
 121                      $styleArray = $this->readStyle($styleArray, $styleAttributes, /** @scrutinizer ignore-type */ $style);
 122                  }
 123                  $this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
 124              }
 125          }
 126      }
 127  
 128      private function addBorderDiagonal(SimpleXMLElement $srssb, array &$styleArray): void
 129      {
 130          if (isset($srssb->Diagonal, $srssb->{'Rev-Diagonal'})) {
 131              $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
 132              $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH;
 133          } elseif (isset($srssb->Diagonal)) {
 134              $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
 135              $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP;
 136          } elseif (isset($srssb->{'Rev-Diagonal'})) {
 137              $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes());
 138              $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN;
 139          }
 140      }
 141  
 142      private function addBorderStyle(SimpleXMLElement $srssb, array &$styleArray, string $direction): void
 143      {
 144          $ucDirection = ucfirst($direction);
 145          if (isset($srssb->$ucDirection)) {
 146              $styleArray['borders'][$direction] = self::parseBorderAttributes($srssb->$ucDirection->attributes());
 147          }
 148      }
 149  
 150      private function calcRotation(SimpleXMLElement $styleAttributes): int
 151      {
 152          $rotation = (int) $styleAttributes->Rotation;
 153          if ($rotation >= 270 && $rotation <= 360) {
 154              $rotation -= 360;
 155          }
 156          $rotation = (abs($rotation) > 90) ? 0 : $rotation;
 157  
 158          return $rotation;
 159      }
 160  
 161      private static function addStyle(array &$styleArray, string $key, string $value): void
 162      {
 163          if (array_key_exists($value, self::$mappings[$key])) {
 164              $styleArray[$key] = self::$mappings[$key][$value];
 165          }
 166      }
 167  
 168      private static function addStyle2(array &$styleArray, string $key1, string $key, string $value): void
 169      {
 170          if (array_key_exists($value, self::$mappings[$key])) {
 171              $styleArray[$key1][$key] = self::$mappings[$key][$value];
 172          }
 173      }
 174  
 175      private static function parseBorderAttributes(?SimpleXMLElement $borderAttributes): array
 176      {
 177          $styleArray = [];
 178          if ($borderAttributes !== null) {
 179              if (isset($borderAttributes['Color'])) {
 180                  $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']);
 181              }
 182  
 183              self::addStyle($styleArray, 'borderStyle', (string) $borderAttributes['Style']);
 184          }
 185  
 186          return $styleArray;
 187      }
 188  
 189      private static function parseGnumericColour(string $gnmColour): string
 190      {
 191          [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour);
 192          $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2);
 193          $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2);
 194          $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2);
 195  
 196          return $gnmR . $gnmG . $gnmB;
 197      }
 198  
 199      private function addColors(array &$styleArray, SimpleXMLElement $styleAttributes): void
 200      {
 201          $RGB = self::parseGnumericColour((string) $styleAttributes['Fore']);
 202          $styleArray['font']['color']['rgb'] = $RGB;
 203          $RGB = self::parseGnumericColour((string) $styleAttributes['Back']);
 204          $shade = (string) $styleAttributes['Shade'];
 205          if (($RGB !== '000000') || ($shade !== '0')) {
 206              $RGB2 = self::parseGnumericColour((string) $styleAttributes['PatternColor']);
 207              if ($shade === '1') {
 208                  $styleArray['fill']['startColor']['rgb'] = $RGB;
 209                  $styleArray['fill']['endColor']['rgb'] = $RGB2;
 210              } else {
 211                  $styleArray['fill']['endColor']['rgb'] = $RGB;
 212                  $styleArray['fill']['startColor']['rgb'] = $RGB2;
 213              }
 214              self::addStyle2($styleArray, 'fill', 'fillType', $shade);
 215          }
 216      }
 217  
 218      private function readStyleRange(SimpleXMLElement $styleAttributes, int $maxCol, int $maxRow): string
 219      {
 220          $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1);
 221          $startRow = $styleAttributes['startRow'] + 1;
 222  
 223          $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol'];
 224          $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1);
 225  
 226          $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']);
 227          $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow;
 228  
 229          return $cellRange;
 230      }
 231  
 232      private function readStyle(array $styleArray, SimpleXMLElement $styleAttributes, SimpleXMLElement $style): array
 233      {
 234          self::addStyle2($styleArray, 'alignment', 'horizontal', (string) $styleAttributes['HAlign']);
 235          self::addStyle2($styleArray, 'alignment', 'vertical', (string) $styleAttributes['VAlign']);
 236          $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1';
 237          $styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes);
 238          $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1';
 239          $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0;
 240  
 241          $this->addColors($styleArray, $styleAttributes);
 242  
 243          $fontAttributes = $style->Style->Font->attributes();
 244          if ($fontAttributes !== null) {
 245              $styleArray['font']['name'] = (string) $style->Style->Font;
 246              $styleArray['font']['size'] = (int) ($fontAttributes['Unit']);
 247              $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1';
 248              $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1';
 249              $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1';
 250              self::addStyle2($styleArray, 'font', 'underline', (string) $fontAttributes['Underline']);
 251  
 252              switch ($fontAttributes['Script']) {
 253                  case '1':
 254                      $styleArray['font']['superscript'] = true;
 255  
 256                      break;
 257                  case '-1':
 258                      $styleArray['font']['subscript'] = true;
 259  
 260                      break;
 261              }
 262          }
 263  
 264          if (isset($style->Style->StyleBorder)) {
 265              $srssb = $style->Style->StyleBorder;
 266              $this->addBorderStyle($srssb, $styleArray, 'top');
 267              $this->addBorderStyle($srssb, $styleArray, 'bottom');
 268              $this->addBorderStyle($srssb, $styleArray, 'left');
 269              $this->addBorderStyle($srssb, $styleArray, 'right');
 270              $this->addBorderDiagonal($srssb, $styleArray);
 271          }
 272          //    TO DO
 273          /*
 274          if (isset($style->Style->HyperLink)) {
 275              $hyperlink = $style->Style->HyperLink->attributes();
 276          }
 277          */
 278  
 279          return $styleArray;
 280      }
 281  }