PluginProbe
WP Job Manager / 2.4.7
WP Job Manager v2.4.7
2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 1.1.2 1.1.3 1.10.0 1.11.0 1.11.1 1.12.0 1.12.1 1.13.0 1.14.0 1.15.0 All 154 releases
wp-job-manager / vendor / sabberworm / php-css-parser / src / RuleSet / RuleSet.php
RuleSet.php
333 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Sabberworm\CSS\RuleSet;
4
5 use Sabberworm\CSS\Comment\Comment;
6 use Sabberworm\CSS\Comment\Commentable;
7 use Sabberworm\CSS\OutputFormat;
8 use Sabberworm\CSS\Parsing\ParserState;
9 use Sabberworm\CSS\Parsing\UnexpectedEOFException;
10 use Sabberworm\CSS\Parsing\UnexpectedTokenException;
11 use Sabberworm\CSS\Renderable;
12 use Sabberworm\CSS\Rule\Rule;
13
14 /**
15 * This class is a container for individual 'Rule's.
16 *
17 * The most common form of a rule set is one constrained by a selector, i.e., a `DeclarationBlock`.
18 * However, unknown `AtRule`s (like `@font-face`) are rule sets as well.
19 *
20 * If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)`
21 * (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).
22 */
23 abstract class RuleSet implements Renderable, Commentable
24 {
25 /**
26 * @var array<string, Rule>
27 */
28 private $aRules;
29
30 /**
31 * @var int
32 */
33 protected $iLineNo;
34
35 /**
36 * @var array<array-key, Comment>
37 */
38 protected $aComments;
39
40 /**
41 * @param int $iLineNo
42 */
43 public function __construct($iLineNo = 0)
44 {
45 $this->aRules = [];
46 $this->iLineNo = $iLineNo;
47 $this->aComments = [];
48 }
49
50 /**
51 * @return void
52 *
53 * @throws UnexpectedTokenException
54 * @throws UnexpectedEOFException
55 */
56 public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet)
57 {
58 while ($oParserState->comes(';')) {
59 $oParserState->consume(';');
60 }
61 while (!$oParserState->comes('}')) {
62 $oRule = null;
63 if ($oParserState->getSettings()->bLenientParsing) {
64 try {
65 $oRule = Rule::parse($oParserState);
66 } catch (UnexpectedTokenException $e) {
67 try {
68 $sConsume = $oParserState->consumeUntil(["\n", ";", '}'], true);
69 // We need to “unfind” the matches to the end of the ruleSet as this will be matched later
70 if ($oParserState->streql(substr($sConsume, -1), '}')) {
71 $oParserState->backtrack(1);
72 } else {
73 while ($oParserState->comes(';')) {
74 $oParserState->consume(';');
75 }
76 }
77 } catch (UnexpectedTokenException $e) {
78 // We’ve reached the end of the document. Just close the RuleSet.
79 return;
80 }
81 }
82 } else {
83 $oRule = Rule::parse($oParserState);
84 }
85 if ($oRule) {
86 $oRuleSet->addRule($oRule);
87 }
88 }
89 $oParserState->consume('}');
90 }
91
92 /**
93 * @return int
94 */
95 public function getLineNo()
96 {
97 return $this->iLineNo;
98 }
99
100 /**
101 * @param Rule|null $oSibling
102 *
103 * @return void
104 */
105 public function addRule(Rule $oRule, Rule $oSibling = null)
106 {
107 $sRule = $oRule->getRule();
108 if (!isset($this->aRules[$sRule])) {
109 $this->aRules[$sRule] = [];
110 }
111
112 $iPosition = count($this->aRules[$sRule]);
113
114 if ($oSibling !== null) {
115 $iSiblingPos = array_search($oSibling, $this->aRules[$sRule], true);
116 if ($iSiblingPos !== false) {
117 $iPosition = $iSiblingPos;
118 $oRule->setPosition($oSibling->getLineNo(), $oSibling->getColNo() - 1);
119 }
120 }
121 if ($oRule->getLineNo() === 0 && $oRule->getColNo() === 0) {
122 //this node is added manually, give it the next best line
123 $rules = $this->getRules();
124 $pos = count($rules);
125 if ($pos > 0) {
126 $last = $rules[$pos - 1];
127 $oRule->setPosition($last->getLineNo() + 1, 0);
128 }
129 }
130
131 array_splice($this->aRules[$sRule], $iPosition, 0, [$oRule]);
132 }
133
134 /**
135 * Returns all rules matching the given rule name
136 *
137 * @example $oRuleSet->getRules('font') // returns array(0 => $oRule, …) or array().
138 *
139 * @example $oRuleSet->getRules('font-')
140 * //returns an array of all rules either beginning with font- or matching font.
141 *
142 * @param Rule|string|null $mRule
143 * Pattern to search for. If null, returns all rules.
144 * If the pattern ends with a dash, all rules starting with the pattern are returned
145 * as well as one matching the pattern with the dash excluded.
146 * Passing a Rule behaves like calling `getRules($mRule->getRule())`.
147 *
148 * @return array<int, Rule>
149 */
150 public function getRules($mRule = null)
151 {
152 if ($mRule instanceof Rule) {
153 $mRule = $mRule->getRule();
154 }
155 /** @var array<int, Rule> $aResult */
156 $aResult = [];
157 foreach ($this->aRules as $sName => $aRules) {
158 // Either no search rule is given or the search rule matches the found rule exactly
159 // or the search rule ends in “-” and the found rule starts with the search rule.
160 if (
161 !$mRule || $sName === $mRule
162 || (
163 strrpos($mRule, '-') === strlen($mRule) - strlen('-')
164 && (strpos($sName, $mRule) === 0 || $sName === substr($mRule, 0, -1))
165 )
166 ) {
167 $aResult = array_merge($aResult, $aRules);
168 }
169 }
170 usort($aResult, function (Rule $first, Rule $second) {
171 if ($first->getLineNo() === $second->getLineNo()) {
172 return $first->getColNo() - $second->getColNo();
173 }
174 return $first->getLineNo() - $second->getLineNo();
175 });
176 return $aResult;
177 }
178
179 /**
180 * Overrides all the rules of this set.
181 *
182 * @param array<array-key, Rule> $aRules The rules to override with.
183 *
184 * @return void
185 */
186 public function setRules(array $aRules)
187 {
188 $this->aRules = [];
189 foreach ($aRules as $rule) {
190 $this->addRule($rule);
191 }
192 }
193
194 /**
195 * Returns all rules matching the given pattern and returns them in an associative array with the rule’s name
196 * as keys. This method exists mainly for backwards-compatibility and is really only partially useful.
197 *
198 * Note: This method loses some information: Calling this (with an argument of `background-`) on a declaration block
199 * like `{ background-color: green; background-color; rgba(0, 127, 0, 0.7); }` will only yield an associative array
200 * containing the rgba-valued rule while `getRules()` would yield an indexed array containing both.
201 *
202 * @param Rule|string|null $mRule $mRule
203 * Pattern to search for. If null, returns all rules. If the pattern ends with a dash,
204 * all rules starting with the pattern are returned as well as one matching the pattern with the dash
205 * excluded. Passing a Rule behaves like calling `getRules($mRule->getRule())`.
206 *
207 * @return array<string, Rule>
208 */
209 public function getRulesAssoc($mRule = null)
210 {
211 /** @var array<string, Rule> $aResult */
212 $aResult = [];
213 foreach ($this->getRules($mRule) as $oRule) {
214 $aResult[$oRule->getRule()] = $oRule;
215 }
216 return $aResult;
217 }
218
219 /**
220 * Removes a rule from this RuleSet. This accepts all the possible values that `getRules()` accepts.
221 *
222 * If given a Rule, it will only remove this particular rule (by identity).
223 * If given a name, it will remove all rules by that name.
224 *
225 * Note: this is different from pre-v.2.0 behaviour of PHP-CSS-Parser, where passing a Rule instance would
226 * remove all rules with the same name. To get the old behaviour, use `removeRule($oRule->getRule())`.
227 *
228 * @param Rule|string|null $mRule
229 * pattern to remove. If $mRule is null, all rules are removed. If the pattern ends in a dash,
230 * all rules starting with the pattern are removed as well as one matching the pattern with the dash
231 * excluded. Passing a Rule behaves matches by identity.
232 *
233 * @return void
234 */
235 public function removeRule($mRule)
236 {
237 if ($mRule instanceof Rule) {
238 $sRule = $mRule->getRule();
239 if (!isset($this->aRules[$sRule])) {
240 return;
241 }
242 foreach ($this->aRules[$sRule] as $iKey => $oRule) {
243 if ($oRule === $mRule) {
244 unset($this->aRules[$sRule][$iKey]);
245 }
246 }
247 } else {
248 foreach ($this->aRules as $sName => $aRules) {
249 // Either no search rule is given or the search rule matches the found rule exactly
250 // or the search rule ends in “-” and the found rule starts with the search rule or equals it
251 // (without the trailing dash).
252 if (
253 !$mRule || $sName === $mRule
254 || (strrpos($mRule, '-') === strlen($mRule) - strlen('-')
255 && (strpos($sName, $mRule) === 0 || $sName === substr($mRule, 0, -1)))
256 ) {
257 unset($this->aRules[$sName]);
258 }
259 }
260 }
261 }
262
263 /**
264 * @return string
265 */
266 public function __toString()
267 {
268 return $this->render(new OutputFormat());
269 }
270
271 /**
272 * @return string
273 */
274 protected function renderRules(OutputFormat $oOutputFormat)
275 {
276 $sResult = '';
277 $bIsFirst = true;
278 $oNextLevel = $oOutputFormat->nextLevel();
279 foreach ($this->aRules as $aRules) {
280 foreach ($aRules as $oRule) {
281 $sRendered = $oNextLevel->safely(function () use ($oRule, $oNextLevel) {
282 return $oRule->render($oNextLevel);
283 });
284 if ($sRendered === null) {
285 continue;
286 }
287 if ($bIsFirst) {
288 $bIsFirst = false;
289 $sResult .= $oNextLevel->spaceBeforeRules();
290 } else {
291 $sResult .= $oNextLevel->spaceBetweenRules();
292 }
293 $sResult .= $sRendered;
294 }
295 }
296
297 if (!$bIsFirst) {
298 // Had some output
299 $sResult .= $oOutputFormat->spaceAfterRules();
300 }
301
302 return $oOutputFormat->removeLastSemicolon($sResult);
303 }
304
305 /**
306 * @param array<string, Comment> $aComments
307 *
308 * @return void
309 */
310 public function addComments(array $aComments)
311 {
312 $this->aComments = array_merge($this->aComments, $aComments);
313 }
314
315 /**
316 * @return array<string, Comment>
317 */
318 public function getComments()
319 {
320 return $this->aComments;
321 }
322
323 /**
324 * @param array<string, Comment> $aComments
325 *
326 * @return void
327 */
328 public function setComments(array $aComments)
329 {
330 $this->aComments = $aComments;
331 }
332 }
333