PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 10.1.4
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v10.1.4
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
acymailing / back / Libraries / Sabberworm / CSS / src / RuleSet / DeclarationBlock.php

DeclarationBlock.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 10.1.4, at back/Libraries/Sabberworm/CSS/src/RuleSet/DeclarationBlock.php

832 lines 28.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\RuleSet;
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;
20
21 /**
22 * Declaration blocks are the parts of a CSS file which denote the rules belonging to a selector.
23 *
24 * Declaration blocks usually appear directly inside a `Document` or another `CSSList` (mostly a `MediaQuery`).
25 */
26 class DeclarationBlock extends RuleSet
27 {
28 /**
29 * @var array<int, Selector|string>
30 */
31 private $aSelectors;
32
33 /**
34 * @param int $iLineNo
35 */
36 public function __construct($iLineNo = 0)
37 {
38 parent::__construct($iLineNo);
39 $this->aSelectors = [];
40 }
41
42 /**
43 * @param CSSList|null $oList
44 *
45 * @return DeclarationBlock|false
46 *
47 * @throws UnexpectedTokenException
48 * @throws UnexpectedEOFException
49 */
50 public static function parse(ParserState $oParserState, $oList = null)
51 {
52 $aComments = [];
53 $oResult = new DeclarationBlock($oParserState->currentLine());
54 try {
55 $aSelectorParts = [];
56 $sStringWrapperChar = false;
57 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 }
66 }
67 } while (!in_array($oParserState->peek(), ['{', '}']) || $sStringWrapperChar !== false);
68 $oResult->setSelectors(implode('', $aSelectorParts), $oList);
69 if ($oParserState->comes('{')) {
70 $oParserState->consume(1);
71 }
72 } catch (UnexpectedTokenException $e) {
73 if ($oParserState->getSettings()->bLenientParsing) {
74 if (!$oParserState->comes('}')) {
75 $oParserState->consumeUntil('}', false, true);
76 }
77 return false;
78 } else {
79 throw $e;
80 }
81 }
82 $oResult->setComments($aComments);
83 RuleSet::parseRuleSet($oParserState, $oResult);
84 return $oResult;
85 }
86
87 /**
88 * @param array<int, Selector|string>|string $mSelector
89 * @param CSSList|null $oList
90 *
91 * @throws UnexpectedTokenException
92 */
93 public function setSelectors($mSelector, $oList = null)
94 {
95 if (is_array($mSelector)) {
96 $this->aSelectors = $mSelector;
97 } else {
98 $this->aSelectors = explode(',', $mSelector);
99 }
100 foreach ($this->aSelectors as $iKey => $mSelector) {
101 if (!($mSelector instanceof Selector)) {
102 if ($oList === null || !($oList instanceof KeyFrame)) {
103 if (!Selector::isValid($mSelector)) {
104 throw new UnexpectedTokenException(
105 "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
106 $mSelector,
107 "custom"
108 );
109 }
110 $this->aSelectors[$iKey] = new Selector($mSelector);
111 } else {
112 if (!KeyframeSelector::isValid($mSelector)) {
113 throw new UnexpectedTokenException(
114 "Selector did not match '" . KeyframeSelector::SELECTOR_VALIDATION_RX . "'.",
115 $mSelector,
116 "custom"
117 );
118 }
119 $this->aSelectors[$iKey] = new KeyframeSelector($mSelector);
120 }
121 }
122 }
123 }
124
125 /**
126 * Remove one of the selectors of the block.
127 *
128 * @param Selector|string $mSelector
129 *
130 * @return bool
131 */
132 public function removeSelector($mSelector)
133 {
134 if ($mSelector instanceof Selector) {
135 $mSelector = $mSelector->getSelector();
136 }
137 foreach ($this->aSelectors as $iKey => $oSelector) {
138 if ($oSelector->getSelector() === $mSelector) {
139 unset($this->aSelectors[$iKey]);
140 return true;
141 }
142 }
143 return false;
144 }
145
146 /**
147 * @return array<int, Selector|string>
148 *
149 * @deprecated will be removed in version 9.0; use `getSelectors()` instead
150 */
151 public function getSelector()
152 {
153 return $this->getSelectors();
154 }
155
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)
165 {
166 $this->setSelectors($mSelector, $oList);
167 }
168
169 /**
170 * @return array<int, Selector|string>
171 */
172 public function getSelectors()
173 {
174 return $this->aSelectors;
175 }
176
177 /**
178 * Splits shorthand declarations (e.g. `margin` or `font`) into their constituent parts.
179 *
180 * @return void
181 */
182 public function expandShorthands()
183 {
184 // border must be expanded before dimensions
185 $this->expandBorderShorthand();
186 $this->expandDimensionsShorthand();
187 $this->expandFontShorthand();
188 $this->expandBackgroundShorthand();
189 $this->expandListStyleShorthand();
190 }
191
192 /**
193 * Creates shorthand declarations (e.g. `margin` or `font`) whenever possible.
194 *
195 * @return void
196 */
197 public function createShorthands()
198 {
199 $this->createBackgroundShorthand();
200 $this->createDimensionsShorthand();
201 // border must be shortened after dimensions
202 $this->createBorderShorthand();
203 $this->createFontShorthand();
204 $this->createListStyleShorthand();
205 }
206
207 /**
208 * Splits shorthand border declarations (e.g. `border: 1px red;`).
209 *
210 * Additional splitting happens in expandDimensionsShorthand.
211 *
212 * Multiple borders are not yet supported as of 3.
213 *
214 * @return void
215 */
216 public function expandBorderShorthand()
217 {
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 }
267 }
268
269 /**
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
276 */
277 public function expandDimensionsShorthand()
278 {
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 }
328 }
329
330 /**
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
336 */
337 public function expandFontShorthand()
338 {
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');
396 }
397
398 /**
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
406 */
407 public function expandBackgroundShorthand()
408 {
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');
474 }
475
476 /**
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
559 *
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 * @throws OutputException
802 */
803 public function __toString()
804 {
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) {
816 // 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);
818 }
819 $sResult = $oOutputFormat->sBeforeDeclarationBlock;
820 $sResult .= $oOutputFormat->implode(
821 $oOutputFormat->spaceBeforeSelectorSeparator() . ',' . $oOutputFormat->spaceAfterSelectorSeparator(),
822 $this->aSelectors
823 );
824 $sResult .= $oOutputFormat->sAfterDeclarationBlockSelectors;
825 $sResult .= $oOutputFormat->spaceBeforeOpeningBrace() . '{';
826 $sResult .= parent::render($oOutputFormat);
827 $sResult .= '}';
828 $sResult .= $oOutputFormat->sAfterDeclarationBlock;
829 return $sResult;
830 }
831 }
832