DanaDanger-css_parser 0.9.1
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.
- data/CHANGELOG +9 -0
- data/LICENSE +21 -0
- data/README +58 -0
- data/lib/css_parser.rb +149 -0
- data/lib/css_parser/parser.rb +345 -0
- data/lib/css_parser/regexps.rb +46 -0
- data/lib/css_parser/rule_set.rb +383 -0
- data/test/fixtures/import-circular-reference.css +4 -0
- data/test/fixtures/import-with-media-types.css +3 -0
- data/test/fixtures/import1.css +3 -0
- data/test/fixtures/simple.css +6 -0
- data/test/fixtures/subdir/import2.css +3 -0
- data/test/test_css_parser_basic.rb +56 -0
- data/test/test_css_parser_downloading.rb +81 -0
- data/test/test_css_parser_media_types.rb +71 -0
- data/test/test_css_parser_misc.rb +143 -0
- data/test/test_css_parser_regexps.rb +68 -0
- data/test/test_helper.rb +8 -0
- data/test/test_merging.rb +88 -0
- data/test/test_rule_set.rb +74 -0
- data/test/test_rule_set_creating_shorthand.rb +90 -0
- data/test/test_rule_set_expanding_shorthand.rb +178 -0
- metadata +87 -0
@@ -0,0 +1,46 @@
|
|
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) #[^\0-\177]
|
6
|
+
RE_UNICODE = Regexp.new('(\\\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])*)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE)
|
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)
|
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)
|
16
|
+
URI_RX = /url\(("([^"]*)"|'([^']*)'|([^)]*))\)/im
|
17
|
+
|
18
|
+
# Initial parsing
|
19
|
+
RE_AT_IMPORT_RULE = /\@import[\s]+(url\()?["']+(.[^'"]*)["']\)?([\w\s\,]*);?/i
|
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)", 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
|
+
|
36
|
+
# Patterns for specificity calculations
|
37
|
+
ELEMENTS_AND_PSEUDO_ELEMENTS_RX = /((^|[\s\+\>]+)[\w]+|\:(first\-line|first\-letter|before|after))/i
|
38
|
+
NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = /(\.[\w]+)|(\[[\w]+)|(\:(link|first\-child|lang))/i
|
39
|
+
|
40
|
+
# Colours
|
41
|
+
RE_COLOUR_RGB = Regexp.new('(rgb[\s]*\([\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*\))', Regexp::IGNORECASE)
|
42
|
+
RE_COLOUR_HEX = /(#([0-9a-f]{6}|[0-9a-f]{3})([\s;]|$))/i
|
43
|
+
RE_COLOUR_NAMED = /([\s]*^)?(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|transparent)([\s]*$)?/i
|
44
|
+
RE_COLOUR = Regexp.union(RE_COLOUR_RGB, RE_COLOUR_HEX, RE_COLOUR_NAMED)
|
45
|
+
# :startdoc:
|
46
|
+
end
|
@@ -0,0 +1,383 @@
|
|
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
|
+
parse_selectors!(selectors) if selectors
|
18
|
+
parse_declarations!(block)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# Get the value of a property
|
23
|
+
def get_value(property)
|
24
|
+
return '' unless property and not property.empty?
|
25
|
+
|
26
|
+
property = property.downcase.strip
|
27
|
+
properties = @declarations.inject('') do |val, (key, data)|
|
28
|
+
#puts "COMPARING #{key} #{key.inspect} against #{property} #{property.inspect}"
|
29
|
+
importance = data[:is_important] ? ' !important' : ''
|
30
|
+
val << "#{data[:value]}#{importance}; " if key.downcase.strip == property
|
31
|
+
val
|
32
|
+
end
|
33
|
+
return properties ? properties.strip : ''
|
34
|
+
end
|
35
|
+
alias_method :[], :get_value
|
36
|
+
|
37
|
+
# Add a CSS declaration to the current RuleSet.
|
38
|
+
#
|
39
|
+
# rule_set.add_declaration!('color', 'blue')
|
40
|
+
#
|
41
|
+
# puts rule_set['color']
|
42
|
+
# => 'blue;'
|
43
|
+
#
|
44
|
+
# rule_set.add_declaration!('margin', '0px auto !important')
|
45
|
+
#
|
46
|
+
# puts rule_set['margin']
|
47
|
+
# => '0px auto !important;'
|
48
|
+
#
|
49
|
+
# If the property already exists its value will be over-written.
|
50
|
+
def add_declaration!(property, value)
|
51
|
+
value.gsub!(/;\Z/, '')
|
52
|
+
is_important = !value.gsub!(CssParser::IMPORTANT_IN_PROPERTY_RX, '').nil?
|
53
|
+
property = property.downcase.strip
|
54
|
+
#puts "SAVING #{property} #{value} #{is_important.inspect}"
|
55
|
+
@declarations[property] = {:value => value, :is_important => is_important}
|
56
|
+
end
|
57
|
+
alias_method :[]=, :add_declaration!
|
58
|
+
|
59
|
+
# Iterate through selectors.
|
60
|
+
#
|
61
|
+
# Options
|
62
|
+
# - +force_important+ -- boolean
|
63
|
+
#
|
64
|
+
# ==== Example
|
65
|
+
# ruleset.each_selector do |sel, dec, spec|
|
66
|
+
# ...
|
67
|
+
# end
|
68
|
+
def each_selector(options = {}) # :yields: selector, declarations, specificity
|
69
|
+
declarations = declarations_to_s(options)
|
70
|
+
if @specificity
|
71
|
+
@selectors.each { |sel| yield sel.strip, declarations, @specificity }
|
72
|
+
else
|
73
|
+
@selectors.each { |sel| yield sel.strip, declarations, CssParser.calculate_specificity(sel) }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Iterate through declarations.
|
78
|
+
def each_declaration # :yields: property, value, is_important
|
79
|
+
@declarations.each do |property, data|
|
80
|
+
value = data[:value]
|
81
|
+
#value += ' !important' if data[:is_important]
|
82
|
+
yield property.downcase.strip, value.strip, data[:is_important]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Return all declarations as a string.
|
87
|
+
#--
|
88
|
+
# TODO: Clean-up regexp doesn't seem to work
|
89
|
+
#++
|
90
|
+
def declarations_to_s(options = {})
|
91
|
+
options = {:force_important => false}.merge(options)
|
92
|
+
str = ''
|
93
|
+
each_declaration do |prop, val, is_important|
|
94
|
+
importance = (options[:force_important] || is_important) ? ' !important' : ''
|
95
|
+
str += "#{prop}: #{val}#{importance}; "
|
96
|
+
end
|
97
|
+
str.gsub(/^[\s]+|[\n\r\f\t]*|[\s]+$/mx, '').strip
|
98
|
+
end
|
99
|
+
|
100
|
+
# Return the CSS rule set as a string.
|
101
|
+
def to_s
|
102
|
+
decs = declarations_to_s
|
103
|
+
"#{@selectors} { #{decs} }"
|
104
|
+
end
|
105
|
+
|
106
|
+
# Split shorthand declarations (e.g. +margin+ or +font+) into their constituent parts.
|
107
|
+
def expand_shorthand!
|
108
|
+
expand_dimensions_shorthand!
|
109
|
+
expand_font_shorthand!
|
110
|
+
expand_background_shorthand!
|
111
|
+
end
|
112
|
+
|
113
|
+
# Create shorthand declarations (e.g. +margin+ or +font+) whenever possible.
|
114
|
+
def create_shorthand!
|
115
|
+
create_background_shorthand!
|
116
|
+
create_dimensions_shorthand!
|
117
|
+
create_font_shorthand!
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
def parse_declarations!(block) # :nodoc:
|
122
|
+
@declarations = {}
|
123
|
+
|
124
|
+
return unless block
|
125
|
+
|
126
|
+
block.gsub!(/(^[\s]*)|([\s]*$)/, '')
|
127
|
+
|
128
|
+
block.split(/[\;$]+/m).each do |decs|
|
129
|
+
if matches = decs.match(/(.[^:]*)\:(.[^;]*)(;|\Z)/i)
|
130
|
+
property, value, end_of_declaration = matches.captures
|
131
|
+
|
132
|
+
add_declaration!(property, value)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
#--
|
138
|
+
# TODO: way too simplistic
|
139
|
+
#++
|
140
|
+
def parse_selectors!(selectors) # :nodoc:
|
141
|
+
@selectors = selectors.split(',')
|
142
|
+
end
|
143
|
+
|
144
|
+
# Split shorthand dimensional declarations (e.g. <tt>margin: 0px auto;</tt>)
|
145
|
+
# into their constituent parts.
|
146
|
+
def expand_dimensions_shorthand! # :nodoc:
|
147
|
+
['margin', 'padding'].each do |property|
|
148
|
+
|
149
|
+
next unless @declarations.has_key?(property)
|
150
|
+
|
151
|
+
value = @declarations[property][:value]
|
152
|
+
is_important = @declarations[property][:is_important]
|
153
|
+
t, r, b, l = nil
|
154
|
+
|
155
|
+
matches = value.scan(CssParser::BOX_MODEL_UNITS_RX)
|
156
|
+
|
157
|
+
case matches.length
|
158
|
+
when 1
|
159
|
+
t, r, b, l = matches[0][0], matches[0][0], matches[0][0], matches[0][0]
|
160
|
+
when 2
|
161
|
+
t, b = matches[0][0], matches[0][0]
|
162
|
+
r, l = matches[1][0], matches[1][0]
|
163
|
+
when 3
|
164
|
+
t = matches[0][0]
|
165
|
+
r, l = matches[1][0], matches[1][0]
|
166
|
+
b = matches[2][0]
|
167
|
+
when 4
|
168
|
+
t = matches[0][0]
|
169
|
+
r = matches[1][0]
|
170
|
+
b = matches[2][0]
|
171
|
+
l = matches[3][0]
|
172
|
+
end
|
173
|
+
|
174
|
+
@declarations["#{property}-top"] = {:value => t.to_s, :is_important => is_important}
|
175
|
+
@declarations["#{property}-right"] = {:value => r.to_s, :is_important => is_important}
|
176
|
+
@declarations["#{property}-bottom"] = {:value => b.to_s, :is_important => is_important}
|
177
|
+
@declarations["#{property}-left"] = {:value => l.to_s, :is_important => is_important}
|
178
|
+
@declarations.delete(property)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Convert shorthand font declarations (e.g. <tt>font: 300 italic 11px/14px verdana, helvetica, sans-serif;</tt>)
|
183
|
+
# into their constituent parts.
|
184
|
+
def expand_font_shorthand! # :nodoc:
|
185
|
+
return unless @declarations.has_key?('font')
|
186
|
+
|
187
|
+
font_props = {}
|
188
|
+
|
189
|
+
# reset properties to 'normal' per http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
|
190
|
+
['font-style', 'font-variant', 'font-weight', 'font-size',
|
191
|
+
'line-height'].each do |prop|
|
192
|
+
font_props[prop] = 'normal'
|
193
|
+
end
|
194
|
+
|
195
|
+
value = @declarations['font'][:value]
|
196
|
+
is_important = @declarations['font'][:is_important]
|
197
|
+
|
198
|
+
in_fonts = false
|
199
|
+
|
200
|
+
matches = value.scan(/("(.*[^"])"|'(.*[^'])'|(\w[^ ,]+))/)
|
201
|
+
matches.each do |match|
|
202
|
+
m = match[0].to_s.strip
|
203
|
+
m.gsub!(/[;]$/, '')
|
204
|
+
|
205
|
+
if in_fonts
|
206
|
+
if font_props.has_key?('font-family')
|
207
|
+
font_props['font-family'] += ', ' + m
|
208
|
+
else
|
209
|
+
font_props['font-family'] = m
|
210
|
+
end
|
211
|
+
elsif m =~ /normal|inherit/i
|
212
|
+
['font-style', 'font-weight', 'font-variant'].each do |font_prop|
|
213
|
+
font_props[font_prop] = m unless font_props.has_key?(font_prop)
|
214
|
+
end
|
215
|
+
elsif m =~ /italic|oblique/i
|
216
|
+
font_props['font-style'] = m
|
217
|
+
elsif m =~ /small\-caps/i
|
218
|
+
font_props['font-variant'] = m
|
219
|
+
elsif m =~ /[1-9]00$|bold|bolder|lighter/i
|
220
|
+
font_props['font-weight'] = m
|
221
|
+
elsif m =~ CssParser::FONT_UNITS_RX
|
222
|
+
if m =~ /\//
|
223
|
+
font_props['font-size'], font_props['line-height'] = m.split('/')
|
224
|
+
else
|
225
|
+
font_props['font-size'] = m
|
226
|
+
end
|
227
|
+
in_fonts = true
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
font_props.each { |font_prop, font_val| @declarations[font_prop] = {:value => font_val, :is_important => is_important} }
|
232
|
+
|
233
|
+
@declarations.delete('font')
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
# Convert shorthand background declarations (e.g. <tt>background: url("chess.png") gray 50% repeat fixed;</tt>)
|
238
|
+
# into their constituent parts.
|
239
|
+
#
|
240
|
+
# See http://www.w3.org/TR/CSS21/colors.html#propdef-background
|
241
|
+
def expand_background_shorthand! # :nodoc:
|
242
|
+
return unless @declarations.has_key?('background')
|
243
|
+
|
244
|
+
value = @declarations['background'][:value]
|
245
|
+
is_important = @declarations['background'][:is_important]
|
246
|
+
|
247
|
+
bg_props = {}
|
248
|
+
|
249
|
+
|
250
|
+
if m = value.match(Regexp.union(CssParser::URI_RX, /none/i)).to_s
|
251
|
+
bg_props['background-image'] = m.strip unless m.empty?
|
252
|
+
value.gsub!(Regexp.union(CssParser::URI_RX, /none/i), '')
|
253
|
+
end
|
254
|
+
|
255
|
+
if m = value.match(/([\s]*^)?(scroll|fixed)([\s]*$)?/i).to_s
|
256
|
+
bg_props['background-attachment'] = m.strip unless m.empty?
|
257
|
+
end
|
258
|
+
|
259
|
+
if m = value.match(/([\s]*^)?(repeat(\-x|\-y)*|no\-repeat)([\s]*$)?/i).to_s
|
260
|
+
bg_props['background-repeat'] = m.strip unless m.empty?
|
261
|
+
end
|
262
|
+
|
263
|
+
if m = value.match(CssParser::RE_COLOUR).to_s
|
264
|
+
bg_props['background-color'] = m.strip unless m.empty?
|
265
|
+
end
|
266
|
+
|
267
|
+
value.scan(CssParser::RE_BACKGROUND_POSITION).each do |m|
|
268
|
+
if bg_props.has_key?('background-position')
|
269
|
+
bg_props['background-position'] += ' ' + m[0].to_s.strip unless m.empty?
|
270
|
+
else
|
271
|
+
bg_props['background-position'] = m[0].to_s.strip unless m.empty?
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
if value =~ /([\s]*^)?inherit([\s]*$)?/i
|
277
|
+
['background-color', 'background-image', 'background-attachment', 'background-repeat', 'background-position'].each do |prop|
|
278
|
+
bg_props["#{prop}"] = 'inherit' unless bg_props.has_key?(prop) and not bg_props[prop].empty?
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
bg_props.each { |bg_prop, bg_val| @declarations[bg_prop] = {:value => bg_val, :is_important => is_important} }
|
283
|
+
|
284
|
+
@declarations.delete('background')
|
285
|
+
end
|
286
|
+
|
287
|
+
|
288
|
+
# Looks for long format CSS background properties (e.g. <tt>background-color</tt>) and
|
289
|
+
# converts them into a shorthand CSS <tt>background</tt> property.
|
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)
|
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
|
+
# Looks for long format CSS dimensional properties (i.e. <tt>margin</tt> and <tt>padding</tt>) and
|
306
|
+
# converts them into shorthand CSS properties.
|
307
|
+
def create_dimensions_shorthand! # :nodoc:
|
308
|
+
# geometric
|
309
|
+
directions = ['top', 'right', 'bottom', 'left']
|
310
|
+
['margin', 'padding'].each do |property|
|
311
|
+
values = {}
|
312
|
+
|
313
|
+
foldable = @declarations.select { |dim, val| dim == "#{property}-top" or dim == "#{property}-right" or dim == "#{property}-bottom" or dim == "#{property}-left" }
|
314
|
+
# All four dimensions must be present
|
315
|
+
if foldable.length == 4
|
316
|
+
values = {}
|
317
|
+
|
318
|
+
directions.each { |d| values[d.to_sym] = @declarations["#{property}-#{d}"][:value].downcase.strip }
|
319
|
+
|
320
|
+
if values[:left] == values[:right]
|
321
|
+
if values[:top] == values[:bottom]
|
322
|
+
if values[:top] == values[:left] # All four sides are equal
|
323
|
+
new_value = values[:top]
|
324
|
+
else # Top and bottom are equal, left and right are equal
|
325
|
+
new_value = values[:top] + ' ' + values[:left]
|
326
|
+
end
|
327
|
+
else # Only left and right are equal
|
328
|
+
new_value = values[:top] + ' ' + values[:left] + ' ' + values[:bottom]
|
329
|
+
end
|
330
|
+
else # No sides are equal
|
331
|
+
new_value = values[:top] + ' ' + values[:right] + ' ' + values[:bottom] + ' ' + values[:left]
|
332
|
+
end # done creating 'new_value'
|
333
|
+
|
334
|
+
# Save the new value
|
335
|
+
unless new_value.strip.empty?
|
336
|
+
@declarations[property] = {:value => new_value.gsub(/[\s]+/, ' ').strip}
|
337
|
+
end
|
338
|
+
|
339
|
+
# Delete the shorthand values
|
340
|
+
directions.each { |d| @declarations.delete("#{property}-#{d}") }
|
341
|
+
end
|
342
|
+
end # done iterating through margin and padding
|
343
|
+
end
|
344
|
+
|
345
|
+
|
346
|
+
# Looks for long format CSS font properties (e.g. <tt>font-weight</tt>) and
|
347
|
+
# tries to convert them into a shorthand CSS <tt>font</tt> property. All
|
348
|
+
# font properties must be present in order to create a shorthand declaration.
|
349
|
+
def create_font_shorthand! # :nodoc:
|
350
|
+
['font-style', 'font-variant', 'font-weight', 'font-size',
|
351
|
+
'line-height', 'font-family'].each do |prop|
|
352
|
+
return unless @declarations.has_key?(prop)
|
353
|
+
end
|
354
|
+
|
355
|
+
new_value = ''
|
356
|
+
['font-style', 'font-variant', 'font-weight'].each do |property|
|
357
|
+
unless @declarations[property][:value] == 'normal'
|
358
|
+
new_value += @declarations[property][:value] + ' '
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
new_value += @declarations['font-size'][:value]
|
363
|
+
|
364
|
+
unless @declarations['line-height'][:value] == 'normal'
|
365
|
+
new_value += '/' + @declarations['line-height'][:value]
|
366
|
+
end
|
367
|
+
|
368
|
+
new_value += ' ' + @declarations['font-family'][:value]
|
369
|
+
|
370
|
+
@declarations['font'] = {:value => new_value.gsub(/[\s]+/, ' ').strip}
|
371
|
+
|
372
|
+
['font-style', 'font-variant', 'font-weight', 'font-size',
|
373
|
+
'line-height', 'font-family'].each do |prop|
|
374
|
+
@declarations.delete(prop)
|
375
|
+
end
|
376
|
+
|
377
|
+
end
|
378
|
+
|
379
|
+
|
380
|
+
|
381
|
+
|
382
|
+
end
|
383
|
+
end
|