PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.3
2.4.3 2.4.4 2.4.2 2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 All 60 releases
easy-invoice / vendor / sabberworm / php-css-parser / src / Parsing / ParserState.php

ParserState.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.3, at vendor/sabberworm/php-css-parser/src/Parsing/ParserState.php

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