Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 401 and 402] [Versions 401 and 403]

   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              $styleAttributes = $style->attributes();
 105              if ($styleAttributes !== null && ($styleAttributes['startRow'] <= $maxRow) && ($styleAttributes['startCol'] <= $maxCol)) {
 106                  $cellRange = $this->readStyleRange($styleAttributes, $maxCol, $maxRow);
 107  
 108                  $styleAttributes = $style->Style->attributes();
 109  
 110                  $styleArray = [];
 111                  // We still set the number format mask for date/time values, even if readDataOnly is true
 112                  //    so that we can identify whether a float is a float or a date value
 113                  $formatCode = $styleAttributes ? (string) $styleAttributes['Format'] : null;
 114                  if ($formatCode && Date::isDateTimeFormatCode($formatCode)) {
 115                      $styleArray['numberFormat']['formatCode'] = $formatCode;
 116                  }
 117                  if ($this->readDataOnly === false && $styleAttributes !== null) {
 118                      //    If readDataOnly is false, we set all formatting information
 119                      $styleArray['numberFormat']['formatCode'] = $formatCode;
 120                      $styleArray = $this->readStyle($styleArray, $styleAttributes, $style);
 121                  }
 122                  $this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
 123              }
 124          }
 125      }
 126  
 127      private function addBorderDiagonal(SimpleXMLElement $srssb, array &$styleArray): void
 128      {
 129          if (isset($srssb->Diagonal, $srssb->{'Rev-Diagonal'})) {
 130              $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
 131              $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH;
 132          } elseif (isset($srssb->Diagonal)) {
 133              $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
 134              $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP;
 135          } elseif (isset($srssb->{'Rev-Diagonal'})) {
 136              $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes());
 137              $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN;
 138          }
 139      }
 140  
 141      private function addBorderStyle(SimpleXMLElement $srssb, array &$styleArray, string $direction): void
 142      {
 143          $ucDirection = ucfirst($direction);
 144          if (isset($srssb->$ucDirection)) {
 145              $styleArray['borders'][$direction] = self::parseBorderAttributes($srssb->$ucDirection->attributes());
 146          }
 147      }
 148  
 149      private function calcRotation(SimpleXMLElement $styleAttributes): int
 150      {
 151          $rotation = (int) $styleAttributes->Rotation;
 152          if ($rotation >= 270 && $rotation <= 360) {
 153              $rotation -= 360;
 154          }
 155          $rotation = (abs($rotation) > 90) ? 0 : $rotation;
 156  
 157          return $rotation;
 158      }
 159  
 160      private static function addStyle(array &$styleArray, string $key, string $value): void
 161      {
 162          if (array_key_exists($value, self::$mappings[$key])) {
 163              $styleArray[$key] = self::$mappings[$key][$value];
 164          }
 165      }
 166  
 167      private static function addStyle2(array &$styleArray, string $key1, string $key, string $value): void
 168      {
 169          if (array_key_exists($value, self::$mappings[$key])) {
 170              $styleArray[$key1][$key] = self::$mappings[$key][$value];
 171          }
 172      }
 173  
 174      private static function parseBorderAttributes(?SimpleXMLElement $borderAttributes): array
 175      {
 176          $styleArray = [];
 177          if ($borderAttributes !== null) {
 178              if (isset($borderAttributes['Color'])) {
 179                  $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']);
 180              }
 181  
 182              self::addStyle($styleArray, 'borderStyle', (string) $borderAttributes['Style']);
 183          }
 184  
 185          return $styleArray;
 186      }
 187  
 188      private static function parseGnumericColour(string $gnmColour): string
 189      {
 190          [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour);
 191          $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2);
 192          $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2);
 193          $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2);
 194  
 195          return $gnmR . $gnmG . $gnmB;
 196      }
 197  
 198      private function addColors(array &$styleArray, SimpleXMLElement $styleAttributes): void
 199      {
 200          $RGB = self::parseGnumericColour((string) $styleAttributes['Fore']);
 201          $styleArray['font']['color']['rgb'] = $RGB;
 202          $RGB = self::parseGnumericColour((string) $styleAttributes['Back']);
 203          $shade = (string) $styleAttributes['Shade'];
 204          if (($RGB !== '000000') || ($shade !== '0')) {
 205              $RGB2 = self::parseGnumericColour((string) $styleAttributes['PatternColor']);
 206              if ($shade === '1') {
 207                  $styleArray['fill']['startColor']['rgb'] = $RGB;
 208                  $styleArray['fill']['endColor']['rgb'] = $RGB2;
 209              } else {
 210                  $styleArray['fill']['endColor']['rgb'] = $RGB;
 211                  $styleArray['fill']['startColor']['rgb'] = $RGB2;
 212              }
 213              self::addStyle2($styleArray, 'fill', 'fillType', $shade);
 214          }
 215      }
 216  
 217      private function readStyleRange(SimpleXMLElement $styleAttributes, int $maxCol, int $maxRow): string
 218      {
 219          $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1);
 220          $startRow = $styleAttributes['startRow'] + 1;
 221  
 222          $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol'];
 223          $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1);
 224  
 225          $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']);
 226          $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow;
 227  
 228          return $cellRange;
 229      }
 230  
 231      private function readStyle(array $styleArray, SimpleXMLElement $styleAttributes, SimpleXMLElement $style): array
 232      {
 233          self::addStyle2($styleArray, 'alignment', 'horizontal', (string) $styleAttributes['HAlign']);
 234          self::addStyle2($styleArray, 'alignment', 'vertical', (string) $styleAttributes['VAlign']);
 235          $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1';
 236          $styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes);
 237          $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1';
 238          $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0;
 239  
 240          $this->addColors($styleArray, $styleAttributes);
 241  
 242          $fontAttributes = $style->Style->Font->attributes();
 243          if ($fontAttributes !== null) {
 244              $styleArray['font']['name'] = (string) $style->Style->Font;
 245              $styleArray['font']['size'] = (int) ($fontAttributes['Unit']);
 246              $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1';
 247              $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1';
 248              $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1';
 249              self::addStyle2($styleArray, 'font', 'underline', (string) $fontAttributes['Underline']);
 250  
 251              switch ($fontAttributes['Script']) {
 252                  case '1':
 253                      $styleArray['font']['superscript'] = true;
 254  
 255                      break;
 256                  case '-1':
 257                      $styleArray['font']['subscript'] = true;
 258  
 259                      break;
 260              }
 261          }
 262  
 263          if (isset($style->Style->StyleBorder)) {
 264              $srssb = $style->Style->StyleBorder;
 265              $this->addBorderStyle($srssb, $styleArray, 'top');
 266              $this->addBorderStyle($srssb, $styleArray, 'bottom');
 267              $this->addBorderStyle($srssb, $styleArray, 'left');
 268              $this->addBorderStyle($srssb, $styleArray, 'right');
 269              $this->addBorderDiagonal($srssb, $styleArray);
 270          }
 271          if (isset($style->Style->HyperLink)) {
 272              //    TO DO
 273              $hyperlink = $style->Style->HyperLink->attributes();
 274          }
 275  
 276          return $styleArray;
 277      }
 278  }