PluginProbe
Yatra – Travel Booking & Tour Operator Software / trunk
Yatra – Travel Booking & Tour Operator Software vtrunk
3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 2.0.11 All 82 releases
yatra / vendor / sabberworm / php-css-parser / src / CSSList / CSSList.php

CSSList.php in Yatra – Travel Booking & Tour Operator Software trunk, at vendor/sabberworm/php-css-parser/src/CSSList/CSSList.php

497 lines 15.9 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\CSSList;
4
5 use Sabberworm\CSS\Comment\Comment;
6 use Sabberworm\CSS\Comment\Commentable;
7 use Sabberworm\CSS\CSSElement;
8 use Sabberworm\CSS\OutputFormat;
9 use Sabberworm\CSS\Parsing\ParserState;
10 use Sabberworm\CSS\Parsing\SourceException;
11 use Sabberworm\CSS\Parsing\UnexpectedEOFException;
12 use Sabberworm\CSS\Parsing\UnexpectedTokenException;
13 use Sabberworm\CSS\Position\Position;
14 use Sabberworm\CSS\Position\Positionable;
15 use Sabberworm\CSS\Property\AtRule;
16 use Sabberworm\CSS\Property\Charset;
17 use Sabberworm\CSS\Property\CSSNamespace;
18 use Sabberworm\CSS\Property\Import;
19 use Sabberworm\CSS\Property\Selector;
20 use Sabberworm\CSS\Renderable;
21 use Sabberworm\CSS\RuleSet\AtRuleSet;
22 use Sabberworm\CSS\RuleSet\DeclarationBlock;
23 use Sabberworm\CSS\RuleSet\RuleSet;
24 use Sabberworm\CSS\Settings;
25 use Sabberworm\CSS\Value\CSSString;
26 use Sabberworm\CSS\Value\URL;
27 use Sabberworm\CSS\Value\Value;
28
29 /**
30 * This is the most generic container available. It can contain `DeclarationBlock`s (rule sets with a selector),
31 * `RuleSet`s as well as other `CSSList` objects.
32 *
33 * It can also contain `Import` and `Charset` objects stemming from at-rules.
34 */
35 abstract class CSSList implements Commentable, CSSElement, Positionable
36 {
37 use Position;
38
39 /**
40 * @var array<array-key, Comment>
41 *
42 * @internal since 8.8.0
43 */
44 protected $aComments;
45
46 /**
47 * @var array<int, RuleSet|CSSList|Import|Charset>
48 *
49 * @internal since 8.8.0
50 */
51 protected $aContents;
52
53 /**
54 * @param int $iLineNo
55 */
56 public function __construct($iLineNo = 0)
57 {
58 $this->aComments = [];
59 $this->aContents = [];
60 $this->setPosition($iLineNo);
61 }
62
63 /**
64 * @return void
65 *
66 * @throws UnexpectedTokenException
67 * @throws SourceException
68 *
69 * @internal since V8.8.0
70 */
71 public static function parseList(ParserState $oParserState, CSSList $oList)
72 {
73 $bIsRoot = $oList instanceof Document;
74 if (is_string($oParserState)) {
75 $oParserState = new ParserState($oParserState, Settings::create());
76 }
77 $bLenientParsing = $oParserState->getSettings()->bLenientParsing;
78 $aComments = [];
79 while (!$oParserState->isEnd()) {
80 $aComments = array_merge($aComments, $oParserState->consumeWhiteSpace());
81 $oListItem = null;
82 if ($bLenientParsing) {
83 try {
84 $oListItem = self::parseListItem($oParserState, $oList);
85 } catch (UnexpectedTokenException $e) {
86 $oListItem = false;
87 }
88 } else {
89 $oListItem = self::parseListItem($oParserState, $oList);
90 }
91 if ($oListItem === null) {
92 // List parsing finished
93 return;
94 }
95 if ($oListItem) {
96 $oListItem->addComments($aComments);
97 $oList->append($oListItem);
98 }
99 $aComments = $oParserState->consumeWhiteSpace();
100 }
101 $oList->addComments($aComments);
102 if (!$bIsRoot && !$bLenientParsing) {
103 throw new SourceException("Unexpected end of document", $oParserState->currentLine());
104 }
105 }
106
107 /**
108 * @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|DeclarationBlock|null|false
109 *
110 * @throws SourceException
111 * @throws UnexpectedEOFException
112 * @throws UnexpectedTokenException
113 */
114 private static function parseListItem(ParserState $oParserState, CSSList $oList)
115 {
116 $bIsRoot = $oList instanceof Document;
117 if ($oParserState->comes('@')) {
118 $oAtRule = self::parseAtRule($oParserState);
119 if ($oAtRule instanceof Charset) {
120 if (!$bIsRoot) {
121 throw new UnexpectedTokenException(
122 '@charset may only occur in root document',
123 '',
124 'custom',
125 $oParserState->currentLine()
126 );
127 }
128 if (count($oList->getContents()) > 0) {
129 throw new UnexpectedTokenException(
130 '@charset must be the first parseable token in a document',
131 '',
132 'custom',
133 $oParserState->currentLine()
134 );
135 }
136 $oParserState->setCharset($oAtRule->getCharset());
137 }
138 return $oAtRule;
139 } elseif ($oParserState->comes('}')) {
140 if ($bIsRoot) {
141 if ($oParserState->getSettings()->bLenientParsing) {
142 return DeclarationBlock::parse($oParserState);
143 } else {
144 throw new SourceException("Unopened {", $oParserState->currentLine());
145 }
146 } else {
147 // End of list
148 return null;
149 }
150 } else {
151 return DeclarationBlock::parse($oParserState, $oList);
152 }
153 }
154
155 /**
156 * @param ParserState $oParserState
157 *
158 * @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|null
159 *
160 * @throws SourceException
161 * @throws UnexpectedTokenException
162 * @throws UnexpectedEOFException
163 */
164 private static function parseAtRule(ParserState $oParserState)
165 {
166 $oParserState->consume('@');
167 $sIdentifier = $oParserState->parseIdentifier();
168 $iIdentifierLineNum = $oParserState->currentLine();
169 $oParserState->consumeWhiteSpace();
170 if ($sIdentifier === 'import') {
171 $oLocation = URL::parse($oParserState);
172 $oParserState->consumeWhiteSpace();
173 $sMediaQuery = null;
174 if (!$oParserState->comes(';')) {
175 $sMediaQuery = trim($oParserState->consumeUntil([';', ParserState::EOF]));
176 }
177 $oParserState->consumeUntil([';', ParserState::EOF], true, true);
178 return new Import($oLocation, $sMediaQuery ?: null, $iIdentifierLineNum);
179 } elseif ($sIdentifier === 'charset') {
180 $oCharsetString = CSSString::parse($oParserState);
181 $oParserState->consumeWhiteSpace();
182 $oParserState->consumeUntil([';', ParserState::EOF], true, true);
183 return new Charset($oCharsetString, $iIdentifierLineNum);
184 } elseif (self::identifierIs($sIdentifier, 'keyframes')) {
185 $oResult = new KeyFrame($iIdentifierLineNum);
186 $oResult->setVendorKeyFrame($sIdentifier);
187 $oResult->setAnimationName(trim($oParserState->consumeUntil('{', false, true)));
188 CSSList::parseList($oParserState, $oResult);
189 if ($oParserState->comes('}')) {
190 $oParserState->consume('}');
191 }
192 return $oResult;
193 } elseif ($sIdentifier === 'namespace') {
194 $sPrefix = null;
195 $mUrl = Value::parsePrimitiveValue($oParserState);
196 if (!$oParserState->comes(';')) {
197 $sPrefix = $mUrl;
198 $mUrl = Value::parsePrimitiveValue($oParserState);
199 }
200 $oParserState->consumeUntil([';', ParserState::EOF], true, true);
201 if ($sPrefix !== null && !is_string($sPrefix)) {
202 throw new UnexpectedTokenException('Wrong namespace prefix', $sPrefix, 'custom', $iIdentifierLineNum);
203 }
204 if (!($mUrl instanceof CSSString || $mUrl instanceof URL)) {
205 throw new UnexpectedTokenException(
206 'Wrong namespace url of invalid type',
207 $mUrl,
208 'custom',
209 $iIdentifierLineNum
210 );
211 }
212 return new CSSNamespace($mUrl, $sPrefix, $iIdentifierLineNum);
213 } else {
214 // Unknown other at rule (font-face or such)
215 $sArgs = trim($oParserState->consumeUntil('{', false, true));
216 if (substr_count($sArgs, "(") != substr_count($sArgs, ")")) {
217 if ($oParserState->getSettings()->bLenientParsing) {
218 return null;
219 } else {
220 throw new SourceException("Unmatched brace count in media query", $oParserState->currentLine());
221 }
222 }
223 $bUseRuleSet = true;
224 foreach (explode('/', AtRule::BLOCK_RULES) as $sBlockRuleName) {
225 if (self::identifierIs($sIdentifier, $sBlockRuleName)) {
226 $bUseRuleSet = false;
227 break;
228 }
229 }
230 if ($bUseRuleSet) {
231 $oAtRule = new AtRuleSet($sIdentifier, $sArgs, $iIdentifierLineNum);
232 RuleSet::parseRuleSet($oParserState, $oAtRule);
233 } else {
234 $oAtRule = new AtRuleBlockList($sIdentifier, $sArgs, $iIdentifierLineNum);
235 CSSList::parseList($oParserState, $oAtRule);
236 if ($oParserState->comes('}')) {
237 $oParserState->consume('}');
238 }
239 }
240 return $oAtRule;
241 }
242 }
243
244 /**
245 * Tests an identifier for a given value. Since identifiers are all keywords, they can be vendor-prefixed.
246 * We need to check for these versions too.
247 *
248 * @param string $sIdentifier
249 * @param string $sMatch
250 *
251 * @return bool
252 */
253 private static function identifierIs($sIdentifier, $sMatch)
254 {
255 return (strcasecmp($sIdentifier, $sMatch) === 0)
256 ?: preg_match("/^(-\\w+-)?$sMatch$/i", $sIdentifier) === 1;
257 }
258
259 /**
260 * Prepends an item to the list of contents.
261 *
262 * @param RuleSet|CSSList|Import|Charset $oItem
263 *
264 * @return void
265 */
266 public function prepend($oItem)
267 {
268 array_unshift($this->aContents, $oItem);
269 }
270
271 /**
272 * Appends an item to the list of contents.
273 *
274 * @param RuleSet|CSSList|Import|Charset $oItem
275 *
276 * @return void
277 */
278 public function append($oItem)
279 {
280 $this->aContents[] = $oItem;
281 }
282
283 /**
284 * Splices the list of contents.
285 *
286 * @param int $iOffset
287 * @param int $iLength
288 * @param array<int, RuleSet|CSSList|Import|Charset> $mReplacement
289 *
290 * @return void
291 */
292 public function splice($iOffset, $iLength = null, $mReplacement = null)
293 {
294 array_splice($this->aContents, $iOffset, $iLength, $mReplacement);
295 }
296
297 /**
298 * Inserts an item in the CSS list before its sibling. If the desired sibling cannot be found,
299 * the item is appended at the end.
300 *
301 * @param RuleSet|CSSList|Import|Charset $item
302 * @param RuleSet|CSSList|Import|Charset $sibling
303 */
304 public function insertBefore($item, $sibling)
305 {
306 if (in_array($sibling, $this->aContents, true)) {
307 $this->replace($sibling, [$item, $sibling]);
308 } else {
309 $this->append($item);
310 }
311 }
312
313 /**
314 * Removes an item from the CSS list.
315 *
316 * @param RuleSet|Import|Charset|CSSList $oItemToRemove
317 * May be a RuleSet (most likely a DeclarationBlock), a Import,
318 * a Charset or another CSSList (most likely a MediaQuery)
319 *
320 * @return bool whether the item was removed
321 */
322 public function remove($oItemToRemove)
323 {
324 $iKey = array_search($oItemToRemove, $this->aContents, true);
325 if ($iKey !== false) {
326 unset($this->aContents[$iKey]);
327 return true;
328 }
329 return false;
330 }
331
332 /**
333 * Replaces an item from the CSS list.
334 *
335 * @param RuleSet|Import|Charset|CSSList $oOldItem
336 * May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`, a `Charset`
337 * or another `CSSList` (most likely a `MediaQuery`)
338 *
339 * @return bool
340 */
341 public function replace($oOldItem, $mNewItem)
342 {
343 $iKey = array_search($oOldItem, $this->aContents, true);
344 if ($iKey !== false) {
345 if (is_array($mNewItem)) {
346 array_splice($this->aContents, $iKey, 1, $mNewItem);
347 } else {
348 array_splice($this->aContents, $iKey, 1, [$mNewItem]);
349 }
350 return true;
351 }
352 return false;
353 }
354
355 /**
356 * @param array<int, RuleSet|Import|Charset|CSSList> $aContents
357 */
358 public function setContents(array $aContents)
359 {
360 $this->aContents = [];
361 foreach ($aContents as $content) {
362 $this->append($content);
363 }
364 }
365
366 /**
367 * Removes a declaration block from the CSS list if it matches all given selectors.
368 *
369 * @param DeclarationBlock|array<array-key, Selector>|string $mSelector the selectors to match
370 * @param bool $bRemoveAll whether to stop at the first declaration block found or remove all blocks
371 *
372 * @return void
373 */
374 public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false)
375 {
376 if ($mSelector instanceof DeclarationBlock) {
377 $mSelector = $mSelector->getSelectors();
378 }
379 if (!is_array($mSelector)) {
380 $mSelector = explode(',', $mSelector);
381 }
382 foreach ($mSelector as $iKey => &$mSel) {
383 if (!($mSel instanceof Selector)) {
384 if (!Selector::isValid($mSel)) {
385 throw new UnexpectedTokenException(
386 "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
387 $mSel,
388 "custom"
389 );
390 }
391 $mSel = new Selector($mSel);
392 }
393 }
394 foreach ($this->aContents as $iKey => $mItem) {
395 if (!($mItem instanceof DeclarationBlock)) {
396 continue;
397 }
398 if ($mItem->getSelectors() == $mSelector) {
399 unset($this->aContents[$iKey]);
400 if (!$bRemoveAll) {
401 return;
402 }
403 }
404 }
405 }
406
407 /**
408 * @return string
409 *
410 * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead.
411 */
412 public function __toString()
413 {
414 return $this->render(new OutputFormat());
415 }
416
417 /**
418 * @return string
419 */
420 protected function renderListContents(OutputFormat $oOutputFormat)
421 {
422 $sResult = '';
423 $bIsFirst = true;
424 $oNextLevel = $oOutputFormat;
425 if (!$this->isRootList()) {
426 $oNextLevel = $oOutputFormat->nextLevel();
427 }
428 foreach ($this->aContents as $oContent) {
429 $sRendered = $oOutputFormat->safely(function () use ($oNextLevel, $oContent) {
430 return $oContent->render($oNextLevel);
431 });
432 if ($sRendered === null) {
433 continue;
434 }
435 if ($bIsFirst) {
436 $bIsFirst = false;
437 $sResult .= $oNextLevel->spaceBeforeBlocks();
438 } else {
439 $sResult .= $oNextLevel->spaceBetweenBlocks();
440 }
441 $sResult .= $sRendered;
442 }
443
444 if (!$bIsFirst) {
445 // Had some output
446 $sResult .= $oOutputFormat->spaceAfterBlocks();
447 }
448
449 return $sResult;
450 }
451
452 /**
453 * Return true if the list can not be further outdented. Only important when rendering.
454 *
455 * @return bool
456 */
457 abstract public function isRootList();
458
459 /**
460 * Returns the stored items.
461 *
462 * @return array<int, RuleSet|Import|Charset|CSSList>
463 */
464 public function getContents()
465 {
466 return $this->aContents;
467 }
468
469 /**
470 * @param array<array-key, Comment> $aComments
471 *
472 * @return void
473 */
474 public function addComments(array $aComments)
475 {
476 $this->aComments = array_merge($this->aComments, $aComments);
477 }
478
479 /**
480 * @return array<array-key, Comment>
481 */
482 public function getComments()
483 {
484 return $this->aComments;
485 }
486
487 /**
488 * @param array<array-key, Comment> $aComments
489 *
490 * @return void
491 */
492 public function setComments(array $aComments)
493 {
494 $this->aComments = $aComments;
495 }
496 }
497