marcosinger-css_parser 1.3.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.
@@ -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