*/ private $selectors = []; /** * @var RuleSet */ private $ruleSet; /** * @param int<1, max>|null $lineNumber */ public function __construct(?int $lineNumber = null) { $this->ruleSet = new RuleSet($lineNumber); $this->setPosition($lineNumber); } /** * @throws UnexpectedTokenException * @throws UnexpectedEOFException * * @internal since V8.8.0 */ public static function parse(ParserState $parserState, ?CSSList $list = null) : ?DeclarationBlock { $comments = []; $result = new DeclarationBlock($parserState->currentLine()); try { $selectors = self::parseSelectors($parserState, $list, $comments); $result->setSelectors($selectors, $list); if ($parserState->comes('{')) { $parserState->consume(1); } } catch (UnexpectedTokenException $e) { if ($parserState->getSettings()->usesLenientParsing()) { if (!$parserState->consumeIfComes('}')) { $parserState->consumeUntil(['}', ParserState::EOF], \false, \true); } return null; } else { throw $e; } } $result->setComments($comments); RuleSet::parseRuleSet($parserState, $result->getRuleSet()); return $result; } /** * @param array|string $selectors * * @throws UnexpectedTokenException */ public function setSelectors($selectors, ?CSSList $list = null) : void { if (\is_array($selectors)) { $selectorsToSet = $selectors; } else { // A string of comma-separated selectors requires parsing. try { $parserState = new ParserState($selectors, Settings::create()); $selectorsToSet = self::parseSelectors($parserState, $list); if (!$parserState->isEnd()) { throw new UnexpectedTokenException('EOF', 'more'); } } catch (UnexpectedTokenException $exception) { // The exception message from parsing may refer to the faux `{` block start token, // which would be confusing. // Rethrow with a more useful message, that also includes the selector(s) string that was passed. throw new UnexpectedTokenException('Selector(s) string is not valid.', $selectors, 'custom'); } } // Convert all items to a `Selector` if not already foreach ($selectorsToSet as $key => $selector) { if (!$selector instanceof Selector) { if ($list === null || !$list instanceof KeyFrame) { if (!Selector::isValid($selector)) { throw new UnexpectedTokenException("Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.", $selector, 'custom'); } $selectorsToSet[$key] = new Selector($selector); } else { if (!KeyframeSelector::isValid($selector)) { throw new UnexpectedTokenException("Selector did not match '" . KeyframeSelector::SELECTOR_VALIDATION_RX . "'.", $selector, 'custom'); } $selectorsToSet[$key] = new KeyframeSelector($selector); } } } // Discard the keys and reindex the array $this->selectors = \array_values($selectorsToSet); } /** * Remove one of the selectors of the block. * * @param Selector|string $selectorToRemove */ public function removeSelector($selectorToRemove) : bool { if ($selectorToRemove instanceof Selector) { $selectorToRemove = $selectorToRemove->getSelector(); } foreach ($this->selectors as $key => $selector) { if ($selector->getSelector() === $selectorToRemove) { unset($this->selectors[$key]); return \true; } } return \false; } /** * @return list */ public function getSelectors() : array { return $this->selectors; } public function getRuleSet() : RuleSet { return $this->ruleSet; } /** * @see RuleSet::addDeclaration() */ public function addDeclaration(Declaration $declarationToAdd, ?Declaration $sibling = null) : void { $this->ruleSet->addDeclaration($declarationToAdd, $sibling); } /** * @return array, Declaration> * * @see RuleSet::getDeclarations() */ public function getDeclarations(?string $searchPattern = null) : array { return $this->ruleSet->getDeclarations($searchPattern); } /** * @param array $declarations * * @see RuleSet::setDeclarations() */ public function setDeclarations(array $declarations) : void { $this->ruleSet->setDeclarations($declarations); } /** * @return array * * @see RuleSet::getDeclarationsAssociative() */ public function getDeclarationsAssociative(?string $searchPattern = null) : array { return $this->ruleSet->getDeclarationsAssociative($searchPattern); } /** * @see RuleSet::removeDeclaration() */ public function removeDeclaration(Declaration $declarationToRemove) : void { $this->ruleSet->removeDeclaration($declarationToRemove); } /** * @see RuleSet::removeMatchingDeclarations() */ public function removeMatchingDeclarations(string $searchPattern) : void { $this->ruleSet->removeMatchingDeclarations($searchPattern); } /** * @see RuleSet::removeAllDeclarations() */ public function removeAllDeclarations() : void { $this->ruleSet->removeAllDeclarations(); } /** * @return non-empty-string * * @throws OutputException */ public function render(OutputFormat $outputFormat) : string { $formatter = $outputFormat->getFormatter(); $result = $formatter->comments($this); if (\count($this->selectors) === 0) { // If all the selectors have been removed, this declaration block becomes invalid throw new OutputException('Attempt to print declaration block with missing selector', $this->getLineNumber()); } $result .= $outputFormat->getContentBeforeDeclarationBlock(); $result .= $formatter->implode($formatter->spaceBeforeSelectorSeparator() . ',' . $formatter->spaceAfterSelectorSeparator(), $this->selectors); $result .= $outputFormat->getContentAfterDeclarationBlockSelectors(); $result .= $formatter->spaceBeforeOpeningBrace() . '{'; $result .= $this->ruleSet->render($outputFormat); $result .= '}'; $result .= $outputFormat->getContentAfterDeclarationBlock(); return $result; } /** * @return array|null> * * @internal */ public function getArrayRepresentation() : array { throw new \BadMethodCallException('`getArrayRepresentation` is not yet implemented for `' . self::class . '`'); } /** * @param list $comments * * @return list * * @throws UnexpectedTokenException */ private static function parseSelectors(ParserState $parserState, ?CSSList $list, array &$comments = []) : array { $selectorClass = $list instanceof KeyFrame ? KeyFrameSelector::class : Selector::class; $selectors = []; while (\true) { $selectors[] = $selectorClass::parse($parserState, $comments); if (!$parserState->consumeIfComes(',')) { break; } } return $selectors; } }