Completed
Push — master ( 2fe87d...547304 )
by Kevin
03:35 queued 01:05
created

RelevantSelectorParser::setCharset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
0 ignored issues
show
Security Bug introduced by
It is not recommended to output anything before PHP's opening tag in non-template files.
Loading history...
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 27 and the first side effect is on line 1.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
<?php namespace Luminaire\Premailer\Parser;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Namespace declaration statement has to be the very first statement in the script
Loading history...
3
4
/**
5
 * Created by Sublime Text 3
6
 *
7
 * @user     Kevin Tanjung
8
 * @website  http://kevintanjung.github.io
9
 * @email    [email protected]
10
 * @date     04/08/2016
11
 * @time     09:09
12
 */
13
14
use Crossjoin\Css\Reader\CssString as StylesheetReader;
15
use Crossjoin\Css\Format\Rule\AtMedia\MediaQuery;
16
use Crossjoin\Css\Format\Rule\AtMedia\MediaRule;
17
use Crossjoin\Css\Format\Rule\Style\StyleRuleSet;
18
use Crossjoin\Css\Format\Rule\Style\StyleSelector;
19
use Illuminate\Support\Arr;
20
use InvalidArgumentException;
21
22
/**
23
 * Retrieve relevant CSS selector from a given CSS rules
24
 *
25
 * @package  \Luminaire\Poseidon\Parser
26
 */
