csspool 3.0.2 → 4.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/Gemfile.lock +4 -4
  2. data/Manifest.txt +13 -0
  3. data/README.rdoc +1 -1
  4. data/lib/csspool/css/document.rb +6 -0
  5. data/lib/csspool/css/document_handler.rb +41 -7
  6. data/lib/csspool/css/document_query.rb +14 -0
  7. data/lib/csspool/css/import_rule.rb +8 -4
  8. data/lib/csspool/css/keyframes_block.rb +14 -0
  9. data/lib/csspool/css/keyframes_rule.rb +14 -0
  10. data/lib/csspool/css/media.rb +5 -3
  11. data/lib/csspool/css/namespace_rule.rb +13 -0
  12. data/lib/csspool/css/parser.rb +970 -425
  13. data/lib/csspool/css/parser.y +227 -30
  14. data/lib/csspool/css/tokenizer.rb +31 -7
  15. data/lib/csspool/css/tokenizer.rex +36 -7
  16. data/lib/csspool/css.rb +4 -0
  17. data/lib/csspool/selector.rb +5 -1
  18. data/lib/csspool/selectors/attribute.rb +6 -1
  19. data/lib/csspool/selectors/pseudo.rb +17 -0
  20. data/lib/csspool/selectors/pseudo_element.rb +13 -0
  21. data/lib/csspool/selectors/simple.rb +0 -4
  22. data/lib/csspool/selectors/type.rb +7 -0
  23. data/lib/csspool/selectors/universal.rb +7 -0
  24. data/lib/csspool/selectors.rb +1 -1
  25. data/lib/csspool/terms/math.rb +18 -0
  26. data/lib/csspool/terms.rb +1 -0
  27. data/lib/csspool/visitors/children.rb +1 -1
  28. data/lib/csspool/visitors/comparable.rb +8 -2
  29. data/lib/csspool/visitors/iterator.rb +1 -1
  30. data/lib/csspool/visitors/to_css.rb +41 -21
  31. data/lib/csspool.rb +1 -1
  32. data/test/_local_helper.rb +2 -0
  33. data/test/css/test_document_query.rb +76 -0
  34. data/test/css/test_import_rule.rb +1 -1
  35. data/test/css/test_keyframes_rule.rb +93 -0
  36. data/test/css/test_namespace_rule.rb +54 -0
  37. data/test/css/test_parser.rb +46 -1
  38. data/test/css/test_tokenizer.rb +0 -45
  39. data/test/helper.rb +6 -1
  40. data/test/sac/test_parser.rb +16 -3
  41. data/test/test_declaration.rb +39 -0
  42. data/test/test_parser.rb +26 -13
  43. data/test/test_selector.rb +205 -5
  44. data/test/test_term.rb +23 -0
  45. data/test/visitors/test_to_css.rb +34 -23
  46. metadata +136 -118
@@ -3,7 +3,15 @@ class CSSPool::CSS::Parser
3
3
  token CHARSET_SYM IMPORT_SYM STRING SEMI IDENT S COMMA LBRACE RBRACE STAR HASH
4
4
  token LSQUARE RSQUARE EQUAL INCLUDES DASHMATCH RPAREN FUNCTION GREATER PLUS
5
5
  token SLASH NUMBER MINUS LENGTH PERCENTAGE EMS EXS ANGLE TIME FREQ URI
6
+ token IMPORTANT_SYM MEDIA_SYM NTH_PSEUDO_CLASS
7
+ token IMPORTANT_SYM MEDIA_SYM DOCUMENT_QUERY_SYM FUNCTION_NO_QUOTE
6
8
  token IMPORTANT_SYM MEDIA_SYM
9
+ token NAMESPACE_SYM TILDE
10
+ token NAMESPACE_SYM PREFIXMATCH SUFFIXMATCH SUBSTRINGMATCH
11
+ token NAMESPACE_SYM NOT_PSEUDO_CLASS
12
+ token NAMESPACE_SYM KEYFRAMES_SYM
13
+ token NAMESPACE_SYM MATCHES_PSEUDO_CLASS
14
+ token NAMESPACE_SYM MATH
7
15
 
8
16
  rule
9
17
  document
@@ -14,8 +22,10 @@ rule
14
22
  stylesheet
15
23
  : charset stylesheet
16
24
  | import stylesheet
25
+ | namespace stylesheet
17
26
  | charset
18
27
  | import
28
+ | namespace
19
29
  | body
20
30
  ;
21
31
  charset
@@ -34,6 +44,14 @@ rule
34
44
  | STRING { result = Terms::String.new interpret_string val.first }
35
45
  | URI { result = Terms::URI.new interpret_uri val.first }
