csspool 4.0.0.pre → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +12 -0
  3. data/Gemfile.lock +6 -6
  4. data/Manifest.txt +10 -2
  5. data/Rakefile +1 -0
  6. data/lib/csspool/css/declaration.rb +1 -1
  7. data/lib/csspool/css/document.rb +5 -1
  8. data/lib/csspool/css/document_handler.rb +43 -11
  9. data/lib/csspool/css/fontface_rule.rb +13 -0
  10. data/lib/csspool/css/import_rule.rb +0 -5
  11. data/lib/csspool/css/media_feature.rb +14 -0
  12. data/lib/csspool/css/media_query.rb +19 -0
  13. data/lib/csspool/css/media_query_list.rb +41 -0
  14. data/lib/csspool/css/{media.rb → media_type.rb} +6 -4
  15. data/lib/csspool/css/parser.rb +1272 -671
  16. data/lib/csspool/css/parser.y +223 -53
  17. data/lib/csspool/css/rule_set.rb +4 -3
  18. data/lib/csspool/css/supports_rule.rb +14 -0
  19. data/lib/csspool/css/tokenizer.rb +92 -16
  20. data/lib/csspool/css/tokenizer.rex +42 -19
  21. data/lib/csspool/css.rb +6 -1
  22. data/lib/csspool/node.rb +22 -0
  23. data/lib/csspool/selector.rb +5 -4
  24. data/lib/csspool/selectors/pseudo.rb +2 -2
  25. data/lib/csspool/terms/function.rb +1 -1
  26. data/lib/csspool/terms/resolution.rb +13 -0
  27. data/lib/csspool/terms.rb +1 -0
  28. data/lib/csspool/visitors/children.rb +16 -2
  29. data/lib/csspool/visitors/comparable.rb +27 -8
  30. data/lib/csspool/visitors/iterator.rb +5 -3
  31. data/lib/csspool/visitors/to_css.rb +73 -20
  32. data/test/css/test_document_query.rb +37 -38
  33. data/test/css/test_font_face.rb +16 -0
  34. data/test/css/test_import_rule.rb +0 -3
  35. data/test/css/test_media_rule.rb +92 -0
  36. data/test/css/test_node_position.rb +81 -0
  37. data/test/css/test_parser.rb +12 -39
  38. data/test/css/test_supports_rule.rb +133 -0
  39. data/test/css/test_tokenizer.rb +4 -4
  40. data/test/css/test_variables.rb +33 -0
  41. data/test/helper.rb +3 -3
  42. data/test/sac/test_parser.rb +1 -0
  43. data/test/test_parser.rb +22 -9
  44. data/test/test_selector.rb +44 -4
  45. data/test/test_term.rb +29 -0
  46. data/test/visitors/test_comparable.rb +8 -0
  47. data/test/visitors/test_to_css.rb +89 -13
  48. metadata +54 -58
  49. data/test/_local_helper.rb +0 -2
@@ -1,17 +1,24 @@
1
1
  class CSSPool::CSS::Parser
2
2
 
3
3
  token CHARSET_SYM IMPORT_SYM STRING SEMI IDENT S COMMA LBRACE RBRACE STAR HASH
4
- token LSQUARE RSQUARE EQUAL INCLUDES DASHMATCH RPAREN FUNCTION GREATER PLUS
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
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
4
+ token LSQUARE RSQUARE EQUAL INCLUDES DASHMATCH LPAREN RPAREN FUNCTION GREATER PLUS
5
+ token SLASH NUMBER MINUS LENGTH PERCENTAGE ANGLE TIME FREQ URI
6
+ token IMPORTANT_SYM MEDIA_SYM NOT ONLY AND NTH_PSEUDO_CLASS
7
+ token DOCUMENT_QUERY_SYM FUNCTION_NO_QUOTE
8
+ token TILDE
9
+ token PREFIXMATCH SUFFIXMATCH SUBSTRINGMATCH
10
+ token NOT_PSEUDO_CLASS
11
+ token KEYFRAMES_SYM
12
+ token MATCHES_PSEUDO_CLASS
13
+ token NAMESPACE_SYM
14
+ token MOZ_PSEUDO_ELEMENT
15
+ token RESOLUTION
16
+ token COLON
17
+ token SUPPORTS_SYM
18
+ token OR
19
+ token VARIABLE_NAME
20
+ token CALC_SYM
21
+ token FONTFACE_SYM
15
22
 
