csspool 2.0.0 → 3.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 (57) hide show
  1. data/.gemtest +0 -0
  2. data/CHANGELOG.rdoc +6 -0
  3. data/Manifest.txt +10 -14
  4. data/README.rdoc +8 -21
  5. data/Rakefile +36 -2
  6. data/lib/csspool/collection.rb +2 -2
  7. data/lib/csspool/css/charset.rb +8 -2
  8. data/lib/csspool/css/declaration.rb +13 -2
  9. data/lib/csspool/css/document_handler.rb +3 -3
  10. data/lib/csspool/css/import_rule.rb +15 -3
  11. data/lib/csspool/css/media.rb +8 -2
  12. data/lib/csspool/css/parser.rb +1090 -0
  13. data/lib/csspool/css/parser.y +347 -0
  14. data/lib/csspool/css/rule_set.rb +8 -3
  15. data/lib/csspool/css/tokenizer.rb +228 -0
  16. data/lib/csspool/css/tokenizer.rex +96 -0
  17. data/lib/csspool/css.rb +1 -0
  18. data/lib/csspool/node.rb +29 -1
  19. data/lib/csspool/sac/document.rb +3 -3
  20. data/lib/csspool/sac/parser.rb +6 -112
  21. data/lib/csspool/terms/function.rb +1 -1
  22. data/lib/csspool/terms/ident.rb +1 -1
  23. data/lib/csspool/terms/number.rb +1 -1
  24. data/lib/csspool/terms/rgb.rb +1 -4
  25. data/lib/csspool/terms/string.rb +1 -1
  26. data/lib/csspool/visitors/children.rb +50 -0
  27. data/lib/csspool/visitors/comparable.rb +9 -3
  28. data/lib/csspool/visitors/iterator.rb +80 -0
  29. data/lib/csspool/visitors/to_css.rb +61 -45
  30. data/lib/csspool/visitors.rb +2 -0
  31. data/lib/csspool.rb +6 -3
  32. data/test/css/test_parser.rb +412 -0
  33. data/test/css/test_tokenizer.rb +320 -0
  34. data/test/helper.rb +2 -2
  35. data/test/sac/test_parser.rb +3 -8
  36. data/test/sac/test_terms.rb +128 -34
  37. data/test/test_collection.rb +1 -1
  38. data/test/test_parser.rb +1 -1
  39. data/test/visitors/test_children.rb +20 -0
  40. data/test/visitors/test_comparable.rb +31 -1
  41. data/test/visitors/test_each.rb +19 -0
  42. data/test/visitors/test_to_css.rb +125 -1
  43. metadata +90 -68
  44. data/lib/csspool/lib_croco/cr_additional_sel.rb +0 -46
  45. data/lib/csspool/lib_croco/cr_attr_sel.rb +0 -16
  46. data/lib/csspool/lib_croco/cr_doc_handler.rb +0 -24
  47. data/lib/csspool/lib_croco/cr_num.rb +0 -13
  48. data/lib/csspool/lib_croco/cr_parser.rb +0 -11
  49. data/lib/csspool/lib_croco/cr_parsing_location.rb +0 -17
  50. data/lib/csspool/lib_croco/cr_pseudo.rb +0 -14
  51. data/lib/csspool/lib_croco/cr_rgb.rb +0 -18
  52. data/lib/csspool/lib_croco/cr_selector.rb +0 -34
  53. data/lib/csspool/lib_croco/cr_simple_sel.rb +0 -54
  54. data/lib/csspool/lib_croco/cr_term.rb +0 -97
  55. data/lib/csspool/lib_croco/glist.rb +0 -21
  56. data/lib/csspool/lib_croco.rb +0 -78
  57. data/lib/csspool/visitable.rb +0 -18