36
46
  ;
47
+ namespace
48
+ : NAMESPACE_SYM ident import_location SEMI {
49
+ @handler.namespace val[1], val[2]
50
+ }
51
+ | NAMESPACE_SYM import_location SEMI {
52
+ @handler.namespace nil, val[1]
53
+ }
54
+ ;
37
55
  medium
38
56
  : medium COMMA IDENT {
39
57
  result = [val.first, Terms::Ident.new(interpret_identifier val.last)]
@@ -44,9 +62,15 @@ rule
44
62
  ;
45
63
  body
46
64
  : ruleset body
47
- | media body
65
+ | conditional_rule body
66
+ | keyframes_rule body
48
67
  | ruleset
49
- | media
68
+ | conditional_rule
69
+ | keyframes_rule
70
+ ;
71
+ conditional_rule
72
+ : media
73
+ | document_query
50
74
  ;
51
75
  media
52
76
  : start_media body RBRACE { @handler.end_media val.first }
@@ -58,6 +82,60 @@ rule
58
82
  }
59
83
  | MEDIA_SYM LBRACE { result = [] }
60
84
  ;
85
+ document_query
86
+ : start_document_query body RBRACE { @handler.end_document_query }
87
+ | start_document_query RBRACE { @handler.end_document_query }
88
+ ;
89
+ start_document_query
90
+ : DOCUMENT_QUERY_SYM url_match_fns LBRACE {
91
+ @handler.start_document_query val[1]
92
+ }
93
+ ;
94
+ url_match_fns
95
+ : url_match_fn COMMA url_match_fns {
96
+ result = [val[0], val[2]].flatten
97
+ }
98
+ | url_match_fn {
99
+ result = val
100
+ }
101
+ ;
102
+ url_match_fn
103
+ : function_no_quote
104
+ | function
105
+ | uri
106
+ ;
107
+ keyframes_rule
108
+ : start_keyframes_rule keyframes_blocks RBRACE
109
+ | start_keyframes_rule RBRACE
110
+ ;
111
+ start_keyframes_rule
112
+ : KEYFRAMES_SYM IDENT LBRACE {
113
+ @handler.start_keyframes_rule val[1]
114
+ }
115
+ ;
116
+ keyframes_blocks
117
+ : keyframes_block keyframes_blocks
118
+ | keyframes_block
119
+ ;
120
+ keyframes_block
121
+ : start_keyframes_block declarations RBRACE { @handler.end_keyframes_block }
122
+ | start_keyframes_block RBRACE { @handler.end_keyframes_block }
123
+ ;
124
+ start_keyframes_block
125
+ : keyframes_selectors LBRACE {
126
+ @handler.start_keyframes_block val[0]
127
+ }
128
+ ;
129
+ keyframes_selectors
130
+ | keyframes_selector COMMA keyframes_selectors {
131
+ result = val[0] + ', ' + val[2]
132
+ }
133
+ | keyframes_selector
134
+ ;
135
+ keyframes_selector
136
+ : IDENT
137
+ | PERCENTAGE { result = val[0].strip }
138
+ ;
61
139
  ruleset
62
140
  : start_selector declarations RBRACE {
63
141
  @handler.end_selector val.first
@@ -97,6 +175,7 @@ rule
97
175
  : S { result = :s }
98
176
  | GREATER { result = :> }
99
177
  | PLUS { result = :+ }
178
+ | TILDE { result = :~ }
100
179
  ;
101
180
  simple_selector
102
181
  : element_name hcap {
@@ -112,9 +191,22 @@ rule
112
191
  result = [ss]
113
192
  }
114
193
  ;
194
+ simple_selectors
195
+ : simple_selector COMMA simple_selectors { result = [val[0], val[2]].flatten }
196
+ | simple_selector
197
+ ;
198
+ ident_with_namespace
199
+ : IDENT { result = [interpret_identifier(val[0]), nil] }
200
+ | IDENT '|' IDENT { result = [interpret_identifier(val[2]), interpret_identifier(val[0])] }
201
+ | '|' IDENT { result = [interpret_identifier(val[1]), nil] }
202
+ | STAR '|' IDENT { result = [interpret_identifier(val[2]), '*'] }
203
+ ;
115
204
  element_name
116
- : IDENT { result = Selectors::Type.new interpret_identifier val.first }
117
- | STAR { result = Selectors::Universal.new val.first }
205
+ : ident_with_namespace { result = Selectors::Type.new val.first[0], nil, val.first[1] }
206
+ | STAR { result = Selectors::Universal.new val.first }
207
+ | '|' STAR { result = Selectors::Universal.new val[1] }
208
+ | STAR '|' STAR { result = Selectors::Universal.new val[2], nil, val[0] }
209
+ | IDENT '|' STAR { result = Selectors::Universal.new val[2], nil, interpret_identifier(val[0]) }
118
210
  ;
119
211
  hcap
120
212
  : hash { result = val }
@@ -136,60 +228,118 @@ rule
136
228
  }