27
class RelevantSelectorParser
28
{
29
30
    /**
31
     * The pseudo classes that can be set in a style attribute and that are
32
     * supported by the Symfony CssSelector (doesn't support CSS4 yet).
33
     *
34
     * @var array
35
     */
36
    protected $allowed_pseudo_classes = [
37
        StyleSelector::PSEUDO_CLASS_FIRST_CHILD,
38
        StyleSelector::PSEUDO_CLASS_ROOT,
39
        StyleSelector::PSEUDO_CLASS_NTH_CHILD,
40
        StyleSelector::PSEUDO_CLASS_NTH_LAST_CHILD,
41
        StyleSelector::PSEUDO_CLASS_NTH_OF_TYPE,
42
        StyleSelector::PSEUDO_CLASS_NTH_LAST_OF_TYPE,
43
        StyleSelector::PSEUDO_CLASS_LAST_CHILD,
44
        StyleSelector::PSEUDO_CLASS_FIRST_OF_TYPE,
45
        StyleSelector::PSEUDO_CLASS_LAST_OF_TYPE,
46
        StyleSelector::PSEUDO_CLASS_ONLY_CHILD,
47
        StyleSelector::PSEUDO_CLASS_ONLY_OF_TYPE,
48
        StyleSelector::PSEUDO_CLASS_EMPTY,
49
        StyleSelector::PSEUDO_CLASS_NOT,
50
    ];
51
52
    /**
53
     * The stylesheet reader instance
54
     *
55
     * @var \Crossjoin\Css\Reader\CssString
56
     */
57
    protected $reader;
58
59
    /**
60
     * The charset of the stylesheet
61
     *
62
     * @var string
63
     */
64
    protected $charset;
65
66
    /**
67
     * Create a new instance of "Relevant Selector Parser"
68
     *
69
     * @param  \Crossjoin\Css\Reader\CssString|string|null  $stylesheet
70
     * @param  string                                       $charset
71
     */
72
    public function __construct($stylesheet = null, $charset = 'UTF-8')
73
    {
74
        $this->charset = $charset;
75
76
        if ( ! is_null($stylesheet))
77
        {
78
            $this->setStylesheetReader($stylesheet);
79
        }
80
    }
81
82
    /**
83
     * Get the relevant selectors
84
     *
85
     * @return array
86
     */
87
    public function extract()
88
    {
89
        $selectors = [];
90
        $rules     = $this->reader->getStyleSheet()->getRules();
91
        $relevant  = $this->getRelevantStyleRules($rules);
92
93
        foreach ($relevant as $rule)
94
        {
95
            $this->populateSelectors($selectors, $rule);
96
        }
97
98
        return $selectors;
99
    }
100
101
    /**
102
     * Store the selectors from the rule to the tank
103
     *
104
     * @param  array   &$tank
105
     * @param  array   $selectors
106
     * @return void
107
     */
108
    protected function populateSelectors(array &$tank, $rule)
109
    {
110
        foreach ($rule->getSelectors() as $selector)
111
        {
112
            if ( ! $this->isPseudoClassAllowed($selector))
113
            {
114
                continue;
115
            }
116
117
            $$this->prepareSelectorArray(
118
                $tank,
119
                $selector->getSpecificity(),
120
                $selector->getValue()
121
            );
122
123
            foreach ($rule->getDeclarations() as $declaration)
124
            {
125
                $this->storeDeclaration(
126
                    $tank,
127
                    $declaration,
128
                    $selector->getSpecificity(),
129
                    $selector->getValue()
130
                );
131
            }
132
        }
133
    }
134
135
    /**
136
     * Store the declaration in the selector tank
137
     *
138
     * @param  array
139
     * @param  \Crossjoin\Css\Format\Rule\Style\StyleDeclaration  $declaration
140
     * @return void
141
     */
142
    protected function storeDeclaration(array &$tank, $declaration)
143
    {
144
        $tank[] = $declaration;
145
    }
146
147
    /**
148
     * Before we build the dictionary of style declaration, we will need to make
149
     * sure, there is an array to be inserted.
150
     *
151
     * @param  array   &$selectors
152
     * @param  string  $specifity
153
     * @param  string  $name
154
     * @return void
155
     */
156
    protected function prepareSelectorArray(array &$selectors, $specifity, $name)
157
    {
158
        if ( ! isset($selectors[$specifity]))
159
        {
160
            $selectors[$specifity] = [];
161
        }
162
163
        if ( ! isset($selectors[$specifity][$name]))
164
        {
165
            $selectors[$specifity][$name] = [];
166
        }
167
    }
168
169
    /**
170
     * Set the stylesheet reader instance
171
     *
172
     * @param  \Crossjoin\Css\Reader\CssString|string  $stylesheet
173
     * @return $this
174
     *
175
     * @throws \InvalidArgumentException
176
     */
177
    public function setStylesheetReader($stylesheet)
178
    {
179
        if (is_string($stylesheet))
180
        {
181
            $stylesheet = new StylesheetReader($stylesheet);
182
        }
183
184
        if ( ! $stylesheet instanceof StylesheetReader)
185
        {
186
            throw new InvalidArgumentException('The argument 0 of the [setStylesheetReader] method expects to be a string of CSS or a [Crossjoin\Css\Reader\CssString]');
187
        }
188
189
        $this->reader = $stylesheet;
190
        $this->reader->setEnvironmentEncoding($this->getCharset());
191
192
        return $this;
193
    }
194
195
    /**
196
     * Get the stylesheet reader instance
197
     *
198
     * @return \Crossjoin\Css\Reader\CssString|null
199
     */
200
    public function getStylesheetReader()
201
    {
202
        return $this->reader;
203
    }
204
205
    /**
206
     * Set the charset of the stylesheet
207
     *
208
     * @param  string  $charset
209
     * @return $this
210
     */
211
    public function setCharset($charset)
212
    {
213
        $this->charset = $charset;
214
215
        return $this;
216
    }
217
218
    /**
219
     * Get the charset of the stylesheet
220
     *
221
     * @return string
222
     */
223
    public function getCharset()
224
    {
225
        return $this->charset;
226
    }
227
228
    /**
229
     * Check if a Selector has a valid Pseudo Class
230
     *
231
     * @param  \Crossjoin\Css\Format\Rule\Style\StyleSelector  $selector
232
     * @return bool
233
     */
234
    public function isPseudoClassAllowed(StyleSelector $selector)
235
    {
236
        foreach ($selector->getPseudoClasses() as $pseudo_class)
237
        {
238
            if ( ! in_array($pseudo_class, $this->allowed_pseudo_classes))
239
            {
240
                return false;
241
            }
242
        }
243
244
        return true;
245
    }
246
247
    /**
248
     * Gets all generally relevant style rules
249
     *
250
     * @param  \Crossjoin\Css\Format\Rule\RuleAbstract[]  $rules
251
     * @return \Crossjoin\Css\Format\Rule\Style\StyleRuleSet[]
252
     */
253
    protected function getRelevantStyleRules(array $rules)
254
    {
255
        $relevants = [];
256
257
        foreach ($rules as $rule)
258
        {
259
            if ($rule instanceof StyleRuleSet)
260
            {
261
                $relevants[] = $rule;
262
            }
263
264
            if ($rule instanceof MediaRule)
265
            {
266
                $this->getRelevantMediaRule($rule, $relevants);
267
            }
268
        }
269
270
        return $relevants;
271
    }
272
273
    /**
274
     * Gets the relevant style rules from a media rule
275
     *
276
     * @param  \Crossjoin\Css\Format\Rule\AtMedia\MediaRule  $rule
277
     * @param  array                                         &$collection
278
     * @return void
279
     */
280
    protected function getRelevantMediaRule(MediaRule $rule, &$collection)
281
    {
282
        foreach ($rule->getQueries() as $media_query)
283
        {
284
            if ( ! $this->isAllowedMediaRule($media_query))
285
            {
286
                continue;
287
            }
288
289
            foreach ($this->getRelevantStyleRules($rule->getRules()) as $style_rule)
290
            {
291
                $collection[] = $style_rule;
292
            }
293
294
            break;
295
        }
296
    }
297
298
    /**
299
     * Check if the media rule should be included
300
     *
301
     * @param  \Crossjoin\Css\Format\Rule\AtMedia\MediaQuery  $media_query
302
     * @return bool
303
     */
304
    protected function isAllowedMediaRule(MediaQuery $media_query)
305
    {
306
        $type      = $media_query->getType();
307
        $condition = count($media_query->getConditions());
308
309
        return ($type === MediaQuery::TYPE_ALL || $type === MediaQuery::TYPE_SCREEN) && $condition === 0;
310
    }
311
312
}
313