16
23
  rule
17
24
  document
@@ -33,7 +40,7 @@ rule
33
40
  ;
34
41
  import
35
42
  : IMPORT_SYM import_location medium SEMI {
36
- @handler.import_style [val[2]].flatten, val[1]
43
+ @handler.import_style val[2], val[1]
37
44
  }
38
45
  | IMPORT_SYM import_location SEMI {
39
46
  @handler.import_style [], val[1]
@@ -54,10 +61,46 @@ rule
54
61
  ;
55
62
  medium
56
63
  : medium COMMA IDENT {
57
- result = [val.first, Terms::Ident.new(interpret_identifier val.last)]
64
+ result = val[0] << MediaType.new(val[2])
58
65
  }
59
66
  | IDENT {
60
- result = Terms::Ident.new interpret_identifier val.first
67
+ result = [MediaType.new(val[0])]
68
+ }
69
+ ;
70
+ media_query_list
71
+ : media_query { result = MediaQueryList.new([ val[0] ]) }
72
+ | media_query_list COMMA media_query { result = val[0] << val[2] }
73
+ | { result = MediaQueryList.new }
74
+ ;
75
+ media_query
76
+ : optional_only_or_not media_type optional_and_exprs { result = MediaQuery.new(val[0], val[1], val[2]) }
77
+ | media_expr optional_and_exprs { result = MediaQuery.new(nil, val[0], val[1]) }
78
+ ;
79
+ optional_only_or_not
80
+ : ONLY { result = :only }
81
+ | NOT { result = :not }
82
+ | { result = nil }
83
+ ;
84
+ media_type
85
+ : IDENT { result = MediaType.new(val[0]) }
86
+ ;
87
+ media_expr
88
+ : LPAREN optional_space IDENT optional_space RPAREN { result = MediaType.new(val[2]) }
89
+ | LPAREN optional_space IDENT optional_space COLON optional_space expr RPAREN { result = MediaFeature.new(val[2], val[6][0]) }
90
+ ;
91
+ optional_space
92
+ : S { result = val[0] }
93
+ | { result = nil }
94
+ ;
95
+ optional_and_exprs
96
+ : optional_and_exprs AND media_expr { result = val[0] << val[2] }
97
+ | { result = [] }
98
+ ;
99
+ resolution
100
+ : RESOLUTION {
101
+ unit = val.first.gsub(/[\s\d.]/, '')
102
+ number = numeric(val.first)
103
+ result = Terms::Resolution.new(number, unit)
61
104
  }
62
105
  ;
63
106
  body
@@ -67,28 +110,38 @@ rule
67
110
  | ruleset
68
111
  | conditional_rule
69
112
  | keyframes_rule
113
+ | fontface_rule
70
114
  ;
71
115
  conditional_rule
72
116
  : media
73
117
  | document_query
118
+ | supports
119
+ ;
120
+ body_in_media
121
+ : body
122
+ | empty_ruleset
74
123
  ;
75
124
  media
76
- : start_media body RBRACE { @handler.end_media val.first }
125
+ : start_media body_in_media RBRACE { @handler.end_media val.first }
77
126
  ;
78
127
  start_media
79
- : MEDIA_SYM medium LBRACE {
80
- result = [val[1]].flatten
128
+ : MEDIA_SYM media_query_list LBRACE {
129
+ result = val[1]
81
130
  @handler.start_media result
82
131
  }
83
- | MEDIA_SYM LBRACE { result = [] }
84
132
  ;
85
133
  document_query
86
- : start_document_query body RBRACE { @handler.end_document_query }
87
- | start_document_query RBRACE { @handler.end_document_query }
134
+ : start_document_query body RBRACE { @handler.end_document_query(before_pos(val), after_pos(val)) }
135
+ | start_document_query RBRACE { @handler.end_document_query(before_pos(val), after_pos(val)) }
88
136
  ;
89
137
  start_document_query
90
- : DOCUMENT_QUERY_SYM url_match_fns LBRACE {
91
- @handler.start_document_query val[1]
138
+ : start_document_query_pos url_match_fns LBRACE {
139
+ @handler.start_document_query(val[1], after_pos(val))
140
+ }
141
+ ;
142
+ start_document_query_pos
143
+ : DOCUMENT_QUERY_SYM {
144
+ @handler.node_start_pos = before_pos(val)
92
145
  }
93
146
  ;
94
147
  url_match_fns
@@ -104,6 +157,48 @@ rule
104
157
  | function
105
158
  | uri
106
159
  ;
160
+ supports
161
+ : start_supports body RBRACE { @handler.end_supports }
162
+ | start_supports RBRACE { @handler.end_supports }
163
+ ;
164
+ start_supports
165
+ : SUPPORTS_SYM supports_condition_root LBRACE {
166
+ @handler.start_supports val[1]
167
+ }
168
+ ;
169
+ supports_condition_root
170
+ : supports_negation { result = val.join('') }
171
+ | supports_conjunction_or_disjunction { result = val.join('') }
172
+ | supports_condition_in_parens { result = val.join('') }
173
+ ;
174
+ supports_condition
175
+ : supports_negation { result = val.join('') }
176
+ | supports_conjunction_or_disjunction { result = val.join('') }
177
+ | supports_condition_in_parens { result = val.join('') }
178
+ ;
179
+ supports_condition_in_parens
180
+ : LPAREN supports_condition RPAREN { result = val.join('') }
181
+ | supports_declaration_condition { result = val.join('') }
182
+ ;
183
+ supports_negation
184
+ : NOT supports_condition_in_parens { result = val.join('') }
185
+ ;
186
+ supports_conjunction_or_disjunction
187
+ : supports_conjunction
188
+ | supports_disjunction
189
+ ;
190
+ supports_conjunction
191
+ : supports_condition_in_parens AND supports_condition_in_parens { result = val.join('') }
192
+ | supports_conjunction_or_disjunction AND supports_condition_in_parens { result = val.join('') }
193
+ ;
194
+ supports_disjunction
195
+ : supports_condition_in_parens OR supports_condition_in_parens { result = val.join('') }
196
+ | supports_conjunction_or_disjunction OR supports_condition_in_parens { result = val.join('') }
197
+ ;
198
+ supports_declaration_condition
199
+ : LPAREN declaration_internal RPAREN { result = val.join('') }
200
+ | LPAREN S declaration_internal RPAREN { result = val.join('') }
201
+ ;
107
202
  keyframes_rule
108
203
  : start_keyframes_rule keyframes_blocks RBRACE
109
204
  | start_keyframes_rule RBRACE
@@ -136,6 +231,15 @@ rule
136
231
  : IDENT
137
232
  | PERCENTAGE { result = val[0].strip }
138
233
  ;
234
+ fontface_rule
235
+ : start_fontface_rule declarations RBRACE { @handler.end_fontface_rule }
236
+ | start_fontface_rule RBRACE { @handler.end_fontface_rule }
237
+ ;
238
+ start_fontface_rule
239
+ : FONTFACE_SYM LBRACE {
240
+ @handler.start_fontface_rule
241
+ }
242
+ ;
139
243
  ruleset
140
244
  : start_selector declarations RBRACE {
141
245
  @handler.end_selector val.first
@@ -144,6 +248,12 @@ rule
144
248
  @handler.end_selector val.first
145
249
  }
146
250
  ;
251
+ empty_ruleset
252
+ : optional_space {
253
+ start = @handler.start_selector([])
254
+ @handler.end_selector(start)
255
+ }
256
+ ;
147
257
  start_selector
148
258
  : S start_selector { result = val.last }
149
259
  | selectors LBRACE {
@@ -153,9 +263,8 @@ rule
153
263
  selectors
154
264
  : selector COMMA selectors
155
265
  {
156
- # FIXME: should always garantee array
157
266
  sel = Selector.new(val.first, {})
158
- result = [sel, val[2]].flatten
267
+ result = [sel].concat(val[2])
159
268
  }
160
269
  | selector
161
270
  {
@@ -165,7 +274,7 @@ rule
165
274
  selector
166
275
  : simple_selector combinator selector
167
276
  {
168
- val = val.flatten
277
+ val.flatten!
169
278
  val[2].combinator = val.delete_at 1
170
279
  result = val
171
280
  }
@@ -334,44 +443,62 @@ rule
334
443
  }
335
444
  ;
336
445
  pseudo
337
- : ':' IDENT {
446
+ : COLON IDENT {
338
447
  result = Selectors::pseudo interpret_identifier(val[1])
339
448
  }
340
- | ':' ':' IDENT {
449
+ | COLON COLON IDENT {
341
450
  result = Selectors::PseudoElement.new(
342
451
  interpret_identifier(val[2])
343
452
  )
344
453
  }
345
- | ':' FUNCTION RPAREN {
454
+ | COLON FUNCTION RPAREN {
346
455
  result = Selectors::PseudoClass.new(
347
456
  interpret_identifier(val[1].sub(/\($/, '')),
348
457
  ''
349
458
  )
350
459
  }
351
- | ':' FUNCTION IDENT RPAREN {
460
+ | COLON FUNCTION IDENT RPAREN {
352
461
  result = Selectors::PseudoClass.new(
353
462
  interpret_identifier(val[1].sub(/\($/, '')),
354
463
  interpret_identifier(val[2])
355
464
  )
356
465
  }
357
- | ':' NOT_PSEUDO_CLASS simple_selector RPAREN {
466
+ | COLON NOT_PSEUDO_CLASS simple_selector RPAREN {
358
467
  result = Selectors::PseudoClass.new(
359
468
  'not',
360
469
  val[2].first.to_s
361
470
  )
362
471
  }
363
- | ':' NTH_PSEUDO_CLASS {
472
+ | COLON NTH_PSEUDO_CLASS {
364
473
  result = Selectors::PseudoClass.new(
365
474
  interpret_identifier(val[1].sub(/\(.*/, '')),
366
475
  interpret_identifier(val[1].sub(/.*\(/, '').sub(/\).*/, ''))
367
476
  )
368
477
  }
369
- | ':' MATCHES_PSEUDO_CLASS simple_selectors RPAREN {
478
+ | COLON MATCHES_PSEUDO_CLASS simple_selectors RPAREN {
370
479
  result = Selectors::PseudoClass.new(
371
480
  val[1].split('(').first.strip,
372
481
  val[2].join(', ')
373
482
  )
374
483
  }
484
+ | COLON MOZ_PSEUDO_ELEMENT any_number_of_idents RPAREN {
485
+ result = Selectors::PseudoElement.new(
486
+ interpret_identifier(val[1].sub(/\($/, ''))
487
+ )
488
+ }
489
+ | COLON COLON MOZ_PSEUDO_ELEMENT any_number_of_idents RPAREN {
490
+ result = Selectors::PseudoElement.new(
491
+ interpret_identifier(val[2].sub(/\($/, ''))
492
+ )
493
+ }
494
+ ;
495
+ any_number_of_idents
496
+ :
497
+ | multiple_idents
498
+ ;
499
+ multiple_idents
500
+ : IDENT
501
+ | IDENT COMMA multiple_idents
375
502
  ;
376
503
  # declarations can be separated by one *or more* semicolons. semi-colons at the start or end of a ruleset are also allowed
377
504
  one_or_more_semis
@@ -386,14 +513,17 @@ rule
386
513
  | one_or_more_semis
387
514
  ;
388
515
  declaration
389
- : property ':' expr prio
390
- { @handler.property val.first, val[2], val[3] }
391
- | property ':' S expr prio
392
- { @handler.property val.first, val[3], val[4] }
393
- | property S ':' expr prio
394
- { @handler.property val.first, val[3], val[4] }
395
- | property S ':' S expr prio
396
- { @handler.property val.first, val[4], val[5] }
516
+ : declaration_internal { @handler.property val.first }
517
+ ;
518
+ declaration_internal
519
+ : property COLON expr prio
520
+ { result = Declaration.new(val.first, val[2], val[3]) }
521
+ | property COLON S expr prio
522
+ { result = Declaration.new(val.first, val[3], val[4]) }
523
+ | property S COLON expr prio
524
+ { result = Declaration.new(val.first, val[3], val[4]) }
525
+ | property S COLON S expr prio
526
+ { result = Declaration.new(val.first, val[4], val[5]) }
397
527
  ;
398
528
  prio
399
529
  : IMPORTANT_SYM { result = true }
@@ -402,6 +532,7 @@ rule
402
532
  property
403
533
  : IDENT { result = interpret_identifier val[0] }
404
534
  | STAR IDENT { result = interpret_identifier val.join }
535
+ | VARIABLE_NAME { result = interpret_identifier val[0] }
405
536
  ;
406
537
  operator
407
538
  : COMMA
@@ -422,8 +553,10 @@ rule
422
553
  | string
423
554
  | uri
424
555
  | hexcolor
425
- | math
556
+ | calc
426
557
  | function
558
+ | resolution
559
+ | VARIABLE_NAME
427
560
  ;
428
561
  function
429
562
  : function S { result = val.first }
@@ -435,6 +568,10 @@ rule
435
568
  result = Terms::Function.new name, val[1]
436
569
  end
437
570
  }
571
+ | FUNCTION RPAREN {
572
+ name = interpret_identifier val.first.sub(/\($/, '')
573
+ result = Terms::Function.new name
574
+ }
438
575
  ;
439
576
  function_no_quote
440
577
  : function_no_quote S { result = val.first }
@@ -444,14 +581,27 @@ rule
444
581
  result = Terms::Function.new(name, [Terms::String.new(interpret_string_no_quote(parts.last))])
445
582
  }
446
583
  ;
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)
584
+ calc
585
+ : CALC_SYM calc_sum RPAREN optional_space {
586
+ result = Terms::Math.new(val.first.split('(').first, val[1])
453
587
  }
454
588
  ;
589
+ # plus and minus are supposed to have whitespace around them, per http://dev.w3.org/csswg/css-values/#calc-syntax, but the numbers are eating trailing whitespace, so we inject it back in
590
+ calc_sum
591
+ : calc_product
592
+ | calc_product PLUS calc_sum { val.insert(1, ' '); result = val.join('') }
593
+ | calc_product MINUS calc_sum { val.insert(1, ' '); result = val.join('') }
594
+ ;
595
+ calc_product
596
+ : calc_value
597
+ | calc_value STAR calc_value { result = val.join('') }
598
+ | calc_value SLASH calc_value { result = val.join('') }
599
+ ;
600
+ calc_value
601
+ : numeric { result = val.join('') }
602
+ | function { result = val.join('') } # for var() variable references
603
+ | LPAREN calc_sum RPAREN { result = val.join('') }
604
+ ;
455
605
  hexcolor
456
606
  : hexcolor S { result = val.first }
457
607
  | HASH { result = Terms::Hash.new val.first.sub(/^#/, '') }
@@ -479,12 +629,6 @@ rule
479
629
  unit = val.first.gsub(/[\s\d.]/, '')
480
630
  result = Terms::Number.new numeric(val.first), nil, unit
481
631
  }
482
- | EMS {
483
- result = Terms::Number.new numeric(val.first), nil, 'em'
484
- }
485
- | EXS {
486
- result = Terms::Number.new numeric(val.first), nil, 'ex'
487
- }
488
632
  | ANGLE {
489
633
  unit = val.first.gsub(/[\s\d.]/, '')
490
634
  result = Terms::Number.new numeric(val.first), nil, unit
@@ -548,3 +692,29 @@ def interpret_escapes s
548
692
  end
549
693
  end.join ''
550
694
  end
695
+
696
+ # override racc's on_error so we can have context in our error messages
697
+ def on_error(t, val, vstack)
698
+ errcontext = (@ss.pre_match[-10..-1] || @ss.pre_match) +
699
+ @ss.matched + @ss.post_match[0..9]
700
+ line_number = @ss.pre_match.lines.count
701
+ raise ParseError, sprintf("parse error on value %s (%s) " +
702
+ "on line %s around \"%s\"",
703
+ val.inspect, token_to_str(t) || '?',
704
+ line_number, errcontext)
705
+ end
706
+
707
+ def before_pos(val)
708
+ # don't include leading whitespace
709
+ return current_pos - val.last.length + val.last[/\A\s*/].size
710
+ end
711
+
712
+ def after_pos(val)
713
+ # don't include trailing whitespace
714
+ return current_pos - val.last[/\s*\z/].size
715
+ end
716
+
717
+ # charpos will work with multibyte strings but is not available until ruby 2
718
+ def current_pos
719
+ @ss.respond_to?('charpos') ? @ss.charpos : @ss.pos
720
+ end
@@ -3,15 +3,16 @@ module CSSPool
3
3
  class RuleSet < CSSPool::Node
4
4
  attr_accessor :selectors
5
5
  attr_accessor :declarations
6
- attr_accessor :media
6
+ attr_accessor :parent_rule
7
7
 
8
- def initialize selectors, declarations = [], media = []
8
+ def initialize selectors, declarations = [], parent_rule = nil
9
9
  @selectors = selectors
10
10
  @declarations = declarations
11
- @media = media
11
+ @parent_rule = parent_rule
12
12
 
13
13
  selectors.each { |sel| sel.rule_set = self }
14
14
  end
15
+
15
16
  end
16
17
  end
17
18
  end
@@ -0,0 +1,14 @@
1
+ # Represents an @supports conditional rule
2
+ module CSSPool
3
+ module CSS
4
+ class SupportsRule < CSSPool::Node
5
+ attr_accessor :conditions
6
+ attr_accessor :rule_sets
7
+
8
+ def initialize conditions
9
+ @conditions = conditions
10
+ @rule_sets = []
11
+ end
12
+ end
13
+ end
14
+ end
@@ -70,7 +70,7 @@ 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))
73
+ when (text = @ss.scan(/not\([\s]*/i))
74
74
  action { [:NOT_PSEUDO_CLASS, st(text)] }
75
75
 
76
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))
@@ -85,10 +85,19 @@ class Tokenizer < Parser
85
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
86
  action { [:FUNCTION_NO_QUOTE, st(text)] }
87
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)] }
88
+ when (text = @ss.scan(/(\-moz\-non\-element|\-moz\-anonymous\-block|\-moz\-anonymous\-positioned\-block|\-moz\-mathml\-anonymous\-block|\-moz\-xul\-anonymous\-block|\-moz\-hframeset\-border|\-moz\-vframeset\-border|\-moz\-line\-frame|\-moz\-button\-content|\-moz\-buttonlabel|\-moz\-cell\-content|\-moz\-dropdown\-list|\-moz\-fieldset\-content|\-moz\-frameset\-blank|\-moz\-display\-comboboxcontrol\-frame|\-moz\-html\-canvas\-content|\-moz\-inline\-table|\-moz\-table|\-moz\-table\-cell|\-moz\-table\-column\-group|\-moz\-table\-column|\-moz\-table\-outer|\-moz\-table\-row\-group|\-moz\-table\-row|\-moz\-canvas|\-moz\-pagebreak|\-moz\-page|\-moz\-pagecontent|\-moz\-page\-sequence|\-moz\-scrolled\-content|\-moz\-scrolled\-canvas|\-moz\-scrolled\-page\-sequence|\-moz\-column\-content|\-moz\-viewport|\-moz\-viewport\-scroll|\-moz\-anonymous\-flex\-item|\-moz\-tree\-column|\-moz\-tree\-row|\-moz\-tree\-separator|\-moz\-tree\-cell|\-moz\-tree\-indentation|\-moz\-tree\-line|\-moz\-tree\-twisty|\-moz\-tree\-image|\-moz\-tree\-cell\-text|\-moz\-tree\-checkbox|\-moz\-tree\-progressmeter|\-moz\-tree\-drop\-feedback|\-moz\-svg\-outer\-svg\-anon\-child|\-moz\-svg\-foreign\-content|\-moz\-svg\-text)\(/i))
89
+ action { [:MOZ_PSEUDO_ELEMENT, st(text)] }
90
90
 
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))
91
+ when (text = @ss.scan(/[\s]*@media[\s]*/i))
92
+ action { @state = :LOGICQUERY; [:MEDIA_SYM, st(text)] }
93
+
94
+ when (text = @ss.scan(/[\s]*@supports[\s]*/i))
95
+ action { @state = :LOGICQUERY; [:SUPPORTS_SYM, st(text)] }
96
+
97
+ when (text = @ss.scan(/calc\(\s*/i))
98
+ action { [:CALC_SYM, st(text)] }
99
+
100
+ 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))
92
101
  action { [:FUNCTION, st(text)] }
93
102
 
94
103
  when (text = @ss.scan(/[\s]*@import[\s]*/i))
@@ -100,22 +109,25 @@ class Tokenizer < Parser
100
109
  when (text = @ss.scan(/[\s]*@charset[\s]*/i))
101
110
  action { [:CHARSET_SYM, st(text)] }
102
111
 
103
- when (text = @ss.scan(/[\s]*@media[\s]*/i))
104
- action { [:MEDIA_SYM, st(text)] }
105
-
106
112
  when (text = @ss.scan(/[\s]*@(\-[A-Za-z]+\-)?document[\s]*/i))
107
113
  action { [:DOCUMENT_QUERY_SYM, st(text)] }
108
114
 
109
115
  when (text = @ss.scan(/[\s]*@namespace[\s]*/i))
110
116
  action { [:NAMESPACE_SYM, st(text)] }
111
117
 
118
+ when (text = @ss.scan(/[\s]*@font\-face[\s]*/i))
119
+ action { [:FONTFACE_SYM, st(text)] }
120
+
112
121
  when (text = @ss.scan(/[\s]*@(\-[A-Za-z]+\-)?keyframes[\s]*/i))
113
122
  action { [:KEYFRAMES_SYM, st(text)] }
114
123
 
115
124
  when (text = @ss.scan(/[\s]*!([\s]*|[\s]*\/\*(.|[\s]*)*?\*\/[\s]*)important[\s]*/i))
116
125
  action { [:IMPORTANT_SYM, st(text)] }
117
126
 
118
- 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])*/i))
127
+ when (text = @ss.scan(/\-\-([_A-Za-z0-9-]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])+/i))
128
+ action { [:VARIABLE_NAME, st(text)] }
129
+
130
+ 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])*/i))
119
131
  action { [:IDENT, st(text)] }
