Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 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, $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
}