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 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   1  <?php
   2  
   3  namespace Sabberworm\CSS\Rule;
   4  
   5  use Sabberworm\CSS\Comment\Comment;
   6  use Sabberworm\CSS\Comment\Commentable;
   7  use Sabberworm\CSS\OutputFormat;
   8  use Sabberworm\CSS\Parsing\ParserState;
   9  use Sabberworm\CSS\Parsing\UnexpectedEOFException;
  10  use Sabberworm\CSS\Parsing\UnexpectedTokenException;
  11  use Sabberworm\CSS\Renderable;
  12  use Sabberworm\CSS\Value\RuleValueList;
  13  use Sabberworm\CSS\Value\Value;
  14  
  15  /**
  16   * RuleSets contains Rule objects which always have a key and a value.
  17   * In CSS, Rules are expressed as follows: “key: value[0][0] value[0][1], value[1][0] value[1][1];”
  18   */
  19  class Rule implements Renderable, Commentable
  20  {
  21      /**
  22       * @var string
  23       */
  24      private $sRule;
  25  
  26      /**
  27       * @var RuleValueList|null
  28       */
  29      private $mValue;
  30  
  31      /**
  32       * @var bool
  33       */
  34      private $bIsImportant;
  35  
  36      /**
  37       * @var array<int, int>
  38       */
  39      private $aIeHack;
  40  
  41      /**
  42       * @var int
  43       */
  44      protected $iLineNo;
  45  
  46      /**
  47       * @var int
  48       */
  49      protected $iColNo;
  50  
  51      /**
  52       * @var array<array-key, Comment>
  53       */
  54      protected $aComments;
  55  
  56      /**
  57       * @param string $sRule
  58       * @param int $iLineNo
  59       * @param int $iColNo
  60       */
  61      public function __construct($sRule, $iLineNo = 0, $iColNo = 0)
  62      {
  63          $this->sRule = $sRule;
  64          $this->mValue = null;
  65          $this->bIsImportant = false;
  66          $this->aIeHack = [];
  67          $this->iLineNo = $iLineNo;
  68          $this->iColNo = $iColNo;
  69          $this->aComments = [];
  70      }
  71  
  72      /**
  73       * @return Rule
  74       *
  75       * @throws UnexpectedEOFException
  76       * @throws UnexpectedTokenException
  77       */
  78      public static function parse(ParserState $oParserState)
  79      {
  80          $aComments = $oParserState->consumeWhiteSpace();
  81          $oRule = new Rule(
  82              $oParserState->parseIdentifier(!$oParserState->comes("--")),
  83              $oParserState->currentLine(),
  84              $oParserState->currentColumn()
  85          );
  86          $oRule->setComments($aComments);
  87          $oRule->addComments($oParserState->consumeWhiteSpace());
  88          $oParserState->consume(':');
  89          $oValue = Value::parseValue($oParserState, self::listDelimiterForRule($oRule->getRule()));
  90          $oRule->setValue($oValue);
  91          if ($oParserState->getSettings()->bLenientParsing) {
  92              while ($oParserState->comes('\\')) {
  93                  $oParserState->consume('\\');
  94                  $oRule->addIeHack($oParserState->consume());
  95                  $oParserState->consumeWhiteSpace();
  96              }
  97          }
  98          $oParserState->consumeWhiteSpace();
  99          if ($oParserState->comes('!')) {
 100              $oParserState->consume('!');
 101              $oParserState->consumeWhiteSpace();
 102              $oParserState->consume('important');
 103              $oRule->setIsImportant(true);
 104          }
 105          $oParserState->consumeWhiteSpace();
 106          while ($oParserState->comes(';')) {
 107              $oParserState->consume(';');
 108          }
 109  
 110          return $oRule;
 111      }
 112  
 113      /**
 114       * @param string $sRule
 115       *
 116       * @return array<int, string>
 117       */
 118      private static function listDelimiterForRule($sRule)
 119      {
 120          if (preg_match('/^font($|-)/', $sRule)) {
 121              return [',', '/', ' '];
 122          }
 123          return [',', ' ', '/'];
 124      }
 125  
 126      /**
 127       * @return int
 128       */
 129      public function getLineNo()
 130      {
 131          return $this->iLineNo;
 132      }
 133  
 134      /**
 135       * @return int
 136       */
 137      public function getColNo()
 138      {
 139          return $this->iColNo;
 140      }
 141  
 142      /**
 143       * @param int $iLine
 144       * @param int $iColumn
 145       *
 146       * @return void
 147       */
 148      public function setPosition($iLine, $iColumn)
 149      {
 150          $this->iColNo = $iColumn;
 151          $this->iLineNo = $iLine;
 152      }
 153  
 154      /**
 155       * @param string $sRule
 156       *
 157       * @return void
 158       */
 159      public function setRule($sRule)
 160      {
 161          $this->sRule = $sRule;
 162      }
 163  
 164      /**
 165       * @return string
 166       */
 167      public function getRule()
 168      {
 169          return $this->sRule;
 170      }
 171  
 172      /**
 173       * @return RuleValueList|null
 174       */
 175      public function getValue()
 176      {
 177          return $this->mValue;
 178      }
 179  
 180      /**
 181       * @param RuleValueList|null $mValue
 182       *
 183       * @return void
 184       */
 185      public function setValue($mValue)
 186      {
 187          $this->mValue = $mValue;
 188      }
 189  
 190      /**
 191       * @param array<array-key, array<array-key, RuleValueList>> $aSpaceSeparatedValues
 192       *
 193       * @return RuleValueList
 194       *
 195       * @deprecated will be removed in version 9.0
 196       *             Old-Style 2-dimensional array given. Retained for (some) backwards-compatibility.
 197       *             Use `setValue()` instead and wrap the value inside a RuleValueList if necessary.
 198       */
 199      public function setValues(array $aSpaceSeparatedValues)
 200      {
 201          $oSpaceSeparatedList = null;
 202          if (count($aSpaceSeparatedValues) > 1) {
 203              $oSpaceSeparatedList = new RuleValueList(' ', $this->iLineNo);
 204          }
 205          foreach ($aSpaceSeparatedValues as $aCommaSeparatedValues) {
 206              $oCommaSeparatedList = null;
 207              if (count($aCommaSeparatedValues) > 1) {
 208                  $oCommaSeparatedList = new RuleValueList(',', $this->iLineNo);
 209              }
 210              foreach ($aCommaSeparatedValues as $mValue) {
 211                  if (!$oSpaceSeparatedList && !$oCommaSeparatedList) {
 212                      $this->mValue = $mValue;
 213                      return $mValue;
 214                  }
 215                  if ($oCommaSeparatedList) {
 216                      $oCommaSeparatedList->addListComponent($mValue);
 217                  } else {
 218                      $oSpaceSeparatedList->addListComponent($mValue);
 219                  }
 220              }
 221              if (!$oSpaceSeparatedList) {
 222                  $this->mValue = $oCommaSeparatedList;
 223                  return $oCommaSeparatedList;
 224              } else {
 225                  $oSpaceSeparatedList->addListComponent($oCommaSeparatedList);
 226              }
 227          }
 228          $this->mValue = $oSpaceSeparatedList;
 229          return $oSpaceSeparatedList;
 230      }
 231  
 232      /**
 233       * @return array<int, array<int, RuleValueList>>
 234       *
 235       * @deprecated will be removed in version 9.0
 236       *             Old-Style 2-dimensional array returned. Retained for (some) backwards-compatibility.
 237       *             Use `getValue()` instead and check for the existence of a (nested set of) ValueList object(s).
 238       */
 239      public function getValues()
 240      {
 241          if (!$this->mValue instanceof RuleValueList) {
 242              return [[$this->mValue]];
 243          }
 244          if ($this->mValue->getListSeparator() === ',') {
 245              return [$this->mValue->getListComponents()];
 246          }
 247          $aResult = [];
 248          foreach ($this->mValue->getListComponents() as $mValue) {
 249              if (!$mValue instanceof RuleValueList || $mValue->getListSeparator() !== ',') {
 250                  $aResult[] = [$mValue];
 251                  continue;
 252              }
 253              if ($this->mValue->getListSeparator() === ' ' || count($aResult) === 0) {
 254                  $aResult[] = [];
 255              }
 256              foreach ($mValue->getListComponents() as $mValue) {
 257                  $aResult[count($aResult) - 1][] = $mValue;
 258              }
 259          }
 260          return $aResult;
 261      }
 262  
 263      /**
 264       * Adds a value to the existing value. Value will be appended if a `RuleValueList` exists of the given type.
 265       * Otherwise, the existing value will be wrapped by one.
 266       *
 267       * @param RuleValueList|array<int, RuleValueList> $mValue
 268       * @param string $sType
 269       *
 270       * @return void
 271       */
 272      public function addValue($mValue, $sType = ' ')
 273      {
 274          if (!is_array($mValue)) {
 275              $mValue = [$mValue];
 276          }
 277          if (!$this->mValue instanceof RuleValueList || $this->mValue->getListSeparator() !== $sType) {
 278              $mCurrentValue = $this->mValue;
 279              $this->mValue = new RuleValueList($sType, $this->iLineNo);
 280              if ($mCurrentValue) {
 281                  $this->mValue->addListComponent($mCurrentValue);
 282              }
 283          }
 284          foreach ($mValue as $mValueItem) {
 285              $this->mValue->addListComponent($mValueItem);
 286          }
 287      }
 288  
 289      /**
 290       * @param int $iModifier
 291       *
 292       * @return void
 293       */
 294      public function addIeHack($iModifier)
 295      {
 296          $this->aIeHack[] = $iModifier;
 297      }
 298  
 299      /**
 300       * @param array<int, int> $aModifiers
 301       *
 302       * @return void
 303       */
 304      public function setIeHack(array $aModifiers)
 305      {
 306          $this->aIeHack = $aModifiers;
 307      }
 308  
 309      /**
 310       * @return array<int, int>
 311       */
 312      public function getIeHack()
 313      {
 314          return $this->aIeHack;
 315      }
 316  
 317      /**
 318       * @param bool $bIsImportant
 319       *
 320       * @return void
 321       */
 322      public function setIsImportant($bIsImportant)
 323      {
 324          $this->bIsImportant = $bIsImportant;
 325      }
 326  
 327      /**
 328       * @return bool
 329       */
 330      public function getIsImportant()
 331      {
 332          return $this->bIsImportant;
 333      }
 334  
 335      /**
 336       * @return string
 337       */
 338      public function __toString()
 339      {
 340          return $this->render(new OutputFormat());
 341      }
 342  
 343      /**
 344       * @return string
 345       */
 346      public function render(OutputFormat $oOutputFormat)
 347      {
 348          $sResult = "{$this->sRule}:{$oOutputFormat->spaceAfterRuleName()}";
 349          if ($this->mValue instanceof Value) { //Can also be a ValueList
 350              $sResult .= $this->mValue->render($oOutputFormat);
 351          } else {
 352              $sResult .= $this->mValue;
 353          }
 354          if (!empty($this->aIeHack)) {
 355              $sResult .= ' \\' . implode('\\', $this->aIeHack);
 356          }
 357          if ($this->bIsImportant) {
 358              $sResult .= ' !important';
 359          }
 360          $sResult .= ';';
 361          return $sResult;
 362      }
 363  
 364      /**
 365       * @param array<array-key, Comment> $aComments
 366       *
 367       * @return void
 368       */
 369      public function addComments(array $aComments)
 370      {
 371          $this->aComments = array_merge($this->aComments, $aComments);
 372      }
 373  
 374      /**
 375       * @return array<array-key, Comment>
 376       */
 377      public function getComments()
 378      {
 379          return $this->aComments;
 380      }
 381  
 382      /**
 383       * @param array<array-key, Comment> $aComments
 384       *
 385       * @return void
 386       */
 387      public function setComments(array $aComments)
 388      {
 389          $this->aComments = $aComments;
 390      }
 391  }