kajabi-css_parser 1.2.7

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