MDL-65759 library: Update php-css-parser to 8.3.0
[moodle.git] / lib / php-css-parser / RuleSet / RuleSet.php
1 <?php
3 namespace Sabberworm\CSS\RuleSet;
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;
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 {
17         private $aRules;
18         protected $iLineNo;
19         protected $aComments;
21         public function __construct($iLineNo = 0) {
22                 $this->aRules = array();
23                 $this->iLineNo = $iLineNo;
24                 $this->aComments = array();
25         }
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         }
62         /**
63          * @return int
64          */
65         public function getLineNo() {
66                 return $this->iLineNo;
67         }
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                 }
75                 $iPosition = count($this->aRules[$sRule]);
77                 if ($oSibling !== null) {
78                         $iSiblingPos = array_search($oSibling, $this->aRules[$sRule], true);
79                         if ($iSiblingPos !== false) {
80                                 $iPosition = $iSiblingPos;
81                         }
82                 }
84                 array_splice($this->aRules[$sRule], $iPosition, 0, array($oRule));
85         }
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         }
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         }
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         }
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         }
158         public function __toString() {
159                 return $this->render(new \Sabberworm\CSS\OutputFormat());
160         }
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                 }
188                 return $oOutputFormat->removeLastSemicolon($sResult);
189         }
191         /**
192          * @param array $aComments Array of comments.
193          */
194         public function addComments(array $aComments) {
195                 $this->aComments = array_merge($this->aComments, $aComments);
196         }
198         /**
199          * @return array
200          */
201         public function getComments() {
202                 return $this->aComments;
203         }
205         /**
206          * @param array $aComments Array containing Comment objects.
207          */
208         public function setComments(array $aComments) {
209                 $this->aComments = $aComments;
210         }