See Release Notes
Long Term Support Release
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body