Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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\Comment\Commentable;
   6  use Sabberworm\CSS\Parsing\ParserState;
   7  use Sabberworm\CSS\Parsing\UnexpectedTokenException;
   8  use Sabberworm\CSS\Renderable;
   9  use Sabberworm\CSS\Rule\Rule;
  10  
  11  /**
  12   * RuleSet is a generic superclass denoting rules. The typical example for rule sets are declaration block.
  13   * However, unknown At-Rules (like @font-face) are also rule sets.
  14   */
  15  abstract class RuleSet implements Renderable, Commentable {
  16  
  17  	 private $aRules;
  18  	 protected $iLineNo;
  19  	 protected $aComments;
  20  
  21  	public function __construct($iLineNo = 0) {
  22  	 	 $this->aRules = array();
  23  	 	 $this->iLineNo = $iLineNo;
  24  	 	 $this->aComments = array();
  25  	 }
  26  
  27  	public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet) {
  28  	 	 while ($oParserState->comes(';')) {
  29  	 	 	 $oParserState->consume(';');
  30  	 	 }
  31  	 	 while (!$oParserState->comes('}')) {
  32  	 	 	 $oRule = null;
  33  	 	 	 if($oParserState->getSettings()->bLenientParsing) {
  34  	 	 	 	 try {
  35  	 	 	 	 	 $oRule = Rule::parse($oParserState);
  36  	 	 	 	 } catch (UnexpectedTokenException $e) {
  37  	 	 	 	 	 try {
  38  	 	 	 	 	 	 $sConsume = $oParserState->consumeUntil(array("\n", ";", '}'), true);
  39  	 	 	 	 	 	 // We need to “unfind” the matches to the end of the ruleSet as this will be matched later
  40  	 	 	 	 	 	 if($oParserState->streql(substr($sConsume, -1), '}')) {
  41  	 	 	 	 	 	 	 $oParserState->backtrack(1);
  42  	 	 	 	 	 	 } else {
  43  	 	 	 	 	 	 	 while ($oParserState->comes(';')) {
  44  	 	 	 	 	 	 	 	 $oParserState->consume(';');
  45  	 	 	 	 	 	 	 }
  46  	 	 	 	 	 	 }
  47  	 	 	 	 	 } catch (UnexpectedTokenException $e) {
  48  	 	 	 	 	 	 // We’ve reached the end of the document. Just close the RuleSet.
  49  	 	 	 	 	 	 return;
  50  	 	 	 	 	 }
  51  	 	 	 	 }
  52  	 	 	 } else {
  53  	 	 	 	 $oRule = Rule::parse($oParserState);
  54  	 	 	 }
  55  	 	 	 if($oRule) {
  56  	 	 	 	 $oRuleSet->addRule($oRule);
  57  	 	 	 }
  58  	 	 }
  59  	 	 $oParserState->consume('}');
  60  	 }
  61  
  62  	 /**
  63  	  * @return int
  64  	  */
  65  	public function getLineNo() {
  66  	 	 return $this->iLineNo;
  67  	 }
  68  
  69  	public function addRule(Rule $oRule, Rule $oSibling = null) {
  70  	 	 $sRule = $oRule->getRule();
  71  	 	 if(!isset($this->aRules[$sRule])) {
  72  	 	 	 $this->aRules[$sRule] = array();
  73  	 	 }
  74  
  75  	 	 $iPosition = count($this->aRules[$sRule]);
  76  
  77  	 	 if ($oSibling !== null) {
  78  	 	 	 $iSiblingPos = array_search($oSibling, $this->aRules[$sRule], true);
  79  	 	 	 if ($iSiblingPos !== false) {
  80  	 	 	 	 $iPosition = $iSiblingPos;
  81  	 	 	 }
  82  	 	 }
  83  
  84  	 	 array_splice($this->aRules[$sRule], $iPosition, 0, array($oRule));
  85  	 }
  86  
  87  	 /**
  88  	  * Returns all rules matching the given rule name
  89  	  * @param (null|string|Rule) $mRule pattern to search for. If null, returns all rules. if the pattern ends with a dash, all rules starting with the pattern are returned as well as one matching the pattern with the dash excluded. passing a Rule behaves like calling getRules($mRule->getRule()).
  90  	  * @example $oRuleSet->getRules('font-') //returns an array of all rules either beginning with font- or matching font.
  91  	  * @example $oRuleSet->getRules('font') //returns array(0 => $oRule, …) or array().
  92  	  * @return Rule[] Rules.
  93  	  */
  94  	public function getRules($mRule = null) {
  95  	 	 if ($mRule instanceof Rule) {
  96  	 	 	 $mRule = $mRule->getRule();
  97  	 	 }
  98  	 	 $aResult = array();
  99  	 	 foreach($this->aRules as $sName => $aRules) {
 100  	 	 	 // Either no search rule is given or the search rule matches the found rule exactly or the search rule ends in “-” and the found rule starts with the search rule.
 101  	 	 	 if(!$mRule || $sName === $mRule || (strrpos($mRule, '-') === strlen($mRule) - strlen('-') && (strpos($sName, $mRule) === 0 || $sName === substr($mRule, 0, -1)))) {
 102  	 	 	 	 $aResult = array_merge($aResult, $aRules);
 103  	 	 	 }
 104  	 	 }
 105  	 	 return $aResult;
 106  	 }
 107  
 108  	 /**
 109  	  * Override all the rules of this set.
 110  	  * @param Rule[] $aRules The rules to override with.
 111  	  */
 112  	public function setRules(array $aRules) {
 113  	 	 $this->aRules = array();
 114  	 	 foreach ($aRules as $rule) {
 115  	 	 	 $this->addRule($rule);
 116  	 	 }
 117  	 }
 118  
 119  	 /**
 120  	  * Returns all rules matching the given pattern and returns them in an associative array with the rule’s name as keys. This method exists mainly for backwards-compatibility and is really only partially useful.
 121  	  * @param (string) $mRule pattern to search for. If null, returns all rules. if the pattern ends with a dash, all rules starting with the pattern are returned as well as one matching the pattern with the dash excluded. passing a Rule behaves like calling getRules($mRule->getRule()).
 122  	  * Note: This method loses some information: Calling this (with an argument of 'background-') on a declaration block like { background-color: green; background-color; rgba(0, 127, 0, 0.7); } will only yield an associative array containing the rgba-valued rule while @link{getRules()} would yield an indexed array containing both.
 123  	  * @return Rule[] Rules.
 124  	  */
 125  	public function getRulesAssoc($mRule = null) {
 126  	 	 $aResult = array();
 127  	 	 foreach($this->getRules($mRule) as $oRule) {
 128  	 	 	 $aResult[$oRule->getRule()] = $oRule;
 129  	 	 }
 130  	 	 return $aResult;
 131  	 }
 132  
 133  	 /**
 134  	  * Remove a rule from this RuleSet. This accepts all the possible values that @link{getRules()} accepts. If given a Rule, it will only remove this particular rule (by identity). If given a name, it will remove all rules by that name. Note: this is different from pre-v.2.0 behaviour of PHP-CSS-Parser, where passing a Rule instance would remove all rules with the same name. To get the old behvaiour, use removeRule($oRule->getRule()).
 135  	  * @param (null|string|Rule) $mRule pattern to remove. If $mRule is null, all rules are removed. If the pattern ends in a dash, all rules starting with the pattern are removed as well as one matching the pattern with the dash excluded. Passing a Rule behaves matches by identity.
 136  	  */
 137  	public function removeRule($mRule) {
 138  	 	 if($mRule instanceof Rule) {
 139  	 	 	 $sRule = $mRule->getRule();
 140  	 	 	 if(!isset($this->aRules[$sRule])) {
 141  	 	 	 	 return;
 142  	 	 	 }
 143  	 	 	 foreach($this->aRules[$sRule] as $iKey => $oRule) {
 144  	 	 	 	 if($oRule === $mRule) {
 145  	 	 	 	 	 unset($this->aRules[$sRule][$iKey]);
 146  	 	 	 	 }
 147  	 	 	 }
 148  	 	 } else {
 149  	 	 	 foreach($this->aRules as $sName => $aRules) {
 150  	 	 	 	 // Either no search rule is given or the search rule matches the found rule exactly or the search rule ends in “-” and the found rule starts with the search rule or equals it (without the trailing dash).
 151  	 	 	 	 if(!$mRule || $sName === $mRule || (strrpos($mRule, '-') === strlen($mRule) - strlen('-') && (strpos($sName, $mRule) === 0 || $sName === substr($mRule, 0, -1)))) {
 152  	 	 	 	 	 unset($this->aRules[$sName]);
 153  	 	 	 	 }
 154  	 	 	 }
 155  	 	 }
 156  	 }
 157  
 158  	public function __toString() {
 159  	 	 return $this->render(new \Sabberworm\CSS\OutputFormat());
 160  	 }
 161  
 162  	public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) {
 163  	 	 $sResult = '';
 164  	 	 $bIsFirst = true;
 165  	 	 foreach ($this->aRules as $aRules) {
 166  	 	 	 foreach($aRules as $oRule) {
 167  	 	 	 	 $sRendered = $oOutputFormat->safely(function() use ($oRule, $oOutputFormat) {
 168  	 	 	 	 	 return $oRule->render($oOutputFormat->nextLevel());
 169  	 	 	 	 });
 170  	 	 	 	 if($sRendered === null) {
 171  	 	 	 	 	 continue;
 172  	 	 	 	 }
 173  	 	 	 	 if($bIsFirst) {
 174  	 	 	 	 	 $bIsFirst = false;
 175  	 	 	 	 	 $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules();
 176  	 	 	 	 } else {
 177  	 	 	 	 	 $sResult .= $oOutputFormat->nextLevel()->spaceBetweenRules();
 178  	 	 	 	 }
 179  	 	 	 	 $sResult .= $sRendered;
 180  	 	 	 }
 181  	 	 }
 182  	 	 
 183  	 	 if(!$bIsFirst) {
 184  	 	 	 // Had some output
 185  	 	 	 $sResult .= $oOutputFormat->spaceAfterRules();
 186  	 	 }
 187  
 188  	 	 return $oOutputFormat->removeLastSemicolon($sResult);
 189  	 }
 190  
 191  	 /**
 192  	  * @param array $aComments Array of comments.
 193  	  */
 194  	public function addComments(array $aComments) {
 195  	 	 $this->aComments = array_merge($this->aComments, $aComments);
 196  	 }
 197  
 198  	 /**
 199  	  * @return array
 200  	  */
 201  	public function getComments() {
 202  	 	 return $this->aComments;
 203  	 }
 204  
 205  	 /**
 206  	  * @param array $aComments Array containing Comment objects.
 207  	  */
 208  	public function setComments(array $aComments) {
 209  	 	 $this->aComments = $aComments;
 210  	 }
 211  
 212  }