120
132
 
121
133
  when (text = @ss.scan(/\#([_A-Za-z0-9-]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])+/i))
@@ -145,6 +157,9 @@ class Tokenizer < Parser
145
157
  when (text = @ss.scan(/[\s]*\)/i))
146
158
  action { [:RPAREN, st(text)] }
147
159
 
160
+ when (text = @ss.scan(/[\s]*\(/i))
161
+ action { [:LPAREN, st(text)] }
162
+
148
163
  when (text = @ss.scan(/\[[\s]*/i))
149
164
  action { [:LSQUARE, st(text)] }
150
165
 
@@ -169,19 +184,16 @@ class Tokenizer < Parser
169
184
  when (text = @ss.scan(/[\s]*;[\s]*/i))
170
185
  action { [:SEMI, st(';')] }
171
186
 
187
+ when (text = @ss.scan(/\:/i))
188
+ action { [:COLON, st(text)] }
189
+
172
190
  when (text = @ss.scan(/\*/i))
173
191
  action { [:STAR, st(text)] }
174
192
 
175
193
  when (text = @ss.scan(/[\s]*~[\s]*/i))
176
194
  action { [:TILDE, st(text)] }
177
195
 
178
- when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)em[\s]*/i))
179
- action { [:EMS, st(text)] }
180
-
181
- when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)ex[\s]*/i))
182
- action { [:EXS, st(text)] }
183
-
184
- when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)(px|cm|mm|in|pt|pc)[\s]*/i))
196
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)(px|cm|mm|in|pt|pc|mozmm|em|ex|ch|rem|vh|vw|vmin|vmax)[\s]*/i))
185
197
  action { [:LENGTH, st(text)] }
