csspool 4.0.0.pre → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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