File: | blib/lib/CSS/Grammar/CSS30.pm |
Coverage: | 100.0% |
line | stmt | bran | cond | sub | time | code |
---|---|---|---|---|---|---|
1 | package CSS::Grammar::CSS30; | |||||
2 | ||||||
3 | 4 4 4 | 19 7 22 | use strict; | |||
4 | 4 4 4 | 24 8 20 | use warnings; | |||
5 | ||||||
6 | 4 4 4 | 26 7 23 | use base 'CSS::Grammar'; | |||
7 | ||||||
8 | # | |||||
9 | # http://www.w3.org/TR/2003/WD-css3-syntax-20030813/ | |||||
10 | # | |||||
11 | ||||||
12 | sub init { | |||||
13 | 4 | 11 | my ($self) = @_; | |||
14 | ||||||
15 | 4 | 8 | my %rx; | |||
16 | ||||||
17 | ##################################################################################### | |||||
18 | ||||||
19 | # %option case-insensitive | |||||
20 | ||||||
21 | 4 | 13 | $self->{case_insensitive} = 1; | |||
22 | ||||||
23 | ##################################################################################### | |||||
24 | ||||||
25 | #nl \n|\r\n|\r|\f | |||||
26 | #h [0-9a-f] | |||||
27 | #nonascii [\200-\377] | |||||
28 | #unicode \\{h}{1,6}[ \t\r\n\f]? | |||||
29 | #escape {unicode}|\\[ -~\200-\377] | |||||
30 | #nmstart [a-z]|{nonascii}|{escape} | |||||
31 | #nmchar [a-z0-9-]|{nonascii}|{escape} | |||||
32 | #string1 \"([\t !#$%&(-~]|\\{nl}|\'|{nonascii}|{escape})*\" | |||||
33 | #string2 \'([\t !#$%&(-~]|\\{nl}|\"|{nonascii}|{escape})*\' | |||||
34 | ||||||
35 | 4 | 11 | $rx{nl} = '(\n|\r\n|\r|\f)'; | |||
36 | 4 | 12 | $rx{h} = '[0-9a-f]'; | |||
37 | 4 | 11 | $rx{nonascii} = '[\\x80-\\xff]'; | |||
38 | 4 | 20 | $rx{unicode} = '(\\'.$rx{h}.'{1,6}(\\r\\n|[ \\t\\r\\n\\f])?)'; | |||
39 | 4 | 17 | $rx{escape} = '('.$rx{unicode}.'|\\\\[ -~\\x80-\\xff])'; | |||
40 | 4 | 25 | $rx{nmstart} = '([a-z]|'.$rx{nonascii}.'|'.$rx{escape}.')'; | |||
41 | 4 | 22 | $rx{nmchar} = '([a-zA-Z0-9-]|'.$rx{nonascii}.'|'.$rx{escape}.')'; | |||
42 | 4 | 33 | $rx{string1} = '("([\\t !#$%&(-~]|\\\\('.$rx{nl}.')|\'|('.$rx{nonascii}.')|('.$rx{escape}.'))*")'; | |||
43 | 4 | 32 | $rx{string2} = '(\'([\\t !#$%&(-~]|\\\\('.$rx{nl}.')|"|('.$rx{nonascii}.')|('.$rx{escape}.'))*\')'; | |||
44 | ||||||
45 | ||||||
46 | #ident [-]?{nmstart}{nmchar}* | |||||
47 | #name {nmchar}+ | |||||
48 | #num [0-9]+|[0-9]*"."[0-9]+ | |||||
49 | #string {string1}|{string2} | |||||
50 | #url ([!#$%&*-~]|{nonascii}|{escape})* | |||||
51 | #w [ \t\r\n\f]* | |||||
52 | #range \?{1,6}|{h}(\?{0,5}|{h}(\?{0,4}|{h}(\?{0,3}|{h}(\?{0,2}|{h}(\??|{h}))))) | |||||
53 | ||||||
54 | 4 | 23 | $rx{ident} = "(-?$rx{nmstart}$rx{nmchar}*)"; | |||
55 | 4 | 19 | $rx{name} = "($rx{nmchar}+)"; | |||
56 | 4 | 10 | $rx{num} = '([0-9]+|[0-9]*\\.[0-9]+)'; | |||
57 | 4 | 26 | $rx{string} = "($rx{string1}|$rx{string2})"; | |||
58 | 4 | 24 | $rx{url} = "(([!#\$%&*-~]|$rx{nonascii}|$rx{escape})*)"; | |||
59 | 4 | 11 | $rx{w} = '[ \t\r\n\f]*'; | |||
60 | 4 | 48 | $rx{range} = "(\\?{1,6}|$rx{h}(\\?{0,5}|$rx{h}(\\?{0,4}|$rx{h}(\\?{0,3}|$rx{h}(\\?{0,2}|$rx{h}(\\??|$rx{h}))))))"; | |||
61 | ||||||
62 | ||||||
63 | ##################################################################################### | |||||
64 | ||||||
65 | #[ \t\r\n\f]+ {return S;} | |||||
66 | #"<!--" {return CDO;} | |||||
67 | #"-->" {return CDC;} | |||||
68 | #"~=" {return INCLUDES;} | |||||
69 | #"|=" {return DASHMATCH;} | |||||
70 | ||||||
71 | #{string} {return STRING;} | |||||
72 | #{ident} {return IDENT;} | |||||
73 | #"#"{name} {return HASH;} | |||||
74 | ||||||
75 | 4 | 21 | $self->add_toke_rule('S' , "[ \t\r\n\f]+"); | |||
76 | 4 | 16 | $self->add_toke_rule('CDO' , '<!--'); | |||
77 | 4 | 15 | $self->add_toke_rule('CDC' , '-->'); | |||
78 | 4 | 15 | $self->add_toke_rule('INCLUDES' , '~='); | |||
79 | 4 | 14 | $self->add_toke_rule('DASHMATCH' , '\\|='); | |||
80 | ||||||
81 | 4 | 17 | $self->add_toke_rule('STRING' , $rx{string}); | |||
82 | 4 | 17 | $self->add_toke_rule('IDENT' , $rx{ident}); | |||
83 | 4 | 22 | $self->add_toke_rule('HASH' , "#$rx{name}"); | |||
84 | ||||||
85 | ||||||
86 | #"@import" {return IMPORT_SYM;} | |||||
87 | #"@page" {return PAGE_SYM;} | |||||
88 | #"@media" {return MEDIA_SYM;} | |||||
89 | #"@font-face" {return FONT_FACE_SYM;} | |||||
90 | #"@charset" {return CHARSET_SYM;} | |||||
91 | #"@namespace" {return NAMESPACE_SYM;} | |||||
92 | ||||||
93 | #"!{w}important" {return IMPORTANT_SYM;} | |||||
94 | ||||||
95 | 4 | 19 | $self->add_toke_rule('IMPORT_SYM' , '@import'); | |||
96 | 4 | 16 | $self->add_toke_rule('PAGE_SYM' , '@page'); | |||
97 | 4 | 16 | $self->add_toke_rule('MEDIA_SYM' , '@media'); | |||
98 | 4 | 13 | $self->add_toke_rule('FONT_FACE_SYM' , '@font-face'); | |||
99 | 4 | 48 | $self->add_toke_rule('CHARSET_SYM' , '@charset'); | |||
100 | 4 | 16 | $self->add_toke_rule('NAMESPACE_SYM' , '@namespace'); | |||
101 | ||||||
102 | 4 | 23 | $self->add_toke_rule('IMPORTANT_SYM' , "!$rx{w}important"); | |||
103 | ||||||
104 | ||||||
105 | #{num}em {return EMS;} | |||||
106 | #{num}ex {return EXS;} | |||||
107 | #{num}px {return LENGTH;} | |||||
108 | #{num}cm {return LENGTH;} | |||||
109 | #{num}mm {return LENGTH;} | |||||
110 | #{num}in {return LENGTH;} | |||||
111 | #{num}pt {return LENGTH;} | |||||
112 | #{num}pc {return LENGTH;} | |||||
113 | #{num}deg {return ANGLE;} | |||||
114 | #{num}rad {return ANGLE;} | |||||
115 | #{num}grad {return ANGLE;} | |||||
116 | #{num}ms {return TIME;} | |||||
117 | #{num}s {return TIME;} | |||||
118 | #{num}Hz {return FREQ;} | |||||
119 | #{num}kHz {return FREQ;} | |||||
120 | #{num}{ident} {return DIMEN;} | |||||
121 | #{num}% {return PERCENTAGE;} | |||||
122 | #{num} {return NUMBER;} | |||||
123 | ||||||
124 | 4 | 21 | $self->add_toke_rule('EMS' , "$rx{num}em"); | |||
125 | 4 | 24 | $self->add_toke_rule('EXS' , "$rx{num}ex"); | |||
126 | 4 | 20 | $self->add_toke_rule('LENGTH' , "$rx{num}(px|cm|mm|in|pt|pc)"); | |||
127 | 4 | 21 | $self->add_toke_rule('ANGLE' , "$rx{num}(deg|rad|grad)"); | |||
128 | 4 | 20 | $self->add_toke_rule('TIME' , "$rx{num}(ms|s)"); | |||
129 | 4 | 22 | $self->add_toke_rule('FREQ' , "$rx{num}(Hz|kHz)"); | |||
130 | 4 | 23 | $self->add_toke_rule('DIMEN' , "$rx{num}$rx{ident}"); | |||
131 | 4 | 22 | $self->add_toke_rule('PERCENTAGE' , "$rx{num}%"); | |||
132 | 4 | 21 | $self->add_toke_rule('NUMBER' , "$rx{num}"); | |||
133 | ||||||
134 | ||||||
135 | #"url("{w}{string}{w}")" {return URI;} | |||||
136 | #"url("{w}{url}{w}")" {return URI;} | |||||
137 | #{ident}"(" {return FUNCTION;} | |||||
138 | ||||||
139 | #U\+{range} {return UNICODERANGE;} | |||||
140 | #U\+{h}{1,6}-{h}{1,6} {return UNICODERANGE;} | |||||
141 | ||||||
142 | 4 | 38 | $self->add_toke_rule('URI' , "url\\($rx{w}($rx{string}|$rx{url}$rx{w})\\)"); | |||
143 | 4 | 22 | $self->add_toke_rule('FUNCTION' , "$rx{ident}\\("); | |||
144 | ||||||
145 | 4 | 31 | $self->add_toke_rule('UNICODERANGE' , "U\\+($rx{range}|${rx{h}}{1,6}-${rx{h}}{1,6})"); | |||
146 | ||||||
147 | ||||||
148 | ##################################################################################### | |||||
149 | ||||||
150 | 4 | 15 | $self->add_toke_rule('_SEMICOLON' , ';'); | |||
151 | 4 | 15 | $self->add_toke_rule('_COMMA' , ','); | |||
152 | 4 | 13 | $self->add_toke_rule('_BRACE_OPEN' , '{'); | |||
153 | 4 | 13 | $self->add_toke_rule('_BRACE_CLOSE' , '}'); | |||
154 | 4 | 15 | $self->add_toke_rule('_COLON' , ':'); | |||
155 | 4 | 15 | $self->add_toke_rule('_SLASH' , '/'); | |||
156 | 4 | 16 | $self->add_toke_rule('_PLUS' , '\\+'); | |||
157 | 4 | 15 | $self->add_toke_rule('_GREATER_THAN' , '>'); | |||
158 | 4 | 18 | $self->add_toke_rule('_MINUS' , '-'); | |||
159 | 4 | 15 | $self->add_toke_rule('_PERIOD' , '\\.'); | |||
160 | 4 | 14 | $self->add_toke_rule('_STAR' , '\\*'); | |||
161 | 4 | 15 | $self->add_toke_rule('_SQUARE_OPEN' , '\\['); | |||
162 | 4 | 15 | $self->add_toke_rule('_EQUALS' , '='); | |||
163 | 4 | 17 | $self->add_toke_rule('_SQUARE_CLOSE' , '\\]'); | |||
164 | 4 | 14 | $self->add_toke_rule('_ROUND_CLOSE' , '\\)'); | |||
165 | ||||||
166 | ||||||
167 | ##################################################################################### | |||||
168 | ||||||
169 | #stylesheet | |||||
170 | # : [ CHARSET_SYM S* STRING S* ';' ]? | |||||
171 | # [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* | |||||
172 | # [ namespace [S|CDO|CDC]* ]* | |||||
173 | # [ [ ruleset | media | page | font_face ] [S|CDO|CDC]* ]* | |||||
174 | # ; | |||||
175 | #import | |||||
176 | # : IMPORT_SYM S* | |||||
177 | # [STRING|URI] S* [ medium [ ',' S* medium]* ]? ';' S* | |||||
178 | # ; | |||||
179 | #namespace | |||||
180 | # : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* | |||||
181 | # ; | |||||
182 | ||||||
183 | 4 | 25 | $self->add_lex_rule('stylesheet', '[ CHARSET_SYM S* STRING S* _SEMICOLON ]? [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*'. | |||
184 | ' [ namespace [S|CDO|CDC]* ]* [ [ ruleset | media | page | font_face ] [S|CDO|CDC]* ]*'); | |||||
185 | 4 | 16 | $self->add_lex_rule('import', 'IMPORT_SYM S* [STRING|URI] S* [ medium [ _COMMA S* medium]* ]? _SEMICOLON S*'); | |||
186 | 4 | 17 | $self->add_lex_rule('namespace', 'NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* _SEMICOLON S*'); | |||
187 | ||||||
188 | ||||||
189 | #namespace_prefix | |||||
190 | # : IDENT | |||||
191 | # ; | |||||
192 | #media | |||||
193 | # : MEDIA_SYM S* medium [ ',' S* medium ]* '{' S* ruleset* '}' S* | |||||
194 | # ; | |||||
195 | #medium | |||||
196 | # : IDENT S* | |||||
197 | # ; | |||||
198 | #page | |||||
199 | # : PAGE_SYM S* IDENT? pseudo_page? S* | |||||
200 | # '{' S* declaration [ ';' S* declaration ]* '}' S* | |||||
201 | # ; | |||||
202 | ||||||
203 | 4 | 16 | $self->add_lex_rule('namespace_prefix', 'IDENT'); | |||
204 | 4 | 17 | $self->add_lex_rule('media', 'MEDIA_SYM S* medium [ _COMMA S* medium ]* _BRACE_OPEN S* ruleset* _BRACE_CLOSE S*'); | |||
205 | 4 | 14 | $self->add_lex_rule('medium', 'IDENT S*'); | |||
206 | 4 | 16 | $self->add_lex_rule('page', 'PAGE_SYM S* IDENT? pseudo_page? S* _BRACE_OPEN S* declaration [ _SEMICOLON S* declaration ]* _BRACE_CLOSE S*'); | |||
207 | ||||||
208 | ||||||
209 | #pseudo_page | |||||
210 | # : ':' IDENT | |||||
211 | # ; | |||||
212 | #font_face | |||||
213 | # : FONT_FACE_SYM S* | |||||
214 | # '{' S* declaration [ ';' S* declaration ]* '}' S* | |||||
215 | # ; | |||||
216 | #operator | |||||
217 | # : '/' S* | ',' S* | /* empty */ | |||||
218 | # ; | |||||
219 | #combinator | |||||
220 | # : '+' S* | '>' S* | /* empty */ | |||||
221 | # ; | |||||
222 | ||||||
223 | 4 | 16 | $self->add_lex_rule('pseudo_page', '_COLON IDENT'); | |||
224 | 4 | 17 | $self->add_lex_rule('font_face', 'FONT_FACE_SYM S* _BRACE_OPEN S* declaration [ _SEMICOLON S* declaration ]* _BRACE_CLOSE S*'); | |||
225 | 4 | 16 | $self->add_lex_rule('operator', '_SLASH S* | _COMMA S* | '); | |||
226 | 4 | 17 | $self->add_lex_rule('combinator', '_PLUS S* | _GREATER_THAN S* | '); | |||
227 | ||||||
228 | ||||||
229 | #unary_operator | |||||
230 | # : '-' | '+' | |||||
231 | # ; | |||||
232 | #property | |||||
233 | # : IDENT S* | |||||
234 | # ; | |||||
235 | #ruleset | |||||
236 | # : selector [ ',' S* selector ]* | |||||
237 | # '{' S* declaration [ ';' S* declaration ]* '}' S* | |||||
238 | # ; | |||||
239 | #selector | |||||
240 | # : simple_selector [ combinator simple_selector ]* | |||||
241 | # ; | |||||
242 | ||||||
243 | 4 | 16 | $self->add_lex_rule('unary_operator', '_MINUS | _PLUS'); | |||
244 | 4 | 17 | $self->add_lex_rule('property', 'IDENT S*'); | |||
245 | 4 | 15 | $self->add_lex_rule('ruleset', 'selector [ _COMMA S* selector ]* _BRACE_OPEN S* declaration [ _SEMICOLON S* declaration ]* _BRACE_CLOSE S*'); | |||
246 | 4 | 17 | $self->add_lex_rule('selector', 'simple_selector [ combinator simple_selector ]*'); | |||
247 | ||||||
248 | ||||||
249 | #simple_selector | |||||
250 | # : element_name? [ HASH | class | attrib | pseudo ]* S* | |||||
251 | # ; | |||||
252 | #class | |||||
253 | # : '.' IDENT | |||||
254 | # ; | |||||
255 | #element_name | |||||
256 | # : IDENT | '*' | |||||
257 | # ; | |||||
258 | #attrib | |||||
259 | # : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* | |||||
260 | # [ IDENT | STRING ] S* ]? ']' | |||||
261 | # ; | |||||
262 | ||||||
263 | 4 | 16 | $self->add_lex_rule('simple_selector', 'element_name? [ HASH | class | attrib | pseudo ]* S*'); | |||
264 | 4 | 16 | $self->add_lex_rule('class', '_PERIOD IDENT'); | |||
265 | 4 | 18 | $self->add_lex_rule('element_name', 'IDENT | _STAR'); | |||
266 | 4 | 15 | $self->add_lex_rule('attrib', '_SQUARE_OPEN S* IDENT S* [ [ _EQUALS_EQUALS | INCLUDES | DASHMATCH ] S* [ IDENT | STRING ] S* ]? _SQUARE_CLOSE'); | |||
267 | ||||||
268 | ||||||
269 | #pseudo | |||||
270 | # : ':' [ IDENT | FUNCTION S* IDENT S* ')' ] | |||||
271 | # ; | |||||
272 | #declaration | |||||
273 | # : property ':' S* expr prio? | |||||
274 | # | /* empty */ | |||||
275 | # ; | |||||
276 | #prio | |||||
277 | # : IMPORTANT_SYM S* | |||||
278 | # ; | |||||
279 | #expr | |||||
280 | # : term [ operator term ]* | |||||
281 | # ; | |||||
282 | ||||||
283 | 4 | 14 | $self->add_lex_rule('pseudo', '_COLON [ IDENT | FUNCTION S* IDENT S* _ROUND_CLOSE ]'); | |||
284 | 4 | 14 | $self->add_lex_rule('declaration', 'property _COLON S* expr prio? | '); | |||
285 | 4 | 17 | $self->add_lex_rule('prio', 'IMPORTANT_SYM S*'); | |||
286 | 4 | 18 | $self->add_lex_rule('expr', 'term [ operator term ]*'); | |||
287 | ||||||
288 | ||||||
289 | #term | |||||
290 | # : unary_operator? | |||||
291 | # [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* | | |||||
292 | # TIME S* | FREQ S* | function ] | |||||
293 | # | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor | |||||
294 | # ; | |||||
295 | #function | |||||
296 | # : FUNCTION S* expr ')' S* | |||||
297 | # ; | |||||
298 | #hexcolor | |||||
299 | # : HASH S* | |||||
300 | # ; | |||||
301 | ||||||
302 | 4 | 16 | $self->add_lex_rule('term', 'unary_operator? [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* | TIME S* | FREQ S* '. | |||
303 | '| function ] | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor'); | |||||
304 | 4 | 17 | $self->add_lex_rule('function', 'FUNCTION S* expr _ROUND_CLOSE S*'); | |||
305 | 4 | 17 | $self->add_lex_rule('hexcolor', 'HASH S*'); | |||
306 | ||||||
307 | ||||||
308 | ##################################################################################### | |||||
309 | ||||||
310 | 4 | 28 | $self->set_base('stylesheet'); | |||
311 | } | |||||
312 | ||||||
313 | ||||||
314 | sub toke { | |||||
315 | 3 | 20 | my ($self, $input) = @_; | |||
316 | ||||||
317 | # strip comments first | |||||
318 | 3 | 8 | $input =~ s!/\*[^*]*\*+([^/*][^*]*\*+)*/!!g; | |||
319 | ||||||
320 | 3 | 31 | $self->SUPER::toke($input); | |||
321 | } | |||||
322 | ||||||
323 | 1; | |||||
324 |