← All changes
|
back/Libraries/Sabberworm/CSS/src/RuleSet/DeclarationBlock.php
+0
-301
11.0.5
→
11.1.1
View file →
| @@ -1,301 +1,0 @@ | ||
| 1 | -<?php | |
| 2 | - | |
| 3 | -declare(strict_types=1); | |
| 4 | - | |
| 5 | -namespace AcyMailing\Libraries\Sabberworm\CSS\RuleSet; | |
| 6 | - | |
| 7 | -use AcyMailing\Libraries\Sabberworm\CSS\Comment\CommentContainer; | |
| 8 | -use AcyMailing\Libraries\Sabberworm\CSS\CSSElement; | |
| 9 | -use AcyMailing\Libraries\Sabberworm\CSS\CSSList\CSSList; | |
| 10 | -use AcyMailing\Libraries\Sabberworm\CSS\CSSList\CSSListItem; | |
| 11 | -use AcyMailing\Libraries\Sabberworm\CSS\CSSList\KeyFrame; | |
| 12 | -use AcyMailing\Libraries\Sabberworm\CSS\OutputFormat; | |
| 13 | -use AcyMailing\Libraries\Sabberworm\CSS\Parsing\OutputException; | |
| 14 | -use AcyMailing\Libraries\Sabberworm\CSS\Parsing\ParserState; | |
| 15 | -use AcyMailing\Libraries\Sabberworm\CSS\Parsing\UnexpectedEOFException; | |
| 16 | -use AcyMailing\Libraries\Sabberworm\CSS\Parsing\UnexpectedTokenException; | |
| 17 | -use AcyMailing\Libraries\Sabberworm\CSS\Position\Position; | |
| 18 | -use AcyMailing\Libraries\Sabberworm\CSS\Position\Positionable; | |
| 19 | -use AcyMailing\Libraries\Sabberworm\CSS\Property\KeyframeSelector; | |
| 20 | -use AcyMailing\Libraries\Sabberworm\CSS\Property\Selector; | |
| 21 | -use AcyMailing\Libraries\Sabberworm\CSS\Rule\Rule; | |
| 22 | - | |
| 23 | -/** | |
| 24 | - * This class represents a `RuleSet` constrained by a `Selector`. | |
| 25 | - * | |
| 26 | - * It contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the | |
| 27 | - * matching elements. | |
| 28 | - * | |
| 29 | - * Declaration blocks usually appear directly inside a `Document` or another `CSSList` (mostly a `MediaQuery`). | |
| 30 | - * | |
| 31 | - * Note that `CSSListItem` extends both `Commentable` and `Renderable`, so those interfaces must also be implemented. | |
| 32 | - */ | |
| 33 | -class DeclarationBlock implements CSSElement, CSSListItem, Positionable, RuleContainer | |
| 34 | -{ | |
| 35 | - use CommentContainer; | |
| 36 | - use Position; | |
| 37 | - | |
| 38 | - /** | |
| 39 | - * @var array<Selector|string> | |
| 40 | - */ | |
| 41 | - private $selectors = []; | |
| 42 | - | |
| 43 | - /** | |
| 44 | - * @var RuleSet | |
| 45 | - */ | |
| 46 | - private $ruleSet; | |
| 47 | - | |
| 48 | - /** | |
| 49 | - * @param int<1, max>|null $lineNumber | |
| 50 | - */ | |
| 51 | - public function __construct(?int $lineNumber = null) | |
| 52 | - { | |
| 53 | - $this->ruleSet = new RuleSet($lineNumber); | |
| 54 | - $this->setPosition($lineNumber); | |
| 55 | - } | |
| 56 | - | |
| 57 | - /** | |
| 58 | - * @throws UnexpectedTokenException | |
| 59 | - * @throws UnexpectedEOFException | |
| 60 | - * | |
| 61 | - * @internal since V8.8.0 | |
| 62 | - */ | |
| 63 | - public static function parse(ParserState $parserState, ?CSSList $list = null): ?DeclarationBlock | |
| 64 | - { | |
| 65 | - $comments = []; | |
| 66 | - $result = new DeclarationBlock($parserState->currentLine()); | |
| 67 | - try { | |
| 68 | - $selectors = []; | |
| 69 | - $selectorParts = []; | |
| 70 | - $stringWrapperCharacter = null; | |
| 71 | - $functionNestingLevel = 0; | |
| 72 | - $consumedNextCharacter = false; | |
| 73 | - static $stopCharacters = ['{', '}', '\'', '"', '(', ')', ',']; | |
| 74 | - do { | |
| 75 | - if (!$consumedNextCharacter) { | |
| 76 | - $selectorParts[] = $parserState->consume(1); | |
| 77 | - } | |
| 78 | - $selectorParts[] = $parserState->consumeUntil($stopCharacters, false, false, $comments); | |
| 79 | - $nextCharacter = $parserState->peek(); | |
| 80 | - $consumedNextCharacter = false; | |
| 81 | - switch ($nextCharacter) { | |
| 82 | - case '\'': | |
| 83 | - // The fallthrough is intentional. | |
| 84 | - case '"': | |
| 85 | - if (!\is_string($stringWrapperCharacter)) { | |
| 86 | - $stringWrapperCharacter = $nextCharacter; | |
| 87 | - } elseif ($stringWrapperCharacter === $nextCharacter) { | |
| 88 | - if (\substr(\end($selectorParts), -1) !== '\\') { | |
| 89 | - $stringWrapperCharacter = null; | |
| 90 | - } | |
| 91 | - } | |
| 92 | - break; | |
| 93 | - case '(': | |
| 94 | - if (!\is_string($stringWrapperCharacter)) { | |
| 95 | - ++$functionNestingLevel; | |
| 96 | - } | |
| 97 | - break; | |
| 98 | - case ')': | |
| 99 | - if (!\is_string($stringWrapperCharacter)) { | |
| 100 | - if ($functionNestingLevel <= 0) { | |
| 101 | - throw new UnexpectedTokenException('anything but', ')'); | |
| 102 | - } | |
| 103 | - --$functionNestingLevel; | |
| 104 | - } | |
| 105 | - break; | |
| 106 | - case ',': | |
| 107 | - if (!\is_string($stringWrapperCharacter) && $functionNestingLevel === 0) { | |
| 108 | - $selectors[] = \implode('', $selectorParts); | |
| 109 | - $selectorParts = []; | |
| 110 | - $parserState->consume(1); | |
| 111 | - $consumedNextCharacter = true; | |
| 112 | - } | |
| 113 | - break; | |
| 114 | - } | |
| 115 | - } while (!\in_array($nextCharacter, ['{', '}'], true) || \is_string($stringWrapperCharacter)); | |
| 116 | - if ($functionNestingLevel !== 0) { | |
| 117 | - throw new UnexpectedTokenException(')', $nextCharacter); | |
| 118 | - } | |
| 119 | - $selectors[] = \implode('', $selectorParts); // add final or only selector | |
| 120 | - $result->setSelectors($selectors, $list); | |
| 121 | - if ($parserState->comes('{')) { | |
| 122 | - $parserState->consume(1); | |
| 123 | - } | |
| 124 | - } catch (UnexpectedTokenException $e) { | |
| 125 | - if ($parserState->getSettings()->usesLenientParsing()) { | |
| 126 | - if (!$parserState->comes('}')) { | |
| 127 | - $parserState->consumeUntil('}', false, true); | |
| 128 | - } | |
| 129 | - return null; | |
| 130 | - } else { | |
| 131 | - throw $e; | |
| 132 | - } | |
| 133 | - } | |
| 134 | - $result->setComments($comments); | |
| 135 | - | |
| 136 | - RuleSet::parseRuleSet($parserState, $result->getRuleSet()); | |
| 137 | - | |
| 138 | - return $result; | |
| 139 | - } | |
| 140 | - | |
| 141 | - /** | |
| 142 | - * @param array<Selector|string>|string $selectors | |
| 143 | - * | |
| 144 | - * @throws UnexpectedTokenException | |
| 145 | - */ | |
| 146 | - public function setSelectors($selectors, ?CSSList $list = null): void | |
| 147 | - { | |
| 148 | - if (\is_array($selectors)) { | |
| 149 | - $this->selectors = $selectors; | |
| 150 | - } else { | |
| 151 | - $this->selectors = \explode(',', $selectors); | |
| 152 | - } | |
| 153 | - foreach ($this->selectors as $key => $selector) { | |
| 154 | - if (!($selector instanceof Selector)) { | |
| 155 | - if ($list === null || !($list instanceof KeyFrame)) { | |
| 156 | - if (!Selector::isValid($selector)) { | |
| 157 | - throw new UnexpectedTokenException( | |
| 158 | - "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.", | |
| 159 | - $selector, | |
| 160 | - 'custom' | |
| 161 | - ); | |
| 162 | - } | |
| 163 | - $this->selectors[$key] = new Selector($selector); | |
| 164 | - } else { | |
| 165 | - if (!KeyframeSelector::isValid($selector)) { | |
| 166 | - throw new UnexpectedTokenException( | |
| 167 | - "Selector did not match '" . KeyframeSelector::SELECTOR_VALIDATION_RX . "'.", | |
| 168 | - $selector, | |
| 169 | - 'custom' | |
| 170 | - ); | |
| 171 | - } | |
| 172 | - $this->selectors[$key] = new KeyframeSelector($selector); | |
| 173 | - } | |
| 174 | - } | |
| 175 | - } | |
| 176 | - } | |
| 177 | - | |
| 178 | - /** | |
| 179 | - * Remove one of the selectors of the block. | |
| 180 | - * | |
| 181 | - * @param Selector|string $selectorToRemove | |
| 182 | - */ | |
| 183 | - public function removeSelector($selectorToRemove): bool | |
| 184 | - { | |
| 185 | - if ($selectorToRemove instanceof Selector) { | |
| 186 | - $selectorToRemove = $selectorToRemove->getSelector(); | |
| 187 | - } | |
| 188 | - foreach ($this->selectors as $key => $selector) { | |
| 189 | - if ($selector->getSelector() === $selectorToRemove) { | |
| 190 | - unset($this->selectors[$key]); | |
| 191 | - return true; | |
| 192 | - } | |
| 193 | - } | |
| 194 | - return false; | |
| 195 | - } | |
| 196 | - | |
| 197 | - /** | |
| 198 | - * @return array<Selector> | |
| 199 | - */ | |
| 200 | - public function getSelectors(): array | |
| 201 | - { | |
| 202 | - return $this->selectors; | |
| 203 | - } | |
| 204 | - | |
| 205 | - public function getRuleSet(): RuleSet | |
| 206 | - { | |
| 207 | - return $this->ruleSet; | |
| 208 | - } | |
| 209 | - | |
| 210 | - /** | |
| 211 | - * @see RuleSet::addRule() | |
| 212 | - */ | |
| 213 | - public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void | |
| 214 | - { | |
| 215 | - $this->ruleSet->addRule($ruleToAdd, $sibling); | |
| 216 | - } | |
| 217 | - | |
| 218 | - /** | |
| 219 | - * @see RuleSet::getRules() | |
| 220 | - * | |
| 221 | - * @return array<int<0, max>, Rule> | |
| 222 | - */ | |
| 223 | - public function getRules(?string $searchPattern = null): array | |
| 224 | - { | |
| 225 | - return $this->ruleSet->getRules($searchPattern); | |
| 226 | - } | |
| 227 | - | |
| 228 | - /** | |
| 229 | - * @see RuleSet::setRules() | |
| 230 | - * | |
| 231 | - * @param array<Rule> $rules | |
| 232 | - */ | |
| 233 | - public function setRules(array $rules): void | |
| 234 | - { | |
| 235 | - $this->ruleSet->setRules($rules); | |
| 236 | - } | |
| 237 | - | |
| 238 | - /** | |
| 239 | - * @see RuleSet::getRulesAssoc() | |
| 240 | - * | |
| 241 | - * @return array<string, Rule> | |
| 242 | - */ | |
| 243 | - public function getRulesAssoc(?string $searchPattern = null): array | |
| 244 | - { | |
| 245 | - return $this->ruleSet->getRulesAssoc($searchPattern); | |
| 246 | - } | |
| 247 | - | |
| 248 | - /** | |
| 249 | - * @see RuleSet::removeRule() | |
| 250 | - */ | |
| 251 | - public function removeRule(Rule $ruleToRemove): void | |
| 252 | - { | |
| 253 | - $this->ruleSet->removeRule($ruleToRemove); | |
| 254 | - } | |
| 255 | - | |
| 256 | - /** | |
| 257 | - * @see RuleSet::removeMatchingRules() | |
| 258 | - */ | |
| 259 | - public function removeMatchingRules(string $searchPattern): void | |
| 260 | - { | |
| 261 | - $this->ruleSet->removeMatchingRules($searchPattern); | |
| 262 | - } | |
| 263 | - | |
| 264 | - /** | |
| 265 | - * @see RuleSet::removeAllRules() | |
| 266 | - */ | |
| 267 | - public function removeAllRules(): void | |
| 268 | - { | |
| 269 | - $this->ruleSet->removeAllRules(); | |
| 270 | - } | |
| 271 | - | |
| 272 | - /** | |
| 273 | - * @return non-empty-string | |
| 274 | - * | |
| 275 | - * @throws OutputException | |
| 276 | - */ | |
| 277 | - public function render(OutputFormat $outputFormat): string | |
| 278 | - { | |
| 279 | - $formatter = $outputFormat->getFormatter(); | |
| 280 | - $result = $formatter->comments($this); | |
| 281 | - if (\count($this->selectors) === 0) { | |
| 282 | - // If all the selectors have been removed, this declaration block becomes invalid | |
| 283 | - throw new OutputException( | |
| 284 | - 'Attempt to print declaration block with missing selector', | |
| 285 | - $this->getLineNumber() | |
| 286 | - ); | |
| 287 | - } | |
| 288 | - $result .= $outputFormat->getContentBeforeDeclarationBlock(); | |
| 289 | - $result .= $formatter->implode( | |
| 290 | - $formatter->spaceBeforeSelectorSeparator() . ',' . $formatter->spaceAfterSelectorSeparator(), | |
| 291 | - $this->selectors | |
| 292 | - ); | |
| 293 | - $result .= $outputFormat->getContentAfterDeclarationBlockSelectors(); | |
| 294 | - $result .= $formatter->spaceBeforeOpeningBrace() . '{'; | |
| 295 | - $result .= $this->ruleSet->render($outputFormat); | |
| 296 | - $result .= '}'; | |
| 297 | - $result .= $outputFormat->getContentAfterDeclarationBlock(); | |
| 298 | - | |
| 299 | - return $result; | |
| 300 | - } | |
| 301 | -} | |