marcosinger-css_parser 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,75 @@
1
+ module CssParser
2
+ # :stopdoc:
3
+ # Base types
4
+ RE_NL = Regexp.new('(\n|\r\n|\r|\f)')
5
+ RE_NON_ASCII = Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE, 'n') #[^\0-\177]
6
+ RE_UNICODE = Regexp.new('(\\\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])*)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE, 'n')
7
+ RE_ESCAPE = Regexp.union(RE_UNICODE, '|(\\\\[^\n\r\f0-9a-f])')
8
+ RE_IDENT = Regexp.new("[\-]?([_a-z]|#{RE_NON_ASCII}|#{RE_ESCAPE})([_a-z0-9\-]|#{RE_NON_ASCII}|#{RE_ESCAPE})*", Regexp::IGNORECASE, 'n')
9
+
10
+ # General strings
11
+ RE_STRING1 = Regexp.new('(\"(.[^\n\r\f\\"]*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\")')
12
+ RE_STRING2 = Regexp.new('(\'(.[^\n\r\f\\\']*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\')')
13
+ RE_STRING = Regexp.union(RE_STRING1, RE_STRING2)
14
+
15
+ RE_URI = Regexp.new('(url\([\s]*([\s]*' + RE_STRING.to_s + '[\s]*)[\s]*\))|(url\([\s]*([!#$%&*\-~]|' + RE_NON_ASCII.to_s + '|' + RE_ESCAPE.to_s + ')*[\s]*)\)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE, 'n')
16
+ URI_RX = /url\(("([^"]*)"|'([^']*)'|([^)]*))\)/im
17
+
18
+ # Initial parsing
19
+ RE_AT_IMPORT_RULE = /\@import[\s]+(url\()?["''"]?(.[^'"\s"']*)["''"]?\)?([\w\s\,^\])]*)\)?;?/
20
+
21
+ #--
22
+ #RE_AT_MEDIA_RULE = Regexp.new('(\"(.[^\n\r\f\\"]*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\")')
23
+
24
+ #RE_AT_IMPORT_RULE = Regexp.new('@import[\s]*(' + RE_STRING.to_s + ')([\w\s\,]*)[;]?', Regexp::IGNORECASE) -- should handle url() even though it is not allowed
25
+ #++
26
+ IMPORTANT_IN_PROPERTY_RX = /[\s]*\!important[\s]*/i
27
+ STRIP_CSS_COMMENTS_RX = /\/\*.*?\*\//m
28
+ STRIP_HTML_COMMENTS_RX = /\<\!\-\-|\-\-\>/m
29
+
30
+ # Special units
31
+ BOX_MODEL_UNITS_RX = /(auto|inherit|0|([\-]*([0-9]+|[0-9]*\.[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%)))([\s;]|\Z)/imx
32
+ RE_LENGTH_OR_PERCENTAGE = Regexp.new('([\-]*(([0-9]*\.[0-9]+)|[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%))', Regexp::IGNORECASE)
33
+ RE_BACKGROUND_POSITION = Regexp.new("((((#{RE_LENGTH_OR_PERCENTAGE})|left|center|right|top|bottom)[\s]*){1,2})", Regexp::IGNORECASE | Regexp::EXTENDED)
34
+ FONT_UNITS_RX = /(([x]+\-)*small|medium|large[r]*|auto|inherit|([0-9]+|[0-9]*\.[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%)*)/i
35
+ RE_BORDER_STYLE = /([\s]*^)?(none|hidden|dotted|dashed|solid|double|dot-dash|dot-dot-dash|wave|groove|ridge|inset|outset)([\s]*$)?/imx
36
+ RE_BORDER_UNITS = Regexp.union(BOX_MODEL_UNITS_RX, /(thin|medium|thick)/i)
37
+
38
+
39
+ # Patterns for specificity calculations
40
+ NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX= /
41
+ (\.[\w]+) # classes
42
+ |
43
+ \[(\w+) # attributes
44
+ |
45
+ (\:( # pseudo classes
46
+ link|visited|active
47
+ |hover|focus
48
+ |lang
49
+ |target
50
+ |enabled|disabled|checked|indeterminate
51
+ |root
52
+ |nth-child|nth-last-child|nth-of-type|nth-last-of-type
53
+ |first-child|last-child|first-of-type|last-of-type
54
+ |only-child|only-of-type
55
+ |empty|contains
56
+ ))
57
+ /ix
58
+ ELEMENTS_AND_PSEUDO_ELEMENTS_RX = /
59
+ ((^|[\s\+\>\~]+)[\w]+ # elements
60
+ |
61
+ \:{1,2}( # pseudo-elements
62
+ after|before
63
+ |first-letter|first-line
64
+ |selection
65
+ )
66
+ )/ix
67
+
68
+ # Colours
69
+ RE_COLOUR_NUMERIC = Regexp.new('((hsl|rgb)[\s]*\([\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*\))', Regexp::IGNORECASE)
70
+ RE_COLOUR_NUMERIC_ALPHA = Regexp.new('((hsla|rgba)[\s]*\([\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*\))', Regexp::IGNORECASE)
71
+ RE_COLOUR_HEX = /(#([0-9a-f]{6}|[0-9a-f]{3})([\s;]|$))/i
72
+ RE_COLOUR_NAMED = /([\s]*^)?(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|transparent)([\s]*$)?/i
73
+ RE_COLOUR = Regexp.union(RE_COLOUR_NUMERIC, RE_COLOUR_NUMERIC_ALPHA, RE_COLOUR_HEX, RE_COLOUR_NAMED)
74
+ # :startdoc:
75
+ end
@@ -0,0 +1,451 @@
1
+ module CssParser
2
+ class RuleSet
3
+ # Patterns for specificity calculations
4
+ RE_ELEMENTS_AND_PSEUDO_ELEMENTS = /((^|[\s\+\>]+)[\w]+|\:(first\-line|first\-letter|before|after))/i
5
+ RE_NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES = /(\.[\w]+)|(\[[\w]+)|(\:(link|first\-child|lang))/i
6
+
7
+ # Array of selector strings.
8
+ attr_reader :selectors
9
+
10
+ # Integer with the specificity to use for this RuleSet.
11
+ attr_accessor :specificity
12
+
13
+ def initialize(selectors, block, specificity = nil)
14
+ @selectors = []
15
+ @specificity = specificity
16
+ @declarations = {}
17
+ @order = 0
18
+ parse_selectors!(selectors) if selectors
19
+ parse_declarations!(block)
20
+ end
21
+
22
+
23
+ # Get the value of a property
24
+ def get_value(property)
25
+ return '' unless property and not property.empty?
26
+
27
+ property = property.downcase.strip
28
+ properties = @declarations.inject('') do |val, (key, data)|
29
+ #puts "COMPARING #{key} #{key.inspect} against #{property} #{property.inspect}"
30
+ importance = data[:is_important] ? ' !important' : ''
31
+ val << "#{data[:value]}#{importance}; " if key.downcase.strip == property
32
+ val
33
+ end
34
+ return properties ? properties.strip : ''
35
+ end
36
+ alias_method :[], :get_value
37
+
38
+ # Add a CSS declaration to the current RuleSet.
39
+ #
40
+ # rule_set.add_declaration!('color', 'blue')
41
+ #
42
+ # puts rule_set['color']
43
+ # => 'blue;'
44
+ #
45
+ # rule_set.add_declaration!('margin', '0px auto !important')
46
+ #
47
+ # puts rule_set['margin']
48
+ # => '0px auto !important;'
49
+ #
50
+ # If the property already exists its value will be over-written.
51
+ def add_declaration!(property, value)
52
+ if value.nil? or value.empty?
53
+ @declarations.delete(property)
54
+ return
55
+ end
56
+
57
+ value.gsub!(/;\Z/, '')
58
+ is_important = !value.gsub!(CssParser::IMPORTANT_IN_PROPERTY_RX, '').nil?
59
+ property = property.downcase.strip
60
+ #puts "SAVING #{property} #{value} #{is_important.inspect}"
61
+ @declarations[property] = {
62
+ :value => value, :is_important => is_important, :order => @order += 1
63
+ }
64
+ end
65
+ alias_method :[]=, :add_declaration!
66
+
67
+ # Remove CSS declaration from the current RuleSet.
68
+ #
69
+ # rule_set.remove_declaration!('color')
70
+ def remove_declaration!(property)
71
+ @declarations.delete(property)
72
+ end
73
+
74
+ # Iterate through selectors.
75
+ #
76
+ # Options
77
+ # - +force_important+ -- boolean
78
+ #
79
+ # ==== Example
80
+ # ruleset.each_selector do |sel, dec, spec|
81
+ # ...
82
+ # end
83
+ def each_selector(options = {}) # :yields: selector, declarations, specificity
84
+ declarations = declarations_to_s(options)
85
+ if @specificity
86
+ @selectors.each { |sel| yield sel.strip, declarations, @specificity }
87
+ else
88
+ @selectors.each { |sel| yield sel.strip, declarations, CssParser.calculate_specificity(sel) }
89
+ end
90
+ end
91
+
92
+ # Iterate through declarations.
93
+ def each_declaration # :yields: property, value, is_important
94
+ decs = @declarations.sort { |a,b| a[1][:order].nil? || b[1][:order].nil? ? 0 : a[1][:order] <=> b[1][:order] }
95
+ decs.each do |property, data|
96
+ value = data[:value]
97
+ yield property.downcase.strip, value.strip, data[:is_important]
98
+ end
99
+ end
100
+
101
+ # Return all declarations as a string.
102
+ #--
103
+ # TODO: Clean-up regexp doesn't seem to work
104
+ #++
105
+ def declarations_to_s(options = {})
106
+ options = {:force_important => false}.merge(options)
107
+ str = ''
108
+ each_declaration do |prop, val, is_important|
109
+ importance = (options[:force_important] || is_important) ? ' !important' : ''
110
+ str += "#{prop}: #{val}#{importance}; "
111
+ end
112
+ str.gsub(/^[\s^(\{)]+|[\n\r\f\t]*|[\s]+$/mx, '').strip
113
+ end
114
+
115
+ # Return the CSS rule set as a string.
116
+ def to_s
117
+ decs = declarations_to_s
118
+ "#{@selectors.join} { #{decs} }"
119
+ end
120
+
121
+ # Split shorthand declarations (e.g. +margin+ or +font+) into their constituent parts.
122
+ def expand_shorthand!
123
+ # border must be expanded before dimensions
124
+ expand_border_shorthand!
125
+ expand_dimensions_shorthand!
126
+ expand_font_shorthand!
127
+ expand_background_shorthand!
128
+ end
129
+
130
+ # Create shorthand declarations (e.g. +margin+ or +font+) whenever possible.
131
+ def create_shorthand!
132
+ create_background_shorthand!
133
+ create_dimensions_shorthand!
134
+ # border must be shortened after dimensions
135
+ create_border_shorthand!
136
+ create_font_shorthand!
137
+ end
138
+
139
+ # Split shorthand border declarations (e.g. <tt>border: 1px red;</tt>)
140
+ # Additional splitting happens in expand_dimensions_shorthand!
141
+ def expand_border_shorthand! # :nodoc:
142
+ ['border', 'border-left', 'border-right', 'border-top', 'border-bottom'].each do |k|
143
+ next unless @declarations.has_key?(k)
144
+
145
+ value = @declarations[k][:value]
146
+
147
+ split_declaration(k, "#{k}-width", value.slice!(CssParser::RE_BORDER_UNITS))
148
+ split_declaration(k, "#{k}-color", value.slice!(CssParser::RE_COLOUR))
149
+ split_declaration(k, "#{k}-style", value.slice!(CssParser::RE_BORDER_STYLE))
150
+
151
+ @declarations.delete(k)
152
+ end
153
+ end
154
+
155
+ # Split shorthand dimensional declarations (e.g. <tt>margin: 0px auto;</tt>)
156
+ # into their constituent parts. Handles margin, padding, border-color, border-style and border-width.
157
+ def expand_dimensions_shorthand! # :nodoc:
158
+ {'margin' => 'margin-%s',
159
+ 'padding' => 'padding-%s',
160
+ 'border-color' => 'border-%s-color',
161
+ 'border-style' => 'border-%s-style',
162
+ 'border-width' => 'border-%s-width'}.each do |property, expanded|
163
+
164
+ next unless @declarations.has_key?(property)
165
+
166
+ value = @declarations[property][:value]
167
+
168
+ # RGB and HSL values in borders are the only units that can have spaces (within params).
169
+ # We cheat a bit here by stripping spaces after commas in RGB and HSL values so that we
170
+ # can split easily on spaces.
171
+ #
172
+ # TODO: rgba, hsl, hsla
173
+ value.gsub!(RE_COLOUR) { |c| c.gsub(/[\s]+/, '') }
174
+
175
+ matches = value.strip.split(/[\s]+/)
176
+
177
+ t, r, b, l = nil
178
+
179
+ case matches.length
180
+ when 1
181
+ t, r, b, l = matches[0], matches[0], matches[0], matches[0]
182
+ when 2
183
+ t, b = matches[0], matches[0]
184
+ r, l = matches[1], matches[1]
185
+ when 3
186
+ t = matches[0]
187
+ r, l = matches[1], matches[1]
188
+ b = matches[2]
189
+ when 4
190
+ t = matches[0]
191
+ r = matches[1]
192
+ b = matches[2]
193
+ l = matches[3]
194
+ end
195
+
196
+ values = @declarations[property]
197
+
198
+ split_declaration(property, expanded % 'top', t)
199
+ split_declaration(property, expanded % 'right', r)
200
+ split_declaration(property, expanded % 'bottom', b)
201
+ split_declaration(property, expanded % 'left', l)
202
+
203
+ @declarations.delete(property)
204
+ end
205
+ end
206
+
207
+ # Convert shorthand font declarations (e.g. <tt>font: 300 italic 11px/14px verdana, helvetica, sans-serif;</tt>)
208
+ # into their constituent parts.
209
+ def expand_font_shorthand! # :nodoc:
210
+ return unless @declarations.has_key?('font')
211
+
212
+ font_props = {}
213
+
214
+ # reset properties to 'normal' per http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
215
+ ['font-style', 'font-variant', 'font-weight', 'font-size',
216
+ 'line-height'].each do |prop|
217
+ font_props[prop] = 'normal'
218
+ end
219
+
220
+ value = @declarations['font'][:value]
221
+ is_important = @declarations['font'][:is_important]
222
+ order = @declarations['font'][:order]
223
+
224
+ in_fonts = false
225
+
226
+ matches = value.scan(/("(.*[^"])"|'(.*[^'])'|(\w[^ ,]+))/)
227
+ matches.each do |match|
228
+ m = match[0].to_s.strip
229
+ m.gsub!(/[;]$/, '')
230
+
231
+ if in_fonts
232
+ if font_props.has_key?('font-family')
233
+ font_props['font-family'] += ', ' + m
234
+ else
235
+ font_props['font-family'] = m
236
+ end
237
+ elsif m =~ /normal|inherit/i
238
+ ['font-style', 'font-weight', 'font-variant'].each do |font_prop|
239
+ font_props[font_prop] = m unless font_props.has_key?(font_prop)
240
+ end
241
+ elsif m =~ /italic|oblique/i
242
+ font_props['font-style'] = m
243
+ elsif m =~ /small\-caps/i
244
+ font_props['font-variant'] = m
245
+ elsif m =~ /[1-9]00$|bold|bolder|lighter/i
246
+ font_props['font-weight'] = m
247
+ elsif m =~ CssParser::FONT_UNITS_RX
248
+ if m =~ /\//
249
+ font_props['font-size'], font_props['line-height'] = m.split('/')
250
+ else
251
+ font_props['font-size'] = m
252
+ end
253
+ in_fonts = true
254
+ end
255
+ end
256
+
257
+ font_props.each { |font_prop, font_val| @declarations[font_prop] = {:value => font_val, :is_important => is_important, :order => order} }
258
+
259
+ @declarations.delete('font')
260
+ end
261
+
262
+ # Convert shorthand background declarations (e.g. <tt>background: url("chess.png") gray 50% repeat fixed;</tt>)
263
+ # into their constituent parts.
264
+ #
265
+ # See http://www.w3.org/TR/CSS21/colors.html#propdef-background
266
+ def expand_background_shorthand! # :nodoc:
267
+ return unless @declarations.has_key?('background')
268
+
269
+ value = @declarations['background'][:value]
270
+
271
+ if value =~ /([\s]*^)?inherit([\s]*$)?/i
272
+ ['background-color', 'background-image', 'background-attachment', 'background-repeat', 'background-position'].each do |prop|
273
+ split_declaration('background', prop, 'inherit')
274
+ end
275
+ end
276
+
277
+ split_declaration('background', 'background-image', value.slice!(Regexp.union(CssParser::URI_RX, /none/i)))
278
+ split_declaration('background', 'background-attachment', value.slice!(/([\s]*^)?(scroll|fixed)([\s]*$)?/i))
279
+ split_declaration('background', 'background-repeat', value.slice!(/([\s]*^)?(repeat(\-x|\-y)*|no\-repeat)([\s]*$)?/i))
280
+ split_declaration('background', 'background-color', value.slice!(CssParser::RE_COLOUR))
281
+ split_declaration('background', 'background-position', value.slice(CssParser::RE_BACKGROUND_POSITION))
282
+
283
+ @declarations.delete('background')
284
+ end
285
+
286
+ # Looks for long format CSS background properties (e.g. <tt>background-color</tt>) and
287
+ # converts them into a shorthand CSS <tt>background</tt> property.
288
+ #
289
+ # Leaves properties declared !important alone.
290
+ def create_background_shorthand! # :nodoc:
291
+ new_value = ''
292
+ ['background-color', 'background-image', 'background-repeat',
293
+ 'background-position', 'background-attachment'].each do |property|
294
+ if @declarations.has_key?(property) and not @declarations[property][:is_important]
295
+ new_value += @declarations[property][:value] + ' '
296
+ @declarations.delete(property)
297
+ end
298
+ end
299
+
300
+ unless new_value.strip.empty?
301
+ @declarations['background'] = {:value => new_value.gsub(/[\s]+/, ' ').strip}
302
+ end
303
+ end
304
+
305
+ # Combine border-color, border-style and border-width into border
306
+ # Should be run after create_dimensions_shorthand!
307
+ #
308
+ # TODO: this is extremely similar to create_background_shorthand! and should be combined
309
+ def create_border_shorthand! # :nodoc:
310
+ new_value = ''
311
+
312
+ ['border-width', 'border-style', 'border-color'].each do |property|
313
+ if @declarations.has_key?(property) and not @declarations[property][:is_important]
314
+
315
+ # can't merge if any value contains a space (i.e. has multiple values)
316
+ return if @declarations[property][:value].strip =~ /[\s]/
317
+ new_value += @declarations[property][:value] + ' '
318
+ end
319
+ end
320
+
321
+ @declarations.delete('border-width')
322
+ @declarations.delete('border-style')
323
+ @declarations.delete('border-color')
324
+
325
+ unless new_value.strip.empty?
326
+ @declarations['border'] = {:value => new_value.gsub(/[\s]+/, ' ').strip}
327
+ end
328
+ end
329
+
330
+ # Looks for long format CSS dimensional properties (margin, padding, border-color, border-style and border-width)
331
+ # and converts them into shorthand CSS properties.
332
+ def create_dimensions_shorthand! # :nodoc:
333
+ directions = ['top', 'right', 'bottom', 'left']
334
+
335
+ {'margin' => 'margin-%s',
336
+ 'padding' => 'padding-%s',
337
+ 'border-color' => 'border-%s-color',
338
+ 'border-style' => 'border-%s-style',
339
+ 'border-width' => 'border-%s-width'}.each do |property, expanded|
340
+
341
+ foldable = @declarations.select do |dim, val|
342
+ dim == expanded % 'top' or dim == expanded % 'right' or dim == expanded % 'bottom' or dim == expanded % 'left'
343
+ end
344
+ # All four dimensions must be present
345
+ if foldable.length == 4
346
+ values = {}
347
+
348
+ directions.each { |d| values[d.to_sym] = @declarations[expanded % d][:value].downcase.strip }
349
+
350
+ if values[:left] == values[:right]
351
+ if values[:top] == values[:bottom]
352
+ if values[:top] == values[:left] # All four sides are equal
353
+ new_value = values[:top]
354
+ else # Top and bottom are equal, left and right are equal
355
+ new_value = values[:top] + ' ' + values[:left]
356
+ end
357
+ else # Only left and right are equal
358
+ new_value = values[:top] + ' ' + values[:left] + ' ' + values[:bottom]
359
+ end
360
+ else # No sides are equal
361
+ new_value = values[:top] + ' ' + values[:right] + ' ' + values[:bottom] + ' ' + values[:left]
362
+ end
363
+
364
+ new_value.strip!
365
+ @declarations[property] = {:value => new_value.strip} unless new_value.empty?
366
+
367
+ # Delete the longhand values
368
+ directions.each { |d| @declarations.delete(expanded % d) }
369
+ end
370
+ end
371
+ end
372
+
373
+
374
+ # Looks for long format CSS font properties (e.g. <tt>font-weight</tt>) and
375
+ # tries to convert them into a shorthand CSS <tt>font</tt> property. All
376
+ # font properties must be present in order to create a shorthand declaration.
377
+ def create_font_shorthand! # :nodoc:
378
+ ['font-style', 'font-variant', 'font-weight', 'font-size',
379
+ 'line-height', 'font-family'].each do |prop|
380
+ return unless @declarations.has_key?(prop)
381
+ end
382
+
383
+ new_value = ''
384
+ ['font-style', 'font-variant', 'font-weight'].each do |property|
385
+ unless @declarations[property][:value] == 'normal'
386
+ new_value += @declarations[property][:value] + ' '
387
+ end
388
+ end
389
+
390
+ new_value += @declarations['font-size'][:value]
391
+
392
+ unless @declarations['line-height'][:value] == 'normal'
393
+ new_value += '/' + @declarations['line-height'][:value]
394
+ end
395
+
396
+ new_value += ' ' + @declarations['font-family'][:value]
397
+
398
+ @declarations['font'] = {:value => new_value.gsub(/[\s]+/, ' ').strip}
399
+
400
+ ['font-style', 'font-variant', 'font-weight', 'font-size',
401
+ 'line-height', 'font-family'].each do |prop|
402
+ @declarations.delete(prop)
403
+ end
404
+
405
+ end
406
+
407
+ private
408
+
409
+ # utility method for re-assign shorthand elements to longhand versions
410
+ def split_declaration(src, dest, v) # :nodoc:
411
+ return unless v and not v.empty?
412
+
413
+ atts = @declarations[src]
414
+
415
+ if @declarations.has_key?(dest)
416
+ #puts "dest #{dest} already exists"
417
+
418
+ if @declarations[dest][:order] > @declarations[src][:order]
419
+ #puts "skipping #{dest}:#{v} due to order "
420
+ return
421
+ else
422
+ @declarations[dest] = {}
423
+ end
424
+ end
425
+ @declarations[dest] = @declarations[src].merge({:value => v.to_s.strip})
426
+ end
427
+
428
+ def parse_declarations!(block) # :nodoc:
429
+ @declarations = {}
430
+
431
+ return unless block
432
+
433
+ block.gsub!(/(^[\s]*)|([\s]*$)/, '')
434
+
435
+ block.split(/[\;$]+/m).each do |decs|
436
+ if matches = decs.match(/(.[^:]*)\:(.[^;]*)(;|\Z)/i)
437
+ property, value, end_of_declaration = matches.captures
438
+
439
+ add_declaration!(property, value)
440
+ end
441
+ end
442
+ end
443
+
444
+ #--
445
+ # TODO: way too simplistic
446
+ #++
447
+ def parse_selectors!(selectors) # :nodoc:
448
+ @selectors = selectors.split(',')
449
+ end
450
+ end
451
+ end