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