PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 10.1.4
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v10.1.4
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
acymailing / back / Libraries / Sabberworm / CSS / src / Parsing / ParserState.php

ParserState.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 10.1.4, at back/Libraries/Sabberworm/CSS/src/Parsing/ParserState.php

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