186
198
 
187
199
  when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)(deg|rad|grad)[\s]*/i))
@@ -211,7 +223,7 @@ class Tokenizer < Parser
211
223
  when (text = @ss.scan(/-->/i))
212
224
  action { [:CDC, st(text)] }
213
225
 
214
- when (text = @ss.scan(/[\s]*\-(?![-@]?([_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))
226
+ when (text = @ss.scan(/[\s]*\-(?!\-?([_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))
215
227
  action { [:MINUS, st(text)] }
216
228
 
217
229
  when (text = @ss.scan(/[\s]*\+[\s]*/i))
@@ -234,6 +246,70 @@ class Tokenizer < Parser
234
246
  raise ScanError, "can not match: '" + text + "'"
235
247
  end # if
236
248
 
249
+ when :LOGICQUERY
250
+ case
251
+ 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))
252
+ action { [:URI, st(text)] }
253
+
254
+ 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))
255
+ action { [:URI, st(text)] }
256
+
257
+ 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))
258
+ action { [:FUNCTION, st(text)] }
259
+
260
+ when (text = @ss.scan(/[\s]*(and|only|not|or)[\s]+/i))
261
+ action { [text.upcase.strip.intern, st(text)] }
262
+
263
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)(dpi|dpcm)/i))
264
+ action { [:RESOLUTION, st(text)]}
265
+
266
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)(px|cm|mm|in|pt|pc|mozmm|em|ex|ch|rem|vh|vw|vmin|vmax)[\s]*/i))
267
+ action { [:LENGTH, st(text)] }
268
+
269
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)(deg|rad|grad)[\s]*/i))
270
+ action { [:ANGLE, st(text)] }
271
+
272
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)(ms|s)[\s]*/i))
273
+ action { [:TIME, st(text)] }
274
+
275
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)[k]?hz[\s]*/i))
276
+ action { [:FREQ, st(text)] }
277
+
278
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)%[\s]*/i))
279
+ action { [:PERCENTAGE, st(text)] }
280
+
281
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)[\s]*/i))
282
+ action { [:NUMBER, st(text)] }
283
+
284
+ 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])*/i))
285
+ action { [:IDENT, st(text)] }
286
+
287
+ when (text = @ss.scan(/[\s]*,[\s]*/i))
288
+ action { [:COMMA, st(',')] }
289
+
290
+ when (text = @ss.scan(/[\s]*\)/i))
291
+ action { [:RPAREN, st(text)] }
292
+
293
+ when (text = @ss.scan(/[\s]*\(/i))
294
+ action { [:LPAREN, st(text)] }
295
+
296
+ when (text = @ss.scan(/\:/i))
297
+ action { [:COLON, st(text)] }
298
+
299
+ when (text = @ss.scan(/[\s]*!([\s]*|[\s]*\/\*(.|[\s]*)*?\*\/[\s]*)important[\s]*/i))
300
+ action { [:IMPORTANT_SYM, st(text)] }
301
+
302
+ when (text = @ss.scan(/[\s]*\{[\s]*/i))
303
+ action { @state = nil; [:LBRACE, st(text)] }
304
+
305
+ when (text = @ss.scan(/[\s]+/i))
306
+ action { [:S, st(text)] }
307
+
308
+ else
309
+ text = @ss.string[@ss.pos .. -1]
310
+ raise ScanError, "can not match: '" + text + "'"
311
+ end # if
312
+
237
313
  else
238
314
  raise ScanError, "undefined state: '" + state.to_s + "'"
239
315
  end # case state