PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.0
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.0
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
← All changes | back/Libraries/Sabberworm/CSS/src/RuleSet/DeclarationBlock.php +178 -708 10.2.2 → 11.1.0 View file →
@@ -1,123 +1,176 @@
1 1 <?php
2 2
3 -namespace Sabberworm\CSS\RuleSet;
3 +declare(strict_types=1);
4 4
5 -use Sabberworm\CSS\CSSList\CSSList;
6 -use Sabberworm\CSS\CSSList\KeyFrame;
7 -use Sabberworm\CSS\OutputFormat;
8 -use Sabberworm\CSS\Parsing\OutputException;
9 -use Sabberworm\CSS\Parsing\ParserState;
10 -use Sabberworm\CSS\Parsing\UnexpectedEOFException;
11 -use Sabberworm\CSS\Parsing\UnexpectedTokenException;
12 -use Sabberworm\CSS\Property\KeyframeSelector;
13 -use Sabberworm\CSS\Property\Selector;
14 -use Sabberworm\CSS\Rule\Rule;
15 -use Sabberworm\CSS\Value\Color;
16 -use Sabberworm\CSS\Value\RuleValueList;
17 -use Sabberworm\CSS\Value\Size;
18 -use Sabberworm\CSS\Value\URL;
19 -use Sabberworm\CSS\Value\Value;
5 +namespace AcyMailing\Libraries\Sabberworm\CSS\RuleSet;
20 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 +
21 23 /**
22 - * Declaration blocks are the parts of a CSS file which denote the rules belonging to a selector.
24 + * This class represents a `RuleSet` constrained by a `Selector`.
23 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 + *
24 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.
25 32 */
26 -class DeclarationBlock extends RuleSet
33 +class DeclarationBlock implements CSSElement, CSSListItem, Positionable, RuleContainer
27 34 {
35 + use CommentContainer;
36 + use Position;
37 +
28 38 /**
29 - * @var array<int, Selector|string>
39 + * @var array<Selector|string>
30 40 */
31 - private $aSelectors;
41 + private $selectors = [];
32 42
33 43 /**
34 - * @param int $iLineNo
44 + * @var RuleSet
35 45 */
36 - public function __construct($iLineNo = 0)
46 + private $ruleSet;
47 +
48 + /**
49 + * @param int<1, max>|null $lineNumber
50 + */
51 + public function __construct(?int $lineNumber = null)
37 52 {
38 - parent::__construct($iLineNo);
39 - $this->aSelectors = [];
53 + $this->ruleSet = new RuleSet($lineNumber);
54 + $this->setPosition($lineNumber);
40 55 }
41 56
42 57 /**
43 - * @param CSSList|null $oList
44 - *
45 - * @return DeclarationBlock|false
46 - *
47 58 * @throws UnexpectedTokenException
48 59 * @throws UnexpectedEOFException
60 + *
61 + * @internal since V8.8.0
49 62 */
50 - public static function parse(ParserState $oParserState, $oList = null)
63 + public static function parse(ParserState $parserState, ?CSSList $list = null): ?DeclarationBlock
51 64 {
52 - $aComments = [];
53 - $oResult = new DeclarationBlock($oParserState->currentLine());
65 + $comments = [];
66 + $result = new DeclarationBlock($parserState->currentLine());
54 67 try {
55 - $aSelectorParts = [];
56 - $sStringWrapperChar = false;
68 + $selectors = [];
69 + $selectorParts = [];
70 + $stringWrapperCharacter = null;
71 + $functionNestingLevel = 0;
72 + $consumedNextCharacter = false;
73 + static $stopCharacters = ['{', '}', '\'', '"', '(', ')', ','];
57 74 do {
58 - $aSelectorParts[] = $oParserState->consume(1)
59 - . $oParserState->consumeUntil(['{', '}', '\'', '"'], false, false, $aComments);
60 - if (in_array($oParserState->peek(), ['\'', '"']) && substr(end($aSelectorParts), -1) != "\\") {
61 - if ($sStringWrapperChar === false) {
62 - $sStringWrapperChar = $oParserState->peek();
63 - } elseif ($sStringWrapperChar == $oParserState->peek()) {
64 - $sStringWrapperChar = false;
65 - }
75 + if (!$consumedNextCharacter) {
76 + $selectorParts[] = $parserState->consume(1);
66 77 }
67 - } while (!in_array($oParserState->peek(), ['{', '}']) || $sStringWrapperChar !== false);
68 - $oResult->setSelectors(implode('', $aSelectorParts), $oList);
69 - if ($oParserState->comes('{')) {
70 - $oParserState->consume(1);
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);
71 118 }
119 + $selectors[] = \implode('', $selectorParts); // add final or only selector
120 + $result->setSelectors($selectors, $list);
121 + if ($parserState->comes('{')) {
122 + $parserState->consume(1);
123 + }
72 124 } catch (UnexpectedTokenException $e) {
73 - if ($oParserState->getSettings()->bLenientParsing) {
74 - if (!$oParserState->comes('}')) {
75 - $oParserState->consumeUntil('}', false, true);
125 + if ($parserState->getSettings()->usesLenientParsing()) {
126 + if (!$parserState->comes('}')) {
127 + $parserState->consumeUntil('}', false, true);
76 128 }
77 - return false;
129 + return null;
78 130 } else {
79 131 throw $e;
80 132 }
81 133 }
82 - $oResult->setComments($aComments);
83 - RuleSet::parseRuleSet($oParserState, $oResult);
84 - return $oResult;
134 + $result->setComments($comments);
135 +
136 + RuleSet::parseRuleSet($parserState, $result->getRuleSet());
137 +
138 + return $result;
85 139 }
86 140
87 141 /**
88 - * @param array<int, Selector|string>|string $mSelector
89 - * @param CSSList|null $oList
142 + * @param array<Selector|string>|string $selectors
90 143 *
91 144 * @throws UnexpectedTokenException
92 145 */
93 - public function setSelectors($mSelector, $oList = null)
146 + public function setSelectors($selectors, ?CSSList $list = null): void
94 147 {
95 - if (is_array($mSelector)) {
96 - $this->aSelectors = $mSelector;
148 + if (\is_array($selectors)) {
149 + $this->selectors = $selectors;
97 150 } else {
98 - $this->aSelectors = explode(',', $mSelector);
151 + $this->selectors = \explode(',', $selectors);
99 152 }
100 - foreach ($this->aSelectors as $iKey => $mSelector) {
101 - if (!($mSelector instanceof Selector)) {
102 - if ($oList === null || !($oList instanceof KeyFrame)) {
103 - if (!Selector::isValid($mSelector)) {
153 + foreach ($this->selectors as $key => $selector) {
154 + if (!($selector instanceof Selector)) {
155 + if ($list === null || !($list instanceof KeyFrame)) {
156 + if (!Selector::isValid($selector)) {
104 157 throw new UnexpectedTokenException(
105 158 "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
106 - $mSelector,
107 - "custom"
159 + $selector,
160 + 'custom'
108 161 );
109 162 }
110 - $this->aSelectors[$iKey] = new Selector($mSelector);
163 + $this->selectors[$key] = new Selector($selector);
111 164 } else {
112 - if (!KeyframeSelector::isValid($mSelector)) {
165 + if (!KeyframeSelector::isValid($selector)) {
113 166 throw new UnexpectedTokenException(
114 167 "Selector did not match '" . KeyframeSelector::SELECTOR_VALIDATION_RX . "'.",
115 - $mSelector,
116 - "custom"
168 + $selector,
169 + 'custom'
117 170 );
118 171 }
119 - $this->aSelectors[$iKey] = new KeyframeSelector($mSelector);
172 + $this->selectors[$key] = new KeyframeSelector($selector);
120 173 }
121 174 }
122 175 }
123 176 }
@@ -124,20 +177,18 @@
124 177
125 178 /**
126 179 * Remove one of the selectors of the block.
127 180 *
128 - * @param Selector|string $mSelector
129 - *
130 - * @return bool
181 + * @param Selector|string $selectorToRemove
131 182 */
132 - public function removeSelector($mSelector)
183 + public function removeSelector($selectorToRemove): bool
133 184 {
134 - if ($mSelector instanceof Selector) {
135 - $mSelector = $mSelector->getSelector();
185 + if ($selectorToRemove instanceof Selector) {
186 + $selectorToRemove = $selectorToRemove->getSelector();
136 187 }
137 - foreach ($this->aSelectors as $iKey => $oSelector) {
138 - if ($oSelector->getSelector() === $mSelector) {
139 - unset($this->aSelectors[$iKey]);
188 + foreach ($this->selectors as $key => $selector) {
189 + if ($selector->getSelector() === $selectorToRemove) {
190 + unset($this->selectors[$key]);
140 191 return true;
141 192 }
142 193 }
143 194 return false;
@@ -143,689 +194,108 @@
143 194 return false;
144 195 }
145 196
146 197 /**
147 - * @return array<int, Selector|string>
148 - *
149 - * @deprecated will be removed in version 9.0; use `getSelectors()` instead
198 + * @return array<Selector>
150 199 */
151 - public function getSelector()
200 + public function getSelectors(): array
152 201 {
153 - return $this->getSelectors();
202 + return $this->selectors;
154 203 }
155 204
156 - /**
157 - * @param Selector|string $mSelector
158 - * @param CSSList|null $oList
159 - *
160 - * @return void
161 - *
162 - * @deprecated will be removed in version 9.0; use `setSelectors()` instead
163 - */
164 - public function setSelector($mSelector, $oList = null)
205 + public function getRuleSet(): RuleSet
165 206 {
166 - $this->setSelectors($mSelector, $oList);
207 + return $this->ruleSet;
167 208 }
168 209
169 210 /**
170 - * @return array<int, Selector|string>
211 + * @see RuleSet::addRule()
171 212 */
172 - public function getSelectors()
213 + public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void
173 214 {
174 - return $this->aSelectors;
215 + $this->ruleSet->addRule($ruleToAdd, $sibling);
175 216 }
176 217
177 218 /**
178 - * Splits shorthand declarations (e.g. `margin` or `font`) into their constituent parts.
219 + * @see RuleSet::getRules()
179 220 *
180 - * @return void
221 + * @return array<int<0, max>, Rule>
181 222 */
182 - public function expandShorthands()
223 + public function getRules(?string $searchPattern = null): array
183 224 {
184 - // border must be expanded before dimensions
185 - $this->expandBorderShorthand();
186 - $this->expandDimensionsShorthand();
187 - $this->expandFontShorthand();
188 - $this->expandBackgroundShorthand();
189 - $this->expandListStyleShorthand();
225 + return $this->ruleSet->getRules($searchPattern);
190 226 }
191 227
192 228 /**
193 - * Creates shorthand declarations (e.g. `margin` or `font`) whenever possible.
229 + * @see RuleSet::setRules()
194 230 *
195 - * @return void
231 + * @param array<Rule> $rules
196 232 */
197 - public function createShorthands()
233 + public function setRules(array $rules): void
198 234 {
199 - $this->createBackgroundShorthand();
200 - $this->createDimensionsShorthand();
201 - // border must be shortened after dimensions
202 - $this->createBorderShorthand();
203 - $this->createFontShorthand();
204 - $this->createListStyleShorthand();
235 + $this->ruleSet->setRules($rules);
205 236 }
206 237
207 238 /**
208 - * Splits shorthand border declarations (e.g. `border: 1px red;`).
239 + * @see RuleSet::getRulesAssoc()
209 240 *
210 - * Additional splitting happens in expandDimensionsShorthand.
211 - *
212 - * Multiple borders are not yet supported as of 3.
213 - *
214 - * @return void
241 + * @return array<string, Rule>
215 242 */
216 - public function expandBorderShorthand()
243 + public function getRulesAssoc(?string $searchPattern = null): array
217 244 {
218 - $aBorderRules = [
219 - 'border',
220 - 'border-left',
221 - 'border-right',
222 - 'border-top',
223 - 'border-bottom',
224 - ];
225 - $aBorderSizes = [
226 - 'thin',
227 - 'medium',
228 - 'thick',
229 - ];
230 - $aRules = $this->getRulesAssoc();
231 - foreach ($aBorderRules as $sBorderRule) {
232 - if (!isset($aRules[$sBorderRule])) {
233 - continue;
234 - }
235 - $oRule = $aRules[$sBorderRule];
236 - $mRuleValue = $oRule->getValue();
237 - $aValues = [];
238 - if (!$mRuleValue instanceof RuleValueList) {
239 - $aValues[] = $mRuleValue;
240 - } else {
241 - $aValues = $mRuleValue->getListComponents();
242 - }
243 - foreach ($aValues as $mValue) {
244 - if ($mValue instanceof Value) {
245 - $mNewValue = clone $mValue;
246 - } else {
247 - $mNewValue = $mValue;
248 - }
249 - if ($mValue instanceof Size) {
250 - $sNewRuleName = $sBorderRule . "-width";
251 - } elseif ($mValue instanceof Color) {
252 - $sNewRuleName = $sBorderRule . "-color";
253 - } else {
254 - if (in_array($mValue, $aBorderSizes)) {
255 - $sNewRuleName = $sBorderRule . "-width";
256 - } else {
257 - $sNewRuleName = $sBorderRule . "-style";
258 - }
259 - }
260 - $oNewRule = new Rule($sNewRuleName, $oRule->getLineNo(), $oRule->getColNo());
261 - $oNewRule->setIsImportant($oRule->getIsImportant());
262 - $oNewRule->addValue([$mNewValue]);
263 - $this->addRule($oNewRule);
264 - }
265 - $this->removeRule($sBorderRule);
266 - }
245 + return $this->ruleSet->getRulesAssoc($searchPattern);
267 246 }
268 247
269 248 /**
270 - * Splits shorthand dimensional declarations (e.g. `margin: 0px auto;`)
271 - * into their constituent parts.
272 - *
273 - * Handles `margin`, `padding`, `border-color`, `border-style` and `border-width`.
274 - *
275 - * @return void
249 + * @see RuleSet::removeRule()
276 250 */
277 - public function expandDimensionsShorthand()
251 + public function removeRule(Rule $ruleToRemove): void
278 252 {
279 - $aExpansions = [
280 - 'margin' => 'margin-%s',
281 - 'padding' => 'padding-%s',
282 - 'border-color' => 'border-%s-color',
283 - 'border-style' => 'border-%s-style',
284 - 'border-width' => 'border-%s-width',
285 - ];
286 - $aRules = $this->getRulesAssoc();
287 - foreach ($aExpansions as $sProperty => $sExpanded) {
288 - if (!isset($aRules[$sProperty])) {
289 - continue;
290 - }
291 - $oRule = $aRules[$sProperty];
292 - $mRuleValue = $oRule->getValue();
293 - $aValues = [];
294 - if (!$mRuleValue instanceof RuleValueList) {
295 - $aValues[] = $mRuleValue;
296 - } else {
297 - $aValues = $mRuleValue->getListComponents();
298 - }
299 - $top = $right = $bottom = $left = null;
300 - switch (count($aValues)) {
301 - case 1:
302 - $top = $right = $bottom = $left = $aValues[0];
303 - break;
304 - case 2:
305 - $top = $bottom = $aValues[0];
306 - $left = $right = $aValues[1];
307 - break;
308 - case 3:
309 - $top = $aValues[0];
310 - $left = $right = $aValues[1];
311 - $bottom = $aValues[2];
312 - break;
313 - case 4:
314 - $top = $aValues[0];
315 - $right = $aValues[1];
316 - $bottom = $aValues[2];
317 - $left = $aValues[3];
318 - break;
319 - }
320 - foreach (['top', 'right', 'bottom', 'left'] as $sPosition) {
321 - $oNewRule = new Rule(sprintf($sExpanded, $sPosition), $oRule->getLineNo(), $oRule->getColNo());
322 - $oNewRule->setIsImportant($oRule->getIsImportant());
323 - $oNewRule->addValue(${$sPosition});
324 - $this->addRule($oNewRule);
325 - }
326 - $this->removeRule($sProperty);
327 - }
253 + $this->ruleSet->removeRule($ruleToRemove);
328 254 }
329 255
330 256 /**
331 - * Converts shorthand font declarations
332 - * (e.g. `font: 300 italic 11px/14px verdana, helvetica, sans-serif;`)
333 - * into their constituent parts.
334 - *
335 - * @return void
257 + * @see RuleSet::removeMatchingRules()
336 258 */
337 - public function expandFontShorthand()
259 + public function removeMatchingRules(string $searchPattern): void
338 260 {
339 - $aRules = $this->getRulesAssoc();
340 - if (!isset($aRules['font'])) {
341 - return;
342 - }
343 - $oRule = $aRules['font'];
344 - // reset properties to 'normal' per http://www.w3.org/TR/21/fonts.html#font-shorthand
345 - $aFontProperties = [
346 - 'font-style' => 'normal',
347 - 'font-variant' => 'normal',
348 - 'font-weight' => 'normal',
349 - 'font-size' => 'normal',
350 - 'line-height' => 'normal',
351 - ];
352 - $mRuleValue = $oRule->getValue();
353 - $aValues = [];
354 - if (!$mRuleValue instanceof RuleValueList) {
355 - $aValues[] = $mRuleValue;
356 - } else {
357 - $aValues = $mRuleValue->getListComponents();
358 - }
359 - foreach ($aValues as $mValue) {
360 - if (!$mValue instanceof Value) {
361 - $mValue = mb_strtolower($mValue);
362 - }
363 - if (in_array($mValue, ['normal', 'inherit'])) {
364 - foreach (['font-style', 'font-weight', 'font-variant'] as $sProperty) {
365 - if (!isset($aFontProperties[$sProperty])) {
366 - $aFontProperties[$sProperty] = $mValue;
367 - }
368 - }
369 - } elseif (in_array($mValue, ['italic', 'oblique'])) {
370 - $aFontProperties['font-style'] = $mValue;
371 - } elseif ($mValue == 'small-caps') {
372 - $aFontProperties['font-variant'] = $mValue;
373 - } elseif (
374 - in_array($mValue, ['bold', 'bolder', 'lighter'])
375 - || ($mValue instanceof Size
376 - && in_array($mValue->getSize(), range(100, 900, 100)))
377 - ) {
378 - $aFontProperties['font-weight'] = $mValue;
379 - } elseif ($mValue instanceof RuleValueList && $mValue->getListSeparator() == '/') {
380 - list($oSize, $oHeight) = $mValue->getListComponents();
381 - $aFontProperties['font-size'] = $oSize;
382 - $aFontProperties['line-height'] = $oHeight;
383 - } elseif ($mValue instanceof Size && $mValue->getUnit() !== null) {
384 - $aFontProperties['font-size'] = $mValue;
385 - } else {
386 - $aFontProperties['font-family'] = $mValue;
387 - }
388 - }
389 - foreach ($aFontProperties as $sProperty => $mValue) {
390 - $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo());
391 - $oNewRule->addValue($mValue);
392 - $oNewRule->setIsImportant($oRule->getIsImportant());
393 - $this->addRule($oNewRule);
394 - }
395 - $this->removeRule('font');
261 + $this->ruleSet->removeMatchingRules($searchPattern);
396 262 }
397 263
398 264 /**
399 - * Converts shorthand background declarations
400 - * (e.g. `background: url("chess.png") gray 50% repeat fixed;`)
401 - * into their constituent parts.
402 - *
403 - * @see http://www.w3.org/TR/21/colors.html#propdef-background
404 - *
405 - * @return void
265 + * @see RuleSet::removeAllRules()
406 266 */
407 - public function expandBackgroundShorthand()
267 + public function removeAllRules(): void
408 268 {
409 - $aRules = $this->getRulesAssoc();
410 - if (!isset($aRules['background'])) {
411 - return;
412 - }
413 - $oRule = $aRules['background'];
414 - $aBgProperties = [
415 - 'background-color' => ['transparent'],
416 - 'background-image' => ['none'],
417 - 'background-repeat' => ['repeat'],
418 - 'background-attachment' => ['scroll'],
419 - 'background-position' => [
420 - new Size(0, '%', null, false, $this->iLineNo),
421 - new Size(0, '%', null, false, $this->iLineNo),
422 - ],
423 - ];
424 - $mRuleValue = $oRule->getValue();
425 - $aValues = [];
426 - if (!$mRuleValue instanceof RuleValueList) {
427 - $aValues[] = $mRuleValue;
428 - } else {
429 - $aValues = $mRuleValue->getListComponents();
430 - }
431 - if (count($aValues) == 1 && $aValues[0] == 'inherit') {
432 - foreach ($aBgProperties as $sProperty => $mValue) {
433 - $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo());
434 - $oNewRule->addValue('inherit');
435 - $oNewRule->setIsImportant($oRule->getIsImportant());
436 - $this->addRule($oNewRule);
437 - }
438 - $this->removeRule('background');
439 - return;
440 - }
441 - $iNumBgPos = 0;
442 - foreach ($aValues as $mValue) {
443 - if (!$mValue instanceof Value) {
444 - $mValue = mb_strtolower($mValue);
445 - }
446 - if ($mValue instanceof URL) {
447 - $aBgProperties['background-image'] = $mValue;
448 - } elseif ($mValue instanceof Color) {
449 - $aBgProperties['background-color'] = $mValue;
450 - } elseif (in_array($mValue, ['scroll', 'fixed'])) {
451 - $aBgProperties['background-attachment'] = $mValue;
452 - } elseif (in_array($mValue, ['repeat', 'no-repeat', 'repeat-x', 'repeat-y'])) {
453 - $aBgProperties['background-repeat'] = $mValue;
454 - } elseif (
455 - in_array($mValue, ['left', 'center', 'right', 'top', 'bottom'])
456 - || $mValue instanceof Size
457 - ) {
458 - if ($iNumBgPos == 0) {
459 - $aBgProperties['background-position'][0] = $mValue;
460 - $aBgProperties['background-position'][1] = 'center';
461 - } else {
462 - $aBgProperties['background-position'][$iNumBgPos] = $mValue;
463 - }
464 - $iNumBgPos++;
465 - }
466 - }
467 - foreach ($aBgProperties as $sProperty => $mValue) {
468 - $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo());
469 - $oNewRule->setIsImportant($oRule->getIsImportant());
470 - $oNewRule->addValue($mValue);
471 - $this->addRule($oNewRule);
472 - }
473 - $this->removeRule('background');
269 + $this->ruleSet->removeAllRules();
474 270 }
475 271
476 272 /**
477 - * @return void
478 - */
479 - public function expandListStyleShorthand()
480 - {
481 - $aListProperties = [
482 - 'list-style-type' => 'disc',
483 - 'list-style-position' => 'outside',
484 - 'list-style-image' => 'none',
485 - ];
486 - $aListStyleTypes = [
487 - 'none',
488 - 'disc',
489 - 'circle',
490 - 'square',
491 - 'decimal-leading-zero',
492 - 'decimal',
493 - 'lower-roman',
494 - 'upper-roman',
495 - 'lower-greek',
496 - 'lower-alpha',
497 - 'lower-latin',
498 - 'upper-alpha',
499 - 'upper-latin',
500 - 'hebrew',
501 - 'armenian',
502 - 'georgian',
503 - 'cjk-ideographic',
504 - 'hiragana',
505 - 'hira-gana-iroha',
506 - 'katakana-iroha',
507 - 'katakana',
508 - ];
509 - $aListStylePositions = [
510 - 'inside',
511 - 'outside',
512 - ];
513 - $aRules = $this->getRulesAssoc();
514 - if (!isset($aRules['list-style'])) {
515 - return;
516 - }
517 - $oRule = $aRules['list-style'];
518 - $mRuleValue = $oRule->getValue();
519 - $aValues = [];
520 - if (!$mRuleValue instanceof RuleValueList) {
521 - $aValues[] = $mRuleValue;
522 - } else {
523 - $aValues = $mRuleValue->getListComponents();
524 - }
525 - if (count($aValues) == 1 && $aValues[0] == 'inherit') {
526 - foreach ($aListProperties as $sProperty => $mValue) {
527 - $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo());
528 - $oNewRule->addValue('inherit');
529 - $oNewRule->setIsImportant($oRule->getIsImportant());
530 - $this->addRule($oNewRule);
531 - }
532 - $this->removeRule('list-style');
533 - return;
534 - }
535 - foreach ($aValues as $mValue) {
536 - if (!$mValue instanceof Value) {
537 - $mValue = mb_strtolower($mValue);
538 - }
539 - if ($mValue instanceof Url) {
540 - $aListProperties['list-style-image'] = $mValue;
541 - } elseif (in_array($mValue, $aListStyleTypes)) {
542 - $aListProperties['list-style-types'] = $mValue;
543 - } elseif (in_array($mValue, $aListStylePositions)) {
544 - $aListProperties['list-style-position'] = $mValue;
545 - }
546 - }
547 - foreach ($aListProperties as $sProperty => $mValue) {
548 - $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo());
549 - $oNewRule->setIsImportant($oRule->getIsImportant());
550 - $oNewRule->addValue($mValue);
551 - $this->addRule($oNewRule);
552 - }
553 - $this->removeRule('list-style');
554 - }
555 -
556 - /**
557 - * @param array<array-key, string> $aProperties
558 - * @param string $sShorthand
273 + * @return non-empty-string
559 274 *
560 - * @return void
561 - */
562 - public function createShorthandProperties(array $aProperties, $sShorthand)
563 - {
564 - $aRules = $this->getRulesAssoc();
565 - $aNewValues = [];
566 - foreach ($aProperties as $sProperty) {
567 - if (!isset($aRules[$sProperty])) {
568 - continue;
569 - }
570 - $oRule = $aRules[$sProperty];
571 - if (!$oRule->getIsImportant()) {
572 - $mRuleValue = $oRule->getValue();
573 - $aValues = [];
574 - if (!$mRuleValue instanceof RuleValueList) {
575 - $aValues[] = $mRuleValue;
576 - } else {
577 - $aValues = $mRuleValue->getListComponents();
578 - }
579 - foreach ($aValues as $mValue) {
580 - $aNewValues[] = $mValue;
581 - }
582 - $this->removeRule($sProperty);
583 - }
584 - }
585 - if (count($aNewValues)) {
586 - $oNewRule = new Rule($sShorthand, $oRule->getLineNo(), $oRule->getColNo());
587 - foreach ($aNewValues as $mValue) {
588 - $oNewRule->addValue($mValue);
589 - }
590 - $this->addRule($oNewRule);
591 - }
592 - }
593 -
594 - /**
595 - * @return void
596 - */
597 - public function createBackgroundShorthand()
598 - {
599 - $aProperties = [
600 - 'background-color',
601 - 'background-image',
602 - 'background-repeat',
603 - 'background-position',
604 - 'background-attachment',
605 - ];
606 - $this->createShorthandProperties($aProperties, 'background');
607 - }
608 -
609 - /**
610 - * @return void
611 - */
612 - public function createListStyleShorthand()
613 - {
614 - $aProperties = [
615 - 'list-style-type',
616 - 'list-style-position',
617 - 'list-style-image',
618 - ];
619 - $this->createShorthandProperties($aProperties, 'list-style');
620 - }
621 -
622 - /**
623 - * Combines `border-color`, `border-style` and `border-width` into `border`.
624 - *
625 - * Should be run after `create_dimensions_shorthand`!
626 - *
627 - * @return void
628 - */
629 - public function createBorderShorthand()
630 - {
631 - $aProperties = [
632 - 'border-width',
633 - 'border-style',
634 - 'border-color',
635 - ];
636 - $this->createShorthandProperties($aProperties, 'border');
637 - }
638 -
639 - /**
640 - * Looks for long format CSS dimensional properties
641 - * (margin, padding, border-color, border-style and border-width)
642 - * and converts them into shorthand CSS properties.
643 - *
644 - * @return void
645 - */
646 - public function createDimensionsShorthand()
647 - {
648 - $aPositions = ['top', 'right', 'bottom', 'left'];
649 - $aExpansions = [
650 - 'margin' => 'margin-%s',
651 - 'padding' => 'padding-%s',
652 - 'border-color' => 'border-%s-color',
653 - 'border-style' => 'border-%s-style',
654 - 'border-width' => 'border-%s-width',
655 - ];
656 - $aRules = $this->getRulesAssoc();
657 - foreach ($aExpansions as $sProperty => $sExpanded) {
658 - $aFoldable = [];
659 - foreach ($aRules as $sRuleName => $oRule) {
660 - foreach ($aPositions as $sPosition) {
661 - if ($sRuleName == sprintf($sExpanded, $sPosition)) {
662 - $aFoldable[$sRuleName] = $oRule;
663 - }
664 - }
665 - }
666 - // All four dimensions must be present
667 - if (count($aFoldable) == 4) {
668 - $aValues = [];
669 - foreach ($aPositions as $sPosition) {
670 - $oRule = $aRules[sprintf($sExpanded, $sPosition)];
671 - $mRuleValue = $oRule->getValue();
672 - $aRuleValues = [];
673 - if (!$mRuleValue instanceof RuleValueList) {
674 - $aRuleValues[] = $mRuleValue;
675 - } else {
676 - $aRuleValues = $mRuleValue->getListComponents();
677 - }
678 - $aValues[$sPosition] = $aRuleValues;
679 - }
680 - $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo());
681 - if ((string)$aValues['left'][0] == (string)$aValues['right'][0]) {
682 - if ((string)$aValues['top'][0] == (string)$aValues['bottom'][0]) {
683 - if ((string)$aValues['top'][0] == (string)$aValues['left'][0]) {
684 - // All 4 sides are equal
685 - $oNewRule->addValue($aValues['top']);
686 - } else {
687 - // Top and bottom are equal, left and right are equal
688 - $oNewRule->addValue($aValues['top']);
689 - $oNewRule->addValue($aValues['left']);
690 - }
691 - } else {
692 - // Only left and right are equal
693 - $oNewRule->addValue($aValues['top']);
694 - $oNewRule->addValue($aValues['left']);
695 - $oNewRule->addValue($aValues['bottom']);
696 - }
697 - } else {
698 - // No sides are equal
699 - $oNewRule->addValue($aValues['top']);
700 - $oNewRule->addValue($aValues['left']);
701 - $oNewRule->addValue($aValues['bottom']);
702 - $oNewRule->addValue($aValues['right']);
703 - }
704 - $this->addRule($oNewRule);
705 - foreach ($aPositions as $sPosition) {
706 - $this->removeRule(sprintf($sExpanded, $sPosition));
707 - }
708 - }
709 - }
710 - }
711 -
712 - /**
713 - * Looks for long format CSS font properties (e.g. `font-weight`) and
714 - * tries to convert them into a shorthand CSS `font` property.
715 - *
716 - * At least `font-size` AND `font-family` must be present in order to create a shorthand declaration.
717 - *
718 - * @return void
719 - */
720 - public function createFontShorthand()
721 - {
722 - $aFontProperties = [
723 - 'font-style',
724 - 'font-variant',
725 - 'font-weight',
726 - 'font-size',
727 - 'line-height',
728 - 'font-family',
729 - ];
730 - $aRules = $this->getRulesAssoc();
731 - if (!isset($aRules['font-size']) || !isset($aRules['font-family'])) {
732 - return;
733 - }
734 - $oOldRule = isset($aRules['font-size']) ? $aRules['font-size'] : $aRules['font-family'];
735 - $oNewRule = new Rule('font', $oOldRule->getLineNo(), $oOldRule->getColNo());
736 - unset($oOldRule);
737 - foreach (['font-style', 'font-variant', 'font-weight'] as $sProperty) {
738 - if (isset($aRules[$sProperty])) {
739 - $oRule = $aRules[$sProperty];
740 - $mRuleValue = $oRule->getValue();
741 - $aValues = [];
742 - if (!$mRuleValue instanceof RuleValueList) {
743 - $aValues[] = $mRuleValue;
744 - } else {
745 - $aValues = $mRuleValue->getListComponents();
746 - }
747 - if ($aValues[0] !== 'normal') {
748 - $oNewRule->addValue($aValues[0]);
749 - }
750 - }
751 - }
752 - // Get the font-size value
753 - $oRule = $aRules['font-size'];
754 - $mRuleValue = $oRule->getValue();
755 - $aFSValues = [];
756 - if (!$mRuleValue instanceof RuleValueList) {
757 - $aFSValues[] = $mRuleValue;
758 - } else {
759 - $aFSValues = $mRuleValue->getListComponents();
760 - }
761 - // But wait to know if we have line-height to add it
762 - if (isset($aRules['line-height'])) {
763 - $oRule = $aRules['line-height'];
764 - $mRuleValue = $oRule->getValue();
765 - $aLHValues = [];
766 - if (!$mRuleValue instanceof RuleValueList) {
767 - $aLHValues[] = $mRuleValue;
768 - } else {
769 - $aLHValues = $mRuleValue->getListComponents();
770 - }
771 - if ($aLHValues[0] !== 'normal') {
772 - $val = new RuleValueList('/', $this->iLineNo);
773 - $val->addListComponent($aFSValues[0]);
774 - $val->addListComponent($aLHValues[0]);
775 - $oNewRule->addValue($val);
776 - }
777 - } else {
778 - $oNewRule->addValue($aFSValues[0]);
779 - }
780 - $oRule = $aRules['font-family'];
781 - $mRuleValue = $oRule->getValue();
782 - $aFFValues = [];
783 - if (!$mRuleValue instanceof RuleValueList) {
784 - $aFFValues[] = $mRuleValue;
785 - } else {
786 - $aFFValues = $mRuleValue->getListComponents();
787 - }
788 - $oFFValue = new RuleValueList(',', $this->iLineNo);
789 - $oFFValue->setListComponents($aFFValues);
790 - $oNewRule->addValue($oFFValue);
791 -
792 - $this->addRule($oNewRule);
793 - foreach ($aFontProperties as $sProperty) {
794 - $this->removeRule($sProperty);
795 - }
796 - }
797 -
798 - /**
799 - * @return string
800 - *
801 275 * @throws OutputException
802 276 */
803 - public function __toString()
277 + public function render(OutputFormat $outputFormat): string
804 278 {
805 - return $this->render(new OutputFormat());
806 - }
807 -
808 - /**
809 - * @return string
810 - *
811 - * @throws OutputException
812 - */
813 - public function render(OutputFormat $oOutputFormat)
814 - {
815 - if (count($this->aSelectors) === 0) {
279 + $formatter = $outputFormat->getFormatter();
280 + $result = $formatter->comments($this);
281 + if (\count($this->selectors) === 0) {
816 282 // If all the selectors have been removed, this declaration block becomes invalid
817 - throw new OutputException("Attempt to print declaration block with missing selector", $this->iLineNo);
283 + throw new OutputException(
284 + 'Attempt to print declaration block with missing selector',
285 + $this->getLineNumber()
286 + );
818 287 }
819 - $sResult = $oOutputFormat->sBeforeDeclarationBlock;
820 - $sResult .= $oOutputFormat->implode(
821 - $oOutputFormat->spaceBeforeSelectorSeparator() . ',' . $oOutputFormat->spaceAfterSelectorSeparator(),
822 - $this->aSelectors
288 + $result .= $outputFormat->getContentBeforeDeclarationBlock();
289 + $result .= $formatter->implode(
290 + $formatter->spaceBeforeSelectorSeparator() . ',' . $formatter->spaceAfterSelectorSeparator(),
291 + $this->selectors
823 292 );
824 - $sResult .= $oOutputFormat->sAfterDeclarationBlockSelectors;
825 - $sResult .= $oOutputFormat->spaceBeforeOpeningBrace() . '{';
826 - $sResult .= parent::render($oOutputFormat);
827 - $sResult .= '}';
828 - $sResult .= $oOutputFormat->sAfterDeclarationBlock;
829 - return $sResult;
293 + $result .= $outputFormat->getContentAfterDeclarationBlockSelectors();
294 + $result .= $formatter->spaceBeforeOpeningBrace() . '{';
295 + $result .= $this->ruleSet->render($outputFormat);
296 + $result .= '}';
297 + $result .= $outputFormat->getContentAfterDeclarationBlock();
298 +
299 + return $result;
830 300 }
831 301 }