PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 10.0.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v10.0.1
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
acymailing / back / Libraries / Sabberworm / CSS / src / RuleSet / RuleSet.php

RuleSet.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 10.0.1, at back/Libraries/Sabberworm/CSS/src/RuleSet/RuleSet.php

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