PluginProbe
WP Job Manager / 2.4.7
WP Job Manager v2.4.7
2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 1.1.2 1.1.3 1.10.0 1.11.0 1.11.1 1.12.0 1.12.1 1.13.0 1.14.0 1.15.0 All 154 releases
wp-job-manager / vendor / sabberworm / php-css-parser / src / Parsing / ParserState.php
ParserState.php
544 lines 14.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Sabberworm\CSS\Parsing;
4
5 use Sabberworm\CSS\Comment\Comment;
6 use Sabberworm\CSS\Settings;
7
8 class ParserState
9 {
10 /**
11 * @var null
12 */
13 const EOF = null;
14
15 /**
16 * @var Settings
17 */
18 private $oParserSettings;
19
20 /**
21 * @var string
22 */
23 private $sText;
24
25 /**
26 * @var array<int, string>
27 */
28 private $aText;
29
30 /**
31 * @var int
32 */
33 private $iCurrentPosition;
34
35 /**
36 * will only be used if the CSS does not contain an `@charset` declaration
37 *
38 * @var string
39 */
40 private $sCharset;
41
42 /**
43 * @var int
44 */
45 private $iLength;
46
47 /**
48 * @var int
49 */
50 private $iLineNo;
51
52 /**
53 * @param string $sText the complete CSS as text (i.e., usually the contents of a CSS file)
54 * @param int $iLineNo
55 */
56 public function __construct($sText, Settings $oParserSettings, $iLineNo = 1)
57 {
58 $this->oParserSettings = $oParserSettings;
59 $this->sText = $sText;
60 $this->iCurrentPosition = 0;
61 $this->iLineNo = $iLineNo;
62 $this->setCharset($this->oParserSettings->sDefaultCharset);
63 }
64
65 /**
66 * Sets the charset to be used if the CSS does not contain an `@charset` declaration.
67 *
68 * @param string $sCharset
69 *
70 * @return void
71 */
72 public function setCharset($sCharset)
73 {
74 $this->sCharset = $sCharset;
75 $this->aText = $this->strsplit($this->sText);
76 if (is_array($this->aText)) {
77 $this->iLength = count($this->aText);
78 }
79 }
80
81 /**
82 * Returns the charset that is used if the CSS does not contain an `@charset` declaration.
83 *
84 * @return string
85 */
86 public function getCharset()
87 {
88 return $this->sCharset;
89 }
90
91 /**
92 * @return int
93 */
94 public function currentLine()
95 {
96 return $this->iLineNo;
97 }
98
99 /**
100 * @return int
101 */
102 public function currentColumn()
103 {
104 return $this->iCurrentPosition;
105 }
106
107 /**
108 * @return Settings
109 */
110 public function getSettings()
111 {
112 return $this->oParserSettings;
113 }
114
115 /**
116 * @return \Sabberworm\CSS\Parsing\Anchor
117 */
118 public function anchor()
119 {
120 return new Anchor($this->iCurrentPosition, $this);
121 }
122
123 /**
124 * @param int $iPosition
125 *
126 * @return void
127 */
128 public function setPosition($iPosition)
129 {
130 $this->iCurrentPosition = $iPosition;
131 }
132
133 /**
134 * @param bool $bIgnoreCase
135 *
136 * @return string
137 *
138 * @throws UnexpectedTokenException
139 */
140 public function parseIdentifier($bIgnoreCase = true)
141 {
142 if ($this->isEnd()) {
143 throw new UnexpectedEOFException('', '', 'identifier', $this->iLineNo);
144 }
145 $sResult = $this->parseCharacter(true);
146 if ($sResult === null) {
147 throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->iLineNo);
148 }
149 $sCharacter = null;
150 while (!$this->isEnd() && ($sCharacter = $this->parseCharacter(true)) !== null) {
151 if (preg_match('/[a-zA-Z0-9\x{00A0}-\x{FFFF}_-]/Sux', $sCharacter)) {
152 $sResult .= $sCharacter;
153 } else {
154 $sResult .= '\\' . $sCharacter;
155 }
156 }
157 if ($bIgnoreCase) {
158 $sResult = $this->strtolower($sResult);
159 }
160 return $sResult;
161 }
162
163 /**
164 * @param bool $bIsForIdentifier
165 *
166 * @return string|null
167 *
168 * @throws UnexpectedEOFException
169 * @throws UnexpectedTokenException
170 */
171 public function parseCharacter($bIsForIdentifier)
172 {
173 if ($this->peek() === '\\') {
174 if (
175 $bIsForIdentifier && $this->oParserSettings->bLenientParsing
176 && ($this->comes('\0') || $this->comes('\9'))
177 ) {
178 // Non-strings can contain \0 or \9 which is an IE hack supported in lenient parsing.
179 return null;
180 }
181 $this->consume('\\');
182 if ($this->comes('\n') || $this->comes('\r')) {
183 return '';
184 }
185 if (preg_match('/[0-9a-fA-F]/Su', $this->peek()) === 0) {
186 return $this->consume(1);
187 }
188 $sUnicode = $this->consumeExpression('/^[0-9a-fA-F]{1,6}/u', 6);
189 if ($this->strlen($sUnicode) < 6) {
190 // Consume whitespace after incomplete unicode escape
191 if (preg_match('/\\s/isSu', $this->peek())) {
192 if ($this->comes('\r\n')) {
193 $this->consume(2);
194 } else {
195 $this->consume(1);
196 }
197 }
198 }
199 $iUnicode = intval($sUnicode, 16);
200 $sUtf32 = "";
201 for ($i = 0; $i < 4; ++$i) {
202 $sUtf32 .= chr($iUnicode & 0xff);
203 $iUnicode = $iUnicode >> 8;
204 }
205 return iconv('utf-32le', $this->sCharset, $sUtf32);
206 }
207 if ($bIsForIdentifier) {
208 $peek = ord($this->peek());
209 // Ranges: a-z A-Z 0-9 - _
210 if (
211 ($peek >= 97 && $peek <= 122)
212 || ($peek >= 65 && $peek <= 90)
213 || ($peek >= 48 && $peek <= 57)
214 || ($peek === 45)
215 || ($peek === 95)
216 || ($peek > 0xa1)
217 ) {
218 return $this->consume(1);
219 }
220 } else {
221 return $this->consume(1);
222 }
223 return null;
224 }
225
226 /**
227 * @return array<int, Comment>|void
228 *
229 * @throws UnexpectedEOFException
230 * @throws UnexpectedTokenException
231 */
232 public function consumeWhiteSpace()
233 {
234 $aComments = [];
235 do {
236 while (preg_match('/\\s/isSu', $this->peek()) === 1) {
237 $this->consume(1);
238 }
239 if ($this->oParserSettings->bLenientParsing) {
240 try {
241 $oComment = $this->consumeComment();
242 } catch (UnexpectedEOFException $e) {
243 $this->iCurrentPosition = $this->iLength;
244 return $aComments;
245 }
246 } else {
247 $oComment = $this->consumeComment();
248 }
249 if ($oComment !== false) {
250 $aComments[] = $oComment;
251 }
252 } while ($oComment !== false);
253 return $aComments;
254 }
255
256 /**
257 * @param string $sString
258 * @param bool $bCaseInsensitive
259 *
260 * @return bool
261 */
262 public function comes($sString, $bCaseInsensitive = false)
263 {
264 $sPeek = $this->peek(strlen($sString));
265 return ($sPeek == '')
266 ? false
267 : $this->streql($sPeek, $sString, $bCaseInsensitive);
268 }
269
270 /**
271 * @param int $iLength
272 * @param int $iOffset
273 *
274 * @return string
275 */
276 public function peek($iLength = 1, $iOffset = 0)
277 {
278 $iOffset += $this->iCurrentPosition;
279 if ($iOffset >= $this->iLength) {
280 return '';
281 }
282 return $this->substr($iOffset, $iLength);
283 }
284
285 /**
286 * @param int $mValue
287 *
288 * @return string
289 *
290 * @throws UnexpectedEOFException
291 * @throws UnexpectedTokenException
292 */
293 public function consume($mValue = 1)
294 {
295 if (is_string($mValue)) {
296 $iLineCount = substr_count($mValue, "\n");
297 $iLength = $this->strlen($mValue);
298 if (!$this->streql($this->substr($this->iCurrentPosition, $iLength), $mValue)) {
299 throw new UnexpectedTokenException($mValue, $this->peek(max($iLength, 5)), $this->iLineNo);
300 }
301 $this->iLineNo += $iLineCount;
302 $this->iCurrentPosition += $this->strlen($mValue);
303 return $mValue;
304 } else {
305 if ($this->iCurrentPosition + $mValue > $this->iLength) {
306 throw new UnexpectedEOFException($mValue, $this->peek(5), 'count', $this->iLineNo);
307 }
308 $sResult = $this->substr($this->iCurrentPosition, $mValue);
309 $iLineCount = substr_count($sResult, "\n");
310 $this->iLineNo += $iLineCount;
311 $this->iCurrentPosition += $mValue;
312 return $sResult;
313 }
314 }
315
316 /**
317 * @param string $mExpression
318 * @param int|null $iMaxLength
319 *
320 * @return string
321 *
322 * @throws UnexpectedEOFException
323 * @throws UnexpectedTokenException
324 */
325 public function consumeExpression($mExpression, $iMaxLength = null)
326 {
327 $aMatches = null;
328 $sInput = $iMaxLength !== null ? $this->peek($iMaxLength) : $this->inputLeft();
329 if (preg_match($mExpression, $sInput, $aMatches, PREG_OFFSET_CAPTURE) === 1) {
330 return $this->consume($aMatches[0][0]);
331 }
332 throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->iLineNo);
333 }
334
335 /**
336 * @return Comment|false
337 */
338 public function consumeComment()
339 {
340 $mComment = false;
341 if ($this->comes('/*')) {
342 $iLineNo = $this->iLineNo;
343 $this->consume(1);
344 $mComment = '';
345 while (($char = $this->consume(1)) !== '') {
346 $mComment .= $char;
347 if ($this->comes('*/')) {
348 $this->consume(2);
349 break;
350 }
351 }
352 }
353
354 if ($mComment !== false) {
355 // We skip the * which was included in the comment.
356 return new Comment(substr($mComment, 1), $iLineNo);
357 }
358
359 return $mComment;
360 }
361
362 /**
363 * @return bool
364 */
365 public function isEnd()
366 {
367 return $this->iCurrentPosition >= $this->iLength;
368 }
369
370 /**
371 * @param array<array-key, string>|string $aEnd
372 * @param string $bIncludeEnd
373 * @param string $consumeEnd
374 * @param array<int, Comment> $comments
375 *
376 * @return string
377 *
378 * @throws UnexpectedEOFException
379 * @throws UnexpectedTokenException
380 */
381 public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, array &$comments = [])
382 {
383 $aEnd = is_array($aEnd) ? $aEnd : [$aEnd];
384 $out = '';
385 $start = $this->iCurrentPosition;
386
387 while (!$this->isEnd()) {
388 $char = $this->consume(1);
389 if (in_array($char, $aEnd)) {
390 if ($bIncludeEnd) {
391 $out .= $char;
392 } elseif (!$consumeEnd) {
393 $this->iCurrentPosition -= $this->strlen($char);
394 }
395 return $out;
396 }
397 $out .= $char;
398 if ($comment = $this->consumeComment()) {
399 $comments[] = $comment;
400 }
401 }
402
403 if (in_array(self::EOF, $aEnd)) {
404 return $out;
405 }
406
407 $this->iCurrentPosition = $start;
408 throw new UnexpectedEOFException(
409 'One of ("' . implode('","', $aEnd) . '")',
410 $this->peek(5),
411 'search',
412 $this->iLineNo
413 );
414 }
415
416 /**
417 * @return string
418 */
419 private function inputLeft()
420 {
421 return $this->substr($this->iCurrentPosition, -1);
422 }
423
424 /**
425 * @param string $sString1
426 * @param string $sString2
427 * @param bool $bCaseInsensitive
428 *
429 * @return bool
430 */
431 public function streql($sString1, $sString2, $bCaseInsensitive = true)
432 {
433 if ($bCaseInsensitive) {
434 return $this->strtolower($sString1) === $this->strtolower($sString2);
435 } else {
436 return $sString1 === $sString2;
437 }
438 }
439
440 /**
441 * @param int $iAmount
442 *
443 * @return void
444 */
445 public function backtrack($iAmount)
446 {
447 $this->iCurrentPosition -= $iAmount;
448 }
449
450 /**
451 * @param string $sString
452 *
453 * @return int
454 */
455 public function strlen($sString)
456 {
457 if ($this->oParserSettings->bMultibyteSupport) {
458 return mb_strlen($sString, $this->sCharset);
459 } else {
460 return strlen($sString);
461 }
462 }
463
464 /**
465 * @param int $iStart
466 * @param int $iLength
467 *
468 * @return string
469 */
470 private function substr($iStart, $iLength)
471 {
472 if ($iLength < 0) {
473 $iLength = $this->iLength - $iStart + $iLength;
474 }
475 if ($iStart + $iLength > $this->iLength) {
476 $iLength = $this->iLength - $iStart;
477 }
478 $sResult = '';
479 while ($iLength > 0) {
480 $sResult .= $this->aText[$iStart];
481 $iStart++;
482 $iLength--;
483 }
484 return $sResult;
485 }
486
487 /**
488 * @param string $sString
489 *
490 * @return string
491 */
492 private function strtolower($sString)
493 {
494 if ($this->oParserSettings->bMultibyteSupport) {
495 return mb_strtolower($sString, $this->sCharset);
496 } else {
497 return strtolower($sString);
498 }
499 }
500
501 /**
502 * @param string $sString
503 *
504 * @return array<int, string>
505 */
506 private function strsplit($sString)
507 {
508 if ($this->oParserSettings->bMultibyteSupport) {
509 if ($this->streql($this->sCharset, 'utf-8')) {
510 return preg_split('//u', $sString, -1, PREG_SPLIT_NO_EMPTY);
511 } else {
512 $iLength = mb_strlen($sString, $this->sCharset);
513 $aResult = [];
514 for ($i = 0; $i < $iLength; ++$i) {
515 $aResult[] = mb_substr($sString, $i, 1, $this->sCharset);
516 }
517 return $aResult;
518 }
519 } else {
520 if ($sString === '') {
521 return [];
522 } else {
523 return str_split($sString);
524 }
525 }
526 }
527
528 /**
529 * @param string $sString
530 * @param string $sNeedle
531 * @param int $iOffset
532 *
533 * @return int|false
534 */
535 private function strpos($sString, $sNeedle, $iOffset)
536 {
537 if ($this->oParserSettings->bMultibyteSupport) {
538 return mb_strpos($sString, $sNeedle, $iOffset, $this->sCharset);
539 } else {
540 return strpos($sString, $sNeedle, $iOffset);
541 }
542 }
543 }
544