137
229
  ;
138
230
  attrib
139
- : LSQUARE IDENT EQUAL IDENT RSQUARE {
231
+ : LSQUARE ident_with_namespace EQUAL IDENT RSQUARE {
140
232
  result = Selectors::Attribute.new(
141
- interpret_identifier(val[1]),
233
+ val[1][0],
142
234
  interpret_identifier(val[3]),
143
- Selectors::Attribute::EQUALS
235
+ Selectors::Attribute::EQUALS,
236
+ val[1][1]
144
237
  )
145
238
  }
146
- | LSQUARE IDENT EQUAL STRING RSQUARE {
239
+ | LSQUARE ident_with_namespace EQUAL STRING RSQUARE {
147
240
  result = Selectors::Attribute.new(
148
- interpret_identifier(val[1]),
241
+ val[1][0],
149
242
  interpret_string(val[3]),
150
- Selectors::Attribute::EQUALS
243
+ Selectors::Attribute::EQUALS,
244
+ val[1][1]
151
245
  )
152
246
  }
153
- | LSQUARE IDENT INCLUDES STRING RSQUARE {
247
+ | LSQUARE ident_with_namespace INCLUDES STRING RSQUARE {
154
248
  result = Selectors::Attribute.new(
155
- interpret_identifier(val[1]),
249
+ val[1][0],
156
250
  interpret_string(val[3]),
157
- Selectors::Attribute::INCLUDES
251
+ Selectors::Attribute::INCLUDES,
252
+ val[1][1]
158
253
  )
159
254
  }
160
- | LSQUARE IDENT INCLUDES IDENT RSQUARE {
255
+ | LSQUARE ident_with_namespace INCLUDES IDENT RSQUARE {
161
256
  result = Selectors::Attribute.new(
162
- interpret_identifier(val[1]),
257
+ val[1][0],
163
258
  interpret_identifier(val[3]),
164
- Selectors::Attribute::INCLUDES
259
+ Selectors::Attribute::INCLUDES,
260
+ val[1][1]
165
261
  )
166
262
  }
167
- | LSQUARE IDENT DASHMATCH IDENT RSQUARE {
263
+ | LSQUARE ident_with_namespace DASHMATCH IDENT RSQUARE {
168
264
  result = Selectors::Attribute.new(
169
- interpret_identifier(val[1]),
265
+ val[1][0],
170
266
  interpret_identifier(val[3]),
171
- Selectors::Attribute::DASHMATCH
267
+ Selectors::Attribute::DASHMATCH,
268
+ val[1][1]
172
269
  )
173
270
  }
174
- | LSQUARE IDENT DASHMATCH STRING RSQUARE {
271
+ | LSQUARE ident_with_namespace DASHMATCH STRING RSQUARE {
175
272
  result = Selectors::Attribute.new(
176
- interpret_identifier(val[1]),
273
+ val[1][0],
177
274
  interpret_string(val[3]),
178
- Selectors::Attribute::DASHMATCH
275
+ Selectors::Attribute::DASHMATCH,
276
+ val[1][1]
179
277
  )
180
278
  }
181
- | LSQUARE IDENT RSQUARE {
279
+ | LSQUARE ident_with_namespace PREFIXMATCH IDENT RSQUARE {
182
280
  result = Selectors::Attribute.new(
183
- interpret_identifier(val[1]),
281
+ val[1][0],
282
+ interpret_identifier(val[3]),
283
+ Selectors::Attribute::PREFIXMATCH,
284
+ val[1][1]
285
+ )
286
+ }
287
+ | LSQUARE ident_with_namespace PREFIXMATCH STRING RSQUARE {
288
+ result = Selectors::Attribute.new(
289
+ val[1][0],
290
+ interpret_string(val[3]),
291
+ Selectors::Attribute::PREFIXMATCH,
292
+ val[1][1]
293
+ )
294
+ }
295
+ | LSQUARE ident_with_namespace SUFFIXMATCH IDENT RSQUARE {
296
+ result = Selectors::Attribute.new(
297
+ val[1][0],
298
+ interpret_identifier(val[3]),
299
+ Selectors::Attribute::SUFFIXMATCH,
300
+ val[1][1]
301
+ )
302
+ }
303
+ | LSQUARE ident_with_namespace SUFFIXMATCH STRING RSQUARE {
304
+ result = Selectors::Attribute.new(
305
+ val[1][0],
306
+ interpret_string(val[3]),
307
+ Selectors::Attribute::SUFFIXMATCH,
308
+ val[1][1]
309
+ )
310
+ }
311
+ | LSQUARE ident_with_namespace SUBSTRINGMATCH IDENT RSQUARE {
312
+ result = Selectors::Attribute.new(
313
+ val[1][0],
314
+ interpret_identifier(val[3]),
315
+ Selectors::Attribute::SUBSTRINGMATCH,
316
+ val[1][1]
317
+ )
318
+ }
319
+ | LSQUARE ident_with_namespace SUBSTRINGMATCH STRING RSQUARE {
320
+ result = Selectors::Attribute.new(
321
+ val[1][0],
322
+ interpret_string(val[3]),
323
+ Selectors::Attribute::SUBSTRINGMATCH,
324
+ val[1][1]
325
+ )
326
+ }
327
+ | LSQUARE ident_with_namespace RSQUARE {
328
+ result = Selectors::Attribute.new(
329
+ val[1][0],
184
330
  nil,
185
- Selectors::Attribute::SET
331
+ Selectors::Attribute::SET,
332
+ val[1][1]
186
333
  )
187
334
  }
188
335
  ;
189
336
  pseudo
190
337
  : ':' IDENT {
191
- result = Selectors::PseudoClass.new(
192
- interpret_identifier(val[1])
338
+ result = Selectors::pseudo interpret_identifier(val[1])
339
+ }
340
+ | ':' ':' IDENT {
341
+ result = Selectors::PseudoElement.new(
342
+ interpret_identifier(val[2])
193
343
  )
194
344
  }
195
345
  | ':' FUNCTION RPAREN {
@@ -204,11 +354,36 @@ rule
204
354
  interpret_identifier(val[2])
205
355
  )
206
356
  }
357
+ | ':' NOT_PSEUDO_CLASS simple_selector RPAREN {
358
+ result = Selectors::PseudoClass.new(
359
+ 'not',
360
+ val[2].first.to_s
361
+ )
362
+ }
363
+ | ':' NTH_PSEUDO_CLASS {
364
+ result = Selectors::PseudoClass.new(
365
+ interpret_identifier(val[1].sub(/\(.*/, '')),
366
+ interpret_identifier(val[1].sub(/.*\(/, '').sub(/\).*/, ''))
367
+ )
368
+ }
369
+ | ':' MATCHES_PSEUDO_CLASS simple_selectors RPAREN {
370
+ result = Selectors::PseudoClass.new(
371
+ val[1].split('(').first.strip,
372
+ val[2].join(', ')
373
+ )
374
+ }
375
+ ;
376
+ # declarations can be separated by one *or more* semicolons. semi-colons at the start or end of a ruleset are also allowed
377
+ one_or_more_semis
378
+ : SEMI
379
+ | SEMI one_or_more_semis
207
380
  ;
208
381
  declarations
209
- : declaration SEMI declarations
210
- | declaration SEMI
382
+ : declaration one_or_more_semis declarations
383
+ | one_or_more_semis declarations
384
+ | declaration one_or_more_semis
211
385
  | declaration
386
+ | one_or_more_semis
212
387
  ;
213
388
  declaration
214
389
  : property ':' expr prio
@@ -247,6 +422,7 @@ rule
247
422
  | string
248
423
  | uri
249
424
  | hexcolor
425
+ | math
250
426
  | function
251
427
  ;
252
428
  function
@@ -260,6 +436,22 @@ rule
260
436
  end
261
437
  }
262
438
  ;
439
+ function_no_quote
440
+ : function_no_quote S { result = val.first }
441
+ | FUNCTION_NO_QUOTE {
442
+ parts = val.first.split('(')
443
+ name = interpret_identifier parts.first
444
+ result = Terms::Function.new(name, [Terms::String.new(interpret_string_no_quote(parts.last))])
445
+ }
446
+ ;
447
+ math
448
+ : MATH {
449
+ parts = val.first.split('(', 2)
450
+ name = parts[0].strip
451
+ expression = parts[1][0..parts[1].rindex(')')-1].strip
452
+ result = Terms::Math.new(name, expression)
453
+ }
454
+ ;
263
455
  hexcolor
264
456
  : hexcolor S { result = val.first }
265
457
  | HASH { result = Terms::Hash.new val.first.sub(/^#/, '') }
@@ -267,6 +459,7 @@ rule
267
459
  uri
268
460
  : uri S { result = val.first }
269
461
  | URI { result = Terms::URI.new interpret_uri val.first }
462
+ ;
270
463
  string
271
464
  : string S { result = val.first }
272
465
  | STRING { result = Terms::String.new interpret_string val.first }
@@ -326,7 +519,11 @@ def interpret_identifier s
326
519
  end
327
520
 
328
521
  def interpret_uri s
329
- interpret_escapes s.match(/^url\((.*)\)$/mu)[1].strip.match(/^(['"]?)((?:\\.|.)*)\1$/mu)[2]
522
+ interpret_escapes s.match(/^url\((.*)\)$/mui)[1].strip.match(/^(['"]?)((?:\\.|.)*)\1$/mu)[2]
523
+ end
524
+
525
+ def interpret_string_no_quote s
526
+ interpret_escapes s.match(/^(.*)\)$/mu)[1].strip.match(/^(['"]?)((?:\\.|.)*)\1$/mu)[2]
330
527
  end
331
528
 
332
529
  def interpret_string s
@@ -58,7 +58,7 @@ class Tokenizer < Parser
58
58
  token = case @state
59
59
  when nil
60
60
  case
61
- when (text = @ss.scan(/url\([\s]*("([^\n\r\f\\"]|\\\n|\r\n|\r|\f|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*"|'([^\n\r\f\\']|\\\n|\r\n|\r|\f|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*')[\s]*\)/i))
61
+ when (text = @ss.scan(/url\([\s]*("([^\n\r\f\\"]|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*"|'([^\n\r\f\\']|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*')[\s]*\)/i))
62
62
  action { [:URI, st(text)] }
63
63
 
64
64
  when (text = @ss.scan(/url\([\s]*([!#\$%&*-~]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*[\s]*\)/i))
@@ -70,6 +70,24 @@ class Tokenizer < Parser
70
70
  when (text = @ss.scan(/[\s]*\/\*(.|[\s]*)*?\*\/[\s]*/i))
71
71
  action { next_token }
72
72
 
73
+ when (text = @ss.scan(/not\(\s*/i))
74
+ action { [:NOT_PSEUDO_CLASS, st(text)] }
75
+
76
+ when (text = @ss.scan(/(nth\-child|nth\-last\-child|nth\-of\-type|nth\-last\-of\-type)\([\s]*([\+\-]?[0-9]*n([\s]*[\+\-][\s]*[0-9]+)?|[\+\-]?[0-9]+|odd|even)[\s]*\)/i))
77
+ action { [:NTH_PSEUDO_CLASS, st(text)] }
78
+
79
+ when (text = @ss.scan(/(\-[A-Za-z]+\-)?(matches|any)\(\s*/i))
80
+ action { [:MATCHES_PSEUDO_CLASS, st(text)] }
81
+
82
+ when (text = @ss.scan(/(domain|url\-prefix)\([\s]*("([^\n\r\f\\"]|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*"|'([^\n\r\f\\']|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*')[\s]*\)/i))
83
+ action { [:FUNCTION_NO_QUOTE, st(text)] }
84
+
85
+ when (text = @ss.scan(/(domain|url\-prefix)\([\s]*([!#\$%&*-~]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*[\s]*\)/i))
86
+ action { [:FUNCTION_NO_QUOTE, st(text)] }
87
+
88
+ when (text = @ss.scan(/[\s]*(\-[A-Za-z]+\-)?calc\([\s]*(([0-9]*\.[0-9]+|[0-9]+)|([0-9]*\.[0-9]+|[0-9]+)(px|cm|mm|in|pt|pc)|([0-9]*\.[0-9]+|[0-9]+)%|([0-9]*\.[0-9]+|[0-9]+)em|([0-9]*\.[0-9]+|[0-9]+)ex)([\s]*(\*[\s]*(([0-9]*\.[0-9]+|[0-9]+)|([0-9]*\.[0-9]+|[0-9]+)(px|cm|mm|in|pt|pc)|([0-9]*\.[0-9]+|[0-9]+)%|([0-9]*\.[0-9]+|[0-9]+)em|([0-9]*\.[0-9]+|[0-9]+)ex)|\/[\s]*([0-9]*\.[0-9]+|[0-9]+)))*([\s]+[\+\-][\s]+(([0-9]*\.[0-9]+|[0-9]+)|([0-9]*\.[0-9]+|[0-9]+)(px|cm|mm|in|pt|pc)|([0-9]*\.[0-9]+|[0-9]+)%|([0-9]*\.[0-9]+|[0-9]+)em|([0-9]*\.[0-9]+|[0-9]+)ex)([\s]*(\*[\s]*(([0-9]*\.[0-9]+|[0-9]+)|([0-9]*\.[0-9]+|[0-9]+)(px|cm|mm|in|pt|pc)|([0-9]*\.[0-9]+|[0-9]+)%|([0-9]*\.[0-9]+|[0-9]+)em|([0-9]*\.[0-9]+|[0-9]+)ex)|\/[\s]*([0-9]*\.[0-9]+|[0-9]+)))*)*[\s]*\)[\s]*[\s]*/i))
89
+ action { [:MATH, st(text)] }
90
+
73
91
  when (text = @ss.scan(/[-@]?([_A-Za-z]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])([_A-Za-z0-9-]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f]|[.])*\(\s*/i))
74
92
  action { [:FUNCTION, st(text)] }
75
93
 
@@ -85,6 +103,15 @@ class Tokenizer < Parser
85
103
  when (text = @ss.scan(/[\s]*@media[\s]*/i))
86
104
  action { [:MEDIA_SYM, st(text)] }
87
105
 
106
+ when (text = @ss.scan(/[\s]*@(\-[A-Za-z]+\-)?document[\s]*/i))
107
+ action { [:DOCUMENT_QUERY_SYM, st(text)] }
108
+
109
+ when (text = @ss.scan(/[\s]*@namespace[\s]*/i))
110
+ action { [:NAMESPACE_SYM, st(text)] }
111
+
112
+ when (text = @ss.scan(/[\s]*@(\-[A-Za-z]+\-)?keyframes[\s]*/i))
113
+ action { [:KEYFRAMES_SYM, st(text)] }
114
+
88
115
  when (text = @ss.scan(/[\s]*!([\s]*|[\s]*\/\*(.|[\s]*)*?\*\/[\s]*)important[\s]*/i))
89
116
  action { [:IMPORTANT_SYM, st(text)] }
90
117
 
@@ -118,7 +145,7 @@ class Tokenizer < Parser
118
145
  when (text = @ss.scan(/[\s]*\)/i))
119
146
  action { [:RPAREN, st(text)] }
120
147
 
121
- when (text = @ss.scan(/[\s]*\[[\s]*/i))
148
+ when (text = @ss.scan(/\[[\s]*/i))
122
149
  action { [:LSQUARE, st(text)] }
123
150
 
124
151
  when (text = @ss.scan(/[\s]*\]/i))
@@ -148,9 +175,6 @@ class Tokenizer < Parser
148
175
  when (text = @ss.scan(/[\s]*~[\s]*/i))
149
176
  action { [:TILDE, st(text)] }
150
177
 
151
- when (text = @ss.scan(/\:not\([\s]*/i))
152
- action { [:NOT, st(text)] }
153
-
154
178
  when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)em[\s]*/i))
155
179
  action { [:EMS, st(text)] }
156
180
 
@@ -196,10 +220,10 @@ class Tokenizer < Parser
196
220
  when (text = @ss.scan(/[\s]+/i))
197
221
  action { [:S, st(text)] }
198
222
 
199
- when (text = @ss.scan(/("([^\n\r\f\\"]|\\\n|\r\n|\r|\f|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*"|'([^\n\r\f\\']|\\\n|\r\n|\r|\f|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*')/i))
223
+ when (text = @ss.scan(/("([^\n\r\f\\"]|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*"|'([^\n\r\f\\']|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*')/i))
200
224
  action { [:STRING, st(text)] }
201
225
 
202
- when (text = @ss.scan(/("([^\n\r\f\\"]|\\\n|\r\n|\r|\f|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*|'([^\n\r\f\\']|\\\n|\r\n|\r|\f|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*)/i))
226
+ when (text = @ss.scan(/("([^\n\r\f\\"]|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*|'([^\n\r\f\\']|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*)/i))
203
227
  action { [:INVALID, st(text)] }
204
228
 
205
229
  when (text = @ss.scan(/./i))
@@ -3,11 +3,17 @@ module CSS
3
3
  class Tokenizer < Parser
4
4
 
5
5
  macro
6
- nl \n|\r\n|\r|\f
6
+ nl (\n|\r\n|\r|\f)
7
7
  w [\s]*
8
8
  nonascii [^\0-\177]
9
9
  num ([0-9]*\.[0-9]+|[0-9]+)
10
+ length {num}(px|cm|mm|in|pt|pc)
11
+ percentage {num}%
12
+ ems {num}em
13
+ exs {num}ex
10
14
  unicode \\[0-9A-Fa-f]{1,6}(\r\n|[\s])?
15
+ nth ([\+\-]?[0-9]*n({w}[\+\-]{w}[0-9]+)?|[\+\-]?[0-9]+|odd|even)
16
+ vendorprefix \-[A-Za-z]+\-
11
17
 
12
18
  escape {unicode}|\\[^\n\r\f0-9A-Fa-f]
13
19
  nmchar [_A-Za-z0-9-]|{nonascii}|{escape}
@@ -23,6 +29,12 @@ macro
23
29
  invalid ({invalid1}|{invalid2})
24
30
  comment \/\*(.|{w})*?\*\/
25
31
 
32
+ unit ({num}|{length}|{percentage}|{ems}|{exs})
33
+ product {unit}({w}(\*{w}{unit}|\/{w}{num}))*
34
+ sum {product}([\s]+[\+\-][\s]+{product})*
35
+ calc ({vendorprefix})?calc\({w}{sum}{w}\)
36
+ math {calc}{w}
37
+
26
38
  rule
27
39
 
28
40
  # [:state] pattern [actions]
@@ -32,11 +44,29 @@ rule
32
44
  U\+[0-9a-fA-F?]{1,6}(-[0-9a-fA-F]{1,6})? {[:UNICODE_RANGE, st(text)] }
33
45
  {w}{comment}{w} { next_token }
34
46
 
47
+ # this one takes a selector as a parameter
48
+ not\(\s* { [:NOT_PSEUDO_CLASS, st(text)] }
49
+
50
+ # this one takes an "nth" value
51
+ (nth\-child|nth\-last\-child|nth\-of\-type|nth\-last\-of\-type)\({w}{nth}{w}\) { [:NTH_PSEUDO_CLASS, st(text)] }
52
+
53
+ # this one takes a comma-separated list of simple selectors as a parameter
54
+ ({vendorprefix})?(matches|any)\(\s* { [:MATCHES_PSEUDO_CLASS, st(text)] }
55
+
56
+ # functions that can take an unquoted string parameter
57
+ (domain|url\-prefix)\({w}{string}{w}\) { [:FUNCTION_NO_QUOTE, st(text)] }
58
+ (domain|url\-prefix)\({w}([!#\$%&*-~]|{nonascii}|{escape})*{w}\) { [:FUNCTION_NO_QUOTE, st(text)] }
59
+
60
+ {w}{math}{w} { [:MATH, st(text)] }
61
+
35
62
  {func}\(\s* { [:FUNCTION, st(text)] }
36
63
  {w}@import{w} { [:IMPORT_SYM, st(text)] }
37
64
  {w}@page{w} { [:PAGE_SYM, st(text)] }
38
65
  {w}@charset{w} { [:CHARSET_SYM, st(text)] }
39
66
  {w}@media{w} { [:MEDIA_SYM, st(text)] }
67
+ {w}@({vendorprefix})?document{w} { [:DOCUMENT_QUERY_SYM, st(text)] }
68
+ {w}@namespace{w} { [:NAMESPACE_SYM, st(text)] }
69
+ {w}@({vendorprefix})?keyframes{w} { [:KEYFRAMES_SYM, st(text)] }
40
70
  {w}!({w}|{w}{comment}{w})important{w} { [:IMPORTANT_SYM, st(text)] }
41
71
  {ident} { [:IDENT, st(text)] }
42
72
  \#{name} { [:HASH, st(text)] }
@@ -48,7 +78,7 @@ rule
48
78
  {w}!={w} { [:NOT_EQUAL, st(text)] }
49
79
  {w}={w} { [:EQUAL, st(text)] }
50
80
  {w}\) { [:RPAREN, st(text)] }
51
- {w}\[{w} { [:LSQUARE, st(text)] }
81
+ \[{w} { [:LSQUARE, st(text)] }
52
82
  {w}\] { [:RSQUARE, st(text)] }
53
83
  {w}\+{w} { [:PLUS, st(text)] }
54
84
  {w}\{{w} { [:LBRACE, st(text)] }
@@ -58,16 +88,15 @@ rule
58
88
  {w};{w} { [:SEMI, st(';')] }
59
89
  \* { [:STAR, st(text)] }
60
90
  {w}~{w} { [:TILDE, st(text)] }
61
- \:not\({w} { [:NOT, st(text)] }
62
- {w}{num}em{w} { [:EMS, st(text)] }
63
- {w}{num}ex{w} { [:EXS, st(text)] }
91
+ {w}{ems}{w} { [:EMS, st(text)] }
92
+ {w}{exs}{w} { [:EXS, st(text)] }
64
93
 
65
- {w}{num}(px|cm|mm|in|pt|pc){w} { [:LENGTH, st(text)] }
94
+ {w}{length}{w} { [:LENGTH, st(text)] }
66
95
  {w}{num}(deg|rad|grad){w} { [:ANGLE, st(text)] }
67
96
  {w}{num}(ms|s){w} { [:TIME, st(text)] }
68
97
  {w}{num}[k]?hz{w} { [:FREQ, st(text)] }
69
98
 
70
- {w}{num}%{w} { [:PERCENTAGE, st(text)] }
99
+ {w}{percentage}{w} { [:PERCENTAGE, st(text)] }
71
100
  {w}{num}{w} { [:NUMBER, st(text)] }
72
101
  {w}\/\/{w} { [:DOUBLESLASH, st(text)] }
73
102
  {w}\/{w} { [:SLASH, st('/')] }
data/lib/csspool/css.rb CHANGED
@@ -6,3 +6,7 @@ require 'csspool/css/rule_set'
6
6
  require 'csspool/css/declaration'
7
7
  require 'csspool/css/document'
8
8
  require 'csspool/css/document_handler'
9
+ require 'csspool/css/document_query'
10
+ require 'csspool/css/namespace_rule'
11
+ require 'csspool/css/keyframes_rule'
12
+ require 'csspool/css/keyframes_block'
@@ -17,10 +17,14 @@ module CSSPool
17
17
  def specificity
18
18
  a = b = c = 0
19
19
  simple_selectors.each do |s|
20
- c += 1
20
+ if !s.name.nil?
21
+ c += 1
22
+ end
21
23
  s.additional_selectors.each do |additional_selector|
22
24
  if Selectors::Id === additional_selector
23
25
  a += 1
26
+ elsif Selectors::PseudoElement === additional_selector
27
+ c += 1
24
28
  else
25
29
  b += 1
26
30
  end
@@ -4,17 +4,22 @@ module CSSPool
4
4
  attr_accessor :name
5
5
  attr_accessor :value
6
6
  attr_accessor :match_way
7
+ attr_accessor :namespace
7
8
 
8
9
  NO_MATCH = 0
9
10
  SET = 1
10
11
  EQUALS = 2
11
12
  INCLUDES = 3
12
13
  DASHMATCH = 4
14
+ PREFIXMATCH = 5
15
+ SUFFIXMATCH = 6
16
+ SUBSTRINGMATCH = 7
13
17
 
14
- def initialize name, value, match_way
18
+ def initialize name, value, match_way, namespace = nil
15
19
  @name = name
16
20
  @value = value
17
21
  @match_way = match_way
22
+ @namespace = namespace
18
23
  end
19
24
  end
20
25
  end
@@ -0,0 +1,17 @@
1
+ require 'csspool/selectors/pseudo_class'
2
+ require 'csspool/selectors/pseudo_element'
3
+
4
+ module CSSPool
5
+ module Selectors
6
+ def Selectors.pseudo name
7
+ # FIXME: This is a bit of an ugly solution. Should be able to handle it
8
+ # more elegantly, and without calling out css2
9
+ css2_pseudo_elements =
10
+ if %w{after before first-letter first-line}.include? name
11
+ PseudoElement.new name, true
12
+ else
13
+ PseudoClass.new name
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module CSSPool
2
+ module Selectors
3
+ class PseudoElement < CSSPool::Selectors::Additional
4
+ attr_accessor :name
5
+ attr_accessor :css2
6
+
7
+ def initialize name, css2 = nil
8
+ @name = name
9
+ @css2 = css2
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,10 +1,6 @@
1
1
  module CSSPool
2
2
  module Selectors
3
3
  class Simple < CSSPool::Node
4
- NO_COMBINATOR = 0
5
- DESCENDENT = 1
6
- PRECEDED_BY = 2
7
- CHILD = 3
8
4
 
9
5
  attr_accessor :name
10
6
  attr_accessor :parse_location
@@ -1,6 +1,13 @@
1
1
  module CSSPool
2
2
  module Selectors
3
3
  class Type < CSSPool::Selectors::Simple
4
+
5
+ attr_accessor :namespace
6
+
7
+ def initialize name, combinator = nil, namespace = nil
8
+ super name, combinator
9
+ @namespace = namespace
10
+ end
4
11
  end
5
12
  end
6
13
  end
@@ -1,6 +1,13 @@
1
1
  module CSSPool
2
2
  module Selectors
3
3
  class Universal < CSSPool::Selectors::Simple
4
+
5
+ attr_accessor :namespace
6
+
7
+ def initialize name, combinator = nil, namespace = nil
8
+ super name, combinator
9
+ @namespace = namespace
10
+ end
4
11
  end
5
12
  end
6
13
  end
@@ -4,5 +4,5 @@ require 'csspool/selectors/type'
4
4
  require 'csspool/selectors/additional'
5
5
  require 'csspool/selectors/id'
6
6
  require 'csspool/selectors/class'
7
- require 'csspool/selectors/pseudo_class'
7
+ require 'csspool/selectors/pseudo'
8
8
  require 'csspool/selectors/attribute'