,]++ | # one or more escaped characters (?:\\\\.)++ | # quoted text, like in `[id="example"]` (?: # opening quote ([\'"]) (?: # sequence of characters except closing quote or backslash (?:(?!\\g{-1}|\\\\).)++ | # one or more escaped characters (?:\\\\.)++ )*+ # zero or more times # closing quote or end (unmatched quote is currently allowed) (?:\\g{-1}|$) ) )*+ # zero or more times )$ /ux'; /** * @var non-empty-list */ private $components; /** * @internal since V8.8.0 */ public static function isValid(string $selector): bool { // Note: We need to use `static::` here as the constant is overridden in the `KeyframeSelector` class. $numberOfMatches = preg_match(static::SELECTOR_VALIDATION_RX, $selector); return $numberOfMatches === 1; } /** * @param non-empty-string|non-empty-list $selector * Providing a string is deprecated in version 9.2 and will not work from v10.0 * * @throws UnexpectedTokenException if the selector is not valid */ final public function __construct($selector) { if (\is_string($selector)) { $this->setSelector($selector); } else { $this->setComponents($selector); } } /** * @param list $comments * * @return non-empty-list * * @throws UnexpectedTokenException */ private static function parseComponents(ParserState $parserState, array &$comments = []): array { // Whitespace is a descendent combinator, not allowed around a compound selector. // (It is allowed within, e.g. as part of a string or within a function like `:not()`.) // Gobble any up now to get a clean start. $parserState->consumeWhiteSpace($comments); $selectorParts = []; while (true) { try { $selectorParts[] = CompoundSelector::parse($parserState, $comments); } catch (UnexpectedTokenException $e) { if ($selectorParts !== [] && \end($selectorParts)->getValue() === ' ') { // The whitespace was not a descendent combinator, and was, in fact, arbitrary, // after the end of the selector. Discard it. \array_pop($selectorParts); break; } else { throw $e; } } try { $selectorParts[] = Combinator::parse($parserState, $comments); } catch (UnexpectedTokenException $e) { // End of selector has been reached. break; } } return $selectorParts; } /** * @param list $comments * * @throws UnexpectedTokenException * * @internal */ public static function parse(ParserState $parserState, array &$comments = []): self { $selectorParts = self::parseComponents($parserState, $comments); // Check that the selector has been fully parsed: if (!\in_array($parserState->peek(), ['{', '}', ',', ''], true)) { throw new UnexpectedTokenException( '`,`, `{`, `}` or EOF', $parserState->peek(5), 'literal', $parserState->currentLine() ); } return new static($selectorParts); } /** * @return non-empty-list */ public function getComponents(): array { return $this->components; } /** * @param non-empty-list $components * This should be an alternating sequence of `CompoundSelector` and `Combinator`, starting and ending with a * `CompoundSelector`, and may be a single `CompoundSelector`. */ public function setComponents(array $components): self { $this->components = $components; return $this; } /** * @return non-empty-string * * @deprecated in version 9.2, will be removed in v10.0. Use either `getComponents()` or `render()` instead. */ public function getSelector(): string { return $this->render(new OutputFormat()); } /** * @param non-empty-string $selector * * @throws UnexpectedTokenException if the selector is not valid * * @deprecated in version 9.2, will be removed in v10.0. Use `setComponents()` instead. */ public function setSelector(string $selector): void { $parserState = new ParserState($selector, Settings::create()); $components = self::parseComponents($parserState); // Check that the selector has been fully parsed: if (!$parserState->isEnd()) { throw new UnexpectedTokenException( 'EOF', $parserState->peek(5), 'literal' ); } $this->components = $components; } /** * @return int<0, max> */ public function getSpecificity(): int { return \array_sum( \array_map( static function (Component $component): int { return $component->getSpecificity(); }, $this->components ) ); } public function render(OutputFormat $outputFormat): string { return \implode( '', \array_map( static function (Component $component) use ($outputFormat): string { return $component->render($outputFormat); }, $this->components ) ); } /** * @return array|null> * * @internal */ public function getArrayRepresentation(): array { return [ 'class' => $this->getShortClassName(), 'components' => \array_map( static function (Component $component): array { return $component->getArrayRepresentation(); }, $this->components ), ]; } }