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.
   1  <?php
   2  
   3  declare(strict_types=1);
   4  
   5  namespace OpenSpout\Writer\XLSX\Manager\Style;
   6  
   7  use OpenSpout\Common\Entity\Style\Style;
   8  use OpenSpout\Writer\Common\Manager\Style\AbstractStyleRegistry as CommonStyleRegistry;
   9  
  10  /**
  11   * @internal
  12   */
  13  class StyleRegistry extends CommonStyleRegistry
  14  {
  15      /**
  16       * Mapping between built-in format and the associated numFmtId.
  17       *
  18       * @see https://msdn.microsoft.com/en-us/library/ff529597(v=office.12).aspx
  19       */
  20      private const builtinNumFormatToIdMapping = [
  21          'General' => 0,
  22          '0' => 1,
  23          '0.00' => 2,
  24          '#,##0' => 3,
  25          '#,##0.00' => 4,
  26          '$#,##0,\-$#,##0' => 5,
  27          '$#,##0,[Red]\-$#,##0' => 6,
  28          '$#,##0.00,\-$#,##0.00' => 7,
  29          '$#,##0.00,[Red]\-$#,##0.00' => 8,
  30          '0%' => 9,
  31          '0.00%' => 10,
  32          '0.00E+00' => 11,
  33          '# ?/?' => 12,
  34          '# ??/??' => 13,
  35          'mm-dd-yy' => 14,
  36          'd-mmm-yy' => 15,
  37          'd-mmm' => 16,
  38          'mmm-yy' => 17,
  39          'h:mm AM/PM' => 18,
  40          'h:mm:ss AM/PM' => 19,
  41          'h:mm' => 20,
  42          'h:mm:ss' => 21,
  43          'm/d/yy h:mm' => 22,
  44  
  45          '#,##0 ,(#,##0)' => 37,
  46          '#,##0 ,[Red](#,##0)' => 38,
  47          '#,##0.00,(#,##0.00)' => 39,
  48          '#,##0.00,[Red](#,##0.00)' => 40,
  49  
  50          '_("$"* #,##0.00_),_("$"* \(#,##0.00\),_("$"* "-"??_),_(@_)' => 44,
  51          'mm:ss' => 45,
  52          '[h]:mm:ss' => 46,
  53          'mm:ss.0' => 47,
  54  
  55          '##0.0E+0' => 48,
  56          '@' => 49,
  57  
  58          '[$-404]e/m/d' => 27,
  59          'm/d/yy' => 30,
  60          't0' => 59,
  61          't0.00' => 60,
  62          't#,##0' => 61,
  63          't#,##0.00' => 62,
  64          't0%' => 67,
  65          't0.00%' => 68,
  66          't# ?/?' => 69,
  67          't# ??/??' => 70,
  68      ];
  69  
  70      /** @var array<string, int> */
  71      private array $registeredFormats = [];
  72  
  73      /** @var array<int, int> [STYLE_ID] => [FORMAT_ID] maps a style to a format declaration */
  74      private array $styleIdToFormatsMappingTable = [];
  75  
  76      /**
  77       * If the numFmtId is lower than 0xA4 (164 in decimal)
  78       * then it's a built-in number format.
  79       * Since Excel is the dominant vendor - we play along here.
  80       *
  81       * @var int the fill index counter for custom fills
  82       */
  83      private int $formatIndex = 164;
  84  
  85      /** @var array<string, int> */
  86      private array $registeredFills = [];
  87  
  88      /** @var array<int, int> [STYLE_ID] => [FILL_ID] maps a style to a fill declaration */
  89      private array $styleIdToFillMappingTable = [];
  90  
  91      /**
  92       * Excel preserves two default fills with index 0 and 1
  93       * Since Excel is the dominant vendor - we play along here.
  94       *
  95       * @var int the fill index counter for custom fills
  96       */
  97      private int $fillIndex = 2;
  98  
  99      /** @var array<string, int> */
 100      private array $registeredBorders = [];
 101  
 102      /** @var array<int, int> [STYLE_ID] => [BORDER_ID] maps a style to a border declaration */
 103      private array $styleIdToBorderMappingTable = [];
 104  
 105      /**
 106       * XLSX specific operations on the registered styles.
 107       */
 108      public function registerStyle(Style $style): Style
 109      {
 110          if ($style->isRegistered()) {
 111              return $style;
 112          }
 113  
 114          $registeredStyle = parent::registerStyle($style);
 115          $this->registerFill($registeredStyle);
 116          $this->registerFormat($registeredStyle);
 117          $this->registerBorder($registeredStyle);
 118  
 119          return $registeredStyle;
 120      }
 121  
 122      /**
 123       * @return null|int Format ID associated to the given style ID
 124       */
 125      public function getFormatIdForStyleId(int $styleId): ?int
 126      {
 127          return $this->styleIdToFormatsMappingTable[$styleId] ?? null;
 128      }
 129  
 130      /**
 131       * @return null|int Fill ID associated to the given style ID
 132       */
 133      public function getFillIdForStyleId(int $styleId): ?int
 134      {
 135          return $this->styleIdToFillMappingTable[$styleId] ?? null;
 136      }
 137  
 138      /**
 139       * @return null|int Fill ID associated to the given style ID
 140       */
 141      public function getBorderIdForStyleId(int $styleId): ?int
 142      {
 143          return $this->styleIdToBorderMappingTable[$styleId] ?? null;
 144      }
 145  
 146      /**
 147       * @return array<string, int>
 148       */
 149      public function getRegisteredFills(): array
 150      {
 151          return $this->registeredFills;
 152      }
 153  
 154      /**
 155       * @return array<string, int>
 156       */
 157      public function getRegisteredBorders(): array
 158      {
 159          return $this->registeredBorders;
 160      }
 161  
 162      /**
 163       * @return array<string, int>
 164       */
 165      public function getRegisteredFormats(): array
 166      {
 167          return $this->registeredFormats;
 168      }
 169  
 170      /**
 171       * Register a format definition.
 172       */
 173      private function registerFormat(Style $style): void
 174      {
 175          $styleId = $style->getId();
 176  
 177          $format = $style->getFormat();
 178          if (null !== $format) {
 179              $isFormatRegistered = isset($this->registeredFormats[$format]);
 180  
 181              // We need to track the already registered format definitions
 182              if ($isFormatRegistered) {
 183                  $registeredStyleId = $this->registeredFormats[$format];
 184                  $registeredFormatId = $this->styleIdToFormatsMappingTable[$registeredStyleId];
 185                  $this->styleIdToFormatsMappingTable[$styleId] = $registeredFormatId;
 186              } else {
 187                  $this->registeredFormats[$format] = $styleId;
 188  
 189                  $id = self::builtinNumFormatToIdMapping[$format] ?? $this->formatIndex++;
 190                  $this->styleIdToFormatsMappingTable[$styleId] = $id;
 191              }
 192          } else {
 193              // The formatId maps a style to a format declaration
 194              // When there is no format definition - we default to 0 ( General )
 195              $this->styleIdToFormatsMappingTable[$styleId] = 0;
 196          }
 197      }
 198  
 199      /**
 200       * Register a fill definition.
 201       */
 202      private function registerFill(Style $style): void
 203      {
 204          $styleId = $style->getId();
 205  
 206          // Currently - only solid backgrounds are supported
 207          // so $backgroundColor is a scalar value (RGB Color)
 208          $backgroundColor = $style->getBackgroundColor();
 209  
 210          if (null !== $backgroundColor) {
 211              $isBackgroundColorRegistered = isset($this->registeredFills[$backgroundColor]);
 212  
 213              // We need to track the already registered background definitions
 214              if ($isBackgroundColorRegistered) {
 215                  $registeredStyleId = $this->registeredFills[$backgroundColor];
 216                  $registeredFillId = $this->styleIdToFillMappingTable[$registeredStyleId];
 217                  $this->styleIdToFillMappingTable[$styleId] = $registeredFillId;
 218              } else {
 219                  $this->registeredFills[$backgroundColor] = $styleId;
 220                  $this->styleIdToFillMappingTable[$styleId] = $this->fillIndex++;
 221              }
 222          } else {
 223              // The fillId maps a style to a fill declaration
 224              // When there is no background color definition - we default to 0
 225              $this->styleIdToFillMappingTable[$styleId] = 0;
 226          }
 227      }
 228  
 229      /**
 230       * Register a border definition.
 231       */
 232      private function registerBorder(Style $style): void
 233      {
 234          $styleId = $style->getId();
 235  
 236          if (null !== ($border = $style->getBorder())) {
 237              $serializedBorder = serialize($border);
 238  
 239              $isBorderAlreadyRegistered = isset($this->registeredBorders[$serializedBorder]);
 240  
 241              if ($isBorderAlreadyRegistered) {
 242                  $registeredStyleId = $this->registeredBorders[$serializedBorder];
 243                  $registeredBorderId = $this->styleIdToBorderMappingTable[$registeredStyleId];
 244                  $this->styleIdToBorderMappingTable[$styleId] = $registeredBorderId;
 245              } else {
 246                  $this->registeredBorders[$serializedBorder] = $styleId;
 247                  $this->styleIdToBorderMappingTable[$styleId] = \count($this->registeredBorders);
 248              }
 249          } else {
 250              // If no border should be applied - the mapping is the default border: 0
 251              $this->styleIdToBorderMappingTable[$styleId] = 0;
 252          }
 253      }
 254  }