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\Value;
   4  
   5  use Sabberworm\CSS\Parsing\ParserState;
   6  use Sabberworm\CSS\Parsing\SourceException;
   7  use Sabberworm\CSS\Parsing\UnexpectedEOFException;
   8  use Sabberworm\CSS\Parsing\UnexpectedTokenException;
   9  use Sabberworm\CSS\Renderable;
  10  
  11  abstract class Value implements Renderable
  12  {
  13      /**
  14       * @var int
  15       */
  16      protected $iLineNo;
  17  
  18      /**
  19       * @param int $iLineNo
  20       */
  21      public function __construct($iLineNo = 0)
  22      {
  23          $this->iLineNo = $iLineNo;
  24      }
  25  
  26      /**
  27       * @param array<array-key, string> $aListDelimiters
  28       *
  29       * @return RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string
  30       *
  31       * @throws UnexpectedTokenException
  32       * @throws UnexpectedEOFException
  33       */
  34      public static function parseValue(ParserState $oParserState, array $aListDelimiters = [])
  35      {
  36          /** @var array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string> $aStack */
  37          $aStack = [];
  38          $oParserState->consumeWhiteSpace();
  39          //Build a list of delimiters and parsed values
  40          while (
  41              !($oParserState->comes('}') || $oParserState->comes(';') || $oParserState->comes('!')
  42              || $oParserState->comes(')')
  43              || $oParserState->comes('\\'))
  44          ) {
  45              if (count($aStack) > 0) {
  46                  $bFoundDelimiter = false;
  47                  foreach ($aListDelimiters as $sDelimiter) {
  48                      if ($oParserState->comes($sDelimiter)) {
  49                          array_push($aStack, $oParserState->consume($sDelimiter));
  50                          $oParserState->consumeWhiteSpace();
  51                          $bFoundDelimiter = true;
  52                          break;
  53                      }
  54                  }
  55                  if (!$bFoundDelimiter) {
  56                      //Whitespace was the list delimiter
  57                      array_push($aStack, ' ');
  58                  }
  59              }
  60              array_push($aStack, self::parsePrimitiveValue($oParserState));
  61              $oParserState->consumeWhiteSpace();
  62          }
  63          // Convert the list to list objects
  64          foreach ($aListDelimiters as $sDelimiter) {
  65              if (count($aStack) === 1) {
  66                  return $aStack[0];
  67              }
  68              $iStartPosition = null;
  69              while (($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) {
  70                  $iLength = 2; //Number of elements to be joined
  71                  for ($i = $iStartPosition + 2; $i < count($aStack); $i += 2, ++$iLength) {
  72                      if ($sDelimiter !== $aStack[$i]) {
  73                          break;
  74                      }
  75                  }
  76                  $oList = new RuleValueList($sDelimiter, $oParserState->currentLine());
  77                  for ($i = $iStartPosition - 1; $i - $iStartPosition + 1 < $iLength * 2; $i += 2) {
  78                      $oList->addListComponent($aStack[$i]);
  79                  }
  80                  array_splice($aStack, $iStartPosition - 1, $iLength * 2 - 1, [$oList]);
  81              }
  82          }
  83          if (!isset($aStack[0])) {
  84              throw new UnexpectedTokenException(
  85                  " {$oParserState->peek()} ",
  86                  $oParserState->peek(1, -1) . $oParserState->peek(2),
  87                  'literal',
  88                  $oParserState->currentLine()
  89              );
  90          }
  91          return $aStack[0];
  92      }
  93  
  94      /**
  95       * @param bool $bIgnoreCase
  96       *
  97       * @return CSSFunction|string
  98       *
  99       * @throws UnexpectedEOFException
 100       * @throws UnexpectedTokenException
 101       */
 102      public static function parseIdentifierOrFunction(ParserState $oParserState, $bIgnoreCase = false)
 103      {
 104          $sResult = $oParserState->parseIdentifier($bIgnoreCase);
 105  
 106          if ($oParserState->comes('(')) {
 107              $oParserState->consume('(');
 108              $aArguments = Value::parseValue($oParserState, ['=', ' ', ',']);
 109              $sResult = new CSSFunction($sResult, $aArguments, ',', $oParserState->currentLine());
 110              $oParserState->consume(')');
 111          }
 112  
 113          return $sResult;
 114      }
 115  
 116      /**
 117       * @return CSSFunction|CSSString|LineName|Size|URL|string
 118       *
 119       * @throws UnexpectedEOFException
 120       * @throws UnexpectedTokenException
 121       * @throws SourceException
 122       */
 123      public static function parsePrimitiveValue(ParserState $oParserState)
 124      {
 125          $oValue = null;
 126          $oParserState->consumeWhiteSpace();
 127          if (
 128              is_numeric($oParserState->peek())
 129              || ($oParserState->comes('-.')
 130                  && is_numeric($oParserState->peek(1, 2)))
 131              || (($oParserState->comes('-') || $oParserState->comes('.')) && is_numeric($oParserState->peek(1, 1)))
 132          ) {
 133              $oValue = Size::parse($oParserState);
 134          } elseif ($oParserState->comes('#') || $oParserState->comes('rgb', true) || $oParserState->comes('hsl', true)) {
 135              $oValue = Color::parse($oParserState);
 136          } elseif ($oParserState->comes('url', true)) {
 137              $oValue = URL::parse($oParserState);
 138          } elseif (
 139              $oParserState->comes('calc', true) || $oParserState->comes('-webkit-calc', true)
 140              || $oParserState->comes('-moz-calc', true)
 141          ) {
 142              $oValue = CalcFunction::parse($oParserState);
 143          } elseif ($oParserState->comes("'") || $oParserState->comes('"')) {
 144              $oValue = CSSString::parse($oParserState);
 145          } elseif ($oParserState->comes("progid:") && $oParserState->getSettings()->bLenientParsing) {
 146              $oValue = self::parseMicrosoftFilter($oParserState);
 147          } elseif ($oParserState->comes("[")) {
 148              $oValue = LineName::parse($oParserState);
 149          } elseif ($oParserState->comes("U+")) {
 150              $oValue = self::parseUnicodeRangeValue($oParserState);
 151          } else {
 152              $oValue = self::parseIdentifierOrFunction($oParserState);
 153          }
 154          $oParserState->consumeWhiteSpace();
 155          return $oValue;
 156      }
 157  
 158      /**
 159       * @return CSSFunction
 160       *
 161       * @throws UnexpectedEOFException
 162       * @throws UnexpectedTokenException
 163       */
 164      private static function parseMicrosoftFilter(ParserState $oParserState)
 165      {
 166          $sFunction = $oParserState->consumeUntil('(', false, true);
 167          $aArguments = Value::parseValue($oParserState, [',', '=']);
 168          return new CSSFunction($sFunction, $aArguments, ',', $oParserState->currentLine());
 169      }
 170  
 171      /**
 172       * @return string
 173       *
 174       * @throws UnexpectedEOFException
 175       * @throws UnexpectedTokenException
 176       */
 177      private static function parseUnicodeRangeValue(ParserState $oParserState)
 178      {
 179          $iCodepointMaxLength = 6; // Code points outside BMP can use up to six digits
 180          $sRange = "";
 181          $oParserState->consume("U+");
 182          do {
 183              if ($oParserState->comes('-')) {
 184                  $iCodepointMaxLength = 13; // Max length is 2 six digit code points + the dash(-) between them
 185              }
 186              $sRange .= $oParserState->consume(1);
 187          } while (strlen($sRange) < $iCodepointMaxLength && preg_match("/[A-Fa-f0-9\?-]/", $oParserState->peek()));
 188          return "U+{$sRange}";
 189      }
 190  
 191      /**
 192       * @return int
 193       */
 194      public function getLineNo()
 195      {
 196          return $this->iLineNo;
 197      }
 198  }