Differences Between: [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
1 <?php 2 3 namespace Sabberworm\CSS\RuleSet; 4 5 use Sabberworm\CSS\Parsing\ParserState; 6 use Sabberworm\CSS\Parsing\OutputException; 7 use Sabberworm\CSS\Property\Selector; 8 use Sabberworm\CSS\Rule\Rule; 9 use Sabberworm\CSS\Value\RuleValueList; 10 use Sabberworm\CSS\Value\Value; 11 use Sabberworm\CSS\Value\Size; 12 use Sabberworm\CSS\Value\Color; 13 use Sabberworm\CSS\Value\URL; 14 15 /** 16 * Declaration blocks are the parts of a css file which denote the rules belonging to a selector. 17 * Declaration blocks usually appear directly inside a Document or another CSSList (mostly a MediaQuery). 18 */ 19 class DeclarationBlock extends RuleSet { 20 21 private $aSelectors; 22 23 public function __construct($iLineNo = 0) { 24 parent::__construct($iLineNo); 25 $this->aSelectors = array(); 26 } 27 28 public static function parse(ParserState $oParserState) { 29 $aComments = array(); 30 $oResult = new DeclarationBlock($oParserState->currentLine()); 31 $oResult->setSelector($oParserState->consumeUntil('{', false, true, $aComments)); 32 $oResult->setComments($aComments); 33 RuleSet::parseRuleSet($oParserState, $oResult); 34 return $oResult; 35 } 36 37 38 public function setSelectors($mSelector) { 39 if (is_array($mSelector)) { 40 $this->aSelectors = $mSelector; 41 } else { 42 $this->aSelectors = explode(',', $mSelector); 43 } 44 foreach ($this->aSelectors as $iKey => $mSelector) { 45 if (!($mSelector instanceof Selector)) { 46 $this->aSelectors[$iKey] = new Selector($mSelector); 47 } 48 } 49 } 50 51 // remove one of the selector of the block 52 public function removeSelector($mSelector) { 53 if($mSelector instanceof Selector) { 54 $mSelector = $mSelector->getSelector(); 55 } 56 foreach($this->aSelectors as $iKey => $oSelector) { 57 if($oSelector->getSelector() === $mSelector) { 58 unset($this->aSelectors[$iKey]); 59 return true; 60 } 61 } 62 return false; 63 } 64 65 /** 66 * @deprecated use getSelectors() 67 */ 68 public function getSelector() { 69 return $this->getSelectors(); 70 } 71 72 /** 73 * @deprecated use setSelectors() 74 */ 75 public function setSelector($mSelector) { 76 $this->setSelectors($mSelector); 77 } 78 79 /** 80 * Get selectors. 81 * 82 * @return Selector[] Selectors. 83 */ 84 public function getSelectors() { 85 return $this->aSelectors; 86 } 87 88 /** 89 * Split shorthand declarations (e.g. +margin+ or +font+) into their constituent parts. 90 * */ 91 public function expandShorthands() { 92 // border must be expanded before dimensions 93 $this->expandBorderShorthand(); 94 $this->expandDimensionsShorthand(); 95 $this->expandFontShorthand(); 96 $this->expandBackgroundShorthand(); 97 $this->expandListStyleShorthand(); 98 } 99 100 /** 101 * Create shorthand declarations (e.g. +margin+ or +font+) whenever possible. 102 * */ 103 public function createShorthands() { 104 $this->createBackgroundShorthand(); 105 $this->createDimensionsShorthand(); 106 // border must be shortened after dimensions 107 $this->createBorderShorthand(); 108 $this->createFontShorthand(); 109 $this->createListStyleShorthand(); 110 } 111 112 /** 113 * Split shorthand border declarations (e.g. <tt>border: 1px red;</tt>) 114 * Additional splitting happens in expandDimensionsShorthand 115 * Multiple borders are not yet supported as of 3 116 * */ 117 public function expandBorderShorthand() { 118 $aBorderRules = array( 119 'border', 'border-left', 'border-right', 'border-top', 'border-bottom' 120 ); 121 $aBorderSizes = array( 122 'thin', 'medium', 'thick' 123 ); 124 $aRules = $this->getRulesAssoc(); 125 foreach ($aBorderRules as $sBorderRule) { 126 if (!isset($aRules[$sBorderRule])) 127 continue; 128 $oRule = $aRules[$sBorderRule]; 129 $mRuleValue = $oRule->getValue(); 130 $aValues = array(); 131 if (!$mRuleValue instanceof RuleValueList) { 132 $aValues[] = $mRuleValue; 133 } else { 134 $aValues = $mRuleValue->getListComponents(); 135 } 136 foreach ($aValues as $mValue) { 137 if ($mValue instanceof Value) { 138 $mNewValue = clone $mValue; 139 } else { 140 $mNewValue = $mValue; 141 } 142 if ($mValue instanceof Size) { 143 $sNewRuleName = $sBorderRule . "-width"; 144 } else if ($mValue instanceof Color) { 145 $sNewRuleName = $sBorderRule . "-color"; 146 } else { 147 if (in_array($mValue, $aBorderSizes)) { 148 $sNewRuleName = $sBorderRule . "-width"; 149 } else/* if(in_array($mValue, $aBorderStyles)) */ { 150 $sNewRuleName = $sBorderRule . "-style"; 151 } 152 } 153 $oNewRule = new Rule($sNewRuleName, $this->iLineNo); 154 $oNewRule->setIsImportant($oRule->getIsImportant()); 155 $oNewRule->addValue(array($mNewValue)); 156 $this->addRule($oNewRule); 157 } 158 $this->removeRule($sBorderRule); 159 } 160 } 161 162 /** 163 * Split shorthand dimensional declarations (e.g. <tt>margin: 0px auto;</tt>) 164 * into their constituent parts. 165 * Handles margin, padding, border-color, border-style and border-width. 166 * */ 167 public function expandDimensionsShorthand() { 168 $aExpansions = array( 169 'margin' => 'margin-%s', 170 'padding' => 'padding-%s', 171 'border-color' => 'border-%s-color', 172 'border-style' => 'border-%s-style', 173 'border-width' => 'border-%s-width' 174 ); 175 $aRules = $this->getRulesAssoc(); 176 foreach ($aExpansions as $sProperty => $sExpanded) { 177 if (!isset($aRules[$sProperty])) 178 continue; 179 $oRule = $aRules[$sProperty]; 180 $mRuleValue = $oRule->getValue(); 181 $aValues = array(); 182 if (!$mRuleValue instanceof RuleValueList) { 183 $aValues[] = $mRuleValue; 184 } else { 185 $aValues = $mRuleValue->getListComponents(); 186 } 187 $top = $right = $bottom = $left = null; 188 switch (count($aValues)) { 189 case 1: 190 $top = $right = $bottom = $left = $aValues[0]; 191 break; 192 case 2: 193 $top = $bottom = $aValues[0]; 194 $left = $right = $aValues[1]; 195 break; 196 case 3: 197 $top = $aValues[0]; 198 $left = $right = $aValues[1]; 199 $bottom = $aValues[2]; 200 break; 201 case 4: 202 $top = $aValues[0]; 203 $right = $aValues[1]; 204 $bottom = $aValues[2]; 205 $left = $aValues[3]; 206 break; 207 } 208 foreach (array('top', 'right', 'bottom', 'left') as $sPosition) { 209 $oNewRule = new Rule(sprintf($sExpanded, $sPosition), $this->iLineNo); 210 $oNewRule->setIsImportant($oRule->getIsImportant()); 211 $oNewRule->addValue(${$sPosition}); 212 $this->addRule($oNewRule); 213 } 214 $this->removeRule($sProperty); 215 } 216 } 217 218 /** 219 * Convert shorthand font declarations 220 * (e.g. <tt>font: 300 italic 11px/14px verdana, helvetica, sans-serif;</tt>) 221 * into their constituent parts. 222 * */ 223 public function expandFontShorthand() { 224 $aRules = $this->getRulesAssoc(); 225 if (!isset($aRules['font'])) 226 return; 227 $oRule = $aRules['font']; 228 // reset properties to 'normal' per http://www.w3.org/TR/21/fonts.html#font-shorthand 229 $aFontProperties = array( 230 'font-style' => 'normal', 231 'font-variant' => 'normal', 232 'font-weight' => 'normal', 233 'font-size' => 'normal', 234 'line-height' => 'normal' 235 ); 236 $mRuleValue = $oRule->getValue(); 237 $aValues = array(); 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 $mValue = mb_strtolower($mValue); 246 } 247 if (in_array($mValue, array('normal', 'inherit'))) { 248 foreach (array('font-style', 'font-weight', 'font-variant') as $sProperty) { 249 if (!isset($aFontProperties[$sProperty])) { 250 $aFontProperties[$sProperty] = $mValue; 251 } 252 } 253 } else if (in_array($mValue, array('italic', 'oblique'))) { 254 $aFontProperties['font-style'] = $mValue; 255 } else if ($mValue == 'small-caps') { 256 $aFontProperties['font-variant'] = $mValue; 257 } else if ( 258 in_array($mValue, array('bold', 'bolder', 'lighter')) 259 || ($mValue instanceof Size 260 && in_array($mValue->getSize(), range(100, 900, 100))) 261 ) { 262 $aFontProperties['font-weight'] = $mValue; 263 } else if ($mValue instanceof RuleValueList && $mValue->getListSeparator() == '/') { 264 list($oSize, $oHeight) = $mValue->getListComponents(); 265 $aFontProperties['font-size'] = $oSize; 266 $aFontProperties['line-height'] = $oHeight; 267 } else if ($mValue instanceof Size && $mValue->getUnit() !== null) { 268 $aFontProperties['font-size'] = $mValue; 269 } else { 270 $aFontProperties['font-family'] = $mValue; 271 } 272 } 273 foreach ($aFontProperties as $sProperty => $mValue) { 274 $oNewRule = new Rule($sProperty, $this->iLineNo); 275 $oNewRule->addValue($mValue); 276 $oNewRule->setIsImportant($oRule->getIsImportant()); 277 $this->addRule($oNewRule); 278 } 279 $this->removeRule('font'); 280 } 281 282 /* 283 * Convert shorthand background declarations 284 * (e.g. <tt>background: url("chess.png") gray 50% repeat fixed;</tt>) 285 * into their constituent parts. 286 * @see http://www.w3.org/TR/21/colors.html#propdef-background 287 * */ 288 289 public function expandBackgroundShorthand() { 290 $aRules = $this->getRulesAssoc(); 291 if (!isset($aRules['background'])) 292 return; 293 $oRule = $aRules['background']; 294 $aBgProperties = array( 295 'background-color' => array('transparent'), 'background-image' => array('none'), 296 'background-repeat' => array('repeat'), 'background-attachment' => array('scroll'), 297 'background-position' => array(new Size(0, '%', null, false, $this->iLineNo), new Size(0, '%', null, false, $this->iLineNo)) 298 ); 299 $mRuleValue = $oRule->getValue(); 300 $aValues = array(); 301 if (!$mRuleValue instanceof RuleValueList) { 302 $aValues[] = $mRuleValue; 303 } else { 304 $aValues = $mRuleValue->getListComponents(); 305 } 306 if (count($aValues) == 1 && $aValues[0] == 'inherit') { 307 foreach ($aBgProperties as $sProperty => $mValue) { 308 $oNewRule = new Rule($sProperty, $this->iLineNo); 309 $oNewRule->addValue('inherit'); 310 $oNewRule->setIsImportant($oRule->getIsImportant()); 311 $this->addRule($oNewRule); 312 } 313 $this->removeRule('background'); 314 return; 315 } 316 $iNumBgPos = 0; 317 foreach ($aValues as $mValue) { 318 if (!$mValue instanceof Value) { 319 $mValue = mb_strtolower($mValue); 320 } 321 if ($mValue instanceof URL) { 322 $aBgProperties['background-image'] = $mValue; 323 } else if ($mValue instanceof Color) { 324 $aBgProperties['background-color'] = $mValue; 325 } else if (in_array($mValue, array('scroll', 'fixed'))) { 326 $aBgProperties['background-attachment'] = $mValue; 327 } else if (in_array($mValue, array('repeat', 'no-repeat', 'repeat-x', 'repeat-y'))) { 328 $aBgProperties['background-repeat'] = $mValue; 329 } else if (in_array($mValue, array('left', 'center', 'right', 'top', 'bottom')) 330 || $mValue instanceof Size 331 ) { 332 if ($iNumBgPos == 0) { 333 $aBgProperties['background-position'][0] = $mValue; 334 $aBgProperties['background-position'][1] = 'center'; 335 } else { 336 $aBgProperties['background-position'][$iNumBgPos] = $mValue; 337 } 338 $iNumBgPos++; 339 } 340 } 341 foreach ($aBgProperties as $sProperty => $mValue) { 342 $oNewRule = new Rule($sProperty, $this->iLineNo); 343 $oNewRule->setIsImportant($oRule->getIsImportant()); 344 $oNewRule->addValue($mValue); 345 $this->addRule($oNewRule); 346 } 347 $this->removeRule('background'); 348 } 349 350 public function expandListStyleShorthand() { 351 $aListProperties = array( 352 'list-style-type' => 'disc', 353 'list-style-position' => 'outside', 354 'list-style-image' => 'none' 355 ); 356 $aListStyleTypes = array( 357 'none', 'disc', 'circle', 'square', 'decimal-leading-zero', 'decimal', 358 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', 'lower-latin', 359 'upper-alpha', 'upper-latin', 'hebrew', 'armenian', 'georgian', 'cjk-ideographic', 360 'hiragana', 'hira-gana-iroha', 'katakana-iroha', 'katakana' 361 ); 362 $aListStylePositions = array( 363 'inside', 'outside' 364 ); 365 $aRules = $this->getRulesAssoc(); 366 if (!isset($aRules['list-style'])) 367 return; 368 $oRule = $aRules['list-style']; 369 $mRuleValue = $oRule->getValue(); 370 $aValues = array(); 371 if (!$mRuleValue instanceof RuleValueList) { 372 $aValues[] = $mRuleValue; 373 } else { 374 $aValues = $mRuleValue->getListComponents(); 375 } 376 if (count($aValues) == 1 && $aValues[0] == 'inherit') { 377 foreach ($aListProperties as $sProperty => $mValue) { 378 $oNewRule = new Rule($sProperty, $this->iLineNo); 379 $oNewRule->addValue('inherit'); 380 $oNewRule->setIsImportant($oRule->getIsImportant()); 381 $this->addRule($oNewRule); 382 } 383 $this->removeRule('list-style'); 384 return; 385 } 386 foreach ($aValues as $mValue) { 387 if (!$mValue instanceof Value) { 388 $mValue = mb_strtolower($mValue); 389 } 390 if ($mValue instanceof Url) { 391 $aListProperties['list-style-image'] = $mValue; 392 } else if (in_array($mValue, $aListStyleTypes)) { 393 $aListProperties['list-style-types'] = $mValue; 394 } else if (in_array($mValue, $aListStylePositions)) { 395 $aListProperties['list-style-position'] = $mValue; 396 } 397 } 398 foreach ($aListProperties as $sProperty => $mValue) { 399 $oNewRule = new Rule($sProperty, $this->iLineNo); 400 $oNewRule->setIsImportant($oRule->getIsImportant()); 401 $oNewRule->addValue($mValue); 402 $this->addRule($oNewRule); 403 } 404 $this->removeRule('list-style'); 405 } 406 407 public function createShorthandProperties(array $aProperties, $sShorthand) { 408 $aRules = $this->getRulesAssoc(); 409 $aNewValues = array(); 410 foreach ($aProperties as $sProperty) { 411 if (!isset($aRules[$sProperty])) 412 continue; 413 $oRule = $aRules[$sProperty]; 414 if (!$oRule->getIsImportant()) { 415 $mRuleValue = $oRule->getValue(); 416 $aValues = array(); 417 if (!$mRuleValue instanceof RuleValueList) { 418 $aValues[] = $mRuleValue; 419 } else { 420 $aValues = $mRuleValue->getListComponents(); 421 } 422 foreach ($aValues as $mValue) { 423 $aNewValues[] = $mValue; 424 } 425 $this->removeRule($sProperty); 426 } 427 } 428 if (count($aNewValues)) { 429 $oNewRule = new Rule($sShorthand, $this->iLineNo); 430 foreach ($aNewValues as $mValue) { 431 $oNewRule->addValue($mValue); 432 } 433 $this->addRule($oNewRule); 434 } 435 } 436 437 public function createBackgroundShorthand() { 438 $aProperties = array( 439 'background-color', 'background-image', 'background-repeat', 440 'background-position', 'background-attachment' 441 ); 442 $this->createShorthandProperties($aProperties, 'background'); 443 } 444 445 public function createListStyleShorthand() { 446 $aProperties = array( 447 'list-style-type', 'list-style-position', 'list-style-image' 448 ); 449 $this->createShorthandProperties($aProperties, 'list-style'); 450 } 451 452 /** 453 * Combine border-color, border-style and border-width into border 454 * Should be run after create_dimensions_shorthand! 455 * */ 456 public function createBorderShorthand() { 457 $aProperties = array( 458 'border-width', 'border-style', 'border-color' 459 ); 460 $this->createShorthandProperties($aProperties, 'border'); 461 } 462 463 /* 464 * Looks for long format CSS dimensional properties 465 * (margin, padding, border-color, border-style and border-width) 466 * and converts them into shorthand CSS properties. 467 * */ 468 469 public function createDimensionsShorthand() { 470 $aPositions = array('top', 'right', 'bottom', 'left'); 471 $aExpansions = array( 472 'margin' => 'margin-%s', 473 'padding' => 'padding-%s', 474 'border-color' => 'border-%s-color', 475 'border-style' => 'border-%s-style', 476 'border-width' => 'border-%s-width' 477 ); 478 $aRules = $this->getRulesAssoc(); 479 foreach ($aExpansions as $sProperty => $sExpanded) { 480 $aFoldable = array(); 481 foreach ($aRules as $sRuleName => $oRule) { 482 foreach ($aPositions as $sPosition) { 483 if ($sRuleName == sprintf($sExpanded, $sPosition)) { 484 $aFoldable[$sRuleName] = $oRule; 485 } 486 } 487 } 488 // All four dimensions must be present 489 if (count($aFoldable) == 4) { 490 $aValues = array(); 491 foreach ($aPositions as $sPosition) { 492 $oRule = $aRules[sprintf($sExpanded, $sPosition)]; 493 $mRuleValue = $oRule->getValue(); 494 $aRuleValues = array(); 495 if (!$mRuleValue instanceof RuleValueList) { 496 $aRuleValues[] = $mRuleValue; 497 } else { 498 $aRuleValues = $mRuleValue->getListComponents(); 499 } 500 $aValues[$sPosition] = $aRuleValues; 501 } 502 $oNewRule = new Rule($sProperty, $this->iLineNo); 503 if ((string) $aValues['left'][0] == (string) $aValues['right'][0]) { 504 if ((string) $aValues['top'][0] == (string) $aValues['bottom'][0]) { 505 if ((string) $aValues['top'][0] == (string) $aValues['left'][0]) { 506 // All 4 sides are equal 507 $oNewRule->addValue($aValues['top']); 508 } else { 509 // Top and bottom are equal, left and right are equal 510 $oNewRule->addValue($aValues['top']); 511 $oNewRule->addValue($aValues['left']); 512 } 513 } else { 514 // Only left and right are equal 515 $oNewRule->addValue($aValues['top']); 516 $oNewRule->addValue($aValues['left']); 517 $oNewRule->addValue($aValues['bottom']); 518 } 519 } else { 520 // No sides are equal 521 $oNewRule->addValue($aValues['top']); 522 $oNewRule->addValue($aValues['left']); 523 $oNewRule->addValue($aValues['bottom']); 524 $oNewRule->addValue($aValues['right']); 525 } 526 $this->addRule($oNewRule); 527 foreach ($aPositions as $sPosition) { 528 $this->removeRule(sprintf($sExpanded, $sPosition)); 529 } 530 } 531 } 532 } 533 534 /** 535 * Looks for long format CSS font properties (e.g. <tt>font-weight</tt>) and 536 * tries to convert them into a shorthand CSS <tt>font</tt> property. 537 * At least font-size AND font-family must be present in order to create a shorthand declaration. 538 * */ 539 public function createFontShorthand() { 540 $aFontProperties = array( 541 'font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family' 542 ); 543 $aRules = $this->getRulesAssoc(); 544 if (!isset($aRules['font-size']) || !isset($aRules['font-family'])) { 545 return; 546 } 547 $oNewRule = new Rule('font', $this->iLineNo); 548 foreach (array('font-style', 'font-variant', 'font-weight') as $sProperty) { 549 if (isset($aRules[$sProperty])) { 550 $oRule = $aRules[$sProperty]; 551 $mRuleValue = $oRule->getValue(); 552 $aValues = array(); 553 if (!$mRuleValue instanceof RuleValueList) { 554 $aValues[] = $mRuleValue; 555 } else { 556 $aValues = $mRuleValue->getListComponents(); 557 } 558 if ($aValues[0] !== 'normal') { 559 $oNewRule->addValue($aValues[0]); 560 } 561 } 562 } 563 // Get the font-size value 564 $oRule = $aRules['font-size']; 565 $mRuleValue = $oRule->getValue(); 566 $aFSValues = array(); 567 if (!$mRuleValue instanceof RuleValueList) { 568 $aFSValues[] = $mRuleValue; 569 } else { 570 $aFSValues = $mRuleValue->getListComponents(); 571 } 572 // But wait to know if we have line-height to add it 573 if (isset($aRules['line-height'])) { 574 $oRule = $aRules['line-height']; 575 $mRuleValue = $oRule->getValue(); 576 $aLHValues = array(); 577 if (!$mRuleValue instanceof RuleValueList) { 578 $aLHValues[] = $mRuleValue; 579 } else { 580 $aLHValues = $mRuleValue->getListComponents(); 581 } 582 if ($aLHValues[0] !== 'normal') { 583 $val = new RuleValueList('/', $this->iLineNo); 584 $val->addListComponent($aFSValues[0]); 585 $val->addListComponent($aLHValues[0]); 586 $oNewRule->addValue($val); 587 } 588 } else { 589 $oNewRule->addValue($aFSValues[0]); 590 } 591 $oRule = $aRules['font-family']; 592 $mRuleValue = $oRule->getValue(); 593 $aFFValues = array(); 594 if (!$mRuleValue instanceof RuleValueList) { 595 $aFFValues[] = $mRuleValue; 596 } else { 597 $aFFValues = $mRuleValue->getListComponents(); 598 } 599 $oFFValue = new RuleValueList(',', $this->iLineNo); 600 $oFFValue->setListComponents($aFFValues); 601 $oNewRule->addValue($oFFValue); 602 603 $this->addRule($oNewRule); 604 foreach ($aFontProperties as $sProperty) { 605 $this->removeRule($sProperty); 606 } 607 } 608 609 public function __toString() { 610 return $this->render(new \Sabberworm\CSS\OutputFormat()); 611 } 612 613 public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) { 614 if(count($this->aSelectors) === 0) { 615 // If all the selectors have been removed, this declaration block becomes invalid 616 throw new OutputException("Attempt to print declaration block with missing selector", $this->iLineNo); 617 } 618 $sResult = $oOutputFormat->sBeforeDeclarationBlock; 619 $sResult .= $oOutputFormat->implode($oOutputFormat->spaceBeforeSelectorSeparator() . ',' . $oOutputFormat->spaceAfterSelectorSeparator(), $this->aSelectors); 620 $sResult .= $oOutputFormat->sAfterDeclarationBlockSelectors; 621 $sResult .= $oOutputFormat->spaceBeforeOpeningBrace() . '{'; 622 $sResult .= parent::render($oOutputFormat); 623 $sResult .= '}'; 624 $sResult .= $oOutputFormat->sAfterDeclarationBlock; 625 return $sResult; 626 } 627 628 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body