Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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  }