See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body