MDL-65759 library: Update php-css-parser to 8.3.0
[moodle.git] / lib / php-css-parser / RuleSet / RuleSet.php
CommitLineData
fbe18cc0
FM
1<?php
2
3namespace Sabberworm\CSS\RuleSet;
4
fbe18cc0 5use Sabberworm\CSS\Comment\Commentable;
376eb156
MM
6use Sabberworm\CSS\Parsing\ParserState;
7use Sabberworm\CSS\Parsing\UnexpectedTokenException;
8use Sabberworm\CSS\Renderable;
9use Sabberworm\CSS\Rule\Rule;
fbe18cc0
FM
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 */
15abstract 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
376eb156
MM
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
fbe18cc0
FM
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().
376eb156 92 * @return Rule[] Rules.
fbe18cc0
FM
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.
376eb156 110 * @param Rule[] $aRules The rules to override with.
fbe18cc0
FM
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.
376eb156 123 * @return Rule[] Rules.
fbe18cc0
FM
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 /**
376eb156
MM
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 */
fbe18cc0
FM
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}