File Coverage

File:blib/lib/CSS/Grammar/Core.pm
Coverage:100.0%

linestmtbrancondsubtimecode
1package CSS::Grammar::Core;
2
3
6
6
6
25
14
41
use strict;
4
6
6
6
40
10
35
use warnings;
5
6
6
6
6
36
11
46
use base 'CSS::Grammar';
7
8
6
6
6
37
10
30
use CSS::Stylesheet;
9
6
6
6
41
16
87
use CSS::Ruleset;
10
6
6
6
39
15
40
use CSS::Declaration;
11
6
6
6
42
13
41
use CSS::Selector;
12
6
6
6
48
14
41
use CSS::AtRule;
13
14
15#
16# http://www.w3.org/TR/REC-CSS2/syndata.html#tokenization
17#
18
19sub init {
20
15
45
        my ($self) = @_;
21
22
15
32
        my %rx;
23
24        #####################################################################################
25
26
15
45
        $self->{case_insensitive} = 1;
27
28        #ident {nmstart}{nmchar}*
29        #name {nmchar}+
30        #nmstart [a-zA-Z]|{nonascii}|{escape}
31        #nonascii [^\0-\177]
32        #unicode \\[0-9a-f]{1,6}[ \n\r\t\f]?
33        #escape {unicode}|\\[ -~\200-\4177777]
34        #nmchar [a-z0-9-]|{nonascii}|{escape}
35        #num [0-9]+|[0-9]*\.[0-9]+
36        #string {string1}|{string2}
37        #string1 \"([\t !#$%&(-~]|\\{nl}|\'|{nonascii}|{escape})*\"
38        #string2 \'([\t !#$%&(-~]|\\{nl}|\"|{nonascii}|{escape})*\'
39        #nl \n|\r\n|\r|\f
40        #w [ \t\r\n\f]*
41
42
15
39
        $rx{w} = '[ \\t\\r\\n\\f]*';
43
15
45
        $rx{nl} = '(\\n|\\r\\n|\\r|\\f)';
44
15
45
        $rx{unicode} = '(\\[0-9a-f]{1,6}[ \\n\\r\\t\\f]?)';
45
15
68
        $rx{escape} = '('.$rx{unicode}.'|\\\\[ -~\\x80-\\xff])';
46
15
46
        $rx{nonascii} = '[\\x80-\\xff]';
47
15
40
        $rx{num} = '([0-9]+|[0-9]*\\.[0-9]+)';
48
49
15
89
        $rx{nmstart} = '([a-z]|'.$rx{nonascii}.'|'.$rx{escape}.')';
50
15
89
        $rx{nmchar} = '([a-z0-9-]|'.$rx{nonascii}.'|'.$rx{escape}.')';
51
15
114
        $rx{string1} = '("([\\t !#$%&(-~]|\\\\('.$rx{nl}.')|\'|('.$rx{nonascii}.')|('.$rx{escape}.'))*")';
52
15
112
        $rx{string2} = '(\'([\\t !#$%&(-~]|\\\\('.$rx{nl}.')|"|('.$rx{nonascii}.')|('.$rx{escape}.'))*\')';
53
54
15
88
        $rx{string} = "($rx{string1}|$rx{string2})";
55
15
78
        $rx{ident} = "$rx{nmstart}$rx{nmchar}*";
56
15
58
        $rx{name} = "$rx{nmchar}+";
57
58
59        #####################################################################################
60
61        #IDENT {ident}
62        #ATKEYWORD @{ident}
63        #STRING {string}
64        #HASH #{name}
65        #NUMBER {num}
66        #PERCENTAGE {num}%
67        #DIMENSION {num}{ident}
68
69
15
134
        $self->add_toke_rule('IDENT' , $rx{ident});
70
15
78
        $self->add_toke_rule('ATKEYWORD' , "\@$rx{ident}");
71
15
64
        $self->add_toke_rule('STRING' , $rx{string});
72
15
80
        $self->add_toke_rule('HASH' , "#$rx{name}");
73
15
69
        $self->add_toke_rule('NUMBER' , "$rx{num}");
74
15
74
        $self->add_toke_rule('PERCENTAGE' , "$rx{num}%");
75
15
82
        $self->add_toke_rule('DIMENSION' , "$rx{num}$rx{ident}");
76
77
78        #URI url\({w}{string}{w}\)
79        #|url\({w}([!#$%&*-~]|{nonascii}|{escape})*{w}\)
80        #UNICODE-RANGE U\+[0-9A-F?]{1,6}(-[0-9A-F]{1,6})?
81        #CDO <!--
82        #CDC -->
83
84
15
263
        $self->add_toke_rule('URI' , "(url\\($rx{w}$rx{string}$rx{w}\\))|(url\\($rx{w}([!#$%&*-~]|$rx{nonascii}|$rx{escape})*$rx{w}\\))");
85
15
59
        $self->add_toke_rule('UNICODE-RANGE' , "U\\+[0-9A-F?]{1,6}(-[0-9A-F]{1,6})?");
86
15
103
        $self->add_toke_rule('CDO' , '<!--');
87
15
53
        $self->add_toke_rule('CDC' , '-->');
88
89
90        #####################################################################################
91
92
15
55
        $self->add_toke_rule('_COLON' , ':');
93
15
52
        $self->add_toke_rule('_SEMICOLON' , ';');
94
15
51
        $self->add_toke_rule('_BRACE_OPEN' , '{');
95
15
54
        $self->add_toke_rule('_BRACE_CLOSE' , '}');
96
15
54
        $self->add_toke_rule('_ROUND_OPEN' , '\\(');
97
15
56
        $self->add_toke_rule('_ROUND_CLOSE' , '\\)');
98
15
54
        $self->add_toke_rule('_SQUARE_OPEN' , '\\[');
99
15
49
        $self->add_toke_rule('_SQUARE_CLOSE' , '\\]');
100
101
102        #####################################################################################
103
104        #
105        # this is after the _BLAH tokens since DELIM will match them (oops)
106        #
107
108        #S [ \t\r\n\f]+
109        #COMMENT \/\*[^*]*\*+([^/][^*]*\*+)*\/
110        #FUNCTION {ident}\(
111        #INCLUDES ~=
112        #DASHMATCH |=
113        #DELIM any other character not matched by the above rules
114
115
15
56
        $self->add_toke_rule('S' , '[ \\t\\r\\n\\f]');
116        # COMMENT \/\*[^*]*\*+([^/][^*]*\*+)*\/
117
15
74
        $self->add_toke_rule('FUNCTION' , "$rx{ident}\\(");
118
15
54
        $self->add_toke_rule('INCLUDES' , '~=');
119
15
57
        $self->add_toke_rule('DASHMATCH' , '\\|=');
120
15
55
        $self->add_toke_rule('DELIM' , '.');
121
122
123        #####################################################################################
124
125        #stylesheet : [ CDO | CDC | S | statement ]*;
126        #statement : ruleset | at-rule;
127        #at-rule : ATKEYWORD S* any* [ block | ';' S* ];
128        #block : '{' S* [ any | block | ATKEYWORD S* | ';' ]* '}' S*;
129        #ruleset : selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*;
130        #selector : any+;
131        #declaration : property ':' S* value;
132        #property : IDENT S*;
133        #value : [ any | block | ATKEYWORD S* ]+;
134        #any : [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING
135        # | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES
136        # | FUNCTION | DASHMATCH | '(' any* ')' | '[' any* ']' ] S*;
137
138
15
78
        $self->add_lex_rule('stylesheet', '[ CDO | CDC | S | statement ]*');
139
15
54
        $self->add_lex_rule('statement', 'ruleset | at-rule');
140
15
59
        $self->add_lex_rule('at-rule', 'ATKEYWORD S* any* [ block | _SEMICOLON S* ]');
141
15
60
        $self->add_lex_rule('block', '_BRACE_OPEN S* [ any | block | ATKEYWORD S* | _SEMICOLON ]* _BRACE_CLOSE S*');
142
15
61
        $self->add_lex_rule('ruleset', 'selector? _BRACE_OPEN S* declaration? [ _SEMICOLON S* declaration? ]* _BRACE_CLOSE S*');
143
15
57
        $self->add_lex_rule('selector', 'any+');
144
15
62
        $self->add_lex_rule('declaration', 'property _COLON S* value');
145
15
56
        $self->add_lex_rule('property', 'IDENT S*');
146
15
57
        $self->add_lex_rule('value', '[ any | block | ATKEYWORD S* ]+');
147
15
58
        $self->add_lex_rule('any', '[ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING | DELIM | _COLON | URI | HASH | '.
148                                        'UNICODE-RANGE | INCLUDES | FUNCTION | DASHMATCH | _ROUND_OPEN any* _ROUND_CLOSE | _SQUARE_OPEN any* _SQUARE_CLOSE ] S*');
149
150
151        #####################################################################################
152
153
15
77
        $self->set_base('stylesheet');
154}
155
156sub toke {
157
13
38
        my ($self, $input) = @_;
158
159        # strip comments first
160
13
57
        $input =~ s!/\*[^*]*\*+([^/*][^*]*\*+)*/!!sg;
161
162
13
99
        $self->SUPER::toke($input);
163}
164
165sub walk_stylesheet {
166
14
47
        my ($self, $stylesheet, $submatches) = @_;
167
168
14
14
26
41
        for my $statement (@{$submatches}){
169
170
43
155
                next unless defined $statement->{subrule};
171
42
154
                next unless $statement->{subrule} eq 'statement';
172
173
41
127
                my $sub = $statement->{submatches}->[0];
174
175
41
159
                if ($sub->{subrule} eq 'ruleset'){
176
177
38
116
                        my $ruleset = $self->walk_ruleset($sub);
178
179
38
38
63
117
                        push @{$stylesheet->{rulesets}}, $ruleset;
180
38
38
62
125
                        push @{$stylesheet->{items}}, $ruleset;
181                }
182
183
41
185
                if ($sub->{subrule} eq 'at-rule'){
184
185
3
12
                        my $atrule = $self->walk_atrule($sub);
186
187
3
3
6
11
                        push @{$stylesheet->{atrules}}, $atrule;
188
3
3
6
13
                        push @{$stylesheet->{items}}, $atrule;
189                }
190
191                #print "subrule: $sub->{subrule}\n";
192        }
193
194
14
39
        return $stylesheet;
195}
196
197sub walk_ruleset {
198
38
97
        my ($self, $match) = @_;
199
200        #
201        # first match token should be a selector, if we have one
202        #
203
204
38
130
        my $ruleset = new CSS::Ruleset;
205
206
38
38
63
182
        for my $submatch (@{$match->{submatches}}){
207
208
97
404
                if ($submatch->{subrule} eq 'selector'){
209
210
38
161
                        $ruleset->{selector} = $submatch->{matched_text};
211
38
252
                        $ruleset->{selector} =~ s/^\s*(.*?)\s*/$1/s;
212
213
38
128
                        $self->walk_selectors($ruleset, $submatch);
214                }
215
216
97
445
                if ($submatch->{subrule} eq 'declaration'){
217
218
59
175
                        my $declaration = $self->walk_declaration($submatch);
219
220
59
59
101
253
                        push @{$ruleset->{declarations}}, $declaration;
221
222                }
223        }
224
225
38
98
        return $ruleset;
226}
227
228sub walk_declaration {
229
59
147
        my ($self, $match) = @_;
230
231
59
193
        my $declaration = new CSS::Declaration;
232
233
59
59
104
198
        for my $submatch (@{$match->{submatches}}){
234
235
236
870
                next unless defined $submatch->{subrule};
236
237
118
575
                if ($submatch->{subrule} eq 'property'){
238
239
59
192
                        $declaration->{property} = $submatch->{matched_text};
240                }
241
242
118
492
                if ($submatch->{subrule} eq 'value'){
243
244
59
184
                        $declaration->{value} = $submatch->{matched_text};
245
59
312
                        $declaration->{simple_value} = $submatch->{matched_text};
246                }
247        }
248
249
59
151
        return $declaration;
250}
251
252sub walk_atrule {
253
4
13
        my ($self, $match) = @_;
254
255
4
7
        my %rx;
256
4
13
        $rx{unicode} = '(?:\\[0-9a-f]{1,6}[ \\n\\r\\t\\f]?)';
257
4
20
        $rx{escape} = '(?:'.$rx{unicode}.'|\\\\[ -~\\x80-\\xff])';
258
4
10
        $rx{nonascii} = '[\\x80-\\xff]';
259
4
23
        $rx{nmstart} = '(?:[a-z]|'.$rx{nonascii}.'|'.$rx{escape}.')';
260
4
23
        $rx{nmchar} = '(?:[a-z0-9-]|'.$rx{nonascii}.'|'.$rx{escape}.')';
261
4
19
        $rx{ident} = "$rx{nmstart}$rx{nmchar}*";
262
263
4
134
        if ($match->{matched_text} =~ m/^\@($rx{ident})(.*?)$/s){
264
265
3
10
                my $rule = $1;
266
3
9
                my $value = $2;
267
268
3
22
                $value =~ s!^\s*(.*?);?\s*$!$1!s;
269
270
3
64
                return new CSS::AtRule($rule, $value);
271        }
272
273
1
6
        return new CSS::AtRule();
274}
275
276sub walk_selectors {
277
38
117
        my ($self, $ruleset, $match) = @_;
278
279
38
71
        my $buffer = '';
280
281
38
38
64
135
        for my $submatch (@{$match->{submatches}}){
282
283
126
469
                if ($submatch->{matched_text} =~ m!\s*,\s*!s){
284
285
30
87
                        $self->commit_selector($ruleset, $buffer);
286
30
90
                        $buffer = '';
287                }else{
288
96
395
                        $buffer .= $submatch->{matched_text};
289                }
290        }
291
292
38
126
        $self->commit_selector($ruleset, $buffer);
293}
294
295sub commit_selector {
296
70
210
        my ($self, $ruleset, $text) = @_;
297
298
70
383
        $text =~ s/^\s*(.*?)\s*$/$1/s;
299
70
69
339
339
        push @{$ruleset->{selectors}}, new CSS::Selector($text) if length $text;
300}
301
3021;