@@ -0,0 +1,347 @@
1
+ class CSSPool::CSS::Parser
2
+
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
7
+
8
+ rule
9
+ document
10
+ : { @handler.start_document }
11
+ stylesheet
12
+ { @handler.end_document }
13
+ ;
14
+ stylesheet
15
+ : charset stylesheet
16
+ | import stylesheet
17
+ | charset
18
+ | import
19
+ | body
20
+ ;
21
+ charset
22
+ : CHARSET_SYM STRING SEMI { @handler.charset interpret_string(val[1]), {} }
23
+ ;
24
+ import
25
+ : IMPORT_SYM import_location medium SEMI {
26
+ @handler.import_style [val[2]].flatten, val[1]
27
+ }
28
+ | IMPORT_SYM import_location SEMI {
29
+ @handler.import_style [], val[1]
30
+ }
31
+ ;
32
+ import_location
33
+ : import_location S
34
+ | STRING { result = Terms::String.new interpret_string val.first }
35
+ | URI { result = Terms::URI.new interpret_uri val.first }
36
+ ;
37
+ medium
38
+ : medium COMMA IDENT {
39
+ result = [val.first, Terms::Ident.new(interpret_identifier val.last)]
40
+ }
41
+ | IDENT {
42
+ result = Terms::Ident.new interpret_identifier val.first
43
+ }
44
+ ;
45
+ body
46
+ : ruleset body
47
+ | media body
48
+ | ruleset
49
+ | media
50
+ ;
51
+ media
52
+ : start_media body RBRACE { @handler.end_media val.first }
53
+ ;
54
+ start_media
55
+ : MEDIA_SYM medium LBRACE {
56
+ result = [val[1]].flatten
57
+ @handler.start_media result
58
+ }
59
+ | MEDIA_SYM LBRACE { result = [] }
60
+ ;
61
+ ruleset
62
+ : start_selector declarations RBRACE {
63
+ @handler.end_selector val.first
64
+ }
65
+ | start_selector RBRACE {
66
+ @handler.end_selector val.first
67
+ }
68
+ ;
69
+ start_selector
70
+ : S start_selector { result = val.last }
71
+ | selectors LBRACE {
72
+ @handler.start_selector val.first
73
+ }
74
+ ;
75
+ selectors
76
+ : selector COMMA selectors
77
+ {
78
+ # FIXME: should always garantee array
79
+ sel = Selector.new(val.first, {})
80
+ result = [sel, val[2]].flatten
81
+ }
82
+ | selector
83
+ {
84
+ result = [Selector.new(val.first, {})]
85
+ }
86
+ ;
87
+ selector
88
+ : simple_selector combinator selector
89
+ {
90
+ val = val.flatten
91
+ val[2].combinator = val.delete_at 1
92
+ result = val
93
+ }
94
+ | simple_selector
95
+ ;
96
+ combinator
97
+ : S { result = :s }
98
+ | GREATER { result = :> }
99
+ | PLUS { result = :+ }
100
+ ;
101
+ simple_selector
102
+ : element_name hcap {
103
+ selector = val.first
104
+ selector.additional_selectors = val.last
105
+ result = [selector]
106
+ }
107
+ | element_name { result = val }
108
+ | hcap
109
+ {
110
+ ss = Selectors::Simple.new nil, nil
111
+ ss.additional_selectors = val.flatten
112
+ result = [ss]
113
+ }
114
+ ;
115
+ element_name
116
+ : IDENT { result = Selectors::Type.new interpret_identifier val.first }
117
+ | STAR { result = Selectors::Universal.new val.first }
118
+ ;
119
+ hcap
120
+ : hash { result = val }
121
+ | class { result = val }
122
+ | attrib { result = val }
123
+ | pseudo { result = val }
124
+ | hash hcap { result = val.flatten }
125
+ | class hcap { result = val.flatten }
126
+ | attrib hcap { result = val.flatten }
127
+ | pseudo hcap { result = val.flatten }
128
+ ;
129
+ hash
130
+ : HASH {
131
+ result = Selectors::Id.new interpret_identifier val.first.sub(/^#/, '')
132
+ }
133
+ class
134
+ : '.' IDENT {
135
+ result = Selectors::Class.new interpret_identifier val.last
136
+ }
137
+ ;
138
+ attrib
139
+ : LSQUARE IDENT EQUAL IDENT RSQUARE {
140
+ result = Selectors::Attribute.new(
141
+ interpret_identifier(val[1]),
142
+ interpret_identifier(val[3]),
143
+ Selectors::Attribute::EQUALS
144
+ )
145
+ }
146
+ | LSQUARE IDENT EQUAL STRING RSQUARE {
147
+ result = Selectors::Attribute.new(
148
+ interpret_identifier(val[1]),
149
+ interpret_string(val[3]),
150
+ Selectors::Attribute::EQUALS
151
+ )
152
+ }
153
+ | LSQUARE IDENT INCLUDES STRING RSQUARE {
154
+ result = Selectors::Attribute.new(
155
+ interpret_identifier(val[1]),
156
+ interpret_string(val[3]),
157
+ Selectors::Attribute::INCLUDES
158
+ )
159
+ }
160
+ | LSQUARE IDENT INCLUDES IDENT RSQUARE {
161
+ result = Selectors::Attribute.new(
162
+ interpret_identifier(val[1]),
163
+ interpret_identifier(val[3]),
164
+ Selectors::Attribute::INCLUDES
165
+ )
166
+ }
167
+ | LSQUARE IDENT DASHMATCH IDENT RSQUARE {
168
+ result = Selectors::Attribute.new(
169
+ interpret_identifier(val[1]),
170
+ interpret_identifier(val[3]),
171
+ Selectors::Attribute::DASHMATCH
172
+ )
173
+ }
174
+ | LSQUARE IDENT DASHMATCH STRING RSQUARE {
175
+ result = Selectors::Attribute.new(
176
+ interpret_identifier(val[1]),
177
+ interpret_string(val[3]),
178
+ Selectors::Attribute::DASHMATCH
179
+ )
180
+ }
181
+ | LSQUARE IDENT RSQUARE {
182
+ result = Selectors::Attribute.new(
183
+ interpret_identifier(val[1]),
184
+ nil,
185
+ Selectors::Attribute::SET
186
+ )
187
+ }
188
+ ;
189
+ pseudo
190
+ : ':' IDENT {
191
+ result = Selectors::PseudoClass.new(
192
+ interpret_identifier(val[1])
193
+ )
194
+ }
195
+ | ':' FUNCTION RPAREN {
196
+ result = Selectors::PseudoClass.new(
197
+ interpret_identifier(val[1].sub(/\($/, '')),
198
+ ''
199
+ )
200
+ }
201
+ | ':' FUNCTION IDENT RPAREN {
202
+ result = Selectors::PseudoClass.new(
203
+ interpret_identifier(val[1].sub(/\($/, '')),
204
+ interpret_identifier(val[2])
205
+ )
206
+ }
207
+ ;
208
+ declarations
209
+ : declaration declarations
210
+ | declaration
211
+ ;
212
+ declaration
213
+ : property ':' expr prio SEMI
214
+ { @handler.property val.first, val[2], val[3] }
215
+ | property ':' S expr prio SEMI
216
+ { @handler.property val.first, val[3], val[4] }
217
+ ;
218
+ prio
219
+ : IMPORTANT_SYM { result = true }
220
+ | { result = false }
221
+ ;
222
+ property
223
+ : IDENT { result = interpret_identifier val[0] }
224
+ ;
225
+ operator
226
+ : COMMA
227
+ | SLASH
228
+ | EQUAL
229
+ ;
230
+ expr
231
+ : term operator expr {
232
+ result = [val.first, val.last].flatten
233
+ val.last.first.operator = val[1]
234
+ }
235
+ | term expr { result = val.flatten }
236
+ | term { result = val }
237
+ ;
238
+ term
239
+ : ident
240
+ | numeric
241
+ | string
242
+ | uri
243
+ | hexcolor
244
+ | function
245
+ ;
246
+ function
247
+ : function S { result = val.first }
248
+ | FUNCTION expr RPAREN {
249
+ name = interpret_identifier val.first.sub(/\($/, '')
250
+ if name == 'rgb'
251
+ result = Terms::Rgb.new(*val[1])
252
+ else
253
+ result = Terms::Function.new name, val[1]
254
+ end
255
+ }
256
+ ;
257
+ hexcolor
258
+ : hexcolor S { result = val.first }
259
+ | HASH { result = Terms::Hash.new val.first.sub(/^#/, '') }
260
+ ;
261
+ uri
262
+ : uri S { result = val.first }
263
+ | URI { result = Terms::URI.new interpret_uri val.first }
264
+ string
265
+ : string S { result = val.first }
266
+ | STRING { result = Terms::String.new interpret_string val.first }
267
+ ;
268
+ numeric
269
+ : unary_operator numeric {
270
+ result = val[1]
271
+ val[1].unary_operator = val.first
272
+ }
273
+ | NUMBER {
274
+ result = Terms::Number.new numeric val.first
275
+ }
276
+ | PERCENTAGE {
277
+ result = Terms::Number.new numeric(val.first), nil, '%'
278
+ }
279
+ | LENGTH {
280
+ unit = val.first.gsub(/[\s\d.]/, '')
281
+ result = Terms::Number.new numeric(val.first), nil, unit
282
+ }
283
+ | EMS {
284
+ result = Terms::Number.new numeric(val.first), nil, 'em'
285
+ }
286
+ | EXS {
287
+ result = Terms::Number.new numeric(val.first), nil, 'ex'
288
+ }
289
+ | ANGLE {
290
+ unit = val.first.gsub(/[\s\d.]/, '')
291
+ result = Terms::Number.new numeric(val.first), nil, unit
292
+ }
293
+ | TIME {
294
+ unit = val.first.gsub(/[\s\d.]/, '')
295
+ result = Terms::Number.new numeric(val.first), nil, unit
296
+ }
297
+ | FREQ {
298
+ unit = val.first.gsub(/[\s\d.]/, '')
299
+ result = Terms::Number.new numeric(val.first), nil, unit
300
+ }
301
+ ;
302
+ unary_operator
303
+ : MINUS { result = :minus }
304
+ | PLUS { result = :plus }
305
+ ;
306
+ ident
307
+ : ident S { result = val.first }
308
+ | IDENT { result = Terms::Ident.new interpret_identifier val.first }
309
+ ;
310
+
311
+ ---- inner
312
+
313
+ def numeric thing
314
+ thing = thing.gsub(/[^\d.]/, '')
315
+ Integer(thing) rescue Float(thing)
316
+ end
317
+
318
+ def interpret_identifier s
319
+ interpret_escapes s
320
+ end
321
+
322
+ def interpret_uri s
323
+ interpret_escapes s.match(/^url\((.*)\)$/mu)[1].strip.match(/^(['"]?)((?:\\.|.)*)\1$/mu)[2]
324
+ end
325
+
326
+ def interpret_string s
327
+ interpret_escapes s.match(/^(['"])((?:\\.|.)*)\1$/mu)[2]
328
+ end
329
+
330
+ def interpret_escapes s
331
+ token_exp = /\\([0-9a-fA-F]{1,6}(?:\r\n|\s)?)|\\(.)|(.)/mu
332
+ characters = s.scan(token_exp).map do |u_escape, i_escape, ident|
333
+ if u_escape
334
+ code = u_escape.chomp.to_i 16
335
+ code = 0xFFFD if code > 0x10FFFF
336
+ [code].pack('U')
337
+ elsif i_escape
338
+ if i_escape == "\n"
339
+ ''
340
+ else
341
+ i_escape
342
+ end
343
+ else
344
+ ident
345
+ end
346
+ end.join ''
347
+ end
@@ -1,11 +1,16 @@
1
1
  module CSSPool
2
2
  module CSS
3
- class RuleSet < Struct.new(:selectors, :declarations, :media)
4
- include CSSPool::Visitable
3
+ class RuleSet < CSSPool::Node
4
+ attr_accessor :selectors
5
+ attr_accessor :declarations
6
+ attr_accessor :media
5
7
 
6
8
  def initialize selectors, declarations = [], media = []
9
+ @selectors = selectors
10
+ @declarations = declarations
11
+ @media = media
12
+
7
13
  selectors.each { |sel| sel.rule_set = self }
8
- super
9
14
  end
10
15
  end
11
16
  end
@@ -0,0 +1,228 @@
1
+ #--
2
+ # DO NOT MODIFY!!!!
3
+ # This file is automatically generated by rex 1.0.5
4
+ # from lexical definition file "lib/csspool/css/tokenizer.rex".
5
+ #++
6
+
7
+ module CSSPool
8
+ module CSS
9
+ class Tokenizer < Parser
10
+ require 'strscan'
11
+
12
+ class ScanError < StandardError ; end
13
+
14
+ attr_reader :lineno
15
+ attr_reader :filename
16
+ attr_accessor :state
17
+
18
+ def scan_setup(str)
19
+ @ss = StringScanner.new(str)
20
+ @lineno = 1
21
+ @state = nil
22
+ end
23
+
24
+ def action
25
+ yield
26
+ end
27
+
28
+ def scan_str(str)
29
+ scan_setup(str)
30
+ do_parse
31
+ end
32
+ alias :scan :scan_str
33
+
34
+ def load_file( filename )
35
+ @filename = filename
36
+ open(filename, "r") do |f|
37
+ scan_setup(f.read)
38
+ end
39
+ end
40
+
41
+ def scan_file( filename )
42
+ load_file(filename)
43
+ do_parse
44
+ end
45
+
46
+
47
+ def next_token
48
+ return if @ss.eos?
49
+
50
+ # skips empty actions
51
+ until token = _next_token or @ss.eos?; end
52
+ token
53
+ end
54
+
55
+ def _next_token
56
+ text = @ss.peek(1)
57
+ @lineno += 1 if text == "\n"
58
+ token = case @state
59
+ when nil
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))
62
+ action { [:URI, st(text)] }
63
+
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))
65
+ action { [:URI, st(text)] }
66
+
67
+ when (text = @ss.scan(/U\+[0-9a-fA-F?]{1,6}(-[0-9a-fA-F]{1,6})?/i))
68
+ action {[:UNICODE_RANGE, st(text)] }
69
+
70
+ when (text = @ss.scan(/[\s]*\/\*(.|[\s]*)*?\*\/[\s]*/i))
71
+ action { next_token }
72
+
73
+ 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
+ action { [:FUNCTION, st(text)] }
75
+
76
+ when (text = @ss.scan(/[\s]*@import[\s]*/i))
77
+ action { [:IMPORT_SYM, st(text)] }
78
+
79
+ when (text = @ss.scan(/[\s]*@page[\s]*/i))
80
+ action { [:PAGE_SYM, st(text)] }
81
+
82
+ when (text = @ss.scan(/[\s]*@charset[\s]*/i))
83
+ action { [:CHARSET_SYM, st(text)] }
84
+
85
+ when (text = @ss.scan(/[\s]*@media[\s]*/i))
86
+ action { [:MEDIA_SYM, st(text)] }
87
+
88
+ when (text = @ss.scan(/[\s]*!([\s]*|[\s]*\/\*(.|[\s]*)*?\*\/[\s]*)important[\s]*/i))
89
+ action { [:IMPORTANT_SYM, st(text)] }
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])*/i))
92
+ action { [:IDENT, st(text)] }
93
+
94
+ 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))
95
+ action { [:HASH, st(text)] }
96
+
97
+ when (text = @ss.scan(/[\s]*~=[\s]*/i))
98
+ action { [:INCLUDES, st(text)] }
99
+
100
+ when (text = @ss.scan(/[\s]*\|=[\s]*/i))
101
+ action { [:DASHMATCH, st(text)] }
102
+
103
+ when (text = @ss.scan(/[\s]*\^=[\s]*/i))
104
+ action { [:PREFIXMATCH, st(text)] }
105
+
106
+ when (text = @ss.scan(/[\s]*\$=[\s]*/i))
107
+ action { [:SUFFIXMATCH, st(text)] }
108
+
109
+ when (text = @ss.scan(/[\s]*\*=[\s]*/i))
110
+ action { [:SUBSTRINGMATCH, st(text)] }
111
+
112
+ when (text = @ss.scan(/[\s]*!=[\s]*/i))
113
+ action { [:NOT_EQUAL, st(text)] }
114
+
115
+ when (text = @ss.scan(/[\s]*=[\s]*/i))
116
+ action { [:EQUAL, st(text)] }
117
+
118
+ when (text = @ss.scan(/[\s]*\)/i))
119
+ action { [:RPAREN, st(text)] }
120
+
121
+ when (text = @ss.scan(/[\s]*\[[\s]*/i))
122
+ action { [:LSQUARE, st(text)] }
123
+
124
+ when (text = @ss.scan(/[\s]*\]/i))
125
+ action { [:RSQUARE, st(text)] }
126
+
127
+ when (text = @ss.scan(/[\s]*\+[\s]*/i))
128
+ action { [:PLUS, st(text)] }
129
+
130
+ when (text = @ss.scan(/[\s]*\{[\s]*/i))
131
+ action { [:LBRACE, st(text)] }
132
+
133
+ when (text = @ss.scan(/[\s]*\}[\s]*/i))
134
+ action { [:RBRACE, st(text)] }
135
+
136
+ when (text = @ss.scan(/[\s]*>[\s]*/i))
137
+ action { [:GREATER, st(text)] }
138
+
139
+ when (text = @ss.scan(/[\s]*,[\s]*/i))
140
+ action { [:COMMA, st(',')] }
141
+
142
+ when (text = @ss.scan(/[\s]*;[\s]*/i))
143
+ action { [:SEMI, st(';')] }
144
+
145
+ when (text = @ss.scan(/\*/i))
146
+ action { [:STAR, st(text)] }
147
+
148
+ when (text = @ss.scan(/[\s]*~[\s]*/i))
149
+ action { [:TILDE, st(text)] }
150
+
151
+ when (text = @ss.scan(/\:not\([\s]*/i))
152
+ action { [:NOT, st(text)] }
153
+
154
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)em[\s]*/i))
155
+ action { [:EMS, st(text)] }
156
+
157
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)ex[\s]*/i))
158
+ action { [:EXS, st(text)] }
159
+
160
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)(px|cm|mm|in|pt|pc)[\s]*/i))
161
+ action { [:LENGTH, st(text)] }
162
+
163
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)(deg|rad|grad)[\s]*/i))
164
+ action { [:ANGLE, st(text)] }
165
+
166
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)(ms|s)[\s]*/i))
167
+ action { [:TIME, st(text)] }
168
+
169
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)[k]?hz[\s]*/i))
170
+ action { [:FREQ, st(text)] }
171
+
172
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)%[\s]*/i))
173
+ action { [:PERCENTAGE, st(text)] }
174
+
175
+ when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)[\s]*/i))
176
+ action { [:NUMBER, st(text)] }
177
+
178
+ when (text = @ss.scan(/[\s]*\/\/[\s]*/i))
179
+ action { [:DOUBLESLASH, st(text)] }
180
+
181
+ when (text = @ss.scan(/[\s]*\/[\s]*/i))
182
+ action { [:SLASH, st('/')] }
183
+
184
+ when (text = @ss.scan(/<!--/i))
185
+ action { [:CDO, st(text)] }
186
+
187
+ when (text = @ss.scan(/-->/i))
188
+ action { [:CDC, st(text)] }
189
+
190
+ 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))
191
+ action { [:MINUS, st(text)] }
192
+
193
+ when (text = @ss.scan(/[\s]*\+[\s]*/i))
194
+ action { [:PLUS, st(text)] }
195
+
196
+ when (text = @ss.scan(/[\s]+/i))
197
+ action { [:S, st(text)] }
198
+
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))
200
+ action { [:STRING, st(text)] }
201
+
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))
203
+ action { [:INVALID, st(text)] }
204
+
205
+ when (text = @ss.scan(/./i))
206
+ action { [st(text), st(text)] }
207
+
208
+ else
209
+ text = @ss.string[@ss.pos .. -1]
210
+ raise ScanError, "can not match: '" + text + "'"
211
+ end # if
212
+
213
+ else
214
+ raise ScanError, "undefined state: '" + state.to_s + "'"
215
+ end # case state
216
+ token
217
+ end # def _next_token
218
+
219
+ def st o
220
+ @st ||= Hash.new { |h,k| h[k] = k }
221
+ @st[o]
222
+ end # class
223
+
224
+ end
225
+ end
226
+ end
227
+
228
+ # vim: syntax=lex
@@ -0,0 +1,96 @@
1
+ module CSSPool
2
+ module CSS
3
+ class Tokenizer < Parser
4
+
5
+ macro
6
+ nl \n|\r\n|\r|\f
7
+ w [\s]*
8
+ nonascii [^\0-\177]
9
+ num ([0-9]*\.[0-9]+|[0-9]+)
10
+ unicode \\[0-9A-Fa-f]{1,6}(\r\n|[\s])?
11
+
12
+ escape {unicode}|\\[^\n\r\f0-9A-Fa-f]
13
+ nmchar [_A-Za-z0-9-]|{nonascii}|{escape}
14
+ nmstart [_A-Za-z]|{nonascii}|{escape}
15
+ ident [-@]?({nmstart})({nmchar})*
16
+ func [-@]?({nmstart})({nmchar}|[.])*
17
+ name ({nmchar})+
18
+ string1 "([^\n\r\f\\"]|\\{nl}|{nonascii}|{escape})*"
19
+ string2 '([^\n\r\f\\']|\\{nl}|{nonascii}|{escape})*'
20
+ string ({string1}|{string2})
21
+ invalid1 "([^\n\r\f\\"]|\\{nl}|{nonascii}|{escape})*
22
+ invalid2 '([^\n\r\f\\']|\\{nl}|{nonascii}|{escape})*
23
+ invalid ({invalid1}|{invalid2})
24
+ comment \/\*(.|{w})*?\*\/
25
+
26
+ rule
27
+
28
+ # [:state] pattern [actions]
29
+
30
+ url\({w}{string}{w}\) { [:URI, st(text)] }
31
+ url\({w}([!#\$%&*-~]|{nonascii}|{escape})*{w}\) { [:URI, st(text)] }
32
+ U\+[0-9a-fA-F?]{1,6}(-[0-9a-fA-F]{1,6})? {[:UNICODE_RANGE, st(text)] }
33
+ {w}{comment}{w} { next_token }
34
+
35
+ {func}\(\s* { [:FUNCTION, st(text)] }
36
+ {w}@import{w} { [:IMPORT_SYM, st(text)] }
37
+ {w}@page{w} { [:PAGE_SYM, st(text)] }
38
+ {w}@charset{w} { [:CHARSET_SYM, st(text)] }
39
+ {w}@media{w} { [:MEDIA_SYM, st(text)] }
40
+ {w}!({w}|{w}{comment}{w})important{w} { [:IMPORTANT_SYM, st(text)] }
41
+ {ident} { [:IDENT, st(text)] }
42
+ \#{name} { [:HASH, st(text)] }
43
+ {w}~={w} { [:INCLUDES, st(text)] }
44
+ {w}\|={w} { [:DASHMATCH, st(text)] }
45
+ {w}\^={w} { [:PREFIXMATCH, st(text)] }
46
+ {w}\$={w} { [:SUFFIXMATCH, st(text)] }
47
+ {w}\*={w} { [:SUBSTRINGMATCH, st(text)] }
48
+ {w}!={w} { [:NOT_EQUAL, st(text)] }
49
+ {w}={w} { [:EQUAL, st(text)] }
50
+ {w}\) { [:RPAREN, st(text)] }
51
+ {w}\[{w} { [:LSQUARE, st(text)] }
52
+ {w}\] { [:RSQUARE, st(text)] }
53
+ {w}\+{w} { [:PLUS, st(text)] }
54
+ {w}\{{w} { [:LBRACE, st(text)] }
55
+ {w}\}{w} { [:RBRACE, st(text)] }
56
+ {w}>{w} { [:GREATER, st(text)] }
57
+ {w},{w} { [:COMMA, st(',')] }
58
+ {w};{w} { [:SEMI, st(';')] }
59
+ \* { [:STAR, st(text)] }
60
+ {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)] }
64
+
65
+ {w}{num}(px|cm|mm|in|pt|pc){w} { [:LENGTH, st(text)] }
66
+ {w}{num}(deg|rad|grad){w} { [:ANGLE, st(text)] }
67
+ {w}{num}(ms|s){w} { [:TIME, st(text)] }
68
+ {w}{num}[k]?hz{w} { [:FREQ, st(text)] }
69
+
70
+ {w}{num}%{w} { [:PERCENTAGE, st(text)] }
71
+ {w}{num}{w} { [:NUMBER, st(text)] }
72
+ {w}\/\/{w} { [:DOUBLESLASH, st(text)] }
73
+ {w}\/{w} { [:SLASH, st('/')] }
74
+ <!-- { [:CDO, st(text)] }
75
+ --> { [:CDC, st(text)] }
76
+ {w}\-(?!{ident}){w} { [:MINUS, st(text)] }
77
+ {w}\+{w} { [:PLUS, st(text)] }
78
+
79
+
80
+ [\s]+ { [:S, st(text)] }
81
+ {string} { [:STRING, st(text)] }
82
+ {invalid} { [:INVALID, st(text)] }
83
+ . { [st(text), st(text)] }
84
+
85
+ inner
86
+
87
+ def st o
88
+ @st ||= Hash.new { |h,k| h[k] = k }
89
+ @st[o]
90
+ end
91
+
92
+ end
93
+ end
94
+ end
95
+
96
+ # vim: syntax=lex
data/lib/csspool/css.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'csspool/css/tokenizer'
1
2
  require 'csspool/css/charset'
2
3
  require 'csspool/css/import_rule'
3
4
  require 'csspool/css/media'